import { afterEach, beforeAll, describe, expect, it } from "vitest"; import { preloadAbilityCatalogsForClasses } from "./abilityCatalog"; import { PLAYER_AGGRO_ID } from "./aggro"; import { applyAura } from "./combatAuras"; import { useCombatStore } from "./combatStore"; import { registerCombatSpatialProvider, resetCombatSpatialProvider } from "./combatSpatial"; import { clearMobRuntimeRegistry, registerMobRuntime } from "./mobRuntimeRegistry"; import { clearPartyRuntimeRegistry } from "./partyRuntimeRegistry"; import { usePartyStore } from "./partyStore"; import { ROM_SCOUT_AUTO_SHOT_ABILITY_ID } from "./romScoutAutoShot"; const TARGET = "pacify-weapon-swing-fixture"; const ORIGIN = [0, 0, 0] as const; const FRONT = [0, 0, -2] as const; function prepare(pacified: boolean, targetId = PLAYER_AGGRO_ID) { useCombatStore.getState().initializeCharacter({ classId: "warrior", level: 30 }); usePartyStore.setState({ members: [] }); useCombatStore.getState().setPlayerPosition(ORIGIN); useCombatStore.getState().registerMob(TARGET, { name: "Synthetic autoattack target", level: 1, maxHealth: 1_000_000, xpReward: 0 }); registerMobRuntime(TARGET, FRONT, 0); registerCombatSpatialProvider({ actor: (id) => id === TARGET ? { id, position: FRONT, yaw: 0 } : { id, position: ORIGIN, yaw: Math.PI }, hasLineOfSight: () => true, projectGroundPoint: (position) => position }); useCombatStore.setState((state) => ({ autoAttackTargetId: TARGET, nextAutoAttackAt: 1_000, nextOffhandAutoAttackAt: 1_000, auras: pacified ? [...applyAura(state.auras, { definition: { id: "synthetic-source-pacify", name: "Accepted pacify aura fixture", disposition: "buff", durationMs: 2_000, utilityTags: ["pacified"], }, sourceId: targetId, targetId, now: 1_000 }).auras] : state.auras, })); } function playerSwings() { return useCombatStore.getState().combatEvents.filter((event) => event.sourceActorId === PLAYER_AGGRO_ID && event.targetActorId === TARGET && event.attackHand === "mainhand"); } describe("accepted pacify aura suppresses player weapon autos", () => { beforeAll(async () => { await preloadAbilityCatalogsForClasses(["warrior", "rom-scout", "rom-rogue"]); }); afterEach(() => { clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); resetCombatSpatialProvider(); }); it("holds due swings through the whole aura window and resumes once at expiry", () => { prepare(true); for (const now of [1_000, 1_500, 2_999]) useCombatStore.getState().tick(0.05, now); expect(playerSwings()).toHaveLength(0); expect(useCombatStore.getState().mobs[TARGET]!.health).toBe(1_000_000); expect(useCombatStore.getState().autoAttackTargetId).toBe(TARGET); useCombatStore.getState().tick(0.05, 3_000); expect(playerSwings()).toHaveLength(1); expect(useCombatStore.getState().nextAutoAttackAt).toBeGreaterThan(3_000); useCombatStore.getState().tick(0.05, 3_001); expect(playerSwings()).toHaveLength(1); }); it("does not borrow another actor's pacify aura", () => { prepare(true, "another-actor"); useCombatStore.getState().tick(0.05, 1_000); expect(playerSwings()).toHaveLength(1); }); it("also holds the owned Scout ranged-auto scheduler until pacify expires", () => { prepare(false); useCombatStore.getState().initializeCharacter({ classId: "rom-scout", secondaryClassId: "rom-rogue", raceId: "rom-human", statRuleset: "rom", level: 30, inventory: [], equipment: {} }); useCombatStore.getState().registerMob(TARGET, { name: "Synthetic ranged auto target", level: 1, maxHealth: 1_000_000, xpReward: 0 }); useCombatStore.getState().setPlayerPosition(ORIGIN); // Synthetic already-selected ranged mode; the real native scheduler and // automatic Training Gear profile resolve subsequent weapon results. useCombatStore.setState((state) => ({ autoShotTargetId: TARGET, nextAutoShotAt: 1_000, auras: [...applyAura(state.auras, { definition: { id: "synthetic-ranged-pacify", name: "Accepted ranged pacify fixture", disposition: "buff", durationMs: 2_000, utilityTags: ["pacified"], }, sourceId: PLAYER_AGGRO_ID, targetId: PLAYER_AGGRO_ID, now: 1_000 }).auras], })); const rangedEvents = () => useCombatStore.getState().combatEvents.filter((event) => event.sourceActorId === PLAYER_AGGRO_ID && event.abilityId === ROM_SCOUT_AUTO_SHOT_ABILITY_ID && event.attackHand === "ranged"); for (const now of [1_000, 1_500, 2_999]) useCombatStore.getState().tick(0.05, now); expect(rangedEvents()).toHaveLength(0); useCombatStore.getState().tick(0.05, 3_000); expect(rangedEvents()).toHaveLength(1); useCombatStore.getState().tick(0.05, 3_001); expect(rangedEvents()).toHaveLength(1); }); });