Написание Paper/Bukkit плагина LiteSMT #1 — Основа понятий и окружение

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Перед началом...

Всем привет! Это серия постов будет иметь много различных тем, но и я так-же не забыл о написании ядра на Rust, скоро будут продолжения)

Немного понятий

Я думаю, что стоит начать с некоторой основной информацией по созданию плагинов, а именно:

  1. Все плагины основываются на Bukkit - API для плагинов, которая может немного отличаться в зависимости от версий.

  2. Плагины пишутся в основном на Java или иногда на Kotlin.

  3. Некоторая информация плагина исключительно для ядра храниться в plugin.yml, где можно найти версию плагина, главный класс плагина и многое другое.

  4. В стандартных случаях плагинам хватает обычного, но существуют моменты, где могут понадобится NMS - net.minecraft.server или же API к самому Minecraft.

  5. Плагины без труда могут использовать API других плагинов, если имеется такая возможность, например PlaceholderAPI и Vault.

  6. Почти всегда, когда ядро обращается к плагину - выполняется в синхронном потоке сервера.

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

Подготавливаем наш тестовый сервер

Всё-таки я думаю нам сначала стоит именно тестовый сервер так-как тема конкретно написания плагина уже гораздо больше.

Для начала нам стоит выбрать основную версию и ядро, я выбрал 1.16.5 версию так-как сейчас она как основа для версий выше.

Статистика bStats
Статистика bStats

Из статистики выше видно, что большинство серверов на 1.16.5 и выше, а ядро Paper и следовательно как изначально планировалось было выбрано ядро Paper. Скачать его версии 1.16.5 можно по этой ссылке.

Скачанный файл по ссылке выше я помещаю в отдельную директорию рядом со своим Start.sh файлом:

Содержимое Start.sh

После первого запуска будет скачан кеш и создан 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 с плоской генерацией. И теперь консоль после запуска выглядит так:

Теперь для начала нам надо загрузить важны плагины:

После загрузки можно перезагрузить частично сервер используя reload confirm. И потом плагины загрузятся и некоторые создадут конфигурации.

Изменённые конфигурации

После сделанных изменений надо снова перезагрузить сервер, а после полной перезагрузки мы можем спокойно зайти на наш тестовый сервер с любого клиента, который поддерживает версию 1.16.5, так-же стоит выдать себе все права используя op <ваш ник>.

Подготовка нашего плагина

Так-как плагин будет написан на стандартном Java, то нам надо подумать о сборщике нашего плагина, есть несколько вариантов: IDE компилятор, Maven и Gradle. Давайте посмотрим основные плюсы и минусы, которые я выделил как основные:

Вы конечно можете сами выбрать сборщик под себя, но я выбрал Gradle и из-за чего дальше будет информация связанная с ним. Так-же выбрал редактор Intellij IDEA от JetBrains так-как он очень замечательно работает с Java.

Первым делом нужно инициализировать Gradle проект:

Создание Gradle проекта в IDEA
Создание Gradle проекта в IDEA

После создания проекта я в первую очередь удалил папку test и изменил build.gradle под проект:

Содержимое build.grade

Теперь нам надо сделать главный класс плагина, который у меня будет 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 создаётся в два клика:

нужный пакет -> ПКМ -> New -> Package
нужный пакет -> ПКМ -> New -> Package

В нашем новом пакете создадим класс с именем MainChatListener, который по началу имеет только публикацию класса и пакет. И после его имени мы должны прописать implements Listener, однако как я говорил раньше, то нужно и импортировать классы, чтобы сборщик мог знать какой класс нам нужен и JVM тоже, поэтому IDEA предлагает выбрать Listener из нескольких пакетов, но нам нужен именно с org.bukkit.event и следом появиться импорт org.bukkit.event.Listener. Теперь данный класс для ядра считается неким слушателем событий, однако он не зарегистрирован и пусть, а это значит, что от него нету толка, для этого в главном классе, в onEnable мы прописываем:

Bukkit.getPluginManager().registerEvents(new MainChatListener(), this);

И теперь для bukkit наш слушатель зарегистрирован на все типы событий, а в качестве второго аргумента мы передаём плагин, это означает, что этот слушатель принадлежит нашему плагин.
- Тогда получается возможно регистрировать слушатели и из других плагинов?
- Верно! Однако не стоит так делать.

Если мы и попробуем сейчас собрать плагин и протестировать его, то у нас не будет никаких отличий так-как слушатель хоть и существует, но он пустой. Давайте попробуем создать в нём функцию для форматирования сообщений из чата, а именно установка своего формата сообщений:

import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

public class MainChatListener implements Listener {
    @EventHandler
    public void onChat(AsyncChatEvent event) {
        event.renderer((source, sourceDisplayName, message, viewer) -> 
                Component.text()
                  .append(sourceDisplayName.color(TextColor.fromHexString("#a8a432")))
                  .append(Component.text(" : "))
                  .append(message)
                  .build());
    }
}
Объяснение кода

Все методы событий обязательно должны иметь аннотацию EventHandler от org.bukkit, чтобы было ясно, что этот метод точно является слушателем событий, а именно AsyncChatEvent так-как именно он указан в аргументе. Далее для форматирования не используется устаревший метод formatter, а новый - renderer, который имеет больше возможностей. Для нашего renderer мы используем так называемый функциональные интерфейсы, в данном случае ChatRenderer и спасибо Java, что тут получилось так сокращённо, ведь иначе вышло бы на несколько строк больше.

Для форматирования мы создаём наш новый чат-компонент Component, но для всего связанного с чатом сейчас используется пакет net.kyori.adventure.text. Component.text() создаёт нам конструктор, который мы используем для связки трёх других компонентов: ник, разделитель для сообщения( : ) и самого сообщения.

Метод TextColor.fromHexString даёт возможность получить нам цвет для чат-компонента из HEX строки c нужным цветом, в моём случае #a8a432. Этот цвет я применяю на переменную компонента sourceDisplayName и добавляю получившейся компонент в конструктор.

Далее я добавляю в конструктор разделитель в сообщении " : ", который можно получить используя Component.text(" : ").

Последнее добавление в конструктор - само сообщение игрока.

Заканчивается создание форматированного компонента методом build.

Теперь если мы попробуем набрать в консоль любое сообщение, то увидим:

Как выглядит итог форматирования.
Как выглядит итог форматирования.

Далее я бы убрал сообщение выхода с сервера таким кодом:

@EventHandler
public void onQuit(PlayerQuitEvent event) {
	event.quitMessage(null);
}

Теперь при повторной сборке и следующей проверки выхода мы не увидим сообщения в консоли о выходе игрока, кроме того, что является от самого ядра.

Можно и приукрасить сообщение о входе:

@EventHandler
public void onJoin(PlayerJoinEvent event) {
	event.joinMessage(Component.text()
		.append(Component.text("["))
    .append(Component.text("+", TextColor.fromHexString("#28ff03")))
    .append(Component.text("] "))
    .append(event.getPlayer().displayName())
    .build());
}

И после сборки этого кода, то при заходе будет данное сообщение:

Итог

Вот и была сделана небольшая основа для плагина, которая дальше будет дополняться всё большим функционалом в следующих частях!

Готовый сходный код доступен по этой ссылке.

Источник: https://habr.com/ru/post/668836/


Интересные статьи

Интересные статьи

Компания «Деловой разговор» — Титановый партнер 3СХ — осуществила расширенную интеграцию IP-АТС 3CX с Битрикс 24. Ранее уже существовали отдельные модули, решающие конкретные задачи, напр...
В прошлой статье мы говорили о том, что основой хорошей интеграции является админка, которая позволяет быстро решать инциденты. Сегодня мы поговорим, как реализовать инте...
Майкл Сибель — сооснователь (в 25 лет) стартапов Justin.tv/Twitch (капитализация $15 млрд) и Socialcam, член правления Reddit. Один из вопросов, который мы получаем на YC — как най...
На сегодняшний день сетевой администратор или инженер ИБ тратит уйму времени и сил, чтобы защитить периметр сети предприятия от различных угроз, осваивает все новые системы предотвра...
В статье описаны необходимые параметры сервера для оптимальной работы сайта на платформе 1С-Битрикс.