1. FMLEventChannel

FML 提供的另一个解决方案,基于 FML 的 EventBus(没错就是第二章里讲的那一个)。有很多 Mod 在用它。下面来做个示范。

// 自行补全需要导入的包

public enum MyNetworkManager {

    INSTANCE;

    //获得一个信道实例。建议使用Modid来命名。
    //当然也可以用别的,保证唯一即可。
    private final FMLEventChannel channel = NetworkRegistry.INSTANCE.newEventDrivenChannel(CHANNEL_NAME);

    private static final String CHANNEL_NAME = "my_mod";

    private MyNetworkManager() {
        //注册listener
        channel.register(this);
    }

    @SubscribeEvent //可加注@SideOnly(Side.CLIENT)
    public void onClientPacketEvent(ClientCustomPacketEvent event) {
        decodeDataClient(event.getPacket().payload(), Minecraft.getMinecraft().thePlayer);
    }

    @SubscribeEvent
    public void onServerPacketEvent(ServerCustomPacketEvent event) {
        decodeDataServer(event.getPacket().payload(), ((NetHandlerPlayServer)event.getHandler()).playerEntity);
    }

    //可加注@SideOnly(Side.CLIENT)
    private void decodeDataClient(ByetBuf input, EntityPlayerSP player) {
        // 服务器->客户端的解包逻辑
        // 解包的第一步逻辑应在这里出现,即识别包的种类
        // 具体实现应将InputStream重新用MyPacket包装后解耦实现
    }

    //但这里就不能@SideOnly(Side.SERVER),因为单机游戏也需要这个。
    //这里的server实质是逻辑服务器端。
    private void decodeDataServer(ByteBuf input, EntityPlayerMP player) {
        // 客户端->服务器的解包逻辑
        // 解包的第一步逻辑应在这里出现,即识别包的种类
        // 具体实现应将InputStream重新用MyPacket包装后解耦实现
    }

    //从这里开始就是一堆封装一样的玩意...

    //向某个维度发包
    public void sendPacketToDim(MyPacket pkt, int dim) {
        channel.sendToDimension(createFMLProxyPacket(pkt), dim);
    }

    //向某个维度的某个点发包
    public void sendPacketAroundPos(MyPacket pkt, int dim, BlockPos pos) {
        // TargetPoint的构造器为:
        // 维度id x坐标 y坐标 z坐标 覆盖范围
        // 其中,覆盖范围指接受此更新数据包的坐标的范围
        // 之所以要强调最后一个参数是double是因为Kotlin并不会帮你把2隐式转换为kotlin.Double....
        channel.sendToAllAround(createFMLProxyPacket(pkt), new NetworkRegistry.TargetPoint(dim, pos.getX(), pos.getY(), pos.getZ(), 2.0D));
    }

    //向某个玩家发包
    public void sendPacketToPlayer(MyPacket pkt, EntityPlayerMP player) {
        channel.sendToPlayer(createFMLProxyPacket(pkt), player);
    }

    //向所有人发包
    public void sendPacketToAll(MyPacket pkt) {
        channel.sendToAll(createFMLProxyPacket(pkt));
    }

    //向服务器发包,这个给客户端用
    public void sendPacketToServer(MyPacket pkt) {
        channel.sendToServer(createFMLProxyPacket(pkt));
    }

    //FMLEventChannel经由这个NetworkHandler暴露出来的方法到此为止

    private FMLProxyPacket createFMLProxyPacket(MyPacket pkt) {
        ByetBuf buffer = Unpooled.buffer();
        packet.writeData(buffer);
        return new FMLProxyPacket(new PacketBuffer(buffer), CHANNEL_NAME);
    }
}

// 简单的数据包实现
public interface MyPacket {

    /**
     * 发包端写数据
     */
    void writeData(ByteBuf out) throws IOException;

    /**
     * 解包端读数据
     */
    void readData(ByteBuf in) throws IOException;

}

就是这样。用法和 SimpleNetworkWrapper 大同小异。

//待补全

1.1. 设计模式

工厂。肯定是工厂啊。

public enum MyDamnitPacketFactory {
    INSTANCE;

    public MyPacket getDummyPacketById(byte id) {
        return PacketType.FACTORY.get(PacketType.VALUES[(int)id]).get();
    }

    private enum PacketType {
        ;
        //按需加枚举类型
        //然后就可以直接按数组进行索引了
        public static final PacketType[] VALUES = PacketType.values()
        public static final Map<PacketType, Supplier<MyPacket>> FACTORY = new EnumMap<>(PacketType.class);
    }
}

看这样多好。根据读取到的第一个byte自动生产空Packet然后解包,NetworkHandler里从此看不见任何实现。 (总感觉多此一举了)