Init
This commit is contained in:
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.
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user