import { beforeEach, describe, expect, it } from "vitest"; import { useCombatStore, PLAYER_AURA_ENTITY_ID } from "./combatStore"; import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry"; import { clearPartyRuntimeRegistry, registerPartyRuntimePosition, updatePartyRuntimeTargetVisibility, } from "./partyRuntimeRegistry"; import { abilityAtLevel, abilitiesForClass, preloadAbilityCatalogsForClasses } from "./abilityCatalog"; import { WOW335_MAGE_NETHERWIND_PRESENCE_TAG } from "./wow335MageNetherwindPresenceRuntime"; import { advanceDungeonPartyCombat, resetPartyRuntimeClock } from "./partyRuntime"; import { createConfiguredPartyMember, usePartyStore } from "./partyStore"; import { EMPTY_ITEM_STATS } from "./itemStats"; async function resetMage(): Promise { clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); resetPartyRuntimeClock(); await preloadAbilityCatalogsForClasses(["mage"]); useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80 }); useCombatStore.getState().resetSettings(); useCombatStore.getState().setPlayerPosition([0, 0, 0]); useCombatStore.setState((state) => ({ resource: state.maxResource, resourcePools: { ...state.resourcePools, mana: state.maxResourcePools.mana }, talentRanks: { ...state.talentRanks, "talent-1846": 1 }, })); useCombatStore.getState().registerMob("netherwind-target", { name: "Target", maxHealth: 100_000 }); setMobPosition("netherwind-target", [4, 0, 0]); useCombatStore.getState().selectMob("netherwind-target"); } describe("WoW Mage Netherwind Presence cast integration", () => { beforeEach(resetMage); it("grants one stack for an accepted Arcane Missiles cast, not for each channel pulse", () => { const arcaneMissiles = useCombatStore.getState().abilities.find((ability) => ability.name === "Arcane Missiles")!; const started = useCombatStore.getState().castAbility(arcaneMissiles.id, undefined, 1_000); expect(started).toMatchObject({ ok: true }); const cast = useCombatStore.getState().activeCast!; expect(cast.mode).toBe("channel"); const hasteAura = () => useCombatStore.getState().auras.find((aura) => ( aura.sourceId === PLAYER_AURA_ENTITY_ID && aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.utilityTags?.includes(WOW335_MAGE_NETHERWIND_PRESENCE_TAG) )); expect(hasteAura()).toMatchObject({ stacks: 1 }); useCombatStore.getState().tick(0.1, cast.completesAt); const impacts = useCombatStore.getState().combatEvents.filter((event) => ( event.kind === "damage" && event.abilityId === arcaneMissiles.id )); expect(impacts).toHaveLength(5); expect(hasteAura()).toMatchObject({ stacks: 1 }); const nextStartAt = cast.completesAt + 1_000; const nextStarted = useCombatStore.getState().castAbility(arcaneMissiles.id, undefined, nextStartAt); expect(nextStarted).toMatchObject({ ok: true }); expect(useCombatStore.getState().activeCast!.originalDurationMs).toBeLessThan(cast.originalDurationMs); expect(hasteAura()).toMatchObject({ stacks: 2 }); }); it("grants the same cast-commit stack to a generated party Mage", async () => { await preloadAbilityCatalogsForClasses(["mage"]); useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80 }); usePartyStore.getState().initializeParty("netherwind-party-mage", 80, useCombatStore.getState().health, "damage", 1); const mage = createConfiguredPartyMember({ name: "Netherwind Companion", classId: "mage", specialization: "Arcane", specializationId: "mage:arcane", talentTreeId: "mage-arcane", talentRanks: { "talent-1846": 1 }, role: "damage", }, 80, 0, "netherwind-party-mage"); const arcaneMissiles = abilityAtLevel( abilitiesForClass("mage").find((ability) => ability.name === "Arcane Missiles")!, 80, ); const cooldowns = Object.fromEntries(abilitiesForClass("mage").map((ability) => [ability.id, 100_000])); delete cooldowns[arcaneMissiles.id]; usePartyStore.setState({ members: [{ ...mage, talentRanks: { "talent-1846": 1 }, gearStats: EMPTY_ITEM_STATS, cooldowns, globalCooldownEndsAt: 0, resource: 10_000, maxResource: 10_000, resourcePools: { ...mage.resourcePools, mana: 10_000 }, }] }); registerPartyRuntimePosition(mage.id, [0, 0, 0]); useCombatStore.getState().synchronizeActors(); useCombatStore.getState().registerMob("netherwind-party-target", { name: "Party Target", maxHealth: 100_000, level: 80, xpReward: 0, }); setMobPosition("netherwind-party-target", [4, 0, 0]); useCombatStore.getState().damageMob("netherwind-party-target", 1, 100); updatePartyRuntimeTargetVisibility(mage.id, "netherwind-party-target", true); usePartyStore.setState({ command: "attack", activeMobId: "netherwind-party-target" }); advanceDungeonPartyCombat(1_000); expect(usePartyStore.getState().members[0]?.activeCast?.abilityId).toBe(arcaneMissiles.id); expect(useCombatStore.getState().auras.find((aura) => ( aura.sourceId === mage.id && aura.targetId === mage.id && aura.definition.utilityTags?.includes(WOW335_MAGE_NETHERWIND_PRESENCE_TAG) ))).toMatchObject({ stacks: 1 }); }); });