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

```java
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

```java
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

```java
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:

```java
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

```java
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

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

```java
// 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
// });
```
