forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
1 parent
39458d5de6
commit
6cc1b9217f
203 files changed
+133929
-31271
No files matched your search
@@ -0,0 +1,133 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
abilitiesForClass,
|
||||
abilityAtLevel,
|
||||
learnedAbilityForBuild,
|
||||
type AbilityDefinition,
|
||||
} from "./abilityCatalog";
|
||||
import { useCombatStore } from "./combatStore";
|
||||
import {
|
||||
evaluateCombatRotation,
|
||||
nativeEarlyPriestHealReserveBlocksAbility,
|
||||
type RotationContext,
|
||||
} from "./combatRotation";
|
||||
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
||||
import { clearPartyRuntimeRegistry } from "./partyRuntimeRegistry";
|
||||
import { healingAbilityCanAidOthers } from "./healingAbilityRules";
|
||||
import { BenchmarkController } from "./benchmark/benchmarkController";
|
||||
import { BENCHMARK_SCENARIOS } from "./benchmark/benchmarkScenarios";
|
||||
|
||||
const urgentHeal = abilityAtLevel(abilitiesForClass("rom-priest").find((ability) => ability.name === "Urgent Heal")!, 1);
|
||||
const risingTide = abilityAtLevel(abilitiesForClass("rom-priest").find((ability) => ability.name === "Rising Tide")!, 1);
|
||||
|
||||
function manaCost(ability: AbilityDefinition): number {
|
||||
return (ability.costs?.length ? ability.costs : [ability.cost])
|
||||
.filter((cost) => cost.resource === "mana")
|
||||
.reduce((total, cost) => total + cost.amount, 0);
|
||||
}
|
||||
|
||||
function rotationContext(overrides: Partial<RotationContext> = {}): RotationContext {
|
||||
const mana = overrides.resources?.mana ?? 36;
|
||||
return {
|
||||
now: 1_000,
|
||||
classId: "rom-priest",
|
||||
statRuleset: "rom",
|
||||
role: "healer",
|
||||
level: 1,
|
||||
health: 100,
|
||||
maxHealth: 100,
|
||||
selfId: "priest",
|
||||
resources: { mana },
|
||||
maximumResources: { mana: 123 },
|
||||
primaryResource: "mana",
|
||||
comboPoints: 0,
|
||||
comboTargetId: null,
|
||||
runes: [],
|
||||
abilities: [risingTide],
|
||||
learnedAbilities: [urgentHeal, risingTide],
|
||||
manaCostForAbility: manaCost,
|
||||
canUseAbility: (ability) => manaCost(ability) <= mana,
|
||||
hostileTarget: { id: "enemy", health: 1_000, maxHealth: 1_000 },
|
||||
hostileCount: 1,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("native early RoM Priest healer Mana reserve", () => {
|
||||
it("keeps the learned direct-heal cost at 25 Mana across the 36/37 spend boundary", () => {
|
||||
expect(manaCost(urgentHeal)).toBe(25);
|
||||
expect(manaCost(risingTide)).toBe(12);
|
||||
const learnedDirectHealCosts = abilitiesForClass("rom-priest")
|
||||
.map((ability) => learnedAbilityForBuild(ability, 1, {}))
|
||||
.filter((ability): ability is AbilityDefinition => ability !== null)
|
||||
.filter((ability) => !ability.passive
|
||||
&& (ability.target === "friendly" || ability.target === "any")
|
||||
&& healingAbilityCanAidOthers(ability)
|
||||
&& ability.effects.some((effect) => effect.kind === "heal"))
|
||||
.map((ability) => `${ability.name}:${manaCost(ability)}`)
|
||||
.sort();
|
||||
expect(learnedDirectHealCosts).toEqual(["Urgent Heal:25"]);
|
||||
|
||||
const at36 = rotationContext();
|
||||
expect(at36.canUseAbility?.(risingTide)).toBe(true);
|
||||
expect(evaluateCombatRotation(at36)).toBeNull();
|
||||
|
||||
const at37 = rotationContext({ resources: { mana: 37 } });
|
||||
expect(evaluateCombatRotation(at37)?.ability.id).toBe(risingTide.id);
|
||||
});
|
||||
|
||||
it("uses resolved Mana costs and leaves Urgent Heal's own target checks intact", () => {
|
||||
const expensiveResolvedStrike = rotationContext({
|
||||
resources: { mana: 38 },
|
||||
manaCostForAbility: (ability) => manaCost(ability) + (ability.id === risingTide.id ? 2 : 0),
|
||||
canUseAbility: () => true,
|
||||
});
|
||||
expect(evaluateCombatRotation(expensiveResolvedStrike)).toBeNull();
|
||||
|
||||
const heal = evaluateCombatRotation(rotationContext({
|
||||
resources: { mana: 25 },
|
||||
abilities: [urgentHeal],
|
||||
friendlyTargets: [{ id: "ally", health: 20, maxHealth: 100, role: "damage" }],
|
||||
}));
|
||||
expect(heal).toMatchObject({ ability: { id: urgentHeal.id }, targetId: "ally" });
|
||||
|
||||
const invalidHealTarget = evaluateCombatRotation(rotationContext({
|
||||
resources: { mana: 25 },
|
||||
abilities: [urgentHeal],
|
||||
friendlyTargets: [{ id: "fallen-ally", health: 0, maxHealth: 100, fallen: true }],
|
||||
}));
|
||||
expect(invalidHealTarget).toBeNull();
|
||||
});
|
||||
|
||||
it("applies only to sub-10 RoM Priests acting as healers", () => {
|
||||
const base = rotationContext();
|
||||
expect(nativeEarlyPriestHealReserveBlocksAbility(risingTide, base)).toBe(true);
|
||||
expect(nativeEarlyPriestHealReserveBlocksAbility(risingTide, { ...base, level: 10 })).toBe(false);
|
||||
expect(nativeEarlyPriestHealReserveBlocksAbility(risingTide, { ...base, role: "damage" })).toBe(false);
|
||||
expect(nativeEarlyPriestHealReserveBlocksAbility(risingTide, { ...base, statRuleset: "wow" })).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps the real level-one benchmark actor from spending its 36-Mana heal reserve", () => {
|
||||
const combat = useCombatStore.getState();
|
||||
combat.initializeCharacter({ classId: "rom-priest", statRuleset: "rom", role: "healer", level: 1, raceId: "rom-human" });
|
||||
combat.setPlayerPosition([0, 0, 0]);
|
||||
combat.registerMob("reserve-target", { name: "Reserve target", level: 1, maxHealth: 100_000, xpReward: 0 });
|
||||
setMobPosition("reserve-target", [10, 0, 0]);
|
||||
combat.selectMob("reserve-target");
|
||||
useCombatStore.setState((state) => ({
|
||||
resource: 36,
|
||||
resourcePools: { ...state.resourcePools, mana: 36 },
|
||||
}));
|
||||
|
||||
const controller = new BenchmarkController();
|
||||
const scenario = { ...BENCHMARK_SCENARIOS[0], hostileTargets: 1 };
|
||||
const at36 = controller.act(100, "healer", scenario, false);
|
||||
expect(at36.cast).toBe(false);
|
||||
expect(useCombatStore.getState().resourcePools.mana).toBe(36);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
});
|
||||
Reference in new issue
Block a user