Files
healer-man/src/game/wow335HunterLockAndLoadParty.test.ts
T
2026-09-30 09:57:13 -04:00

136 lines
5.8 KiB
TypeScript

import { beforeEach, describe, expect, it } from "vitest";
import { abilitiesForClass, preloadAbilityCatalogsForClasses } from "./abilityCatalog";
import { applyAura } from "./combatAuras";
import { useCombatStore } from "./combatStore";
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
import {
clearPartyRuntimeRegistry,
registerPartyRuntimePosition,
updatePartyRuntimeTargetVisibility,
} from "./partyRuntimeRegistry";
import { advanceDungeonPartyCombat, resetPartyRuntimeClock } from "./partyRuntime";
import { createConfiguredPartyMember, usePartyStore } from "./partyStore";
import { WOW335_HUNTER_LOCK_AND_LOAD_READY_TAG } from "./wow335HunterRuntime";
import { partyAbilityCosts, partyAbilityIsReady } from "./partyAbilityAi";
describe("WoW Hunter Lock and Load companion runtime", () => {
beforeEach(async () => {
clearMobRuntimeRegistry();
clearPartyRuntimeRegistry();
resetPartyRuntimeClock();
await preloadAbilityCatalogsForClasses(["hunter"]);
useCombatStore.getState().initializeCharacter({ classId: "mage", role: "damage", level: 80 });
usePartyStore.getState().initializeParty(
"hunter-lock-and-load-party",
80,
useCombatStore.getState().health,
"damage",
1,
);
});
it("lets a companion spend two free Arcane Shots through its cooldown and mana checks", () => {
const arcaneShot = abilitiesForClass("hunter").find((ability) => ability.name === "Arcane Shot")!;
const hunterAbilities = abilitiesForClass("hunter");
const hunterBase = createConfiguredPartyMember({
name: "Survival Hunter",
classId: "hunter",
specialization: "Survival",
specializationId: "hunter:survival",
talentTreeId: "hunter-survival",
role: "damage",
talentRanks: { "talent-1306": 3 },
}, 80, 0, "hunter-lock-and-load-party");
const hunter = {
...hunterBase,
resource: 0,
resourcePools: { ...hunterBase.resourcePools, mana: 0 },
lastResourceSpentAt: {},
cooldowns: Object.fromEntries(hunterAbilities.map((ability) => [ability.id, 60_000])),
globalCooldownEndsAt: 0,
nextActionAt: 0,
status: "ready" as const,
};
usePartyStore.setState({ members: [hunter], command: "attack" });
registerPartyRuntimePosition(hunter.id, [0, 0, 0]);
useCombatStore.getState().synchronizeActors();
useCombatStore.getState().registerMob("hunter-lock-and-load-party-target", {
name: "Training Target", maxHealth: 100_000, xpReward: 0,
});
setMobPosition("hunter-lock-and-load-party-target", [0, 0, 12]);
useCombatStore.getState().damageMob("hunter-lock-and-load-party-target", 1, 500);
updatePartyRuntimeTargetVisibility(hunter.id, "hunter-lock-and-load-party-target", true);
useCombatStore.setState((state) => ({
auras: applyAura(state.auras, {
definition: {
id: "wow335:hunter:lock-and-load-ready",
name: "Lock and Load",
disposition: "buff",
durationMs: 15_000,
maxStacks: 2,
initialStacks: 2,
stackBehavior: "replace",
utilityTags: [WOW335_HUNTER_LOCK_AND_LOAD_READY_TAG],
},
sourceId: hunter.id,
targetId: hunter.id,
now: 1_000,
}).auras,
}));
expect(partyAbilityIsReady(hunter, arcaneShot, 1_000)).toBe(true);
expect(partyAbilityCosts(hunter, arcaneShot, 1_000)).toEqual(expect.arrayContaining([
expect.objectContaining({ resource: "mana", amount: 0 }),
]));
advanceDungeonPartyCombat(1_000);
expect(usePartyStore.getState().members[0]?.cooldowns[arcaneShot.id]).toBe(0);
expect(usePartyStore.getState().members[0]?.lastResourceSpentAt.mana).toBe(0);
expect(useCombatStore.getState().auras.find((aura) => (
aura.targetId === hunter.id && aura.definition.utilityTags?.includes(WOW335_HUNTER_LOCK_AND_LOAD_READY_TAG)
))?.stacks).toBe(1);
advanceDungeonPartyCombat(3_000);
expect(usePartyStore.getState().members[0]?.cooldowns[arcaneShot.id]).toBe(0);
expect(usePartyStore.getState().members[0]?.lastResourceSpentAt.mana).toBe(0);
expect(useCombatStore.getState().auras.some((aura) => (
aura.targetId === hunter.id && aura.definition.utilityTags?.includes(WOW335_HUNTER_LOCK_AND_LOAD_READY_TAG)
))).toBe(false);
});
it("creates a companion Freezing Arrow trap that procs the companion's talent", () => {
const hunter = createConfiguredPartyMember({
name: "Survival Hunter",
classId: "hunter",
specialization: "Survival",
specializationId: "hunter:survival",
talentTreeId: "hunter-survival",
role: "damage",
talentRanks: { "talent-1306": 3 },
}, 80, 0, "hunter-freezing-arrow-party");
usePartyStore.setState({ members: [hunter] });
registerPartyRuntimePosition(hunter.id, [0, 0, 0]);
useCombatStore.getState().synchronizeActors();
useCombatStore.getState().registerMob("hunter-freezing-arrow-party-target", {
name: "Training Target", maxHealth: 100_000, xpReward: 0,
});
setMobPosition("hunter-freezing-arrow-party-target", [0, 0, 12]);
const freezingArrow = abilitiesForClass("hunter").find((ability) => ability.name === "Freezing Arrow")!;
const areaId = useCombatStore.getState().createActorAreaEffect(
hunter.id, freezingArrow, [0, 0, 0], 1_000,
);
expect(areaId).toBeTruthy();
setMobPosition("hunter-freezing-arrow-party-target", [0, 0, 1]);
useCombatStore.getState().tick(1, 2_000);
expect(useCombatStore.getState().mobs["hunter-freezing-arrow-party-target"]?.statuses)
.toEqual(expect.arrayContaining([expect.objectContaining({ kind: "freeze", sourceActorId: hunter.id })]));
expect(useCombatStore.getState().auras).toEqual(expect.arrayContaining([
expect.objectContaining({ targetId: hunter.id, stacks: 2, expiresAt: 17_000 }),
]));
});
});