Files
healer-man/src/game/wow335AbilityCatalog.test.ts
T
2026-09-17 09:13:15 -04:00

266 lines
11 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { abilityAtLevel, type AbilityDefinition, type AbilityEffect } from "./abilityCatalog";
import { WOW335_ABILITIES_BY_CLASS } from "./wow335AbilityCatalog";
function byId(id: string): AbilityDefinition {
const ability = Object.values(WOW335_ABILITIES_BY_CLASS)
.flat()
.find((candidate) => candidate.id === id);
expect(ability, id).toBeDefined();
return ability!;
}
function effectsOfKind<K extends AbilityEffect["kind"]>(
ability: Pick<AbilityDefinition, "effects">,
kind: K,
): readonly Extract<AbilityEffect, { kind: K }>[] {
return ability.effects.filter(
(effect): effect is Extract<AbilityEffect, { kind: K }> => effect.kind === kind,
);
}
describe("WoW 3.3.5 executable effect mapping", () => {
it("gives every imported classic rank executable or explicit utility semantics", () => {
const effects = Object.values(WOW335_ABILITIES_BY_CLASS)
.flat()
.flatMap((ability) => ability.ranks?.flatMap((rank) => rank.effects) ?? ability.effects);
expect(effects.length).toBeGreaterThan(1_000);
expect(effectsOfKind({ effects }, "scripted")).toEqual([]);
expect(effectsOfKind({ effects }, "unsupported")).toEqual([]);
for (const effect of effectsOfKind({ effects }, "utility")) {
expect(effect.action).toMatch(/^(?:create-item|open-lock|portal|weapon-enchant|pickpocket|farsight|class-script)$/);
}
for (const ability of Object.values(WOW335_ABILITIES_BY_CLASS).flat()) {
if (ability.passive) continue;
for (const rank of ability.ranks ?? []) {
expect(rank.effects.length, `${ability.name} rank ${rank.rank}`).toBeGreaterThan(0);
const hasExecutableSemantics = rank.effects.some((effect) => {
if (["trigger-spell", "scripted", "unsupported"].includes(effect.kind)) return false;
if (effect.kind !== "apply-aura") return true;
return Boolean(
effect.control
|| effect.aura.modifiers?.length
|| effect.aura.absorbs?.length
|| effect.aura.procs?.length
|| effect.aura.utilityTags?.length
|| effect.aura.controlImmunities?.length
|| effect.aura.reflectSpellChance
|| effect.aura.redirectDamagePercent,
);
});
if (!hasExecutableSemantics && ability.talentNodeId) {
expect(rank.effects.some((effect) => effect.kind !== "scripted" && effect.kind !== "unsupported"),
`${ability.name} rank ${rank.rank}`).toBe(true);
} else {
expect(hasExecutableSemantics, `${ability.name} rank ${rank.rank}`).toBe(true);
}
}
}
});
it("does not promote triggered rank-zero child spells into player ranks", () => {
expect(byId("wow335-warrior-execute").ranks?.map((rank) => rank.spellId)).not.toContain(20647);
expect(byId("wow335-priest-divine-hymn").ranks?.map((rank) => rank.spellId)).toEqual([64843]);
expect(byId("wow335-death-knight-death-coil").ranks?.map((rank) => rank.spellId)).not.toContain(47632);
expect(byId("wow335-death-knight-death-coil")).toMatchObject({
target: "hostile",
effects: expect.arrayContaining([expect.objectContaining({ kind: "damage" })]),
});
});
it("freezes mana costs per learned rank while keeping form resources source-correct", () => {
expect(byId("wow335-mage-frostbolt").cost).toEqual({ resource: "mana", amount: 17 });
const pain = byId("wow335-priest-shadow-word-pain");
expect(abilityAtLevel(pain, 26)).toMatchObject({
dbcSpellId: 992,
cost: { resource: "mana", amount: 133 },
});
expect(abilityAtLevel(pain, 32)).toMatchObject({
dbcSpellId: 992,
cost: { resource: "mana", amount: 133 },
});
expect(abilityAtLevel(pain, 34)).toMatchObject({
dbcSpellId: 2767,
cost: { resource: "mana", amount: 188 },
});
expect(byId("wow335-druid-claw").cost).toMatchObject({ resource: "energy", amount: 45 });
expect(byId("wow335-druid-maul").cost).toMatchObject({ resource: "rage", amount: 15 });
});
it("never leaves an imported player mana cost tied to maximum mana", () => {
const manaRanks = Object.values(WOW335_ABILITIES_BY_CLASS)
.flat()
.flatMap((ability) => ability.ranks ?? [])
.filter((rank) => rank.cost.resource === "mana");
expect(manaRanks.length).toBeGreaterThan(100);
for (const rank of manaRanks) {
expect(rank.cost.percentage, `spell ${rank.spellId} rank ${rank.rank}`).not.toBe(true);
expect(Number.isFinite(rank.cost.amount), `spell ${rank.spellId} rank ${rank.rank}`).toBe(true);
}
});
it("provides mechanics for previously silent class scripts and hidden proc spells", () => {
expect(effectsOfKind(byId("wow335-warrior-slam"), "damage")).not.toEqual([]);
expect(effectsOfKind(byId("wow335-death-knight-death-grip"), "pull")).not.toEqual([]);
expect(effectsOfKind(byId("wow335-shaman-windfury-weapon"), "apply-aura")[0]?.aura.procs).not.toEqual([]);
expect(effectsOfKind(byId("wow335-shaman-lightning-shield"), "apply-aura")[0]?.aura.procs).toEqual(expect.arrayContaining([
expect.objectContaining({ triggers: "damage-taken", charges: 3 }),
]));
});
it("keeps triggered shield damage children out of learned Shaman ranks", () => {
const lightningShield = abilityAtLevel(byId("wow335-shaman-lightning-shield"), 80);
expect(lightningShield.dbcSpellId).toBe(49281);
expect(effectsOfKind(lightningShield, "damage")).toEqual([]);
expect(effectsOfKind(lightningShield, "apply-aura")[0]?.aura).toMatchObject({
exclusiveGroup: "shaman:elemental-shield",
procs: expect.arrayContaining([expect.objectContaining({ triggers: "damage-taken", charges: 3 })]),
});
const waterShield = abilityAtLevel(byId("wow335-shaman-water-shield"), 80);
expect(waterShield.dbcSpellId).toBe(57960);
expect(effectsOfKind(waterShield, "apply-aura").every((effect) => (
effect.aura.exclusiveGroup === "shaman:elemental-shield"
))).toBe(true);
});
it("creates source-named stat and avoidance auras", () => {
const fortitude = effectsOfKind(byId("wow335-priest-power-word-fortitude"), "apply-aura")[0];
expect(fortitude).toMatchObject({
sourceEffectType: 6,
sourceAuraType: 29,
aura: {
id: "wow335:1243:effect:0:aura:29",
name: "Power Word: Fortitude (DBC 1243, Aura 29)",
disposition: "buff",
dispelCategory: "magic",
modifiers: [{ kind: "stat", stat: "stamina", operation: "flat", value: 3 }],
},
});
const evasion = effectsOfKind(byId("wow335-rogue-evasion"), "apply-aura")[0];
expect(evasion.aura.modifiers).toContainEqual({
kind: "stat",
stat: "dodge",
operation: "percent",
value: 0.5,
});
});
it("preserves permanent DBC toggles as non-expiring caster auras", () => {
const devotion = effectsOfKind(byId("wow335-paladin-devotion-aura"), "apply-aura")[0];
expect(devotion).toMatchObject({
recipient: "caster",
aura: {
disposition: "buff",
durationMs: null,
exclusiveGroup: "paladin:aura",
},
});
});
it("represents absorbs as named aura capacity instead of a generic shield", () => {
const shield = byId("wow335-priest-power-word-shield");
const aura = effectsOfKind(shield, "apply-aura").find((effect) => effect.sourceAuraType === 69);
expect(aura?.aura).toMatchObject({
disposition: "buff",
durationMs: 30_000,
absorbs: [{ amount: 44, perStack: false }],
});
expect(shield.effects.some((effect) => effect.kind === "shield")).toBe(false);
});
it("maps Paladin emergency cooldowns to their source-backed mechanics", () => {
const layOnHands = byId("wow335-paladin-lay-on-hands");
expect(effectsOfKind(layOnHands, "heal")).toContainEqual(expect.objectContaining({
coefficient: 0,
percentMaxHealth: 1,
}));
const handOfProtection = byId("wow335-paladin-hand-of-protection");
const immunity = effectsOfKind(handOfProtection, "apply-aura")
.find((effect) => effect.sourceAuraType === 39);
expect(immunity?.aura).toMatchObject({
durationMs: 6_000,
modifiers: [{
kind: "damage",
direction: "taken",
school: "physical",
operation: "percent",
value: -1,
}],
});
});
it("keeps periodic damage and healing auras out of the generic HUD strip", () => {
const pain = byId("wow335-priest-shadow-word-pain");
const painAura = effectsOfKind(pain, "apply-aura")
.find((effect) => effect.sourceAuraType === 3);
const renew = byId("wow335-priest-renew");
const renewAura = effectsOfKind(renew, "apply-aura")
.find((effect) => effect.sourceAuraType === 8);
expect(painAura?.aura).toMatchObject({
disposition: "debuff",
hideFromAuraStrip: true,
});
expect(renewAura?.aura).toMatchObject({
disposition: "buff",
hideFromAuraStrip: true,
});
expect(effectsOfKind(pain, "dot")).toHaveLength(1);
expect(effectsOfKind(renew, "hot")).toHaveLength(1);
});
it("maps cleanse, purge, and dual-purpose dispel categories", () => {
expect(effectsOfKind(byId("wow335-paladin-cleanse"), "dispel")).toEqual(expect.arrayContaining([
expect.objectContaining({ mode: "cleanse", relationship: "friendly", categories: ["magic"] }),
expect.objectContaining({ mode: "cleanse", relationship: "friendly", categories: ["disease"] }),
expect.objectContaining({ mode: "cleanse", relationship: "friendly", categories: ["poison"] }),
]));
expect(effectsOfKind(byId("wow335-shaman-purge"), "dispel")).toContainEqual(
expect.objectContaining({ mode: "purge", relationship: "hostile", categories: ["magic"] }),
);
expect(effectsOfKind(byId("wow335-priest-dispel-magic"), "dispel")).toContainEqual(
expect.objectContaining({ relationship: "target", categories: ["magic"] }),
);
});
it("maps interrupts, combo-point awards, and source-backed proc triggers", () => {
expect(effectsOfKind(byId("wow335-rogue-kick"), "interrupt")).toEqual([
expect.objectContaining({ lockoutMs: 5_000 }),
]);
expect(effectsOfKind(byId("wow335-rogue-sinister-strike"), "combo")).toEqual([
{ kind: "combo", amount: 1 },
]);
const seal = effectsOfKind(byId("wow335-paladin-seal-of-justice"), "apply-aura")
.find((effect) => effect.sourceAuraType === 42);
expect(seal?.aura.procs).toEqual([
expect.objectContaining({
triggers: "hit",
action: {
kind: "custom",
id: "classic-status",
data: { status: "stun", durationMs: 2_000 },
},
}),
]);
});
it("keeps fear as a named, dispellable control aura", () => {
const fear = effectsOfKind(byId("wow335-warlock-fear"), "apply-aura")
.find((effect) => effect.control?.kind === "fear");
expect(fear).toMatchObject({
control: { kind: "fear" },
aura: { disposition: "debuff", dispelCategory: "magic" },
});
});
});