[1.7.2][Tutorial]Netty Packet Handling - удобная система пакетов

771
5
Ниже приведен общий абстрактный пакет, который должен быть родителем любого пакета, который вы хотите отправить. Любой результат действия пакета могут быть описаны в специфических методах(handleClientSide и handleServerSide). 

ПРИМЕЧАНИЕ: Все дочерные классы этого класса ДОЛЖНЫ иметь пустой конструктор (Можно использовать несколько конструкторов, но один должен быть пустой!).

Код:
package you.packethandling;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;

import net.minecraft.entity.player.EntityPlayer;

/**
 * Класс AbstractPacket. Должен быть родителем всех пакетов, желающих использовать PacketPipeline. 
 * @author sirgingalot
 */
public abstract class AbstractPacket{

    /**
     * Кодирование пакетные данные в поток ByteBuf. Сложные наборы данных, возможно, потребуют конкретных обработчиков данных(например, ItemStack) (См. {cpw.mods.fml.common.network.ByteBuffUtils}) 
  * 
  * @param ctx контекст канала 
  * @param buffer буфер для кодирования в  
     */
    public abstract void encodeInto(ChannelHandlerContext ctx, ByteBuf buffer);

    /**
     * Декодировать пакет данных из потока ByteBuf. Сложные наборы данных, возможно, потребуют конкретных обработчиков данных(например, ItemStack) (См. {cpw.mods.fml.common.network.ByteBuffUtils})
     *
     * @param ctx контекст канала 
     * @param buffer буфер для кодирования из
     */
    public abstract void decodeInto(ChannelHandlerContext ctx, ByteBuf buffer);

    /**
     * Действия пакета на стороне клиента. Обратите внимание, это происходит после завершения декодирования. 
     *
     * @param player игрок
     */
    public abstract void handleClientSide(EntityPlayer player);

    /**
     * Действия пакета на стороне сервера. Обратите внимание, это происходит после завершения декодирования.
     *
     * @param player игрок
     */
    public abstract void handleServerSide(EntityPlayer player);
}

Обработчик пакетов.

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

ПРИМЕЧАНИЕ: Не забудьте переименовать канал на свой, а не как в туториале("TUT").

Код:
package you.packethandling;

import java.util.*;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;

import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.INetHandler;
import net.minecraft.network.NetHandlerPlayServer;

import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.network.FMLEmbeddedChannel;
import cpw.mods.fml.common.network.FMLOutboundHandler;
import cpw.mods.fml.common.network.NetworkRegistry;
import cpw.mods.fml.common.network.internal.FMLProxyPacket;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;

/**
 * Класс PacketPipeline. Направляет все зарегистрированные пакетные данные на обработку. 
 * @author sirgingalot
 * Некоторый код: cpw
 */
@ChannelHandler.Sharable
public class PacketPipeline extends MessageToMessageCodec<FMLProxyPacket, AbstractPacket>{

    private EnumMap<Side, FMLEmbeddedChannel>           channels;
    private LinkedList<Class<? extends AbstractPacket>> packets           = new LinkedList<Class<? extends AbstractPacket>>();
    private boolean                                     isPostInitialised = false;

    /**
     * Зарегистрировать свой пакет. Дискриминаторы устанавливаются автоматически. 
     *
     * @param clazz класс пакета, который необходимо зарегистрировать.
     *
     * @return Вернуть тру, если регистрация была успешна. Отказ может произойти, если 256 пакетов было зарегистрировано или если в реестре уже содержится этот пакет.
     */
    public boolean registerPacket(Class<? extends AbstractPacket> clazz) {
        if(packets.size() > 256){
            return false;
        }

        if(packets.contains(clazz)){
            return false;
        }

        if(isPostInitialised){
            return false;
        }
 packets.add(clazz);
        return true;
    }

    //Кодирование пакета, в том числе настройки дискриминатора.
    @Override
    protected void encode(ChannelHandlerContext ctx, AbstractPacket msg, List<Object> out) throws Exception {
        ByteBuf buffer = Unpooled.buffer();
        Class<? extends AbstractPacket> clazz = msg.getClass();
        if (!packets.contains(msg.getClass())) {
            throw new NullPointerException("No Packet Registered for: " + msg.getClass().getCanonicalName());
        }
        byte discriminator = (byte) this.packets.indexOf(clazz);
        buffer.writeByte(discriminator);
        msg.encodeInto(ctx, buffer);
        FMLProxyPacket proxyPacket = new FMLProxyPacket(buffer.copy(), ctx.channel().attr(NetworkRegistry.FML_CHANNEL).get());
        out.add(proxyPacket);
    }

    //Декодирования и обработка пакета 
    @Override
    protected void decode(ChannelHandlerContext ctx, FMLProxyPacket msg, List<Object> out) throws Exception {
        ByteBuf payload = msg.payload();
        byte discriminator = payload.readByte();
        Class<? extends AbstractPacket> clazz = this.packets.get(discriminator);
        if (clazz == null) {
            throw new NullPointerException("No packet registered for discriminator: " + discriminator);
        }
        AbstractPacket pkt = clazz.newInstance();
        pkt.decodeInto(ctx, payload.slice());
        EntityPlayer player;
        switch(FMLCommonHandler.instance().getEffectiveSide()){
            case CLIENT:
                player = this.getClientPlayer();
                pkt.handleClientSide(player);
                break;
            case SERVER:
                INetHandler netHandler = ctx.channel().attr(NetworkRegistry.NET_HANDLER).get();
                player = ((NetHandlerPlayServer) netHandler).playerEntity;
                pkt.handleServerSide(player);
                break;
            default:
        }
        out.add(pkt);
    }

    //Метод для создания нового канала в FMLInitializationEvent.
    public void initialise() {
        channels = NetworkRegistry.INSTANCE.newChannel("TUT", this); //Поменяйте "TUT" на свое имя канала
    }

    //Метод для создания нового канала в FMLPostInitializationEvent.
    //Гарантирует, что пакетные дискриминаторы являются общими между сервером и клиентом с помощью логической сортировки.
    public void postInitialise(){
        if (isPostInitialised){
            return;
        }
        isPostInitialised = true;
        Collections.sort(packets, new Comparator<Class<? extends AbstractPacket>>(){
            public int compare(Class<? extends AbstractPacket> clazz1, Class<? extends AbstractPacket> clazz2){
                int com = String.CASE_INSENSITIVE_ORDER.compare(clazz1.getCanonicalName(), clazz2.getCanonicalName());
                if (com == 0){
                    com = clazz1.getCanonicalName().compareTo(clazz2.getCanonicalName());
                }
                return com;
            }
        });
    }

 //Простой метод, который возвращает клиенсткого игрока.
    @SideOnly(Side.CLIENT)
    private EntityPlayer getClientPlayer(){
        return Minecraft.getMinecraft().thePlayer;
    }

    /**
     * Отправить пакет всем.
  *
     * По материалам когда cpw's из cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper.
     *
     * @param message Пакет, который нужно отправить.
     */
    public void sendToAll(AbstractPacket message){
        channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.ALL);
        channels.get(Side.SERVER).writeAndFlush(message);
    }

    /**
     * Отправить пакет определенному игроку
     * 
     * По материалам когда cpw's из cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper
     *
     * @param message Пакет, который нужно отправить.
     * @param player  Игрок, которому нужно отправить пакет.
     */
    public void sendTo(AbstractPacket message, EntityPlayerMP player){
        channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.PLAYER);
        channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(player);
        channels.get(Side.SERVER).writeAndFlush(message);
    }

    /**
     * Отправить пакет всем в определенном диапазоне точки
     * 
     * По материалам когда cpw's из cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper
     *
     * @param message Пакет, который нужно отправить.
     * @param point Точки, {cpw.mods.fml.common.network.NetworkRegistry.TargetPoint} в которых нужно отправить пакет
     */
    public void sendToAllAround(AbstractPacket message, NetworkRegistry.TargetPoint point){
        channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.ALLAROUNDPOINT);
        channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(point);
        channels.get(Side.SERVER).writeAndFlush(message);
    }

    /**
     * Отправить пакет в определенном измерении
     * 
     * По материалам когда cpw's из cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper
     *
     * @param message Пакет, который нужно отправить.
     * @param dimensionId Айди измерения.
     */
    public void sendToDimension(AbstractPacket message, int dimensionId){
        channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.DIMENSION);
        channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(dimensionId);
        channels.get(Side.SERVER).writeAndFlush(message);
    }

    /**
     * Отправить пакет на сервер.
     * 
     * По материалам когда cpw's из cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper
     *
     * @param message Пакет, который нужно отправить.
     */
    public void sendToServer(AbstractPacket message){
        channels.get(Side.CLIENT).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.TOSERVER);
        channels.get(Side.CLIENT).writeAndFlush(message);
    }
}

Регистрация PacketPipeline в главном классе мода.

   
Код:
public static final PacketPipeline packetPipeline = new PacketPipeline();

    @EventHandler
    public void initialise(FMLInitializationEvent evt){
        packetPipeline.initialise();
        packetPipeline.registerPacket(YourPacket.class); //Ну и другие ваши пакеты аналогично.
    }

    @EventHandler
    public void postInitialise(FMLPostInitializationEvent evt){
        packetPipeline.postInitialise();
    }

Пример отправки пакета.
Давайте, например, сделаем пакет открытия гуи на сервере(это нужно, когда открываешь гуи на кнопку в KeyHandler'e).

Код:
public class PacketOpenGui extends AbstractPacket{

    int id;

    public PacketOpenGui(){
 //Как я и говорил, должен быть пустой конструктор.
    }

    public PacketOpenGui(int guiId){
        id = guiId;
    }

    public void encodeInto(ChannelHandlerContext ctx, ByteBuf buffer){
        buffer.writeInt(id);
    }

    public void decodeInto(ChannelHandlerContext ctx, ByteBuf buffer){
        id = buffer.readInt();
    }

    public void handleClientSide(EntityPlayer player){
 //На клиенте ничего не делаем.
    }

    public void handleServerSide(EntityPlayer player){
        player.openGui(YourMod.instance, id, player.worldObj, (int)player.posX, (int)player.posY, (int)player.posZ); //id - айди нашего гуи.
    }
}

Потом в KeyHandler'e просто пишем YourMod.packetPipeline.sendToServer(new PacketOpenGui(id));

Оригинальная статья: тык.
 

necauqua

когда-то был anti344
Администратор
1,216
27
172
Огосподибожемойсколькоересивсякой.
У меня как-то попроще получалось, например без целых классов для пакетов и наследуя классы, в которых уже есть ересь такого рода.
 
771
5
anti344 написал(а):
Огосподибожемойсколькоересивсякой.
У меня как-то попроще получалось, например без целых классов для пакетов и наследуя классы, в которых уже есть ересь такого рода.
Ну, этот метод сейчас многие юзают, даже тинкер.
 

necauqua

когда-то был anti344
Администратор
1,216
27
172
Ну как-бы у тебя просто есть готовые реализации, которые ты не используешь, мне, например, достаточно посылать массив байтов куда надо(сервер/игрок/клиент/не_важно) и там уже его обработать(достать айди и нужную инфу). А это сделать можно гораздо проще.
 
771
5
Ну фиг его знает, я в них не шарю,  мне так легче и удобнее.
Запилил бы ты видео по пакетам, было бы классно.
 
905
5
Меняешь размер кода на непонятность. По тинкеру и БК учатся же, там поэтому особых усложнений нет.
 

necauqua

когда-то был anti344
Администратор
1,216
27
172
И откуда там непоянтность? Все понятно и просто, просто те классы, что делают дело, находятся не у тебя, а у Forge и Netty.
 
771
5
Я просто не понимаю что отправлять, куда отправлять, когда отправлять и т.д...
 
Сверху