Minecraft Modding: Tips For Networking And Packets

Consider Using The EntityDataManager (Previously DataWatcher) Where Appropriate


Before going through the effort of making custom packets, consider using the built in EntityDataManager (1.9+) or DataWatcher (earlier versions). It is a thread-safe client sync mechanism. If you're still modding in older versions, check out Jabelar's DataWatcher tips.

The EntityDataManager is quite useful. It is a bit more extended than the previous DataWatcher and much easier than setting up custom packets. The easiest way to understand how to use the EntityDataManager in your own mods is to see how they are used in the vanilla entity classes. The basic idea is that if any of your custom entity fields change on the server are useful on the client (usually things that cause visual effects like potion effects, amount of air they have, health, etc.) then you can consider using the EntityDataManager.

All entities already have an EntityDataManager instance called dataManager inherited from the Entity class, so you don't need to (and probably should not) make a new one. It has protected scope so you can access it directly in your custom entity subclasses.

The EntityDataManager is apparently limited to 255 according to Choonster, although I have not verified this. This limitation is likely due to the related packet using a single byte as the discriminator. You can send simple data types such as Boolean, Integer, String, FloatByte, as well as some Minecraft-specific types like BlockPos, EnumFacing, ItemStackUUID, and NBTTagCompunds It also looks like you could create a custom data serializer to support other types, which you would register with the DataSerializers#registerSerializer() method.

The basic idea is:

  1. In your custom entity class you can create custom DataParameter instances using the dataManager.createKey() method passing as parameters the target Entity class and the appropriate DataSerializer and getting a DataParameter with the same as the DataSerializer back.
  2. @Override the entityInit() method (inherited from Entity) and make sure you call the super-method then call the dataManager.register() method for each DataParameter instance created in step 1. 
  3. @Override the writeEntityToNBT() and readEntityToNBT() to add your parameters to the compound that is saved and loaded.
  4. Anywhere in your code where you want to change the value, you call the dataManager.set() method.
  5. Anywhere in your code where you want to use the value, you call the dataManager.get() method.
Important: The entityInit() method is called by the Entity constructor which is called before your custom entity constructor and also before fields in your class are initialized (because in Java the super-constructor is called before field initialization).

Whenever your code on the server wants to change the value you should update the data manager and whenever you need the data on client or server you should get it from the data manager.

If you're updating your code from earlier DataWatcher, the following shows the general equivalency between the systems:

  • DataWatcher#addObject() -> EntityDataManager#register()
  • DataWatcher#updateObject() -> EntityDataManager#set()
  • DataWatcher#getWatchableObject*() has been replaced with EntityDataManager#get()


Implications Of 1.8+ Using Separate Thread For Networking


There are good reasons for using a separate thread for networking, but it has some implications that you should be aware of. 

First of all, in 1.7.10 and earlier if you sent a custom packet you could predict the order it would be received relative to the built-in packets. For example, I used to send a custom packet from the constructor of a custom entity and could trust that the entity would be spawned on the client before the custom packet was received. However, in 1.8 sometimes that same code would fail because my custom packet would make it to the client before the packet that spawned the entity causing a null pointer exception! So in 1.8 you need to make sure you don't rely on expectation of ordering of custom packets relative to built-in ones.

Secondly, the simple network wrapper implementation needs to be made "thread-safe". See TheGreyGhost's 1.8 Networking Thread Tutorial for more info on this.

How To Send Vanilla Packets


There are many cases where vanilla packets will be sent for you, provided you're using the standard hooks. For example, custom GUIs with containers need the container contents to be synced between client and server but if you register an IGuiHandler class then it will take care of it for you.

However, sometimes you'll find that it is useful to send a vanilla packet. For example, when canceling player interact event for a bucket it doesn't do it cleanly so you need to send a  S23PacketBlockChange packet. If you need to send such a packet, you simply need to instantiate it and then use EntityMP#playerNetServerHandler.sendPacket() method to send it.

Key Point: Use EntityMP#playerNetServerHandler.sendPacket() method to send vanilla packets.

How To Intercept Vanilla Packets


See my Intercepting Vanilla Packets Tutorial.

Current Recommended Custom Packet Approach


In Forge 1.7.2 they started using the Netty networking framework for packets in Minecraft.  There was some confusion early on how to use it, and several people came up with possible approaches, such as my own tutorial, a now deprecated wiki, and CoolAlias' previous tutorial (he now has updated one that also uses SimpleNetworkWrapper).

Meanwhile the SimpleNetworkWrapper system in Forge was improved and fixed and now is a workable system.  Here are tutorials from respected modders:
However, there is still concern about the general implementation and remaining bugs/limitations.  For example, see the final comment from CoolAlias on his tutorial.

Warning: There is a limitation on the length of the networking channel name. It must be 20 characters or less.

So I think many people may still find my tutorial useful.  It is not believed to have the memory leak issue of the wiki implementation.  Anyway, check it out here: Jabelar's Custom Packets Using FMLProxyPackets Tutorial.

Protection From Griefing And Hacking


One thing to be careful of when sending custom packets is that they can open the game to people creating hacks and griefing.  To protect against this you should "never trust the client" without validation on the server.  

For example, let's say you want a GUI on client side to trigger destroying a block.  Instead of sending a packet that contains coordinates where the server immediately deletes the block, it is better for the packet to simply indicate the information from the GUI (e.g. information indicating what button was pressed) and have the server determine what the appropriate action is (like what the real player position is, whether the player truly has permission to delete the block, etc.)

Referencing Entities In Packet Payloads


Thanks to CoolAlias for figuring out the player step described below.

Often you may want to use packets to synchronize entities.  The difficulty though is that the instances of the entity are independent on each side -- you can't use Java "this" or UUID because they will be different.  The World class however keeps a list of entities with common ID on both side to aid in matching up the instances.  So basically, in the sending packet you can find the id using the Entity#getEntityID() method:

yourEntity.getEntityId()

Then in the receiving packet you can look up the actual instance by ID by using the World#getEntityByID() method.  But there is a problem -- how do you get a world instance in the message handling class?  There is not field specifically in that class with the world instance, but luckily there is a slightly tricky way to do this: on the server you can use the message context to get the player and from the player get the world object like this:  ctx.getServerHandler().playerEntity

The reason this works is because the packet system has to know which client (i.e. player) the packet is going to or from -- that is part of the message context.

But the code needed to get the world object is different on the client because you will get the player from Minecraft.getMinecraft().thePlayer instead.  That's fine but if you're running the integrated client the message handling will execute on both the client and server side, and this method will fail (it may not actually crash but will give back the client side player even from the server -- according to CoolAlias' investigation of this behavior.

So the code you should use in your message handler to get the player instance is:

EntityPlayer thePlayer = (ctx.side.isClient() ? Minecraft.getMinecraft().thePlayer : ctx.getServerHandler().playerEntity);

Then you can get the worldObj directly with thePlayer.worldobj.  And you can then (finally!) look up an entity id using that world instance.  So you can get the entity that is mapped to an ID that you send in the message payload like this:

thePlayer.worldObj.getEntityByID(message.entityId)

Note that the message.entityId is an int value I defined for the message class, passed through the toBytes() and fromBytes() method.

Putting it all together, here is the code from a message in one of my mods that looks up an entity ID:

public class MessageSyncEntityToServer implements IMessage 
{
    private int entityId ;
    private NBTTagCompound entitySyncDataCompound;

    public MessageSyncEntityToServer() 
    { 
     // need this constructor
    }

    public MessageSyncEntityToServer(int parEntityId, NBTTagCompound parTagCompound) 
    {
     entityId = parEntityId;
        entitySyncDataCompound = parTagCompound;
        // DEBUG
        System.out.println("SyncEntityToClient constructor");
    }

    @Override
    public void fromBytes(ByteBuf buf) 
    {
     entityId = ByteBufUtils.readVarInt(buf, 4);
     entitySyncDataCompound = ByteBufUtils.readTag(buf); // this class is very useful in general for writing more complex objects
     // DEBUG
     System.out.println("fromBytes");
    }

    @Override
    public void toBytes(ByteBuf buf) 
    {
     ByteBufUtils.writeVarInt(buf, entityId, 4);
     ByteBufUtils.writeTag(buf, entitySyncDataCompound);
        // DEBUG
        System.out.println("toBytes encoded");
    }

    public static class Handler implements IMessageHandler<MessageSyncEntityToServer, IMessage> 
    {
        @Override
        public IMessage onMessage(MessageSyncEntityToServer message, MessageContext ctx) 
        {
            EntityPlayer thePlayer = MagicBeans.proxy.getPlayerEntityFromContext(ctx);
            IEntityMagicBeans theEntity = (IEntityMagicBeans)thePlayer.worldObj
                  .getEntityByID(message.entityId);
            theEntity.setSyncDataCompound(message.entitySyncDataCompound);
            // DEBUG
            System.out.println("MessageSyncEnitityToClient onMessage(), entity ID = "
                  +message.entityId);
            return null; // no response in this case
        }
    }
}

The idea is that I'm passing an entity ID along with an NBT compound that includes information I want to sync between client and server. To understand how to properly use this handler, refer to

3 comments:

  1. When is it a good idea to use a datamanager and not to? Do they have their own disadvanatages or more advanatage than just synchronization on CLient and Server?

    ReplyDelete
  2. Nice thanks, for all tutorials... :D :D :D

    ReplyDelete