package cpw.mods.fml.common.network.simpleimpl; import io.netty.channel.ChannelFutureListener; import java.util.EnumMap; import com.google.common.base.Throwables; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.network.INetHandler; import net.minecraft.network.Packet; import net.minecraft.tileentity.TileEntity; import cpw.mods.fml.common.event.FMLPreInitializationEvent; 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.NetworkRegistry.TargetPoint; import cpw.mods.fml.relauncher.Side; /** * This class is a simplified netty wrapper for those not wishing to deal with the full power of netty. * It provides a simple message driven system, based on a discriminator byte over the custom packet channel. * It assumes that you have a series of unique message types with each having a unique handler. Generally, context should be * derived at message handling time. * * Usage is simple:<ul> * <li>construct, and store, an instance of this class. It will automatically register and configure your underlying netty channel. * * <li>Then, call {@link #registerMessage(Class, Class, byte, Side)} for each message type you want to exchange * providing an {@link IMessageHandler} implementation class as well as an {@link IMessage} implementation class. The side parameter * to that method indicates which side (server or client) the <em>message processing</em> will occur on. The discriminator byte * should be unique for this channelName - it is used to discriminate between different types of message that might * occur on this channel (a simple form of message channel multiplexing, if you will). * <li>To get a packet suitable for presenting to the rest of minecraft, you can call {@link #getPacketFrom(IMessage)}. The return result * is suitable for returning from things like {@link TileEntity#getDescriptionPacket()} for example. * <li>Finally, use the sendXXX to send unsolicited messages to various classes of recipients. * </ul> * * Example * <code> * <pre> * // Request message * public Message1 implements IMessage { * // message structure * public fromBytes(ByteBuf buf) { * // build message from byte array * } * public toBytes(ByteBuf buf) { * // put message content into byte array * } * } * // Reply message * public Message2 implements IMessage { * // stuff as before * } * // Message1Handler expects input of type Message1 and returns type Message2 * public Message1Handler implements IMessageHandler<Message1,Message2> { * public Message2 onMessage(Message1 message, MessageContext ctx) { * // do something and generate reply message * return aMessage2Object; * } * } * // Message2Handler expects input of type Message2 and returns no message (IMessage) * public Message2Handler implements IMessageHandler<Message2,IMessage> { * public IMessage onMessage(Message2 message, MessageContext ctx) { * // handle the message 2 response message at the other end * // no reply for this message - return null * return null; * } * } * * // Code in a {@link FMLPreInitializationEvent} or {@link FMLInitializationEvent} handler * SimpleNetworkWrapper wrapper = NetworkRegistry.newSimpleChannel("MYCHANNEL"); * // Message1 is handled by the Message1Handler class, it has discriminator id 1 and it's on the client * wrapper.registerMessage(Message1Handler.class, Message1.class, 1, Side.CLIENT); * // Message2 is handled by the Message2Handler class, it has discriminator id 2 and it's on the server * wrapper.registerMessage(Message2Handler.class, Message2.class, 2, Side.SERVER); * </pre> * </code> * * * @author cpw * */ public class SimpleNetworkWrapper { private EnumMap<Side, FMLEmbeddedChannel> channels; private SimpleIndexedCodec packetCodec; public SimpleNetworkWrapper(String channelName) { packetCodec = new SimpleIndexedCodec(); channels = NetworkRegistry.INSTANCE.newChannel(channelName, packetCodec); } /** * Register a message and it's associated handler. The message will have the supplied discriminator byte. The message handler will * be registered on the supplied side (this is the side where you want the message to be processed and acted upon). * * @param messageHandler the message handler type * @param requestMessageType the message type * @param discriminator a discriminator byte * @param side the side for the handler */ public <REQ extends IMessage, REPLY extends IMessage> void registerMessage(Class<? extends IMessageHandler<REQ, REPLY>> messageHandler, Class<REQ> requestMessageType, int discriminator, Side side) { registerMessage(instantiate(messageHandler), requestMessageType, discriminator, side); } static <REQ extends IMessage, REPLY extends IMessage> IMessageHandler<? super REQ, ? extends REPLY> instantiate(Class<? extends IMessageHandler<? super REQ, ? extends REPLY>> handler) { try { return handler.newInstance(); } catch (Exception e) { throw Throwables.propagate(e); } } /** * Register a message and it's associated handler. The message will have the supplied discriminator byte. The message handler will * be registered on the supplied side (this is the side where you want the message to be processed and acted upon). * * @param messageHandler the message handler instance * @param requestMessageType the message type * @param discriminator a discriminator byte * @param side the side for the handler */ public <REQ extends IMessage, REPLY extends IMessage> void registerMessage(IMessageHandler<? super REQ, ? extends REPLY> messageHandler, Class<REQ> requestMessageType, int discriminator, Side side) { packetCodec.addDiscriminator(discriminator, requestMessageType); FMLEmbeddedChannel channel = channels.get(side); String type = channel.findChannelHandlerNameForType(SimpleIndexedCodec.class); if (side == Side.SERVER) { addServerHandlerAfter(channel, type, messageHandler, requestMessageType); } else { addClientHandlerAfter(channel, type, messageHandler, requestMessageType); } } private <REQ extends IMessage, REPLY extends IMessage, NH extends INetHandler> void addServerHandlerAfter(FMLEmbeddedChannel channel, String type, IMessageHandler<? super REQ, ? extends REPLY> messageHandler, Class<REQ> requestType) { SimpleChannelHandlerWrapper<REQ, REPLY> handler = getHandlerWrapper(messageHandler, Side.SERVER, requestType); channel.pipeline().addAfter(type, messageHandler.getClass().getName(), handler); } private <REQ extends IMessage, REPLY extends IMessage, NH extends INetHandler> void addClientHandlerAfter(FMLEmbeddedChannel channel, String type, IMessageHandler<? super REQ, ? extends REPLY> messageHandler, Class<REQ> requestType) { SimpleChannelHandlerWrapper<REQ, REPLY> handler = getHandlerWrapper(messageHandler, Side.CLIENT, requestType); channel.pipeline().addAfter(type, messageHandler.getClass().getName(), handler); } private <REPLY extends IMessage, REQ extends IMessage> SimpleChannelHandlerWrapper<REQ, REPLY> getHandlerWrapper(IMessageHandler<? super REQ, ? extends REPLY> messageHandler, Side side, Class<REQ> requestType) { return new SimpleChannelHandlerWrapper<REQ, REPLY>(messageHandler, side, requestType); } /** * Construct a minecraft packet from the supplied message. Can be used where minecraft packets are required, such as * {@link TileEntity#getDescriptionPacket}. * * @param message The message to translate into packet form * @return A minecraft {@link Packet} suitable for use in minecraft APIs */ public Packet getPacketFrom(IMessage message) { return channels.get(Side.SERVER).generatePacketFrom(message); } /** * Send this message to everyone. * The {@link IMessageHandler} for this message type should be on the CLIENT side. * * @param message The message to send */ public void sendToAll(IMessage message) { channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.ALL); channels.get(Side.SERVER).writeAndFlush(message).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } /** * Send this message to the specified player. * The {@link IMessageHandler} for this message type should be on the CLIENT side. * * @param message The message to send * @param player The player to send it to */ public void sendTo(IMessage 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).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } /** * Send this message to everyone within a certain range of a point. * The {@link IMessageHandler} for this message type should be on the CLIENT side. * * @param message The message to send * @param point The {@link TargetPoint} around which to send */ public void sendToAllAround(IMessage 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).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } /** * Send this message to everyone within the supplied dimension. * The {@link IMessageHandler} for this message type should be on the CLIENT side. * * @param message The message to send * @param dimensionId The dimension id to target */ public void sendToDimension(IMessage 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).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } /** * Send this message to the server. * The {@link IMessageHandler} for this message type should be on the SERVER side. * * @param message The message to send */ public void sendToServer(IMessage message) { channels.get(Side.CLIENT).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.TOSERVER); channels.get(Side.CLIENT).writeAndFlush(message).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } }