forked from phenom/healer-man
Update class mechanics, stat rulesets, and CoA reference catalogs
This commit is contained in:
1 parent
b16821e92e
commit
39458d5de6
221 files changed
+2584962
-568
No files matched your search
@@ -0,0 +1,222 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { AbilityDefinition } from "./abilityCatalog";
|
||||
import {
|
||||
ascensionSpellById,
|
||||
coaSpellById,
|
||||
type CoaProgressionChain,
|
||||
} from "./coaLiveCatalog";
|
||||
import { ascensionProgressionAbility } from "./coaAbilityCatalog";
|
||||
|
||||
const fixture = vi.hoisted(() => {
|
||||
const makeEffect = (effectType: number, triggerSpellId = 0, basePoints = 1) => ({
|
||||
index: 0,
|
||||
effectType,
|
||||
auraType: 0,
|
||||
basePoints,
|
||||
dieSides: 0,
|
||||
pointsPerLevel: 0,
|
||||
mechanic: 0,
|
||||
implicitTargetA: 6,
|
||||
implicitTargetB: 0,
|
||||
radiusIndex: 0,
|
||||
radius: 0,
|
||||
periodMs: 0,
|
||||
valueMultiplier: 0,
|
||||
chainTargets: 0,
|
||||
itemType: 0,
|
||||
miscValue: 0,
|
||||
miscValueB: 0,
|
||||
triggerSpellId,
|
||||
pointsPerCombo: 0,
|
||||
coefficient: 0,
|
||||
});
|
||||
|
||||
const makeDbc = (
|
||||
id: number,
|
||||
name: string,
|
||||
effects: ReturnType<typeof makeEffect>[] = [],
|
||||
startRecoveryTimeMs = 1_500,
|
||||
) => ({
|
||||
id,
|
||||
name,
|
||||
rankText: "Rank 1",
|
||||
description: `${name} fixture description`,
|
||||
categoryId: 0,
|
||||
dispelType: 0,
|
||||
mechanic: 0,
|
||||
attributes: Array(8).fill(0),
|
||||
castTimeIndex: 0,
|
||||
castTimeMs: 0,
|
||||
recoveryTimeMs: 0,
|
||||
categoryRecoveryTimeMs: 0,
|
||||
interruptFlags: 0,
|
||||
auraInterruptFlags: 0,
|
||||
channelInterruptFlags: 0,
|
||||
procFlags: 0,
|
||||
procChance: 0,
|
||||
procCharges: 0,
|
||||
maxLevel: 80,
|
||||
baseLevel: 1,
|
||||
spellLevel: 1,
|
||||
durationIndex: 0,
|
||||
durationMs: 0,
|
||||
powerType: 0,
|
||||
powerCost: 0,
|
||||
powerCostPerLevel: 0,
|
||||
powerCostPerSecond: 0,
|
||||
powerCostPerSecondPerLevel: 0,
|
||||
rangeIndex: 0,
|
||||
rangeMin: 0,
|
||||
rangeMax: 30,
|
||||
projectileSpeed: 0,
|
||||
stackAmount: 0,
|
||||
equippedItemClass: 0,
|
||||
equippedItemSubclassMask: 0,
|
||||
equippedItemInventoryMask: 0,
|
||||
iconId: 0,
|
||||
powerCostPercentage: 0,
|
||||
startRecoveryCategory: 0,
|
||||
startRecoveryTimeMs,
|
||||
maximumTargets: 0,
|
||||
damageClass: 0,
|
||||
preventionType: 0,
|
||||
schoolMask: 1,
|
||||
runeCostId: 0,
|
||||
powerDisplayId: 0,
|
||||
effects,
|
||||
});
|
||||
|
||||
const makeLiveSpell = (id: number, name: string, dbc?: ReturnType<typeof makeDbc>) => ({
|
||||
id,
|
||||
classIds: [11, 27],
|
||||
name,
|
||||
iconBasename: "fixture",
|
||||
level: 1,
|
||||
rank: "Rank 1",
|
||||
description: `${name} live description`,
|
||||
sourceIds: [],
|
||||
advancementType: "",
|
||||
advancementTabId: 0,
|
||||
talented: false,
|
||||
excluded: false,
|
||||
...(dbc ? { dbc } : {}),
|
||||
});
|
||||
|
||||
const mainSpells = [
|
||||
makeLiveSpell(900, "Shared Classic Root", makeDbc(900, "Classic Root", [], 700)),
|
||||
makeLiveSpell(910, "Shared Trigger Root", makeDbc(910, "Classic Trigger Root", [makeEffect(64, 920)])),
|
||||
makeLiveSpell(920, "Shared First Payload", makeDbc(920, "Classic First Payload", [makeEffect(64, 930)])),
|
||||
makeLiveSpell(930, "Shared Nested Payload", makeDbc(930, "Classic Nested Payload", [makeEffect(2)])),
|
||||
makeLiveSpell(940, "Shared Missing CoA Base", makeDbc(940, "Area52 Overlay Payload", [makeEffect(2)])),
|
||||
{ ...makeLiveSpell(950, "Classic Only Root", makeDbc(950, "Classic Only Root", [makeEffect(2)])), classIds: [11] },
|
||||
];
|
||||
const coaOverrides = [
|
||||
makeLiveSpell(900, "Shared CoA Root", makeDbc(900, "CoA Root", [], 0)),
|
||||
makeLiveSpell(910, "Shared CoA Trigger Root", makeDbc(910, "CoA Trigger Root", [makeEffect(64, 920)])),
|
||||
makeLiveSpell(920, "Shared CoA First Payload", makeDbc(920, "CoA First Payload", [makeEffect(64, 930)])),
|
||||
makeLiveSpell(930, "Shared CoA Nested Payload", makeDbc(930, "CoA Nested Payload", [makeEffect(67)])),
|
||||
makeLiveSpell(940, "Shared CoA Missing Base"),
|
||||
];
|
||||
const overlayPayload = makeDbc(940, "Area52 Overlay Payload", [makeEffect(2)]);
|
||||
|
||||
return {
|
||||
liveData: {
|
||||
source: {
|
||||
builderUrl: "fixture",
|
||||
databaseClassUrl: "fixture",
|
||||
realmId: 1,
|
||||
realmSlug: "fixture",
|
||||
realmName: "Fixture",
|
||||
maximumLevel: 80,
|
||||
schemaVersion: {},
|
||||
synchronizedAt: "2026-01-01T00:00:00.000Z",
|
||||
spellDbcPath: "fixture/classic/Spell.dbc",
|
||||
supportDbcDirectory: "fixture/support",
|
||||
},
|
||||
counts: {},
|
||||
classes: [],
|
||||
entries: [],
|
||||
progressionChains: [],
|
||||
spells: mainSpells,
|
||||
coaSpellOverrides: coaOverrides,
|
||||
},
|
||||
triggeredCatalog: {
|
||||
spells: [overlayPayload],
|
||||
classicSpells: [overlayPayload],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./coaLiveData.generated.json", () => ({ default: fixture.liveData }));
|
||||
vi.mock("./generated/coaTriggeredSpellCatalog.json", () => ({ default: fixture.triggeredCatalog }));
|
||||
|
||||
function chain(spellId: number): CoaProgressionChain {
|
||||
return {
|
||||
id: `scope-${spellId}`,
|
||||
classId: 11,
|
||||
name: `Scope ${spellId}`,
|
||||
iconBasename: "fixture",
|
||||
talented: false,
|
||||
advancementType: "",
|
||||
trainer: false,
|
||||
ranks: [{ spellId, rank: 1, level: 1 }],
|
||||
};
|
||||
}
|
||||
|
||||
function ability(spellId: number, classId: "druid" | "sun-cleric"): AbilityDefinition {
|
||||
const classic = classId === "druid";
|
||||
const result = ascensionProgressionAbility(
|
||||
chain(spellId),
|
||||
classId,
|
||||
"mana",
|
||||
classic ? "ascension-progression" : "coa-progression",
|
||||
);
|
||||
if (!result) throw new Error(`Fixture spell ${spellId} did not produce an ability`);
|
||||
return result;
|
||||
}
|
||||
|
||||
describe("classic and CoA spell source scope", () => {
|
||||
it("excludes classic-only records from CoA lookup", () => {
|
||||
expect(ascensionSpellById(950)?.dbc?.name).toBe("Classic Only Root");
|
||||
expect(coaSpellById(950)).toBeNull();
|
||||
expect(ability(950, "druid").effects.some((effect) => effect.kind === "damage")).toBe(true);
|
||||
expect(ability(950, "sun-cleric").effects.some((effect) => effect.kind === "damage")).toBe(false);
|
||||
});
|
||||
|
||||
it("resolves a shared root spell through separate classic and CoA wrappers", () => {
|
||||
expect(ascensionSpellById(900)?.dbc?.name).toBe("Classic Root");
|
||||
expect(coaSpellById(900)?.dbc?.name).toBe("CoA Root");
|
||||
|
||||
expect(ability(900, "druid").ranks?.[0].gcdMs).toBe(700);
|
||||
expect(ability(900, "sun-cleric").ranks?.[0].gcdMs).toBe(0);
|
||||
});
|
||||
|
||||
it("keeps nested triggered payloads in the root spell's class scope", () => {
|
||||
const classicEffects = ability(910, "druid").ranks?.[0].effects ?? [];
|
||||
const coaEffects = ability(910, "sun-cleric").ranks?.[0].effects ?? [];
|
||||
|
||||
expect(classicEffects).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "trigger-spell", spellId: 920 }),
|
||||
expect.objectContaining({ kind: "trigger-spell", spellId: 930 }),
|
||||
expect.objectContaining({ kind: "damage" }),
|
||||
]));
|
||||
expect(classicEffects.some((effect) => effect.kind === "heal")).toBe(false);
|
||||
expect(coaEffects).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "trigger-spell", spellId: 920 }),
|
||||
expect.objectContaining({ kind: "trigger-spell", spellId: 930 }),
|
||||
expect.objectContaining({ kind: "heal" }),
|
||||
]));
|
||||
expect(coaEffects.some((effect) => effect.kind === "damage")).toBe(false);
|
||||
});
|
||||
|
||||
it("does not fall back to classic overlay DBC when a CoA override has no base row", () => {
|
||||
expect(ascensionSpellById(940)?.dbc?.name).toBe("Area52 Overlay Payload");
|
||||
expect(coaSpellById(940)).toMatchObject({ id: 940, name: "Shared CoA Missing Base" });
|
||||
expect(coaSpellById(940)?.dbc).toBeUndefined();
|
||||
|
||||
const classicEffects = ability(940, "druid").ranks?.[0].effects ?? [];
|
||||
const coaEffects = ability(940, "sun-cleric").ranks?.[0].effects ?? [];
|
||||
expect(classicEffects.some((effect) => effect.kind === "damage")).toBe(true);
|
||||
expect(coaEffects.some((effect) => effect.kind === "damage")).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user