[1.7-1.16]Пакетная система CodeChickenLib

7,166
329
1,544
Данный урок писался для 1.7.10 и код тестировался на этой версии. Но в библиотеке не изменился api в 1.8, наверное, тоже пойдет
Update! Юзаю эту либу на 1.11.2 - интерфейс не поменялся, только несколько новых методов добавилось для записи данных в пакет
Update! Юзаю эту либу на 1.14.4 и 1.15.2 - поменялась регистрация хандлеров, апи в целом осталось тем же
Введение​
Предполагаю, что тему могут читать совсем новички в моддинге, поэтому поясню, что такое пакеты и зачем они могут быть нужны.​
Майнкрафт состоит из сервера и клиента(даже в синглплеере), которым соответствует клиентская и серверная стороны кода. Пакет - это сообщение, отправляемое с одной стороны на другую.
Каждая сторона имеет какую-то свою информацию. Например, клиент знает, что игрок нажимает левую кнопку мыши, но сервер не знает об этом. Поэтому клиент должен отправить на сервер пакет о действии атаки. Сервер примет пакет и соответствующе отреагирует.
Подобное поведение мы и будет реализовывать для примера.

Для начала расскажу о плюсах и минусах использования пакетной системы CodeChickenLib​

Плюсы:
  • Простота использования
  • Каждый пакет не требуется регистрировать отдельно
  • Хороша для небольшого количества пакетов
  • Получается мало кода
  • CCL на первой странице курса по популярности и на второй по закачкам, библиотека уже содержится во множестве сборок
  • Апи не зависит от версии майнкрафта - ваши моды легче портировать
Минусы:
  • При большом количестве пакетов обработчики сильно распухают

Архитектура пакетной системы CodeChickenLib​
1600282720329.png
Реализации интерфейсов IClientPacketHandler и IServerPacketHandler - сущности, отвечающие за обработку принятых пакетов со стороны клиента и со стороны сервера.
PacketCustom - класс пакетов. Каждый пакет имеет канал назначения("строка") и тип-идентификатор(целое число >=1). В пакет могут быть записаны какие-то данные при помощи write-методов и прочтены при помощи read-методов
Java:
//Каждому write-методу в списке соответствует read-метод
writeBoolean
writeByte
writeShort
writeInt
writeFloat
writeDouble
writeLong
writeChar
writeVarInt //формат переменной длины(в битах), подробнее тут https://wiki.vg/Data_types
writeVarShort //формат переменной длины
writeVarLong//формат переменной длины
writeArray //записывает массив байтов byte[]
writeString
writeUUID
writeEnum
writeResourceLocation
writePos //записывает BlockPos
writeVector
writeNBTTagCompound
writeItemStack
writeFluidStack

Добавление библиотеки в проект​
Тут есть два варианта.
Скачать можно тут: CodeChicken Lib 1.8.+ или из репозитория. Выберите версию deobf для разработки, для релиза - universal.
После того, как скачали, помещаем библиотеку в папку ./libs/ проекта и обновляем/пересобираем gradle-проект. В intellij idea достаточно нажать на 1575200696518.pngRrefresh all gradle projects. В эклипсе не пробовал, но пересборка проекта и повторный импорт точно подействуют.
Для старых версий forge может потребоваться добавить в build.gradle зависимость от папки libs
Gradle (Groovy):
dependencies {
    compile fileTree(dir: './libs/', include: '*.jar')
}
Спасибо @Agravaine :j за совет
Gradle (Groovy):
repositories {
    maven {
        name = "chickenbones"
        url = "https://chickenbones.net/maven" //или https://maven.covers1624.net/
    }
}

dependencies {
    compile "codechicken:CodeChickenLib:<version>:deobf"
}
значения <version>, например, 1.12.2-3.2.3.358
Этот способ может не работать на последних версиях mdk для 1.12.2. Используйте mdk версии 14.23.5.2847 или первый способ добавления.

1600282518844.png​
Реализуемая фича состоит в том, чтобы хоткеем [H]+[1..9] заюзать зелья, лежащие в девяти верхних слотах инвентаря.​
Поэтому в пакет нужно будет записать номер нажатой клавиши, чтобы сервер знал, из какого слота нужно юзать зельку.
Для отслеживания нажатия клавиш будем использовать обработчик InputEvent.KeyInputEvent
Подробнее о событиях: Что такое события и как их ловить. Короткая и простая тема.

Обработчик события:
@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(modid = Main.modid, value = Side.CLIENT)
public class ClientUpdate {
    private static KeyBinding key = new KeyBinding("potionUse", Keyboard.KEY_H, "HotKeys");

    public static void init() { //нужно вызвать это в preinit клиента
        ClientRegistry.registerKeyBinding(key);
    }

    @SubscribeEvent
    public static void onKeyPress(InputEvent.KeyInputEvent e) {
        if (Keyboard.isKeyDown(key.getKeyCode())) {
            KeyBinding[] keyBindsHotbar = Minecraft.getMinecraft().gameSettings.keyBindsHotbar;

            for (int key = 0; key < keyBindsHotbar.length; key++)
                if (Keyboard.isKeyDown(keyBindsHotbar[key].getKeyCode()))
                    new PacketCustom(Main.modid, 1)//Создаем пакет с типом-идентификатором 1
                            .writeInt(key)//Записываем в него индекс хоткея слота инвентаря
                            .sendToServer();//Отправляем
        }
    }
}
Создание нового пакета - new PacketCustom(Main.modid, 1) - выглядит несколько громоздко, если у нас будет больше одного пакета. Поэтому сделаем вспомогательный метод:
Java:
public static PacketCustom createPacket(int type) {
    return new PacketCustom(Main.modid, type);
}

В ClientProxy в обработчик FMLPreInitializationEvent добавим ClientUpdate.init();, чтобы зарегистрировать хоткей.

Отправка пакета у нас готова, теперь нужно написать логику приема.
Для этого делаем реализации IClientPacketHandler и IServerPacketHandler:
Серверный обработчик пакетов:
public class ServerPacketHandler implements IServerPacketHandler {
    @Override
    public void handlePacket(PacketCustom packetCustom, EntityPlayerMP player, INetHandlerPlayServer iNetHandlerPlayServer) {
        switch (packetCustom.getType()) {//чтобы определить тип-идентификатор пакета
            case 1:
                int potionHotSlot = packetCustom.readInt();//читаем из пакета слот, из которого нужно выпить зелье
                ItemStack stack = player.inventory.getStackInSlot(9 + potionHotSlot);
                if (stack.getItem() == Items.POTIONITEM)//если в слоте зелька, то Стив выпьет ее одним глотком :D
                {
                    ItemStack stack1 = Items.POTIONITEM.onItemUseFinish(stack, player.world, player);
                    System.out.println(stack1.getCount());
                    player.inventory.setInventorySlotContents(9 + potionHotSlot,
                            stack1);
                }
                break;
            default:

        }
    }
}
На клиент мы не отправляем никакие пакеты, но сделаем для него обработчик тоже
Клиентский обработчик пакетов:
public class ClientPacketHandler implements IClientPacketHandler {
    @Override
    public void handlePacket(PacketCustom packetCustom, Minecraft minecraft, INetHandlerPlayClient iNetHandlerPlayClient) {
        switch (packetCustom.getType()) {//Определяем тип пакета
            default:
        }
    }
}
Теперь нужно зарегистрировать обработчики пакетов. Делать это нужно во время FMLPreInitializationEvent
Java:
public class CommonProxy {
    public void preinit(FMLPreInitializationEvent e) {
        //Даже, если у вас есть отдельный класс для серверного прокси, все равно регистрацию серверного обработчика делайте здесь
        PacketCustom.assignHandler(Main.modid/*имя канала не длиннее 20*/, new ServerPacketHandler());
    }
}

public class ClientProxy extends CommonProxy {
    public void preinit(FMLPreInitializationEvent e) {
        super.preinit(e);
        PacketCustom.assignHandler(Main.modid, new ClientPacketHandler());
        ClientUpdate.init();
    }
}
На новых версиях нужно в конструкторе главного класса мода вызвать PacketCustomChannelBuilder
Java:
PacketCustomChannelBuilder
    .named(name)//имя канала
    .assignClientHandler(() -> ClientPacketHandler::new)
    .assignServerHandler(() -> ServerPacketHandler::new)
    .build();

Запускаем, заходим в локальный мир, или на локальный сервер, тестируем.

Ссылки
Github: hohserg1/CCL-example

На этом все, надеюсь, вам понравилось)
 
Последнее редактирование:
Тут многие java-то знают на уровне копипаста, а ты scala суешь :)
А есть вообще преимущества над форжевскими пакетами?
 
И чем они отличаются вообще?
 
Dahaka написал(а):
Тут многие java-то знают на уровне копипаста, а ты scala суешь :)
А есть вообще преимущества над форжевскими пакетами?
Все преимущества - дело вкуса.
Мне, например, нравится интерфейсы, их простота:
2 обработчика, для клиента и сервера, их регистрация и все. Пакеты можно отправлять одной строчкой, что тоже удобно и не захломляет код.
 
Хах, только хотел посоветовать в твоей теме в вопросах эту либу) Жди, скоро сделаю пример для java
 
Вот совсем непонятно. Даже хаскелл больше на джаву похож, чем это отродье. Переделывай
 
Похож-не похож, а писать на scala приятнее, т.к. очень выразительный язык.
Прикрепил пример на java, делающий то же самое. Посмотри, какой он громоздкий.
 
А как к примеру при нажатии кнопки давать эффект зелья тому, на кого смотришь?
 
Похож-не похож, а писать на scala приятнее, т.к. очень выразительный язык.
Прикрепил пример на java, делающий то же самое. Посмотри, какой он громоздкий.

Это была ирония. Там все понятно, не знаю, к чему @Zarak придрался. Хоть объяснил бы, что не ясно
 
Это не относится к теме пакетов. Про сущность, на которую смотришь была тема
 
Точно также, но метод отправки другой
Есть sendToPlayer, sendToClients, посмотри в классе PacketCustom
 
Использование этой либы на версии 1.7.10 является актуальным на данный момент?
И как обстоят дела с безопасностью?
 
Конечно актуально. Безопасность такая же, как с обычными пакетами
 
А CCL есть там?
 
Назад
Сверху