79 lines
4.7 KiB
TypeScript
79 lines
4.7 KiB
TypeScript
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);
|
|
});
|
|
});
|