- 216
- 6
- 19
Для 1.7.10 и, возможно, выше.
Например, это может пригодится если вы хотите сделать меню кланов. У вас есть плагин (либо самописный, либо с исходниками) и вам необходимо реализовать на клиенте ГУИ с информацией и всякими кнопочкам для редактирования ("Выйти из клана", "Повысить игрока" и бла-бла-бла).
Мой основная тема.
Часть мода
Так же, нам нужны абстрактные классы отдельно для серверных пакетов (те пакеты, что мод будет принимать) и для клиентских (те пакеты, которые будут отправлены на сервер)
Регистрируем дискриминаторы пакетов при запуске
Регистрируем обработчик при запуске
Все, должно работать! Ошибки пишите в комментарии, отзывы и предложения туда же
Обещал выпустить эту тему намного раньше, но время не хватило даже на одну тему :c
Например, это может пригодится если вы хотите сделать меню кланов. У вас есть плагин (либо самописный, либо с исходниками) и вам необходимо реализовать на клиенте ГУИ с информацией и всякими кнопочкам для редактирования ("Выйти из клана", "Повысить игрока" и бла-бла-бла).
Мой основная тема.
Часть мода
Начнем сие процессию с создания класса-обработчика пакетов в МОДЕ.
Код:
/** Created by keelfy */
//Обязательно этот обработчик должен работать только на клиенте
@SideOnly(Side.CLIENT)
public class PacketHandlerClient
{
//Список каналов
private EnumMap<Side, FMLEmbeddedChannel> channels;
public PacketHandlerClient()
{
//Добавляем в список новый канал, свой (будет использован для передачи данных)
this.channels = NetworkRegistry.INSTANCE.newChannel("yourschanel", new ChannelCodec());
//Задаем слушателя нашего канала (в моем случае, ChannelHandler(), этот класс объявится ниже)
FMLEmbeddedChannel clientChannel = this.channels.get(Side.CLIENT);
String codec = clientChannel.findChannelHandlerNameForType(ChannelCodec.class);
clientChannel.pipeline().addAfter(codec, "yourschanel", new ChannelHandler());
}
//Создает пакет (нужный для отправки) из того класса-пакета, который был создан у нас в моде
public Packet createPacketFrom(BasePacket msg, Side side)
{
return this.channels.get(side).generatePacketFrom(msg);
}
//Метод для отправки указанного пакета к серверу
public void sendPacketToServer(Packet packet)
{
this.channels.get(Side.CLIENT).writeOutbound(packet);
}
//Метод для отправки указанного пакета к серверу (Перегруженный метод, создает пакет из нашего класса)
public static void sendPacketToServer(BasePacket packet)
{
Packet generatedPacket = this.createPacketFrom(packet, Side.SERVER);
this.sendPacketToServer(generatedPacket);
}
//Вот и ChannelHandler(), который я регистрировал как слушателя нашего канала выше
//Класс BasePacket я создам позже, пока не обращайте внимания на ошибку
private static class ChannelHandler extends SimpleChannelInboundHandler<BasePacket>
{
//Метод, слушающий канал (унаследован)
@Override
protected void channelRead0(ChannelHandlerContext ctx, BasePacket packet) throws Exception
{
//DEBUG
System.out.println("Got packet: " + packet.getClass().getSimpleName());
//Проверяем на то, какой пакет был получен от плагина
try {
//Нам необходим экземпляр класса обработчика пакетов
PacketHandlerClient handler = new PacketHandlerClient();
//Класс пакета так же, будет создан позже (Мы, ради примера, попробуем передать данные об имени игрока через пакеты, как только игрок пошлет запрос на передачу (Да, я знаю что это бесполезно, но это все-таки пример :D))
if (packet instanceof C0PacketName)
//Просто вызываем метод в котором будут происходить все нужные нам действия при получении пакета
handler.handleC0PacketName((C0PacketName) packet);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//Этот класс отвечает за установку дискриминаторов для пакетов, без него сервер будет часто выдавать ошибку
private static class ChannelCodec extends FMLIndexedMessageToMessageCodec<BasePacket>
{
public ChannelCodec()
{
for (EnumPacket type : EnumPacket.values())
{
this.addDiscriminator(type.ordinal(), type.getPacketClass());
}
}
@Override
public void encodeInto(ChannelHandlerContext ctx,BasePacket msg, ByteBuf target) throws Exception
{
msg.write(target);
}
@Override
public void decodeInto(ChannelHandlerContext ctx, ByteBuf source, BasePacket msg)
{
msg.read(source);
}
}
//Метод, вызванный в слушателе канала
private void handleC0PacketName(C0PacketName packet)
{
//Выводим в консоль строку с из полученного пакета
Constants.log.info(packet.payload);
//Если вам необходимо задать, при получении пакета, какую-либо строку в гуи (Я надеюсь, с гуи, при необходимости вы разберетесь)
//YourGUI.setString(packet.payload);
}
}
Код:
/** Created by keelfy */
//Этот абстрактный класс - основа для остальных пакетов
public abstract class BasePacket {
//Да-да, нам нужен пустой конструктор
public BasePacket(){}
//Метод для записи в поток данных
public abstract void write(ByteBuf data) throws IndexOutOfBoundsException;
//Метод для чтения из потока данных
public abstract void read(ByteBuf data) throws IndexOutOfBoundsException;
//ООО-чень полезный метод для записи строки в поток
public void writeString(String string, ByteBuf data) throws IndexOutOfBoundsException
{
byte[] stringBytes = string.getBytes();
data.writeInt(stringBytes.length);
data.writeBytes(stringBytes);
}
//И для чтения строки из потока
public String readString(ByteBuf data) throws IndexOutOfBoundsException
{
int length = data.readInt();
byte[] stringBytes = new byte[length];
data.readBytes(stringBytes);
return new String(stringBytes);
}
}
Код:
//Абстрактный класс для клиента
/** Created by keelfy */
public abstract class AbstractPacketClient extends BasePacket
{
@Override
public final void write(ByteBuf data) {}
}
Код:
//Абстрактный класс для клиента
/** Created by keelfy */
public abstract class AbstractPacketServer extends BasePacket
{
@Override
public final void read(ByteBuf data) {}
}
Код:
/** Created by keelfy */
public enum EnumPacket
{
//Клиентские пакеты
GUILD_NAME(C0PacketName.class), //Этот пакет будет принимать отосланный сервером пакет с данными
//Серверные пакеты
GUILD_GETNAME(S0PacketGetName.class); //Этот пакет будет отправляться на сервер, из нужной части нашего мода, и говорить ему что нужно отправить в клиент иноформацию о нике игрока (в нашем случае)
//Методами ниже я получаю класс пакета (в скобочках указан), нужно для установки дискриминаторов
private Class<? extends BasePacket> packetClass;
private EnumPacket(Class<? extends BasePacket> packetClass) { this.packetClass = packetClass; }
public Class<? extends BasePacket> getPacketClass() { return packetClass; }
}
Код:
//Т.к. этот пакет клиентский = то он будет лишь читать отосланные ему данные (Данные будут в виде строки, ибо мы будем отсылать информацию об имени игрока)
/** Created by keelfy */
public class C0PacketName extends AbstractPacketClient {
public String payload = "";
@Override
public void read(ByteBuf data) throws IndexOutOfBoundsException {
this.payload = this.readString(data);
}
}
Код:
//Серверный пакет, грубо говоря, отправляющий запрос плагину на отправку ножного пакета клиенту
/** Created by keelfy */
public class S0PacketGetName extends AbstractPacketServer
{
@Override
public void write(ByteBuf data) throws IndexOutOfBoundsException {
//Можно использовать любой тип данных для "сигнала" плагину
data.writeBoolean(true);
}
/*
Делать для каждого запроса новый пакет, ведь не выгодно для оптимизации, да?
Тогда, может быть, стоит сделать как я - отправлять строку из Enum'a.
То бишь создать перечисление и в конструкторе пакета указывать какой именно пункт из перечисления я хочу запросить у сервера.
И в плагине создать абсолютно идентичное перечисление, а затем проверять какой пункт был отправлен с сервера.
Пункты из перечисления можно передавать методом ordinal() он возвращает порядковое значение нужного вам пункта из Enum'a
*/
}
Под конец добавим строку в инициализацию мода
Будем, для примера, посылать пакет-запрос на сервер по нажатию кнопки и выводить полученные данные в консоль
Созданим KeyHandler для обработчки биндов, а в чат информацию мы уже и так выводим
И регистрируем обработчик биндов в инициализации
Код:
new PacketHandlerClient();
Созданим KeyHandler для обработчки биндов, а в чат информацию мы уже и так выводим
Код:
/** Created by keelfy */
public class KeyHandler
{
private static final Minecraft mc = Minecraft.getMinecraft();
private List<KeyBinding> keyBindings;
public KeyHandler()
{
this.keyBindings = new ArrayList<KeyBinding>();
this.keyBindings.add(new KeyBinding("Get Player Name", 'p', "key.categories.misc"));
for (KeyBinding binding : this.keyBindings)
{
ClientRegistry.registerKeyBinding(binding);
}
}
@SubscribeEvent
public void key(KeyInputEvent event)
{
int key = Keyboard.getEventCharacter();
for (KeyBinding binding : this.keyBindings)
{
if (key == binding.getKeyCode())
{
PacketHandlerClient.sendPacketToServer(new S0PacketGetName());
return;
}
}
}
}
Код:
new KeyHandler();
Часть плагина
//ВНИМАНИЕ! Название каналов должно быть везде одинаковым! В моде, в плагине.
Код:
//В onEnable()
Bukkit.getMessenger().registerOutgoingPluginChannel(plugin, "yourchannelfrommod");
Bukkit.getMessenger().registerIncomingPluginChannel(plugin, "yourchannelfrommod", new PacketHandlerPlugin(this));
//В onDisable()
Bukkit.getMessenger().unregisterIncomingPluginChannel(plugin, "yourchannelfrommod");
Базовый класс для пакетов
2 абстрактных пакета для клиентских и серверных пакетов
Только в плагине значения клиентского и серверного пакетов меняются. Клиентский отправляет пакеты, серверный - наобарот
Сами пакеты
Код:
//Без комментариев. Идентичен с его собратом из мода
/** Created by keelfy */
public abstract class BasePacket
{
public abstract void write(ByteBuffer data) throws BufferOverflowException;
public abstract void read(ByteBuffer data) throws BufferUnderflowException;
//Только добавился один метод. Отвечает за настройку размера пакета
public abstract int getSize();
public static void writeString(String string, ByteBuffer data) throws BufferOverflowException
{
byte[] stringBytes = string.getBytes();
data.putInt(stringBytes.length);
data.put(stringBytes);
}
public static String readString(ByteBuffer data) throws BufferUnderflowException
{
int length = data.getInt();
byte[] stringBytes = new byte[length];
data.get(stringBytes);
return new String(stringBytes);
}
}
Только в плагине значения клиентского и серверного пакетов меняются. Клиентский отправляет пакеты, серверный - наобарот
Код:
/** Created by keelfy */
public abstract class AbstractPacketServer extends BasePacket
{
@Override
public void read(ByteBuffer data) throws BufferUnderflowException {}
}
Код:
/** Created by keelfy */
public abstract class AbstractPacketClient extends BasePacket
{
@Override
public final void write(ByteBuffer data) throws BufferUnderflowException {}
//При отправке нам необходимо указывать размер отправляемого пакета
@Override
public final int getSize()
{
return 0;
}
}
Код:
//Пакет - приемник пакета в моде S0PacketGetName (Отправляет информацию)
/** Created by keelfy */
public class C0PacketGetName extends AbstractPacketClient
{
//Пустой конструктор? Надо.
public C0PacketGetName() {}
@Override
public void read(ByteBuffer data){} //Нам не надо ничего считывать из пакета, мы будем производить действия просто при его получении
}
Код:
//Пакет - отправитель для пакета в моде C0PacketName (Принимаем информацию)
/** Created by keelfy */
public class S0PacketName extends AbstractPacketServer {
//Инициализируем пременную имени игрока
public String name = "";
public S0PacketName() {}
//Указываем ник игрока при отправке пакета
public S0PacketName(String name) {
this.name = name;
}
//Отправляем ник в виде строки
@Override
public void write(ByteBuffer data) throws BufferOverflowException {
writeString(name, data);
}
@Override
public int getSize() {
return Byte.SIZE;
}
}
Код:
/** Created by keelfy */
public enum EnumPacket {
//Клиентские пакеты
GUILD_NAME(S0PacketName.class), //Этот пакет будет отправлять пакет с именем
//Серверные пакеты
GUILD_GETNAME(C0PacketGetName.class); //Этот пакет будет ловить информацию, отправленную модом
private Class<? extends BasePacket> packetClass;
private EnumPacket(Class<? extends BasePacket> packetClass) { this.packetClass = packetClass; }
public Class<? extends BasePacket> getPacketClass() { return packetClass; }
}
Код:
//Сие творение ОТ ЧАСТИ не мое, так что не осмелюсь тут ставить свои копирайты
public class PacketManager
{
//Коллекция дискриминаторов
private static HashMap<Class<? extends BasePacket>, Byte> discriminators;
//Коллекция классов-пакетов
private static HashMap<Byte, Class<? extends BasePacket>> classes;
public PacketManager()
{
//Инициализируем нужные переменные
discriminators = new HashMap<Class<? extends BasePacket>, Byte>();
classes = new HashMap<Byte, Class<? extends BasePacket>>();
//Добавляем дискриминаторы для наших пакетов
initDiscriminators();
}
private static void initDiscriminators()
{
//Система схожа с форджевской, только метод addDiscriminator() придется делать самим :с
for (EnumPacket type : EnumPacket.values())
{
addDiscriminator((byte) type.ordinal(), type.getPacketClass());
}
}
//Метод для добавления новых дискриминаторов
public static void addDiscriminator(byte discriminator, Class<? extends BasePacket> clazz)
{
//Проверяем, не зарегистрирован ли уже указанный пакет
if (!discriminators.containsKey(clazz) && !classes.containsKey(discriminator))
{
//Добавляем информацию о пакете и даем ему дискриминатор
discriminators.put(clazz, discriminator);
classes.put(discriminator, clazz);
}
}
//Метод для получения дискриминатора
public static byte getDiscriminator(Class<? extends BasePacket> clazz)
{
return discriminators.get(clazz);
}
//Метод для получения класса пакета по дискриминатору
public static Class<? extends BasePacket> getDiscriminatorClass(byte discriminator)
{
return classes.get(discriminator);
}
//Метод опрделеяет какой пакет был получен в виде байтов и возвращает его
public static BasePacket getPacketFromBytes(byte[] bytes) throws BufferUnderflowException, InstantiationException, IllegalAccessException
{
ByteBuffer data = ByteBuffer.wrap(bytes).asReadOnlyBuffer();
byte discriminator = data.get();
Class<? extends BasePacket> packetClass = getDiscriminatorClass(discriminator);
BasePacket packet = packetClass.newInstance();
packet.read(data);
return packet;
}
//Метод записывает указанный пакет в байты для отправки
public static byte[] getBytesFromPacket(BasePacket packet) throws BufferOverflowException
{
byte discriminator = getDiscriminator(packet.getClass());
ByteBuffer buffer = ByteBuffer.allocate(packet.getSize() + Byte.SIZE);
buffer.put(discriminator);
packet.write(buffer);
return buffer.array();
}
}
Код:
//В onEnable()
new PacketManager();
Код:
/** Created by keelfy */
public class PacketHandlerPlugin implements PluginMessageListener
{
//Нужный нам, экземпляр главного класса
private static Main plugin;
public PacketHandlerPlugin(Main plugin) {
this.plugin = plugin;
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] bytes)
{
if(channel.equals("yourchannelfrommod")) { //Проверяем пакет
BasePacket packet; //Класс базового пакета в плагине, создадим позже
try{
//PacketManager мы создадим чуть позже
packet = PacketManager.getPacketFromBytes(bytes);
//Проверяем, какой пакет мы получили
if (packet instanceof C0PacketGetName)
{
//После получения пакета отправляем клиенту информацию с ником игрока
sendPacketToPlayer(player, new S0PacketName(player.getDisplayName()));
}
} catch (Exception e){
e.printStackTrace();
}
}
}
//Метод, отправляеющий пакет игроку
public void sendPacketToPlayer(Player player, BasePacket packet)
{
try {
//Переводим указанный пакет в байты
PacketManager packetManager = new PacketManager();
byte[] bytes = packetManager.getBytesFromPacket(packet);
//Отправляем
player.sendPluginMessage(plugin, "yourchannelfrommod", bytes);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Код:
//В onEnable()
new PacketHandlerPlugin(this);
Все, должно работать! Ошибки пишите в комментарии, отзывы и предложения туда же
Обещал выпустить эту тему намного раньше, но время не хватило даже на одну тему :c
Последнее редактирование: