import { beforeEach, describe, expect, it } from "vitest"; import { PLAYER_AGGRO_ID } from "./aggro"; import { applyAura, resolveStatValue } from "./combatAuras"; import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry"; import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry"; import { preloadAbilityCatalogsForClasses } from "./abilityCatalog"; import { PLAYER_AURA_ENTITY_ID, useCombatStore } from "./combatStore"; import { usePartyStore } from "./partyStore"; import { DEFAULT_ENEMY_ATTACK } from "./enemyAttacks"; const TARGET_ID = "druid-proc-target"; function unit(seed: string): number { let hash = 2166136261; for (let index = 0; index < seed.length; index += 1) { hash ^= seed.charCodeAt(index); hash = Math.imul(hash, 16777619); } return (hash >>> 0) / 0x1_0000_0000; } function abilityId(name: string): string { const ability = useCombatStore.getState().abilities.find((candidate) => candidate.name === name); expect(ability, name).toBeDefined(); return ability!.id; } function findDruidProcStart(name: string, startAt: number, chance: number, procTag: string): number { const ability = useCombatStore.getState().abilities.find((candidate) => candidate.name === name); expect(ability, name).toBeDefined(); for (let start = startAt; start < startAt + 100_000; start += 100) { const impactAt = start + ability!.castTimeMs; const directSeed = `${PLAYER_AGGRO_ID}:${ability!.id}:${TARGET_ID}:${impactAt}:direct`; if (unit(`${directSeed}:outcome`) > 0.1 && unit(`${directSeed}:${procTag}`) < chance) return start; } throw new Error(`No deterministic ${name} proc seed found.`); } function castAndFinish(name: string, startedAt: number) { const id = abilityId(name); const result = useCombatStore.getState().castAbility(id, { targetActorId: TARGET_ID, now: startedAt }); expect(result.ok, JSON.stringify(result)).toBe(true); const activeCast = useCombatStore.getState().activeCast; if (activeCast?.abilityId === id) { useCombatStore.getState().tick(Math.max(0.1, (activeCast.completesAt - startedAt) / 1_000), activeCast.completesAt); } return useCombatStore.getState().combatEvents.filter((event) => ( event.kind === "damage" && event.abilityId === id )).at(-1); } function guaranteeDruidMeleeCrits(): void { useCombatStore.setState((state) => ({ auras: [...applyAura(state.auras, { definition: { id: "test-druid-guaranteed-melee-crit", name: "Test Melee Critical Chance", disposition: "buff", durationMs: null, modifiers: [ { kind: "stat", stat: "hit", operation: "flat", value: 100 }, { kind: "stat", stat: "physical-critical-strike", operation: "flat", value: 100 }, { kind: "stat", stat: "spell-hit", operation: "flat", value: 100 }, { kind: "stat", stat: "spell-critical-strike", operation: "flat", value: 100 }, ], }, sourceId: PLAYER_AURA_ENTITY_ID, targetId: PLAYER_AURA_ENTITY_ID, now: 1_000, }).auras], mobs: { ...state.mobs, [TARGET_ID]: { ...state.mobs[TARGET_ID]!, characterDefender: { level: 80, armor: 0, dodgePercent: 0, parryPercent: 0, blockPercent: 0, blockValue: 0, }, }, }, })); } function findDruidPrimalFurySeed(procTag: string, chance: number, shouldProc: boolean): string { for (let index = 0; index < 100_000; index += 1) { const seed = `druid-primal-fury:${procTag}:${index}`; if ((unit(`${seed}:wow335-druid-primal-fury-${procTag}`) < chance) === shouldProc) return seed; } throw new Error(`No deterministic Primal Fury ${procTag} seed found.`); } describe("WoW Druid proc runtime", () => { beforeEach(async () => { clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); await preloadAbilityCatalogsForClasses(["druid"]); useCombatStore.getState().initializeCharacter({ classId: "druid", level: 80, xp: 0 }); useCombatStore.getState().resetSettings(); useCombatStore.getState().setPlayerPosition([0, 0, 0]); usePartyStore.getState().initializeParty("wow335-druid-proc-tests", 1, useCombatStore.getState().health); useCombatStore.setState((state) => ({ resource: state.maxResource, resourcePools: { ...state.resourcePools, mana: state.maxResourcePools.mana }, })); useCombatStore.getState().registerMob(TARGET_ID, { name: "Target", maxHealth: 1_000_000 }); setMobPosition(TARGET_ID, [4, 0, 0]); useCombatStore.getState().selectMob(TARGET_ID); }); it("procs Solar Eclipse from Starfire and gives Wrath its source 30% damage bonus", () => { const baseline = castAndFinish("Wrath", 1_000); expect(baseline?.kind).toBe("damage"); useCombatStore.setState((state) => ({ talentRanks: { ...state.talentRanks, "talent-1924": 3 } })); castAndFinish("Starfire", 5_000); const solar = useCombatStore.getState().auras.find((aura) => ( aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.utilityTags?.includes("wow335-druid-eclipse-solar") )); expect(solar?.expiresAt).toBe(23_500); expect(useCombatStore.getState().cooldowns["wow335-druid-eclipse-solar"]).toBe(38_500); const buffed = castAndFinish("Wrath", 10_000); expect(buffed?.kind).toBe("damage"); expect(buffed?.rawAmount).toBe(Math.round(baseline!.rawAmount * 1.3)); }); it("procs Lunar Eclipse from Wrath and adds 30 percentage points to Starfire crit chance", () => { const baselineEvent = castAndFinish("Starfire", 1_000); expect(baselineEvent?.kind).toBe("damage"); const baselineCritChance = useCombatStore.getState().lastCombatResult?.attackTable.criticalPercent; expect(baselineCritChance).toBeDefined(); useCombatStore.setState((state) => ({ talentRanks: { ...state.talentRanks, "talent-1924": 3 } })); const wrathStart = findDruidProcStart("Wrath", 6_000, 0.6, "druid-eclipse"); castAndFinish("Wrath", wrathStart); const lunar = useCombatStore.getState().auras.find((aura) => ( aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.utilityTags?.includes("wow335-druid-eclipse-lunar") )); expect(lunar).toBeDefined(); castAndFinish("Starfire", wrathStart + 2_100); expect(useCombatStore.getState().lastCombatResult?.attackTable.criticalPercent) .toBe(baselineCritChance! + 30); }); it("grants Nature's Grace after a direct healing critical and applies 20% cast haste for four seconds", () => { const healingTouch = abilityId("Healing Touch"); let startedAt = 1_000; while (unit(`${PLAYER_AGGRO_ID}:${healingTouch}:${PLAYER_AURA_ENTITY_ID}:${startedAt + 3_000}:heal:druid-omen-of-clarity`) >= 0.06) { startedAt += 100; } useCombatStore.setState((state) => ({ health: Math.round(state.maxHealth / 2), talentRanks: { ...state.talentRanks, "talent-789": 3, "talent-827": 3 }, auras: [...applyAura(state.auras, { definition: { id: "test-druid-guaranteed-spell-crit", name: "Test Critical Chance", disposition: "buff", durationMs: null, modifiers: [{ kind: "stat", stat: "spell-critical-strike", operation: "flat", value: 100 }], }, sourceId: PLAYER_AURA_ENTITY_ID, targetId: PLAYER_AURA_ENTITY_ID, now: startedAt, }).auras], })); expect(useCombatStore.getState().castAbility(healingTouch, { targetActorId: null, now: startedAt }).ok).toBe(true); const firstCast = useCombatStore.getState().activeCast; expect(firstCast?.abilityId).toBe(healingTouch); useCombatStore.getState().tick(0.1, firstCast!.completesAt); expect(useCombatStore.getState().combatEvents.some((event) => ( event.kind === "healing" && event.abilityId === healingTouch && event.critical ))).toBe(true); const natureGrace = useCombatStore.getState().auras.find((aura) => ( aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.utilityTags?.includes("wow335-druid-natures-grace") )); expect(natureGrace?.expiresAt).toBe(firstCast!.completesAt + 4_000); expect(resolveStatValue(0, [natureGrace!], "haste")).toBe(20); expect(useCombatStore.getState().auras.some((aura) => ( aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.utilityTags?.includes("wow335-druid-clearcasting") ))).toBe(true); const manaAfterFirstHeal = useCombatStore.getState().resource; const nextStart = firstCast!.completesAt + 1_500; useCombatStore.setState((state) => ({ lastResourceSpentAt: { ...state.lastResourceSpentAt, mana: nextStart }, })); expect(useCombatStore.getState().castAbility(healingTouch, { targetActorId: null, now: nextStart }).ok).toBe(true); expect(useCombatStore.getState().resource).toBe(manaAfterFirstHeal); const hastedCast = useCombatStore.getState().activeCast; expect(hastedCast?.originalDurationMs).toBe(Math.round(3_000 / 1.2)); useCombatStore.getState().tick(0.1, hastedCast!.completesAt); const overhealClearcasting = useCombatStore.getState().auras.find((aura) => ( aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.utilityTags?.includes("wow335-druid-clearcasting") )); expect(overhealClearcasting?.expiresAt).toBe(hastedCast!.completesAt + 15_000); expect(useCombatStore.getState().cooldowns["wow335-druid-omen-of-clarity"]) .toBe(hastedCast!.completesAt + 2_000); }); it("procs Omen of Clarity from damage, makes the next eligible cast free, and respects its two-second ICD", () => { useCombatStore.setState((state) => ({ talentRanks: { ...state.talentRanks, "talent-827": 3 } })); const moonfire = useCombatStore.getState().abilities.find((ability) => ability.name === "Moonfire"); expect(moonfire).toBeDefined(); const start = (() => { for (let now = 1_000; now < 100_000; now += 1) { const seed = `${PLAYER_AGGRO_ID}:${moonfire!.id}:${TARGET_ID}:${now}:direct`; if (unit(`${seed}:outcome`) > 0.1 && unit(`${seed}:druid-omen-of-clarity`) < 0.06) return now; } throw new Error("No deterministic Omen of Clarity proc seed found."); })(); const first = useCombatStore.getState().castAbility(moonfire!.id, { targetActorId: TARGET_ID, now: start }); expect(first.ok, JSON.stringify(first)).toBe(true); const manaAfterFirst = useCombatStore.getState().resource; const clearcasting = useCombatStore.getState().auras.find((aura) => ( aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.utilityTags?.includes("wow335-druid-clearcasting") )); expect(clearcasting?.expiresAt).toBe(start + 15_000); expect(useCombatStore.getState().cooldowns["wow335-druid-omen-of-clarity"]).toBe(start + 2_000); const second = useCombatStore.getState().castAbility(moonfire!.id, { targetActorId: TARGET_ID, now: start + 1_500, }); expect(second.ok, JSON.stringify(second)).toBe(true); expect(useCombatStore.getState().resource).toBe(manaAfterFirst); expect(useCombatStore.getState().auras.some((aura) => ( aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.utilityTags?.includes("wow335-druid-clearcasting") ))).toBe(false); }); it("allows a successful healing spell that fully overheals to proc Omen of Clarity", () => { const healingTouch = abilityId("Healing Touch"); useCombatStore.setState((state) => ({ health: state.maxHealth, talentRanks: { ...state.talentRanks, "talent-827": 3 }, })); let startedAt = 1_000; while (unit(`${PLAYER_AGGRO_ID}:${healingTouch}:${PLAYER_AURA_ENTITY_ID}:${startedAt + 3_000}:heal:druid-omen-of-clarity`) >= 0.06) { startedAt += 100; } const result = useCombatStore.getState().castAbility(healingTouch, { targetActorId: null, now: startedAt }); expect(result.ok, JSON.stringify(result)).toBe(true); const activeCast = useCombatStore.getState().activeCast; expect(activeCast?.abilityId).toBe(healingTouch); useCombatStore.getState().tick(0.1, activeCast!.completesAt); const healingEvent = useCombatStore.getState().combatEvents.find((event) => ( event.kind === "healing" && event.abilityId === healingTouch && event.occurredAt === activeCast!.completesAt )); expect(healingEvent).toMatchObject({ rawAmount: expect.any(Number), effectiveAmount: 0 }); expect(useCombatStore.getState().auras.some((aura) => ( aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.utilityTags?.includes("wow335-druid-clearcasting") ))).toBe(true); }); it("allows landed player auto attacks to trigger Omen of Clarity", () => { useCombatStore.setState((state) => ({ talentRanks: { ...state.talentRanks, "talent-827": 3 } })); let now = 1_000; let seed = `druid-auto-attack:${now}`; while (unit(`${seed}:druid-omen-of-clarity`) >= 0.06) { now += 1; seed = `druid-auto-attack:${now}`; } const result = useCombatStore.getState().damageMob(TARGET_ID, 100, now, undefined, "physical", { attackKind: "melee", seed, rules: { isAutoAttack: true, canMiss: false, canDodge: false, canParry: false, canBlock: false, canCrit: false }, }); expect(result.amount).toBeGreaterThan(0); expect(useCombatStore.getState().auras.some((aura) => ( aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.utilityTags?.includes("wow335-druid-clearcasting") ))).toBe(true); }); it("stores 30% of an eligible critical heal and blooms on the target's next direct hit", () => { const healingTouch = abilityId("Healing Touch"); const startedAt = 1_000; useCombatStore.setState((state) => ({ health: 1, talentRanks: { ...state.talentRanks, "talent-1922": 3 }, auras: [...applyAura(state.auras, { definition: { id: "test-druid-guaranteed-spell-crit", name: "Test Critical Chance", disposition: "buff", durationMs: null, modifiers: [{ kind: "stat", stat: "spell-critical-strike", operation: "flat", value: 100 }], }, sourceId: PLAYER_AURA_ENTITY_ID, targetId: PLAYER_AURA_ENTITY_ID, now: startedAt, }).auras], })); expect(useCombatStore.getState().castAbility(healingTouch, { targetActorId: null, now: startedAt }).ok).toBe(true); const cast = useCombatStore.getState().activeCast; expect(cast?.abilityId).toBe(healingTouch); useCombatStore.getState().tick(0.1, cast!.completesAt); const heal = useCombatStore.getState().combatEvents.find((event) => ( event.kind === "healing" && event.abilityId === healingTouch && event.occurredAt === cast!.completesAt )); expect(heal).toMatchObject({ critical: true, effectiveAmount: expect.any(Number) }); const storedSeed = useCombatStore.getState().auras.find((aura) => ( aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.utilityTags?.includes("wow335-druid-living-seed") )); expect(storedSeed?.expiresAt).toBe(cast!.completesAt + 15_000); expect(storedSeed?.druidLivingSeedAmount).toBe(Math.round(heal!.effectiveAmount * 0.3)); const healthBeforeHit = useCombatStore.getState().health; const damage = useCombatStore.getState().damagePlayer(100, "physical", 80, cast!.completesAt + 1); expect(damage).toBeGreaterThan(0); expect(useCombatStore.getState().health).toBeGreaterThan(healthBeforeHit - damage); expect(useCombatStore.getState().auras.some((aura) => ( aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.utilityTags?.includes("wow335-druid-living-seed") ))).toBe(false); expect(useCombatStore.getState().combatEvents.some((event) => ( event.kind === "healing" && event.abilityId === "wow335:druid:living-seed" && event.effectiveAmount > 0 ))).toBe(true); }); it("expires an untriggered Living Seed after fifteen seconds", () => { const healingTouch = abilityId("Healing Touch"); const startedAt = 1_000; useCombatStore.setState((state) => ({ health: 1, talentRanks: { ...state.talentRanks, "talent-1922": 3 }, auras: [...applyAura(state.auras, { definition: { id: "test-druid-guaranteed-spell-crit", name: "Test Critical Chance", disposition: "buff", durationMs: null, modifiers: [{ kind: "stat", stat: "spell-critical-strike", operation: "flat", value: 100 }], }, sourceId: PLAYER_AURA_ENTITY_ID, targetId: PLAYER_AURA_ENTITY_ID, now: startedAt, }).auras], })); expect(useCombatStore.getState().castAbility(healingTouch, { targetActorId: null, now: startedAt }).ok).toBe(true); const cast = useCombatStore.getState().activeCast; useCombatStore.getState().tick(0.1, cast!.completesAt); const expiresAt = cast!.completesAt + 15_000; expect(useCombatStore.getState().auras.some((aura) => ( aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.utilityTags?.includes("wow335-druid-living-seed") ))).toBe(true); const healthBeforeHit = useCombatStore.getState().health; const damage = useCombatStore.getState().damagePlayer(100, "physical", 80, expiresAt); expect(damage).toBeGreaterThan(0); expect(useCombatStore.getState().health).toBe(healthBeforeHit - damage); expect(useCombatStore.getState().auras.some((aura) => ( aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.utilityTags?.includes("wow335-druid-living-seed") ))).toBe(false); expect(useCombatStore.getState().combatEvents.some((event) => ( event.kind === "healing" && event.abilityId === "wow335:druid:living-seed" ))).toBe(false); }); it("blooms on a landed direct enemy hit to a party ally", () => { const member = usePartyStore.getState().members[0]!; const memberMaxHealth = 1_000; usePartyStore.setState((state) => ({ members: state.members.map((candidate) => candidate.id === member.id ? { ...candidate, health: 100, maxHealth: memberMaxHealth } : candidate), })); useCombatStore.setState((state) => ({ actors: { ...state.actors, [member.id]: { ...state.actors[member.id]!, health: 100, maxHealth: memberMaxHealth, alive: true }, }, talentRanks: { ...state.talentRanks, "talent-1922": 3 }, auras: [...applyAura(state.auras, { definition: { id: "test-druid-guaranteed-spell-crit", name: "Test Critical Chance", disposition: "buff", durationMs: null, modifiers: [{ kind: "stat", stat: "spell-critical-strike", operation: "flat", value: 100 }], }, sourceId: PLAYER_AURA_ENTITY_ID, targetId: PLAYER_AURA_ENTITY_ID, now: 1_000, }).auras, ...applyAura(state.auras, { definition: { id: "test-druid-guaranteed-ally-hit", name: "Test Incoming Hit Chance", disposition: "buff", durationMs: null, modifiers: [{ kind: "stat", stat: "incoming-melee-hit", operation: "flat", value: 100 }], }, sourceId: PLAYER_AURA_ENTITY_ID, targetId: member.id, now: 1_000, }).auras], })); registerPartyRuntimePosition(member.id, [2, 0, 0]); const healingTouch = abilityId("Healing Touch"); useCombatStore.getState().synchronizeActors(); useCombatStore.getState().selectFriendlyActor(member.id); const cast = useCombatStore.getState().castAbility(healingTouch, { targetActorId: member.id, now: 1_000 }); expect(cast.ok, JSON.stringify(cast)).toBe(true); const activeCast = useCombatStore.getState().activeCast; useCombatStore.getState().tick(0.1, activeCast!.completesAt); const storedSeed = useCombatStore.getState().auras.find((aura) => ( aura.targetId === member.id && aura.definition.utilityTags?.includes("wow335-druid-living-seed") )); expect(storedSeed?.druidLivingSeedAmount).toBeGreaterThan(0); const mobId = "druid-living-seed-party-hit"; const attackId = "druid-living-seed-party-swing"; useCombatStore.getState().registerMob(mobId, { name: "Party attacker", level: 1, maxHealth: 1_000, xpReward: 0, attacks: [{ ...DEFAULT_ENEMY_ATTACK, id: attackId, initialCooldownMs: [0, 0], repeatCooldownMs: [1_000, 1_000], }], }); setMobPosition(mobId, [4, 0, 0]); useCombatStore.setState((state) => ({ mobs: { ...state.mobs, [mobId]: { ...state.mobs[mobId]!, engaged: true, combatPhase: "attacking", targetActorId: member.id, threatByActor: { [member.id]: 100 }, }, }, })); useCombatStore.getState().advanceMobCombat(activeCast!.completesAt + 500); const enemyDamage = useCombatStore.getState().combatEvents.find((event) => ( event.kind === "damage" && event.sourceActorId === mobId && event.targetActorId === member.id )); expect(enemyDamage).toMatchObject({ outcome: "hit" }); const remainingSeed = useCombatStore.getState().auras.find((aura) => ( aura.targetId === member.id && aura.definition.utilityTags?.includes("wow335-druid-living-seed") )); expect(remainingSeed).toBeUndefined(); expect(useCombatStore.getState().combatEvents.some((event) => ( event.kind === "healing" && event.abilityId === "wow335:druid:living-seed" && event.targetActorId === member.id && event.effectiveAmount > 0 ))).toBe(true); }); it("grants five Rage on Bear and Dire Bear Form critical auto-attacks at Primal Fury's 50% rank", () => { useCombatStore.setState((state) => ({ talentRanks: { ...state.talentRanks, "talent-801": 1 }, resourcePools: { ...state.resourcePools, rage: 0 }, maxResourcePools: { ...state.maxResourcePools, rage: 100 }, })); guaranteeDruidMeleeCrits(); const form = useCombatStore.getState().castAbility(abilityId("Bear Form"), { targetActorId: null, now: 1_000, }); expect(form.ok, JSON.stringify(form)).toBe(true); const procSeed = findDruidPrimalFurySeed("rage", 0.5, true); const proc = useCombatStore.getState().damageMob(TARGET_ID, 100, 2_000, undefined, "physical", { attackKind: "melee", seed: procSeed, rules: { isAutoAttack: true, canMiss: false, canDodge: false, canParry: false, canBlock: false }, }); expect(proc.combatResult?.critical).toBe(true); expect(useCombatStore.getState().resourcePools.rage).toBe(5); expect(useCombatStore.getState().combatEvents.at(-1)).toMatchObject({ kind: "resource", sourceActorId: PLAYER_AGGRO_ID, targetActorId: PLAYER_AURA_ENTITY_ID, abilityId: "wow335-druid-primal-fury", rawAmount: 5, effectiveAmount: 5, }); const noProcSeed = findDruidPrimalFurySeed("rage", 0.5, false); const noProc = useCombatStore.getState().damageMob(TARGET_ID, 100, 3_000, undefined, "physical", { attackKind: "melee", seed: noProcSeed, rules: { isAutoAttack: true, canMiss: false, canDodge: false, canParry: false, canBlock: false }, }); expect(noProc.combatResult?.critical).toBe(true); expect(useCombatStore.getState().resourcePools.rage).toBe(5); const direBearForm = useCombatStore.getState().castAbility(abilityId("Dire Bear Form"), { targetActorId: null, now: 4_000, }); expect(direBearForm.ok, JSON.stringify(direBearForm)).toBe(true); const direBearProc = useCombatStore.getState().damageMob(TARGET_ID, 100, 5_000, undefined, "physical", { attackKind: "melee", seed: findDruidPrimalFurySeed("rage", 0.5, true), rules: { isAutoAttack: true, canMiss: false, canDodge: false, canParry: false, canBlock: false }, }); expect(direBearProc.combatResult?.critical).toBe(true); expect(useCombatStore.getState().resourcePools.rage).toBe(10); }); it("adds one capped combo point on a Cat Form combo-builder critical and ignores other hits", () => { useCombatStore.setState((state) => ({ talentRanks: { ...state.talentRanks, "talent-801": 2 }, resourcePools: { ...state.resourcePools, rage: 0 }, maxResourcePools: { ...state.maxResourcePools, rage: 100 }, })); guaranteeDruidMeleeCrits(); const form = useCombatStore.getState().castAbility(abilityId("Cat Form"), { targetActorId: null, now: 1_000, }); expect(form.ok, JSON.stringify(form)).toBe(true); const claw = castAndFinish("Claw", 3_000); expect(claw).toMatchObject({ critical: true, sourceActorId: PLAYER_AGGRO_ID, targetActorId: TARGET_ID }); expect(useCombatStore.getState().comboPoints).toBe(2); expect(useCombatStore.getState().comboTargetId).toBe(TARGET_ID); expect(useCombatStore.getState().resourcePools.rage).toBe(0); useCombatStore.setState((state) => ({ comboPoints: 4, comboTargetId: TARGET_ID, resourcePools: { ...state.resourcePools, energy: state.maxResourcePools.energy, }, })); const cappedClaw = castAndFinish("Claw", 5_000); expect(cappedClaw?.critical).toBe(true); expect(useCombatStore.getState().comboPoints).toBe(5); expect(useCombatStore.getState().comboTargetId).toBe(TARGET_ID); useCombatStore.setState((state) => ({ comboPoints: 0, comboTargetId: null })); const clawId = abilityId("Claw"); useCombatStore.getState().damageMob(TARGET_ID, 100, 7_000, undefined, "physical", { attackKind: "melee", seed: "druid-primal-fury-cat-auto-attack", abilityId: clawId, rules: { isAutoAttack: true, canMiss: false, canDodge: false, canParry: false, canBlock: false }, }); expect(useCombatStore.getState().comboPoints).toBe(0); expect(useCombatStore.getState().comboTargetId).toBeNull(); useCombatStore.setState((state) => ({ comboPoints: 0, comboTargetId: null, resourcePools: { ...state.resourcePools, energy: state.maxResourcePools.energy }, })); const rake = castAndFinish("Rake", 8_000); expect(rake).toMatchObject({ critical: true, sourceActorId: PLAYER_AGGRO_ID, targetActorId: TARGET_ID }); expect(useCombatStore.getState().comboPoints).toBe(2); useCombatStore.getState().tick(9, 17_000); expect(useCombatStore.getState().comboPoints).toBe(2); expect(useCombatStore.getState().comboTargetId).toBe(TARGET_ID); useCombatStore.setState((state) => ({ comboPoints: 0, comboTargetId: null })); useCombatStore.getState().damageMob(TARGET_ID, 100, 18_000, undefined, "arcane", { attackKind: "spell", seed: "druid-primal-fury-cat-non-combo", abilityId: abilityId("Moonfire"), rules: { canMiss: false, canDodge: false, canParry: false, canBlock: false }, }); expect(useCombatStore.getState().comboPoints).toBe(0); expect(useCombatStore.getState().comboTargetId).toBeNull(); }); });