Files
healer-man/src/game/romDruidMageInspiration.integration.test.ts
T
2026-10-02 10:38:12 -04:00

189 lines
7.9 KiB
TypeScript

import { beforeEach, describe, expect, it } from "vitest";
import { PLAYER_AGGRO_ID } from "./aggro";
import { useCombatStore } from "./combatStore";
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
import { advanceDungeonPartyCombat, resetPartyRuntimeClock } from "./partyRuntime";
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
import { ROM_ALL_ABILITIES } from "./romAbilityCatalog";
import {
ROM_DRUID_MAGE_INSPIRATION_AURA_TAG,
romDruidMageInspirationApplySpendStack,
romDruidMageInspirationHastePercent,
} from "./romDruidMageInspirationRuntime";
import { createConfiguredPartyMember, usePartyStore } from "./partyStore";
const EARTH_CHAIN_ID = "rom-druid-rom-mage-elite-15";
const earthChain = ROM_ALL_ABILITIES.find((ability) => ability.id === EARTH_CHAIN_ID);
function inspirationAura(targetId: string) {
return useCombatStore.getState().auras.find((aura) => aura.targetId === targetId
&& aura.definition.utilityTags?.includes(ROM_DRUID_MAGE_INSPIRATION_AURA_TAG));
}
function priest(name: string, seed: string, index: number) {
return createConfiguredPartyMember({
name,
classId: "rom-priest",
specialization: "Primary",
role: "damage",
}, 45, index, seed);
}
describe("CoA Druid/Mage Inspiration player and companion integration", () => {
beforeEach(() => {
resetPartyRuntimeClock();
clearMobRuntimeRegistry();
clearPartyRuntimeRegistry();
useCombatStore.getState().initializeCharacter({
classId: "rom-priest",
raceId: "rom-human",
level: 45,
role: "healer",
});
usePartyStore.getState().initializeParty(
"inspiration-party-test", 45, useCombatStore.getState().health, "healer", 4, "rom",
);
});
it("awards one nearby-party stack for a player's completed Nature Power spend and applies cast haste", () => {
if (!earthChain) throw new Error("Missing generated Druid/Mage Earth Chain row");
useCombatStore.getState().initializeCharacter({
classId: "rom-druid",
secondaryClassId: "rom-mage",
raceId: "rom-elf",
level: 45,
role: "damage",
});
useCombatStore.getState().setPlayerPosition([0, 0, 0]);
const seed = "inspiration-player-spend";
const edge = priest("Range Edge", seed, 0);
const outside = priest("Outside Range", seed, 1);
const fallen = { ...priest("Fallen Ally", seed, 2), health: 0 };
usePartyStore.setState({ members: [edge, outside, fallen] });
registerPartyRuntimePosition(edge.id, [3, 4, 0]);
registerPartyRuntimePosition(outside.id, [5.01, 0, 0]);
registerPartyRuntimePosition(fallen.id, [1, 0, 0]);
useCombatStore.getState().synchronizeActors();
useCombatStore.setState((state) => ({
resourcePools: { ...state.resourcePools, "nature-power": 1 },
maxResourcePools: { ...state.maxResourcePools, "nature-power": 10 },
}));
const targetId = "inspiration-player-target";
useCombatStore.getState().registerMob(targetId, { name: "Target", level: 1, maxHealth: 100_000, xpReward: 0 });
setMobPosition(targetId, [2, 0, 0]);
useCombatStore.getState().selectMob(targetId);
expect(useCombatStore.getState().castAbility(EARTH_CHAIN_ID, {
origin: [0, 0, 0], targetActorId: targetId, now: 1_000,
}).ok).toBe(true);
const unbuffedCastDuration = useCombatStore.getState().activeCast?.originalDurationMs ?? 0;
expect(unbuffedCastDuration).toBeGreaterThan(0);
useCombatStore.getState().tick(2, 3_000);
expect(useCombatStore.getState().resourcePools["nature-power"]).toBe(0);
expect(inspirationAura(PLAYER_AGGRO_ID)).toMatchObject({ stacks: 1, expiresAt: 13_000 });
expect(inspirationAura(edge.id)?.stacks).toBe(1);
expect(inspirationAura(outside.id)).toBeUndefined();
expect(inspirationAura(fallen.id)).toBeUndefined();
expect(useCombatStore.getState().castAbility(EARTH_CHAIN_ID, {
origin: [0, 0, 0], targetActorId: targetId, now: 7_000,
}).ok).toBe(true);
expect(useCombatStore.getState().activeCast?.originalDurationMs).toBeLessThan(unbuffedCastDuration);
});
it("awards one caster-centered stack when a generated Druid/Mage companion spends Nature Power", () => {
if (!earthChain) throw new Error("Missing generated Druid/Mage Earth Chain row");
const seed = "inspiration-companion-spend";
const caster = createConfiguredPartyMember({
name: "Druid/Mage",
classId: "rom-druid",
secondaryClassId: "rom-mage",
specialization: "Primary",
role: "damage",
}, 45, 0, seed);
const outside = priest("Outside Range", seed, 1);
usePartyStore.setState({
members: [{
...caster,
resourcePools: { ...caster.resourcePools, "nature-power": 1 },
activeCast: {
abilityId: EARTH_CHAIN_ID,
school: "nature",
mode: "cast",
startedAt: 0,
completesAt: 1_000,
originalDurationMs: 1_000,
pushbackHits: 0,
origin: [0, 0, 0],
targetId: "inspiration-companion-target",
},
activeCastTargetId: "inspiration-companion-target",
status: "attacking",
}, outside],
command: "attack",
activeMobId: "inspiration-companion-target",
});
registerPartyRuntimePosition(caster.id, [0, 0, 0]);
registerPartyRuntimePosition(outside.id, [5.01, 0, 0]);
useCombatStore.getState().synchronizeActors();
useCombatStore.getState().registerMob("inspiration-companion-target", {
name: "Target", level: 1, maxHealth: 100_000, xpReward: 0,
});
setMobPosition("inspiration-companion-target", [2, 0, 0]);
useCombatStore.getState().damageMob("inspiration-companion-target", 1, 500);
advanceDungeonPartyCombat(1_000);
expect(usePartyStore.getState().members[0]?.resourcePools["nature-power"]).toBe(0);
expect(inspirationAura(caster.id)?.stacks).toBe(1);
expect(inspirationAura(PLAYER_AGGRO_ID)?.stacks).toBe(1);
expect(inspirationAura(outside.id)).toBeUndefined();
});
it("applies Inspiration spell haste only once to a Mage receiving the party buff", () => {
useCombatStore.getState().initializeCharacter({
classId: "rom-mage",
secondaryClassId: "rom-druid",
raceId: "rom-elf",
level: 45,
role: "damage",
});
useCombatStore.getState().setPlayerPosition([0, 0, 0]);
const targetId = "inspiration-mage-target";
useCombatStore.getState().registerMob(targetId, { name: "Target", level: 1, maxHealth: 100_000, xpReward: 0 });
setMobPosition(targetId, [2, 0, 0]);
useCombatStore.getState().selectMob(targetId);
const ability = useCombatStore.getState().abilities.find((candidate) => candidate.id === "rom-mage-primary-1");
expect(ability?.castTimeMs).toBe(3_000);
expect(useCombatStore.getState().castAbility("rom-mage-primary-1", {
origin: [0, 0, 0], targetActorId: targetId, now: 1_000,
}).ok).toBe(true);
const unbuffedDuration = useCombatStore.getState().activeCast?.originalDurationMs ?? 0;
expect(unbuffedDuration).toBeGreaterThan(0);
useCombatStore.getState().tick(3, 4_000);
useCombatStore.setState((state) => ({
auras: [...romDruidMageInspirationApplySpendStack(
state.auras, "druid-mage-source", [PLAYER_AGGRO_ID], 5_000,
)],
}));
expect(inspirationAura(PLAYER_AGGRO_ID)?.stacks).toBe(1);
expect(romDruidMageInspirationHastePercent(
useCombatStore.getState().auras, PLAYER_AGGRO_ID, 5_000,
)).toBe(2);
expect(useCombatStore.getState().castAbility("rom-mage-primary-1", {
origin: [0, 0, 0], targetActorId: targetId, now: 5_000,
}).ok).toBe(true);
expect(useCombatStore.getState().activeCast?.startedAt).toBe(5_000);
const buffedDuration = useCombatStore.getState().activeCast?.originalDurationMs ?? 0;
const durationReduction = 1 - buffedDuration / unbuffedDuration;
expect(durationReduction).toBeGreaterThan(0.01);
expect(durationReduction).toBeLessThan(0.03);
});
});