94 lines
4.6 KiB
TypeScript
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([]);
|
|
});
|
|
});
|