10 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();
for (Player player : world.getPlayers()) {
if (player.getPosition().distanceTo(sourcePos) <= maxDistance) {
player.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
if (targetPos.y() < 0 || targetPos.y() > 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.
});