Minecraft Forge 1.7.2 Netty Custom Packet Handling Using FMLProxyPacket

Warning -- Deprecated: cpw Recommends SimpleNetworkWrapper Method

Introduction

This tutorial is just for interest's sake.  It works and is modern, but the recommended method is described here: CatDany's Packets Are Easy example or another one here: diesieben07's SimpleNetworkWrapper Packet System Tutorial

When Minecraft is being played there are actually two "sides" executing.  There is a server side which really does most of the work of creating and maintaining the world(s) and there is a client side which show ("renders") the world to each player and takes input from that player.

The server and client are connected with a networking channel, even if you are running single player (in which case the server and client are running on same computer).

Networking means that data is transmitted in serialized form, in chunks called packets.  The custom data carried by the packet is called the "payload".

Java extended to Minecraft extended to FML contain a number of ways to send and receive packets now using an overall framework called Netty.

Until recently there has been some confusion about the FML-intended way of sending simple custom message.  This caused a number of modders, including me, to come up with our own systems.  CPW has now clarified (with a "warning" posted on the wiki tutorial) that the SimpleNetworkWrapper and IMessage interface are recommended.

Consider everything below as deprecated.  It does work though, and may still be interesting to readers.

When I was looking at creating a packet system I noticed that FML provides a custom packet type (FMLProxyPacket) we can easily make use of.  I show that here.

Background: Comparison Of Other Sync Mechanisms

Refer to some other information on this topic here: http://www.minecraftforge.net/wiki/SMP_Coding_Guidelines

All synchronization between server and client is using packets in some way.  During the course of Minecraft being developed, several different uses of packets have been implemented.  Pretty much any of them can be used in some way by modders, but there are some pros and cons to using each.

Even without any custom mods, in Minecraft with Forge all of the following are related to using packets in some way to sync clients and servers:
  • NBT -- data format that can be used for packet payloads
  • Metadata and Damage Values -- efficient but limited packets for blocks and items
  • DataWatcher -- efficient but limited data, used for entities
  • TileEntity -- comprehensive and flexible data for blocks
  • GUIContainer -- helps direct user input into TileEntity.
  • EntityTracker -- built-in mechanism that keeps track of existence and location of all entities and syncs all the clients.  
I'll briefly explain each of these, but what you can see is missing is a comprehensive and flexible packet system for entities.  So that is what my tutorial is for.

NBT:
  • NBT is just a convenient way to look up and organize custom field data, using human-readable strings as tags. 
  • NBT can be used for creating the payloads of packets, although I don't actually do that (yet).  For example,  TileEntity uses NBT for its payloads.  If you want to do this you can use the built-in ByteBufUtils to get this additional functionality for your packet payload.
  • Don't get confused between NBT for saving versus packet handling. Most modders are familiar with its typical use for saving and loading data (i.e. when the game loads or quits, or when a world chunk loads or quits).  But saving the NBT does not sync the client and server -- you still need packets.
Metadata (Blocks) and Damage Values (Items):
  • For blocks, the people who coded Minecraft had a problem to solve -- they wanted to have a very large number of blocks in the world so they needed to keep the amount of unique data per block to a minimum to both save memory (in the computer) and save bandwidth (on the networking required to synchronize).  But the blocks do need some unique information -- such as if it is partially damaged.  So they allowed each block to have a very compact (only 4 bits!) piece of unique data called meta data.
  • The downside to using metadata is that it is extremely limited -- only 16 possible values -- so you can't store much except Boolean (a bit can represent a flag) or a very small counter or index.
  • If you want a block to have more complicated data stored, you should use a TileEntity to associate with the block. 
Tile Entities:
  • A TileEntity is specifically designed to hold more complicated data sets and also provide a sync packet system between client and server, but are generally intended to be associated with a block in a fixed position (that's why they're called "tile" entities). 
  • Also, you are limited by the same issue that Minecraft coders originally faced -- you can't have a lot of these or they will use up too much system memory and network bandwidth.  So they are great for use for a signpost or a treasure chest, but you wouldn't want to add tile entities to a new type of tree (assuming the tree was common in some biomes).
  • The data in the TileEntity is very flexible and uses NBT to both save/load and as packet payloads (refer to the TileEntity getDescriptionPacket() method). 
  • So, if you are looking at this tutorial for TileEntity syncing, don't -- just use the built-in methods.
dataWatcher:
  • dataWatcher is a built-in method for the Entity class and is an efficient way to pack information used for entities.
  • The downside is there are only a limited number of bytes available -- it is not useful for sending strings and arrays.
  • Another downside is that if another mod uses the same bytes in dataWatcher for same entities you can end up with a conflict.  
EntityTracker:
  • This is the built-in class for Minecraft to track the existence and location of all entities and communicate it to the clients.  Perhaps it is possible to customize this with custom data, but I'm not aware of how to do this.  But it does use packets.

A Packet Handling Approach

For programming or modding noobs, packet handling can seem a little bit complicated, but it isn't really that bad

There are a few tutorials out there; however, I found that many of the tutorials were "overkill" -- I just wanted to send a couple bytes of information occasionally and didn't need complicated system of pipelines and discriminators.

Furthermore, I assumed that FML should have some hooks that we could easily use, and it did.  FML already has the following built in classes:
  • FMLProxyPacket is class that is sided and takes byteBuf payloads
  • ServerCustomPacketReceived is an event you can subscribe to which fires when an FMLProxyPacket is received on server side
  • ClientCustomPacket Received is an event you can subscribe to which fires when an FMLProxyPacket is received on the client side
So combine the above with creating byteBuf payloads, and open a networking FMLEventChannel and you've got a full-fledged system for sending and receiving packets.

Some Alternative Packet Handling Tutorials

The Netty system was added fairly recently and the modding community is still catching up.  FML provided some simple implementations but initially there were some bugs plus there were not any great tutorials so people developed a number of alternatives.  These work, but some are also buggy (memory leak potential).  Ideally we should stick to what FML intended us to use.  This is an evolving topic, but currently on the wiki tutorial the following message is displayed:
This is a poor example of using Netty. It can cause memory leaks. It doesn't separate handlers from codecs properly. It reimplements functionality existant in FML for months. If you are using this, consider switching to using FMLIndexedMessageToMessageCodec, or better yet, use the simpleimpl Message functionality.
I haven't personally used these methods yet, but I intend to try them and will update my tutorial/recommendations accordingly.

Aside: Java Throws, Try and Catch

When Java (or other programming languages) try to access external system resources, such as files or networking channels, there are reasons why the access may fail even though it is not really a failure of the program.  So the program needs to handle these errors "gracefully".  Since we're doing networking, we need to use these methods and in fact Eclipse (or other IDE) will indicate errors unless you apply these.  For the most part you can just use your IDE to insert them where needed and if you want to make your error handling more sophisticated you can add more code to the catch portion.

More info on this basic programming concept can be found at:  Java tutorial on exceptions

The Steps Needed For A Packet Handling System

Basically, you’ll need a packet system that:

a. Registers a networking channel specific for your mod (this allows you to ignore packets that may come from other mods). You could theoretically use an existing channel but then there would be chance that your payload format conflicts with other mods or vanilla packets.

b. Has packet handler classes on each side that is called on packet events.  Depending on the packet system, you may need to subscribe to an event or simply use an onMessage() method or similar.  In my system I @Subscribe to the onClientPacketReceived and the onServerPacketReceived events.

c. Has a class on each side to create packets. The creation mostly involves creating a payload which is usually a ByteBuf.  Most systems make the first int as an identifier of the packet type, often called the "discriminator".  You can make up your own types.  The rest of the creation of the payload is often called "encoding" but really just means you put the values you care about into the buffer in an order of your choosing.

d. Has a class on each side to process received packet, distinguish packet type and what side it is on and process the packet accordingly. You could actually put this code in the handler, but as your mod grows the number of packet types and complexity of their payloads will grow so I put them in separate message classes. Again most of the work is related to the payload, with the first int usually being a discriminator that represents the packet type, then remaining payload which needs to be "decoded" by retrieving values from the ByteBuf in the order you expect them for that packet type.

d. Sends the packets as needed (ideally just when there are changes, which is often in a setter method).

(In my examples, my mod is called WildAnimals -- wherever you see that you should replace with something relevant to your mod.)

Register a networking channel:


  - in your mod's main class create variables:

public static final String networkChannelName = "WildAnimals"; // put the name of your mod here public static FMLEventChannel channel;

  - in your mod's init event handler method (probably in your proxy class) you need:

WildAnimals.channel = NetworkRegistry.INSTANCE
      .newEventDrivenChannel(WildAnimals
      .networkChannelName); // replace WildAnimals with the name of your main class

Create and Register Your Packet Handler Classes:

  - also in same init event handler method register your packet handler classes.  In the CommonProxy init() you need:

WildAnimals.channel.register(new ServerPacketHandler());  // replace WildAnimals with name of your main class

  - and in your client proxy init() you need to additionally register the packet handler class:

  WildAnimals.channel.register(new ClientPacketHandler()); // replace WildAnimals with name of your main class

Create a new class called ServerPacketHandler which contains subscription to a server packet event with something like:

package com.blogspot.jabelarminecraft.wildanimals.networking;

import java.io.IOException;

import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.NetHandlerPlayServer;

import com.blogspot.jabelarminecraft.wildanimals.WildAnimals;
import com.blogspot.jabelarminecraft.wildanimals.networking.entities.ProcessPacketServerSide;

import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.network.FMLNetworkEvent.ServerCustomPacketEvent;

public class ServerPacketHandler 
{
   protected String channelName;
   protected EntityPlayerMP thePlayer;
 
   @SubscribeEvent
   public void onServerPacket(ServerCustomPacketEvent event) throws IOException 
   {
      channelName = event.packet.channel();
  
      // Thanks to GoToLink for helping figure out how to get player entity
      NetHandlerPlayServer theNetHandlerPlayServer = (NetHandlerPlayServer)event.handler;
      thePlayer = theNetHandlerPlayServer.playerEntity;
  
      // if you want the server (the configurationManager is useful as it has player lists and such
      // you can use something like
      // MinecraftServer server MinecraftServer.getServer();
      if (channelName.equals(WildAnimals.networkChannelName))
      {
         // DEBUG
         System.out.println("Server received packet from player = "+thePlayer.getEntityId());
         ProcessPacketServerSide.processPacketOnServer(event.packet.payload(), event.packet.getTarget(), thePlayer);
      }
   }
}

And create a ClientPacketHandler class that extends the ServerPacketHandler class and subscribes to a client packet event:

package com.blogspot.jabelarminecraft.wildanimals.networking;

import java.io.IOException;

import com.blogspot.jabelarminecraft.wildanimals.WildAnimals;
import com.blogspot.jabelarminecraft.wildanimals.networking.entities.ProcessPacketClientSide;

import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.network.FMLNetworkEvent.ClientCustomPacketEvent;

// Remember "client" run configuration includes server side execution too
public class ClientPacketHandler extends ServerPacketHandler
{
   @SubscribeEvent
   public void onClientPacket(ClientCustomPacketEvent event) throws IOException 
   {
      channelName = event.packet.channel();
      if (channelName.equals(WildAnimals.networkChannelName))
      {
         // DEBUG
         System.out.println("Client received packet from server");
   
         ProcessPacketClientSide.processPacketOnClient(event.packet.payload(), event.packet.getTarget());
      }
   }  
}

Classes To Create And Send Packets On Each Side

The actual data you create and receive will need to be changed for your particular need.  Hopefully you can understand how my example packet could be modified for your need.

There are a few things to understand -- a packet has a payload that consists of a byte stream (which you build in a ByteBuf).  It is totally up to you what bytes you put in the packet, but you have to come up with a useful format. 

Confirm The Packet Came In On Your Channel

When you receive a packet, you can tell it is a packet from your mod because of the channel it comes on (you check for the channel). Then because you'll probably have lots of custom packet types I recommend that first data sent consists of a packet type identifier.  I create some constants for the various packet types.  So in my main class I define the packet type IDs:

// define IDs for custom packet types
// enumerate packets from server
public final static int PACKET_TYPE_ENTITY_SYNC = 1;
// enumerate packets from client
public final static int PACKET_TYPE_C2S_TEST = 1;

Note that I did not use actual Java Enum because I want to read and write these from byte buffers so might as well have already in int type for easy use.

Also note that I process packets on each side separately so there is no issue with having same value in each set of types.

After that, you put your custom data -- it can be anything you want.  In my case I was syncing data about my animal entities, so my next information is the entityID.  But since I have multiple types of entities, I have to get the class from the entityID and then change my processing based on that.  In that processing I simply take the information I want to send and put it in.  The key thing is to read back the data in the same order that you put it in when you create the packet.

Class that creates packets from the client:

package com.blogspot.jabelarminecraft.wildanimals.networking.entities;

import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;

import java.io.IOException;

import net.minecraft.entity.Entity;

import com.blogspot.jabelarminecraft.wildanimals.WildAnimals;
import com.blogspot.jabelarminecraft.wildanimals.entities.bigcats.EntityBigCat;
import com.blogspot.jabelarminecraft.wildanimals.entities.herdanimals.EntityHerdAnimal;
import com.blogspot.jabelarminecraft.wildanimals.entities.serpents.EntitySerpent;

import cpw.mods.fml.common.network.internal.FMLProxyPacket;

public class CreatePacketServerSide
{
 
   public CreatePacketServerSide()
   {
      // don't need anything here
   }

   // *****************************************
   // Server To Client Packet Creation
   // *****************************************

   public static FMLProxyPacket createEntityPacket(Entity parEntity) throws IOException
   {
      // DEBUG
      System.out.println("Sending Entity Sync Packet on Server Side");
      ByteBufOutputStream bbos = new ByteBufOutputStream(Unpooled.buffer());
      // create payload by writing to data stream
      // first identity packet type
      bbos.writeInt(WildAnimals.PACKET_TYPE_ENTITY_SYNC);
  
      // write entity instance id (not the class registry id!)
      bbos.writeInt(parEntity.getEntityId());
      // now write entity-specific custom fields
      // process herd animals
      if (parEntity instanceof EntityHerdAnimal)
      {
         EntityHerdAnimal entityHerdAnimal = (EntityHerdAnimal)parEntity;
         bbos.writeFloat(entityHerdAnimal.getScaleFactor());
         bbos.writeBoolean(entityHerdAnimal.isRearing()); 
         bbos.writeInt(entityHerdAnimal.getRearingCounter());
      }
      // process serpents
      else if (parEntity instanceof EntitySerpent)
      {
         EntitySerpent entitySerpent = (EntitySerpent)parEntity;
         bbos.writeFloat(entitySerpent.getScaleFactor());
      }
      // process big cats
      else if (parEntity instanceof EntityBigCat)
      {
         EntityBigCat entityBigCat = (EntityBigCat)parEntity;
         bbos.writeFloat(entityBigCat.getScaleFactor());
      }
      // put payload into a packet  
      FMLProxyPacket thePacket = new FMLProxyPacket(bbos.buffer(), WildAnimals.networkChannelName);
      // don't forget to close stream to avoid memory leak
      bbos.close();
  
      return thePacket;
   }
 
   // method to send sync packet from server to client, should send whenever a custom field is set (from setter method)
   // send sync packet to client, if on server side
   public static void sendToAll(FMLProxyPacket parPacket)
   {
      WildAnimals.channel.sendToAll(parPacket);
   }
  
   public static void sendS2CEntitySync(Entity parEntity)
   {
      try 
      {
         sendToAll(createEntityPacket(parEntity));
      } 
      catch (IOException e) 
      {
         e.printStackTrace();
      }
   }
}

In my mod I didn't have need for packets from client to server, but for tutorial purposes here is a test packet.

Class that creates and sends packets from the client:

package com.blogspot.jabelarminecraft.wildanimals.networking.entities;

import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;

import java.io.IOException;

import com.blogspot.jabelarminecraft.wildanimals.WildAnimals;

import cpw.mods.fml.common.network.internal.FMLProxyPacket;

// this class is intended to be sent from server to client to keep custom entities synced
public class CreatePacketClientSide
{
   public CreatePacketClientSide()
   {
      // don't need anything here
   }

   // *****************************************
   // Client to Server Packet Creation
   // *****************************************

   public static FMLProxyPacket createClientToServerTestPacket(int parTestValue) throws IOException
   {
      // DEBUG
      System.out.println("Sending ProcessPacketClientSide on Client Side");

      ByteBufOutputStream bbos = new ByteBufOutputStream(Unpooled.buffer());

      // create payload by writing to data stream
      // first identity packet type
      bbos.writeInt(WildAnimals.PACKET_TYPE_C2S_TEST);

      // write test payload data
      bbos.writeInt(parTestValue);

      // put payload into a packet
      FMLProxyPacket thePacket = new FMLProxyPacket(bbos.buffer(), WildAnimals.networkChannelName);

      // don't forget to close stream to avoid memory leak
      bbos.close();

      return thePacket;
   }

   public static void sendToServer(FMLProxyPacket parPacket)
   {
      WildAnimals.channel.sendToServer(parPacket);
   }
 
   public static void sendTestPacket(int parTestData)
   {
      try 
      {
         sendToServer(createClientToServerTestPacket(parTestData));
      
      catch (IOException e) 
      {
         e.printStackTrace();
      }
   }
}

Classes To Process Received Packets On Each Side

All you have to do is to process the payload in same order as you created it.  If you wrote an int, then read an int, and so forth.

Class that processes the packet received on client side:

package com.blogspot.jabelarminecraft.wildanimals.networking.entities;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;

import java.io.IOException;

import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.world.World;

import com.blogspot.jabelarminecraft.wildanimals.WildAnimals;
import com.blogspot.jabelarminecraft.wildanimals.entities.bigcats.EntityBigCat;
import com.blogspot.jabelarminecraft.wildanimals.entities.herdanimals.EntityHerdAnimal;
import com.blogspot.jabelarminecraft.wildanimals.entities.serpents.EntitySerpent;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;

// this class is intended to be sent from server to client to keep custom entities synced
public class ProcessPacketClientSide
{
public ProcessPacketClientSide()
{
// don't need anything here
}

// *****************************************
// Received By Client Packet Processing
// *****************************************

@SideOnly(Side.CLIENT)
public static void processPacketOnClient(ByteBuf parBB, Side parSide) throws IOException
{
if (parSide == Side.CLIENT) // packet received on client side
{
// DEBUG
System.out.println("Received ProcessPacketClientSide on Client Side");

World theWorld = Minecraft.getMinecraft().theWorld;
ByteBufInputStream bbis = new ByteBufInputStream(parBB);

// process data stream
// first read packet type
int packetTypeID = bbis.readInt();

switch (packetTypeID)
{
case WildAnimals.PACKET_TYPE_ENTITY_SYNC:  // a packet sent from server to sync entity custom fields
{
// find entity instance
int entityID = bbis.readInt();

// DEBUG
System.out.println("Entity ID = "+entityID);

Entity foundEntity = getEntityByID(entityID, theWorld);

// DEBUG
if (foundEntity != null)
{
System.out.println("Entity Class Name = "+foundEntity.getClass().getSimpleName());
}
else
{
System.out.println("Entity Class Name = null");
}

// process based on type of entity class
// process herd animals
if (foundEntity instanceof EntityHerdAnimal)
{
EntityHerdAnimal foundEntityHerdAnimal = (EntityHerdAnimal)foundEntity;
// apply custom fields to entity instance
foundEntityHerdAnimal.setScaleFactor(bbis.readFloat());
foundEntityHerdAnimal.setRearing(bbis.readBoolean());
foundEntityHerdAnimal.setRearingCounter(bbis.readInt());
// DEBUG
System.out.println("Is rearing = "+foundEntityHerdAnimal.isRearing());
}
// process serpents
else if (foundEntity instanceof EntitySerpent)
{
EntitySerpent foundEntitySerpent = (EntitySerpent)foundEntity;
// apply custom fields to entity instance
foundEntitySerpent.setScaleFactor(bbis.readFloat());
}
// process big cats
else if (foundEntity instanceof EntityBigCat)
{
EntityBigCat foundEntityBigCat = (EntityBigCat)foundEntity;
// apply custom fields to entity instance
foundEntityBigCat.setScaleFactor(bbis.readFloat());
}
break;
}
}

// don't forget to close stream to avoid memory leak
bbis.close();
}
}

// some helper functions
public static Entity getEntityByID(int entityID, World world)        
{         
for(Object o: world.getLoadedEntityList())                
{                        
if(((Entity)o).getEntityId() == entityID)                        
{                                
System.out.println("Found the entity");                                
return ((Entity)o);                        
}                
}                
return null;        
}
}

And here is the class the processes the packets received on the server side:

package com.blogspot.jabelarminecraft.wildanimals.networking.entities;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;

import java.io.IOException;

import net.minecraft.entity.player.EntityPlayerMP;

import com.blogspot.jabelarminecraft.wildanimals.WildAnimals;

import cpw.mods.fml.relauncher.Side;

// this class is intended to be sent from server to client to keep custom entities synced
public class ProcessPacketServerSide
{

public ProcessPacketServerSide()
{
// don't need anything here
}

// *****************************************
// Received By Server Packet Processing
// *****************************************

public static void processPacketOnServer(ByteBuf parBB, Side parSide, EntityPlayerMP parPlayer) throws IOException
{
if (parSide == Side.SERVER) // packet received on server side
{
// DEBUG
System.out.println("Received Packet on Server Side from Player = "+parPlayer.getEntityId());

ByteBufInputStream bbis = new ByteBufInputStream(parBB);

// process data stream
// first read packet type
int packetTypeID = bbis.readInt();

// DEBUG
System.out.println("Packet type ID = "+packetTypeID);

switch (packetTypeID)
{
case WildAnimals.PACKET_TYPE_C2S_TEST:
{
// DEBUG
System.out.println("Test packet received");

int testVal = bbis.readInt();

// DEBUG
System.out.println("Test payload value = "+testVal);

break ;
}

}

// don't forget to close stream to avoid memory leak
bbis.close();
}
}
}

Send the Packet When Needed:

Now all you need to do is to send the packet when you need to.  You could just send it in the onUpdate() method of your entity, but that is a bad idea because it is wasteful -- it sends a packet every tick.  Instead you should send it every time the variables you are tracking change.  Personally it can be tricky to remember to send a packet every time, so I rely on encapsulation -- I only change my variables by using a "setter" method and I put the sending packet inside that sender. I highly recommend this.

Anyway, it is up to you when you want to send the packets to sync up the client.  The code you need depends on whether you're sending it to just one player (e.g. for their personal GUI) or to all the players (like in my case where I want all the players to see the entity animations at the same time).  Here are some examples of the code you can use to send the packet, these are some setter methods in my actual entity class:

// *****************************************************
// ENCAPSULATION SETTER AND GETTER METHODS
// Don't forget to send sync packets in setters
// *****************************************************
        
public void setRearing(Boolean parSetRearing)
{
   if (parSetRearing && getAITarget()==null) // don't rear if already has target
   {
      rearingCounter = rearingTicksMax;
      isRearing = true;
      // DEBUG
      System.out.println("Rearing instead of fleeing");
      System.out.println("rearingCounter = "+rearingCounter);
   }
   else
   {
      rearingCounter = 0;
      isRearing = false;
      // DEBUG
      System.out.println("Finished Rearing");
      System.out.println("rearingCounter = "+rearingCounter);
   }
     
   // don't forget to sync client and server
   if (!worldObj.isRemote)
   {
      // DEBUG
      System.out.println("Should send a sync packet");
      CreatePacketServerSide.sendS2CEntitySync(this);     
   }
}
    
public boolean isRearing()
{
   return isRearing;
}
    
public void setScaleFactor(float parScaleFactor)
{
   scaleFactor = Math.abs(parScaleFactor);
    
   // don't forget to sync client and server
   if (!worldObj.isRemote)
   {
      CreatePacketServerSide.sendS2CEntitySync(this);     
   }
}
    
public float getScaleFactor()
{
   return scaleFactor;
}
    
public void setRearingCounter(int parTicks)
{
   rearingCounter = parTicks;
        
   // don't forget to sync client and server
   if (!worldObj.isRemote)
   {
      CreatePacketServerSide.sendS2CEntitySync(this);     
   }
}
    
public void decrementRearingCounter()
{
   --rearingCounter;
        
   // don't forget to sync client and server
   if (!worldObj.isRemote)
   {
      CreatePacketServerSide.sendS2CEntitySync(this);     
   }
}

You should check out the other methods for the channel, because you can sendToServer(), etc.  Note that that sendToAll() and similar methods should only be sent from the server side as the client isn't aware of all the other clients.

Be Careful To Avoid Endless Sync Packet Loops

The goal is syncing the client and server, so when something changes on one side you send a packet to the other side, but that will cause the other side to change their stuff and you don't want that change to cause another packet to be sent.  So it is very important to only have syncing go one direction (that makes sense): if you have a server-updated value like something from AI then you want to only sync that from server to client; if you had user input type data then you want to only sync that from client to server.

To ensure the packets are only sent in proper direction, make sure you check whether the class is executing in on client or server side before sending the packet.  For example in an Entity class you can check worldObj.isRemote which will be true on client and false on server.  Failure to do this can cause an endless loop of packets being sent back and forth, each initiating a data update that in turn sends out a sync packet.

Conclusion

Hope this helps.  I know it is annoying that you have to do so much work in order to simply send a couple values to the client, but trust me that you have to do it and you might as well learn.

I won't feel bad if you use a different tutorial to do it, but I'm pretty happy with my implementation so far.  I mostly did this to learn packet handling and came up with something I thought I'd share.

Please comment if you see any errors or have suggestions for improvement!

2 comments:

  1. When I am using bbos.buffer() it gives syntax error that I have to cast it to a packet buffer. When I do that, it compiles but has a runtime error "io.netty.buffer.UnpooledHeapByteBuf cannot be cast to net.minecraft.network.PacketBuffer" with a bunch of stuff after than. I am assuming that 1.8 stopped using ByteBuffOutputStream, and started using PacketBuffer. I will try a few things and post my results, but help would be appreciated! Thanks for the tutorial.

    ReplyDelete
    Replies
    1. As mentioned in this tutorial, these methods are deprecated -- meaning no long recommended or guaranteed to work.

      Did you look at my updated information here: http://jabelarminecraft.blogspot.com/p/minecraft-forge.html

      Delete