forked from phenom/healer-man
64 lines
2.8 KiB
TypeScript
64 lines
2.8 KiB
TypeScript
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<PartyMember, "id" | "health">[],
|
|
mobs: Readonly<Record<string, MobCombatState>>,
|
|
actors: Readonly<Record<string, Pick<CombatActorState, "health">>> = {},
|
|
): 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;
|
|
}
|