import { beforeEach, describe, expect, it } from "vitest"; import { abilitiesForCharacter, learnedAbilityForBuild, type AbilityDefinition } from "./abilityCatalog"; import { useCombatStore } from "./combatStore"; import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry"; import { romScoutRogueCastEffects, romScoutRogueProcActionSpec, romScoutRogueWhirlwindBonusEffect } from "./romScoutRogueOverrides"; const scoutRogue = abilitiesForCharacter("rom-scout", "rom-rogue"); const scoutWarrior = abilitiesForCharacter("rom-scout", "rom-warrior"); const rogueWarrior = abilitiesForCharacter("rom-rogue", "rom-warrior"); function skill(abilities: readonly AbilityDefinition[], id: string): AbilityDefinition { const result = abilities.find((ability) => ability.id === id); if (!result) throw new Error(`Missing skill ${id}`); return result; } function modifiers(ability: AbilityDefinition) { return ability.effects.flatMap((effect) => effect.kind === "apply-aura" ? effect.aura.modifiers ?? [] : []); } function workingPassive(ability: AbilityDefinition): boolean { return ability.effects.some((effect) => effect.kind === "apply-aura" && Boolean( effect.aura.modifiers?.length || effect.aura.procs?.length || effect.aura.utilityTags?.length, )); } function prime(primary: "rom-scout" | "rom-rogue", level: number): void { useCombatStore.getState().initializeCharacter({ classId: primary, secondaryClassId: primary === "rom-scout" ? "rom-rogue" : "rom-warrior", raceId: primary === "rom-scout" ? "rom-elf" : "rom-human", level, }); for (const [id, x, z] of [["target", 3, 0], ["behind", 6, 0], ["nearby", 3, 3]] as const) { useCombatStore.getState().registerMob(id, { name: id, maxHealth: 1_000_000 }); setMobPosition(id, [x, 0, z]); } useCombatStore.getState().selectMob("target"); } function primeScoutWarrior(level: number): void { useCombatStore.getState().initializeCharacter({ classId: "rom-scout", secondaryClassId: "rom-warrior", raceId: "rom-elf", level, }); for (const [id, x, z] of [["target", 3, 0], ["behind", 6, 0], ["nearby", 3, 3]] as const) { useCombatStore.getState().registerMob(id, { name: id, maxHealth: 1_000_000 }); setMobPosition(id, [x, 0, z]); } useCombatStore.getState().selectMob("target"); } describe("accepted RoM Scout/Rogue and Rogue/Warrior pairings", () => { beforeEach(() => clearMobRuntimeRegistry()); it.each([ ["rom-scout", "rom-rogue", [491493, 491336, 492342, 491496, 492621, 492916, 493007, 493253, 499534, 499536]], ["rom-rogue", "rom-warrior", [491526, 491527, 492349, 491339, 492625, 492920, 493017, 493257, 499476, 499480]], ] as const)("learns all ten %s/%s source elites at their primary levels", (primary, secondary, ids) => { const elites = abilitiesForCharacter(primary, secondary).filter((ability) => ability.secondaryClassId); expect(elites.map((ability) => ability.dbcSpellId)).toEqual(ids); expect(elites.map((ability) => ability.unlockLevel)).toEqual([15, 20, 25, 30, 35, 40, 45, 50, 60, 70]); for (const [level, count] of [[10, 0], [30, 4], [60, 9], [79, 10]] as const) { expect(elites.filter((ability) => learnedAbilityForBuild(ability, level, {}) !== null), `level ${level}`).toHaveLength(count); } }); it.each([10, 30, 60, 79])("deals ordinary single-target damage with both pairings at level %i", (level) => { for (const [primary, id, pool] of [ ["rom-scout", "rom-scout-primary-1", "focus"], ["rom-rogue", "rom-rogue-general-0", "energy"], ] as const) { prime(primary, level); const before = useCombatStore.getState().mobs.target!.health; const resource = useCombatStore.getState().resourcePools[pool]!; expect(useCombatStore.getState().castAbility(id, undefined, 1_000), id).toMatchObject({ ok: true }); expect(useCombatStore.getState().mobs.target!.health, id).toBeLessThan(before); expect(useCombatStore.getState().resourcePools[pool], id).toBeLessThan(resource); } }); it("runs Scout and Rogue offensive cooldowns and Rogue's defensive Evasion", () => { prime("rom-scout", 60); expect(useCombatStore.getState().castAbility("rom-scout-primary-0", undefined, 1_000)).toMatchObject({ ok: true }); expect(useCombatStore.getState().auras.some((aura) => aura.definition.modifiers?.some((modifier) => modifier.kind === "damage" && modifier.direction === "dealt" && modifier.attackKind === "ranged"))).toBe(true); prime("rom-rogue", 60); expect(useCombatStore.getState().castAbility("rom-rogue-primary-0", undefined, 1_000)).toMatchObject({ ok: true }); expect(useCombatStore.getState().auras.some((aura) => aura.definition.modifiers?.some((modifier) => modifier.kind === "stat" && modifier.stat === "physical-critical-strike"))).toBe(true); expect(useCombatStore.getState().castAbility("rom-rogue-primary-14", undefined, 2_600)).toMatchObject({ ok: true }); expect(useCombatStore.getState().auras.some((aura) => aura.definition.modifiers?.some((modifier) => modifier.kind === "stat" && modifier.stat === "dodge"))).toBe(true); }); it("Piercing Arrow hits a line without applying an unrelated focus drain (490400)", () => { prime("rom-scout", 60); const cast = useCombatStore.getState().castAbility("rom-scout-primary-5", undefined, 1_000); expect(cast).toMatchObject({ ok: true, affectedMobIds: ["target", "behind"] }); expect(useCombatStore.getState().combatEvents.filter((event) => event.abilityId === "rom-scout-primary-5" && event.kind === "damage" && event.rawAmount > 0).map((event) => event.targetActorId)) .toEqual(["target", "behind"]); expect(modifiers(skill(scoutRogue, "rom-scout-primary-5")).some((modifier) => modifier.kind === "resource" && modifier.resource === "focus")).toBe(false); }); it("Combo Shot hits three times and Reflected Shot has two weaker bounces (490424, 490457)", () => { expect(skill(scoutRogue, "rom-scout-primary-9").effects.filter((effect) => effect.kind === "damage")).toHaveLength(3); prime("rom-scout", 40); expect(useCombatStore.getState().castAbility("rom-scout-primary-11", undefined, 1_000)) .toMatchObject({ ok: true, affectedMobIds: expect.arrayContaining(["target", "behind", "nearby"]) }); const rawDamage = useCombatStore.getState().combatEvents.filter((event) => event.abilityId === "rom-scout-primary-11" && event.kind === "damage").map((event) => event.rawAmount); expect(rawDamage).toHaveLength(3); expect(rawDamage[0]).toBeGreaterThan(rawDamage[1]!); expect(rawDamage[1]).toBeGreaterThan(rawDamage[2]!); }); it("Poisonous Spit removes bounce falloff and Master Reflected Shot adds two jumps (493253, 499534)", () => { prime("rom-scout", 60); setMobPosition("behind", [4, 0, 0]); setMobPosition("nearby", [5, 0, 0]); for (const [id, x, z] of [["fourth", 6, 0], ["fifth", 7, 0]] as const) { useCombatStore.getState().registerMob(id, { name: id, maxHealth: 1_000_000 }); setMobPosition(id, [x, 0, z]); } const cast = useCombatStore.getState().castAbility("rom-scout-primary-11", undefined, 1_000); expect(cast).toMatchObject({ ok: true, affectedMobIds: expect.arrayContaining([ "target", "behind", "nearby", "fourth", "fifth", ]) }); const damage = useCombatStore.getState().combatEvents.filter((event) => event.abilityId === "rom-scout-primary-11" && event.kind === "damage").map((event) => event.rawAmount); expect(damage).toHaveLength(5); expect(new Set(damage).size).toBe(1); }); it("Master Reflected Shot uses replacement 499535's 0.85 / 0.75 DPS ratio", () => { const ability = skill(scoutRogue, "rom-scout-primary-11"); const base = ability.effects.find((effect) => effect.kind === "damage"); const mastered = romScoutRogueCastEffects(ability, ability.effects, ["rom-poisonous-spit", "rom-master-reflected-shot"], []) .find((effect) => effect.kind === "damage"); expect(base?.kind).toBe("damage"); expect(mastered?.kind).toBe("damage"); if (base?.kind !== "damage" || mastered?.kind !== "damage") return; expect(mastered.coefficient).toBeCloseTo(base.coefficient * 0.85 / 0.75); expect(mastered.falloffPerJump).toBe(1); }); it("proc resolver gates Shot crit vulnerability and preserves source 2% / 12s", () => { expect(romScoutRogueProcActionSpec("rom-exploiting-shot", 490400, false)).toBeNull(); expect(romScoutRogueProcActionSpec("rom-exploiting-shot", 490423, false)).toMatchObject({ kind: "aura", recipient: "target", aura: { durationMs: 12_000, modifiers: [ { kind: "damage", direction: "taken", school: "physical", operation: "percent", value: 0.02 }, ], }, }); const proc = skill(scoutRogue, "rom-scout-rom-rogue-elite-40").effects.find((effect) => effect.kind === "apply-aura"); expect(proc?.kind === "apply-aura" ? proc.aura.procs?.[0]?.internalCooldownMs : null).toBe(20_000); }); it("proc resolver gates Piercing Arrow critical buff to its own hit", () => { expect(romScoutRogueProcActionSpec("rom-deadly-counterattack", 490423, false)).toBeNull(); expect(romScoutRogueProcActionSpec("rom-deadly-counterattack", 490400, false)).toMatchObject({ kind: "aura", recipient: "caster", provenance: "hybrid-estimate", aura: { durationMs: 5_000, modifiers: [{ kind: "stat", stat: "physical-critical-strike", operation: "flat", value: 20 }], }, }); const proc = skill(scoutRogue, "rom-scout-rom-rogue-elite-45").effects.find((effect) => effect.kind === "apply-aura"); expect(proc?.kind === "apply-aura" ? proc.aura.procs?.[0]?.internalCooldownMs : null).toBe(20_000); }); it("Poisonous Spit uses the Scout/Rogue loadout's carried Poison Powder supply", () => { for (const sourceId of [490457, 490424]) { expect(romScoutRogueProcActionSpec("rom-poisonous-spit", sourceId, false)).toMatchObject({ kind: "dot", recipient: "target", provenance: "source", effect: { kind: "dot", ticks: 4, intervalMs: 2_000, basePoints: 7.1, pointsPerLevel: 2.1 }, }); expect(romScoutRogueProcActionSpec("rom-poisonous-spit", sourceId, true)).toMatchObject({ kind: "dot", recipient: "target", provenance: "source", effect: { kind: "dot", ticks: 4, intervalMs: 2_000, basePoints: 7.1, pointsPerLevel: 2.1 }, }); } expect(romScoutRogueProcActionSpec("rom-poisonous-spit", 490423, true)).toBeNull(); const proc = skill(scoutRogue, "rom-scout-rom-rogue-elite-50").effects.find((effect) => effect.kind === "apply-aura"); expect(proc?.kind === "apply-aura" ? proc.aura.procs?.[0]?.internalCooldownMs : null).toBe(20_000); }); it("executes Poisonous Spit from the loadout supply without Rogue's Poison Bottle buff", () => { prime("rom-scout", 60); expect(useCombatStore.getState().castAbility("rom-scout-primary-11", undefined, 1_000)) .toMatchObject({ ok: true }); expect(useCombatStore.getState().effects.some((effect) => effect.tag === "rom-poisonous-spit" && effect.targetId === "target")).toBe(true); }); it("executes Accurate Targeting on a successful Shot for its sourced duration and modifier", () => { primeScoutWarrior(60); const passive = scoutWarrior.find((ability) => ability.dbcSpellId === 493006); if (!passive) throw new Error("Missing Accurate Targeting"); const proc = passive.effects.find((effect) => effect.kind === "apply-aura"); expect(proc?.kind === "apply-aura" ? proc.aura.procs?.[0] : null).toMatchObject({ internalCooldownMs: 30_000, action: { kind: "custom", id: "rom-accurate-targeting" }, }); expect(romScoutRogueProcActionSpec("rom-accurate-targeting", 490457, false)).toBeNull(); expect(romScoutRogueProcActionSpec("rom-accurate-targeting", 490423, false)).toMatchObject({ kind: "aura", recipient: "caster", provenance: "source", aura: { durationMs: 15_000, modifiers: [{ kind: "stat", stat: "physical-attack", operation: "flat", value: 50 }], }, }); const shot = scoutRogue.find((ability) => ability.dbcSpellId === 490423); if (!shot) throw new Error("Missing Shot"); expect(useCombatStore.getState().castAbility(shot.id, undefined, 1_000)).toMatchObject({ ok: true }); expect(useCombatStore.getState().auras.some((aura) => aura.definition.id === "rom-scout-accurate-targeting" && aura.definition.modifiers?.some((modifier) => modifier.kind === "stat" && modifier.stat === "physical-attack" && modifier.value === 50))).toBe(true); }); it("proc resolver routes Reclaim Energy by trigger skill", () => { expect(romScoutRogueProcActionSpec("rom-reclaim-energy", 490306, false)).toMatchObject({ kind: "resource", resource: "energy", amount: 15, provenance: "hybrid-estimate", }); expect(romScoutRogueProcActionSpec("rom-reclaim-energy", 492625, true)).toMatchObject({ kind: "resource", resource: "rage", amount: 10, provenance: "hybrid-estimate", }); expect(romScoutRogueProcActionSpec("rom-reclaim-energy", 490309, false)).toBeNull(); const proc = skill(rogueWarrior, "rom-rogue-rom-warrior-elite-50").effects.find((effect) => effect.kind === "apply-aura"); expect(proc?.kind === "apply-aura" ? proc.aura.procs?.[0]?.chance : null).toBe(0.25); }); it("Toxic Splash's 493296 Infection upgrade can return 20 rage on its hit", () => { expect(romScoutRogueProcActionSpec("rom-infection-rage", 490338, false)).toBeNull(); expect(romScoutRogueProcActionSpec("rom-infection-rage", 492625, false)).toMatchObject({ kind: "resource", recipient: "caster", resource: "rage", amount: 20, provenance: "hybrid-estimate", }); const proc = skill(rogueWarrior, "rom-rogue-rom-warrior-elite-60").effects.find((effect) => effect.kind === "apply-aura"); expect(proc?.kind === "apply-aura" ? proc.aura.procs?.[0]?.chance : null).toBe(0.25); }); it("maps 499477/499481 through cast upgrades rather than redundant procs", () => { expect(romScoutRogueProcActionSpec("rom-toxic-splash", 499477, true)).toBeNull(); expect(romScoutRogueProcActionSpec("rom-poison-battle-technique", 499481, true)).toBeNull(); }); it("Meridian Attack upgrades Wrist Attack's physical and spell damage penalty", () => { const ability = skill(scoutRogue, "rom-scout-general-6"); expect(ability.dbcSpellId).toBe(490451); const upgraded = romScoutRogueCastEffects(ability, ability.effects, ["rom-meridian-attack"], []); const debuff = upgraded.find((effect) => effect.kind === "apply-aura" && effect.recipient !== "caster"); expect(debuff?.kind === "apply-aura" ? debuff.aura : null).toMatchObject({ durationMs: 10_000, modifiers: expect.arrayContaining([ { kind: "damage", direction: "dealt", school: "physical", operation: "percent", value: -0.3 }, { kind: "damage", direction: "dealt", attackKind: "spell", operation: "percent", value: -0.3 }, ]), }); }); it("applies Meridian Attack's ten-second Wrist Attack debuff at level 79", () => { prime("rom-scout", 79); expect(useCombatStore.getState().castAbility("rom-scout-general-6", undefined, 1_000)) .toMatchObject({ ok: true }); const debuff = useCombatStore.getState().auras.find((aura) => aura.targetId === "target" && aura.definition.modifiers?.some((modifier) => modifier.kind === "damage" && modifier.direction === "dealt" && modifier.attackKind === "spell")); expect(debuff?.definition.durationMs).toBe(10_000); }); it("Toxic Splash upgrades Poison Spray to 493296's hit and 622992's six-second poison", () => { const ability = skill(rogueWarrior, "rom-rogue-rom-warrior-elite-40"); const upgraded = romScoutRogueCastEffects(ability, ability.effects, ["rom-toxic-splash"], []); expect(upgraded.filter((effect) => effect.kind === "damage")).toHaveLength(2); expect(upgraded.filter((effect) => effect.kind === "damage")[1]).toMatchObject({ kind: "damage", coefficient: 1.11375, coefficientPerLevel: 0.06375, }); expect(upgraded.find((effect) => effect.kind === "dot")).toMatchObject({ kind: "dot", ticks: 3, intervalMs: 2_000, tag: "rom-622992", basePoints: 16.2, pointsPerLevel: 4.2, }); }); it("spreads Toxic Splash's replacement poison to nearby enemies at level 79", () => { prime("rom-rogue", 79); useCombatStore.setState((state) => ({ resourcePools: { ...state.resourcePools, rage: 100 } })); expect(useCombatStore.getState().castAbility("rom-rogue-primary-6", undefined, 1_000)) .toMatchObject({ ok: true }); useCombatStore.getState().tick(1.1, 2_100); const infectionCast = useCombatStore.getState().castAbility("rom-rogue-rom-warrior-elite-35", undefined, 2_600); expect(infectionCast, infectionCast.reason).toMatchObject({ ok: true }); expect(useCombatStore.getState().castAbility("rom-rogue-rom-warrior-elite-40", undefined, 4_200)) .toMatchObject({ ok: true, affectedMobIds: expect.arrayContaining(["target", "nearby"]) }); expect(useCombatStore.getState().effects.some((effect) => effect.tag === "rom-622992" && effect.targetId === "nearby")).toBe(true); expect(useCombatStore.getState().effects.some((effect) => effect.kind === "dot" && effect.sourceAbilityId === "rom-rogue-rom-warrior-elite-35" && effect.targetId === "target")).toBe(false); }); it("Poison Battle Technique upgrades Whirlwind and adds a per-target poisoned hit", () => { const ability = skill(rogueWarrior, "rom-warrior-general-2"); const base = ability.effects.find((effect) => effect.kind === "damage"); const upgraded = romScoutRogueCastEffects(ability, ability.effects, ["rom-poison-battle-technique"], []) .find((effect) => effect.kind === "damage"); expect(base?.kind).toBe("damage"); expect(upgraded?.kind).toBe("damage"); if (base?.kind !== "damage" || upgraded?.kind !== "damage") return; expect(upgraded.coefficient).toBeCloseTo(base.coefficient * 0.75 / 0.70); expect(romScoutRogueWhirlwindBonusEffect(ability, ["rom-poison-battle-technique"], [])) .toBeNull(); expect(romScoutRogueWhirlwindBonusEffect(ability, ["rom-poison-battle-technique"], ["rom-622992"])) .toMatchObject({ kind: "damage", coefficient: 0.5, coefficientPerLevel: 0.03 }); }); it("Whirlwind grants its Poison Battle Technique rider only to sprayed targets at level 79", () => { prime("rom-rogue", 79); useCombatStore.getState().registerMob("clean", { name: "clean", maxHealth: 1_000_000 }); setMobPosition("clean", [-4, 0, 0]); // Whirlwind range, outside Spray's target-centered radius. useCombatStore.setState((state) => ({ resourcePools: { ...state.resourcePools, rage: 100 } })); expect(useCombatStore.getState().castAbility("rom-rogue-primary-6", undefined, 1_000)).toMatchObject({ ok: true }); useCombatStore.getState().tick(1.1, 2_100); expect(useCombatStore.getState().castAbility("rom-rogue-rom-warrior-elite-35", undefined, 2_600)) .toMatchObject({ ok: true }); expect(useCombatStore.getState().castAbility("rom-rogue-rom-warrior-elite-40", undefined, 4_200)) .toMatchObject({ ok: true }); expect(useCombatStore.getState().effects.some((effect) => effect.tag === "rom-622992" && effect.targetId === "target")) .toBe(true); expect(useCombatStore.getState().effects.some((effect) => effect.tag === "rom-622992" && effect.targetId === "clean")) .toBe(false); expect(useCombatStore.getState().castAbility("rom-warrior-general-2", undefined, 5_800)) .toMatchObject({ ok: true, affectedMobIds: expect.arrayContaining(["target", "clean"]) }); const hits = useCombatStore.getState().combatEvents.filter((event) => event.abilityId === "rom-warrior-general-2" && event.kind === "damage"); expect(hits.filter((event) => event.targetActorId === "target")).toHaveLength(2); expect(hits.filter((event) => event.targetActorId === "clean")).toHaveLength(1); }); it("Rogue Combo Throw has two hits and Poison grants a proc state (490338, 490329)", () => { expect(skill(rogueWarrior, "rom-rogue-primary-15").effects.filter((effect) => effect.kind === "damage")).toHaveLength(2); prime("rom-rogue", 60); expect(useCombatStore.getState().castAbility("rom-rogue-primary-6", undefined, 1_000)).toMatchObject({ ok: true }); useCombatStore.getState().tick(0.1, 2_100); expect(useCombatStore.getState().auras.some((aura) => aura.definition.utilityTags?.includes("rom-poisonous") && Boolean(aura.definition.procs?.length))).toBe(true); }); it("Rogue Vanish drops combat without performing an unrelated heal (490354)", () => { prime("rom-rogue", 60); useCombatStore.getState().damagePlayer(100); const health = useCombatStore.getState().health; expect(useCombatStore.getState().castAbility("rom-rogue-primary-11", undefined, 1_000)).toMatchObject({ ok: true }); expect(useCombatStore.getState().health).toBe(health); expect(skill(rogueWarrior, "rom-rogue-primary-11").effects.some((effect) => effect.kind === "heal")).toBe(false); }); it("Weak Spot has one hit and a timed critical buff (491493)", () => { const ability = skill(scoutRogue, "rom-scout-rom-rogue-elite-15"); expect(ability.effects.filter((effect) => effect.kind === "damage")).toHaveLength(1); expect(ability.effects.some((effect) => effect.kind === "apply-aura" && effect.recipient === "caster" && effect.aura.durationMs !== null && effect.aura.durationMs > 0 && effect.aura.modifiers?.some((modifier) => modifier.kind === "stat" && modifier.stat === "physical-critical-strike"))).toBe(true); }); it("Blinding Powder control breaks on damage (491336)", () => { prime("rom-scout", 30); expect(useCombatStore.getState().castAbility("rom-scout-rom-rogue-elite-20", undefined, 1_000)).toMatchObject({ ok: true }); expect(useCombatStore.getState().mobs.target!.statuses.some((status) => status.kind === "blind")).toBe(true); useCombatStore.getState().damageMob("target", 1, 1_100); expect(useCombatStore.getState().mobs.target!.statuses.some((status) => status.kind === "blind")).toBe(false); }); it("Lasso Master prevents movement and attacks (492342)", () => { prime("rom-scout", 30); expect(useCombatStore.getState().castAbility("rom-scout-primary-4", undefined, 1_000)).toMatchObject({ ok: true }); useCombatStore.getState().tick(0.1, 2_100); expect(useCombatStore.getState().mobs.target!.statuses.map((status) => status.kind)) .toEqual(expect.arrayContaining(["root", "disarm"])); }); it("Sapping Arrow lowers physical and magical defense (491496)", () => { const values = modifiers(skill(scoutRogue, "rom-scout-rom-rogue-elite-30")); expect(values.some((modifier) => modifier.kind === "stat" && modifier.stat === "armor" && modifier.value < 0)).toBe(true); expect(values.some((modifier) => modifier.kind === "stat" && modifier.stat === "magical-defense" && modifier.value < 0)).toBe(true); }); it("Deadly Poison Bite has one base hit and a conditional bonus (492621)", () => { prime("rom-scout", 60); expect(useCombatStore.getState().castAbility("rom-scout-rom-rogue-elite-35", undefined, 1_000)).toMatchObject({ ok: true }); useCombatStore.getState().tick(0.1, 2_100); expect(useCombatStore.getState().combatEvents.filter((event) => event.abilityId === "rom-scout-rom-rogue-elite-35" && event.kind === "damage" && event.targetActorId === "target")).toHaveLength(1); }); it.each([ ["rom-scout-rom-rogue-elite-40", 492916], ["rom-scout-rom-rogue-elite-45", 493007], ] as const)("%s uses an attack-triggered passive (source %i)", (id, sourceId) => { const ability = skill(scoutRogue, id); expect(ability.dbcSpellId).toBe(sourceId); expect(ability.effects.some((effect) => effect.kind === "apply-aura" && Boolean(effect.aura.procs?.length))).toBe(true); expect(ability.effects.some((effect) => effect.kind === "apply-aura" && effect.recipient === "caster" && effect.aura.modifiers?.some((modifier) => modifier.kind === "damage" && modifier.direction === "taken"))).toBe(false); }); it.each([ ["rom-scout-rom-rogue-elite-50", 493253], ["rom-scout-rom-rogue-elite-60", 499534], ["rom-scout-rom-rogue-elite-70", 499536], ] as const)("%s has executable cross-skill effects (source %i)", (id, sourceId) => { const ability = skill(scoutRogue, id); expect(ability.dbcSpellId).toBe(sourceId); expect(workingPassive(ability)).toBe(true); }); it("Throwing Mastery gives Throw a five-second slow (491526)", () => { prime("rom-rogue", 30); expect(useCombatStore.getState().castAbility("rom-rogue-general-1", undefined, 1_000)).toMatchObject({ ok: true }); expect(useCombatStore.getState().mobs.target!.statuses.some((status) => status.kind === "slow" && status.endsAt >= 6_000)).toBe(true); }); it("One-Hand Axe Mastery does not boost dagger damage (491527)", () => { const ability = skill(rogueWarrior, "rom-rogue-rom-warrior-elite-20"); expect(ability.effects.some((effect) => effect.kind === "apply-aura" && effect.aura.modifiers?.some((modifier) => modifier.kind === "damage" && modifier.attackKind === "melee"))).toBe(false); }); it("Whirlwind Mastery lowers rage cost and cooldown (492349)", () => { prime("rom-rogue", 30); useCombatStore.setState((state) => ({ resourcePools: { ...state.resourcePools, rage: 40 } })); expect(useCombatStore.getState().castAbility("rom-warrior-general-2", undefined, 1_000)).toMatchObject({ ok: true }); expect(useCombatStore.getState().resourcePools.rage).toBe(10); expect(useCombatStore.getState().cooldowns["rom-warrior-general-2"]).toBeLessThanOrEqual(11_000); }); it("Death's Touch damages and reduces received healing (491339)", () => { const ability = skill(rogueWarrior, "rom-rogue-rom-warrior-elite-30"); expect(ability.effects.some((effect) => effect.kind === "damage")).toBe(true); expect(modifiers(ability).some((modifier) => modifier.kind === "healing" && modifier.direction === "taken" && modifier.value < 0)).toBe(true); }); it("Poisonous Infection damages and poisons without making the victim immune (492625)", () => { const ability = skill(rogueWarrior, "rom-rogue-rom-warrior-elite-35"); expect(ability.effects.some((effect) => effect.kind === "damage")).toBe(true); expect(ability.effects.some((effect) => effect.kind === "dot")).toBe(true); expect(modifiers(ability).some((modifier) => modifier.kind === "damage" && modifier.direction === "taken" && modifier.school === "physical" && modifier.value <= -1)).toBe(false); }); it("Poison Spray spreads poison and damage to nearby enemies (492920)", () => { const ability = skill(rogueWarrior, "rom-rogue-rom-warrior-elite-40"); expect(ability.radius).toBeGreaterThan(0); expect(ability.effects.some((effect) => effect.kind === "damage")).toBe(true); expect(ability.effects.some((effect) => effect.kind === "dot" || effect.kind === "apply-aura")).toBe(true); }); it("Decay requires Poisonous and grants timed critical power (493017)", () => { prime("rom-rogue", 60); expect(useCombatStore.getState().castAbility("rom-rogue-rom-warrior-elite-45", undefined, 1_000)) .toMatchObject({ ok: false, reason: "condition-not-met" }); expect(modifiers(skill(rogueWarrior, "rom-rogue-rom-warrior-elite-45")).some((modifier) => modifier.kind === "stat" && modifier.stat === "physical-critical-strike")).toBe(true); }); it.each([ ["rom-rogue-rom-warrior-elite-50", 493257], ["rom-rogue-rom-warrior-elite-60", 499476], ["rom-rogue-rom-warrior-elite-70", 499480], ] as const)("%s has executable triggered effects (source %i)", (id, sourceId) => { const ability = skill(rogueWarrior, id); expect(ability.dbcSpellId).toBe(sourceId); expect(workingPassive(ability)).toBe(true); }); });