forked from phenom/healer-man
108 lines
5.5 KiB
TypeScript
108 lines
5.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import generated from "./romPlayerCatalog.generated.json";
|
|
import audit from "../../scripts/runewaker-pipeline/snapshots/runewaker-player-skill-audit.json";
|
|
import {
|
|
abilitiesForCharacter,
|
|
abilityAtLevel,
|
|
defaultActionBarForCharacter,
|
|
} from "./abilityCatalog";
|
|
import { ROM_ALL_ABILITIES, ROM_ELITE_ABILITIES } from "./romAbilityCatalog";
|
|
import { romDualClassBaseStats, romNativeClassBaseStats } from "./romStats";
|
|
import { EMPTY_ITEM_STATS } from "./itemStats";
|
|
import { deriveCharacterStats } from "./wotlkStats";
|
|
|
|
const RACE_MATRIX = {
|
|
"rom-human": ["rom-warrior", "rom-scout", "rom-rogue", "rom-mage", "rom-priest", "rom-knight"],
|
|
"rom-elf": ["rom-warrior", "rom-scout", "rom-rogue", "rom-mage", "rom-warden", "rom-druid"],
|
|
"rom-dwarf": ["rom-warrior", "rom-rogue", "rom-mage", "rom-priest", "rom-warlock", "rom-champion"],
|
|
};
|
|
const ELITE_LEVELS = [15, 20, 25, 30, 35, 40, 45, 50, 60, 70];
|
|
|
|
describe("generated RuneWaker player catalog", () => {
|
|
it("imports the exact released roster and race eligibility without Duelist", () => {
|
|
expect(generated.canonicalClasses.map((entry) => entry.name)).toEqual([
|
|
"Warrior", "Scout", "Rogue", "Mage", "Priest", "Knight", "Warden", "Druid", "Warlock", "Champion",
|
|
]);
|
|
expect(generated.canonicalClasses.some((entry) => entry.name === "Duelist")).toBe(false);
|
|
expect(generated.raceMatrix).toEqual(RACE_MATRIX);
|
|
expect(generated.eliteLevels).toEqual(ELITE_LEVELS);
|
|
expect(audit.coverage).toMatchObject({
|
|
releasedClasses: 10,
|
|
duelistExcluded: true,
|
|
races: 3,
|
|
orderedPairs: 66,
|
|
eliteMilestones: 660,
|
|
ordinaryFamilies: 269,
|
|
iconResolved: 929,
|
|
uniqueNativeIcons: 918,
|
|
unresolvedVisible: 0,
|
|
});
|
|
expect(audit.hashes.interfaceFdb).toMatch(/^[a-f0-9]{64}$/);
|
|
});
|
|
|
|
it("resolves every ordered pair to all ten executable elite milestones", () => {
|
|
expect(ROM_ELITE_ABILITIES).toHaveLength(660);
|
|
const pairGroups = new Map<string, typeof ROM_ELITE_ABILITIES[number][]>();
|
|
for (const ability of ROM_ELITE_ABILITIES) {
|
|
const key = `${ability.classId}:${ability.secondaryClassId}`;
|
|
pairGroups.set(key, [...(pairGroups.get(key) ?? []), ability]);
|
|
}
|
|
expect(pairGroups.size).toBe(66);
|
|
for (const elites of pairGroups.values()) {
|
|
expect(elites.map((ability) => ability.unlockLevel)).toEqual(ELITE_LEVELS);
|
|
expect(elites.every((ability) => ability.source === "runewaker" && ability.effects.length > 0)).toBe(true);
|
|
}
|
|
expect(ROM_ALL_ABILITIES).toHaveLength(929);
|
|
expect(ROM_ALL_ABILITIES.every((ability) => (
|
|
/^\/assets\/ui\/rom-spells\/\d+\.png$/.test(ability.icon)
|
|
&& ability.icon === generated.skills.find((skill) => skill.id === ability.id)?.icon
|
|
))).toBe(true);
|
|
expect(generated.skills.every((ability) => (
|
|
ability.iconResolution === "native-interface-fdb"
|
|
&& /^\/assets\/ui\/rom-spells\/\d+\.png$/.test(ability.icon)
|
|
))).toBe(true);
|
|
});
|
|
|
|
it("composes only primary, secondary-general, and active-orientation elite skills", () => {
|
|
const warriorScout = abilitiesForCharacter("rom-warrior", "rom-scout");
|
|
const scoutWarrior = abilitiesForCharacter("rom-scout", "rom-warrior");
|
|
expect(warriorScout.some((ability) => ability.classId === "rom-scout" && ability.romSkillGroup === "primary")).toBe(false);
|
|
expect(warriorScout.some((ability) => ability.classId === "rom-scout" && ability.romSkillGroup === "general")).toBe(true);
|
|
expect(warriorScout.filter((ability) => ability.secondaryClassId)).toHaveLength(10);
|
|
expect(warriorScout.filter((ability) => ability.secondaryClassId).every((ability) => (
|
|
ability.classId === "rom-warrior" && ability.secondaryClassId === "rom-scout"
|
|
))).toBe(true);
|
|
expect(scoutWarrior.filter((ability) => ability.secondaryClassId).every((ability) => (
|
|
ability.classId === "rom-scout" && ability.secondaryClassId === "rom-warrior"
|
|
))).toBe(true);
|
|
expect(defaultActionBarForCharacter("rom-warrior", "rom-scout", 14).some((id) => id.includes("-elite-"))).toBe(false);
|
|
expect(defaultActionBarForCharacter("rom-warrior", "rom-scout", 15).filter((id) => id.includes("-elite-"))).toHaveLength(1);
|
|
});
|
|
|
|
it("selects the highest eligible source rank automatically", () => {
|
|
const ranked = ROM_ALL_ABILITIES.find((ability) => (ability.ranks?.length ?? 0) > 1)!;
|
|
const eligible = ranked.ranks!.filter((rank) => rank.level <= 80).at(-1)!;
|
|
expect(abilityAtLevel(ranked, 80).dbcSpellId).toBe(eligible.spellId);
|
|
});
|
|
|
|
it("adds exactly ten percent of the secondary class base stats", () => {
|
|
const primary = romNativeClassBaseStats("rom-warrior", 40);
|
|
const secondary = romNativeClassBaseStats("rom-scout", 40);
|
|
const combined = romDualClassBaseStats("rom-warrior", "rom-scout", 40)!;
|
|
for (const stat of Object.keys(primary) as (keyof typeof primary)[]) {
|
|
expect(combined[stat]).toBeCloseTo(primary[stat] + secondary[stat] * 0.1, 8);
|
|
}
|
|
});
|
|
|
|
it("gives level-one healers native mana pools and imports their real spell costs", () => {
|
|
expect(deriveCharacterStats("rom-priest", 1, EMPTY_ITEM_STATS, "rom-human").mana).toBe(103);
|
|
expect(deriveCharacterStats("rom-druid", 1, EMPTY_ITEM_STATS, "rom-elf").mana).toBe(86);
|
|
expect(generated.skills.find((ability) => ability.id === "rom-priest-general-1")?.costs).toEqual([
|
|
{ resource: "mana", amount: 12, percentage: false },
|
|
]);
|
|
expect(generated.skills.find((ability) => ability.id === "rom-druid-primary-0")?.costs).toEqual([
|
|
{ resource: "mana", amount: 12, percentage: false },
|
|
]);
|
|
});
|
|
});
|