import { beforeAll, beforeEach, describe, expect, it } from "vitest"; import { abilitiesForCharacter, learnedAbilityForBuild, preloadAbilityCatalogsForClasses } from "./abilityCatalog"; import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry"; import { clearPartyRuntimeRegistry, registerPartyRuntimePosition, updatePartyRuntimeTargetVisibility } from "./partyRuntimeRegistry"; import { advanceDungeonPartyCombat, resetPartyRuntimeClock } from "./partyRuntime"; import { useCombatStore } from "./combatStore"; import { createConfiguredPartyMember, usePartyStore } from "./partyStore"; import { ROM_COA_WARLOCK_WARRIOR_ENRAGED } from "./romWarlockWarriorEnragedCoaMechanics"; const ABILITY_ID = "rom-warlock-rom-warrior-general-coa-499871"; const TARGET_ID = "rom-warlock-warrior-enraged-target"; function enragedAbility() { return abilitiesForCharacter("rom-warlock", "rom-warrior").find((entry) => entry.id === ABILITY_ID)!; } function setPlayerPools(pools: { rage: number; focus: number; psi: number }) { useCombatStore.setState((state) => ({ resource: pools.focus, maxResource: 100, resourcePools: { ...state.resourcePools, ...pools }, maxResourcePools: { ...state.maxResourcePools, rage: 100, focus: 100, psi: 6 }, })); } function prepareTarget() { useCombatStore.getState().registerMob(TARGET_ID, { name: "Enraged Test Target", maxHealth: 100_000 }); setMobPosition(TARGET_ID, [5, 0, 0]); } describe("current CoA Warlock/Warrior Enraged runtime", () => { beforeAll(async () => { await preloadAbilityCatalogsForClasses(["rom-warlock"]); }); beforeEach(() => { clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); resetPartyRuntimeClock(); useCombatStore.setState({ playerPosition: [0, 0, 0] }); }); it("is learnable only at level 60 for Warlock/Warrior and does not replace the other Enraged row", () => { const ability = enragedAbility(); expect(learnedAbilityForBuild(ability, 59, {})).toBeNull(); expect(learnedAbilityForBuild(ability, 60, {})).toMatchObject({ dbcSpellId: 499_871 }); expect(abilitiesForCharacter("rom-warlock", "rom-priest").some((candidate) => ( candidate.dbcSpellId === ROM_COA_WARLOCK_WARRIOR_ENRAGED.spellId ))).toBe(false); expect(abilitiesForCharacter("rom-warrior", "rom-warlock").some((candidate) => ( candidate.dbcSpellId === ROM_COA_WARLOCK_WARRIOR_ENRAGED.spellId ))).toBe(false); expect(abilitiesForCharacter("rom-warrior", undefined).find((candidate) => ( candidate.dbcSpellId === ROM_COA_WARLOCK_WARRIOR_ENRAGED.localWarriorGeneralSpellId ))).toBeDefined(); }); it("restores all three player resources, clamps to their caps, and honors the 40-second cooldown", () => { useCombatStore.getState().initializeCharacter({ classId: "rom-warlock", secondaryClassId: "rom-warrior", raceId: "rom-dwarf", level: 80, }); setPlayerPools({ rage: 40, focus: 50, psi: 5 }); const ability = enragedAbility(); expect(useCombatStore.getState().castAbility(ability.id, { now: 1_000 })).toMatchObject({ ok: true }); expect(useCombatStore.getState().resourcePools).toMatchObject({ rage: 100, focus: 85, psi: 6 }); expect(useCombatStore.getState().cooldowns[ability.id]).toBe(41_000); setPlayerPools({ rage: 50, focus: 80, psi: 5 }); expect(useCombatStore.getState().castAbility(ability.id, { now: 3_000 })).toMatchObject({ ok: false, reason: "cooldown", }); expect(useCombatStore.getState().castAbility(ability.id, { now: 41_000 })).toMatchObject({ ok: true }); expect(useCombatStore.getState().resourcePools).toMatchObject({ rage: 100, focus: 100, psi: 6 }); }); it("restores and caps companion Rage, Focus, and Psi through the shared resource effects", () => { useCombatStore.getState().initializeCharacter({ classId: "rom-priest", raceId: "rom-dwarf", level: 80 }); usePartyStore.getState().initializeParty("rom-warlock-warrior-enraged-party", 80, useCombatStore.getState().health, "damage", 2, "rom"); const member = createConfiguredPartyMember({ name: "Enraged Companion", classId: "rom-warlock", secondaryClassId: "rom-warrior", raceId: "rom-dwarf", specialization: "Warlock", role: "damage", }, 80, 0, "rom-warlock-warrior-enraged-party"); const ability = enragedAbility(); const pairAbilities = abilitiesForCharacter("rom-warlock", "rom-warrior"); const maximumCooldowns = Object.fromEntries(pairAbilities .filter((candidate) => candidate.id !== ability.id) .map((candidate) => [candidate.id, 100_000])); usePartyStore.setState({ command: "attack", members: [{ ...member, cooldowns: maximumCooldowns, resourcePools: { ...member.resourcePools, focus: 20, rage: 40, psi: 5 }, resource: 20, maxResource: 100, }], }); useCombatStore.getState().synchronizeActors(); registerPartyRuntimePosition(member.id, [0, 0, 0]); prepareTarget(); useCombatStore.getState().damageMob(TARGET_ID, 100, 900); updatePartyRuntimeTargetVisibility(member.id, TARGET_ID, true); advanceDungeonPartyCombat(1_000); advanceDungeonPartyCombat(3_100); const result = usePartyStore.getState().members.find((candidate) => candidate.id === member.id); expect(result?.cooldowns[ability.id]).toBe(41_000); expect(result?.resourcePools.rage).toBe(100); expect(result?.resourcePools.focus).toBeGreaterThanOrEqual(55); expect(result?.resourcePools.focus).toBeLessThanOrEqual(100); expect(result?.resourcePools.psi).toBe(6); expect(useCombatStore.getState().mobs[TARGET_ID]?.health).toBe(99_900); }); });