package com.flansmod.common.network;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
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 net.minecraft.network.Packet;
import net.minecraft.network.PacketBuffer;
import net.minecraft.server.MinecraftServer;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.network.FMLEmbeddedChannel;
import net.minecraftforge.fml.common.network.FMLOutboundHandler;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.network.internal.FMLProxyPacket;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import com.flansmod.common.FlansMod;
/**
* Flan's Mod packet handler class. Directs packet data to packet classes.
* @author Flan
* With much inspiration from http://www.minecraftforge.net/wiki/Netty_Packet_Handling
* */
@ChannelHandler.Sharable
public class PacketHandler extends MessageToMessageCodec<FMLProxyPacket, PacketBase>
{
//Map of channels for each side
private EnumMap<Side, FMLEmbeddedChannel> channels;
//The list of registered packets. Should contain no more than 256 packets.
private LinkedList<Class<? extends PacketBase>> packets = new LinkedList<Class<? extends PacketBase>>();
//Whether or not Flan's Mod has initialised yet. Once true, no more packets may be registered.
private boolean modInitialised = false;
/** Store received packets in these queues and have the main Minecraft threads use these */
private ConcurrentLinkedQueue<PacketBase> receivedPacketsClient = new ConcurrentLinkedQueue<PacketBase>();
private HashMap<String, ConcurrentLinkedQueue<PacketBase>> receivedPacketsServer = new HashMap<String, ConcurrentLinkedQueue<PacketBase>>();
/** Registers a packet with the handler */
public boolean registerPacket(Class<? extends PacketBase> cl)
{
if(packets.size() > 256)
{
FlansMod.log("Packet limit exceeded in Flan's Mod packet handler by packet " + cl.getCanonicalName() + ".");
return false;
}
if(packets.contains(cl))
{
FlansMod.log("Tried to register " + cl.getCanonicalName() + " packet class twice.");
return false;
}
if(modInitialised)
{
FlansMod.log("Tried to register packet " + cl.getCanonicalName() + " after mod initialisation.");
return false;
}
packets.add(cl);
return true;
}
@Override
protected void encode(ChannelHandlerContext ctx, PacketBase msg, List<Object> out) throws Exception
{
try
{
//Define a new buffer to store our data upon encoding
ByteBuf encodedData = Unpooled.buffer();
//Get the packet class
Class<? extends PacketBase> cl = msg.getClass();
//If this packet has not been registered by our handler, reject it
if(!packets.contains(cl))
throw new NullPointerException("Packet not registered : " + cl.getCanonicalName());
//Like a packet ID. Stored as the first entry in the packet code for recognition
byte discriminator = (byte) packets.indexOf(cl);
encodedData.writeByte(discriminator);
//Get the packet class to encode our packet
msg.encodeInto(ctx, encodedData);
//Convert our packet into a Forge packet to get it through the Netty system
FMLProxyPacket proxyPacket = new FMLProxyPacket(new PacketBuffer(encodedData.copy()), ctx.channel().attr(NetworkRegistry.FML_CHANNEL).get());
//Add our packet to the outgoing packet queue
out.add(proxyPacket);
}
catch(Exception e)
{
FlansMod.log("ERROR encoding packet");
e.printStackTrace();
}
}
@Override
protected void decode(ChannelHandlerContext ctx, FMLProxyPacket msg, List<Object> out) throws Exception
{
try
{
//Get the encoded data from the incoming packet
ByteBuf encodedData = msg.payload();
//Get the class for interpreting this packet
byte discriminator = encodedData.readByte();
Class<? extends PacketBase> cl = packets.get(discriminator);
//If this discriminator returns no class, reject it
if(cl == null)
throw new NullPointerException("Packet not registered for discriminator : " + discriminator);
//Create an empty packet and decode our packet data into it
PacketBase packet = cl.newInstance();
packet.decodeInto(ctx, encodedData.slice());
//Check the side and handle our packet accordingly
switch(FMLCommonHandler.instance().getEffectiveSide())
{
case CLIENT :
{
receivedPacketsClient.offer(packet);
//packet.handleClientSide(getClientPlayer());
break;
}
case SERVER :
{
INetHandler netHandler = ctx.channel().attr(NetworkRegistry.NET_HANDLER).get();
EntityPlayer player = ((NetHandlerPlayServer)netHandler).playerEntity;
if(!receivedPacketsServer.containsKey(player.getName()))
receivedPacketsServer.put(player.getName(), new ConcurrentLinkedQueue<PacketBase>());
receivedPacketsServer.get(player.getName()).offer(packet);
//packet.handleServerSide();
break;
}
}
}
catch(Exception e)
{
FlansMod.log("ERROR decoding packet");
e.printStackTrace();
}
}
public void handleClientPackets()
{
for(PacketBase packet = receivedPacketsClient.poll(); packet != null; packet = receivedPacketsClient.poll())
{
packet.handleClientSide(getClientPlayer());
}
}
public void handleServerPackets()
{
for(String playerName : receivedPacketsServer.keySet())
{
ConcurrentLinkedQueue<PacketBase> receivedPacketsFromPlayer = receivedPacketsServer.get(playerName);
EntityPlayerMP player = MinecraftServer.getServer().getConfigurationManager().getPlayerByUsername(playerName);
for(PacketBase packet = receivedPacketsFromPlayer.poll(); packet != null; packet = receivedPacketsFromPlayer.poll())
{
packet.handleServerSide(player);
}
}
}
/** Initialisation method called from FMLInitializationEvent in FlansMod */
public void initialise()
{
channels = NetworkRegistry.INSTANCE.newChannel("FlansMod", this);
registerPacket(PacketAAGunAngles.class);
registerPacket(PacketBaseEdit.class);
registerPacket(PacketBreakSound.class);
registerPacket(PacketBuyArmour.class);
registerPacket(PacketBuyWeapon.class);
registerPacket(PacketCraftDriveable.class);
registerPacket(PacketDriveableControl.class);
registerPacket(PacketDriveableDamage.class);
registerPacket(PacketDriveableGUI.class);
registerPacket(PacketDriveableKey.class);
registerPacket(PacketDriveableKeyHeld.class);
registerPacket(PacketFlak.class);
//registerPacket(PacketGunFire.class);
registerPacket(PacketGunPaint.class);
registerPacket(PacketKillMessage.class);
registerPacket(PacketMechaControl.class);
registerPacket(PacketMGFire.class);
registerPacket(PacketMGMount.class);
registerPacket(PacketOffHandGunInfo.class);
registerPacket(PacketPlaneControl.class);
registerPacket(PacketPlaySound.class);
registerPacket(PacketReload.class);
registerPacket(PacketRepairDriveable.class);
registerPacket(PacketRoundFinished.class);
registerPacket(PacketSeatUpdates.class);
registerPacket(PacketSelectOffHandGun.class);
registerPacket(PacketShotData.class);
registerPacket(PacketTeamInfo.class);
registerPacket(PacketTeamSelect.class);
registerPacket(PacketVehicleControl.class);
registerPacket(PacketVoteCast.class);
registerPacket(PacketVoting.class);
registerPacket(PacketRequestDebug.class);
registerPacket(PacketLoadoutData.class);
registerPacket(PacketOpenRewardBox.class);
registerPacket(PacketAddSingleRewardBoxInstance.class);
}
/** Post-Initialisation method called from FMLPostInitializationEvent in FlansMod
* Logically sorts the packets client and server side to ensure a matching ordering */
public void postInitialise()
{
if(modInitialised)
return;
modInitialised = true;
//Define our comparator on the fly and apply it to our list
Collections.sort(packets,
new Comparator<Class<? extends PacketBase>>()
{
@Override
public int compare(Class<? extends PacketBase> c1, Class<? extends PacketBase> c2)
{
int com = String.CASE_INSENSITIVE_ORDER.compare(c1.getCanonicalName(), c2.getCanonicalName());
if(com == 0)
com = c1.getCanonicalName().compareTo(c2.getCanonicalName());
return com;
}
});
}
@SideOnly(Side.CLIENT)
private EntityPlayer getClientPlayer()
{
return Minecraft.getMinecraft().thePlayer;
}
/** Send a packet to all players */
public void sendToAll(PacketBase packet)
{
channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.ALL);
channels.get(Side.SERVER).writeAndFlush(packet);
}
/** Send a packet to a player */
public void sendTo(PacketBase packet, 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(packet);
}
/** Send a packet to all around a point */
public void sendToAllAround(PacketBase packet, 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(packet);
}
/** Send a packet to all in a dimension */
public void sendToDimension(PacketBase packet, 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(packet);
}
/** Send a packet to the server */
public void sendToServer(PacketBase packet)
{
channels.get(Side.CLIENT).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.TOSERVER);
channels.get(Side.CLIENT).writeAndFlush(packet);
}
//Vanilla packets follow
/** Send a packet to all players */
public void sendToAll(Packet packet)
{
MinecraftServer.getServer().getConfigurationManager().sendPacketToAllPlayers(packet);
}
/** Send a packet to a player */
public void sendTo(Packet packet, EntityPlayerMP player)
{
player.playerNetServerHandler.sendPacket(packet);
}
/** Send a packet to all around a point */
public void sendToAllAround(Packet packet, NetworkRegistry.TargetPoint point)
{
MinecraftServer.getServer().getConfigurationManager().sendToAllNear(point.x, point.y, point.z, point.range, point.dimension, packet);
}
/** Send a packet to all in a dimension */
public void sendToDimension(Packet packet, int dimensionID)
{
MinecraftServer.getServer().getConfigurationManager().sendPacketToAllPlayersInDimension(packet, dimensionID);
}
/** Send a packet to the server */
public void sendToServer(Packet packet)
{
Minecraft.getMinecraft().thePlayer.sendQueue.addToSendQueue(packet);
}
/** Send a packet to all around a point without having to create one's own TargetPoint */
public void sendToAllAround(PacketBase packet, double x, double y, double z, float range, int dimension)
{
sendToAllAround(packet, new NetworkRegistry.TargetPoint(dimension, x, y, z, range));
}
}