Init
This commit is contained in:
272
content/advanced/networking/_index.en.md
Normal file
272
content/advanced/networking/_index.en.md
Normal file
@@ -0,0 +1,272 @@
|
||||
---
|
||||
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
|
||||
266
content/advanced/networking/_index.fr.md
Normal file
266
content/advanced/networking/_index.fr.md
Normal file
@@ -0,0 +1,266 @@
|
||||
---
|
||||
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
|
||||
414
content/advanced/networking/packets.en.md
Normal file
414
content/advanced/networking/packets.en.md
Normal file
@@ -0,0 +1,414 @@
|
||||
---
|
||||
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<UUID, Long> 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.
|
||||
});
|
||||
```
|
||||
407
content/advanced/networking/packets.fr.md
Normal file
407
content/advanced/networking/packets.fr.md
Normal file
@@ -0,0 +1,407 @@
|
||||
---
|
||||
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();
|
||||
|
||||
for (Player player : world.getPlayers()) {
|
||||
if (player.getPosition().distanceTo(sourcePos) <= maxDistance) {
|
||||
player.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<UUID, Long> 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
|
||||
if (targetPos.y() < 0 || targetPos.y() > 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.
|
||||
});
|
||||
```
|
||||
467
content/advanced/networking/sync.en.md
Normal file
467
content/advanced/networking/sync.en.md
Normal file
@@ -0,0 +1,467 @@
|
||||
---
|
||||
title: Synchronization
|
||||
type: docs
|
||||
weight: 2
|
||||
---
|
||||
|
||||
Hytale uses an efficient synchronization system to keep game state consistent between the server and all connected clients. This page explains how entity and world data is synchronized and how to work effectively with the sync system.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The synchronization system handles:
|
||||
|
||||
- **Entity State**: Position, rotation, velocity, health, equipment
|
||||
- **World State**: Block changes, chunk loading/unloading
|
||||
- **Player Data**: Inventory, effects, permissions
|
||||
- **Game Events**: Particles, sounds, animations
|
||||
|
||||
{{< callout type="info" >}}
|
||||
Synchronization is automatic for most operations. The server tracks what needs to be sent to each client and batches updates efficiently.
|
||||
{{< /callout >}}
|
||||
|
||||
---
|
||||
|
||||
## The Network Outdated Pattern
|
||||
|
||||
Hytale uses a "dirty flag" pattern called `isNetworkOutdated` to track which components need synchronization:
|
||||
|
||||
```java
|
||||
// Simplified component sync interface
|
||||
public interface NetworkSyncable {
|
||||
/**
|
||||
* Check if this component has changes that need to be sent to clients
|
||||
*/
|
||||
boolean isNetworkOutdated();
|
||||
|
||||
/**
|
||||
* Mark this component as having pending changes
|
||||
*/
|
||||
void markNetworkOutdated();
|
||||
|
||||
/**
|
||||
* Clear the outdated flag after changes have been sent
|
||||
*/
|
||||
void clearNetworkOutdated();
|
||||
}
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Update Cycle │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 1. Game Logic 2. Mark Outdated 3. Sync Phase │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ entity.set │ → │ component. │ → │ Send to │ │
|
||||
│ │ Position() │ │ markNetwork │ │ clients │ │
|
||||
│ │ │ │ Outdated() │ │ │ │
|
||||
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
|
||||
│ │ │
|
||||
│ 4. Clear Flag │ │
|
||||
│ ┌─────────────┐ │ │
|
||||
│ │ clearNetwork│ ←────────────────────────────────┘ │
|
||||
│ │ Outdated() │ │
|
||||
│ └─────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entity Synchronization
|
||||
|
||||
### Components That Sync
|
||||
|
||||
| Component | Sync Trigger | Data Sent |
|
||||
|-----------|--------------|-----------|
|
||||
| TransformComponent | Position/rotation change | x, y, z, pitch, yaw, roll |
|
||||
| Velocity | Velocity change | vx, vy, vz |
|
||||
| Health | Health change | current, max |
|
||||
| DisplayNameComponent | Name change | display name |
|
||||
| EntityScaleComponent | Scale change | scale factor |
|
||||
| ActiveAnimationComponent | Animation change | animation state |
|
||||
|
||||
### Automatic Sync
|
||||
|
||||
Most entity modifications automatically trigger synchronization:
|
||||
|
||||
```java
|
||||
// All of these automatically sync to clients
|
||||
entity.setPosition(new Vector3d(100, 64, 200));
|
||||
entity.setVelocity(new Vector3d(0, 1, 0));
|
||||
|
||||
Player player = (Player) entity;
|
||||
player.setHealth(10.0f);
|
||||
```
|
||||
|
||||
### Manual Component Updates
|
||||
|
||||
When modifying components directly, the sync flag is managed automatically:
|
||||
|
||||
```java
|
||||
// Getting a component and modifying it
|
||||
TransformComponent transform = entity.getComponent(TransformComponent.class);
|
||||
if (transform != null) {
|
||||
// Internal methods handle markNetworkOutdated()
|
||||
transform.setPosition(newPosition);
|
||||
}
|
||||
|
||||
// Setting a new component
|
||||
entity.setComponent(DisplayNameComponent.class,
|
||||
new DisplayNameComponent(Message.raw("Custom Name")));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## World Synchronization
|
||||
|
||||
### Block Updates
|
||||
|
||||
Block changes are synchronized to players who have the relevant chunks loaded:
|
||||
|
||||
```java
|
||||
// Single block change
|
||||
world.setBlock(position, blockType);
|
||||
|
||||
// The server:
|
||||
// 1. Updates the block in world data
|
||||
// 2. Marks the chunk as having pending updates
|
||||
// 3. On next sync tick, sends BlockChange to relevant players
|
||||
```
|
||||
|
||||
### Chunk Loading
|
||||
|
||||
Chunks are sent to players based on view distance:
|
||||
|
||||
```java
|
||||
// When a player moves or joins
|
||||
// 1. Server calculates which chunks should be visible
|
||||
// 2. New chunks are queued for sending
|
||||
// 3. Chunks out of range are marked for unloading
|
||||
|
||||
// Events you can listen to:
|
||||
@Subscribe
|
||||
public void onChunkLoad(ChunkPreLoadProcessEvent event) {
|
||||
// Chunk is about to be loaded for processing
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onChunkUnload(ChunkUnloadEvent event) {
|
||||
// Chunk is being unloaded
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onChunkSave(ChunkSaveEvent event) {
|
||||
// Chunk is being saved
|
||||
}
|
||||
```
|
||||
|
||||
### View Distance
|
||||
|
||||
```java
|
||||
// Players receive updates based on their view distance
|
||||
// This is configured server-side
|
||||
|
||||
// Particles have a fixed distance
|
||||
public static final int DEFAULT_PARTICLE_DISTANCE = 75;
|
||||
|
||||
// Entity sync uses view distance (configurable)
|
||||
// Block sync uses chunk loading range
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Player Data Synchronization
|
||||
|
||||
### Inventory Sync
|
||||
|
||||
Inventory changes sync automatically:
|
||||
|
||||
```java
|
||||
Player player = ...;
|
||||
Inventory inventory = player.getInventory();
|
||||
|
||||
// These trigger sync automatically
|
||||
inventory.addItem(itemStack);
|
||||
inventory.removeItem(slot);
|
||||
inventory.setItem(slot, itemStack);
|
||||
```
|
||||
|
||||
### Effects Sync
|
||||
|
||||
Status effects are synchronized when added or removed:
|
||||
|
||||
```java
|
||||
// Adding an effect syncs to the player
|
||||
player.addEffect(effect, duration, amplifier);
|
||||
|
||||
// Removing syncs the removal
|
||||
player.removeEffect(effectType);
|
||||
|
||||
// Clearing all effects
|
||||
player.clearEffects();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Batching
|
||||
|
||||
The server automatically batches updates:
|
||||
|
||||
{{< tabs items="How Batching Works,Example" >}}
|
||||
{{< tab >}}
|
||||
|
||||
Updates are collected throughout each tick and sent together:
|
||||
|
||||
```
|
||||
Tick Start
|
||||
│
|
||||
├── Game Logic: entity1.setPosition(...)
|
||||
├── Game Logic: entity2.setPosition(...)
|
||||
├── Game Logic: entity3.setHealth(...)
|
||||
├── Game Logic: world.setBlock(...)
|
||||
├── Game Logic: world.setBlock(...)
|
||||
│
|
||||
Tick End
|
||||
│
|
||||
└── Sync Phase: Send all updates in batched packets
|
||||
```
|
||||
|
||||
Benefits:
|
||||
- Fewer packets sent
|
||||
- Lower network overhead
|
||||
- More efficient compression
|
||||
- Reduced client processing
|
||||
|
||||
{{< /tab >}}
|
||||
{{< tab >}}
|
||||
|
||||
```java
|
||||
// This code results in ONE batched update, not 100 individual packets
|
||||
public void updateManyBlocks(World world) {
|
||||
for (int x = 0; x < 10; x++) {
|
||||
for (int z = 0; z < 10; z++) {
|
||||
world.setBlock(
|
||||
new Vector3i(baseX + x, 64, baseZ + z),
|
||||
blockType
|
||||
);
|
||||
}
|
||||
}
|
||||
// All 100 block changes are batched and sent together
|
||||
}
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
### Delta Compression
|
||||
|
||||
Only changed data is sent:
|
||||
|
||||
```java
|
||||
// If an entity only moves (position changes)
|
||||
// Only position data is sent, not health, inventory, etc.
|
||||
|
||||
entity.setPosition(newPosition);
|
||||
// Sends: position update only
|
||||
|
||||
entity.setHealth(newHealth);
|
||||
// Sends: health update only
|
||||
```
|
||||
|
||||
### Relevancy Filtering
|
||||
|
||||
Updates are only sent to relevant clients:
|
||||
|
||||
```java
|
||||
// Entity updates go to players tracking that entity
|
||||
// (players within view distance)
|
||||
|
||||
// Block updates go to players with that chunk loaded
|
||||
|
||||
// Sound effects respect distance
|
||||
world.playSound(soundEvent, position, volume, pitch);
|
||||
// Only players within hearing range receive this
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Synchronization
|
||||
|
||||
### Creating Synced Data
|
||||
|
||||
If you need to sync custom data, use player metadata or entity components:
|
||||
|
||||
```java
|
||||
// Using player metadata (persisted per-player)
|
||||
public class CustomSyncPlugin extends ServerPlugin {
|
||||
|
||||
private static final String CUSTOM_DATA_KEY = "myplugin:custom_data";
|
||||
|
||||
public void setCustomData(Player player, int value) {
|
||||
player.setMetadata(CUSTOM_DATA_KEY, value);
|
||||
// Send update to player
|
||||
sendCustomDataUpdate(player, value);
|
||||
}
|
||||
|
||||
public int getCustomData(Player player) {
|
||||
Object data = player.getMetadata(CUSTOM_DATA_KEY);
|
||||
return data instanceof Integer ? (Integer) data : 0;
|
||||
}
|
||||
|
||||
private void sendCustomDataUpdate(Player player, int value) {
|
||||
// Use messages to inform client of data changes
|
||||
// (custom client mod required to interpret this)
|
||||
player.sendMessage(Message.raw("")); // or use a custom channel
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Entity Visibility Control
|
||||
|
||||
Control which entities players can see:
|
||||
|
||||
```java
|
||||
// Hide an entity from a specific player
|
||||
entity.setVisibleTo(player, false);
|
||||
|
||||
// Show a previously hidden entity
|
||||
entity.setVisibleTo(player, true);
|
||||
|
||||
// Check visibility
|
||||
boolean canSee = entity.isVisibleTo(player);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging Sync Issues
|
||||
|
||||
{{< tabs items="Common Issues,Diagnostic Tools" >}}
|
||||
{{< tab >}}
|
||||
|
||||
**Entity Not Appearing:**
|
||||
```java
|
||||
// Check if entity is in a loaded chunk
|
||||
Chunk chunk = world.getChunk(entity.getChunkPosition());
|
||||
if (chunk == null || !chunk.isLoaded()) {
|
||||
log.warn("Entity in unloaded chunk!");
|
||||
}
|
||||
|
||||
// Check visibility
|
||||
if (!entity.isVisibleTo(player)) {
|
||||
log.warn("Entity hidden from player!");
|
||||
}
|
||||
|
||||
// Check distance
|
||||
double distance = entity.getPosition().distanceTo(player.getPosition());
|
||||
if (distance > viewDistance) {
|
||||
log.warn("Entity out of view distance: " + distance);
|
||||
}
|
||||
```
|
||||
|
||||
**Block Changes Not Syncing:**
|
||||
```java
|
||||
// Verify chunk is loaded for player
|
||||
Vector3i chunkPos = new Vector3i(
|
||||
blockPos.x() >> 4,
|
||||
0,
|
||||
blockPos.z() >> 4
|
||||
);
|
||||
|
||||
// Check if player has chunk loaded
|
||||
// (implementation depends on server API)
|
||||
```
|
||||
|
||||
**Delayed Updates:**
|
||||
```java
|
||||
// Updates are sent at end of tick
|
||||
// If you need immediate sync, there's usually no way to force it
|
||||
// Design your code to work with batched updates
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
{{< tab >}}
|
||||
|
||||
**Logging Sync Activity:**
|
||||
```java
|
||||
@Subscribe
|
||||
public void onEntityMove(EntityMoveEvent event) {
|
||||
log.debug("Entity {} moved to {}",
|
||||
event.getEntity().getUuid(),
|
||||
event.getNewPosition());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onBlockChange(BlockChangeEvent event) {
|
||||
log.debug("Block at {} changed to {}",
|
||||
event.getPosition(),
|
||||
event.getNewBlockType());
|
||||
}
|
||||
```
|
||||
|
||||
**Monitoring Sync Timing:**
|
||||
```java
|
||||
// Track update frequency for an entity
|
||||
private final Map<UUID, Long> lastUpdateTime = new HashMap<>();
|
||||
|
||||
public void onEntityUpdate(Entity entity) {
|
||||
long now = System.currentTimeMillis();
|
||||
Long last = lastUpdateTime.put(entity.getUuid(), now);
|
||||
|
||||
if (last != null) {
|
||||
long delta = now - last;
|
||||
if (delta < 50) { // Less than 1 tick
|
||||
log.warn("Rapid updates on entity {}: {}ms apart",
|
||||
entity.getUuid(), delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
{{< callout type="info" >}}
|
||||
**Summary of best practices:**
|
||||
|
||||
1. **Trust automatic sync** - Don't try to manually trigger synchronization
|
||||
2. **Batch your updates** - Make multiple changes within a single tick when possible
|
||||
3. **Use events** - Listen for sync-related events to react to state changes
|
||||
4. **Consider view distance** - Don't waste resources updating entities far from players
|
||||
5. **Handle disconnections** - Clean up player-specific sync data when they leave
|
||||
{{< /callout >}}
|
||||
|
||||
```java
|
||||
// Example: Efficient entity spawning with proper sync
|
||||
public class EfficientSpawnPlugin extends ServerPlugin {
|
||||
|
||||
public void spawnManyEntities(World world, Vector3d center, int count) {
|
||||
// Spawn all entities in one tick - they'll be batched
|
||||
for (int i = 0; i < count; i++) {
|
||||
Vector3d offset = new Vector3d(
|
||||
Math.random() * 10 - 5,
|
||||
0,
|
||||
Math.random() * 10 - 5
|
||||
);
|
||||
|
||||
Entity entity = world.spawnEntity(
|
||||
entityType,
|
||||
center.add(offset)
|
||||
);
|
||||
|
||||
// Configure entity (still same tick, still batched)
|
||||
entity.setComponent(DisplayNameComponent.class,
|
||||
new DisplayNameComponent(Message.raw("Entity #" + i)));
|
||||
}
|
||||
// All spawns sent in one batched update at end of tick
|
||||
}
|
||||
}
|
||||
```
|
||||
467
content/advanced/networking/sync.fr.md
Normal file
467
content/advanced/networking/sync.fr.md
Normal file
@@ -0,0 +1,467 @@
|
||||
---
|
||||
title: Synchronization
|
||||
type: docs
|
||||
weight: 2
|
||||
---
|
||||
|
||||
Hytale uses an efficient synchronization system to keep game state consistent between the server and all connected clients. This page explains how entity and world data is synchronized and how to work effectively with the sync system.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The synchronization system handles:
|
||||
|
||||
- **Entity State**: Position, rotation, velocity, health, equipment
|
||||
- **World State**: Block changes, chunk loading/unloading
|
||||
- **Player Data**: Inventory, effects, permissions
|
||||
- **Game Events**: Particles, sounds, animations
|
||||
|
||||
{{< callout type="info" >}}
|
||||
Synchronization is automatic for most operations. The server tracks what needs to be sent to each client and batches updates efficiently.
|
||||
{{< /callout >}}
|
||||
|
||||
---
|
||||
|
||||
## The Network Outdated Pattern
|
||||
|
||||
Hytale uses a "dirty flag" pattern called `isNetworkOutdated` to track which components need synchronization:
|
||||
|
||||
```java
|
||||
// Simplified component sync interface
|
||||
public interface NetworkSyncable {
|
||||
/**
|
||||
* Check if this component has changes that need to be sent to clients
|
||||
*/
|
||||
boolean isNetworkOutdated();
|
||||
|
||||
/**
|
||||
* Mark this component as having pending changes
|
||||
*/
|
||||
void markNetworkOutdated();
|
||||
|
||||
/**
|
||||
* Clear the outdated flag after changes have been sent
|
||||
*/
|
||||
void clearNetworkOutdated();
|
||||
}
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Update Cycle │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 1. Game Logic 2. Mark Outdated 3. Sync Phase │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ entity.set │ → │ component. │ → │ Send to │ │
|
||||
│ │ Position() │ │ markNetwork │ │ clients │ │
|
||||
│ │ │ │ Outdated() │ │ │ │
|
||||
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
|
||||
│ │ │
|
||||
│ 4. Clear Flag │ │
|
||||
│ ┌─────────────┐ │ │
|
||||
│ │ clearNetwork│ ←────────────────────────────────┘ │
|
||||
│ │ Outdated() │ │
|
||||
│ └─────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entity Synchronization
|
||||
|
||||
### Components That Sync
|
||||
|
||||
| Component | Sync Trigger | Data Sent |
|
||||
|-----------|--------------|-----------|
|
||||
| TransformComponent | Position/rotation change | x, y, z, pitch, yaw, roll |
|
||||
| Velocity | Velocity change | vx, vy, vz |
|
||||
| Health | Health change | current, max |
|
||||
| DisplayNameComponent | Name change | display name |
|
||||
| EntityScaleComponent | Scale change | scale factor |
|
||||
| ActiveAnimationComponent | Animation change | animation state |
|
||||
|
||||
### Automatic Sync
|
||||
|
||||
Most entity modifications automatically trigger synchronization:
|
||||
|
||||
```java
|
||||
// All of these automatically sync to clients
|
||||
entity.setPosition(new Vector3d(100, 64, 200));
|
||||
entity.setVelocity(new Vector3d(0, 1, 0));
|
||||
|
||||
Player player = (Player) entity;
|
||||
player.setHealth(10.0f);
|
||||
```
|
||||
|
||||
### Manual Component Updates
|
||||
|
||||
When modifying components directly, the sync flag is managed automatically:
|
||||
|
||||
```java
|
||||
// Getting a component and modifying it
|
||||
TransformComponent transform = entity.getComponent(TransformComponent.class);
|
||||
if (transform != null) {
|
||||
// Internal methods handle markNetworkOutdated()
|
||||
transform.setPosition(newPosition);
|
||||
}
|
||||
|
||||
// Setting a new component
|
||||
entity.setComponent(DisplayNameComponent.class,
|
||||
new DisplayNameComponent(Message.raw("Custom Name")));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## World Synchronization
|
||||
|
||||
### Block Updates
|
||||
|
||||
Block changes are synchronized to players who have the relevant chunks loaded:
|
||||
|
||||
```java
|
||||
// Single block change
|
||||
world.setBlock(position, blockType);
|
||||
|
||||
// The server:
|
||||
// 1. Updates the block in world data
|
||||
// 2. Marks the chunk as having pending updates
|
||||
// 3. On next sync tick, sends BlockChange to relevant players
|
||||
```
|
||||
|
||||
### Chunk Loading
|
||||
|
||||
Chunks are sent to players based on view distance:
|
||||
|
||||
```java
|
||||
// When a player moves or joins
|
||||
// 1. Server calculates which chunks should be visible
|
||||
// 2. New chunks are queued for sending
|
||||
// 3. Chunks out of range are marked for unloading
|
||||
|
||||
// Events you can listen to:
|
||||
@Subscribe
|
||||
public void onChunkLoad(ChunkPreLoadProcessEvent event) {
|
||||
// Chunk is about to be loaded for processing
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onChunkUnload(ChunkUnloadEvent event) {
|
||||
// Chunk is being unloaded
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onChunkSave(ChunkSaveEvent event) {
|
||||
// Chunk is being saved
|
||||
}
|
||||
```
|
||||
|
||||
### View Distance
|
||||
|
||||
```java
|
||||
// Players receive updates based on their view distance
|
||||
// This is configured server-side
|
||||
|
||||
// Particles have a fixed distance
|
||||
public static final int DEFAULT_PARTICLE_DISTANCE = 75;
|
||||
|
||||
// Entity sync uses view distance (configurable)
|
||||
// Block sync uses chunk loading range
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Player Data Synchronization
|
||||
|
||||
### Inventory Sync
|
||||
|
||||
Inventory changes sync automatically:
|
||||
|
||||
```java
|
||||
Player player = ...;
|
||||
Inventory inventory = player.getInventory();
|
||||
|
||||
// These trigger sync automatically
|
||||
inventory.addItem(itemStack);
|
||||
inventory.removeItem(slot);
|
||||
inventory.setItem(slot, itemStack);
|
||||
```
|
||||
|
||||
### Effects Sync
|
||||
|
||||
Status effects are synchronized when added or removed:
|
||||
|
||||
```java
|
||||
// Adding an effect syncs to the player
|
||||
player.addEffect(effect, duration, amplifier);
|
||||
|
||||
// Removing syncs the removal
|
||||
player.removeEffect(effectType);
|
||||
|
||||
// Clearing all effects
|
||||
player.clearEffects();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Batching
|
||||
|
||||
The server automatically batches updates:
|
||||
|
||||
{{< tabs items="How Batching Works,Example" >}}
|
||||
{{< tab >}}
|
||||
|
||||
Updates are collected throughout each tick and sent together:
|
||||
|
||||
```
|
||||
Tick Start
|
||||
│
|
||||
├── Game Logic: entity1.setPosition(...)
|
||||
├── Game Logic: entity2.setPosition(...)
|
||||
├── Game Logic: entity3.setHealth(...)
|
||||
├── Game Logic: world.setBlock(...)
|
||||
├── Game Logic: world.setBlock(...)
|
||||
│
|
||||
Tick End
|
||||
│
|
||||
└── Sync Phase: Send all updates in batched packets
|
||||
```
|
||||
|
||||
Benefits:
|
||||
- Fewer packets sent
|
||||
- Lower network overhead
|
||||
- More efficient compression
|
||||
- Reduced client processing
|
||||
|
||||
{{< /tab >}}
|
||||
{{< tab >}}
|
||||
|
||||
```java
|
||||
// This code results in ONE batched update, not 100 individual packets
|
||||
public void updateManyBlocks(World world) {
|
||||
for (int x = 0; x < 10; x++) {
|
||||
for (int z = 0; z < 10; z++) {
|
||||
world.setBlock(
|
||||
new Vector3i(baseX + x, 64, baseZ + z),
|
||||
blockType
|
||||
);
|
||||
}
|
||||
}
|
||||
// All 100 block changes are batched and sent together
|
||||
}
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
### Delta Compression
|
||||
|
||||
Only changed data is sent:
|
||||
|
||||
```java
|
||||
// If an entity only moves (position changes)
|
||||
// Only position data is sent, not health, inventory, etc.
|
||||
|
||||
entity.setPosition(newPosition);
|
||||
// Sends: position update only
|
||||
|
||||
entity.setHealth(newHealth);
|
||||
// Sends: health update only
|
||||
```
|
||||
|
||||
### Relevancy Filtering
|
||||
|
||||
Updates are only sent to relevant clients:
|
||||
|
||||
```java
|
||||
// Entity updates go to players tracking that entity
|
||||
// (players within view distance)
|
||||
|
||||
// Block updates go to players with that chunk loaded
|
||||
|
||||
// Sound effects respect distance
|
||||
world.playSound(soundEvent, position, volume, pitch);
|
||||
// Only players within hearing range receive this
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Synchronization
|
||||
|
||||
### Creating Synced Data
|
||||
|
||||
If you need to sync custom data, use player metadata or entity components:
|
||||
|
||||
```java
|
||||
// Using player metadata (persisted per-player)
|
||||
public class CustomSyncPlugin extends ServerPlugin {
|
||||
|
||||
private static final String CUSTOM_DATA_KEY = "myplugin:custom_data";
|
||||
|
||||
public void setCustomData(Player player, int value) {
|
||||
player.setMetadata(CUSTOM_DATA_KEY, value);
|
||||
// Send update to player
|
||||
sendCustomDataUpdate(player, value);
|
||||
}
|
||||
|
||||
public int getCustomData(Player player) {
|
||||
Object data = player.getMetadata(CUSTOM_DATA_KEY);
|
||||
return data instanceof Integer ? (Integer) data : 0;
|
||||
}
|
||||
|
||||
private void sendCustomDataUpdate(Player player, int value) {
|
||||
// Use messages to inform client of data changes
|
||||
// (custom client mod required to interpret this)
|
||||
player.sendMessage(Message.raw("")); // or use a custom channel
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Entity Visibility Control
|
||||
|
||||
Control which entities players can see:
|
||||
|
||||
```java
|
||||
// Hide an entity from a specific player
|
||||
entity.setVisibleTo(player, false);
|
||||
|
||||
// Show a previously hidden entity
|
||||
entity.setVisibleTo(player, true);
|
||||
|
||||
// Check visibility
|
||||
boolean canSee = entity.isVisibleTo(player);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging Sync Issues
|
||||
|
||||
{{< tabs items="Common Issues,Diagnostic Tools" >}}
|
||||
{{< tab >}}
|
||||
|
||||
**Entity Not Appearing:**
|
||||
```java
|
||||
// Check if entity is in a loaded chunk
|
||||
Chunk chunk = world.getChunk(entity.getChunkPosition());
|
||||
if (chunk == null || !chunk.isLoaded()) {
|
||||
log.warn("Entity in unloaded chunk!");
|
||||
}
|
||||
|
||||
// Check visibility
|
||||
if (!entity.isVisibleTo(player)) {
|
||||
log.warn("Entity hidden from player!");
|
||||
}
|
||||
|
||||
// Check distance
|
||||
double distance = entity.getPosition().distanceTo(player.getPosition());
|
||||
if (distance > viewDistance) {
|
||||
log.warn("Entity out of view distance: " + distance);
|
||||
}
|
||||
```
|
||||
|
||||
**Block Changes Not Syncing:**
|
||||
```java
|
||||
// Verify chunk is loaded for player
|
||||
Vector3i chunkPos = new Vector3i(
|
||||
blockPos.x() >> 4,
|
||||
0,
|
||||
blockPos.z() >> 4
|
||||
);
|
||||
|
||||
// Check if player has chunk loaded
|
||||
// (implementation depends on server API)
|
||||
```
|
||||
|
||||
**Delayed Updates:**
|
||||
```java
|
||||
// Updates are sent at end of tick
|
||||
// If you need immediate sync, there's usually no way to force it
|
||||
// Design your code to work with batched updates
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
{{< tab >}}
|
||||
|
||||
**Logging Sync Activity:**
|
||||
```java
|
||||
@Subscribe
|
||||
public void onEntityMove(EntityMoveEvent event) {
|
||||
log.debug("Entity {} moved to {}",
|
||||
event.getEntity().getUuid(),
|
||||
event.getNewPosition());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onBlockChange(BlockChangeEvent event) {
|
||||
log.debug("Block at {} changed to {}",
|
||||
event.getPosition(),
|
||||
event.getNewBlockType());
|
||||
}
|
||||
```
|
||||
|
||||
**Monitoring Sync Timing:**
|
||||
```java
|
||||
// Track update frequency for an entity
|
||||
private final Map<UUID, Long> lastUpdateTime = new HashMap<>();
|
||||
|
||||
public void onEntityUpdate(Entity entity) {
|
||||
long now = System.currentTimeMillis();
|
||||
Long last = lastUpdateTime.put(entity.getUuid(), now);
|
||||
|
||||
if (last != null) {
|
||||
long delta = now - last;
|
||||
if (delta < 50) { // Less than 1 tick
|
||||
log.warn("Rapid updates on entity {}: {}ms apart",
|
||||
entity.getUuid(), delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
{{< callout type="info" >}}
|
||||
**Summary of best practices:**
|
||||
|
||||
1. **Trust automatic sync** - Don't try to manually trigger synchronization
|
||||
2. **Batch your updates** - Make multiple changes within a single tick when possible
|
||||
3. **Use events** - Listen for sync-related events to react to state changes
|
||||
4. **Consider view distance** - Don't waste resources updating entities far from players
|
||||
5. **Handle disconnections** - Clean up player-specific sync data when they leave
|
||||
{{< /callout >}}
|
||||
|
||||
```java
|
||||
// Example: Efficient entity spawning with proper sync
|
||||
public class EfficientSpawnPlugin extends ServerPlugin {
|
||||
|
||||
public void spawnManyEntities(World world, Vector3d center, int count) {
|
||||
// Spawn all entities in one tick - they'll be batched
|
||||
for (int i = 0; i < count; i++) {
|
||||
Vector3d offset = new Vector3d(
|
||||
Math.random() * 10 - 5,
|
||||
0,
|
||||
Math.random() * 10 - 5
|
||||
);
|
||||
|
||||
Entity entity = world.spawnEntity(
|
||||
entityType,
|
||||
center.add(offset)
|
||||
);
|
||||
|
||||
// Configure entity (still same tick, still batched)
|
||||
entity.setComponent(DisplayNameComponent.class,
|
||||
new DisplayNameComponent(Message.raw("Entity #" + i)));
|
||||
}
|
||||
// All spawns sent in one batched update at end of tick
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user