Files
2026-01-20 20:33:59 +01:00

11 KiB

title, type, weight
title type weight
Packets docs 1

Hytale uses a binary packet protocol for efficient client-server communication. While most networking is handled internally, understanding the packet system helps when debugging or implementing advanced features.

{{< callout type="warning" >}} Direct packet manipulation is generally not recommended. Use the high-level APIs provided by the plugin system whenever possible. {{< /callout >}}


Packet Structure

All packets follow a common structure:

┌──────────────┬───────────────┬─────────────────────┐
│  Packet ID   │  Payload Size │      Payload        │
│   (varint)   │   (varint)    │  (variable bytes)   │
└──────────────┴───────────────┴─────────────────────┘
Field Type Description
Packet ID VarInt Unique identifier for packet type
Payload Size VarInt Size of payload in bytes
Payload bytes Packet-specific data

Known Packet IDs

{{< tabs items="Client-bound,Server-bound" >}} {{< tab >}}

Packets sent from server to client:

ID Name Description
152 SpawnParticleSystem Spawns particles at a location
-- EntitySpawn Spawns an entity on the client
-- EntityDespawn Removes an entity from the client
-- EntityMove Updates entity position
-- BlockChange Updates a single block
-- ChunkData Sends chunk block data
-- ChatMessage Sends chat/system message
-- PlaySound Plays a sound effect
-- SetWeather Changes weather state
-- TimeUpdate Synchronizes world time
-- PlayerInfo Updates player list data

{{< callout type="info" >}} Packet IDs marked with -- are internal and subject to change between versions. {{< /callout >}}

{{< /tab >}} {{< tab >}}

Packets sent from client to server:

ID Name Description
-- PlayerPosition Player movement update
-- PlayerAction Player action (attack, use, etc.)
-- ChatMessage Chat message from player
-- BlockBreak Block breaking progress
-- BlockPlace Block placement request
-- UseItem Item use request
-- Interaction Entity/block interaction
-- MouseMotion Mouse movement data
-- MouseButton Mouse button press/release

{{< /tab >}} {{< /tabs >}}


Particle System Packet

The particle system packet (ID: 152) is used to spawn visual effects:

// Internal packet structure (for reference only)
public class SpawnParticleSystemPacket {
    private ParticleSystem particleSystem;
    private Vector3d position;
    private Vector3f rotation;      // Optional
    private Vector3f scale;         // Optional
    private Entity attachedEntity;  // Optional
}

Broadcast Distance: DEFAULT_PARTICLE_DISTANCE = 75 blocks

Spawning Particles via API

// Simple particle spawn
world.spawnParticle(particleSystem, position);

// Particle with rotation
world.spawnParticle(particleSystem, position, rotation);

// Particle attached to entity
world.spawnParticle(particleSystem, entity);

Entity Packets

Entity Spawn

When an entity enters a player's view distance, the server sends entity spawn data:

// Automatic - when entity is created in world
Entity entity = world.spawnEntity(entityType, position);
// Server automatically notifies nearby clients

// Manual control via entity visibility
entity.setVisibleTo(player, true);  // Force show to specific player
entity.setVisibleTo(player, false); // Hide from specific player

Entity Updates

Entity state changes are batched and sent periodically:

Component Update Frequency Priority
TransformComponent Every tick if changed High
Velocity Every tick if changed High
Health On change Medium
DisplayName On change Low
Equipment On change Medium
Effects On change Low
// Position updates are automatic
entity.setPosition(newPos);

// Velocity updates are automatic
entity.setVelocity(newVelocity);

// Display name updates
entity.setComponent(DisplayNameComponent.class, new DisplayNameComponent("New Name"));

Block Packets

Single Block Change

// Server-side API
world.setBlock(position, blockType);
// Automatically sends BlockChange to players with loaded chunk

// With block state
world.setBlock(position, blockType, blockState);

Multi-Block Change

For bulk updates, changes are batched by chunk:

// Multiple blocks in same chunk are batched
for (int x = 0; x < 16; x++) {
    for (int z = 0; z < 16; z++) {
        world.setBlock(new Vector3i(chunkX * 16 + x, 64, chunkZ * 16 + z), blockType);
    }
}
// Server batches these into a single multi-block update packet

Sound Packets

Playing Sounds

// Play sound at position
world.playSound(soundEvent, position, volume, pitch);

// Play sound to specific player
player.playSound(soundEvent, position, volume, pitch);

// Play sound from entity
entity.playSound(soundEvent, volume, pitch);

Sound Categories

Category Description Default Volume
MASTER Overall volume 1.0
MUSIC Background music 0.5
AMBIENT Environmental sounds 0.8
BLOCKS Block interaction sounds 1.0
ENTITIES Entity sounds 1.0
PLAYERS Player-made sounds 1.0
UI User interface sounds 1.0

Chat/Message Packets

Message Types

// Chat message (appears in chat)
player.sendMessage(Message.raw("Hello!"));

// Translation-based message
player.sendMessage(Message.translation("my.translation.key")
    .param("player", player.getDisplayName()));

Rich Text Formatting

Hytale's Message class supports styling through method chaining:

// Raw text message
Message message = Message.raw("Hello, World!");

// Colored message (using java.awt.Color)
Message colored = Message.raw("Important!")
    .color(Color.RED);

// Message with parameters (for translations)
Message parameterized = Message.translation("quest.completed")
    .param("player", playerName)
    .param("reward", "100 XP");

// Bold/italic formatting
Message styled = Message.raw("Notice!")
    .bold(true)
    .italic(false);

// Inserting multiple messages
Message combined = Message.empty()
    .insert(Message.raw("Part 1"))
    .insert(Message.raw(" - "))
    .insert(Message.raw("Part 2"));

Network Events

Intercepting Network Activity

public class NetworkMonitorPlugin extends ServerPlugin {

    @Subscribe
    public void onPlayerConnect(PlayerConnectEvent event) {
        // New connection established
        log.info("Player connected: " + event.getUsername());
    }

    @Subscribe
    public void onPlayerSetupConnect(PlayerSetupConnectEvent event) {
        // Before connection is fully established
        // Can be cancelled
        if (isBlacklisted(event.getUuid())) {
            event.setCancelled(true);
        }
    }

    @Subscribe
    public void onPlayerDisconnect(PlayerDisconnectEvent event) {
        // Connection closed
        log.info("Player disconnected: " + event.getPlayerRef().getUuid());
    }
}

Performance Considerations

{{< tabs items="Optimization,Debugging" >}} {{< tab >}}

Reducing Network Load

1. Batch Updates:

// Instead of sending many small updates
world.setBlock(pos1, block);
world.setBlock(pos2, block);
world.setBlock(pos3, block);

// The server automatically batches these per tick
// No manual batching needed

2. Use Appropriate Update Rates:

// For frequently changing data, consider throttling
private long lastUpdate = 0;
private static final long UPDATE_INTERVAL_MS = 50; // 20 updates/sec

public void updateDisplay() {
    long now = System.currentTimeMillis();
    if (now - lastUpdate >= UPDATE_INTERVAL_MS) {
        lastUpdate = now;
        sendDisplayUpdate();
    }
}

3. Limit Broadcast Scope:

// Only send to players who need the update
double maxDistance = 100.0;
Vector3d sourcePos = event.getPosition();

// Note: Player doesn't have getPosition() directly
// Use PlayerRef to get position via getTransform()
for (PlayerRef playerRef : world.getPlayerRefs()) {
    Transform transform = playerRef.getTransform();
    if (transform != null) {
        double distance = transform.getPosition().distance(sourcePos);
        if (distance <= maxDistance) {
            playerRef.sendMessage(notification);
        }
    }
}

{{< /tab >}} {{< tab >}}

Debugging Network Issues

1. Log Network Activity:

@Subscribe
public void onPlayerConnect(PlayerConnectEvent event) {
    log.debug("Connect from {} ({})",
        event.getUsername(),
        event.getUuid());
}

@Subscribe
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
    log.debug("Disconnect: {}",
        event.getPlayerRef().getUuid());
}

2. Monitor Packet Timing:

// Track message round-trip time
private final Map<UUID, Long> pingTimes = new ConcurrentHashMap<>();

public void sendPing(Player player) {
    pingTimes.put(player.getUuid(), System.currentTimeMillis());
    player.sendMessage(Message.raw("ping"));
}

// In start() method:
getEventRegistry().register(PlayerChatEvent.class, event -> {
    if (event.getContent().equals("pong")) {
        Long sent = pingTimes.remove(event.getSender().getUuid());
        if (sent != null) {
            long rtt = System.currentTimeMillis() - sent;
            getLogger().at(Level.INFO).log("RTT for " + event.getSender().getUsername() + ": " + rtt + "ms");
        }
    }
});

{{< /tab >}} {{< /tabs >}}


Security Notes

{{< callout type="error" >}} Never trust client data! Always validate:

  • Positions (within valid range, not too far from player)
  • Actions (player has permission, cooldown respected)
  • Resources (item counts, block placements) {{< /callout >}}
// Example: Validating a block placement
getEventRegistry().register(PlaceBlockEvent.class, event -> {
    Vector3i targetPos = event.getTargetBlock();

    // Validate world bounds
    // Note: Vector3i uses getY(), NOT y()
    if (targetPos.getY() < 0 || targetPos.getY() > 255) {
        event.setCancelled(true);
        return;
    }

    // Note: PlaceBlockEvent is an ECS event - player access is through
    // the component system, not direct getPlayer() calls.
    // For player-specific validation, consider using entity components.
});