Skip to main content

networking

Creating a Message Class

To send a packet you need to create a class that contains the data to be sent. Even above 1.16, it can NOT be a record (the version of Gson shipped with Minecraft does not support them) or have any final fields.

When you're ready to send a packet, just create a new instance of your data class, this is the message that will be passed to WrapperLib to handle.

ExampleMessage message = new ExampleMessage("test", 19);

See Serialization for more information on what types of data are supported.

Handling

You need to tell WrapperLib what code should be run when your packet message is received on the other side. You can do this by making your message class implement ServerSideHandler (if sending from the client to the server) or ClientSideHandler (if sending from the server to the client). Each interface has a handle method that will be called when the packet is received. You can even implement both on the same class if you want it to be sent both ways (the methods have different signatures because the server has an argument for the player that sent the message).

When implementing ClientSideHandler, it is safe to call client only code (net.minecraft.client) even though the class will be initialized on the server. The handle method is only called when the packet is received so anything referenced there will not be class loaded earlier. Doing that seems to not work reliably for reasons I don't understand. Maybe forge is looking at your imports even if the code that uses them never runs. Best to keep client only code in its own class.

public class ExampleMessage implements ClientSideHandler, ServerSideHandler {
// fields & constructor omitted

// ClientSideHandler
@Override
public void handle(){
// received on client
}

// ServerSideHandler
@Override
public void handle(ServerPlayer player){
// received on server
}
}

Manually Registered Handlers

Alternatively, if you prefer to keep your data separate from your behaviour, you may manually register a handler method in your mod initializer. Personally I find this annoying but whatever floats your boat. A manually registered handler will take priority over the interface method if both are available for the same class. Only one handler may be registered per class (adding another will overwrite the first). Handlers registered this way will only fire for exactly matching classes, you cannot send an object of a class that extends ExampleMessage without registering its own handler.

NetworkWrapper.registerClientHandler(ExampleMessage.class, (message) -> {
// received on client
});
NetworkWrapper.registerServerHandler(ExampleMessage.class, (message, player) -> {
// received on server
});

Exceptions

If a runtime exception is thrown while running one of your packet handlers, the game will not crash. The data and class name of your message, as well as the exception stack trace, will be logged.

If you accidentally send a packet message that does not implement a handler interface and does not have a manually registered handler, the game will not crash. We just log an error.

Sending a Packet

  • NetworkWrapper.sendToServer(message)
  • NetworkWrapper.sendToClient(player, message)

There are also some helper methods for easily sending to some common groups of clients.

  • NetworkWrapper.sendToAllClients(message)
  • NetworkWrapper.sendToTrackingClients: (level, message), (tile, message), (level, pos, message), (entity, message)
  • NetworkWrapper.sendToTrackingAndSelf(player, message)

Make sure that the message class has an available handler for the side you're sending to (either implement the interface or register it).

Helpers that redirect to those NetworkWrapper methods are available on the ClientSideHandler and ServerSideHandler interfaces as well. So you can call message.sendTo* if you prefer. This means you can do your whole networking setup without ever directly interacting with NetworkWrapper, just implement a *Handler interface and call a sendTo* method.

Trying to send a client bound packet from the client side to the server (wrong direction) while connected to a dedicated server may crash on forge so be careful.

Simple Example

This is the example from the Fabric wiki's networking tutorial.

// HighlightPacket.java
public class HighlightPacket implements ClientSideHandler {
BlockPos target;

public HighlightPacket(BlockPos target){
this.target = target;
}

public void handle(){
ClientBlockHighlighting.highlightBlock(MinecraftClient.getInstance(), this.target);
}
}

// HighlightingWandItem.java
class HighlightingWandItem extends Item {
public HighlightingWand(Item.Settings settings) {
super(settings)
}

public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
if (world.isClient()) return super.use(world, user, hand);

// Raycast and find the block the user is facing at
BlockPos target = ...

new HighlightPacket(target).sendToTrackingClients((ServerWorld) world, target);

return TypedActionResult.success(user.getStackInHand(hand));
}
}

Version Handshake

WrapperLib allows you to easily communicate the version of your mod's network protocol to the other side so you can catch incompatibilities quickly. You can register your (mod id, version) pair and when a player connects to a server they will be checked to see if their versions are compatible. Using this feature is completely optional, it's just a way to give a more meaningful error message instead of some strange packet parsing crash.

When a client connects, the server sends it a list of all the (mod id, version) pairs. The client then checks each of these against the corresponding clientCheckVersion, disconnecting if any fail. If all pass, the client sends all its (mod id, version) pairs. The server then checks each of these against the corresponding checkServerVersion, disconnecting if any fail. If all pass, the client is allowed to connect successfully.

The basic usage will use the equals method to check the version. So the client and server must have the same version string to allow the connection. Just make sure to increment your version number whenever you make a breaking change to your networking code that would result in incompatibility between your mod versions. Put the following code in your mod initializer.

NetworkWrapper.handshake("examplemod", "1");

You can also give it arbitrarily complex validation logic. If an exception is thrown while running your version checking code, it will be treated the same as them returning false. In example below, the client will be willing to connect to any server but the server will only accept clients with a version below five.

NetworkWrapper.handshake("examplemod", "1", (versionOnServer) -> true, (versionOnClient) -> Integer.parseInt(versionOnClient) < 5);

toVanillaPacket

  • NetworkWrapper.toVanillaPacket(message, isClientBound)
  • ServerSideHandler#toVanillaServerBound or ClientSideHandler#toVanillaClientBound

This is useful for interfacing with vanilla code. For example, if you create an entity and want to override Entity#getAddEntityPacket but include more information than a ClientboundAddEntityPacket, you can create a WrapperLib custom packet, with whatever information you want, that you convert directly into a packet vanilla can handle instead of figuring out the correct time to send it yourself.

You can also use this for overriding BlockEntity#getUpdatePacket.