• Гость, темы в этой категории создаются через Ресурсы!
    После создания туториала в Ресурсах в этом форуме будет автоматически создана тема для обсуждения.

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

hohserg1

Золотая лига
Сообщения
3,287
Лучшие ответы
83
Симпатии
217
#1
Данный урок писался для 1.7.10 и код тестировался на этой версии. Но в библиотеке не изменился api в 1.8, наверное, тоже пойдет
Update! Юзаю эту либу на 1.11.2 - интерфейс не поменялся, только несколько новых методов добавилось для записи данных в пакет
Предполагаю, что тему могут читать совсем новички в моддинге, поэтому поясню, зачем могут быть нужны пакеты.
Допустим, игрок нажимает клавишу, делает какой-то жест мышкой или производит какое-то другое действие на стороне клиента. А в результате этого действия нужно что-то сделать на стороне сервера. Как сервер узнает о клиентском событии? Клиентская часть мода должна отправить на сервер пакет с информацией о том, что сделал пользователь. И сервер теперь знает, что такой-то игрок, например, нажал такую-то клавишу, поэтому нужно заюзать предмет в это инвентаре.
Это мы и будет реализовывать для примера.

Еще есть вопросы? Почему именно CodeChickenLib? Все просто: эта библиотека требуется для работы NEI, которая включается в большинство сборок.

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

Первое, что нам нужно: обработчики пакетов.
import codechicken.lib.packet.PacketCustom
import codechicken.lib.packet.PacketCustom.{IClientPacketHandler, IServerPacketHandler}


Scala:
class ClientPacketHandler extends IClientPacketHandler{//Клиент не принимает пакетов от сервера, но это шаблон, как делать
override def handlePacket(packetCustom: PacketCustom, minecraft: Minecraft, iNetHandlerPlayClient: INetHandlerPlayClient): Unit = {
    packetCustom.getType match {//Это паттерн-матчинг, здесь это выполняет функцию switch-case, чтобы определить тип пакета
     case <тип пакета, целое число> =>
     case _ =>
   }

}
}
class ServerPacketHandler extends IServerPacketHandler{//Сервер принимает пакет о  нажатии клавиши
import Items.potionitem//импортируем предмет-зелье, мы не раз к нему будем обращаться
override def handlePacket(packetCustom: PacketCustom, player: EntityPlayerMP, iNetHandlerPlayServer: INetHandlerPlayServer): Unit = {
   packetCustom.getType match {
     case 1 =>
      //Можно считывать значения из пакета, методами read... Но в данном случае не нужно: клавиша у нас одна
      val stack= player.inventory.getStackInSlot(9)
      if(stack!=null && stack.getItem==potionitem)//если в 9 слоте зелька, то Стив выпьет ее один глотком :D
      player.inventory.setInventorySlotContents(9,
                                    potionitem.onEaten(stack,player.worldObj,player))
     case _ =>
   }

}
}
Второе, что нам нужно: прокси.
Scala:
@Mod(modid = "modid", version = "1.0", name = "ModName", modLanguage = "scala")
object Main {
@SidedProxy(clientSide="modpackage.ClientProxy", serverSide="modpackage.CommonProxy")//Регистрация прокси.
var proxy: CommonProxy = null//forge сам заполняет это поле
//метод инициализации мода
@EventHandler
def preinit(e: FMLPreInitializationEvent): Unit = {
   proxy.preinit(e)
}

}
Scala:
class CommonProxy{
def preinit(e: FMLPreInitializationEvent): Unit = {//Регистрируем обработчики
   PacketCustom.assignHandler("modid"/*имя канала не длиннее 20*/, new ServerPacketHandler)//Даже, если у вас есть отдельный класс для серверного прокси, все равно регистрацию серверного обработчика делайте здесь
}
}


class ClientProxy extends CommonProxy{
override def preinit(e: FMLPreInitializationEvent): Unit = {
   super.preinit(e)
   PacketCustom.assignHandler("modid", new ClientPacketHandler)
}

}
Отлично, теперь мы можем отправлять пакеты вот так:
Scala:
new PacketCustom("modid",<тип пакета, целое число>).writeString(<записали строку>).writeInt(<записали число>).sendToServer()

Каждый раз писать вызов конструктора и передавать modid - это некрасиво.
Вынесем этот код в отдельный метод:
Scala:
object Packet{//объект - это синглтон, доступен из любого места
def createPacket(t:Int):PacketCustom = new PacketCustom("modid",t)
}

//И теперь мы можем писать так:

import Packet.createPacket //Импортируем, если много вызовов

createPacket(<тип пакета, целое число>).sendToServer()
Посмотрите методы класса PacketCustom. При помощи методов начинающихся на write мы можем записывать данные в пакет, на read мы можем читать из пакета. При помощи sendTo мы можем отправить пакет куда-то(игроку, игрокам, серверу и т.д.)
Теперь нам нужно отловить событие нажатия клавиши на клиенте:
Scala:
class ClientUpdate {
val key=new KeyBinding("potionUse",Keyboard.KEY_H,"HotKeys")
ClientRegistry.registerKeyBinding(key)
@SideOnly(Side.CLIENT)
@SubscribeEvent
def onKeyPress(e:KeyInputEvent): Unit ={//Слушатель события
   if(key.isPressed) {
      Packet.createPacket(1).sendToServer()//Отправляем
   }
}
}
//Добавим в ClientProxy.preinit
FMLCommonHandler.instance().bus().register(new ClientUpdate)
Подробнее о событиях: Что такое события и как их ловить. Короткая и простая тема.
Запускаем, заходим в локальный мир, или на локальный сервер, тестируем.

На этом все, надеюсь, вам понравилось :)

Тот же самый пример, но на java:
ccl_example.rar
 
Последнее редактирование:

Dahaka

Золотая лига
Сообщения
2,312
Лучшие ответы
81
Симпатии
247
#2
Тут многие java-то знают на уровне копипаста, а ты scala суешь :)
А есть вообще преимущества над форжевскими пакетами?
 

svk2140

Каменная лига
Сообщения
608
Лучшие ответы
6
Симпатии
12
#3
И чем они отличаются вообще?
 

hohserg1

Золотая лига
Сообщения
3,287
Лучшие ответы
83
Симпатии
217
#4
Dahaka написал(а):
Тут многие java-то знают на уровне копипаста, а ты scala суешь :)
А есть вообще преимущества над форжевскими пакетами?
Все преимущества - дело вкуса.
Мне, например, нравится интерфейсы, их простота:
2 обработчика, для клиента и сервера, их регистрация и все. Пакеты можно отправлять одной строчкой, что тоже удобно и не захломляет код.
 

hohserg1

Золотая лига
Сообщения
3,287
Лучшие ответы
83
Симпатии
217
#6
Хах, только хотел посоветовать в твоей теме в вопросах эту либу) Жди, скоро сделаю пример для java
 

CoomingSoon

Железная лига
Сообщения
1,227
Лучшие ответы
10
Симпатии
105
#7
Вот совсем непонятно. Даже хаскелл больше на джаву похож, чем это отродье. Переделывай
 

hohserg1

Золотая лига
Сообщения
3,287
Лучшие ответы
83
Симпатии
217
#8
Похож-не похож, а писать на scala приятнее, т.к. очень выразительный язык.
Прикрепил пример на java, делающий то же самое. Посмотри, какой он громоздкий.
 

Зарак

Каменная лига
Сообщения
373
Лучшие ответы
11
Симпатии
36
#9
А как к примеру при нажатии кнопки давать эффект зелья тому, на кого смотришь?
 

CoomingSoon

Железная лига
Сообщения
1,227
Лучшие ответы
10
Симпатии
105
#10
Похож-не похож, а писать на scala приятнее, т.к. очень выразительный язык.
Прикрепил пример на java, делающий то же самое. Посмотри, какой он громоздкий.
Это была ирония. Там все понятно, не знаю, к чему @Zarak придрался. Хоть объяснил бы, что не ясно
 

Зарак

Каменная лига
Сообщения
373
Лучшие ответы
11
Симпатии
36
#11
Я задал вопрос выше?Объясните это , пойму пакеты
 

hohserg1

Золотая лига
Сообщения
3,287
Лучшие ответы
83
Симпатии
217
#12
Это не относится к теме пакетов. Про сущность, на которую смотришь была тема
 

Kreatifchk

Каменная лига
Сообщения
43
Лучшие ответы
0
Симпатии
0
#13
А как наоборот, послать пакет с сервера на клиент?
 

hohserg1

Золотая лига
Сообщения
3,287
Лучшие ответы
83
Симпатии
217
#14
Точно также, но метод отправки другой
Есть sendToPlayer, sendToClients, посмотри в классе PacketCustom
 

Yeti

Каменная лига
Сообщения
217
Лучшие ответы
3
Симпатии
57
#15
Использование этой либы на версии 1.7.10 является актуальным на данный момент?
И как обстоят дела с безопасностью?
 

hohserg1

Золотая лига
Сообщения
3,287
Лучшие ответы
83
Симпатии
217
#16
Конечно актуально. Безопасность такая же, как с обычными пакетами
 
Сверху