forked from phenom/healer-man
Release Healer Man 0.1.39
This commit is contained in:
1 parent
b417b7c3c3
commit
ed0d2fea3d
51 files changed
+17811
-49
No files matched your search
@@ -0,0 +1,96 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { DUNGEON_DEFINITIONS, requireDungeonDefinition } from "./dungeonRegistry";
|
||||
import { completedDungeonRun, finalDungeonBoss, useDungeonRunStore } from "./dungeonRunStore";
|
||||
import { normalizeDungeonRunProgress, formatDungeonDuration, type DungeonRunMember } from "./dungeonRunTypes";
|
||||
import { activateDungeonEncounterDefinitions, activeDungeonCompletedObjectiveIds, activeDungeonRunIsComplete,
|
||||
advanceDungeonEncounters, createDungeonEncounterSnapshot, exportDungeonEncounterCheckpointSnapshots,
|
||||
importDungeonEncounterSnapshots, reduceDungeonEncounter, resetDungeonEncounters } from "./dungeonEncounterRuntime";
|
||||
|
||||
const party: readonly DungeonRunMember[] = [{ name: "Tester", classId: "rom-priest", level: 1, role: "healer", npc: false }];
|
||||
beforeEach(() => { useDungeonRunStore.setState({ progress: null }); });
|
||||
|
||||
describe("dungeon completion and run timing", () => {
|
||||
it("completes Barren Caves when Anglo dies, records its real spawn id, and survives checkpoint hydration", () => {
|
||||
const dungeon = requireDungeonDefinition("barren-caves");
|
||||
activateDungeonEncounterDefinitions(dungeon.id, dungeon.encounters ?? [], "normal");
|
||||
resetDungeonEncounters("dungeon", dungeon.id);
|
||||
const run = useDungeonRunStore.getState();
|
||||
run.begin(dungeon.id, "normal"); run.start(1000, party);
|
||||
advanceDungeonEncounters(dungeon.id, { kind: "tick", now: 1000, facts: { registeredEntityIds: [], deadEntityIds: [] } });
|
||||
expect(activeDungeonRunIsComplete(dungeon.id)).toBe(false);
|
||||
const boss = finalDungeonBoss(dungeon)!;
|
||||
expect(boss.name).toBe("Anglo");
|
||||
advanceDungeonEncounters(dungeon.id, { kind: "tick", now: 12456,
|
||||
facts: { registeredEntityIds: [boss.id], deadEntityIds: [boss.id] } });
|
||||
run.advance(activeDungeonCompletedObjectiveIds(dungeon.id), [boss.id], 12456, party);
|
||||
expect(activeDungeonRunIsComplete(dungeon.id)).toBe(true);
|
||||
expect(completedDungeonRun(useDungeonRunStore.getState().progress!)).toMatchObject({
|
||||
durationMs: 11456, fullClear: true, killedBossIds: [boss.id], party,
|
||||
});
|
||||
const snapshots = exportDungeonEncounterCheckpointSnapshots(dungeon.id, 0);
|
||||
resetDungeonEncounters("dungeon", dungeon.id);
|
||||
importDungeonEncounterSnapshots(dungeon.id, snapshots, { authoritative: true });
|
||||
expect(activeDungeonRunIsComplete(dungeon.id)).toBe(true);
|
||||
const result = useDungeonRunStore.getState().progress;
|
||||
run.advance([boss.id], [boss.id], 99999, []); run.start(99999, []);
|
||||
expect(useDungeonRunStore.getState().progress).toBe(result);
|
||||
});
|
||||
|
||||
it("adds durable completion bookkeeping to every RoM dungeon with configured bosses, without awarding missing bosses", () => {
|
||||
for (const dungeon of DUNGEON_DEFINITIONS.filter((entry) => entry.expansion === "runewaker" && entry.bosses.length > 0)) {
|
||||
const baseline = dungeon.encounters!.find((entry) => entry.id === `${dungeon.id}:completion-baseline`)!;
|
||||
expect(baseline, dungeon.id).toBeDefined();
|
||||
let snapshot = createDungeonEncounterSnapshot(baseline, 0);
|
||||
snapshot = reduceDungeonEncounter(baseline, snapshot, { kind: "tick", now: 10,
|
||||
facts: { registeredEntityIds: [], deadEntityIds: [] } }).snapshot;
|
||||
expect(snapshot.flags["dungeon-complete"], dungeon.id).toBe(false);
|
||||
const ordinary = [...new Set([...dungeon.objectiveOrder, ...dungeon.bosses]
|
||||
.filter((boss) => !boss.eventOnly).map((boss) => boss.id))];
|
||||
snapshot = reduceDungeonEncounter(baseline, snapshot, { kind: "tick", now: 20,
|
||||
facts: { registeredEntityIds: ordinary, deadEntityIds: ordinary } }).snapshot;
|
||||
expect(snapshot.completedObjectiveIds, dungeon.id).toEqual(expect.arrayContaining(ordinary));
|
||||
if (!dungeon.objectiveOrder.some((boss) => !boss.optional && boss.eventOnly)) {
|
||||
expect(snapshot.flags["dungeon-complete"], dungeon.id).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("finishes a final boss clear even when earlier bosses were skipped, but keeps it out of full clears", () => {
|
||||
const dungeon = requireDungeonDefinition("deadmines");
|
||||
const final = finalDungeonBoss(dungeon)!;
|
||||
const run = useDungeonRunStore.getState();
|
||||
run.begin(dungeon.id, dungeon.defaultDifficultyId); run.start(1000, party);
|
||||
run.advance([], [final.id], 2000, party);
|
||||
expect(useDungeonRunStore.getState().progress?.finishedAt).toBeNull();
|
||||
run.advance([final.id], [final.id], 3000, party);
|
||||
expect(completedDungeonRun(useDungeonRunStore.getState().progress!)).toMatchObject({ durationMs: 2000, fullClear: false });
|
||||
});
|
||||
|
||||
it("keeps the original wall-clock timer and boss history through a wipe and a saved resume", () => {
|
||||
const dungeon = requireDungeonDefinition("deadmines");
|
||||
const run = useDungeonRunStore.getState();
|
||||
run.begin(dungeon.id, "normal"); run.start(1000, party);
|
||||
run.advance([], [dungeon.bosses[0].id], 2000, party);
|
||||
const saved = normalizeDungeonRunProgress(JSON.parse(JSON.stringify(useDungeonRunStore.getState().progress)))!;
|
||||
run.begin(dungeon.id, "normal", saved); run.start(5000, party);
|
||||
run.advance([finalDungeonBoss(dungeon)!.id], [], 9000, party);
|
||||
expect(useDungeonRunStore.getState().progress).toMatchObject({ id: saved.id, startedAt: 1000,
|
||||
durationMs: 8000, killedBossIds: expect.arrayContaining([dungeon.bosses[0].id]) });
|
||||
});
|
||||
|
||||
it("announces legacy resumed runs without inventing a leaderboard time", () => {
|
||||
const run = useDungeonRunStore.getState(); run.begin("barren-caves", "normal", undefined, true);
|
||||
run.start(1000, party); run.advance([finalDungeonBoss(requireDungeonDefinition("barren-caves"))!.id], [], 2000, party);
|
||||
expect(useDungeonRunStore.getState().progress?.finishedAt).toBe(2000);
|
||||
expect(completedDungeonRun(useDungeonRunStore.getState().progress!)).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects corrupted saved timers and impossible party levels", () => {
|
||||
useDungeonRunStore.getState().begin("barren-caves", "normal");
|
||||
const progress = useDungeonRunStore.getState().progress!;
|
||||
expect(normalizeDungeonRunProgress({ ...progress, startedAt: NaN })).toBeNull();
|
||||
expect(normalizeDungeonRunProgress({ ...progress, startedAt: 100, finishedAt: 50, durationMs: 10 })).toBeNull();
|
||||
expect(normalizeDungeonRunProgress({ ...progress, party: [{ ...party[0], level: 81 }] })).toBeNull();
|
||||
expect(formatDungeonDuration(123456)).toBe("02:03.4");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user