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,194 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { ROM_ALL_ABILITIES } from "./romAbilityCatalog";
|
||||
import { clearMobRuntimeRegistry, getMobPosition, setMobPosition } from "./mobRuntimeRegistry";
|
||||
import { useCombatStore } from "./combatStore";
|
||||
import { romEffectsForCast, type RomCastMechanics } from "./romCombatMechanics";
|
||||
import { romChampionCastEffects } from "./romChampionOverrides";
|
||||
|
||||
const empty: RomCastMechanics = {
|
||||
sealStacks: 0,
|
||||
bleedCount: 0,
|
||||
charged: false,
|
||||
mainHandDagger: false,
|
||||
casterMaxHealth: 2_000,
|
||||
targetHealthPercent: 1,
|
||||
};
|
||||
|
||||
function sourceAbility(spellId: number) {
|
||||
const ability = ROM_ALL_ABILITIES.find((candidate) => candidate.dbcSpellId === spellId);
|
||||
if (!ability) throw new Error(`Missing RoM source ability ${spellId}`);
|
||||
return ability;
|
||||
}
|
||||
|
||||
function castEffects(spellId: number, context: Partial<RomCastMechanics> = {}) {
|
||||
return romEffectsForCast(sourceAbility(spellId), { ...empty, ...context });
|
||||
}
|
||||
|
||||
describe("RoM Champion source-specific behavior", () => {
|
||||
beforeEach(() => {
|
||||
clearMobRuntimeRegistry();
|
||||
useCombatStore.setState({ playerPosition: [0, 0, 0] });
|
||||
});
|
||||
|
||||
|
||||
it("ends Rune Overload when its Rage drain reaches zero", () => {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "rom-champion", raceId: "rom-dwarf", level: 40 });
|
||||
useCombatStore.setState((state) => ({
|
||||
resource: 30,
|
||||
resourcePools: { ...state.resourcePools, rage: 30 },
|
||||
}));
|
||||
|
||||
expect(useCombatStore.getState().castAbility("rom-champion-primary-17", undefined, 1_000))
|
||||
.toMatchObject({ ok: true });
|
||||
expect(useCombatStore.getState().auras.some((aura) => (
|
||||
aura.definition.utilityTags?.includes("rom-champion-rune-overload")
|
||||
))).toBe(true);
|
||||
|
||||
expect(useCombatStore.getState().resourcePools.rage).toBe(15);
|
||||
useCombatStore.getState().tick(1, 2_000);
|
||||
|
||||
expect(useCombatStore.getState().resourcePools.rage).toBe(0);
|
||||
expect(useCombatStore.getState().auras.some((aura) => (
|
||||
aura.definition.utilityTags?.includes("rom-champion-rune-overload")
|
||||
))).toBe(false);
|
||||
});
|
||||
|
||||
it("applies Fearless Blow's bonus only below 30% and gives its Rage rider to the caster", () => {
|
||||
const atThreshold = castEffects(498531, { targetHealthPercent: 0.3 });
|
||||
const belowThreshold = castEffects(498531, { targetHealthPercent: 0.299 });
|
||||
const baseDamage = atThreshold.find((effect) => effect.kind === "damage");
|
||||
const boostedDamage = belowThreshold.find((effect) => effect.kind === "damage");
|
||||
expect(baseDamage?.kind).toBe("damage");
|
||||
expect(boostedDamage?.kind).toBe("damage");
|
||||
if (baseDamage?.kind === "damage" && boostedDamage?.kind === "damage") {
|
||||
expect(boostedDamage.coefficient).toBeCloseTo(baseDamage.coefficient * 1.3);
|
||||
expect(boostedDamage.basePoints ?? 0).toBeCloseTo((baseDamage.basePoints ?? 0) * 1.3);
|
||||
}
|
||||
expect(atThreshold.some((effect) => effect.kind === "apply-aura" && effect.aura.id.includes("621179"))).toBe(false);
|
||||
const rage = belowThreshold.find((effect) => effect.kind === "apply-aura" && effect.aura.id.includes("621183"));
|
||||
expect(rage?.kind).toBe("apply-aura");
|
||||
if (rage?.kind === "apply-aura") expect(rage.recipient).toBe("caster");
|
||||
});
|
||||
|
||||
it("adds Rune Draw's source-described pull and moves the selected target toward the Champion", () => {
|
||||
const runeDraw = sourceAbility(498564);
|
||||
expect(runeDraw.effects.filter((effect) => effect.kind === "pull")).toHaveLength(1);
|
||||
expect(runeDraw.effects.some((effect) => effect.kind === "interrupt")).toBe(true);
|
||||
expect(runeDraw.effects.some((effect) => effect.kind === "taunt")).toBe(true);
|
||||
|
||||
useCombatStore.getState().initializeCharacter({ classId: "rom-champion", raceId: "rom-dwarf", level: 20 });
|
||||
useCombatStore.getState().registerMob("champion-rune-draw-target", {
|
||||
name: "Rune Draw Target",
|
||||
maxHealth: 100_000,
|
||||
});
|
||||
setMobPosition("champion-rune-draw-target", [8, 0, 0]);
|
||||
useCombatStore.getState().selectMob("champion-rune-draw-target");
|
||||
|
||||
expect(useCombatStore.getState().castAbility("rom-champion-primary-1", undefined, 1_000)).toMatchObject({ ok: true });
|
||||
expect(getMobPosition("champion-rune-draw-target")?.[0]).toBeCloseTo(1.5);
|
||||
});
|
||||
|
||||
it("rejects Indomitable Spirit at 65% health and permits it below the threshold", () => {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-champion",
|
||||
secondaryClassId: "rom-warlock",
|
||||
raceId: "rom-dwarf",
|
||||
level: 70,
|
||||
});
|
||||
const maxHealth = useCombatStore.getState().maxHealth;
|
||||
useCombatStore.setState((state) => ({
|
||||
health: Math.ceil(maxHealth * 0.65),
|
||||
resource: 20,
|
||||
resourcePools: { ...state.resourcePools, rage: 20 },
|
||||
}));
|
||||
expect(useCombatStore.getState().castAbility("rom-champion-rom-warlock-elite-35", undefined, 1_000))
|
||||
.toMatchObject({ ok: false, reason: "condition-not-met" });
|
||||
|
||||
useCombatStore.setState((state) => ({
|
||||
health: Math.floor(maxHealth * 0.65) - 1,
|
||||
resource: 20,
|
||||
resourcePools: { ...state.resourcePools, rage: 20 },
|
||||
}));
|
||||
expect(useCombatStore.getState().castAbility("rom-champion-rom-warlock-elite-35", undefined, 1_000))
|
||||
.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it("activates Forge for five minutes and grants one stack and Rage on a normal hit", () => {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "rom-champion", raceId: "rom-dwarf", level: 20 });
|
||||
useCombatStore.getState().registerMob("champion-forge-target", { name: "Forge Target", maxHealth: 100_000 });
|
||||
setMobPosition("champion-forge-target", [3, 0, 0]);
|
||||
useCombatStore.getState().selectMob("champion-forge-target");
|
||||
|
||||
const forge = ROM_ALL_ABILITIES.find((ability) => ability.dbcSpellId === 498523)!;
|
||||
expect(useCombatStore.getState().castAbility(forge.id, undefined, 1_000)).toMatchObject({ ok: true });
|
||||
const activeForge = useCombatStore.getState().auras.find((aura) => aura.definition.id === "rom-champion-forge-active");
|
||||
expect(activeForge?.expiresAt).toBe(301_000);
|
||||
expect(useCombatStore.getState().auras.some((aura) => aura.definition.id === "rom-champion-forge-stack")).toBe(false);
|
||||
|
||||
useCombatStore.setState({ autoAttackTargetId: "champion-forge-target", nextAutoAttackAt: 1_000 });
|
||||
useCombatStore.getState().tick(0.1, 1_000);
|
||||
const forgeStack = useCombatStore.getState().auras.find((aura) => aura.definition.id === "rom-champion-forge-stack");
|
||||
expect(forgeStack?.stacks).toBe(1);
|
||||
expect(forgeStack?.expiresAt).toBe(9_000);
|
||||
expect(useCombatStore.getState().resourcePools.rage).toBeGreaterThanOrEqual(10);
|
||||
});
|
||||
|
||||
it("scales Determination Rune's source max-health shield and leaves unrelated casts unchanged", () => {
|
||||
const determinationRune = sourceAbility(498584);
|
||||
const sourceShield = determinationRune.effects.find((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.absorbs?.length);
|
||||
expect(sourceShield?.kind).toBe("apply-aura");
|
||||
if (sourceShield?.kind !== "apply-aura") throw new Error("Missing Determination Rune shield effect");
|
||||
expect(sourceShield.aura.absorbs?.[0]?.amount).toBe(30);
|
||||
expect(determinationRune.ranks?.[0]?.effects.find((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.absorbs?.length)).toMatchObject({
|
||||
kind: "apply-aura",
|
||||
aura: { absorbs: [expect.objectContaining({ amount: 30 })] },
|
||||
});
|
||||
|
||||
const twoThousandHp = castEffects(498584, { casterMaxHealth: 2_000 });
|
||||
const fiveThousandHp = castEffects(498584, { casterMaxHealth: 5_000 });
|
||||
expect(twoThousandHp.find((effect) => effect.kind === "apply-aura" && effect.aura.absorbs?.length))
|
||||
.toMatchObject({ kind: "apply-aura", aura: { absorbs: [expect.objectContaining({ amount: 600 })] } });
|
||||
expect(fiveThousandHp.find((effect) => effect.kind === "apply-aura" && effect.aura.absorbs?.length))
|
||||
.toMatchObject({ kind: "apply-aura", aura: { absorbs: [expect.objectContaining({ amount: 1_500 })] } });
|
||||
|
||||
const otherClass = { ...determinationRune, classId: "rom-mage" as const };
|
||||
expect(romChampionCastEffects(otherClass, determinationRune.effects, 1, 2_000))
|
||||
.toBe(determinationRune.effects);
|
||||
const otherPair = { ...determinationRune, secondaryClassId: "rom-mage" as const };
|
||||
expect(romChampionCastEffects(otherPair, determinationRune.effects, 1, 2_000))
|
||||
.toBe(determinationRune.effects);
|
||||
const otherChampionAbility = sourceAbility(498568);
|
||||
expect(romChampionCastEffects(otherChampionAbility, otherChampionAbility.effects, 1, 2_000))
|
||||
.toBe(otherChampionAbility.effects);
|
||||
});
|
||||
|
||||
it("casts Determination Rune with an absorb equal to 30% of the current max HP", () => {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-champion",
|
||||
secondaryClassId: "rom-warrior",
|
||||
raceId: "rom-dwarf",
|
||||
level: 30,
|
||||
});
|
||||
const maxHealth = useCombatStore.getState().maxHealth;
|
||||
|
||||
expect(useCombatStore.getState().castAbility("rom-champion-rom-warrior-elite-30", undefined, 1_000))
|
||||
.toMatchObject({ ok: true });
|
||||
|
||||
const shield = useCombatStore.getState().auras.find((aura) => aura.definition.absorbs?.length);
|
||||
expect(shield?.definition.absorbs?.[0]?.amount).toBe(Math.round(maxHealth * 0.3));
|
||||
});
|
||||
|
||||
it("keeps Champion cooldown resets source-scoped and removes undefined proc chances", () => {
|
||||
const heartRuneResets = castEffects(498651)
|
||||
.filter((effect) => effect.kind === "cooldown-reset");
|
||||
expect(heartRuneResets.length).toBeGreaterThan(0);
|
||||
expect(heartRuneResets.every((effect) => effect.kind === "cooldown-reset"
|
||||
&& effect.abilityName === "Rune Siphon")).toBe(true);
|
||||
|
||||
expect(castEffects(498599).some((effect) => effect.kind === "damage")).toBe(true);
|
||||
expect(castEffects(498599).some((effect) => effect.kind === "cooldown-reset")).toBe(false);
|
||||
expect(castEffects(498535).some((effect) => effect.kind === "cooldown-reset")).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user