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

8.5 KiB

title, type, weight
title type weight
Networking docs 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:

// 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

// 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)

// 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

// 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:

// 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:

// 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:

// 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:

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:

@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:

// 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 >}}


  • [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