import { beforeEach, describe, expect, it } from "vitest"; import { PLAYER_AGGRO_ID } from "./aggro"; import { abilityAtLevel, abilityById } from "./abilityCatalog"; import { useCombatStore, stableCombatUnit, PLAYER_AURA_ENTITY_ID } from "./combatStore"; import { DEFAULT_ENEMY_ATTACK, type EnemyDamageSchool } from "./enemyAttacks"; import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry"; import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry"; import { advanceDungeonPartyCombat, resetPartyRuntimeClock } from "./partyRuntime"; import { createConfiguredPartyMember, usePartyStore } from "./partyStore"; import { EMPTY_ITEM_STATS } from "./itemStats"; import { WOW335_MAGE_FROST_WARDING_TALENT_ID, WOW335_MAGE_FROST_WARDING_WARD_ABILITIES, } from "./wow335MageFrostWardingRuntime"; const ATTACK = { ...DEFAULT_ENEMY_ATTACK, id: "frost-warding-incoming-test", name: "Frost Warding incoming test", delivery: "projectile" as const, animation: "cast" as const, castTimeMs: 0, range: 20, cooldownMs: 10_000, initialCooldownMs: [0, 0] as const, repeatCooldownMs: [10_000, 10_000] as const, damageMultiplier: 1, }; function incomingEvent(mobId: string, targetId: string) { const event = useCombatStore.getState().combatEvents.find((candidate) => ( candidate.kind === "damage" && candidate.sourceActorId === mobId && candidate.targetActorId === targetId )); if (!event) throw new Error(`Expected an incoming damage event on ${targetId}.`); return event; } function hitTime(mobId: string, targetId: string): number { for (let now = 1_000; now < 200_000; now += 1) { const resultRoll = stableCombatUnit(`${mobId}:${ATTACK.id}:${targetId}:0:${now}:outcome`); const wardRoll = stableCombatUnit(`${targetId}:${mobId}:${ATTACK.id}:0:wow335-frost-warding`); if (resultRoll >= 0.2 && resultRoll < 0.9 && wardRoll < 0.5) return now; } throw new Error("Could not find a landed hit that passes rank 2 Frost Warding's 50% roll."); } function mobIdForWardProc(prefix: string, targetId: string): string { for (let index = 0; index < 1_000; index += 1) { const mobId = `${prefix}-${index}`; if (stableCombatUnit(`${targetId}:${mobId}:${ATTACK.id}:0:wow335-frost-warding`) < 0.5) return mobId; } throw new Error("Could not find a stable Frost Warding success seed."); } function setupAttacker(mobId: string, targetId: string, now: number, school: EnemyDamageSchool): void { useCombatStore.getState().registerMob(mobId, { name: "Frost Warding test attacker", level: 80, maxHealth: 100_000, xpReward: 0, attacks: [{ ...ATTACK, school }], }); setMobPosition(mobId, [2, 0, 0]); useCombatStore.setState((state) => ({ mobs: { ...state.mobs, [mobId]: { ...state.mobs[mobId]!, attackDamage: 4_000 } }, })); useCombatStore.getState().damageMob(mobId, 1, now - 1, { actorId: targetId, role: "tank" }, "physical"); } function runPlayerHit(rank: number, ward: "fire" | "frost") { clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80, xp: 0 }); usePartyStore.getState().initializeParty(`frost-warding-player-${ward}`, 80, useCombatStore.getState().health, "damage", 1); const spell = abilityById(WOW335_MAGE_FROST_WARDING_WARD_ABILITIES[ward].abilityId); if (!spell) throw new Error(`Missing local Mage ${ward} ward.`); useCombatStore.setState((state) => ({ talentRanks: { ...state.talentRanks, [WOW335_MAGE_FROST_WARDING_TALENT_ID]: rank }, resource: 10_000, resourcePools: { ...state.resourcePools, mana: 10_000 }, })); expect(useCombatStore.getState().castAbility(spell.id, undefined, 1_000)).toMatchObject({ ok: true }); useCombatStore.setState((state) => ({ resource: 1_000, resourcePools: { ...state.resourcePools, mana: 1_000 }, })); const mobId = mobIdForWardProc(`frost-warding-player-${ward}`, PLAYER_AGGRO_ID); const now = hitTime(mobId, PLAYER_AGGRO_ID); setupAttacker(mobId, PLAYER_AGGRO_ID, now, ward); useCombatStore.getState().advanceMobCombat(now); return { event: incomingEvent(mobId, PLAYER_AGGRO_ID), mana: useCombatStore.getState().resourcePools.mana ?? 0 }; } function runCompanionHit(rank: number, ward: "fire" | "frost") { clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80, xp: 0 }); usePartyStore.getState().initializeParty(`frost-warding-companion-${ward}`, 80, useCombatStore.getState().health, "damage", 1); const talentRanks = { [WOW335_MAGE_FROST_WARDING_TALENT_ID]: rank }; const member = createConfiguredPartyMember({ name: "Frost Warding Mage", classId: "mage", specialization: "Frost", specializationId: "mage:frost", talentTreeId: "mage-frost", talentRanks, role: "tank", }, 80, 0, `frost-warding-companion-${ward}-${rank}`); const wardAbility = abilityById(WOW335_MAGE_FROST_WARDING_WARD_ABILITIES[ward].abilityId); if (!wardAbility) throw new Error("Missing local Mage Ward ability."); const resolvedWard = abilityAtLevel(wardAbility, 80); const auraEffect = resolvedWard.effects.find((effect) => effect.kind === "apply-aura"); if (!auraEffect || auraEffect.kind !== "apply-aura") throw new Error("Mage Ward has no generated aura effect."); usePartyStore.setState({ members: [{ ...member, health: 100_000, maxHealth: 100_000, gearStats: EMPTY_ITEM_STATS, talentRanks, resource: 1_000, maxResource: 20_000, resourcePools: { ...member.resourcePools, mana: 1_000 }, }] }); registerPartyRuntimePosition(member.id, [0, 0, 0]); useCombatStore.getState().synchronizeActors(); expect(useCombatStore.getState().applyActorAura(member.id, member.id, resolvedWard, auraEffect, 1_000)).toBe(true); const mobId = mobIdForWardProc(`frost-warding-companion-${ward}`, member.id); const now = hitTime(mobId, member.id); setupAttacker(mobId, member.id, now, ward); advanceDungeonPartyCombat(now); useCombatStore.getState().advanceMobCombat(now); const actor = useCombatStore.getState().actors[member.id]; return { event: incomingEvent(mobId, member.id), mana: actor?.resources.mana?.current ?? 0 }; } describe("WoW Mage Frost Warding combat integration", () => { beforeEach(() => { clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); resetPartyRuntimeClock(); }); it.each(["fire", "frost"] as const)( "reduces matching %s spell damage before its Ward absorb and refunds the same Mana to player and companion", (ward) => { const baselinePlayer = runPlayerHit(0, ward); const wardedPlayer = runPlayerHit(2, ward); const baselineCompanion = runCompanionHit(0, ward); const wardedCompanion = runCompanionHit(2, ward); const baselineAfterAbsorb = baselinePlayer.event.effectiveAmount + baselinePlayer.event.absorbed; const wardedAfterAbsorb = wardedPlayer.event.effectiveAmount + wardedPlayer.event.absorbed; const companionBaselineAfterAbsorb = baselineCompanion.event.effectiveAmount + baselineCompanion.event.absorbed; const companionWardedAfterAbsorb = wardedCompanion.event.effectiveAmount + wardedCompanion.event.absorbed; expect(baselineAfterAbsorb).toBeGreaterThan(0); expect(wardedAfterAbsorb).toBe(Math.round(baselineAfterAbsorb * 0.5)); expect(companionBaselineAfterAbsorb).toBeGreaterThan(0); expect(companionWardedAfterAbsorb).toBe(Math.round(companionBaselineAfterAbsorb * 0.5)); expect(wardedPlayer.mana).toBeGreaterThan(1_000); expect(wardedCompanion.mana).toBeGreaterThan(1_000); }, ); });