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

94 lines
4.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { abilitiesForClass } from "./abilityCatalog";
import { channelAmountEffects, channelPulseAt, channelPulseCount } from "./combatChannelAmounts";
describe("channel amount expansion", () => {
it("spreads a normal channel's full amount across its pulses", () => {
const ability = abilitiesForClass("mage")[0];
const effects = channelAmountEffects({ ...ability, effects: [
{ kind: "damage", coefficient: 1, basePoints: 100 },
{ kind: "dot", coefficient: 1, ticks: 5, intervalMs: 1_000, tag: "channel" },
] }, 5);
expect(effects.map(({ multiplier }) => multiplier)).toEqual([0.2, 1]);
});
it("uses the authored periodic payload for Radiant Flame", () => {
const ability = abilitiesForClass("sun-cleric").find((candidate) => candidate.name === "Radiant Flame")!;
const amounts = channelAmountEffects(ability, 5);
expect(amounts).toHaveLength(1);
expect(amounts[0]).toMatchObject({ effect: { kind: "damage", basePoints: 1 }, multiplier: 1 });
const conditional = { ...ability, effects: ability.effects.map((effect) => effect.kind === "apply-aura"
? { ...effect, aura: { ...effect.aura, procs: effect.aura.procs?.map((proc) => ({ ...proc, chance: 0.5 })) } }
: effect) };
expect(channelAmountEffects(conditional, 5)).toEqual([]);
});
it("deals Chain of Light's source hit on every pulse", () => {
const ability = abilitiesForClass("rom-priest").find((candidate) => candidate.name === "Chain of Light")!;
expect(ability.castMode).toBe("channel");
expect(ability.castTimeMs).toBe(4_000);
expect(channelPulseCount(ability)).toBe(4);
expect(channelAmountEffects(ability, 4)).toEqual([
{ effect: ability.effects.find((effect) => effect.kind === "damage"), multiplier: 1 },
]);
});
it("uses source intervals for classic channels and starts Penance immediately", () => {
const cases = [
["priest", "Penance", 3, 0, 1_000],
["priest", "Mind Flay", 3, 1_000, 1_000],
["priest", "Divine Hymn", 4, 2_000, 2_000],
["hunter", "Volley", 6, 1_000, 1_000],
["mage", "Blizzard", 8, 1_000, 1_000],
["warlock", "Rain of Fire", 4, 2_000, 2_000],
["druid", "Tranquility", 4, 2_000, 2_000],
["druid", "Hurricane", 10, 1_000, 1_000],
] as const;
for (const [classId, name, pulses, firstAt, interval] of cases) {
const ability = abilitiesForClass(classId).find((candidate) => candidate.name === name)!;
expect(ability.castMode, name).toBe("channel");
expect(channelPulseCount(ability), name).toBe(pulses);
expect(channelPulseAt(ability, 0, ability.castTimeMs, 0), name).toBe(firstAt);
expect(channelPulseAt(ability, 0, ability.castTimeMs, 1), name).toBe(firstAt + interval);
}
const penance = abilitiesForClass("priest").find((candidate) => candidate.name === "Penance")!;
const accelerated = { ...penance, castTimeMs: 1_000 };
expect(channelPulseCount(accelerated)).toBe(3);
expect(channelPulseAt(accelerated, 0, 1_000, 1)).toBe(500);
});
it("honors an explicit channel cadence through the cast end", () => {
const mindFlay = abilitiesForClass("priest").find((candidate) => candidate.name === "Mind Flay")!;
const necroticWoundChannel = {
...mindFlay,
castMode: "channel" as const,
castTimeMs: 1_500,
channelPulses: 3,
channelTickMs: 500,
};
expect([0, 1, 2].map((pulse) => channelPulseAt(necroticWoundChannel, 1_000, 1_500, pulse)))
.toEqual([1_500, 2_000, 2_500]);
const penance = abilitiesForClass("priest").find((candidate) => candidate.name === "Penance")!;
const explicitPenance = { ...penance, channelTickMs: 1_000 };
expect([0, 1, 2].map((pulse) => channelPulseAt(explicitPenance, 1_000, explicitPenance.castTimeMs, pulse)))
.toEqual([1_000, 2_000, 3_000]);
});
it("gives damaging and healing classic channels a pulse payload", () => {
const expected = [
["priest", "Penance"], ["priest", "Mind Flay"], ["priest", "Mind Sear"],
["priest", "Divine Hymn"], ["hunter", "Volley"],
["mage", "Arcane Missiles"], ["mage", "Blizzard"],
["warlock", "Drain Life"], ["warlock", "Drain Soul"],
["warlock", "Health Funnel"], ["warlock", "Rain of Fire"], ["warlock", "Hellfire"],
["druid", "Tranquility"], ["druid", "Hurricane"],
] as const;
const missing = expected.filter(([classId, name]) => {
const ability = abilitiesForClass(classId).find((candidate) => candidate.name === name)!;
return channelAmountEffects(ability, channelPulseCount(ability)).length === 0;
});
expect(missing).toEqual([]);
});
});