Разработка собственного плагина для сервера Minecraft
Еще с детства я начал покорять бесконечные просторы Minecraft. Естественно о разработке в то время никакой речи не шло. Но с недавних пор загорелся идеей создать о свой проект серверов.
На Java до этого никогда не писал, но есть бекграунд на других языках, поэтому осталось только приспособиться. Соотвественно разработка плагинов, Bukkit и другие библиотеки вижу впервые, но посмотрев несколько туторов, стала понятна примерная концепция.
Ранее писал на таких языках как PHP, JS. В данный момент веду разработку на языке Go. Сильно привык к «гошке» и его синтаксису и в процессе написания плагина часто использовал синтаксис Go для написания логических конструкций.
Мне не сильно хотелось использовать какие-то готовые решения, ведь тогда не будет углубленных знаний, которые я получу в процессе написания кода. Хочется одновременно и поучить Java и написать что-то свое (самое главное).
В этой статье я не буду затрагивать процесс настройки окружения, установки IDE и стороннего софта.
Идея плагина
На серверах часто используются постройки, находящиеся в пустоте, например летающие лобби, острова. Такую модель постройки мы выбрали вместе с моим другом: летающие острова. Одной из проблем таких построек — Игрок может провалиться в пустоту и не выбраться.
Прошерстив Google мне удалось найти парочку подходящих плагинов, которые уже решают это проблему. Но один из них, который оказался поддерживаем разработчиком и самими ядром сервера, предоставлял ограниченный функционал, расширенный можно было приобрести на X евро. Фича, которая мне понравилась в платном плагине — создание анимаций из частиц после телепортации из пустоты.
Мне захотелось самому понять, как это сделать, разработать собственный плагин, а потом с удовольствием им пользоваться, поддерживать, находить баги — мое мелкое детище, как никак.
Создаем сам плагин
Назвал я плагин просто — VoidTeleport.
Первым делом создал класс для управления конфигурацией плагина.
public class Config < private static File file; private static FileConfiguration config; private static final String fileNameConfig = "config.yml"; /** * Initializes the static Config class. */ public static void init() < // Получаем инстанс нашего плагина. Plugin plugin = Bukkit.getServer().getPluginManager().getPlugin(VoidTeleport.PluginName); if (plugin == null) < // На этом моменте что-то пошло не так, // нужно обработать и залогировать. Bukkit.getLogger().log( Level.WARNING, MessageFormat.format("Cannot get plugin ", VoidTeleport.PluginName) ); return; > file = new File(plugin.getDataFolder(), fileNameConfig); // Мы не знаем существует ли файл, поэтому пытаемся создать его. // Если файл уже есть, то выражение file.createNewFile() вернет false. try < if (file.createNewFile()) < plugin.getLogger().log( Level.INFO, MessageFormat.format("New config file with name was created", fileNameConfig) ); > > catch (IOException e) < plugin.getLogger().log(Level.SEVERE, e.toString()); return; >// На данно моменте наш конфиг пустой, // поэтому подгружаем его из файла. reload(); > /** * Getter * @return FileConfiguration */ public static FileConfiguration get() < return config; >public static void reload() < // Самый простой анмаршаллер YAML из файла. config = YamlConfiguration.loadConfiguration(file); >
Отлично! Класс для работы с конфигом уже есть, теперь нужно определиться со структурой файла config.yml . Нужно реализовать поддержку для разных миров, поэтому не придумал ничего проще, как просто указать список нужных миров.
worlds: # Наименование мира, например spawn, world, world_the_end - name: spawn # Координаты для респавна игрока при падении в пустоту spawnLocation: x: 0 y: 0 z: 0
Конфиг есть, теперь можно приступить к созданию обработчика событий. Мой выбор пал на событие EntityDamageByBlockEvent. Можно было бы и слушать событие PlayerMoveEvent, но оно случается гораздо чаще, чем триггер на получение урона. Лишняя нагрузка на сервер не нужна, поэтому стал слушать урон.
public class PlayerDamageListener implements Listener < // Хеш мапа в которой хранится наименования мира и точка телепортации. private HashMapworlds = new HashMap<>(); @EventHandler public void onPlayerDamage(EntityDamageByBlockEvent e) < if (!(e.getEntity() instanceof Player)) < // Это не игрок. return; >if (e.getCause() != EntityDamageEvent.DamageCause.VOID) < // Урон не от пустоты. return; >Player player = (Player) e.getEntity(); // Получаем мир, в котором находится Игрок. World world = player.getWorld(); // Пытаемся найти в хеш мапе значение по наименованию мира. Location spawnLocation = this.worlds.get(world.getName()); if (spawnLocation == null) < // К этому миру не действует правило телепорта. return; >// Данный код является костылем, который я быстро сообразил. // Проблема в том, что мир может быть = null. // В таком случае устанавливаем мир на тот, в котором находится игрок. if (spawnLocation.getWorld() == null) < spawnLocation.setWorld(world); >// Добрались до самого главного. // Отменяем событие, которое наносит урон игроку. e.setCancelled(true); // Отменяем сам урон от падения, // чтобы при телепортации игрок не разбился. player.setFallDistance(0); // Телепортируем игрока. player.teleport(spawnLocation); // Доабвляем анимацию из частиц при попадании на точку телепортации. Spiral.spawn(player); > @SuppressWarnings("unchecked") public void updateWorlds(@Nullable ArrayList> listWorlds) < if (listWorlds == null) < // Ну если null, так null - ничего не делаем. return; >// Очищаем мапу. this.worlds = new HashMap<>(); for (HashMap world: listWorlds) < String worldName = (String) world.get("name"); if (Objects.equals(worldName, "")) < // Тут хорошо бы залогировать, но просто скипаем. continue; >Location spawnLocation = Location.deserialize((Map) world.get("spawnLocation")); // Т.к. мир у нас не указан, поэтому получаем его. spawnLocation.setWorld(Bukkit.getWorld(worldName)); // Сохраняем в хеш мапу. this.worlds.put(worldName, spawnLocation); > > >
Тепер разберем вызов эффекта анимации при телепортации Spiral.spawn(player) . Назвал класс Spiral, потому что эффект будет в виде спирали.
Т.к. это мой первый плагин, то не стал заморачиваться с Пакетами и ProtocolLib.
Описываем анимацию в отдельном классе Spiral. Я предпочел реализовать спираль под названием Helix — достаточно простая в реализации модель. Пришлось немного вспомнить тригонометрию, но у меня получилось!
public class Spiral < public static void spawn(@NotNull Player player) < Location location = player.getLocation(); // Задаем радиут спирали. double radius = 0.5; for (double y = 0; y catch (InterruptedException e) < Bukkit.getLogger().log(Level.SEVERE, e.toString()); >> > >
Почему в коде 23? Это число является ограничением для координаты y. Т.е. по сути спираль будет подниматься вверх на y = 2.3 . Как можно заметить, при указании смещения particleLocation.add(x, y / 10, z) y делится на 10. Еще одной причиной стало то, что спираль не успевает несколько раз «обернуть» игрока.
Собираем все вместе
Наконец можем собрать наш код в единой точке и протестировать, что получилось.
public final class VoidTeleport extends JavaPlugin < public static final String PluginName = "VoidTeleport"; @Override public void onEnable() < getLogger().log(Level.INFO, "Plugin enabled!"); // Инициализируем конфиг Config.init(); // Регистрируем обработчик событий для входщего урона this.registerDamageEvent(); >@Override public void onDisable() < getLogger().log(Level.INFO, "Plugin disabled!"); >@SuppressWarnings("unchecked") private void registerDamageEvent() < // Инициализируем обработчик PlayerDamageListener damageListener = new PlayerDamageListener(); // Достаем из конфига нужные значения и обновляем хеш мапу в обработчике damageListener.updateWorlds((ArrayList>) Config.get().get("worlds")); // Регистрируем новое событие на сервере getServer().getPluginManager().registerEvents(damageListener, this); > >
Результат
При заданным настройкам файле конфигурации мы успешно попадаем в указанную точки и наблюдаем просто классную анимацию, как по мне.
И без указания мира в конфиге.
Можно посмотреть код этого плагина в моем репозитории Github.
Скачать можно последний релиз.
[Урок][Глава 1] Учимся писать плагин для сервера
Вводим там следующие пункты
1. name — Название вашего проекта
2. main — Название.main.Название — Будет использоваться чтобы использовать плагин на сервере
3. version — версия плагина.
Далее мы создаем Package в файле scr и в Package создаем Class. Все файлы называть как название плагина.
Когда вы создадите файл, вы должны добавить к 2 строке extends JavaPlugin, затем, навести на него курсор и нажать импорт. После чего, у вас появится строка с импортом для работы плагина
И так. Дабы не томить вас нудными скринами, составил вам сразу готовый код выдачи лога в консоль. Давайте же разберемся, а не тупо спишем.
private Logger Log — Создает лог плагина, после чего, идут сообщения в консоль.
public void onEnable () < - Данное действие при запуске консоли(сервера), будем информировать нас и включать плагин
getLogger().info(«Enabled!») — Благодаря этому, в консоль будет выводиться сообщение «Enabled», когда плагин будет включен
log.info(«MAIN LOGGER») — Включается когда запускается консоль
log.warning(«Warning») — Включается когда имеется баг
log.error(«Error») — Включается, когда идет фатальная ошибка (полностью отключается плагин)
Все это выше выводит всё в консоль
public void onDisable() < - Данное действие будет выключать всё при выключении сервера
getLogger().info(«Disabled») — Выключение плагина. Выводит сообщение в консоль
Вы наверное спросите, зачем мы пишем < и >> Несколько раз?
Дело в том, что эти скобочки, открывают взаимодействие с пабликом. Если после ввода public void onEnable() поставить Чтобы закрыть паблик, нужно ввести >. Чтобы полностью закончить код, нужно в самом конце и в самом начале строке, написать еще раз >. Это означает, что паблик и код завершены
В сегодняшнем уроке, мы научились выставлять сообщение в консоль о запуске/выключении консоли.
Если у вас есть вопросы, обращайтесь ко мне в ВК — vk.com/vovakostikov
Спасибо за просмотр новости
Делаем плагины на Python
Многие программы поддерживают так называемые плагины (дополнение, расширения и т.п.), с помощью которых можно расширять функциональность программы. На Python делать программы, поддерживающие плагины особенно легко и приятно. Потому что с одной стороны в качестве плагина могут выступать полноценные классы, а с другой стороны благодаря кроссплатформенности языка Python плагины так же остаются кроссплатформенными. Давайте посмотрим что нужно сделать, чтобы ваша программа тоже поддерживала плагины. Заодно убедимся как это легко.
Первый пример. Основные функции
Вначале давайте договоримся о структуре тестовых примеров. Модуль основной программы будет называться main.py, а плагины будут лежать в папке plugins, располагающейся рядом с этим файлом. Чтобы Python принял plugins за пакет, в нем должен находиться файл с именем __init__.py (в нашем случае он будет пустым).
Сначала представим, что динамически во время выполнения программы нам не нужно узнавать имя плагина и мы его знаем на этапе программирования. Пусть плагин имеет имя my_plugin.py и располагается в папке plugins. И пусть внутри файла my_plugin.py находится класс pluginClass, который содержит всю функциональность плагина. Вот его код:
class pluginClass ( object ) :
def __init__ ( self ) :
pass
def run ( self ) :
print «Hello, Plug-in!»
В конечном итоге нам необходимо во время выполнения программы добраться до этого класса, создать его экземпляр и выполнить метод run. Для простоты на первое время договоримся, что в этом модуле нет других классов кроме pluginClass. Как бы мы поступили, если бы все имена (и модуля, и класса) были бы доступны во время программирования? Скорее всего вот так:
import plugins. my_plugin
cls = plugins. my_plugin . pluginClass ( )
cls. run ( )
И в результате получили бы сообщение «Hello, Plug-in!». А теперь вернемся к нашей задаче. Нам нужно сделать то же самое, но при этом имя модуля my_plugin и имя класса pluginClass хранится в соответствующих строковых переменных.
Импорт модуля плагина
Аналогом встроенной директивы import является функция __import__ , она позволяет импортировать модули, имена которых на этапе написания программы неизвестны. У функции __import__ пять параметров, но обязательным является только первый. Необязательные параметры в данной статье мы использовать не будем, поэтому про них умолчим. Итак, единственный обязательный параметр — это имя пакета или модуля, который мы хотим импортировать. Если импорт пройдет удачно, функция возвратит экземпляр класса, который хранит все импортированные элементы.
Начнем с импортирования модуля. Директива import нам не поможет. Зато мы можем воспользоваться функцией __import__ . Аналогом первой строки из записанного выше примера будет следующий код:
package_obj = __import__ ( «plugins.my_plugin» )
После этого переменная package_obj станет экземпляром класса загруженного модуля (пакета) plugins. Чтобы убедиться в этом выполним команду
print package_obj
При этом мы получим что-то вроде (путь, разумеется, может быть другой):
Это сообщение мало информативно, поэтому применим к переменной package_obj встроенную функцию dir, которая возвращает имеющиеся в package_obj атрибуты. Итак, выполним следующий код:
print dir ( package_obj )
В результате на экран выведется такой список:
[‘__builtins__’, ‘__doc__’, ‘__file__’, ‘__name__’, ‘__path__’, ‘my_plugin’]
Обратите внимание на последний элемент списка — это и есть наш плагин. Итак, пакет мы загрузили, но как нам добраться до модуля нашего плагина? Для этого сначала воспользуемся встроенной функцией getattr, которая позволяет получить из модуля или пакета (в нашем случае package_obj) экземпляр класса атрибута (а нашем случае my_plugin). Эта функция принимает два параметра: соответственно экземпляр объекта, атрибут которого надо получить и строковую переменную, которая содержит имя атрибута. Применяя функцию getattr на пакет, в случае успеха мы получим экземпляр загруженного модуля. Выполним следующий код:
module_obj = getattr ( package_obj , «my_plugin» )
print module_obj
Если все прошло удачно, на экране мы увидим примерно такой результат:
Но скорее всего в программе придется загружать не один плагин, а несколько. Как в этом случае поведет себя функция __import__ ? Рассмотрим пример, в котором загружаются два плагина (все они должны находиться в папке plugins.
modulename1 = «my_plugin_1»
modulename2 = «my_plugin_2»
classname = «pluginClass1»
package_obj = __import__ ( «plugins.» + modulename1 )
package_obj = __import__ ( «plugins.» + modulename2 )
print dir ( package_obj )
В результате на экране вы увидите следующий результат:
[‘__builtins__’, ‘__doc__’, ‘__file__’, ‘__name__’, ‘__path__’, ‘my_plugin_1’, ‘my_plugin_2’]
В этом примере результат импорта мы каждый раз присваиваем одной и той же переменной. Но в результате после каждой операции импорта в нее добавляется новый импортированный модуль. Скачать этот пример можно по адресу — test1_1.zip.
Получаем доступ к классу
Итак, модуль my_plugin мы загрузили. Осталось добраться до класса, который содержится внутри. Для этого воспользуемся уже знакомой нам функцией dir и убедимся, внутри действительно хранится наш класс:
print dir ( module_obj )
В результате выполнения этого кода получим:
Как написать мод для minecraft на python
[Programming] [Перезалив] Пишем программы на Python. Урок 1.
Python — один из самых простых языков в мире, и активно используется для разработки игр, сайтов и даже мобильных приложений. Google почти весь построен на нем. Он развивает популярность все больше и больше и в конце случится взрыв. Python развивается больше и больше, и имеет огромное число своих юзеров, а также свое комьюнити. Язык прекрасно подойдет вам. если вы хотите научиться программить и не знаете на чем.
Прошу не пинать, так как я не супер-профи-мега-очень ни в Python, ни в составление новостей.
Давайте попробуем установить Python и написать первую простенькую программку.
Я пишу на PYTHON 2.7
Если вы сидите на Windows то открываем python.org и скачиваем с раздела Download нашу версию.
С Ubuntu же надо просто скачать Package в библиотеке пакетов Юбунды)
А вообще советую почитать на тему установки Python в инете, если у кого-то не получилось.
Вот, вы все установили и готовы писать вашу первую программу!
Открываем IDLE безинтерфейса и пишем:
Print — функция языка, и она пише в нашу консоль все, что мы вставили скобки, вы может поэкспереминтировать, вставив вместо Hello World любое русское или английское слово.
В каждом языке есть переменные. обычно они управляют всем миром языком.
Если быть точным, переменные — это ячейки памяти в которых что-то сохраняется.
Давайте попробуем сохранить в переменную некое случайное число.
Для этого нам потребуется модуль random
Во многих языках есть комментарии. В Python они тоже присуствуют, и сделаны они ради того чтобы нашему юному разрабу не забыть что он, собственно говоря, кодил.
Выделяются они в данном языке программинга красным цветом и пишутся на одной строке после #(Решетки).
А на этой простенькой программе мы заканчиваем кодить. Люди, я реально не знаю как еще лучше объяснить, простите если что-то не так.
Майнкрафт – одна из популярнейших и одна из самых продаваемых видеоигр за всю историю, собравшая вокруг себя огромнейшее игровое сообщество и предлагающая множество возможностей в обучении программированию.
Большое количество людей по всему миру используют Майнкрафт в образовательных целях: начиная с курсов в университетах, посвященных изучению и разработке искусственного интеллекта, заканчивая школами и летними лагерями. Эта популярная компьютерная игра также используется в изучении таких дисциплин, как химия, физика, математика, и даже история и многих других!
Во время занятий ребята:
- познакомятся с языком программирования Python и игровой средой Майнкрафт;
- изучат синтаксис языка Python, структуру кода, условия и типы данных;
- научатся связывать программный код с объектами внутри игры;
- создадут свои миры в среде Майнкрафт, быстрее и лучше с Python;
- построят сложные структуры, выполнят трудоемкие и полезные задания с помощью алгоритмов на Python и Майнкрафт, не затрачивая на это много времени.
и многое другое!
Зачем ребенку изучать Python:
Занятия проводятся в игровом формате, который позволит детям не только научиться писать программы на Python, но также применять их в среде Майнкрафт и строить собственные миры!
Как мы понимаем, что учащийся освоил программу?
Во время обучения мы оцениваем как практическую сторону вопроса (как это сделать?), так и понимание темы (почему и зачем).
Ученики демонстрируют свои знания, решая задачи своим собственным способом, объясняя алгоритмы своими словами, помогая одногруппникам исправлять ошибки и создавая презентации по своим законченным проектам.

6000 рублей в месяц за 4 занятия
Продолжительность курса: 3 месяца
по субботам или воскресеньям, 1 раз в неделю
Есть такая замечательная игра — Minecraft. В целом очень напоминает Lego — можно строить из различных кубиков всё что душа пожелает. На Youtube можно найти много очень впечатляющих построек. Более того, строить можно совместно на специальных серверах.
В общем, в последнее время я довольно много играю в Minecraft
Одна из особенностей игры — процедурная генерация ландшафта. Т.е. в каком бы направлении Вы ни пошли, окружающий пейзаж будет создаваться по мере надобности. Общая площадь карты в Minecraft в несколько раз больше площади поверхности Земли. Сама карта подгружается частями — держать в памяти такое количество данных целиком просто бессмысленно. Недавно я от нечего делать решил написать парсер файлов Minecraft, чтобы сделать карту своей территории (я знаю что для этого есть инструменты, тот же c10t, но самому же всегда интереснее). Чтобы не было скучно, писать решил на Питоне
Вообще к Питону я приглядываюсь достаточно давно, и русскоязычное издание Learning Python лежит у меня под столом уже полгода. Но до сих пор ничего сложнее пробного hello world я на нём не писал. Парсер и картограф будут неплохим началом.
Формат файлов
Карта в Minecaft, как понятно из вышеприведённого описания, может быть очень большой. Поэтому она разбита на куски (chunks) размером 16 x 16 x 128 метров — этакие высокие колонны. Чанки сложены в директории по хешу их координат. В целом всё выглядит так: в корневой папке лежит основной файл level.dat, в котором хранится в т.ч. random seed для генерации ландшафта, и 64 папки верхнего уровня. В каждой из них находится по 64 папки второго уровня, в которых и лежат gzip-нутые файлы чанков.
Я уже писал что чанк отправляется в папку, соответствующую хешу его координат. Вот так можно вычислить этот хеш:
Код для преобразования в base36 взят с Википедии и допилен для отрицательных чисел.
Сам чанк (уже разархивированный) записан в специальном формате, называемом named binary tag — по сути, двоичный файл с именованными полями. В файле могут встречаться 11 видов стандартных тегов, последний из которых, compound, может содержать произволное количество других тегов (в том числе и типа compound). Стало быть, у нас будут структуры произвольного уровня вложенности.
Внутри самого файла, в поле с именем Blocks находится массив из 32768 байт — это и есть наша карта. На каджый блок, соответственно, отводится по одному байту, блоки идут вертикальными колоннами снизу вверх, с востока на запад и с севера на юг.
Кроме блоков в файле хранятся сущности, принадлежащие чанку — например, монстры. Но нас пока что это не интересует.
Разбор файла
Вначале я просто вырезал из файла байт за байтом, сверяясь со спецификацией формата, но когда освоился, переписал парсер нормально. Всего получилось 100 строк. Парсер целиком можно найти здесь. Основная функция выглядит так:
Каждый тег состоит из байта, указывающего на его тип, pascal-строки с именем тега и содержанием, которое зависит от типа тега. Нулевой тег — особый, ему не положено даже имя. Он завершает списки тегов внутри compound-тега.
Сначала мы читаем тип, если он ненулевой — читаем ещё и имя, и достаём содержание согласно типу. Чтение содержания для каждого типа я выделил в отдельную функцию (кроме типов 4,5 и 6, которые хранят длинные числовые значения в том или ином виде). Функция read_list_payload должна по хорошему называться read_list, но в принципе и так сойдёт.
Если мы прогоним этот скрипт по файлу какого нибудь чанка (предварительно распаковав его Winrar’ом или 7zip’ом), мы увидим что теги внутри compound-тега Level идут не в том порядке, который указан в спецификации. Т.к. все теги помечены именем, найти тег Blocks мы всё равно сможем, просто не получится взять его по заранее вычисленному смещению.
Вот примерная схема одного чанка:
Для обрамляющего compound-тега названия нет — на соответствуюшем месте просто записан нулевой байт (длина строки — 0).
Разбираем блоки
Получив наконец блоки, мы увидим что в результате python преобразовывает массив байт в строку. Нас это вполне устраивает. Теперь нам надо сориентироваться в их пространственном расположении. Для начала, Minecraft своеобразно подходит к определеению осей координат: ось Y у него вертикальна. Вики по Minecraft нам подсказывает, что в массиве блоков можно найти нужный элемент по такой формуле:
Находим тег с блоками в результатах разбора:
Система типов Питона мне пока немного непривычна, поэтому я использовал простые кортежи и списки. Хотя, по хорошему, теги надо было бы сохранять в виде словаря — их порядок нам всё равно не важен, а обращаться по имени тега было бы легче.
Вооружившись приведенной выше формулой, сделаем срез чанка на высоте, к примеру, 16. В обычном ландшафте это как раз самая толща породы внизу, с редкими пещерами и лавой.
Фиксируем высоту (координату y) на 16, и выводим получившуюся матрицу чисел 16 на 16. Потом смотрим в таблицу значений блоков, чтобы узнать что именно мы увидели.
Видим много камня и немного руды, сгруппированной в депозиты. Что ж, похоже на правду Теперь надо отобразить результат покрасивее.
Генерация карты
Чтобы сохранять изображения, нам потребуется Python Imaging Library. Устанавливается она без проблем. После установки можно будет приступить к генерации карты среза.
Изображения мы не задумываясь возьмём прямо из стандартного набора текстур Minecraft Идём в %appdata%\.minecraft, распаковываем minecraft.jar и достаём оттуда terrain.png. Выглядит он вот так:

Нам надо нарезать этот набор на аккуратные квадраты 16 на 16, и составить из них новое изображение. Вначале получаем тайлы:
Вот примерно так. Функция get_cropbox написана для вычисления координат вырезания и вставки. Это не все блоки, которые могут встретиться в игре, но с файлом текстур дополнить этот список Вы сможете и самостоятельно. Я пока остановлюсь на том что может встретиться под землёй.
Теперь, примерно так же как мы выводили текстовые значения блоков, мы заполняем изображение карты:
Здесь опять не все идентификаторы блоков, и если встретится что то незнакомое, на карте будет просто чёрный квадрат. И ещё тут надо помнить про систему координат, в частности, не путать y и z :). В результате этих манипуляций должен получиться PNG-файл со срезом карты:

Вот как то так Это конечно не полноценный картограф, но уже неплохое начало. На его основе можно например сделать маленький скрипт для поиска наиболее богатых рудой чанков или оптимальной глубины шахты.
