Advanced Modding: Packet Handler

Goal

I want to show you how to use the Packet Handler class provided by Miner's Basic to send custom packets between Client and Server.

Difficulty


4/10 - Moderately Easy

Prerequisites

Forge Version

This Tutorial was created with Forge 11.14.1.1334 for Minecraft 1.8. If anything doesn't work with other versions, please contact me!

Version

This tutorial requires at least version 1.0-b.324 of Miner's Basic.


Creating a Packet Handler

How does Minecraft work: Packet Handling

In Minecraft it is possible to send data packets from the server to the client or the other way round. This can be used for communication between Server and Client.

Packets are sent using a Packet Handler. This handler reserves a "channelid" which is used to identify the packets. These channelids must be unique, so it's a convention to use the modid or the modid with a suffix.

Each packet type gets a one byte ID which is used to identify an incoming packet. Thus, 255 packets can be registered per Packet Handler (ID 256 is reserved). Each packet needs to implement methods to convert it into a byte array that can be sent. Also, each packet needs to have a handler class which does something with that packet.

In this tutorial we'll use the PacketHandler class provided by Miner's Basic. (minersbasic.api.network.PacketHandler)


First, we need to create an instance of our Mod's Packet Handler. The best place for the instance is our main class, so we'll add this to the class (only the bold part!):

Main.java:
[...]
@SidedProxy(clientSide="com.bedrockminer.tutorial.ClientProxy", serverSide="com.bedrockminer.tutorial.ServerProxy")
public static CommonProxy proxy;

public static PacketHandler packetHandler;

@EventHandler
public void preInit(FMLPreInitializationEvent e) {
[...]

As I said earlier, it is not a good way of coding if we added something to the @EventHandler methods, so we'll open the CommonProxy class and initialize the packet handler variable there:

CommonProxy.java:
public void init(FMLInitializationEvent e) {
    Main.packetHandler = MinersbasicAPI.createPacketHandler("tutorial");
}

It is not necessary to do this in the init phase, but I strongly recommend doing so because it is a logical order (preInit: Blocks, Items; init: Everything else; postInit: inter-mod communication).

There is also another way of creating a PacketHandler object:

CommonProxy.java:
public void init(FMLInitializationEvent e) {
    Main.packetHandler = new PacketHandler("tutorial");
}

It doesn't matter which one you use.

However, it does matter which channelid you choose. The channelid is "tutorial" in this case, which is the modid I'm using (I could also enter Main.MODID instead of "tutorial"). If I needed more than one packet handler in my mod for some weird reason, I would use "tutorial_firstPacketHandler" and "tutorial_secondPacketHandler" to keep the channelids unique (Most likely I'd replace the suffix with something more informative).


Now we have created the Packet Handler, so we can move on to the packets themselves.

Creating a Packet

A packet class needs to implement the interface IMessage. This interface provides the methods to turn the packet into a byte array and read it from one. It's also good to keep the packets in one package: I always use mymod.network.packets.

Let's create a packet now! Here, I'll name it TutorialPacket.

TutorialPacket.java:
package com.bedrockminer.tutorial.network.packets;

import io.netty.buffer.ByteBuf;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;

public class TutorialPacket implements IMessage {

    @Override
    public void fromBytes(ByteBuf buf) {
    }

    @Override
    public void toBytes(ByteBuf buf) {
    }

}

As a simple example for our packet, I decided to create a message packet that can be sent from a Client to the Server which prints the message in chat for everyone. Not useful, but it will explain how everything works.


Our packet should only send a message, so it's only variable is a String.

We can add this variable now, together with the Constructors:

TutorialPacket.java:
public class TutorialPacket implements IMessage {

    private String message;

    public TutorialPacket() {}

    public TutorialPacket(String message) {
        this.message = message;
    }
[...]
}

Note that your packet class must  have a Constructor without any arguments, otherwise an incoming packet cannot be instantiated.


The next step is to encode and decode the packet from a byte array. The methods to do this are toBytes and fromBytes. Both have a ByteBuf as an argument, which is basically a byte array with several methods to write primitive data types into it.

In the following table I'll show how to write and read different data types from a ByteBuf (also using some utility methods by Minecraft)

Type Read
Write
boolean
buffer.readBoolean()
buffer.writeBoolean(b);
byte buffer.readByte();
buffer.writeByte(b);
short
buffer.readShort();
buffer.writeShort(s);
int
buffer.readInt();
buffer.writeInt(i);
long
buffer.readLong();
buffer.writeLong(l);
float
buffer.readFloat();
buffer.writeFloat(f);
double
buffer.readDouble();
buffer.writeDouble(d);
String
ByteBufUtils.readUTF8String(buffer);
ByteBufUtils.writeUTF8String(buffer, s);
ItemStack
ByteBufUtils.readItemStack(buffer);
ByteBufUtils.writeItemStack(buffer, s);
NBTTagCompound
ByteBufUtils.readTag(buffer);
ByteBufUtils.writeTag(buffer, t);


Note that ByteBuf also has other methods which are less frequently needed. However, they are well-documented and can easily be used like the ones above.


For our example packet, we need to save and load our String. This looks like this:

TutorialPacket.java:
@Override
public void fromBytes(ByteBuf buf) {
    this.message = ByteBufUtils.readUTF8String(buf);
}

@Override
public void toBytes(ByteBuf buf) {
    ByteBufUtils.writeUTF8String(buf, this.message);
}

If you have more variables, you need to take care that the reading process is in the exact same order as the writing process.


Now our packet is finished. But we still need a way to handle it. Therefore, we'll create a handler. I always create the handler class inside the packet class to get access to all the private values of the packet without a getter method. Also, I avoid getting too much class files by doing so.

A handler class needs to extend one of three abstract classes from Miner's Basic:

  • MessageHandler.Client<Yourpacket> for packets that can be sent to a client
  • MessageHandler.Server<Yourpacket> for packets that can be sent to the server
  • MessageHandler.Bidiractional<Yourpacket> for packets that can be sent in both directions

In my example, we want to send the packet to the server, so we pick the second option.

TutorialPacket.java:
public class TutorialPacket implements IMessage {

    [the rest of the packet class goes here]

    // =========================================================================

    public static class Handler extends MessageHandler.Server<TutorialPacket> {

        @Override
        public IMessage handleServerMessage(EntityPlayer player, TutorialPacket msg, MessageContext ctx) {
            return null;
        }
    }
}

The class MessageHandler.Server wants us to override the method handleServerMessage, which handles a packet sent to the Server. The client handler needs handleClientMessage and Bidirectional needs both.

In the method we can add the function our packet should have when arriving at the server.

One very important thing here:

The packets are handled in a different thread than the server game loop, thus we cannot access the server directly or we would risk a ConcurrentException if we try to access data that is accessed from the server thread as well.

To avoid that issue, we need to create a runnable object and add it to the server execution queue. There's a simple method in the Miner's Basic ServerUtils class that enqueues a runnable on server side, so we'll use this to handle our packet.

TutorialPacket.java:
@Override
public IMessage handleServerMessage(EntityPlayer player, TutorialPacket msg, MessageContext ctx) {
    ServerUtils.addScheduledTask(new Runnable() {
        @Override public void run() {
            //Here we can add our code
        }
    });
    return null;
}

The message sent in our packet should be published in a chat message. To do this, we need an instance of MinecraftServer. We could use MinecraftServer.getServer(), but there's a method called mc() in the ServerUtils class that shortens our code a bit:

TutorialPacket.java:
@Override
public IMessage handleServerMessage(EntityPlayer player, final TutorialPacket msg, MessageContext ctx) {
    ServerUtils.addScheduledTask(new Runnable() {
        @Override public void run() {
            ServerUtils.mc().getConfigurationManager().sendChatMsg(new ChatComponentText(msg.message));
        }
    });
    return null;
}

Note that we need to declare the packet parameter final when we want to use it from the runnable object. Also note that we can access the private variable message. That's why I prefer writing the handler inside the packet class.


Some of you may have noticed that the method has the returntype IMessage. If our handler returns something else than null, the returned message would be sent back as a reply. This can only be used if the packet is processed outside of a Runnable, directly in the method. However, then the packet must not use the player object or other objects that could be used by the server in order to prevent a ConcurrentException. It's mostly easier to return null and send another packet back later or from inside the Runnable.


Now, our handler is finished as well, so we can proceed by registering our packet.

Registering a packet

To register a packet we need to call the following method of our packet handler:

CommonProxy.java:
public void init(FMLInitializationEvent e) {
    Main.packetHandler = MinersbasicAPI.createPacketHandler("tutorial");
    Main.packetHandler.registerPacket(TutorialPacket.class, new TutorialPacket.Handler(), Side.SERVER);
}

If we wanted to register a packet for the client side, we would need to change the side from SERVER to CLIENT.

If we planned to register a bidirectional packet, we need to use the method registerBidiPacket.


While registration, our packet automatically gets a unique ID for the channel (in this case it would be 0). If we had too many packets for that channel, an exception would be thrown.


If you add more packets it could be useful to create a special class to register them all. Decreases the amount of code in the proxy and makes it easier to read.

Sending a packet

There are seven different methods to send a packet:

  • packetHandler.sendTo(IMessage msg, EntityPlayerMP player);
  • packetHandler.sendToAll(IMessage msg);
  • packetHandler.sendToAllAround(IMessage msg, TargetPoint target);
  • packetHandler.sendToAllAround(IMessage msg, Entity center, double range);
  • packetHandler.sendToAllAround(IMessage msg, int dimension, double x, double y, double z, double range);
  • packetHandler.sendToDimension(IMessage msg, int dimension);
  • packetHandler.sendToServer(IMessage);

Only the last method sends a packet to the server, the others send packets to clients with different ways of choosing the target.


For our example I created an item on the fly which sends the packet with the message "Hello world" to the server when it is right-klicked.

ModItems.java:
public static void createItems() {
    GameRegistry.registerItem(new Item() {
        @Override
        public ItemStack onItemRightClick(ItemStack itemStackIn, World worldIn, EntityPlayer playerIn) {
            if (worldIn.isRemote) {
                Main.packetHandler.sendToServer(new TutorialPacket("Hello world"));
            }
            return itemStackIn;
        }
    }.setCreativeTab(CreativeTabs.tabMisc), "packetitem");
}

Note that the packet is only sent if the world is remote, i.e. we're on client side.

I didn't add any rendering or language settings, this is only for demonstration and may be ugly ingame ;-) .


However, if we right-click now we'll receive a chat message saying: Hello World.

Making Packets without Miner's Basic

If you don't want to use Miner's Basic for some reason, you can copy the PacketHandler class from it. It's pretty standalone, so you don't need much extra classes.

But still, it's easier to use Miner's Basic, because you have every utility class at one place.


You can download the code used in this tutorial as a .zip file from here.


Recommended tutorials to continue with

Take a look at the tutorial overview and decide what you want to do next.


Comments and Questions:

If you want to report modding problems, please make sure to include the code in a pastebin link or something else! Don't just write "It doesn't work", otherwise your post will be deleted. For more complicated problems, please use the troubleshooter form.