| name | hytale-networking |
| description | Handle Hytale network packets, create custom packets, and implement client-server communication. Use when asked to "create custom packets", "handle network messages", "send data to client", "receive client data", or "implement networking". |
| metadata | {"author":"Liam Robinson (MnkyArts)","version":"1.0.0"} |
Hytale Networking API
Complete guide for handling network packets and client-server communication.
When to use this skill
Use this skill when:
- Sending data to clients
- Receiving data from clients
- Creating custom packet types
- Implementing custom UI synchronization
- Handling real-time player input
- Syncing custom game state
Network Architecture Overview
Hytale uses a packet-based networking system:
Client <---> Server
| |
Packet Packet
Encoder Decoder
| |
ByteBuf <-> ByteBuf
Protocol Components
| Component | Description |
|---|
Packet | Data structure with serialization |
PacketRegistry | Maps packet IDs to classes |
PacketHandler | Processes incoming packets |
PacketEncoder | Serializes packets to bytes |
PacketDecoder | Deserializes bytes to packets |
Packet Structure
Packet Interface
All packets implement the Packet interface:
public interface Packet {
int getId();
void serialize(ByteBuf buffer);
int computeSize();
}
Built-in Packet Categories
| Category | Direction | Examples |
|---|
auth | Bidirectional | AuthToken, ConnectAccept |
connection | Bidirectional | Connect, Disconnect, Ping |
setup | S→C | WorldSettings, AssetInitialize |
player | C→S | ClientMovement, MouseInteraction |
entities | S→C | EntityUpdates, PlayAnimation |
world | S→C | SetChunk, ServerSetBlock |
inventory | Bidirectional | MoveItemStack, SetActiveSlot |
window | Bidirectional | OpenWindow, CloseWindow |
interface | S→C | ChatMessage, Notification |
interaction | Bidirectional | SyncInteractionChains |
camera | S→C | CameraShakeEffect |
Registering Packet Handlers
SubPacketHandler Pattern
Create modular packet handlers:
public class MyPacketHandler implements SubPacketHandler {
private final MyPlugin plugin;
public MyPacketHandler(MyPlugin plugin) {
this.plugin = plugin;
}
@Override
public void registerHandlers(IPacketHandler handler) {
handler.registerHandler(108, this::handleClientMovement);
handler.registerHandler(111, this::handleMouseInteraction);
handler.registerHandler(CustomPacket.ID, this::handleCustomPacket);
}
private void handleClientMovement(Packet packet) {
ClientMovement movement = (ClientMovement) packet;
Vector3d position = movement.getPosition();
}
private void handleMouseInteraction(Packet packet) {
MouseInteraction interaction = (MouseInteraction) packet;
}
}
Registering in Plugin
@Override
protected void setup() {
ServerManager serverManager = HytaleServer.get().getServerManager();
serverManager.registerSubPacketHandler(new MyPacketHandler(this));
}
Sending Packets
Send to Single Player
public void sendToPlayer(Player player, Packet packet) {
player.getConnection().send(packet);
}
Send to Multiple Players
public void sendToAll(Packet packet) {
for (Player player : HytaleServer.get().getOnlinePlayers()) {
player.getConnection().send(packet);
}
}
public void sendToWorld(World world, Packet packet) {
for (Player player : world.getPlayers()) {
player.getConnection().send(packet);
}
}
public void sendToNearby(Vector3d position, double radius, Packet packet) {
for (Player player : HytaleServer.get().getOnlinePlayers()) {
if (player.getPosition().distanceTo(position) <= radius) {
player.getConnection().send(packet);
}
}
}
Send with Callback
player.getConnection().send(packet).thenAccept(result -> {
if (result.isSuccess()) {
getLogger().atInfo().log("Packet sent successfully");
} else {
getLogger().atWarning().log("Packet failed to send: %s", result.getError());
}
});
Built-in Packets Reference
Chat Message
ChatMessage chatPacket = new ChatMessage(
"Hello, World!",
ChatMessage.Type.SYSTEM
);
player.getConnection().send(chatPacket);
Notification
Notification notification = new Notification(
"Achievement Unlocked!",
"You found the secret area",
Notification.Type.SUCCESS,
5000
);
player.getConnection().send(notification);
Play Sound
PlaySoundPacket sound = new PlaySoundPacket(
"MyPlugin/Sounds/alert",
position,
1.0f,
1.0f
);
player.getConnection().send(sound);
Entity Updates
EntityUpdates updates = new EntityUpdates(
entityId,
new HashMap<>() {{
put("health", health);
put("position", position);
}}
);
sendToNearby(position, 64, updates);
Set Block
ServerSetBlock setBlock = new ServerSetBlock(
position,
blockTypeId,
blockState
);
sendToWorld(world, setBlock);
Creating Custom Packets
Define Packet Class
public class MyCustomPacket implements Packet {
public static final int ID = 5000;
private final String message;
private final int value;
private final Vector3d position;
public MyCustomPacket(ByteBuf buffer) {
this.message = PacketIO.readString(buffer);
this.value = buffer.readInt();
this.position = new Vector3d(
buffer.readDouble(),
buffer.readDouble(),
buffer.readDouble()
);
}
public MyCustomPacket(String message, int value, Vector3d position) {
this.message = message;
this.value = value;
this.position = position;
}
@Override
public int getId() {
return ID;
}
@Override
public void serialize(ByteBuf buffer) {
PacketIO.writeString(buffer, message);
buffer.writeInt(value);
buffer.writeDouble(position.x());
buffer.writeDouble(position.y());
buffer.writeDouble(position.z());
}
@Override
public int computeSize() {
return PacketIO.stringSize(message) + 4 + 24;
}
public String getMessage() { return message; }
public int getValue() { return value; }
public Vector3d getPosition() { return position; }
}
Register Custom Packet
@Override
protected void setup() {
PacketRegistry.register(
MyCustomPacket.ID,
MyCustomPacket.class,
MyCustomPacket::new,
MyCustomPacket::validate
);
}
public static boolean validate(ByteBuf buffer) {
return buffer.readableBytes() >= 28;
}
PacketIO Utilities
Writing Data
buffer.writeByte(value);
buffer.writeShort(value);
buffer.writeInt(value);
buffer.writeLong(value);
buffer.writeFloat(value);
buffer.writeDouble(value);
buffer.writeBoolean(value);
PacketIO.writeString(buffer, string);
VarInt.write(buffer, value);
PacketIO.writeArray(buffer, items, PacketIO::writeString);
PacketIO.writeUUID(buffer, uuid);
PacketIO.writeVector3d(buffer, vector);
PacketIO.writeVector3i(buffer, blockPos);
PacketIO.writeOptional(buffer, value, PacketIO::writeString);
Reading Data
byte b = buffer.readByte();
short s = buffer.readShort();
int i = buffer.readInt();
long l = buffer.readLong();
float f = buffer.readFloat();
double d = buffer.readDouble();
boolean bool = buffer.readBoolean();
String str = PacketIO.readString(buffer);
int var = VarInt.read(buffer);
List<String> items = PacketIO.readArray(buffer, PacketIO::readString);
UUID uuid = PacketIO.readUUID(buffer);
Vector3d pos = PacketIO.readVector3d(buffer);
Vector3i blockPos = PacketIO.readVector3i(buffer);
String optional = PacketIO.readOptional(buffer, PacketIO::readString);
Compression
Large packets are automatically compressed using Zstd:
public class LargeDataPacket implements Packet {
public static final boolean IS_COMPRESSED = true;
private final byte[] data;
@Override
public void serialize(ByteBuf buffer) {
buffer.writeBytes(data);
}
}
Manual compression:
byte[] compressed = PacketIO.compress(data);
byte[] decompressed = PacketIO.decompress(compressed);
Client-Bound Custom UI
Custom Page Packet
Send custom UI pages to clients:
public void showCustomUI(Player player, String pageId, Map<String, Object> data) {
CustomPage page = new CustomPage(
pageId,
serializeData(data)
);
player.getConnection().send(page);
}
Window System
OpenWindow openWindow = new OpenWindow(
windowId,
"MyPlugin:CustomWindow",
windowData
);
player.getConnection().send(openWindow);
handler.registerHandler(SendWindowAction.ID, packet -> {
SendWindowAction action = (SendWindowAction) packet;
int windowId = action.getWindowId();
String actionType = action.getActionType();
processWindowAction(player, windowId, actionType, action.getData());
});
CloseWindow closeWindow = new CloseWindow(windowId);
player.getConnection().send(closeWindow);
Handling Client Input
Mouse Interaction
handler.registerHandler(MouseInteraction.ID, packet -> {
MouseInteraction interaction = (MouseInteraction) packet;
MouseButton button = interaction.getButton();
Vector3d hitPos = interaction.getHitPosition();
Vector3i blockPos = interaction.getBlockPosition();
int entityId = interaction.getEntityId();
if (button == MouseButton.RIGHT) {
handleRightClick(player, hitPos, blockPos, entityId);
} else if (button == MouseButton.LEFT) {
handleLeftClick(player, hitPos, blockPos, entityId);
}
});
Movement
handler.registerHandler(ClientMovement.ID, packet -> {
ClientMovement movement = (ClientMovement) packet;
Vector3d position = movement.getPosition();
Vector3f rotation = movement.getRotation();
Vector3d velocity = movement.getVelocity();
boolean onGround = movement.isOnGround();
validateMovement(player, position, velocity);
});
Key Input
handler.registerHandler(KeyInputPacket.ID, packet -> {
KeyInputPacket input = (KeyInputPacket) packet;
int keyCode = input.getKeyCode();
boolean pressed = input.isPressed();
handleKeyInput(player, keyCode, pressed);
});
Rate Limiting
Protect against packet spam:
public class RateLimitedHandler implements SubPacketHandler {
private final Map<UUID, RateLimiter> limiters = new ConcurrentHashMap<>();
@Override
public void registerHandlers(IPacketHandler handler) {
handler.registerHandler(MyPacket.ID, this::handleWithRateLimit);
}
private void handleWithRateLimit(Packet packet) {
Player player = getPacketSender();
RateLimiter limiter = limiters.computeIfAbsent(
player.getUUID(),
k -> new RateLimiter(10, 1000)
);
if (!limiter.tryAcquire()) {
getLogger().atWarning().log("Rate limit exceeded for %s", player.getName());
return;
}
processPacket(packet);
}
}
Error Handling
handler.registerHandler(MyPacket.ID, packet -> {
try {
processPacket(packet);
} catch (Exception e) {
getLogger().atSevere().withCause(e).log("Error processing packet");
if (e instanceof CriticalPacketError) {
player.disconnect("Protocol error");
}
}
});
Packet Validation
public class ValidatedPacket implements Packet {
@Override
public void serialize(ByteBuf buffer) {
int checksum = computeChecksum();
buffer.writeInt(checksum);
}
public static ValidatedPacket deserialize(ByteBuf buffer) {
int checksum = buffer.readInt();
ValidatedPacket packet = new ValidatedPacket(...);
if (packet.computeChecksum() != checksum) {
throw new PacketValidationException("Checksum mismatch");
}
return packet;
}
}
Performance Tips
Packet Batching
public class PacketBatcher {
private final List<Packet> pending = new ArrayList<>();
private final Player player;
public void queue(Packet packet) {
pending.add(packet);
}
public void flush() {
if (pending.isEmpty()) return;
BatchPacket batch = new BatchPacket(pending);
player.getConnection().send(batch);
pending.clear();
}
}
Delta Compression
Only send changes:
public class EntityStateSync {
private final Map<Integer, EntityState> lastSent = new HashMap<>();
public void sync(Player player, List<Entity> entities) {
List<EntityDelta> deltas = new ArrayList<>();
for (Entity entity : entities) {
EntityState current = captureState(entity);
EntityState last = lastSent.get(entity.getId());
if (last == null || !current.equals(last)) {
deltas.add(computeDelta(last, current));
lastSent.put(entity.getId(), current);
}
}
if (!deltas.isEmpty()) {
player.getConnection().send(new EntityDeltaPacket(deltas));
}
}
}
Lazy Serialization
public class LazyPacket implements Packet {
private ByteBuf cached;
@Override
public void serialize(ByteBuf buffer) {
if (cached == null) {
cached = Unpooled.buffer();
doSerialize(cached);
}
buffer.writeBytes(cached.duplicate());
}
}
Troubleshooting
Packet Not Received
- Verify packet ID is registered
- Check handler is registered
- Ensure packet is properly serialized
- Check for exceptions in handler
Deserialization Errors
- Verify read order matches write order
- Check data type sizes
- Validate buffer has enough bytes
- Add bounds checking
Connection Drops
- Check for unhandled exceptions
- Verify packet size limits
- Monitor bandwidth usage
- Check ping/timeout settings
See references/packet-list.md for complete packet catalog.
See references/serialization.md for serialization patterns.