Loading...
Loading...
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".
npx skill4agent add mnkyarts/hytale-skills hytale-networkingClient <---> Server
| |
Packet Packet
Encoder Decoder
| |
ByteBuf <-> ByteBuf| Component | Description |
|---|---|
| Data structure with serialization |
| Maps packet IDs to classes |
| Processes incoming packets |
| Serializes packets to bytes |
| Deserializes bytes to packets |
Packetpublic interface Packet {
int getId();
void serialize(ByteBuf buffer);
int computeSize();
}| Category | Direction | Examples |
|---|---|---|
| Bidirectional | AuthToken, ConnectAccept |
| Bidirectional | Connect, Disconnect, Ping |
| S→C | WorldSettings, AssetInitialize |
| C→S | ClientMovement, MouseInteraction |
| S→C | EntityUpdates, PlayAnimation |
| S→C | SetChunk, ServerSetBlock |
| Bidirectional | MoveItemStack, SetActiveSlot |
| Bidirectional | OpenWindow, CloseWindow |
| S→C | ChatMessage, Notification |
| Bidirectional | SyncInteractionChains |
| S→C | CameraShakeEffect |
public class MyPacketHandler implements SubPacketHandler {
private final MyPlugin plugin;
public MyPacketHandler(MyPlugin plugin) {
this.plugin = plugin;
}
@Override
public void registerHandlers(IPacketHandler handler) {
// Register by packet ID
handler.registerHandler(108, this::handleClientMovement);
handler.registerHandler(111, this::handleMouseInteraction);
// Or by packet class
handler.registerHandler(CustomPacket.ID, this::handleCustomPacket);
}
private void handleClientMovement(Packet packet) {
ClientMovement movement = (ClientMovement) packet;
Vector3d position = movement.getPosition();
// Process movement
}
private void handleMouseInteraction(Packet packet) {
MouseInteraction interaction = (MouseInteraction) packet;
// Process mouse input
}
}@Override
protected void setup() {
// Register packet handler with server manager
ServerManager serverManager = HytaleServer.get().getServerManager();
serverManager.registerSubPacketHandler(new MyPacketHandler(this));
}public void sendToPlayer(Player player, Packet packet) {
player.getConnection().send(packet);
}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);
}
}
}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());
}
});// Send chat message to player
ChatMessage chatPacket = new ChatMessage(
"Hello, World!",
ChatMessage.Type.SYSTEM
);
player.getConnection().send(chatPacket);// Send notification popup
Notification notification = new Notification(
"Achievement Unlocked!",
"You found the secret area",
Notification.Type.SUCCESS,
5000 // Duration in ms
);
player.getConnection().send(notification);// Play sound at position
PlaySoundPacket sound = new PlaySoundPacket(
"MyPlugin/Sounds/alert",
position,
1.0f, // Volume
1.0f // Pitch
);
player.getConnection().send(sound);// Send entity state update
EntityUpdates updates = new EntityUpdates(
entityId,
new HashMap<>() {{
put("health", health);
put("position", position);
}}
);
sendToNearby(position, 64, updates);// Update block on client
ServerSetBlock setBlock = new ServerSetBlock(
position,
blockTypeId,
blockState
);
sendToWorld(world, setBlock);public class MyCustomPacket implements Packet {
public static final int ID = 5000; // Custom ID (use high numbers)
private final String message;
private final int value;
private final Vector3d position;
// Deserialize constructor
public MyCustomPacket(ByteBuf buffer) {
this.message = PacketIO.readString(buffer);
this.value = buffer.readInt();
this.position = new Vector3d(
buffer.readDouble(),
buffer.readDouble(),
buffer.readDouble()
);
}
// Create constructor
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; // int + 3 doubles
}
// Getters
public String getMessage() { return message; }
public int getValue() { return value; }
public Vector3d getPosition() { return position; }
}@Override
protected void setup() {
PacketRegistry.register(
MyCustomPacket.ID,
MyCustomPacket.class,
MyCustomPacket::new, // Deserializer
MyCustomPacket::validate // Optional validator
);
}
// Optional validation
public static boolean validate(ByteBuf buffer) {
// Quick validation without full parse
return buffer.readableBytes() >= 28; // Minimum size
}// Primitives
buffer.writeByte(value);
buffer.writeShort(value);
buffer.writeInt(value);
buffer.writeLong(value);
buffer.writeFloat(value);
buffer.writeDouble(value);
buffer.writeBoolean(value);
// Strings
PacketIO.writeString(buffer, string);
// Variable-length integers
VarInt.write(buffer, value);
// Collections
PacketIO.writeArray(buffer, items, PacketIO::writeString);
// UUIDs
PacketIO.writeUUID(buffer, uuid);
// Vectors
PacketIO.writeVector3d(buffer, vector);
PacketIO.writeVector3i(buffer, blockPos);
// Optional values (nullable)
PacketIO.writeOptional(buffer, value, PacketIO::writeString);// Primitives
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();
// Strings
String str = PacketIO.readString(buffer);
// Variable-length integers
int var = VarInt.read(buffer);
// Collections
List<String> items = PacketIO.readArray(buffer, PacketIO::readString);
// UUIDs
UUID uuid = PacketIO.readUUID(buffer);
// Vectors
Vector3d pos = PacketIO.readVector3d(buffer);
Vector3i blockPos = PacketIO.readVector3i(buffer);
// Optional values
String optional = PacketIO.readOptional(buffer, PacketIO::readString);public class LargeDataPacket implements Packet {
public static final boolean IS_COMPRESSED = true;
private final byte[] data;
@Override
public void serialize(ByteBuf buffer) {
// Data will be automatically compressed if IS_COMPRESSED = true
buffer.writeBytes(data);
}
}// Compress
byte[] compressed = PacketIO.compress(data);
// Decompress
byte[] decompressed = PacketIO.decompress(compressed);public void showCustomUI(Player player, String pageId, Map<String, Object> data) {
CustomPage page = new CustomPage(
pageId,
serializeData(data)
);
player.getConnection().send(page);
}// Open custom window
OpenWindow openWindow = new OpenWindow(
windowId,
"MyPlugin:CustomWindow",
windowData
);
player.getConnection().send(openWindow);
// Handle window actions
handler.registerHandler(SendWindowAction.ID, packet -> {
SendWindowAction action = (SendWindowAction) packet;
int windowId = action.getWindowId();
String actionType = action.getActionType();
processWindowAction(player, windowId, actionType, action.getData());
});
// Close window
CloseWindow closeWindow = new CloseWindow(windowId);
player.getConnection().send(closeWindow);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);
}
});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);
});handler.registerHandler(KeyInputPacket.ID, packet -> {
KeyInputPacket input = (KeyInputPacket) packet;
int keyCode = input.getKeyCode();
boolean pressed = input.isPressed();
handleKeyInput(player, keyCode, pressed);
});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) // 10 per second
);
if (!limiter.tryAcquire()) {
getLogger().atWarning().log("Rate limit exceeded for %s", player.getName());
return;
}
processPacket(packet);
}
}handler.registerHandler(MyPacket.ID, packet -> {
try {
processPacket(packet);
} catch (Exception e) {
getLogger().atSevere().withCause(e).log("Error processing packet");
// Optionally disconnect on critical errors
if (e instanceof CriticalPacketError) {
player.disconnect("Protocol error");
}
}
});public class ValidatedPacket implements Packet {
@Override
public void serialize(ByteBuf buffer) {
// Add checksum
int checksum = computeChecksum();
buffer.writeInt(checksum);
// Write data
}
public static ValidatedPacket deserialize(ByteBuf buffer) {
int checksum = buffer.readInt();
// Read data
ValidatedPacket packet = new ValidatedPacket(...);
if (packet.computeChecksum() != checksum) {
throw new PacketValidationException("Checksum mismatch");
}
return packet;
}
}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;
// Send as batch if supported
BatchPacket batch = new BatchPacket(pending);
player.getConnection().send(batch);
pending.clear();
}
}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));
}
}
}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());
}
}references/packet-list.mdreferences/serialization.md