import { useCombatStore, type MobCombatState } from "./combatStore"; import type { CombatActorState } from "./combatActors"; import { requireDungeonDefinition } from "./dungeonRegistry"; import { useGameStore } from "./store"; import { resolveResurrectionCheckpoint } from "./resurrectionCheckpoints"; import { usePartyStore, type PartyMember } from "./partyStore"; import { activeDungeonCompletedObjectiveIds, resetDungeonEncounters, } from "./dungeonEncounterRuntime"; export function partyFightStillActive( members: readonly Pick[], mobs: Readonly>, actors: Readonly>> = {}, ): boolean { return members.some((member) => (actors[member.id]?.health ?? member.health) > 0) && Object.values(mobs).some((mob) => mob.engaged && !mob.dead && !mob.despawned); } /** Recover only the player during an ongoing party fight; otherwise retain wipe recovery. */ export function respawnDefeatedPlayer(checkpointId?: string): boolean { const combat = useCombatStore.getState(); if (combat.health > 0) return false; const game = useGameStore.getState(); if (game.gameMode !== "dungeon") return false; const checkpoint = checkpointId === undefined ? null : resolveResurrectionCheckpoint(checkpointId); if (checkpointId !== undefined && !checkpoint) return false; if (partyFightStillActive(usePartyStore.getState().members, combat.mobs, combat.actors)) { const spawn = checkpoint?.spawn ?? game.activeSpawn; if (!combat.respawnPlayerOnly(spawn.footPosition)) return false; // Relocation does not fire the resetRevision watched by PartyBridge/PartyPopulation. useGameStore.getState().relocatePlayer(spawn, true); useGameStore.getState().closeOverlay(); return true; } const dungeonId = useGameStore.getState().activeDungeonId; const completedObjectiveIds = new Set(activeDungeonCompletedObjectiveIds(dungeonId)); const retrySpawnIds = new Set( (requireDungeonDefinition(dungeonId).encounterSpawnGroups ?? []) .flatMap((group) => group.spawns.map((spawn) => spawn.id)), ); const preserveDefeatedIds = Object.values(combat.mobs) .filter((mob) => ( mob.dead && (!retrySpawnIds.has(mob.id) || completedObjectiveIds.has(mob.id)) )) .map((mob) => mob.id); if (typeof document !== "undefined" && document.pointerLockElement) { document.exitPointerLock(); } // Select the saved return spawn without relocating first or resetting twice. if (checkpoint) useGameStore.setState({ activeSpawn: checkpoint.spawn }); useGameStore.getState().resetAfterDefeat(); resetDungeonEncounters("boss-wipe", dungeonId); const respawnPosition = useGameStore.getState().playerPosition; combat.resetEncounter({ preserveDefeatedIds }); useCombatStore.getState().setPlayerPosition(respawnPosition); return true; }