import { beforeEach, describe, expect, it } from "vitest"; import { useCombatStore } from "./combatStore"; import { activateDungeonEncounterDefinitions, activeDungeonCompletedObjectiveIds, advanceDungeonEncounters, dispatchActiveDungeonEntityEvent, resetDungeonEncounters } from "./dungeonEncounterRuntime"; import { requireDungeonDefinition } from "./dungeonRegistry"; import { respawnDefeatedPlayer } from "./deathRespawn"; import { availableResurrectionCheckpoints, dungeonResurrectionCheckpoints } from "./resurrectionCheckpoints"; import { useGameStore } from "./store"; const dungeon = requireDungeonDefinition("stormwind-stockade"); describe("boss resurrection checkpoints", () => { beforeEach(() => { useGameStore.getState().activateDungeon("stormwind-stockade"); activateDungeonEncounterDefinitions(dungeon.id, dungeon.encounters ?? []); resetDungeonEncounters("dungeon", dungeon.id); useCombatStore.getState().initializeCharacter({ classId: "priest", level: 16 }); useCombatStore.setState({ mobs: {} }); }); it("always offers the entrance and unlocks every killed boss, including optional bosses", () => { expect(dungeonResurrectionCheckpoints(dungeon, {})).toHaveLength(1); const result = dungeonResurrectionCheckpoints(dungeon, {}, dungeon.bosses.map((boss) => boss.id)); expect(result.map((checkpoint) => checkpoint.id)).toEqual(["entrance", ...dungeon.bosses.map((boss) => `boss:${boss.id}`)]); dungeon.bosses.forEach((boss, index) => expect(result[index + 1].spawn.footPosition).toEqual(boss.position)); }); it("uses durable encounter completion after hydration when no corpse is registered", () => { const boss = dungeon.bosses[0]; dispatchActiveDungeonEntityEvent({ kind: "entity-death", entityId: boss.id, now: 100 }); advanceDungeonEncounters(dungeon.id, { kind: "tick", now: 100, facts: { deadEntityIds: [boss.id], registeredEntityIds: [boss.id] } }); expect(activeDungeonCompletedObjectiveIds(dungeon.id)).toContain(boss.id); expect(availableResurrectionCheckpoints().map((checkpoint) => checkpoint.id)).toContain(`boss:${boss.id}`); resetDungeonEncounters("boss-wipe", dungeon.id); expect(availableResurrectionCheckpoints().map((checkpoint) => checkpoint.id)).toContain(`boss:${boss.id}`); resetDungeonEncounters("dungeon", dungeon.id); expect(availableResurrectionCheckpoints()).toHaveLength(1); }); it("does not award a checkpoint for an alive or script-despawned boss", () => { const boss = dungeon.bosses[0]; useCombatStore.getState().registerMob(boss.id, { name: boss.name, maxHealth: 100, boss: true }); expect(availableResurrectionCheckpoints()).toHaveLength(1); const mob = useCombatStore.getState().mobs[boss.id]; useCombatStore.setState({ mobs: { [boss.id]: { ...mob, dead: true, health: 0, despawned: true } } }); expect(availableResurrectionCheckpoints()).toHaveLength(1); }); it("returns the defeated player and party reset signal to the selected boss, preserving the kill", () => { const boss = dungeon.bosses[0]; useCombatStore.getState().registerMob(boss.id, { name: boss.name, maxHealth: 1, boss: true }); useCombatStore.getState().damageMob(boss.id, 1, 100); useCombatStore.getState().damagePlayer(useCombatStore.getState().maxHealth * 10); const before = useGameStore.getState().resetRevision; expect(respawnDefeatedPlayer(`boss:${boss.id}`)).toBe(true); expect(useGameStore.getState().playerPosition).toEqual(boss.position); expect(useGameStore.getState().resetRevision).toBe(before + 1); expect(useCombatStore.getState().playerPosition).toEqual(boss.position); expect(useCombatStore.getState().health).toBe(useCombatStore.getState().maxHealth); expect(useCombatStore.getState().mobs[boss.id].dead).toBe(true); expect(availableResurrectionCheckpoints().map((checkpoint) => checkpoint.id)).toContain(`boss:${boss.id}`); }); it("lets a defeated player explicitly choose the entrance instead of a previously saved boss spawn", () => { useGameStore.setState({ activeSpawn: { footPosition: dungeon.bosses[0].position, yaw: 1 } }); useCombatStore.getState().damagePlayer(useCombatStore.getState().maxHealth * 10); expect(respawnDefeatedPlayer("entrance")).toBe(true); expect(useGameStore.getState().playerPosition).toEqual(dungeon.entrance.footPosition); }); it("rejects locked boss choices and cannot teleport a living player", () => { const before = useGameStore.getState(); expect(respawnDefeatedPlayer("entrance")).toBe(false); useCombatStore.getState().damagePlayer(useCombatStore.getState().maxHealth * 10); expect(respawnDefeatedPlayer(`boss:${dungeon.bosses[0].id}`)).toBe(false); expect(useCombatStore.getState().health).toBe(0); expect(useGameStore.getState()).toBe(before); }); });