Player Management & Persistence
This guide covers the Hytale player management and persistence systems, including how to look up players, store custom player data, handle player events, and understand server authentication.
Architecture Overview
Section titled “Architecture Overview”Universe├── PlayerRef (connected players map)├── PlayerStorage (data persistence)└── World ├── Players (world-specific player map) └── EntityStore (ECS for player entities)
Player Data Flow:PlayerStorage.load() -> Holder<EntityStore> -> World.addPlayer() -> Ref<EntityStore>PlayerRef - Player Session Handle
Section titled “PlayerRef - Player Session Handle”PlayerRef is the primary handle for accessing a connected player’s session. It provides access to player identity, networking, and entity state.
Key Properties
Section titled “Key Properties”import com.hypixel.hytale.server.core.universe.PlayerRef;import com.hypixel.hytale.server.core.universe.Universe;
// Get a player by UUIDPlayerRef player = Universe.get().getPlayer(uuid);
// Access player informationUUID uuid = player.getUuid();String username = player.getUsername();String language = player.getLanguage();
// Get the player's packet handler for sending packetsPacketHandler packetHandler = player.getPacketHandler();
// Get the entity reference (if player is in a world)Ref<EntityStore> entityRef = player.getReference();
// Get the holder (if player is between worlds)Holder<EntityStore> holder = player.getHolder();Looking Up Players
Section titled “Looking Up Players”import com.hypixel.hytale.server.core.NameMatching;import com.hypixel.hytale.server.core.universe.Universe;
Universe universe = Universe.get();
// By UUID (exact match)PlayerRef player = universe.getPlayer(uuid);
// By username with matching strategyPlayerRef playerExact = universe.getPlayerByUsername("PlayerName", NameMatching.EXACT);PlayerRef playerPartial = universe.getPlayerByUsername("Play", NameMatching.STARTS_WITH);PlayerRef playerFuzzy = universe.getPlayerByUsername("playername", NameMatching.IGNORE_CASE);
// Get all connected playersList<PlayerRef> allPlayers = universe.getPlayers();int playerCount = universe.getPlayerCount();World-Specific Player Access
Section titled “World-Specific Player Access”import com.hypixel.hytale.server.core.universe.world.World;
World world = Universe.get().getWorld("default");
// Get players in this specific worldCollection<PlayerRef> worldPlayers = world.getPlayerRefs();Player Entity Component
Section titled “Player Entity Component”The Player class is an entity component that extends LivingEntity and provides gameplay-related functionality.
Accessing Player Component
Section titled “Accessing Player Component”import com.hypixel.hytale.server.core.entity.entities.Player;import com.hypixel.hytale.component.Ref;import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
// From PlayerRef (when player is in a world)Ref<EntityStore> ref = playerRef.getReference();if (ref != null) { Store<EntityStore> store = ref.getStore(); Player player = store.getComponent(ref, Player.getComponentType());
// Access player-specific data GameMode gameMode = player.getGameMode(); Inventory inventory = player.getInventory(); int viewRadius = player.getViewRadius();}
// From Holder (when player is between worlds)Holder<EntityStore> holder = playerRef.getHolder();if (holder != null) { Player player = holder.getComponent(Player.getComponentType());}Player Managers
Section titled “Player Managers”// Access various player managers through the Player componentWindowManager windowManager = player.getWindowManager();PageManager pageManager = player.getPageManager();HudManager hudManager = player.getHudManager();HotbarManager hotbarManager = player.getHotbarManager();WorldMapTracker worldMapTracker = player.getWorldMapTracker();Player Storage System
Section titled “Player Storage System”PlayerStorage Interface
Section titled “PlayerStorage Interface”The PlayerStorage interface defines how player data is loaded and saved:
import com.hypixel.hytale.server.core.universe.playerdata.PlayerStorage;import com.hypixel.hytale.component.Holder;import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
public interface PlayerStorage { // Load player data by UUID CompletableFuture<Holder<EntityStore>> load(UUID uuid);
// Save player data CompletableFuture<Void> save(UUID uuid, Holder<EntityStore> holder);
// Remove player data CompletableFuture<Void> remove(UUID uuid);
// Get all stored player UUIDs Set<UUID> getPlayers() throws IOException;}Accessing PlayerStorage
Section titled “Accessing PlayerStorage”import com.hypixel.hytale.server.core.universe.Universe;
// Get the active player storagePlayerStorage storage = Universe.get().getPlayerStorage();
// Load a player's stored datastorage.load(playerUuid).thenAccept(holder -> { // Process the loaded player data Player player = holder.getComponent(Player.getComponentType()); PlayerConfigData config = player.getPlayerConfigData();});
// Replace the player storage (for custom implementations)Universe.get().setPlayerStorage(myCustomStorage);Built-in Storage Providers
Section titled “Built-in Storage Providers”DefaultPlayerStorageProvider
Section titled “DefaultPlayerStorageProvider”The default provider that delegates to DiskPlayerStorageProvider:
import com.hypixel.hytale.server.core.universe.playerdata.DefaultPlayerStorageProvider;
// ID: "Hytale"// Uses DiskPlayerStorageProvider internallyDiskPlayerStorageProvider
Section titled “DiskPlayerStorageProvider”Stores player data as JSON files on disk:
import com.hypixel.hytale.server.core.universe.playerdata.DiskPlayerStorageProvider;
// ID: "Disk"// Stores files at: universe/players/{uuid}.jsonServer Configuration
Section titled “Server Configuration”Configure the storage provider in config.json:
{ "PlayerStorageProvider": { "Type": "Disk", "Path": "universe/players" }}Storing Custom Player Data
Section titled “Storing Custom Player Data”Using PlayerConfigData
Section titled “Using PlayerConfigData”PlayerConfigData stores persistent player information across sessions:
import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerConfigData;import com.hypixel.hytale.server.core.entity.entities.player.data.PlayerWorldData;
Player player = /* get player component */;PlayerConfigData config = player.getPlayerConfigData();
// Global player dataString currentWorld = config.getWorld();String preset = config.getPreset();Set<String> knownRecipes = config.getKnownRecipes();Set<String> discoveredZones = config.getDiscoveredZones();Set<UUID> discoveredInstances = config.getDiscoveredInstances();Object2IntMap<String> reputationData = config.getReputationData();
// Per-world player dataPlayerWorldData worldData = config.getPerWorldData("world_name");Transform lastPosition = worldData.getLastPosition();boolean isFirstSpawn = worldData.isFirstSpawn();PlayerRespawnPointData[] respawnPoints = worldData.getRespawnPoints();
// Mark data as changed to trigger saveconfig.markChanged();Saving Player Data
Section titled “Saving Player Data”Player data is automatically saved periodically, but you can trigger a save manually:
import com.hypixel.hytale.server.core.universe.Universe;
// Save player config and entity dataPlayer player = /* get player component */;World world = player.getWorld();Holder<EntityStore> holder = playerRef.getHolder();
// This saves the player's entity stateplayer.saveConfig(world, holder).thenRun(() -> { System.out.println("Player data saved!");});
// Or save via PlayerStorage directlyUniverse.get().getPlayerStorage().save(playerUuid, holder);Adding Custom Components
Section titled “Adding Custom Components”For custom persistent data, register and use your own components:
import com.hypixel.hytale.component.ComponentType;import com.hypixel.hytale.codec.builder.BuilderCodec;
// Define your custom componentpublic class MyPlayerData implements Component<EntityStore> { public static final BuilderCodec<MyPlayerData> CODEC = BuilderCodec.builder( MyPlayerData.class, MyPlayerData::new ) .addField(new KeyedCodec<>("customField", Codec.STRING), (data, value) -> data.customField = value, data -> data.customField) .build();
private String customField;
public String getCustomField() { return customField; } public void setCustomField(String value) { this.customField = value; }
@Override public Component<EntityStore> clone() { MyPlayerData copy = new MyPlayerData(); copy.customField = this.customField; return copy; }}
// Register in your plugin's setup@Overrideprotected void setup() { ComponentType<EntityStore, MyPlayerData> myDataType = getEntityStoreRegistry().registerComponent( MyPlayerData.class, MyPlayerData::new );}
// Use the componentHolder<EntityStore> holder = playerRef.getHolder();MyPlayerData data = holder.ensureAndGetComponent(myDataType);data.setCustomField("value");Player Events
Section titled “Player Events”Connection Events
Section titled “Connection Events”import com.hypixel.hytale.server.core.event.events.player.*;
@Overrideprotected void setup() { EventRegistry events = getEventRegistry();
// Player connecting to server (before entering world) events.register(PlayerConnectEvent.class, event -> { PlayerRef playerRef = event.getPlayerRef(); Holder<EntityStore> holder = event.getHolder(); World targetWorld = event.getWorld();
// Redirect to a different world event.setWorld(Universe.get().getWorld("lobby")); });
// Player disconnected from server events.register(PlayerDisconnectEvent.class, event -> { PlayerRef playerRef = event.getPlayerRef(); PacketHandler.DisconnectReason reason = event.getDisconnectReason(); getLogger().info(playerRef.getUsername() + " disconnected: " + reason); });}World Events
Section titled “World Events”// Player added to a world (keyed by world name)events.register(AddPlayerToWorldEvent.class, "world_name", event -> { Holder<EntityStore> holder = event.getHolder(); World world = event.getWorld();
// Suppress join message broadcast event.setBroadcastJoinMessage(false);});
// Player removed from a world (keyed by world name)events.register(DrainPlayerFromWorldEvent.class, "world_name", event -> { Holder<EntityStore> holder = event.getHolder(); World world = event.getWorld(); Transform position = event.getTransform();
// Redirect to a different world event.setWorld(Universe.get().getWorld("hub")); event.setTransform(new Transform(0, 64, 0));});
// Player ready to receive gameplay (client loaded)events.register(PlayerReadyEvent.class, event -> { Player player = event.getPlayer(); Ref<EntityStore> ref = event.getRef(); int readyId = event.getReadyId();
// Safe to send initial game state now});Global Registration
Section titled “Global Registration”To listen for events across all worlds:
// Listen to all player additions regardless of worldevents.registerGlobal(AddPlayerToWorldEvent.class, event -> { // Handles all worlds});Server Authentication
Section titled “Server Authentication”ServerAuthManager
Section titled “ServerAuthManager”The ServerAuthManager handles server-side authentication with Hytale’s authentication services:
import com.hypixel.hytale.server.core.auth.ServerAuthManager;
ServerAuthManager authManager = ServerAuthManager.getInstance();
// Check authentication statusServerAuthManager.AuthMode authMode = authManager.getAuthMode();boolean hasSession = authManager.hasSessionToken();boolean hasIdentity = authManager.hasIdentityToken();String status = authManager.getAuthStatus();
// Check if a player is the server ownerboolean isOwner = authManager.isOwner(playerUuid);
// Check if running in singleplayer modeboolean isSingleplayer = authManager.isSingleplayer();Authentication Modes
Section titled “Authentication Modes”public enum AuthMode { NONE, // No authentication SINGLEPLAYER, // Singleplayer mode EXTERNAL_SESSION, // Session from CLI/environment tokens OAUTH_BROWSER, // Authenticated via browser OAuth OAUTH_DEVICE, // Authenticated via device code flow OAUTH_STORE // Restored from stored credentials}AuthConfig Constants
Section titled “AuthConfig Constants”import com.hypixel.hytale.server.core.auth.AuthConfig;
// OAuth endpointsString authUrl = AuthConfig.OAUTH_AUTH_URL;String tokenUrl = AuthConfig.OAUTH_TOKEN_URL;String deviceAuthUrl = AuthConfig.DEVICE_AUTH_URL;
// Service URLsString sessionService = AuthConfig.SESSION_SERVICE_URL;String accountData = AuthConfig.ACCOUNT_DATA_URL;
// Environment variables for tokensString sessionTokenEnv = AuthConfig.ENV_SERVER_SESSION_TOKEN;String identityTokenEnv = AuthConfig.ENV_SERVER_IDENTITY_TOKEN;
// OAuth scopesString[] scopes = AuthConfig.SCOPES; // ["openid", "offline", "auth:server"]Credential Storage
Section titled “Credential Storage”Configure credential storage in config.json:
{ "AuthCredentialStore": { "Type": "Memory" }}Available types:
Memory- Stores credentials in memory only (lost on restart)Encrypted- Stores credentials encrypted on disk
Player Transfer Between Worlds
Section titled “Player Transfer Between Worlds”Moving Players Between Worlds
Section titled “Moving Players Between Worlds”import com.hypixel.hytale.server.core.universe.Universe;import com.hypixel.hytale.server.core.universe.world.World;import com.hypixel.hytale.math.vector.Transform;
World targetWorld = Universe.get().getWorld("adventure");Transform spawnPoint = new Transform(100, 64, 200);
// Add player to new worldtargetWorld.addPlayer(playerRef, spawnPoint) .thenAccept(player -> { getLogger().info("Player transferred to " + targetWorld.getName()); }) .exceptionally(error -> { getLogger().severe("Transfer failed: " + error.getMessage()); return null; });Resetting Player Data
Section titled “Resetting Player Data”Universe universe = Universe.get();
// Reset player with fresh datauniverse.resetPlayer(playerRef).thenAccept(newPlayerRef -> { getLogger().info("Player reset complete");});
// Reset with specific holder and destinationHolder<EntityStore> freshHolder = universe.getPlayerStorage().load(uuid).join();World targetWorld = universe.getWorld("spawn");Transform spawn = new Transform(0, 64, 0);
universe.resetPlayer(playerRef, freshHolder, targetWorld, spawn) .thenAccept(newPlayerRef -> { getLogger().info("Player reset and moved to spawn"); });Server Referral
Section titled “Server Referral”Transfer players to another server:
// Refer player to another serverplayerRef.referToServer("other.server.com", 25565);
// With custom data payload (max 4096 bytes)byte[] transferData = /* custom data */;playerRef.referToServer("other.server.com", 25565, transferData);Complete Example Plugin
Section titled “Complete Example Plugin”package com.example.playermanager;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;import com.hypixel.hytale.server.core.plugin.JavaPluginInit;import com.hypixel.hytale.server.core.event.events.player.*;import com.hypixel.hytale.server.core.universe.Universe;import com.hypixel.hytale.server.core.universe.PlayerRef;import com.hypixel.hytale.server.core.entity.entities.Player;import com.hypixel.hytale.component.Holder;import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import java.util.Map;import java.util.UUID;import java.util.concurrent.ConcurrentHashMap;
public class PlayerManagerPlugin extends JavaPlugin {
private final Map<UUID, Long> sessionStartTimes = new ConcurrentHashMap<>();
public PlayerManagerPlugin(JavaPluginInit init) { super(init); }
@Override protected void setup() { // Track player connections getEventRegistry().register(PlayerConnectEvent.class, this::onPlayerConnect); getEventRegistry().register(PlayerDisconnectEvent.class, this::onPlayerDisconnect);
// Handle world joins getEventRegistry().registerGlobal(AddPlayerToWorldEvent.class, this::onPlayerJoinWorld);
// Player ready for gameplay getEventRegistry().registerGlobal(PlayerReadyEvent.class, this::onPlayerReady); }
private void onPlayerConnect(PlayerConnectEvent event) { PlayerRef playerRef = event.getPlayerRef(); UUID uuid = playerRef.getUuid();
sessionStartTimes.put(uuid, System.currentTimeMillis()); getLogger().info("Player connecting: " + playerRef.getUsername());
// Check if this is a new player Holder<EntityStore> holder = event.getHolder(); Player player = holder.getComponent(Player.getComponentType());
if (player.getPlayerConfigData().getWorld() == null) { // New player - send to tutorial world event.setWorld(Universe.get().getWorld("tutorial")); } }
private void onPlayerDisconnect(PlayerDisconnectEvent event) { PlayerRef playerRef = event.getPlayerRef(); UUID uuid = playerRef.getUuid();
Long startTime = sessionStartTimes.remove(uuid); if (startTime != null) { long sessionDuration = System.currentTimeMillis() - startTime; getLogger().info(playerRef.getUsername() + " played for " + (sessionDuration / 1000) + " seconds"); } }
private void onPlayerJoinWorld(AddPlayerToWorldEvent event) { // Suppress default join message for silent joins if (event.getWorld().getName().equals("admin")) { event.setBroadcastJoinMessage(false); } }
private void onPlayerReady(PlayerReadyEvent event) { Player player = event.getPlayer();
// Send welcome message player.sendMessage(Message.of("Welcome back, " + player.getDisplayName() + "!"));
// Check first spawn in this world if (player.isFirstSpawn()) { player.sendMessage(Message.of("This is your first time in this world!")); } }
// API for other plugins public long getSessionDuration(UUID playerUuid) { Long startTime = sessionStartTimes.get(playerUuid); if (startTime == null) return 0; return System.currentTimeMillis() - startTime; }}Best Practices
Section titled “Best Practices”- Use PlayerRef for session data - Keep runtime state in PlayerRef, persistent state in PlayerConfigData
- Check entity reference validity - Always verify
playerRef.getReference() != nullbefore accessing entity state - Handle async operations - Player storage operations are async; use CompletableFuture properly
- Mark data as changed - Call
markChanged()on PlayerConfigData when modifying persistent fields - Use world-keyed events - Register for specific worlds when possible for better performance
- Clean up on disconnect - Remove temporary data in PlayerDisconnectEvent handlers
- Respect player state - Check if player is in a world (Reference) vs between worlds (Holder)
- Handle transfer failures - World transfers can fail; always handle exceptions