136 lines
5.8 KiB
TypeScript
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 }),
|
|
]));
|
|
});
|
|
});
|