Перед началом…
Всем привет! Это серия постов будет иметь много различных тем, но и я так-же не забыл о написании ядра на Rust, скоро будут продолжения).
Немного понятий
Я думаю, что стоит начать с некоторой основной информацией по созданию плагинов, а именно:
-
Все плагины основываются на Bukkit — API для плагинов, которая может немного отличаться в зависимости от версий.
-
Плагины пишутся в основном на Java или иногда на Kotlin.
-
Некоторая информация плагина исключительно для ядра храниться в plugin.yml, где можно найти версию плагина, главный класс плагина и многое другое.
-
В стандартных случаях плагинам хватает обычного, но существуют моменты, где могут понадобится NMS — net.minecraft.server или же API к самому Minecraft.
-
Плагины без труда могут использовать API других плагинов, если имеется такая возможность, например PlaceholderAPI и Vault.
-
Почти всегда, когда ядро обращается к плагину — выполняется в синхронном потоке сервера.
Думаю этой малой информации может хватить для того, чтобы мы могли подготовить основу нашего плагина и небольшое окружение для работы со своим плагином.
Подготавливаем наш тестовый сервер
Всё-таки я думаю нам сначала стоит именно тестовый сервер так-как тема конкретно написания плагина уже гораздо больше.
Для начала нам стоит выбрать основную версию и ядро, я выбрал 1.16.5 версию так-как сейчас она как основа для версий выше.
Из статистики выше видно, что большинство серверов на 1.16.5 и выше, а ядро Paper и следовательно как изначально планировалось было выбрано ядро Paper. Скачать его версии 1.16.5 можно по этой ссылке.
Скачанный файл по ссылке выше я помещаю в отдельную директорию рядом со своим Start.sh файлом:
Содержимое Start.sh
java -Xms512M -Xmx1G -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:+AlwaysPreTouch -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1 -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:InitiatingHeapOccupancyPercent=15 -Dusing.aikars.flags=https://mcflags.emc.gs -Daikars.new.flags=true -jar paper-1.16.5-794.jar nogui
После первого запуска будет скачан кеш и создан eula.txt( в котором надо будет установить true вместо false ):
После изменения eula.txt будет такой вывод с полным запуском:
Как я настроил некоторые файлы при разработке
bukkit.yml
settings: allow-end: false warn-on-overload: true permissions-file: permissions.yml update-folder: update plugin-profiling: false connection-throttle: 0 query-plugins: false deprecated-verbose: default shutdown-message: Server closed minimum-api: none spawn-limits: monsters: 10 animals: 10 water-animals: 5 water-ambient: 10 ambient: 5 chunk-gc: period-in-ticks: 900 ticks-per: animal-spawns: 1 monster-spawns: 1 water-spawns: 1 water-ambient-spawns: 1 ambient-spawns: 1 autosave: 12000 aliases: now-in-commands.yml
enable-jmx-monitoring=false rcon.port=25575 level-seed= gamemode=survival enable-command-block=false enable-query=false generator-settings= level-name=world motd=Test Server query.port=25565 pvp=true generate-structures=false difficulty=easy network-compression-threshold=256 max-tick-time=60000 max-players=20 use-native-transport=true online-mode=false enable-status=true allow-flight=false broadcast-rcon-to-ops=true view-distance=6 max-build-height=256 server-ip= allow-nether=false server-port=25565 enable-rcon=false sync-chunk-writes=true op-permission-level=4 prevent-proxy-connections=false resource-pack= entity-broadcast-range-percentage=100 rcon.password= player-idle-timeout=0 debug=false force-gamemode=false rate-limit=0 hardcore=false white-list=false broadcast-console-to-ops=true spawn-npcs=true spawn-animals=true snooper-enabled=true function-permission-level=2 level-type=flat text-filtering-config= spawn-monsters=true enforce-whitelist=false resource-pack-sha1= spawn-protection=16 max-world-size=500
После изменений конфигурации я удалил все миры, чтобы был создан только world с плоской генерацией. И теперь консоль после запуска выглядит так:
Теперь для начала нам надо загрузить важны плагины:
-
PlaceholderAPI — API для плейсхолдеров.
-
PlugManX — форк оригинального PlugMan для работы с плагинами(перезагрузка, загрузка, выгрузка и тд).
-
Auto Reload — автоматическая перезагрузка плагинов в случае изменений.
После загрузки можно перезагрузить частично сервер используя reload confirm. И потом плагины загрузятся и некоторые создадут конфигурации.
Изменённые конфигурации
plugins/PlugManX/config.yml
ignored-plugins: [PlugManX] notify-on-broken-command-removal: true auto-load: enabled: true check-every-seconds: 2 auto-unload: enabled: true check-every-seconds: 2 auto-reload: enabled: false check-every-seconds: 2
plugins/PlaceholderAPI/config.yml
# PlaceholderAPI # Version: 2.11.1 # Created by: extended_clip # Contributors: https://github.com/PlaceholderAPI/PlaceholderAPI/graphs/contributors # Issues: https://github.com/PlaceholderAPI/PlaceholderAPI/issues # Expansions: https://api.extendedclip.com/all/ # Wiki: https://github.com/PlaceholderAPI/PlaceholderAPI/wiki # Discord: https://helpch.at/discord # No placeholders are provided with this plugin by default. # Download placeholders: /papi ecloud check_updates: true cloud_enabled: true cloud_sorting: "name" cloud_allow_unverified_expansions: true boolean: 'true': 'yes' 'false': 'no' date_format: MM/dd/yy HH:mm:ss debug: true
plugins/bStats/config.yml
enabled: false serverUuid: 00000-00000 logFailedRequests: false
После сделанных изменений надо снова перезагрузить сервер, а после полной перезагрузки мы можем спокойно зайти на наш тестовый сервер с любого клиента, который поддерживает версию 1.16.5, так-же стоит выдать себе все права используя op <ваш ник>.
Подготовка нашего плагина
Так-как плагин будет написан на стандартном Java, то нам надо подумать о сборщике нашего плагина, есть несколько вариантов: IDE компилятор, Maven и Gradle. Давайте посмотрим основные плюсы и минусы, которые я выделил как основные:
-
Компилятор IDE(IDEA, Eclipse и др)
Плюсы:
1) Все зависимости и компиляция настроены прямо в настройках редактора.
2) Может быть быстрым но зависит от настроек компиляции.
Минусы:
1) Для редактирования может потребоваться конкретный редактор или его поддержка в другом редакторе.
2) Часто ограничен возможностями самого редактора.
3) Компиляция часто может идти тяжелее нежели на Maven или Gradle. -
Maven
Плюсы:
1) Возможно настроить и форматирование файлов, все зависимости и репозитори, а так-же плагины компиляции.
2) Не зависит от конкретного редактора.
3) Быстро компилирует, но хуже Gradle.
Минусы:
1) Для компиляции каждый раз запускается новый процесс, который каждый раз заново читает и собирает информацию. -
Gradle
Плюсы:
1) Есть настройки форматирования файлов, зависимостей, репозиторий и различных дополнений(например Lombok).
2) Запускает компиляцию в фоновом процессе, который считывает конфиги и другое после изменений или при другой нужде.
3) Не зависит от редактора как и Maven.
4) Можно писать конфигурацию компиляции на языке Groovy или Kotlin DSL
5) Запуск компиляции проходит с помощью запуска фонового процесса(если не запущен) или обращение к нему.
Минусы:
1) Фоновый процесс постоянно требует некоторое количество ОЗУ.
Вы конечно можете сами выбрать сборщик под себя, но я выбрал Gradle и из-за чего дальше будет информация связанная с ним. Так-же выбрал редактор Intellij IDEA от JetBrains так-как он очень замечательно работает с Java.
Первым делом нужно инициализировать Gradle проект:
После создания проекта я в первую очередь удалил папку test и изменил build.gradle под проект:
Содержимое build.grade
plugins { id 'java' } group 'xyz.distemi' version '1.0-SNAPSHOT' repositories { mavenCentral() maven { name = "PaperMC" url = "https://repo.papermc.io/repository/maven-public/" } maven { name = "PlaceholderAPI" url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/' } } dependencies { compileOnly 'com.destroystokyo.paper:paper-api:1.16.5-R0.1-SNAPSHOT' // PaperMC compileOnly 'me.clip:placeholderapi:2.11.1' // API плагина PlaceholderAPI compileOnly 'org.projectlombok:lombok:1.18.24' // Lombok API annotationProcessor 'org.projectlombok:lombok:1.18.24' // Lombok процессор }
И следом я нажал на кнопку синхронизации в своей IDEA.
Теперь нам надо сделать главный класс плагина, который у меня будет xyz.distemi.litesmt.LiteSMT:
Базовый код главного класса плагина LiteSMT
package xyz.distemi.litesmt; // Объявляем наш пакет // Импортируем Getter из ломбок, абстрактный класс JavaPlugin и // интерфейс логгера. import lombok.Getter; import org.bukkit.plugin.java.JavaPlugin; import java.util.logging.Logger; public class LiteSMT extends JavaPlugin { // Создаём две статичные переменные: @Getter private static LiteSMT instance; // Класс плагина. @Getter private static Logger jlogger; // Логгер. @Override public void onEnable() { // Устанавливаем наши статичные переменные: instance = this; jlogger = super.getLogger(); // Выводим в консоль сообщение Hello from LiteSMT // от имени плагина. jlogger.info("Hello from LiteSMT!"); } }
Для людей, которые не знают Java или хотящие подробное объяснение
В классе выше мы создали файл в папке src/main/xyz/distemi/litesmt файл LiteSMT.java с содержанием выше.
Импорты передают компилятору информацию о использованных классах и другого, без импорта ему неизвестно какой именно класс/интерфейс или другое вы используете.
public class позволяет нам показать класс как главный в этом файле, а это значит, что никто не мешает создать рядом просто class без public.
private является областью видимости поля, которая может быть применена как на метод, так и на переменную.
static — модификатор, говорящий, что данное поле может быть использовано, инициализировано и тд без конструирования самого класса( new Class() ).
Аннотация @Override позволяет нам перезаписать метод из класса-предка(у нас это абстрактный класс JavaPlugin)
Аннотация @Getter из Lombok указывает, что должен будет сгенерироваться дополнительный код, используя процессор аннотаций Lombok-а.
Метод onEnable работает как конструктор, но только вот он исключительно для нашего плагина. Метод возвращает тип void, а именно ничего.
Внутри перезаписанного метода onEnable мы устанавливаем глобальные переменные instance и jlogger, но если с первым случаем ясно, что ссылаемся на сконструированный класс, то во втором случае используем super уже для обращению к «предку», а именно JavaPlugin.
Следом выводим в консоль сообщение из аргумента.
Чтож, главный класс у нас имеется, но для работы плагина этого недостаточно!
Серверу нужно знать какой же класс главный и другую информацию, для этого нам нужно создать файл plugin.yml, но уже не в качестве кода, а файл-ресурса, в Gradle такие файлы можно создать в директории проекта src/main/resources/, где файлы внутри не могут быть скомпилированы, а копируются в наш jar «сырыми», но есть например возможность некоторого форматирования, однако пока думаю можно будет обойтись и без него.
Создание plugin.yml
В папке ресурсов я создаю файл plugin.yml, который по умолчанию не имеет ничего я записываю содержимое ниже:
name: LiteSMT main: xyz.distemi.litesmt.LiteSMT version: 1.0 author: Distemi prefix: LSMT depend: - PlaceholderAPI
Как вы можете заметить, то мы устанавливаем всего шесть значений, однако некоторые необязательные.
name (Обязательно) — даёт знать ядру о «имени» плагина, которое может использоваться как в некоторых командах сервера, так и других плагинах по типу того же PlugManX.
main (Обязательно) — указывает класс в нашем jar, который становиться главным в работе плагина, 1 jar = 1 плагин.
version (Обязательно) — атрибут, означающий версию нашего плагина, может быть как 1.0 так и 1.0.0.
author — указывает автора плагина.
prefix — префикс в логе вместо названия плагина.
depend — обязательные зависимости для плагина, если каких-то нету, то плагин не будет загружен, в списке указываются «имена» плагинов.
Если интересно почитать о других атрибутах и тд для plugin.yml, то можете почитать по этой ссылке.
Всё почти готово, однако теперь нам нужно собрать плагин и перекинуть в папку с сервером, можно конечно это делать руками, но я больше предпочитаю делать это автоматически, используя свои задачи в Gradle, для чего нам нужно в build.gradle добавить следующее:
task copyToDevEnv_1_16_5() { doLast { copy { from "build/libs/LiteSMT-1.0-SNAPSHOT.jar" into "../test-server1.16.5/plugins/" } } } build.finalizedBy copyToDevEnv_1_16_5
Тут мы объявляем задачу, которая копирует готовый jar плагина в папку указанную из into, а build.finalizedBy означает, что задачу build мы всегда заканчиваем с copyToDevEnv_1_16_5. Давайте теперь мы впишем ./gradlew build —offline -x test, где мы запускаем компиляцию без доступа к интернету(—offline) и исключаем задачу(-x) тестов(test). Теперь смотрим в нашу папку с плагинами и видим:
Ура! Наш плагин успешно собрался и сам был помещён в директорию с плагинами. Теперь пробуем запустить наш сервер и видим…
Да! Плагин наш был успешно запущен и при запуске вывел в консоль наше сообщение, однако для проверки автоматической перезагрузки плагина в случае изменений я могу чуть изменить сообщение в коде и заново собрать плагин прошлой командой в консоль/терминал и увидеть уже в консоли сервера:
Всё-таки я добавил «Changed from me!» в строку вывода и после сборки плагин AutoReload сам увидел изменение и перезагрузил плагин, ну не удобство ли, когда надо бывает частенько и главный класс изменить?)
Так-же как вы могли бы заметить, то наш префикс из plugin.yml тоже показывает результат так-как без того атрибута у нас выводился бы LiteSMT.
В плагине мы можем так-же допустим реализовать работу с конфигурацией, событиями и другим, но я в этой части покажу лишь работу с событиями так-как на конфигурацию у меня есть много интересного содержания)
Обработка событий в плагине или как можно приукрасить чат.
Сейчас мы будем работать лишь с тремя из большого количества событий, а именно вход игрока, выход и написание сообщений в чат. Наверное многим не нравиться стандартный формат чата из и игры из-за чего скачивают и устанавливают плагины по типу Chatty и другого, но сейчас мы сделаем некую свою мини альтернативу, правда не всё, но хоть что-то)
Для событий в плагинах используют отдельные классы, которые просто помимо своего существования должны наследовать один из интерфейсов от Bukkit и быть зарегистрированы в событиях Bukkit-а.
Для событий чата будет у меня отдельный пакет в моём jar, а именно xyz.distemi.litesmt.listeners.chat, который в IDEA создаётся в два клика:
ссылка на оригинал статьи https://habr.com/ru/post/668836/
Добавить комментарий