--- title: Networking type: docs weight: 6 --- The Hytale server uses a custom networking system for client-server communication. Understanding how data is synchronized and transmitted is essential for creating responsive and efficient plugins. {{< cards cols="2" >}} {{< card link="packets" title="Packets" subtitle="Network packet reference and usage" icon="server" >}} {{< card link="sync" title="Synchronization" subtitle="Entity and world state synchronization" icon="refresh" >}} {{< /cards >}} --- ## Overview The networking system in Hytale handles: - **Client-Server Communication**: Bidirectional packet transmission - **Entity Synchronization**: Keeping entity states consistent across clients - **World Updates**: Broadcasting block changes and world events - **Player Actions**: Processing and validating player inputs {{< callout type="info" >}} Most networking operations are handled automatically by the server. Plugins typically interact with the high-level APIs rather than raw packets. {{< /callout >}} --- ## Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ Server │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ World │ │ Entity │ │ Plugin │ │ │ │ State │ │ Manager │ │ Events │ │ │ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │ │ │ │ │ │ │ └────────────────┼─────────────────────┘ │ │ │ │ │ ┌─────┴─────┐ │ │ │ Network │ │ │ │ Layer │ │ │ └─────┬─────┘ │ └──────────────────────────┼──────────────────────────────────┘ │ ┌────────────┼────────────┐ │ │ │ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │ Client │ │ Client │ │ Client │ │ 1 │ │ 2 │ │ N │ └─────────┘ └─────────┘ └─────────┘ ``` --- ## Key Concepts ### Broadcast Distance Different types of data have different broadcast distances: | Data Type | Default Distance | Description | |-----------|------------------|-------------| | Particles | 75 blocks | `DEFAULT_PARTICLE_DISTANCE` | | Entities | View distance | Based on server configuration | | Sounds | 16 blocks | Default sound range | | Block changes | Chunk-based | Sent to players with loaded chunks | ### Network Outdated Pattern The server uses a "network outdated" pattern to efficiently synchronize state: ```java // Components implement this pattern for network synchronization public interface NetworkSyncable { boolean isNetworkOutdated(); void markNetworkOutdated(); void clearNetworkOutdated(); } ``` When a component's state changes: 1. The component marks itself as `networkOutdated` 2. The sync system detects outdated components 3. Updates are batched and sent to relevant clients 4. The outdated flag is cleared after sending --- ## Common Patterns ### Sending Messages to Players ```java // Send to a specific player player.sendMessage(Message.raw("Hello!")); // Send to all players in a world for (Player p : world.getPlayers()) { p.sendMessage(Message.raw("World announcement")); } // Send to all online players for (Player p : server.getOnlinePlayers()) { p.sendMessage(Message.raw("Server broadcast")); } ``` ### Spawning Particles (Network Broadcast) ```java // Spawn particles visible to nearby players world.spawnParticle(particleSystem, position); // The server handles: // 1. Finding players within DEFAULT_PARTICLE_DISTANCE (75 blocks) // 2. Sending SpawnParticleSystem packet (ID: 152) to those players // 3. Client-side rendering ``` ### Entity Position Updates ```java // When you modify an entity's position entity.setPosition(newPosition); // The server automatically: // 1. Marks the TransformComponent as network outdated // 2. Batches position updates // 3. Sends updates to players tracking this entity ``` --- ## Best Practices {{< tabs items="Performance,Security,Reliability" >}} {{< tab >}} **Minimize Network Traffic:** ```java // BAD: Sending updates every tick @Subscribe public void onTick(TickEvent event) { for (Player p : server.getOnlinePlayers()) { p.sendMessage(Message.raw("Tick!")); // Don't do this! } } // GOOD: Batch updates and use intervals private int tickCounter = 0; @Subscribe public void onTick(TickEvent event) { tickCounter++; if (tickCounter >= 20) { // Every second tickCounter = 0; broadcastScoreboard(); } } ``` **Use Appropriate Broadcast Distances:** ```java // Only notify nearby players for local events Vector3d eventPos = event.getPosition(); double range = 50.0; for (Player p : world.getPlayers()) { if (p.getPosition().distanceTo(eventPos) <= range) { p.sendMessage(Message.raw("Something happened nearby!")); } } ``` {{< /tab >}} {{< tab >}} **Validate Client Input:** ```java // Note: PlayerInteractEvent is deprecated. For block interactions, use UseBlockEvent. getEventRegistry().register(PlayerInteractEvent.class, event -> { PlayerRef playerRef = event.getPlayerRef(); Vector3i targetBlock = event.getTargetBlock(); if (targetBlock != null && playerRef != null) { // Get player position via PlayerRef's transform Transform transform = playerRef.getTransform(); if (transform != null) { Vector3d playerPos = transform.getPosition(); // Note: Vector3i uses getX(), getY(), getZ() - NOT x(), y(), z() Vector3d targetPos = new Vector3d(targetBlock.getX(), targetBlock.getY(), targetBlock.getZ()); double distance = playerPos.distance(targetPos); if (distance > MAX_INTERACTION_DISTANCE) { event.setCancelled(true); return; } } } }); ``` **Rate Limit Actions:** ```java private final Map lastAction = new HashMap<>(); private static final long COOLDOWN_MS = 1000; public boolean canPerformAction(Player player) { long now = System.currentTimeMillis(); Long last = lastAction.get(player.getUuid()); if (last != null && now - last < COOLDOWN_MS) { return false; } lastAction.put(player.getUuid(), now); return true; } ``` {{< /tab >}} {{< tab >}} **Handle Disconnections:** ```java @Subscribe public void onPlayerDisconnect(PlayerDisconnectEvent event) { UUID playerId = event.getPlayerRef().getUuid(); // Clean up player-specific data playerData.remove(playerId); pendingOperations.removeIf(op -> op.getPlayerId().equals(playerId)); } ``` **Use PlayerRef for Async Operations:** ```java // PlayerRef is safe to use across async boundaries PlayerRef playerRef = event.getPlayerRef(); World world = event.getWorld(); CompletableFuture.runAsync(() -> { // Some async operation processData(); }).thenAccept(result -> { // Return to world thread for game state changes world.execute(() -> { // PlayerRef.getPlayer() does NOT exist - send message directly on PlayerRef playerRef.sendMessage(Message.raw("Operation complete!")); }); }); ``` {{< /tab >}} {{< /tabs >}} --- ## Related Topics - [Player API]({{< relref "/world/entities/player-api" >}}) - Player networking methods - [Events]({{< relref "/core-concepts/events" >}}) - Network-related events - [Entity Components]({{< relref "/world/entities/entity-components" >}}) - Synchronized component data