115 lines
3.6 KiB
TypeScript
115 lines
3.6 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { encounterTimeline } from "./encounterTimeline";
|
|
import { useCombatStore } from "./combatStore";
|
|
|
|
describe("encounter mechanic timeline", () => {
|
|
beforeEach(() => {
|
|
useCombatStore.getState().initializeCharacter({ classId: "priest", level: 40 });
|
|
useCombatStore.getState().registerMob("boss", {
|
|
name: "Timeline Boss",
|
|
boss: true,
|
|
maxHealth: 1_000,
|
|
attacks: [{
|
|
id: "timed-quake",
|
|
name: "Timed Quake",
|
|
delivery: "area",
|
|
target: "nearby-party",
|
|
school: "nature",
|
|
animation: "cast",
|
|
range: 20,
|
|
radius: 10,
|
|
cooldownMs: 15_000,
|
|
repeatCooldownMs: [12_000, 18_000],
|
|
damageMultiplier: 1,
|
|
}, {
|
|
id: "health-adds",
|
|
name: "Awaken Adds",
|
|
delivery: "area",
|
|
target: "self",
|
|
school: "physical",
|
|
animation: "cast",
|
|
range: 0,
|
|
cooldownMs: 1_000,
|
|
maximumCasts: 1,
|
|
damageMultiplier: 0,
|
|
effect: { kind: "summon", creatureEntry: 1, count: 2 },
|
|
condition: { kind: "self-health-percent", minimum: 0, maximum: 70 },
|
|
}],
|
|
});
|
|
useCombatStore.setState((state) => ({
|
|
mobs: {
|
|
...state.mobs,
|
|
boss: {
|
|
...state.mobs.boss,
|
|
engaged: true,
|
|
abilityReadyAt: { "timed-quake": 16_000, "health-adds": 1_000 },
|
|
},
|
|
},
|
|
}));
|
|
});
|
|
|
|
it("shows both timer countdowns and pending health thresholds", () => {
|
|
const model = encounterTimeline(useCombatStore.getState().mobs, 10_000);
|
|
expect(model?.bossName).toBe("Timeline Boss");
|
|
expect(model?.rows.map((row) => [row.name, row.kind, row.value])).toEqual([
|
|
["Timed Quake", "timer", "6.0s"],
|
|
["Awaken Adds", "health", "70% HP"],
|
|
]);
|
|
});
|
|
|
|
it("promotes an active cast and hides a completed one-shot threshold", () => {
|
|
useCombatStore.setState((state) => ({
|
|
mobs: {
|
|
...state.mobs,
|
|
boss: {
|
|
...state.mobs.boss,
|
|
abilityCastCounts: { "health-adds": 1 },
|
|
activeCast: {
|
|
attackId: "timed-quake",
|
|
name: "Timed Quake",
|
|
school: "nature",
|
|
interruptible: true,
|
|
targetActorId: "__player__",
|
|
sourcePosition: [0, 0, 0],
|
|
targetPosition: [0, 0, 5],
|
|
sourceYaw: 0,
|
|
startedAt: 10_000,
|
|
completesAt: 12_000,
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
expect(encounterTimeline(useCombatStore.getState().mobs, 11_250)?.rows).toMatchObject([{
|
|
name: "Timed Quake",
|
|
kind: "casting",
|
|
value: "0.8s",
|
|
}]);
|
|
});
|
|
|
|
it("keeps every pending health threshold visible beyond the countdown row limit", () => {
|
|
const boss = useCombatStore.getState().mobs.boss;
|
|
const extraTimers = Array.from({ length: 5 }, (_, index) => ({
|
|
...boss.attacks[0],
|
|
id: `extra-timer-${index}`,
|
|
name: `Extra Timer ${index}`,
|
|
}));
|
|
useCombatStore.setState((state) => ({
|
|
mobs: {
|
|
...state.mobs,
|
|
boss: {
|
|
...state.mobs.boss,
|
|
attacks: [...state.mobs.boss.attacks, ...extraTimers],
|
|
abilityReadyAt: {
|
|
...state.mobs.boss.abilityReadyAt,
|
|
...Object.fromEntries(extraTimers.map((attack, index) => [attack.id, 11_000 + index * 1_000])),
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
const rows = encounterTimeline(useCombatStore.getState().mobs, 10_000, 4)?.rows ?? [];
|
|
expect(rows.filter((row) => row.kind === "timer")).toHaveLength(4);
|
|
expect(rows.filter((row) => row.kind === "health").map((row) => row.value)).toEqual(["70% HP"]);
|
|
});
|
|
});
|