import { beforeEach, describe, expect, it } from "vitest"; import { PLAYER_AGGRO_ID } from "./aggro"; import { applyAura } from "./combatAuras"; import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry"; import { clearPartyRuntimeRegistry, updatePartyRuntimePosition } from "./partyRuntimeRegistry"; import { useCombatStore, PLAYER_AURA_ENTITY_ID } from "./combatStore"; import { usePartyStore } from "./partyStore"; import type { EnemyAttackDefinition } from "./enemyAttacks"; import { WOW335_DRUID_PREDATORY_INSTINCTS_TALENT_ID } from "./wow335DruidPredatoryInstinctsRuntime"; const TARGET_ID = "druid-predatory-instincts-target"; const ENEMY_ID = "druid-predatory-instincts-area-enemy"; const AREA_ATTACK: EnemyAttackDefinition = { id: "predatory-area-hit", name: "Predatory Instincts Area Hit", delivery: "area", target: "nearby-party", school: "fire", animation: "cast", castTimeMs: 0, range: 80, radius: 80, cooldownMs: 10_000, initialCooldownMs: [0, 0], repeatCooldownMs: [10_000, 10_000], damageMultiplier: 1, }; function stableUnit(seed: string): number { let hash = 2_166_136_261; for (let index = 0; index < seed.length; index += 1) { hash ^= seed.charCodeAt(index); hash = Math.imul(hash, 16_777_619); } return (hash >>> 0) / 0x1_0000_0000; } function areaHitTime(actorId: string): number { for (let now = 3_101; now < 20_000; now += 1) { if (stableUnit(`${ENEMY_ID}:${AREA_ATTACK.id}:${actorId}:0:${now}:outcome`) >= 0.2) return now; } throw new Error(`Could not find an area-hit roll for ${actorId}`); } function catFormAura(actorId: string, now = 1_000) { return { definition: { id: "wow335:768:cat-form", name: "Cat Form", disposition: "buff" as const, durationMs: null, }, sourceId: actorId, targetId: actorId, now, }; } function setCatForm(actorId: string): void { useCombatStore.setState((state) => ({ auras: [...applyAura(state.auras, catFormAura(actorId)).auras], })); } function registerCriticalTarget(): void { useCombatStore.getState().registerMob(TARGET_ID, { name: TARGET_ID, maxHealth: 1_000_000, xpReward: 0 }); useCombatStore.setState((state) => ({ mobs: { ...state.mobs, [TARGET_ID]: { ...state.mobs[TARGET_ID]!, characterDefender: { level: 80, armor: 0, dodgePercent: 0, parryPercent: 0, blockPercent: 0, blockValue: 0, }, }, }, })); setMobPosition(TARGET_ID, [4, 0, 0]); } function criticalDamageForSource(sourceActorId: string, abilityId: string): number { const member = usePartyStore.getState().members.find((candidate) => candidate.id === sourceActorId); useCombatStore.getState().damageMob(TARGET_ID, 100, 2_000, { actorId: sourceActorId, role: member?.role ?? "damage", abilityId, }, "physical", { attackKind: "melee", seed: `druid-predatory-critical-${sourceActorId}-${abilityId}`, abilityId, criticalChanceBonusPercent: 1_000, rules: { canMiss: false, canCrit: true, canDodge: false, canParry: false, canBlock: false }, }); return useCombatStore.getState().lastCombatResult!.damageBeforeMitigation; } function areaDamageForActor(actorKind: "player" | "companion", rank: number): number { clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); useCombatStore.getState().initializeCharacter({ classId: "druid", level: 80, xp: 0 }); useCombatStore.getState().setPlayerPosition([0, 0, 0]); usePartyStore.getState().initializeParty(`druid-predatory-area-${actorKind}`, 1, useCombatStore.getState().health); const firstMember = usePartyStore.getState().members[0]!; usePartyStore.setState((state) => ({ members: state.members.map((member, index) => index === 0 ? { ...member, classId: "druid", talentRanks: { ...member.talentRanks, [WOW335_DRUID_PREDATORY_INSTINCTS_TALENT_ID]: rank }, } : member), })); for (const member of usePartyStore.getState().members) updatePartyRuntimePosition(member.id, 0, 0, 0); useCombatStore.getState().registerMob(ENEMY_ID, { name: ENEMY_ID, level: 80, maxHealth: 1_000, xpReward: 0, attacks: [{ ...AREA_ATTACK, target: actorKind === "player" ? "primary" : "nearby-party", }], }); setMobPosition(ENEMY_ID, [2, 0, 0]); useCombatStore.setState((state) => ({ talentRanks: { ...state.talentRanks, [WOW335_DRUID_PREDATORY_INSTINCTS_TALENT_ID]: rank }, mobs: { ...state.mobs, [ENEMY_ID]: { ...state.mobs[ENEMY_ID]!, attackDamage: 100 } }, })); const actorId = actorKind === "player" ? PLAYER_AURA_ENTITY_ID : firstMember.id; if (actorKind === "player") setCatForm(actorId); else { useCombatStore.setState((state) => ({ auras: [...applyAura(state.auras, catFormAura(actorId)).auras] })); } const healthBefore = actorKind === "player" ? useCombatStore.getState().health : usePartyStore.getState().members.find((member) => member.id === actorId)!.health; useCombatStore.getState().damageMob(ENEMY_ID, 1, 1_449); useCombatStore.getState().advanceMobCombat(1_450); useCombatStore.getState().advanceMobCombat(areaHitTime(actorId)); const healthAfter = actorKind === "player" ? useCombatStore.getState().health : usePartyStore.getState().members.find((member) => member.id === actorId)!.health; return healthBefore - healthAfter; } describe("WoW Druid Predatory Instincts integration", () => { beforeEach(() => { clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); useCombatStore.getState().initializeCharacter({ classId: "druid", level: 80, xp: 0 }); usePartyStore.getState().initializeParty("druid-predatory-critical-tests", 1, useCombatStore.getState().health); }); it("applies the Cat Form critical-damage rank to the player and generated companion", () => { registerCriticalTarget(); useCombatStore.setState((state) => ({ talentRanks: { ...state.talentRanks, [WOW335_DRUID_PREDATORY_INSTINCTS_TALENT_ID]: 3 }, })); const playerWithoutCat = criticalDamageForSource(PLAYER_AGGRO_ID, "predatory-player-test"); setCatForm(PLAYER_AURA_ENTITY_ID); const playerWithCat = criticalDamageForSource(PLAYER_AGGRO_ID, "predatory-player-test-cat"); const member = usePartyStore.getState().members[0]!; usePartyStore.setState((state) => ({ members: state.members.map((candidate) => candidate.id === member.id ? { ...candidate, classId: "druid", talentRanks: { ...candidate.talentRanks, [WOW335_DRUID_PREDATORY_INSTINCTS_TALENT_ID]: 3 }, } : candidate), })); const companionWithoutCat = criticalDamageForSource(member.id, "predatory-companion-test"); setCatForm(member.id); const companionWithCat = criticalDamageForSource(member.id, "predatory-companion-test-cat"); expect(playerWithoutCat).toBe(200); expect(playerWithCat).toBe(215); expect(companionWithoutCat).toBe(200); expect(companionWithCat).toBe(215); }); it.each(["player", "companion"] as const)("reduces only area attack damage for a Cat Form %s", (actorKind) => { const withoutTalent = areaDamageForActor(actorKind, 0); const withTalent = areaDamageForActor(actorKind, 3); expect(withoutTalent).toBeGreaterThan(0); expect(withTalent).toBe(Math.round(withoutTalent * 0.9)); }); });