8.5 KiB
title, type, weight
| title | type | weight |
|---|---|---|
| Networking | docs | 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:
// Components implement this pattern for network synchronization
public interface NetworkSyncable {
boolean isNetworkOutdated();
void markNetworkOutdated();
void clearNetworkOutdated();
}
When a component's state changes:
- The component marks itself as
networkOutdated - The sync system detects outdated components
- Updates are batched and sent to relevant clients
- The outdated flag is cleared after sending
Common Patterns
Sending Messages to Players
// 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)
// 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
// 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:
// 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:
// 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:
// 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:
private final Map<UUID, Long> 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:
@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:
// 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