Перейти к содержимому

Как писать плагины для minecraft на python

  • автор:

Делаем плагины на 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

Еще с детства я начал покорять бесконечные просторы 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.

Скачать можно последний релиз.

Вопросы с меткой [minecraft]

Используется при вопросах напрямую связанных с игрой Minecraft. Например: Создание ботов для Minecraft, разработка модов (например используя Java), взаимодействие со своим сервером Minecraft.

120 вопросов
Конкурсные
Неотвеченные

  • Конкурсные 0
  • Неотвеченные
  • Цитируемые
  • Рейтинг
  • Неотвеченные (мои метки)

Как создать блок для minecraft forge java 1.19 [закрыт]

Здравствуйте помогите пожалуйста как создать блок для майнкарфта чтобы его можно было ставить и открывать? Я хочу сделать свой для игры с другом. Я искал в ютубе но нечего не нашел кроме mccreator (а .

задан 19 окт в 12:38
40 показов

Виртуальная папка windows 10 [закрыт]

Я хочу установить Minecraft на диск E: и я пробовал сделать это через mklink /D E:\Artem\.minecraft C:\Users\AppData\Roaming\.minecraft Но у меня получилось наоборот: основная папка на диске С: а .

задан 11 окт в 15:11
40 показов

Скрипт для поиска блоков в Minecraft Bedrock Edition

Задача написать скрипт на Java, который будет читать уровень Minecraft Bedrock Edition и находить координаты определенных видов блоков. Проблема с чтением карты Minecraft Bedrock Edition (файл .

задан 4 сен в 8:18
50 показов

Написал бота для майнкрафта на Python, который может отвечать на сообщения, но на определённых игроков не работает

Написал бота на Python с библиотекой mineflayer @On(bot, ‘chat’) def msgHandler(this, user, message, *args): user_parts = message.split(‘ ‘, 3) if len(user_parts) > 1: .

задан 29 авг в 22:04
38 показов

Кикает бота minecraft когда он пвп

Я сделал бота майнкрафт для пвп, когда он пвп и рядом нету предметов всё нормально но когда он убивает моба и из него что то выпадает бота сразу кикает (когда он не пвп его тоже кикает от предметов), .

задан 28 авг в 2:12

Как добавить уровень света блоку Minecraft? Forge Modding 1.20.1

Я в Java совсем недавно, поэтому вопросы глупые наверное и поэтому не могу найти ответ на них в интернете, буду рад если вы сможете помочь с этим Когда пытаюсь добавить lightLevel к блоку, он говорит .

задан 26 авг в 13:25
39 показов

Как перенаправлять tcp-трафик только для определенного процесса?

Появилась необходимость перенаправлять весь tcp-трафик через свой сервер на примере игры Minecraft, но только для процесса этой игры. Пробовал создавать правило для процесса через утилиты вроде .

задан 24 авг в 12:20
11 показов

Почему импортируемый пакет bukkit горит красным?

Скачал чужую работу, была только папка src. Открыл через Idea, но пакеты bukkit и kyori горят красным. Пытался добавить зависимость через Project Structure -> Libraries, но не понял, а что именно .

задан 19 авг в 8:55
17 показов

Как создать мод для более старых версий майнкрафт. iIDEA

Я хотел бы написать мод для minecraft 1.8.9 в InteliJ IDEA, но вне зависимости от моих версий JDK, в расширении minecfrat dev.. показывает для forge только возможные версии с 1.16.5 — 1.12.1. Как .

задан 12 авг в 12:55
13 показов

Forge 1.12.2. Мод не видит кириллицу

Хочу написать мод, который меняет все некультурные слова на культурные при отправке сообщения. Игрок отправляет сообщение с матом, а на самом деле отправляется уже без мата. Вот что я уже написал: .

задан 23 июл в 18:31

Нет вывода в консоле сервера Minecraft 1.18.2 Paper

Установил Windows 10 от Flibuster и появилась проблема, запуская start.bat консоль полностью пустая и писать в неё ничего нельзя, но сервер при этом работает, в диспетчере задач появляется Java: .

задан 15 июл в 19:17
77 показов

пишу бота для майна при запуске выдает ошибки

from javascript import require, On from time import sleep minefalyer = require(‘minefalyer’) bot = minefalyer.createBot(< 'host' : 'localhost', 'port' : '32980', 'username' : 'fisher', .

задан 14 июл в 17:43
45 показов

Minecraft Forge / Не получается синхронизировать клиент и сервер

Возможно, проблема даже и не в этом, однако я не знаю, по какой причине ещё мой код не работает как надо. Задача: Написать мод Minecraft 1.12.2, нажимающий от имени игрока ПКМ при наличии на экране в .

задан 9 июл в 15:30
19 показов

Каким образом происходит передача токена авторизации между лаунчером и клиентом

Недавно решил чисто из интереса разобраться в том, как работает передача данных между лаунчером и клиентом пиратских серверов minecraft. Перерыл большое кол-во информации, понял что при запуске jar .

задан 25 июн в 9:16

Minecraft bot mineflayer не работает

код from javascript import require, Once, once mineflayer = require(«mineflayer») random_number = id([]) % 1000 # Получаем случайное число до 1000 BOT_USERNAME = f’colab_‘ .

задан 27 мая в 15:39
15 30 50 на странице

    Важное на Мете

Связанные метки

Подписаться на ленту

Лента новых вопросов с меткой [minecraft]

Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.

Дизайн сайта / логотип © 2023 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2023.10.27.43697

Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.

Как писать плагины для minecraft на python

Фриланс маркетплейс

Пользователей онлайн: 398

Последний заказ: 3 мин. назад

Логотип и брендинг

Презентации и инфографика

Арт и иллюстрации

Веб и мобильный дизайн

Маркетплейсы и соцсети

Интерьер и экстерьер

Обработка и редактирование

Полиграфия

Промышленный дизайн

Наружная реклама

Доработка и настройка сайта

Создание сайтов

Десктоп программирование

Скрипты и боты

Мобильные приложения

Сервера и хостинг

Юзабилити, тесты и помощь

Тексты и наполнение сайта

Набор текста

Продающие и бизнес тексты

Резюме и вакансии

Статистика и аналитика

Соцсети и SMM

Контекстная реклама

Базы данных и клиентов

E-mail рассылки

Маркетплейсы и доски объявлений

Реклама и PR

Аудиозапись и озвучка

Музыка и песни

Редактирование аудио

Интро и анимация логотипа

Видеоролики

Персональный помощник

Бухгалтерия и налоги

Обзвоны и продажи

Юридическая помощь

Продажа сайтов

Подбор персонала

Презентация

Обучение и консалтинг

Стройка и ремонт

Логотип и брендинг

Презентации и инфографика

Арт и иллюстрации

Веб и мобильный дизайн

Маркетплейсы и соцсети

Интерьер и экстерьер

Обработка и редактирование

Полиграфия

Промышленный дизайн

Наружная реклама

Доработка и настройка сайта

Создание сайтов

Десктоп программирование

Скрипты и боты

Мобильные приложения

Сервера и хостинг

Юзабилити, тесты и помощь

Тексты и наполнение сайта

Набор текста

Продающие и бизнес тексты

Резюме и вакансии

Статистика и аналитика

Соцсети и SMM

Контекстная реклама

Базы данных и клиентов

E-mail рассылки

Маркетплейсы и доски объявлений

Реклама и PR

Аудиозапись и озвучка

Музыка и песни

Редактирование аудио

Интро и анимация логотипа

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *