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"); }); });