Files
healer-man/src/game/romSmashRuntime.test.ts
T
2026-09-28 22:46:45 -04:00

95 lines
3.6 KiB
TypeScript

import { beforeEach, describe, expect, it } from "vitest";
import { PLAYER_AGGRO_ID } from "./aggro";
import { useCombatStore } from "./combatStore";
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
import { usePartyStore } from "./partyStore";
import { EMPTY_ITEM_STATS } from "./itemStats";
const SMASH_ID = "rom-knight-rom-rogue-elite-20";
function registerTarget(): void {
useCombatStore.getState().registerMob("smash-target", {
name: "Smash target",
maxHealth: 1_000_000,
xpReward: 0,
});
setMobPosition("smash-target", [2, 0, 0]);
useCombatStore.getState().selectMob("smash-target");
}
function playerDamageEvent(critical: boolean, id: number, sourceActorId = PLAYER_AGGRO_ID) {
return {
id,
occurredAt: id * 1_000,
kind: "damage" as const,
sourceActorId,
targetActorId: "smash-target",
abilityId: "rom-knight-primary-1",
school: "holy" as const,
rawAmount: 100,
effectiveAmount: 100,
overheal: 0,
absorbed: 0,
critical,
outcome: critical ? "crit" : "hit",
};
}
describe("RuneWaker Smash prerequisite", () => {
beforeEach(() => {
clearMobRuntimeRegistry();
useCombatStore.getState().initializeCharacter({
classId: "rom-knight",
secondaryClassId: "rom-rogue",
raceId: "rom-human",
level: 80,
xp: 0,
});
useCombatStore.getState().setPlayerPosition([0, 0, 0]);
usePartyStore.getState().initializeParty("rom-smash-test", 80, useCombatStore.getState().health);
registerTarget();
});
it("rejects Smash without a preceding player critical damage event without spending health or cooldown", () => {
const before = useCombatStore.getState();
const result = before.castAbility(SMASH_ID, undefined, 1_000);
expect(result).toMatchObject({ ok: false, reason: "condition-not-met", abilityId: SMASH_ID });
expect(useCombatStore.getState().health).toBe(before.health);
expect(useCombatStore.getState().cooldowns[SMASH_ID] ?? 0).toBe(0);
expect(useCombatStore.getState().mobs["smash-target"]?.health).toBe(1_000_000);
});
it("allows Smash after a real player critical hit and ignores the later cast event", () => {
useCombatStore.setState({
gearStats: { ...EMPTY_ITEM_STATS, criticalStrikeRating: 1_000_000, hitRating: 1_000_000 },
});
const holySeal = useCombatStore.getState().abilities.find((ability) => ability.id === "rom-knight-primary-1");
expect(holySeal).toBeDefined();
expect(useCombatStore.getState().castAbility(holySeal!.id, undefined, 2_000).ok).toBe(true);
expect(useCombatStore.getState().combatEvents.some((event) => (
event.kind === "damage"
&& event.sourceActorId === PLAYER_AGGRO_ID
&& event.critical
&& event.effectiveAmount > 0
))).toBe(true);
const result = useCombatStore.getState().castAbility(SMASH_ID, undefined, 4_000);
expect(result).toMatchObject({ ok: true, abilityId: SMASH_ID });
expect(useCombatStore.getState().cooldowns[SMASH_ID]).toBe(9_000);
});
it("requires the latest player damage event to be a landed crit", () => {
useCombatStore.setState({ combatEvents: [
playerDamageEvent(true, 1),
playerDamageEvent(false, 2),
] });
expect(useCombatStore.getState().castAbility(SMASH_ID, undefined, 3_000))
.toMatchObject({ ok: false, reason: "condition-not-met" });
useCombatStore.setState({ combatEvents: [playerDamageEvent(true, 1, "party-member")] });
expect(useCombatStore.getState().castAbility(SMASH_ID, undefined, 3_000))
.toMatchObject({ ok: false, reason: "condition-not-met" });
});
});