Files
Documentation/content/world/player-refs.en.md
2026-01-20 20:33:59 +01:00

5.7 KiB

title, type, weight
title type weight
PlayerRef docs 4

PlayerRef provides a thread-safe way to reference players across worlds and threads.

Why PlayerRef?

{{< callout type="warning" >}} Direct Player references should never be stored or used across threads. Always use PlayerRef for:

  • Async operations
  • Storing player references
  • Cross-world player tracking {{< /callout >}}

Getting a PlayerRef

import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import java.util.UUID;

Player player = ...;
PlayerRef ref = player.getPlayerRef();

// PlayerRef contains essential player data
UUID uuid = ref.getUuid();
String name = ref.getUsername();  // Note: getUsername(), NOT getName()

Using PlayerRef in Async Code

import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.component.Ref;
import java.util.concurrent.CompletableFuture;

getEventRegistry().register(PlayerConnectEvent.class, event -> {
    PlayerRef ref = event.getPlayerRef();
    World world = event.getWorld();

    // Use ref in async operations
    CompletableFuture.runAsync(() -> {
        // Safe to use ref here
        PlayerData data = loadPlayerData(ref.getUuid());

        // Return to world thread to interact with player
        if (world != null) {
            world.execute(() -> {
                Ref<EntityStore> entityRef = ref.getReference();
                if (entityRef != null && entityRef.isValid()) {
                    applyData(entityRef, data);
                }
                // sendMessage is directly on PlayerRef
                ref.sendMessage(Message.raw("Your data has been loaded!"));
            });
        }
    });
});

PlayerRef Methods

PlayerRef ref = player.getPlayerRef();

// Get player UUID (always available)
UUID uuid = ref.getUuid();

// Get player username (always available)
// Note: It's getUsername(), NOT getName()
String name = ref.getUsername();

// Get entity reference
Ref<EntityStore> entityRef = ref.getReference();

// Check if reference is valid
boolean isValid = ref.isValid();

// Send message directly to player
ref.sendMessage(Message.raw("Hello!"));

// Get transform (position/rotation)
Transform transform = ref.getTransform();

{{< callout type="warning" >}} Note: PlayerRef.getPlayer() does NOT exist. PlayerRef stores essential data that survives world changes, but doesn't hold a direct Player reference. {{< /callout >}}

World PlayerRefs

Worlds provide access to PlayerRefs:

import com.hypixel.hytale.server.core.universe.Universe;
import java.util.Collection;
import java.util.List;

World world = Universe.get().getWorld("default");

// Get all player refs in this world
Collection<PlayerRef> playerRefs = world.getPlayerRefs();

// Get all active players (returns List<Player>)
List<Player> players = world.getPlayers();

Storing Player References

import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class MyPlugin extends JavaPlugin {
    // Store PlayerRefs, NOT Players
    private final Map<UUID, PlayerData> playerData = new ConcurrentHashMap<>();

    public MyPlugin(JavaPluginInit init) {
        super(init);
    }

    @Override
    public void setup() {
        getEventRegistry().register(PlayerConnectEvent.class, event -> {
            PlayerRef ref = event.getPlayerRef();
            playerData.put(ref.getUuid(), new PlayerData(ref));
        });

        getEventRegistry().register(PlayerDisconnectEvent.class, event -> {
            playerData.remove(event.getPlayerRef().getUuid());
        });
    }
}

class PlayerData {
    private final PlayerRef playerRef;
    private int score;

    public PlayerData(PlayerRef ref) {
        this.playerRef = ref;
    }

    public void addScore(int amount) {
        this.score += amount;
        playerRef.sendMessage(Message.raw("Score: " + score));
    }
}

Cross-World Messaging

// Send message to player regardless of their current world
public void sendGlobalMessage(PlayerRef ref, String message) {
    // PlayerRef.sendMessage handles cross-world messaging directly
    ref.sendMessage(Message.raw(message));
}

// Broadcast to all players in all worlds
public void broadcastAll(String message) {
    Message msg = Message.raw(message);
    for (World world : Universe.get().getWorlds().values()) {
        for (PlayerRef ref : world.getPlayerRefs()) {
            ref.sendMessage(msg);
        }
    }
}

PlayerRef vs Player

Feature Player PlayerRef
Thread-safe No Yes
Storable No Yes
Cross-world No Yes
Get UUID Yes Yes
Get Username Yes (getDisplayName()) Yes (getUsername())
Send Message Yes Yes (directly)
Get Position Via TransformComponent Via getTransform()

Best Practices

{{< callout type="info" >}} Always use PlayerRef when:

  • Storing player references beyond the current method
  • Passing to async tasks
  • Tracking players across world changes
  • Building player-related data structures {{< /callout >}}
// Good: Store PlayerRef
private final Map<UUID, PlayerRef> trackedPlayers = new ConcurrentHashMap<>();

// Bad: Store Player directly
// private final Map<UUID, Player> players = new HashMap<>(); // DON'T DO THIS

// Good: Use ref in async
CompletableFuture.runAsync(() -> {
    PlayerRef ref = ...;
    // ref is safe here
});

// Bad: Use Player in async
// CompletableFuture.runAsync(() -> {
//     Player player = ...;  // NOT SAFE
// });