Player session manager
PlayerSessionManager tracks player sessions across disconnect and reconnect. When a player drops mid-game, the manager records their game id, slot, and phase, holds their instance slot for a configurable window, and on reconnect resolves a SessionPolicy to teleport them back to the right place. Records persist to <world>/data/conduit/player_sessions.dat via vanilla SavedData so a clean server restart preserves them.
When to use it
Section titled “When to use it”- Your game is built around instances or rounds and “rejoin where you left off” matters.
- You’re routing reconnecting players to a hub, lobby, or in-progress arena based on phase.
- You need a hold window so a player’s instance slot isn’t released the second they disconnect.
package me.zlex.conduit.player;
public final class PlayerSessionManager { public static final long DEFAULT_HOLD_WINDOW_MS = 10L * 60_000L;
public static void init(); public static void registerGame(String gameId, SessionPolicy defaultPolicy); public static void setPhasePolicyOverride(String gameId, String phaseName, SessionPolicy policy);
public static void recordDisconnect(ServerPlayer player, String gameId, int instanceSlot, String phaseName); public static void holdSlot(int slot, UUID player, long untilMs); public static void setHoldExpiryHandler(BiConsumer<Integer, UUID> handler);
public static void addReconnectListener(BiConsumer<ServerPlayer, SessionRecord> listener); public static @Nullable SessionRecord get(MinecraftServer server, UUID player); public static void clear(MinecraftServer server, UUID player);}registerGame declares the default routing policy for a game id; setPhasePolicyOverride lets one phase route differently than another. recordDisconnect is called by the game when it observes a mid-game disconnect. On reconnect, the registered policy resolves a TargetLocation and the player is teleported via SafeTeleport.
Example
Section titled “Example”import me.zlex.conduit.player.PlayerSessionManager;import me.zlex.conduit.player.ArenaSessionPolicies;
public final class MyMod implements ModInitializer {
@Override public void onInitialize() { PlayerSessionManager.init(); PlayerSessionManager.registerGame("my-game", ArenaSessionPolicies.lobbyOfInstance()); PlayerSessionManager.setPhasePolicyOverride("my-game", "IN_GAME", ArenaSessionPolicies.gameOfInstance()); PlayerSessionManager.setPhasePolicyOverride("my-game", "CELEBRATION", ArenaSessionPolicies.hub()); }
/** Called from your damage / lobby-leave handler. */ public void onPlayerDrop(ServerPlayer p, int slot, String phase) { PlayerSessionManager.recordDisconnect(p, "my-game", slot, phase); }}- The engine listens to vanilla
DISCONNECTto clear transient state, but mods own the call that decides “this disconnect counts as mid-game”. setHoldExpiryHandleris normally wired byInstanceManager— your game doesn’t usually touch it.- Crash recovery is best-effort (vanilla SavedData semantics).