NMS, better known as Net-Minecraft-Server, is a complicated and undocumented set of internals used in Minecraft. This post will go over the use
of Packet Handlers, and why they are important in the context of NMS.
I use Packet Handlers in many of my Minecraft plugins, especially the ones
that are more complex. But what are they?
Introduction
The netty library is the backbone of Minecraft: Java Edition’s multiplayer system. It often looks something like this:
Packet Handlers heavily utilize the netty library to manage incoming and outgoing packets. They are used to intercept packets the player sends to the server to process by your plugin,
instead of being thrown away from the server. This is useful for advanced functionality, like Sign GUIs and WASD-Rideable Entities.
Design
This tutorial will be using the official Mojang Mappings for the NMS classes, for simplicity sake, and for the fact that I don’t like the other mappings.
Part 1: PacketHandler Class
The PacketHandler class is the main class that you register to a player’s ServerCommonPacketListenerImpl class on the NMS side. Let’s first design the PacketHandler class:
Write your class statement, extending ChannelDuplexHandler
For the purposes of a tutorial, we’re going to have a global map of player UUIDs to Predicates containing the packet. The purpose of this map is to map
a function to a player, so that we can intercept packets from the player and process them.
Next, create your constructor to accept a Player. Each player is going to have their own packet handler, so we want to be able to reference it later.
Last, we need to implement the channelRead function, which reads the packet from the handler context, tests the function that handled the packet, and then
performs the usualy netty operations necessary so the client doesn’t disconnect.
Your final class should look something like this:
Part 2: Registering the PacketHandler
Now that we have a PacketHandler class, we need to register it to our target player. This is done by getting the player’s ServerCommonPacketListenerImpl class,
getting the netty channel object through a couple of fields (and reflection), and then injecting the object onto the Channel Pipeline. To do this,
we need to declare a PACKET_INJECTOR_ID constant to put it on, then set it after the server decodes the packet, and therefore before the player’s natural
packet handler.
Keep in mind that internal field names are subject to change, and you should always check the mappings before using them in your plugin. These names are compatible
with Minecraft v1.20.4.
You can call this function when the player joins the server:
Optional: Remove the Packet Handler
It’s best practice to remove the handler manually, and not have Minecraft deal with your custom stuff when a player leaves the server. Plus, if you
ever need to turn it off, you can do so easily.
You can call this function when the player leaves the server:
Part 3: Using the Packet Handler
Now let’s define some custom functionality, now that we can intercept packets that the player sends to the server.
Sign GUI
Many of my plugins require player text input from Signs. This function uses a Consumer<String[]> to handle the input from the player.
Sign input always has an array of 4 string, regardless of whether they are empty, so you can handle the input like this:
WASD-Rideable Entities
My BattleCards uses this feature to allow players to ride entities with WASD controls.
The original code in Kotlin:
This code intercepts a ServerboundPlayerInputPacket packet, which is sent by the client when the player moves. It then moves the vehicle in the direction the player is facing,
and uses the native move function based on a vector calculated from the player’s input.
Conclusion
Packet Handlers are a powerful tool in the NMS developer’s toolkit. They allow you to intercept packets sent by the player to the server, and process them in your plugin.
This demo shows how to create a PacketHandler class, register it to a player, and use it to create custom functionality like Sign GUIs and WASD-Rideable Entities, using examples
that I personally use in my plugins. Thank you for reading!