267 lines
8.1 KiB
Markdown
267 lines
8.1 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 -> {
|
|
Player player = event.getPlayer();
|
|
Vector3i targetBlock = event.getTargetBlock();
|
|
|
|
if (targetBlock != null) {
|
|
// Validate distance
|
|
Vector3d targetPos = new Vector3d(targetBlock.x(), targetBlock.y(), targetBlock.z());
|
|
double distance = player.getPosition().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();
|
|
|
|
CompletableFuture.runAsync(() -> {
|
|
// Some async operation
|
|
processData();
|
|
}).thenAccept(result -> {
|
|
// Player might have disconnected
|
|
Player player = playerRef.getPlayer();
|
|
if (player != null) {
|
|
player.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
|