import { useCombatStore } from "./combatStore"; import { releaseDungeonGltfCache } from "./dungeonAssetCache"; import { clearDungeonRuntimeCaches } from "./dungeonRuntimeReset"; import { clearMobRuntimeRegistry } from "./mobRuntimeRegistry"; import { clearPartyRuntimeRegistry } from "./partyRuntimeRegistry"; import { resetPartyRuntimeClock } from "./partyRuntime"; import { usePartyStore } from "./partyStore"; import { useGameStore, type GameMode } from "./store"; import { dungeonCanEnter, dungeonDefinitionById, type DungeonId } from "./dungeonRegistry"; import type { WorldSpawn } from "./dungeonTypes"; import type { GmReviewTarget } from "./gmContentReview"; import { shelvedDungeonSpawn } from "@shelvedDungeonSession"; import { activateDungeonEncounterDefinitions, exportDungeonEncounterSnapshots, importDungeonEncounterSnapshots, resetDungeonEncounters, } from "./dungeonEncounterRuntime"; import type { DungeonEncounterSnapshot } from "./dungeonEncounterTypes"; import { dungeonContentIsUsable } from "../content/contentManager"; export interface DungeonSessionOptions { readonly gameMode?: GameMode; readonly spawn?: WorldSpawn; readonly gmTestMode?: boolean; readonly gmReviewTarget?: GmReviewTarget | null; /** Used by in-game encounter transitions so character progression and inventory survive. */ readonly preserveCharacter?: boolean; /** Hydrates save-safe state after definitions have been activated and reset. */ readonly encounterSnapshots?: readonly DungeonEncounterSnapshot[]; } /** * The single dungeon-switch boundary. It deliberately clears renderer-facing * registries before publishing the new active id, preventing stale positions, * targets, routes, or prepared animation templates from crossing instances. */ export function activateDungeonSession( dungeonId: string, difficultyId?: string, options: DungeonSessionOptions = {}, ): boolean { const definition = dungeonDefinitionById(dungeonId); if (!definition || !dungeonCanEnter(dungeonId) || !dungeonContentIsUsable(dungeonId)) { return false; } const previousDefinition = dungeonDefinitionById(useGameStore.getState().activeDungeonId); if (previousDefinition) releaseDungeonGltfCache(previousDefinition); activateDungeonEncounterDefinitions( dungeonId, definition.encounters ?? [], difficultyId ?? definition.defaultDifficultyId, ); resetDungeonEncounters("dungeon", dungeonId); clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); clearDungeonRuntimeCaches(); resetPartyRuntimeClock(); if (options.preserveCharacter) { useCombatStore.getState().resetEncounter(); const combat = useCombatStore.getState(); useCombatStore.setState({ mobs: {}, selectedTargetId: null, activeCast: null, effects: [], comboPoints: 0, feedback: null, }); usePartyStore.getState().resetCombat(combat.level, combat.maxHealth); } else { useCombatStore.setState({ mobs: {}, selectedTargetId: null, activeCast: null, effects: [], comboPoints: 0, feedback: null, }); usePartyStore.setState({ seed: null, members: [], command: "defend", selectedMemberId: null, selfSelected: false, routeBlocked: false, activeMobId: null, objectiveBossId: null, memberTargetIds: {}, }); } useGameStore.getState().activateDungeon(dungeonId as DungeonId, difficultyId, { gameMode: options.gameMode, spawn: shelvedDungeonSpawn(dungeonId, options.spawn), gmTestMode: options.gmTestMode, gmReviewTarget: options.gmReviewTarget, }); if (options.encounterSnapshots?.length) { importDungeonEncounterSnapshots(dungeonId, options.encounterSnapshots, { authoritative: true }); } useCombatStore.getState().setPlayerPosition(useGameStore.getState().playerPosition); return true; } /** * Rebuilds the current encounter after an asset/render failure without * discarding the selected character, party composition, or run progression. */ export function restartActiveDungeonSession(): boolean { const game = useGameStore.getState(); const encounterSnapshots = exportDungeonEncounterSnapshots(game.activeDungeonId); return activateDungeonSession(game.activeDungeonId, game.activeDifficultyId, { gameMode: game.gameMode, spawn: game.activeSpawn, gmTestMode: game.gmTestMode, gmReviewTarget: game.activeGmReviewTarget, preserveCharacter: true, encounterSnapshots, }); }