1. Этот сайт использует файлы cookie. Продолжая пользоваться данным сайтом, Вы соглашаетесь на использование нами Ваших файлов cookie. Узнать больше.
  2. Вы находитесь в сообществе Rubukkit. Мы - администраторы серверов Minecraft, разрабатываем собственные плагины и переводим на различные языки плагины наших коллег из других стран.
    Скрыть объявление
Скрыть объявление
В преддверии глобального обновления, мы проводим исследования, которые помогут нам сделать опыт пользования форумом ещё удобнее. Помогите нам, примите участие!

Стартап Как работать с пакетами на любой версии?

Тема в разделе "Разработка плагинов для новичков", создана пользователем Dymeth, 26 авг 2023.

  1. Автор темы
    Dymeth

    Dymeth Активный участник Пользователь

    Баллы:
    98
    Имя в Minecraft:
    Dymeth
    Всем привет. Эта тема для тех, кто хочет научиться перехватывать игровые пакеты и отправлять свои собственные. В первую очередь эта статья - гайд по всем деталям и тонкостям работы с протоколом игры, а уже во вторую - туториал по ProtocolLib.

    Статья является черновиком и находится в доработке, будет дополняться и обновляться. При необходимости задавайте вопросы в комментариях.

    Что такое пакеты?

    В рамках интернета это куски информации, которыми обмениваются разные пользователи сети: клиент с сервером, сервер с клиентом, сервер с сервером или даже клиент с клиентом (при помощи технологии P2P).

    Конкретно мы будем говорить про пакеты в Minecraft. В нашем случае пакеты могут быть лишь двух направлений: от клиента (игрока) к серверу или от сервера клиенту. Вся игровая информация передаётся как раз при помощи этих пакетов.
    Например, игрок отправил сообщение в чат - на сервер сразу улетел пакет, содержащий текст этого сообщения. Или же сервер отправил игроку чанк - в таком пакете будет вся информация о блоках, их расположении и освещенности.

    Внимание!

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

    Также необходимо понимать, что на разных версиях игры один и тот же пакет может передавать разные типы данных. Поэтому если ваш плагин будет работать на одной версии ядра - он совсем не обязан работать на другой версии ядра. Обращаю внимание, что роль тут играет именно ядро. Даже если на сервере установлен ViaVersion - это не имеет никакого значения. Ваш плагин должен корректно работать именно для той версии, для которой предназначено само ядро. Также отмечу, что ProtocolLib тоже не решает проблему поддержки разных версий. Как поддерживать сразу несколько версий - читайте ниже.

    Поехали!

    Прежде всего, нам необходимо определиться, каким образом мы будем отправлять и принимать пакеты. Существует два основных варианта:
    1) При помощи ProtocolLib или аналогичных плагинов
    2) Без использования второстепенных плагинов

    В рамках данной статьи будет рассмотрен лишь вариант с ProtocolLib. Он прост в использовании для новичков, стабилен и актуален. В будущем при вы сможете самостоятельно изучить второй способ. При грамотном подходе он может работать производительней, нежели ProtocolLib.

    Окей, подключаем ProtocolLib. Заходим на страницу плагина, там видим абзац "For Developers", а в нём две полезные ссылки.
    Как подключить ProtocolLib к проекту: https://github.com/dmulloy2/ProtocolLib/wiki/Adding-ProtocolLib-as-a-Dependency
    Как принимать и отправлять пакеты: https://dev.bukkit.org/projects/protocollib/pages/tutorial
    На самом деле, на вики есть ещё куча полезного. Чуть позже я продублирую в тему часть информации. Пока просьба ознакомиться самостоятельно, при необходимости используя переводчик.

    Пока хочу упомянуть вещи, которые не указаны в вики.

    Как найти название нужного пакета?

    Самый простой способ - попытаться подобрать название методом тыка. Попробовать одну формулировку, попробовать синоним, попробовать перефразировать. Тут вам сильно поможет автодополнение среды разработки.

    Для примера возьмём задачу из темы: https://rubukkit.org/threads/188885
    Человек хочет отправить игрокам анимацию поднятия дропнутого предмета с земли.

    Поскольку это пакет с сервера на клиент - он находится в классе PacketType.Play.Server. В нашем случае можно попробовать следующие слова: ANIMATION, PICKUP, ITEM, DROP, COLLECT. Натыкаемся на много разных вариантов, исключаем самые нелогичные, в итоге остаётся всего два подходящих:
    PacketType.Play.Server.ANIMATION
    PacketType.Play.Server.COLLECT

    Но какой из них? А если мы не нашли ни одного варианта? И какие данные необходимо передавать внутри пакета? Можно просто погуглить. Но лучше научиться разбираться самостоятельно, потому что в будущем вы сэкономите кучу времени на этом. Что ж, обращаемся к замечательному сайту: https://wiki.vg/Protocol
    И уже тут мы видим, какое содержимое передаётся в каждом из пакетов. Можем найти нужный нам пакет при помощи тех же ключевых слов и поиска по странице (CTRL+F). Используя навыки английского языка довольно быстро находим нужный пакет, на вики он называется Pickup Item: https://wiki.vg/Protocol#Pickup_Item

    Окей, а как определить, как этот пакет называется в ProtocolLib'е?
    Тут всё просто - у каждого пакета есть свой номер - это айди пакета. На вики он указан в первом столбце, в нашем случае это 0x67. 0x означает, что номер указан в шестнадцатеричной системе счисления, а не в десятичной. Это важно, обратите внимание. Теперь идём в исходный код ProtocolLib, в класс PacketType:
    https://github.com/dmulloy2/Protoco...n/java/com/comphenix/protocol/PacketType.java
    Прокручиваем класс чуть ниже и видим, как создаются все виды пакетов. В конструктор пакета при этом как раз передаётся айди этого пакета. Опять используем поиск по странице (CTRL+F), ищем текст "0x67" и находим замечательную строку:
    PHP:
    public static final PacketType COLLECT
    = new PacketType(PROTOCOLSENDER0x67"Collect""SPacketCollectItem")
    Отлично! Это то, что мы искали. Тип пакета нам теперь известен. Но этот способ работает хорошо, только если знать три важные тонкости:

    1) На разных версиях игры номер одного и того же пакета может быть разным. Поэтому при поиске убедитесь, что и вики, и протоколлиб описывают одну и ту же версию игры. Как правило, протоколлиб содержит пакеты для последней версии игры, а вот вики может долго обновляться - в самом верху страницы указана версия, к которой относится страница:
    This article is about the protocol for the latest stable release of Minecraft Java Edition (1.20.1, protocol 763)

    Но вы всегда можете посмотреть более старую версию страницы:
    upload_2023-8-26_13-39-13.png
    upload_2023-8-26_13-42-19.png

    Для поиска нужной версии страницы проще всего ориентироваться по дате выхода обновления игры. Информация по дате выхода версий есть, например, на Minecraft Wiki.

    2) Пакетов с нужным айди может быть несколько. Откуда? Есть пакеты с сервера на клиент, есть с клиента на сервер - айди у них пересекаются, потому что у каждого вида подсчёт начинается с нуля. Но и это ещё не всё. Есть несколько состояний-"режимов работы игры", каждый из которых отвечает за отдельный процесс:
    Status - обмен информацией, которая доступна без подключения (мотд, онлайн и т.д.)
    Handshake - обмен первичной информацией при начале подключения к серверу
    Login - авторизация игрока в случае онлайн-мода
    Resource - синхронизация игровых компонентов (данный этап был добавлен в игру недавно, поэтому в ProtocolLib его ещё нет)
    Play - Основной гейплей
    Таким образом, теоретически может быть аж 12 пакетов с одним и тем же айди. Поэтому при поиске пакетов по исходному коду ProtocolLib всегда обращайте внимание, в каком java-классе находится найденный сетевой пакет. В 99% случаев вам будут нужны классы PacketType.Play.Server и PacketType.Play.Client.

    3) Если для поиска по ProtocolLib вы не используете исходный код, а декомпилируете - вам придётся перевести айди пакета из шестнадцатеричной системы в десятичную. В декомпиле все значения указаны именно в десятичной. Если у вас с этим сложности - в интернете полно удобных калькуляторов для перевода из одной системы в другую.

    Вот теперь мы действительно разобрались с поиском типа пакета. Теперь дело за малым.

    Какими данными заполнять пакет?

    Мы возвращаемся на Вики и смотрим описание пакета из нашего примера. Тут мы видим, что пакет включает в себя три числа:
    - Айди сущности Item, которая была подобрана
    - Айди сущности, которая подобрала предмет (Player или Villager, например)
    - Количество поднятых предметов

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

    PHP:
    Item item// Предмет, который был поднят
    Player player// Игрок, который поднял предмет

    ProtocolManager manager ProtocolLibrary.getProtocolManager(); // Менеджер управления пакетами из ProtocolLib
    PacketContainer packet manager.createPacket(PacketType.Player.Server.COLLECT); // Создаём объект пакета

    packet.getIntegers().write(0item.getEntityId());
    packet.getIntegers().write(1player.getEntityId());
    packet.getIntegers().write(2item.getItemStack().getAmount());

    manager.broadcastServerPacket(packet); // Отправляет пакет всем игрокам на сервере
    Выглядит просто, не так ли? Но можно ли сделать ещё проще и понятней? Можно. Для этого существует...

    PacketWrapper

    Мы просто создадим отдельный класс, который будет работать с этим видом пакета.
    Выглядеть этот класс будет примерно так: https://github.com/dmulloy2/PacketW...x/packetwrapper/WrapperPlayServerCollect.java
    Можно взять и этот класс, но придется его подправить. Он был сделан под более старую версию игры, поэтому в нем отсутствует возможность задать количество поднятых предметов. Можем скопировать класс к себе в проект и добавить сеттер количества.
    Помимо этого, для удобства можно заменить айди сущностей на объекты Entity, и уже внутри методов получать их айди. Но не всегда это удобно, поэтому в примере оставил числовой entity id.
    Также обращаю внимание, что WrapperPlayServerCollect наследует общий абстрактный класс для всех остальных пакетов - AbstractPacket: https://github.com/dmulloy2/PacketW...m/comphenix/packetwrapper/AbstractPacket.java
    Его тоже придётся скопировать к себе.

    После этого наш основной код будет выглядеть так:

    PHP:
    WrapperPlayServerCollect packet = new WrapperPlayServerCollect();

    packet.setCollectedEntityId(item.getEntityId());
    packet.setCollectorEntityId(player.getEntityId());
    packet.setCollectedItemsAmount(item getItemStack().getAmount());

    packet.broadcastPacket(); // Отправляем всем игрокам
    Только что мы применили PacketWrapper. Враппер пакетов - это проект, который содержит в себе классы под каждый пакет в игре. Вернее так было раньше. К сожалению, данный проект сильно устарел, поэтому часть пакетов отсутствует, у многих других пакетов изменились передаваемые данные. Поэтому при использовании джава-пакетов из этого проекта крайне рекомендую проверять, действительно ли всё совпадает с информацией на wiki.vg и при необходимости вносить изменения.

    Уже лучше. Но дальше возникает вопрос...

    Как заставить плагин работать на разных версиях ядра?

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

    Прежде всего выясняем, в какой версии добавили это количество. Смотрим разные версии вики, находим, в какой появилась строчка с количеством предметов. Для этого удобно использовать вот эту памятку: https://wiki.vg/Protocol_version_numbers
    Ссылка "page" ведёт на версию страницы, которая описывает нужную версию игры.
    В итоге выясняет, что строчка с количеством предметов появилась на версии 1.11.
    Таким образом, до версии 1.10 мы не должны использовать поле количества, а начиная с 1.11 поле появилось, и его необходимо корректно заполнять.

    Чисто технически это можно сделать по-разному:

    1) Банальным if-условием внутри класса враппера WrapperPlayServerCollect:
    PHP:
    if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.EXPLORATION_UPDATE)) {
        
    this.container.getIntegers().write(2itemsAmount);
    }
    2) Путём создания разных версий врапперов. Это удобно, когда структура пакета поменялась очень сильно или несколько раз за всё время. А чтобы при использовании врапперов не писать каждый раз условия - переложим задачу по созданию и отправке пакетов на наш собственный менеджер пакетов. Это должен быть интерфейс с реализациями под разные версии игры:
    PHP:
    public interface PacketsManager {
        
    void sendItemPickupAnimation(Item itemPlayer collectorint itemsAmount);
    }
    Для версии 1.10 и старше:
    PHP:
    public class PacketsManager_10 implements PacketsManager {
        @
    Override
        
    public void sendItemPickupAnimation(Item itemPlayer collectorint itemsAmount) {
            
    WrapperPlayServerCollect_10 packet = new WrapperPlayServerCollect_10();
            
    packet.setCollectedEntityId(item.getEntityId());
            
    packet.setCollectorEntityId(item.getEntityId());
            
    packet.broadcastPacket();
        }
    }
    Для версии 1.11 и выше:
    PHP:
    public class PacketsManager_11 implements PacketsManager {
        @
    Override
        
    public void sendItemPickupAnimation(Item itemPlayer collectorint itemsAmount) {
            
    WrapperPlayServerCollect_11 packet = new WrapperPlayServerCollect_11();
            
    packet.setCollectedEntityId(item.getEntityId());
            
    packet.setCollectorEntityId(item.getEntityId());
            
    packet.setItemsAmount(itemsAmount);
            
    packet.broadcastPacket();
        }
    }

    Менеджер создаём один раз при запуске плагина, потом буквально одной строкой используем во всех необходимых местах:
    PHP:
    private PacketsManager packetsManager;

    @
    Override
    public void onEnable() {
        if (
    MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.EXPLORATION_UPDATE)) {
            
    this.packetsManager = new PacketsManager_11():
        } else {
            
    this.packetsManager = new PacketsManager_10():
        }
    }

    public 
    void someAction() {
        
    Item item;
        
    Player collector;
        
    int itemsAmount;
        
    this.packetsManager.sendItemPickupAnimation(itemcollectoritemsAmount);
    }
    Готово, ваш плагин работает как на 1.11+, так и на 1.10 и более старых версиях. Тем не менее, я рекомендую всегда тестировать работу пакетов на всех ключевых версиях ядра - в данном случае на 1.10 и 1.11. Точно таким же образом можно реализовать поддержку хоть с 1.8 по 1.20 - само собой, врапперов/условий будет больше, однако если всё организовать грамотно - код по-прежнему будет читабелен.

    Продолжение статьи в следующем посте.
     
    Последнее редактирование: 26 авг 2023
  2. Автор темы
    Dymeth

    Dymeth Активный участник Пользователь

    Баллы:
    98
    Имя в Minecraft:
    Dymeth
    Полезные ссылки:

    - Описания, айдишники и содержимое пакетов: последняя, 1.20.1, 1.19.4, 1.18.2, 1.17.1, 1.16.5, 1.15.2, 1.14.4, 1.13.2, 1.12.2, 1.11.2, 1.10.2, 1.9.4, 1.8.9, 1.7.10, промежуточные релизы и снопшоты
    - Айди пакетов в ProtocolLib: https://github.com/dmulloy2/Protoco...n/java/com/comphenix/protocol/PacketType.java
    - Версии игры в ProtocolLib: https://github.com/dmulloy2/Protoco...phenix/protocol/utility/MinecraftVersion.java
    - Враппер пакетов (устарел): https://github.com/dmulloy2/PacketW...per/src/main/java/com/comphenix/packetwrapper

    Послесловие:

    На текущем этапе всё. В будущем хочу рассказать, как получать пакеты и зачем их отменять.

    Темы, которые могут быть затронуты, если заинтересуют вас:
    - Как работать с пакетами используя маппинги (преимущества и недостатки)
    - Про версии протоколов
    - Как работать с мета-данными сущностей
    - Про битовые маски
    - Как читать и писать собственные типы данных, которых нет в ProtocolLib
    - Пример работы со сложным пакетом по типу пакета чанка

    Жду ваши вопросы и уточнения в комментариях
     
    Последнее редактирование: 26 авг 2023
  3. SlenderMix

    SlenderMix Активный участник Пользователь

    Баллы:
    76
    Имя в Minecraft:
    HIDDEN
    Это прям совсем для детей.
     
  4. SlenderMix

    SlenderMix Активный участник Пользователь

    Баллы:
    76
    Имя в Minecraft:
    HIDDEN
    Зачем пытаться понять немого, если можно изобрести свой язык понятный вам обоим.
     
  5. Автор темы
    Dymeth

    Dymeth Активный участник Пользователь

    Баллы:
    98
    Имя в Minecraft:
    Dymeth
    Начинать нужно с малого. Порой не хватает как раз базовой информации о самых простых вещах. Именно для этого в теме уже указал несколько моментов, про которые раньше не читал в каких-либо материалах.
    И если есть желание увидеть что-то конкретное - всегда открыт к предложениям. Текущий план дополнения статьи указан в послесловии.

    Подправил, спасибо.

    UPD: Дополнил статью деталями, ссылками, информацией о поддержке разных версий ядра
     
    Последнее редактирование: 26 авг 2023

Поделиться этой страницей