Packets are defined in com.hypixel.hytale.protocol and organized by category in com.hypixel.hytale.protocol.packets. Base class is Packet and the sub-packages contain actual packet classes. The low-level Netty handler is com.hypixel.hytale.server.core.io.netty.PlayerChannelHandler, it delegates packet processing logic to com.hypixel.hytale.server.core.io.adapter.PacketAdapters.
The server has a built-in adapter system that makes injection very straightforward. You don't need to hook into Netty manually. You can use the PacketAdapters class to register inbound (Client -> Server) and outbound (Server -> Client) watchers. The process for registering inbound packets is the same as for outbound so I'll focus just on inbound in this guide.
Take a look at PacketAdapters class. It provides several registerInbound method overloads. Possible argument types are PacketWatcher, PacketFilter, PlayerPacketWatcher and PlayerPacketFilter. Those are just functional interfaces, so you can just pass a lambda to the method. The difference between PacketWatcher and PacketFilter is that Filter can prevent packet processing and block the packet from reaching the server (for inbound) or reaching the player (for outbound). If you take a look at registerInbound which takes a PlayerWatcher, you will see that it just constructs a PlayerFilter which always allows the packets to go through. It's signalized by the return false. Formally, a PacketWatcher is just void accept(PacketHandler, Packet), while a PacketFilter is boolean test(PacketHandler, Packet). If a filter returns true, it stops propagating the packet further and it's effectively forgotten.
The lambdas take a PacketHandler and a Packet. Packet is obviously the packet, but a handler is a different story. Depending on what phase the connection is at, you can get different stuff from the handler. During gameplay, it will be GamePacketHandler. But during authentication it will be AuthenticationPacketHandler.
Looking at the registerInbound methods that take a PlayerPacketFilter or Watcher you will notice that they also give you access to a PlayerRef but only if the handler is a GamePacketHandler, which makes sense, because the PlayerRef doesn't exist during other phases.
Example code. You can put it anywhere, such as the plugin init function.
Print all packets going from server to client
PacketAdapters.registerOutbound((PacketHandler handler, Packet packet) -> {
var handlerName = handler.getClass().getSimpleName();
var packetName = packet.getClass().getSimpleName();
// some common packets excluded to reduce spam
if (!"EntityUpdates".equals(packetName) && !"CachedPacket".equals(packetName)) {
logger.at(Level.INFO)
.log("[" + handlerName + "] Sent packet id=" + packet.getId() + ": " + packetName);
}
});
Modify an incoming packet containing skin data to remove that data from the packet. With this, every player will have default skin.
PacketAdapters.registerInbound((PacketHandler handler, Packet packet) -> {
if (packet instanceof PlayerOptions skinPacket) {
skinPacket.skin = null;
}
});
A PacketFilter which prevents some chat messages.
PacketAdapters.registerInbound((PacketHandler handler, Packet packet) -> {
if (packet instanceof ChatMessage chatPacket) {
if (chatPacket.message.contains("bad words"))
return true; // prevent packet from propagating to other handlers
// make lowercase 'a'-s uppercase
chatPacket.message = chatPacket.message.replaceAll("a", "A");
}
return false; // allow packet to propagate to other handlers
});