139 lines
6.2 KiB
TypeScript
139 lines
6.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { ROM_ALL_ABILITIES, ROM_ELITE_ABILITIES } from "./romAbilityCatalog";
|
|
import { channelAmountEffects, channelPulseAt, channelPulseCount } from "./combatChannelAmounts";
|
|
import { romAbilityRequiredTargetStateTag, romEffectsForCast, type RomCastMechanics } from "./romCombatMechanics";
|
|
import { ROM_DRUID_ROGUE_CORROSIVE_POISON_TAG } from "./romDruidRogueCurrentCoa";
|
|
|
|
const empty: RomCastMechanics = {
|
|
sealStacks: 0,
|
|
bleedCount: 0,
|
|
charged: false,
|
|
mainHandDagger: false,
|
|
casterMaxHealth: 10_000,
|
|
targetHealthPercent: 1,
|
|
};
|
|
|
|
function ability(spellId: number) {
|
|
const found = ROM_ALL_ABILITIES.find((entry) => entry.dbcSpellId === spellId
|
|
&& entry.classId === "rom-druid" && entry.secondaryClassId === "rom-rogue");
|
|
expect(found, `Druid/Rogue ability ${spellId}`).toBeDefined();
|
|
return found!;
|
|
}
|
|
|
|
describe("current CoA Druid/Rogue Necrotic Wound profile", () => {
|
|
it("uses the sourced gate, costs, channel, range, radius, and three full per-hit values", () => {
|
|
const wound = ability(494321);
|
|
expect(wound).toMatchObject({
|
|
name: "Necrotic Wound",
|
|
target: "hostile",
|
|
range: { min: 0, max: 15 },
|
|
radius: 8,
|
|
targeting: { shape: "circle", radius: 8 },
|
|
castMode: "channel",
|
|
castTimeMs: 1_500,
|
|
channelPulses: 3,
|
|
channelTickMs: 500,
|
|
cooldownMs: 0,
|
|
costs: [
|
|
{ resource: "mana", amount: 4, percentage: true },
|
|
{ resource: "nature-power", amount: 1 },
|
|
],
|
|
});
|
|
expect(romAbilityRequiredTargetStateTag(wound)).toBe(ROM_DRUID_ROGUE_CORROSIVE_POISON_TAG);
|
|
expect(romEffectsForCast(wound, { ...empty, naturePower: 1 })).toEqual([]);
|
|
|
|
const currentEffects = romEffectsForCast(wound, {
|
|
...empty,
|
|
naturePower: 1,
|
|
targetTags: [ROM_DRUID_ROGUE_CORROSIVE_POISON_TAG],
|
|
});
|
|
expect(currentEffects.filter((effect) => effect.kind === "apply-aura"
|
|
&& effect.aura.modifiers?.some((modifier) => modifier.kind === "healing" && modifier.direction === "taken")))
|
|
.toHaveLength(0);
|
|
expect(currentEffects.filter((effect) => effect.kind === "apply-aura" && effect.control?.kind === "root"))
|
|
.toHaveLength(0);
|
|
expect(currentEffects.filter((effect) => effect.kind === "damage"))
|
|
.toEqual([{ kind: "damage", coefficient: 3.297, school: "shadow" }]);
|
|
expect(currentEffects.filter((effect) => effect.kind === "utility" && effect.action === "class-script"))
|
|
.toEqual([expect.objectContaining({ triggerSpellId: 494024, durationMs: 12_000 })]);
|
|
expect(currentEffects.filter((effect) => effect.kind === "cooldown-reset"))
|
|
.toEqual([{ kind: "cooldown-reset", mode: "reduce", abilityName: "Sinister Request", amountMs: 1_000 }]);
|
|
|
|
const channelAmounts = channelAmountEffects(wound, 3);
|
|
expect(channelAmounts).toEqual([{ effect: currentEffects.find((effect) => effect.kind === "damage"), multiplier: 1 }]);
|
|
expect(channelPulseCount(wound)).toBe(3);
|
|
expect([0, 1, 2].map((pulse) => channelPulseAt(wound, 1_000, 1_500, pulse)))
|
|
.toEqual([1_500, 2_000, 2_500]);
|
|
});
|
|
|
|
it("replaces Corrosive Poison with the current +105, one-second Dark DoT and adds the spread prerequisite", () => {
|
|
const poison = ability(494024);
|
|
const dot = poison.effects.find((effect) => effect.kind === "dot");
|
|
expect(poison).toMatchObject({
|
|
range: { max: 15 },
|
|
castMode: "instant",
|
|
castTimeMs: 0,
|
|
cooldownMs: 1_000,
|
|
costs: [{ resource: "energy", amount: 25 }],
|
|
});
|
|
expect(dot).toMatchObject({
|
|
kind: "dot",
|
|
school: "shadow",
|
|
basePoints: 11_342,
|
|
sourceLevel: 105,
|
|
ticks: 12,
|
|
intervalMs: 1_000,
|
|
tag: ROM_DRUID_ROGUE_CORROSIVE_POISON_TAG,
|
|
});
|
|
});
|
|
|
|
it("adds the exact one-second cooldown rider to both successful casts", () => {
|
|
const cooldownRiders = [494022, 494321].map((spellId) => romEffectsForCast(ability(spellId), {
|
|
...empty,
|
|
naturePower: 1,
|
|
targetTags: spellId === 494321 ? [ROM_DRUID_ROGUE_CORROSIVE_POISON_TAG] : [],
|
|
}).filter((effect) => effect.kind === "cooldown-reset"));
|
|
expect(cooldownRiders).toEqual([
|
|
[{ kind: "cooldown-reset", mode: "reduce", abilityName: "Sinister Request", amountMs: 1_000 }],
|
|
[{ kind: "cooldown-reset", mode: "reduce", abilityName: "Sinister Request", amountMs: 1_000 }],
|
|
]);
|
|
|
|
const kneeUlcer = ROM_ELITE_ABILITIES.find((entry) => entry.dbcSpellId === 499944);
|
|
expect(kneeUlcer).toBeDefined();
|
|
const passiveTag = kneeUlcer!.effects.find((effect) => effect.kind === "apply-aura");
|
|
expect(passiveTag?.kind).toBe("apply-aura");
|
|
const withObsoletePassive = romEffectsForCast(ability(494321), {
|
|
...empty,
|
|
naturePower: 1,
|
|
targetTags: [ROM_DRUID_ROGUE_CORROSIVE_POISON_TAG],
|
|
passiveTags: passiveTag?.kind === "apply-aura" ? passiveTag.aura.utilityTags : [],
|
|
});
|
|
expect(withObsoletePassive.some((effect) => effect.kind === "apply-aura" && effect.control?.kind === "root"))
|
|
.toBe(false);
|
|
});
|
|
|
|
it("uses the current Sinister Request identity and does not leak the old Speed Catalysis aura", () => {
|
|
const request = ability(494023);
|
|
expect(request).toMatchObject({
|
|
name: "Sinister Request",
|
|
target: "self",
|
|
range: { max: 5 },
|
|
radius: 15,
|
|
cooldownMs: 15_000,
|
|
costs: [],
|
|
effects: expect.arrayContaining([
|
|
{ kind: "resource", amount: 100, resource: "mana", percentage: true, scope: "party" },
|
|
{ kind: "resource", amount: 100, resource: "rage", percentage: true, scope: "party" },
|
|
{ kind: "resource", amount: 100, resource: "focus", percentage: true, scope: "party" },
|
|
{ kind: "resource", amount: 100, resource: "energy", percentage: true, scope: "party" },
|
|
{ kind: "resource", amount: 100, resource: "psi", percentage: true, scope: "party" },
|
|
{ kind: "resource", amount: 100, resource: "nature-power", percentage: true, scope: "party" },
|
|
]),
|
|
});
|
|
expect(request.description).toContain("restores the MP, Rage, Focus, Energy, Psi, and Nature's Power");
|
|
expect(request.description).toContain("within radius 150");
|
|
expect(request.description).toContain("Cooldown: 15 seconds");
|
|
expect(request.description).not.toContain("attack and casting speed");
|
|
});
|
|
});
|