import { beforeEach, describe, expect, it } from "vitest"; import { companionActorFromMember } from "./combatActorAuthority"; import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry"; import { commitPartyAbilityUse, partyAbilityCosts } from "./partyAbilityAi"; import { advanceDungeonPartyCombat, resetPartyRuntimeClock } from "./partyRuntime"; import { clearPartyRuntimeRegistry } from "./partyRuntimeRegistry"; import { createConfiguredPartyMember, usePartyStore } from "./partyStore"; import { abilitiesForClass, resourceProfileForClass } from "./abilityCatalog"; import { useCombatStore } from "./combatStore"; describe("Ranger Focus resource runtime", () => { beforeEach(() => { clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); resetPartyRuntimeClock(); }); it("spends and restores the player's displayed primary Focus pool", () => { const profile = resourceProfileForClass("ranger"); expect(profile).toMatchObject({ type: "focus", name: "Focus", maximum: 100, initial: 100, regenerationPerSecond: 10 }); useCombatStore.getState().initializeCharacter({ classId: "ranger", raceId: "human", level: 20 }); const initial = useCombatStore.getState(); expect(initial.resourceName).toBe("Focus"); expect(initial.resource).toBe(initial.resourcePools.focus); expect(initial.maxResource).toBe(initial.maxResourcePools.focus); const powerShot = abilitiesForClass("ranger").find((ability) => ability.dbcSpellId === 501698)!; expect(powerShot.name).toBe("Power Shot"); expect(powerShot.cost.resource).toBe("focus"); useCombatStore.getState().registerMob("ranger-focus-target", { name: "Ranger Focus Target", level: 20, maxHealth: 50_000, }); setMobPosition("ranger-focus-target", [8, 0, 0]); useCombatStore.getState().selectMob("ranger-focus-target"); expect(useCombatStore.getState().castAbility(powerShot.id, undefined, 1_000)).toMatchObject({ ok: true }); const afterCast = useCombatStore.getState(); expect(afterCast.resource).toBeLessThan(initial.resource); expect(afterCast.resourcePools.focus).toBe(afterCast.resource); useCombatStore.getState().tick(1, 2_000); const afterRecovery = useCombatStore.getState(); expect(afterRecovery.resource).toBeGreaterThan(afterCast.resource); expect(afterRecovery.resourcePools.focus).toBe(afterRecovery.resource); }); it("keeps an Archery companion's primary Focus pool aligned through party spending and recovery", () => { useCombatStore.getState().initializeCharacter({ classId: "warrior", raceId: "human", level: 80 }); usePartyStore.getState().initializeParty( "ranger-focus-party", 80, useCombatStore.getState().health, "damage", 1, ); const ranger = createConfiguredPartyMember({ name: "Archery Ranger", classId: "ranger", specialization: "Archery", specializationId: "ranger:archery", talentTreeId: "coa-ranger-54", talentRanks: {}, role: "damage", }, 80, 0, "ranger-focus-party"); expect(ranger.resourceType).toBe("focus"); expect(ranger.resource).toBe(ranger.resourcePools.focus); expect(ranger.maxResource).toBe(ranger.resourceMaximums?.focus); const serratedShot = abilitiesForClass("ranger").find((ability) => ability.dbcSpellId === 500073)!; expect(serratedShot.name).toBe("Serrated Shot"); expect(partyAbilityCosts(ranger, serratedShot, 1_000)).toContainEqual({ resource: "focus", amount: 25 }); const spent = commitPartyAbilityUse(ranger, serratedShot, 1_000); expect(spent).not.toBeNull(); expect(spent!.resource).toBeLessThan(ranger.resource); expect(spent!.resourcePools.focus).toBe(spent!.resource); const actor = companionActorFromMember(spent!); expect(actor.resources.focus).toMatchObject({ current: spent!.resource, maximum: spent!.maxResource, regenerationPerSecond: resourceProfileForClass("ranger").regenerationPerSecond, }); usePartyStore.setState({ members: [spent!], command: "stop", activeMobId: null }); useCombatStore.getState().commitCompanionActors(usePartyStore.getState().members); advanceDungeonPartyCombat(1_000); advanceDungeonPartyCombat(2_000); const recovered = usePartyStore.getState().members[0]!; expect(recovered.resourcePools.focus).toBeGreaterThan(spent!.resourcePools.focus ?? 0); expect(recovered.resource).toBe(recovered.resourcePools.focus); }); });