forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
1 parent
9d7e0e2f9e
commit
35dfdaed61
94 files changed
+17034
-418
No files matched your search
@@ -0,0 +1,188 @@
|
||||
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { preloadAbilityCatalogsForClasses } from "./abilityCatalog";
|
||||
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
||||
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
|
||||
import { PLAYER_AURA_ENTITY_ID, useCombatStore } from "./combatStore";
|
||||
import { installCombatClock } from "./combatClock";
|
||||
import { createConfiguredPartyMember, usePartyStore } from "./partyStore";
|
||||
import { companionResourceMaximum } from "./combatActorAuthority";
|
||||
import { romDruidPeriodicHealAmount } from "./romDruidOverrides";
|
||||
import { ROM_ALL_ABILITIES } from "./romAbilityCatalog";
|
||||
|
||||
const TARGET_ID = "rom-druid-runtime-target";
|
||||
|
||||
function ability(spellId: number) {
|
||||
const result = ROM_ALL_ABILITIES.find((candidate) => candidate.dbcSpellId === spellId);
|
||||
expect(result, `RoM Druid spell ${spellId}`).toBeDefined();
|
||||
return result!;
|
||||
}
|
||||
|
||||
function finishCast(now: number) {
|
||||
const cast = useCombatStore.getState().activeCast;
|
||||
if (cast) useCombatStore.getState().tick((cast.completesAt - now) / 1_000 + 0.01, cast.completesAt);
|
||||
}
|
||||
|
||||
function prepareDruid(secondaryClassId?: "rom-warden" | "rom-scout") {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-druid",
|
||||
...(secondaryClassId ? { secondaryClassId } : {}),
|
||||
raceId: "rom-elf",
|
||||
level: 80,
|
||||
});
|
||||
useCombatStore.setState((state) => ({
|
||||
resource: 1_000,
|
||||
maxResource: 1_000,
|
||||
resourcePools: { ...state.resourcePools, mana: 1_000, "nature-power": 0 },
|
||||
maxResourcePools: { ...state.maxResourcePools, mana: 1_000 },
|
||||
}));
|
||||
}
|
||||
|
||||
describe("RuneWaker Druid runtime", () => {
|
||||
beforeAll(async () => {
|
||||
await preloadAbilityCatalogsForClasses(["rom-druid"]);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
useCombatStore.setState({ playerPosition: [0, 0, 0] });
|
||||
});
|
||||
|
||||
it("casts Spirit Guidance for one Nature Power and applies the source three-second Charm", () => {
|
||||
prepareDruid();
|
||||
useCombatStore.getState().registerMob(TARGET_ID, { name: "Guidance Target", maxHealth: 10_000 });
|
||||
setMobPosition(TARGET_ID, [5, 0, 0]);
|
||||
const spiritGuidance = ability(493536);
|
||||
|
||||
expect(spiritGuidance.costs).toContainEqual({ resource: "nature-power", amount: 1 });
|
||||
expect(useCombatStore.getState().castAbility(spiritGuidance.id, { targetActorId: TARGET_ID, now: 1_000 }))
|
||||
.toMatchObject({ ok: false, reason: "not-enough-resource" });
|
||||
useCombatStore.setState((state) => ({ resourcePools: { ...state.resourcePools, "nature-power": 1 } }));
|
||||
expect(useCombatStore.getState().castAbility(spiritGuidance.id, { targetActorId: TARGET_ID, now: 3_000 }).ok).toBe(true);
|
||||
const activeCast = useCombatStore.getState().activeCast;
|
||||
expect(activeCast?.abilityId).toBe(spiritGuidance.id);
|
||||
finishCast(3_000);
|
||||
expect(useCombatStore.getState().resourcePools["nature-power"]).toBe(0);
|
||||
expect(useCombatStore.getState().mobs[TARGET_ID]!.statuses).toContainEqual(expect.objectContaining({
|
||||
kind: "charm",
|
||||
endsAt: activeCast!.completesAt + 3_000,
|
||||
}));
|
||||
// The combat model supports the Charm control effect; it has no faction/allegiance conversion state.
|
||||
});
|
||||
|
||||
it("applies Unity with Mother Earth as a 15-second healing and guaranteed spell-crit aura", () => {
|
||||
prepareDruid();
|
||||
const unity = ability(493548);
|
||||
expect(unity.target).toBe("self");
|
||||
expect(useCombatStore.getState().castAbility(unity.id, { now: 1_000 }).ok).toBe(true);
|
||||
const aura = useCombatStore.getState().auras.find((entry) => (
|
||||
entry.targetId === PLAYER_AURA_ENTITY_ID
|
||||
&& entry.definition.utilityTags?.includes("rom-druid-unity-with-mother-earth")
|
||||
));
|
||||
expect(aura?.expiresAt).toBe(16_000);
|
||||
expect(aura?.definition.modifiers).toContainEqual({
|
||||
kind: "stat", stat: "magical-critical-strike", operation: "flat", value: 100,
|
||||
});
|
||||
expect(aura?.definition.modifiers).toContainEqual({
|
||||
kind: "healing", direction: "done", operation: "percent", value: 0.1,
|
||||
});
|
||||
|
||||
useCombatStore.getState().registerMob(TARGET_ID, { name: "Unity Target", maxHealth: 10_000 });
|
||||
setMobPosition(TARGET_ID, [5, 0, 0]);
|
||||
const arrow = ability(493538);
|
||||
expect(useCombatStore.getState().castAbility(arrow.id, { targetActorId: TARGET_ID, now: 3_000 }).ok).toBe(true);
|
||||
finishCast(3_000);
|
||||
expect(useCombatStore.getState().lastCombatResult?.critical).toBe(true);
|
||||
});
|
||||
|
||||
it("prevents movement during Unity and restores movement when its 15-second aura expires", () => {
|
||||
let now = 1_000;
|
||||
const restoreClock = installCombatClock(() => now);
|
||||
try {
|
||||
prepareDruid();
|
||||
const unity = ability(493548);
|
||||
expect(useCombatStore.getState().castAbility(unity.id, { now }).ok).toBe(true);
|
||||
useCombatStore.getState().setPlayerPosition([6, 0, 0]);
|
||||
expect(useCombatStore.getState().playerPosition).toEqual([0, 0, 0]);
|
||||
|
||||
now = 16_001;
|
||||
useCombatStore.getState().setPlayerPosition([6, 0, 0]);
|
||||
expect(useCombatStore.getState().playerPosition).toEqual([6, 0, 0]);
|
||||
} finally {
|
||||
restoreClock();
|
||||
}
|
||||
});
|
||||
|
||||
it("raises Nature Power's maximum to 15 only after learning Brilliance of Nature", () => {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-druid", secondaryClassId: "rom-scout", raceId: "rom-elf", level: 44,
|
||||
});
|
||||
expect(useCombatStore.getState().maxResourcePools["nature-power"]).toBe(10);
|
||||
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-druid", secondaryClassId: "rom-scout", raceId: "rom-elf", level: 45,
|
||||
});
|
||||
expect(useCombatStore.getState().abilities.some((entry) => entry.dbcSpellId === 494931 && entry.passive)).toBe(true);
|
||||
expect(useCombatStore.getState().maxResourcePools["nature-power"]).toBe(15);
|
||||
|
||||
const companion = createConfiguredPartyMember({
|
||||
name: "Druid Companion",
|
||||
classId: "rom-druid",
|
||||
secondaryClassId: "rom-scout",
|
||||
raceId: "rom-elf",
|
||||
specialization: "Primary",
|
||||
role: "healer",
|
||||
}, 45, 0, "rom-druid-brilliance-cap");
|
||||
expect(companionResourceMaximum(companion, "nature-power")).toBe(15);
|
||||
});
|
||||
|
||||
it("adds exactly one percent of each healing target's max health to the Spirit of Life tick", () => {
|
||||
expect(romDruidPeriodicHealAmount(500, 10_000, 0.01)).toBe(600);
|
||||
expect(romDruidPeriodicHealAmount(500, 5_000, 0.01)).toBe(550);
|
||||
});
|
||||
|
||||
it("gives Spirit of Life a 40-second, four-second party heal within its source radius", () => {
|
||||
prepareDruid("rom-warden");
|
||||
usePartyStore.getState().initializeParty("rom-druid-spirit-of-life", 80, useCombatStore.getState().health, "healer", 5, "rom");
|
||||
const [near, far] = usePartyStore.getState().members;
|
||||
expect(near).toBeDefined();
|
||||
expect(far).toBeDefined();
|
||||
useCombatStore.setState({ health: 1 });
|
||||
usePartyStore.setState((state) => ({ members: state.members.map((member) => ({ ...member, health: 1 })) }));
|
||||
registerPartyRuntimePosition(near!.id, [5, 0, 0]);
|
||||
registerPartyRuntimePosition(far!.id, [12, 0, 0]);
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
const spirit = ability(494938);
|
||||
const hot = spirit.effects.find((effect) => effect.kind === "hot");
|
||||
expect(hot).toMatchObject({
|
||||
ticks: 10, intervalMs: 4_000, scope: "party", radius: 7, center: "caster", percentMaxHealth: 0.01,
|
||||
});
|
||||
expect(spirit.effects.some((effect) => effect.kind === "summon" || effect.kind === "heal")).toBe(false);
|
||||
const beforePlayer = useCombatStore.getState().health;
|
||||
const beforeNear = usePartyStore.getState().members.find((member) => member.id === near!.id)!.health;
|
||||
const beforeFar = usePartyStore.getState().members.find((member) => member.id === far!.id)!.health;
|
||||
expect(useCombatStore.getState().castAbility(spirit.id, { now: 1_000 }).ok).toBe(true);
|
||||
expect(useCombatStore.getState().summons.some((summon) => summon.sourceAbilityId === spirit.id)).toBe(false);
|
||||
useCombatStore.getState().tick(4.1, 5_100);
|
||||
|
||||
expect(useCombatStore.getState().health).toBeGreaterThan(beforePlayer);
|
||||
expect(usePartyStore.getState().members.find((member) => member.id === near!.id)!.health).toBeGreaterThan(beforeNear);
|
||||
expect(usePartyStore.getState().members.find((member) => member.id === far!.id)!.health).toBe(beforeFar);
|
||||
});
|
||||
|
||||
it("casts Savage Blessing on the selected friendly target with beneficial hit buffs", () => {
|
||||
prepareDruid();
|
||||
usePartyStore.getState().initializeParty("rom-druid-savage-blessing", 80, useCombatStore.getState().health, "healer", 5, "rom");
|
||||
const ally = usePartyStore.getState().members[0]!;
|
||||
registerPartyRuntimePosition(ally.id, [4, 0, 0]);
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
const blessing = ability(493531);
|
||||
|
||||
expect(blessing.target).toBe("friendly");
|
||||
expect(useCombatStore.getState().castAbility(blessing.id, { targetActorId: ally.id, now: 1_000 }).ok).toBe(true);
|
||||
finishCast(1_000);
|
||||
const buffs = useCombatStore.getState().auras.filter((aura) => aura.definition.name.startsWith("Savage Blessing"));
|
||||
expect(buffs.length).toBeGreaterThanOrEqual(2);
|
||||
expect(buffs.every((aura) => aura.targetId === ally.id && aura.definition.disposition === "buff")).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user