121 lines
5.1 KiB
TypeScript
121 lines
5.1 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { abilityById } from "./abilityCatalog";
|
|
import { PLAYER_AGGRO_ID } from "./aggro";
|
|
import { useCombatStore } from "./combatStore";
|
|
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
|
import { advanceDungeonPartyCombat, resetPartyRuntimeClock } from "./partyRuntime";
|
|
import {
|
|
clearPartyRuntimeRegistry,
|
|
registerPartyRuntimePosition,
|
|
updatePartyRuntimeTargetVisibility,
|
|
} from "./partyRuntimeRegistry";
|
|
import { createConfiguredPartyMember, usePartyStore } from "./partyStore";
|
|
import {
|
|
ROM_MAGE_KNIGHT_LIGHT_OF_DAY,
|
|
} from "./romMageRuntime";
|
|
|
|
const TARGET_ID = "light-of-day-target";
|
|
const ACTIVE_TAG = "rom-mage-knight-light-of-day-active";
|
|
|
|
function initializeMageKnight(): void {
|
|
useCombatStore.getState().initializeCharacter({
|
|
classId: "rom-mage",
|
|
secondaryClassId: "rom-knight",
|
|
raceId: "rom-human",
|
|
level: 80,
|
|
});
|
|
}
|
|
|
|
function registerTarget(): void {
|
|
useCombatStore.getState().registerMob(TARGET_ID, { name: TARGET_ID, maxHealth: 1_000_000, xpReward: 0 });
|
|
setMobPosition(TARGET_ID, [3, 0, 0]);
|
|
useCombatStore.getState().selectMob(TARGET_ID);
|
|
}
|
|
|
|
function aura(actorId: string, tag: string, now: number) {
|
|
return useCombatStore.getState().auras.find((entry) => entry.targetId === actorId
|
|
&& (entry.expiresAt === null || entry.expiresAt > now)
|
|
&& entry.definition.utilityTags?.includes(tag));
|
|
}
|
|
|
|
describe("RoM Mage/Knight Light of Day", () => {
|
|
beforeEach(() => {
|
|
clearMobRuntimeRegistry();
|
|
clearPartyRuntimeRegistry();
|
|
resetPartyRuntimeClock();
|
|
initializeMageKnight();
|
|
});
|
|
|
|
it("stays suppressed at startup, triggers on both named hits, and observes its 20-second ICD", () => {
|
|
registerTarget();
|
|
const holyLightStrike = abilityById("rom-mage-rom-knight-elite-15");
|
|
const lightCharge = abilityById("rom-mage-rom-knight-elite-35");
|
|
const passive = abilityById("rom-mage-rom-knight-elite-45");
|
|
if (!holyLightStrike || !lightCharge || !passive) throw new Error("Missing local Mage/Knight Light of Day rows");
|
|
expect([holyLightStrike.dbcSpellId, lightCharge.dbcSpellId]).toEqual([491_594, 492_634]);
|
|
expect(passive.dbcSpellId).toBe(ROM_MAGE_KNIGHT_LIGHT_OF_DAY.passiveSpellId);
|
|
expect(passive.effects).toEqual([]);
|
|
expect(aura(PLAYER_AGGRO_ID, ACTIVE_TAG, 1_000)).toBeUndefined();
|
|
|
|
expect(useCombatStore.getState().castAbility(holyLightStrike.id, undefined, 1_000)).toMatchObject({ ok: true });
|
|
useCombatStore.getState().tick(0.1, 3_100);
|
|
expect(aura(PLAYER_AGGRO_ID, ACTIVE_TAG, 3_100)?.expiresAt).toBe(18_100);
|
|
expect(aura(PLAYER_AGGRO_ID, ROM_MAGE_KNIGHT_LIGHT_OF_DAY.cooldownAuraTag, 3_100)?.expiresAt).toBe(23_100);
|
|
|
|
expect(useCombatStore.getState().castAbility(lightCharge.id, undefined, 7_000)).toMatchObject({ ok: true });
|
|
expect(aura(PLAYER_AGGRO_ID, ACTIVE_TAG, 7_000)?.expiresAt).toBe(18_100);
|
|
|
|
expect(useCombatStore.getState().castAbility(lightCharge.id, undefined, 24_000)).toMatchObject({ ok: true });
|
|
expect(aura(PLAYER_AGGRO_ID, ACTIVE_TAG, 24_000)?.expiresAt).toBe(39_000);
|
|
expect(aura(PLAYER_AGGRO_ID, ROM_MAGE_KNIGHT_LIGHT_OF_DAY.cooldownAuraTag, 24_000)?.expiresAt).toBe(44_000);
|
|
});
|
|
|
|
it("triggers for a landed party Mage/Knight hit with an independent caster ICD", () => {
|
|
const seed = "light-of-day-party-mage-knight";
|
|
const member = createConfiguredPartyMember({
|
|
name: "Light of Day Mage",
|
|
classId: "rom-mage",
|
|
secondaryClassId: "rom-knight",
|
|
specialization: "Primary",
|
|
role: "damage",
|
|
}, 80, 0, seed);
|
|
const holyLightStrike = abilityById("rom-mage-rom-knight-elite-15");
|
|
if (!holyLightStrike) throw new Error("Missing local Mage/Knight Holy Light Strike row");
|
|
|
|
usePartyStore.getState().initializeParty(seed, 80, useCombatStore.getState().health, "damage", 2, "rom");
|
|
usePartyStore.getState().setCommand("attack");
|
|
usePartyStore.setState({ members: [{
|
|
...member,
|
|
resource: 10_000,
|
|
maxResource: 10_000,
|
|
resourcePools: { ...member.resourcePools, mana: 10_000 },
|
|
activeCast: {
|
|
abilityId: holyLightStrike.id,
|
|
school: "holy",
|
|
mode: "cast",
|
|
startedAt: 500,
|
|
completesAt: 2_500,
|
|
originalDurationMs: 2_000,
|
|
pushbackHits: 0,
|
|
origin: [0, 0, 0],
|
|
targetId: TARGET_ID,
|
|
},
|
|
activeCastTargetId: TARGET_ID,
|
|
status: "attacking",
|
|
}] });
|
|
registerPartyRuntimePosition(member.id, [0, 0, 0]);
|
|
registerTarget();
|
|
useCombatStore.getState().damageMob(TARGET_ID, 1, 2_400);
|
|
updatePartyRuntimeTargetVisibility(member.id, TARGET_ID, true);
|
|
useCombatStore.getState().synchronizeActors();
|
|
|
|
advanceDungeonPartyCombat(2_500);
|
|
|
|
expect(useCombatStore.getState().combatEvents.some((event) => event.sourceActorId === member.id
|
|
&& event.abilityId === holyLightStrike.id && event.outcome === "hit")).toBe(true);
|
|
expect(aura(member.id, ACTIVE_TAG, 2_500)?.expiresAt).toBe(17_500);
|
|
expect(aura(member.id, ROM_MAGE_KNIGHT_LIGHT_OF_DAY.cooldownAuraTag, 2_500)?.expiresAt).toBe(22_500);
|
|
expect(aura(PLAYER_AGGRO_ID, ACTIVE_TAG, 2_500)).toBeUndefined();
|
|
});
|
|
});
|