import { beforeEach, describe, expect, it } from "vitest"; import { EMPTY_ITEM_STATS } from "./itemStats"; import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry"; import { clearPartyRuntimeRegistry } from "./partyRuntimeRegistry"; import { usePartyStore, createConfiguredPartyMember } from "./partyStore"; import { PLAYER_AURA_ENTITY_ID, useCombatStore } from "./combatStore"; import { WOW335_SHAMAN_FLURRY_AURA_TAG, WOW335_SHAMAN_FLURRY_DURATION_MS, } from "./wow335ShamanFlurryRuntime"; const TARGET_ID = "wow335-shaman-flurry-target"; const FLURRY_RANK_ID = "talent-602"; function registerTarget(): void { useCombatStore.getState().registerMob(TARGET_ID, { name: "Flurry target", level: 80, maxHealth: 1_000_000, xpReward: 0, }); setMobPosition(TARGET_ID, [3, 0, 0]); useCombatStore.setState((state) => ({ mobs: { ...state.mobs, [TARGET_ID]: { ...state.mobs[TARGET_ID]!, characterDefender: { level: 80, dodgePercent: 0, parryPercent: 0, blockPercent: 0 }, }, }, })); } function setupPlayerShaman(rank: number): void { clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); useCombatStore.getState().initializeCharacter({ classId: "shaman", level: 80, xp: 0 }); useCombatStore.getState().resetSettings(); useCombatStore.getState().setPlayerPosition([0, 0, 0]); usePartyStore.getState().initializeParty("wow-shaman-flurry-player", 80, useCombatStore.getState().health); registerTarget(); useCombatStore.setState((state) => ({ gearStats: { ...state.gearStats, hitRating: 100_000, criticalStrikeRating: 100_000, expertiseRating: 100_000, }, talentRanks: { ...state.talentRanks, [FLURRY_RANK_ID]: rank }, autoAttackTargetId: TARGET_ID, nextAutoAttackAt: 1_000, })); } function flurryAura(actorId: string) { return useCombatStore.getState().auras.find((aura) => ( aura.sourceId === actorId && aura.targetId === actorId && aura.definition.utilityTags?.includes(WOW335_SHAMAN_FLURRY_AURA_TAG) )); } describe("WoW Shaman Flurry combat integration", () => { beforeEach(() => setupPlayerShaman(0)); it("procs from the player's landed melee crit and hastens the next main-hand interval", () => { const intervalForRank = (rank: number) => { setupPlayerShaman(rank); useCombatStore.getState().tick(0.1, 1_000); const state = useCombatStore.getState(); expect(state.lastCombatResult?.critical).toBe(true); expect(state.lastCombatResult?.landed).toBe(true); return state.nextAutoAttackAt - 1_000; }; const baselineInterval = intervalForRank(0); const flurryInterval = intervalForRank(1); const aura = flurryAura(PLAYER_AURA_ENTITY_ID); expect(flurryInterval).toBeLessThan(baselineInterval); expect(aura).toMatchObject({ stacks: 3, appliedAt: 1_000, expiresAt: 1_000 + WOW335_SHAMAN_FLURRY_DURATION_MS, definition: { modifiers: [{ kind: "stat", stat: "melee-haste", operation: "flat", value: 4, perStack: false }], }, }); }); it("uses a generated companion Shaman's exact rank and consumes the next three autoattack attempts", () => { clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80, xp: 0 }); usePartyStore.getState().initializeParty("wow-shaman-flurry-companion", 80, useCombatStore.getState().health); const baseMember = createConfiguredPartyMember({ name: "Flurry Shaman", classId: "shaman", specialization: "Enhancement", specializationId: "shaman:enhancement", talentTreeId: "shaman-enhancement", talentRanks: { [FLURRY_RANK_ID]: 2 }, role: "damage", }, 80, 0, "wow-shaman-flurry-companion:member"); const member = { ...baseMember, gearStats: { ...EMPTY_ITEM_STATS, hitRating: 100_000, criticalStrikeRating: 100_000, expertiseRating: 100_000 }, health: baseMember.maxHealth, }; usePartyStore.setState({ members: [member] }); useCombatStore.getState().commitCompanionActors([member]); registerTarget(); const trigger = useCombatStore.getState().damageMob( TARGET_ID, 100, 2_000, { actorId: member.id, role: member.role, abilityId: "party-auto-attack" }, "physical", { attackKind: "melee", seed: "flurry-companion-trigger", abilityId: "party-auto-attack", rules: { isAutoAttack: true, canMiss: false, canDodge: false, canParry: false, canBlock: false, }, }, ); expect(trigger.combatResult?.landed).toBe(true); expect(trigger.combatResult?.critical).toBe(true); expect(flurryAura(member.id)).toMatchObject({ stacks: 3, definition: { modifiers: [{ kind: "stat", stat: "melee-haste", operation: "flat", value: 6, perStack: false }], }, }); // A fake rank on the Mage player must not supply the companion's Flurry value. useCombatStore.setState((state) => ({ talentRanks: { ...state.talentRanks, [FLURRY_RANK_ID]: 3 }, })); for (let swing = 1; swing <= 3; swing += 1) { useCombatStore.getState().damageMob( TARGET_ID, 100, 2_000 + swing * 100, { actorId: member.id, role: member.role, abilityId: "party-auto-attack" }, "physical", { attackKind: "melee", seed: `flurry-companion-swing-${swing}`, abilityId: "party-auto-attack", rules: { isAutoAttack: true, canCrit: false, canMiss: false, canDodge: false, canParry: false, canBlock: false, }, }, ); const aura = flurryAura(member.id); expect(aura?.stacks ?? 0).toBe(3 - swing); } expect(flurryAura(PLAYER_AURA_ENTITY_ID)).toBeUndefined(); }); });