--- title: Packets type: docs weight: 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: ```java // 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 ```java // 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: ```java // 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 | ```java // 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 ```java // 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: ```java // 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 ```java // 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 ```java // 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: ```java // 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 ```java 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:** ```java // 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:** ```java // 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:** ```java // 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:** ```java @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:** ```java // Track message round-trip time private final Map 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 >}} ```java // 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. }); ```