Мультиверсия
В этом разделе рассматриваются архитектурные принципы, на которых основана поддержка мультиверсии в McProtoNet.Protocol.
Главная идея
Для того чтобы лучше понять, как устроена мультиверсия, рассмотрим пакет UseItem
, который претерпел несколько изменений в разных версиях протокола:
Имя | Тип |
---|---|
Hand | VarInt |
Имя | Тип |
---|---|
Hand | VarInt |
Sequence | VarInt |
Имя | Тип |
---|---|
Hand | VarInt |
Sequence | VarInt |
Rotation | Vector2 |
Как видно, общее поле для всех версий — это Hand
, поэтому для этого пакета соответвует класс UseItemPacket
, который содержит только одно поле — Hand: int
.
Сериализация пакета на разных версиях
На других версиях пакет может включать дополнительные поля, которые необходимо записать в сетевой поток, иначе сервер не сможет корректно прочитать пакет. Эта проблема решается тем, что в необщие поля записываются значения по умолчанию. Например, для числовых значений это будет 0, для строк — пустая строка, а для массивов — пустой массив.
В контексте пакета UseItem
на версиях 767-769 в поле Sequence
будет записан 0, а в поле Rotation
— два нуля (так как Vector2
состоит из двух числовых значений).
Конкретные версии
Использование пакетов с общими полями — удобное решение, но могу появиться сценарии, где необходимо записывать в пакет конкретные значения для каждого поля на конкретной версии у пакета. Либо же пакет может меняться из версии в версию настолько сильно, что общих полей нет, либо их крайне мало.
Для решения этой проблемы используются вложенные классы, в которых определены недостающие поля для каждой версии. Эти классы именуются в формате V<min_version>_<max_version>
, и они наследуются от базового класса. Вот как это выглядит для пакета UseItem
.
Серверные пакеты
Принципы архитектуры серверных пакетов схожи с клиентскими, но есть одно отличие: класс с общими полями является абстрактным. При чтении пакета от сервера создаётся конкретный экземпляр вложенного класса, благодаря методу CreateClientboundPacket
из класса PacketFactory
.
Примеры кода
Дабы закрепить теорию, рассмотрим примеры отправки и получения пакетов.
Отправка пакета
Предположим, наша задача — отправить пакет BlockPlace
.
Если нас не интересуют детали для конкретных версий, мы можем отправить пакет следующим образом:
Но если нам нужно контролировать поле InsideBlocks
на версии протокола 500, тогда необходимо использовать вложенный класс V477_758
:
Однако возникает проблема: если клиент подключён на версии протокола ниже 477 или выше 758, метод SendPacket
сгенерирует исключение ProtocolNotSupportException
, так как проверяется поддерживаемая версия протокола с помощью метода IsSupportedVersion
у пакета.
Чтобы избежать этой ошибки, можно использовать метод расширения TrySend
:
Обработка серверных пакетов
Один из часто используемых пакетов от сервера в Minecraft — это пакет Position
, который отвечает за позицию игрока в игровом мире. Рассмотрим, как в McProtoNet.Protocol осуществляется обработка серверных пакетов.
Обработка серверных пакетов проста — нужно лишь проверить тип прочитанного пакета:
Если необходимо обработать конкретные поля на определённых версиях, например, поле DismountVehicle
на версиях от 755 до 761, это делается следующим образом: