Files
Documentation/content/advanced/networking/_index.en.md
2026-01-20 20:33:59 +01:00

273 lines
8.5 KiB
Markdown

---
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<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:**
```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