Как обратиться к Velocity из Bukkit?
Взаимодействие между Velocity и Bukkit. Как сделать отправку команд на все сервера разом? Как перезагрузить сервер прямо из Velocity? Сейчас расскажу!
Предисловие
Для тех, кому нужна только основная часть статьи - Нажми сюда
Ситуация, которая побудила меня написать эту статью следующая: Поступил ко мне заказ на плагин для Bukkit, который будет реализовывать механику анимированной перезагрузки сервера. Что я имею ввиду под "анимированной перезагрузкой"? Согласен, звучит странно, однако это видео должно дать понимание того, что от меня требовалось:
Перезагрузка сервера с анимацией тумана
С этим туманом там вообще отдельная история, но она у меня заготовлена под другой пост. Как мы видим, игрок вводит команду и сервер перезагружается. Но перед тем, как это происходит игрок видит красивую анимацию экспансии мира вокруг него.
Дело в том, что такая анимация может быть отправлена игроку только с Прокси-сервера, который, собственно Velocity. С ним, да и вообще с прокси-серверами я толком ранее не сталкивался, как разработчик. Соответственно, как там всё устроено знал поверхностно. Разобрался, кстати, довольно быстро, API там удобное и практичное, но несколько отличается от привычного ведро-крана. Итого, план работы плагина у меня получился следующий:
Отправить игроку туман
Отправить игроку Title
Отправить игроку звук
Выполнить нужные команды на сервере
По окончании таймера кинуть игрока в лобби
Стопнуть сервер
С туманом, тайтлом - всё понятно. Но как игроку отправить звук из велосити? (На момент написания статьи этот метод есть в Velocity, но он пока ничего не делает, о чем нам говорит предупреждение от разработчиков)
Вот тут мы уже перейдем к основной части статьи.
Как заставить Bukkit и Velocity обмениваться данным между собой?
В первую очередь, стоит сказать, что часть информации, которая будет рассказана далее была взята с официальной документации Paper.
ВАЖНО: В предоставленном коде используется библиотека lombok.
Кейс 1: Взаимодействие Velocity -> Bukkit
Для начала разберём Velocity часть, а после уже перейдем к Bukkit.
Первым делом, мы зарегистрируем наш канал общения. Сделать это можно прямо в главном классе нашего плагина. Канал будет носить ключ "plugin:main".
// Создаём переменную с идентификатором нашего канала
public final MinecraftChannelIdentifier IDENTIFIER = MinecraftChannelIdentifier.from("plugin:main");
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent event) {
// Регистрируем канал
proxyServer.getChannelRegistrar().register(IDENTIFIER);
}Далее мы создадим Enum (перечисление) всех сообщений, которые мы будем отправлять. Для хранения всех енумов, я создал пакет enums.
import lombok.Getter;
@Getter
public enum MessageType {
EXECUTE_COMMAND("execute_command"), // Выполнить команду
SHUTDOWN("shutdown"), // Выключить сервер
PLAY_SOUND("play_sound"); // Проиграть звук всем на сервере
private final String type;
MessageType(String type) {
this.type = type;
}
}Разбирать мы будем именно 3 этих сообщения - Выполнение какой-либо команды, выключение сервера и проигрывание звука всем игрокам на сервере.
Следующим нашим шагом будет объект отправщика сообщений, в который мы передадим главный класс плагина, идентификатор канала общения и логгер (необязательно)
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import ru.er1one.messaginglesson.MessagingLesson; // Импорт главного класса
import ru.er1one.messaginglesson.enums.MessageType; // Импорт нашего енума
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.util.Optional;
public class MessageSender {
private final MessagingLesson plugin; // Главный класс
private final MinecraftChannelIdentifier channel; // Канал общения
private final Logger logger; // Логгер
public MessageSender(MessagingLesson plugin, MinecraftChannelIdentifier channel) {
this.plugin = plugin;
this.channel = channel;
this.logger = plugin.getLogger();
}
@SneakyThrows
public void sendMessage(MessageType messageType, String serverName,
String... args) {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(byteOut);
out.writeUTF(messageType.getType());
for (String arg : args) {
out.writeUTF(arg);
}
Optional<RegisteredServer> server = plugin.getProxy().getServer(serverName);
if (server.isEmpty()) {
logger.warn("Отправка сообщения {}: Сервер {} не найден!",
messageType.name(), serverName);
return;
}
server.get().sendPluginMessage(channel, byteOut.toByteArray());
}
}
Аннотация @SneakyThrows у метода sendMessage автоматически сгенерирует проверенные исключения, что облегчит читаемость кода.
Теперь построчно разберём метод sendMessage
ByteArrayOutputStream byteOut = - Создаём байтовый массив, для хранения данных.
new ByteArrayOutputStream()DataOutputStream out = new DataOutputStream(byteOut) - Создаём поток для записи данных в байтовый массив.out.writeUTF(messageType - Первым делом записываем в поток данных тип нашего сообщения.
.getType())
Далее у нас идёт цикл, где мы перебираем каждый переданный аргумент и также записываем его в поток данных.Optional<RegisteredServer> server = - Опционально получаем бэкэнд сервер, на который будем отправлять наше сообщения по его названию из конфига Velocity.
plugin.getProxy().
getServer(serverName)if (server.isEmpty()) { ... } - Проверяем получили ли мы сервер или его не существует и логируем в консоль предупреждение о том, что сервер не был найден.server.get()- Отправляем сообщение серверу по заданному каналу, преобразовывая наш поток данных в массив байтов.
.sendPluginMessage(channel,
byteOut.toByteArray())
Следующим шагом мы объявим объект нашего отправщика сообщения в главном классе private MessageSender messageSender и создадим его, при запуске сервер this.messageSender =
new MessageSender(this, CHANNEL)
Теперь, всё что нам требуется для того, чтобы отправить какое-либо сообщение на бэкэнд, это вызвать метод sendMessage:
messageSender.sendMessage(MessageType.EXECUTE_COMMAND, "test_server", "/broadcast Привет!");
messageSender.sendMessage(MessageType.SHUTDOWN, "test_server");
messageSender.sendMessage(MessageType.PLAY_SOUND, "test_server", "ui.button.click");Однако, наш бэкенд сервер пока что не умеет обрабатывать такие сообщения. Теперь мы займемся Bukkit частью.
TO BE CONTINUED...