Skip to content

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.

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

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 DISCONNECT to clear transient state, but mods own the call that decides “this disconnect counts as mid-game”.
  • setHoldExpiryHandler is normally wired by InstanceManager — your game doesn’t usually touch it.
  • Crash recovery is best-effort (vanilla SavedData semantics).