415 lines
11 KiB
Markdown
415 lines
11 KiB
Markdown
---
|
|
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.
|
|
});
|
|
```
|