import { beforeEach, describe, expect, it } from "vitest"; import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry"; import { usePartyStore } from "./partyStore"; import { createConfiguredPartyMember } from "./partyStore"; import { EMPTY_ITEM_STATS } from "./itemStats"; import { abilitiesForCharacter } from "./abilityCatalog"; import { applyPartyRomCoaExhaustion } from "./partyRuntime"; import { PLAYER_AGGRO_ID } from "./aggro"; import { PLAYER_AURA_ENTITY_ID } from "./combatStore"; import { stableCombatUnit, useCombatStore } from "./combatStore"; import { ROM_LOCAL_ACTIVE_BLOCKING_STANCE_AURA_ID } from "./romWarriorCurrentCoaMechanics"; function stablePartyRoll(seed: string): number { let hash = 2166136261; for (const character of seed) { hash ^= character.charCodeAt(0); hash = Math.imul(hash, 16777619); } return (hash >>> 0) / 0x1_0000_0000; } function registerTarget(id: string): void { useCombatStore.getState().registerMob(id, { name: id, level: 1, maxHealth: 50_000, xpReward: 0 }); setMobPosition(id, [2, 0, 0]); useCombatStore.getState().selectMob(id); } describe("current CoA Warrior mechanics runtime", () => { beforeEach(() => { clearMobRuntimeRegistry(); useCombatStore.getState().initializeCharacter({ classId: "rom-warrior", secondaryClassId: "rom-rogue", level: 50, xp: 0, }); usePartyStore.getState().initializeParty("rom-warrior-coa-runtime", 50, useCombatStore.getState().health); useCombatStore.getState().setPlayerPosition([0, 0, 0]); }); it("awards Exhaustion resources on a seeded landed CoA Probing Attack and records capped resource events", () => { const targetId = "coa-probing-target"; registerTarget(targetId); const state = useCombatStore.getState(); const ability = state.abilities.find((candidate) => candidate.dbcSpellId === 493283); expect(ability).toBeDefined(); useCombatStore.setState((current) => ({ resource: 40, maxResource: 100, maxResourcePools: { ...current.maxResourcePools, rage: 100, energy: 100 }, resourcePools: { ...current.resourcePools, rage: 40, energy: 0 }, })); const now = Array.from({ length: 100 }, (_, index) => 1_000 + index * 1_000) .find((candidate) => stableCombatUnit( `${PLAYER_AGGRO_ID}:${ability!.id}:${targetId}:${candidate}:rom-coa-exhaustion`, ) < 0.3)!; expect(useCombatStore.getState().castAbility(ability!.id, { targetActorId: targetId, now })) .toMatchObject({ ok: true, affectedMobIds: [targetId] }); const finalState = useCombatStore.getState(); expect(finalState.resourcePools).toMatchObject({ energy: 25, rage: 70 }); expect(finalState.combatEvents.filter((event) => event.kind === "resource" && event.abilityId === ability!.id)) .toEqual(expect.arrayContaining([ expect.objectContaining({ targetActorId: PLAYER_AURA_ENTITY_ID, rawAmount: 25, effectiveAmount: 25 }), expect.objectContaining({ targetActorId: PLAYER_AURA_ENTITY_ID, rawAmount: 50, effectiveAmount: 50 }), ])); }); it("applies companion Exhaustion after a landed CoA Probing Attack and caps both resources", () => { const seed = "coa-probing-companion"; const companion = createConfiguredPartyMember({ name: "Warrior/Rogue", classId: "rom-warrior", secondaryClassId: "rom-rogue", raceId: "rom-dwarf", specialization: "Warrior/Rogue", role: "damage", }, 80, 0, seed); usePartyStore.getState().initializeParty(seed, 80, useCombatStore.getState().health, "damage", 2, "rom"); usePartyStore.setState({ members: [{ ...companion, gearStats: EMPTY_ITEM_STATS, resourcePools: { ...companion.resourcePools, energy: 90, rage: 90 }, }] }); useCombatStore.getState().synchronizeActors(); const probingAttack = abilitiesForCharacter("rom-warrior", "rom-rogue") .find((ability) => ability.dbcSpellId === 493283)!; const targetId = "coa-probing-companion-target"; const now = Array.from({ length: 100 }, (_, index) => 10_000 + index) .find((candidate) => stablePartyRoll( `${companion.id}:${probingAttack.id}:${targetId}:${candidate}:rom-coa-exhaustion`, ) < 0.3)!; const priorEventCount = useCombatStore.getState().combatEvents.length; const updated = applyPartyRomCoaExhaustion( companion, probingAttack, true, `${companion.id}:${probingAttack.id}:${targetId}:${now}:rom-coa-exhaustion`, now, ); expect(updated.resourcePools).toMatchObject({ energy: 100, rage: 100 }); expect(useCombatStore.getState().combatEvents.slice(priorEventCount).filter((event) => ( event.kind === "resource" && event.sourceActorId === companion.id && event.abilityId === probingAttack.id ))).toEqual(expect.arrayContaining([ expect.objectContaining({ rawAmount: 25, effectiveAmount: 10 }), expect.objectContaining({ rawAmount: 50, effectiveAmount: 10 }), ])); }); it("uses the Knight passive marker without carrying the retired active heal payload", () => { useCombatStore.getState().initializeCharacter({ classId: "rom-knight", level: 50, xp: 0 }); const passive = useCombatStore.getState().abilities.find((candidate) => candidate.dbcSpellId === 490172); expect(passive).toMatchObject({ passive: true, target: "self", effects: [] }); expect(passive?.description).toContain("198%"); }); it("recognizes the local active stance separately from the current CoA passive marker", () => { expect(ROM_LOCAL_ACTIVE_BLOCKING_STANCE_AURA_ID).toBe("rom-aura-491480-502007-0-parry"); }); });