151 lines
5.2 KiB
TypeScript
151 lines
5.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import referenceData from "./generated/azerothCoreReference.json";
|
|
import sourceLock from "./generated/azerothCoreSourceLock.json";
|
|
import worldAiData from "./generated/azerothCoreWorldAi.json";
|
|
import baseStatsData from "./generated/wow335BaseStats.json";
|
|
import {
|
|
creatureRuntimeBaseStats,
|
|
creatureTemplateReference,
|
|
} from "./azerothCoreReference";
|
|
|
|
interface PlayerClassRow {
|
|
readonly attributes: readonly number[];
|
|
readonly resources: readonly number[];
|
|
}
|
|
|
|
interface ReferenceShape {
|
|
readonly source: {
|
|
readonly branch: string;
|
|
readonly commit: string;
|
|
readonly dirty: boolean;
|
|
};
|
|
readonly player: {
|
|
readonly classStats: Readonly<Record<string, PlayerClassRow>>;
|
|
readonly raceOffsets: Readonly<Record<string, readonly number[]>>;
|
|
readonly attributes: Readonly<Record<string, readonly (readonly number[] | null)[]>>;
|
|
readonly resources: Readonly<Record<string, readonly (readonly number[] | null)[]>>;
|
|
readonly xpForLevel: readonly number[];
|
|
};
|
|
}
|
|
|
|
const REFERENCE = referenceData as unknown as ReferenceShape;
|
|
|
|
describe("pinned AzerothCore reference", () => {
|
|
it("locks the pinned AzerothCore snapshot and every consumed SQL file by SHA-256", () => {
|
|
expect(REFERENCE.source).toMatchObject({
|
|
branch: "master",
|
|
commit: "4d9d1d4b5723e819c5c390f6ca1177283dbfb8e3",
|
|
dirty: false,
|
|
});
|
|
expect(sourceLock.files.length).toBeGreaterThanOrEqual(18);
|
|
expect(sourceLock.source.dirtyStateWarning).toBeNull();
|
|
expect(sourceLock.schemas).toEqual({
|
|
normalizedReference: 2,
|
|
normalizedWorldAi: 1,
|
|
sourceLock: 1,
|
|
dungeonSnapshot: 2,
|
|
playerBaseStats: 2,
|
|
});
|
|
for (const file of sourceLock.files) {
|
|
expect(file.file).not.toMatch(/[\\/]/);
|
|
expect(file.sha256).toMatch(/^[a-f0-9]{64}$/);
|
|
expect(file.byteLength).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it("normalizes creature spells, SmartAI, and encounter metadata for offline builds", () => {
|
|
expect(worldAiData.schemaVersion).toBe(1);
|
|
expect(worldAiData.smartScriptsByEntry["598"]).toContainEqual(expect.objectContaining({
|
|
action_param1: 6016,
|
|
action_type: 11,
|
|
event_type: 0,
|
|
target_type: 2,
|
|
}));
|
|
expect(worldAiData.creatureSpellsByEntry["598"]).toContainEqual({
|
|
index: 0,
|
|
spellId: 6016,
|
|
verifiedBuild: 12340,
|
|
});
|
|
expect(worldAiData.encountersByMap["43"].map((encounter) => encounter.name)).toEqual(
|
|
expect.arrayContaining([
|
|
"Lady Anacondra",
|
|
"Lord Serpentis",
|
|
"Mutanus the Devourer",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("verifies every valid race/class/level primary-stat row and resource row", () => {
|
|
expect(baseStatsData.attributes).toEqual(REFERENCE.player.attributes);
|
|
expect(baseStatsData.resources).toEqual(REFERENCE.player.resources);
|
|
|
|
for (const [raceClass, rows] of Object.entries(REFERENCE.player.attributes)) {
|
|
const [race, characterClass] = raceClass.split(":");
|
|
const offsets = REFERENCE.player.raceOffsets[race];
|
|
expect(offsets, raceClass).toHaveLength(5);
|
|
expect(rows, raceClass).toHaveLength(80);
|
|
rows.forEach((attributes, index) => {
|
|
const classRow = REFERENCE.player.classStats[`${characterClass}:${index + 1}`];
|
|
if (!classRow) {
|
|
expect(attributes, `${raceClass}:${index + 1}`).toBeNull();
|
|
return;
|
|
}
|
|
expect(attributes, `${raceClass}:${index + 1}`).toEqual(
|
|
classRow.attributes.map((value, attributeIndex) => value + offsets[attributeIndex]),
|
|
);
|
|
expect(
|
|
REFERENCE.player.resources[characterClass]?.[index],
|
|
`${characterClass}:${index + 1}`,
|
|
).toEqual(classRow.resources);
|
|
});
|
|
}
|
|
});
|
|
|
|
it("ships every exact level threshold through level 79", () => {
|
|
expect(REFERENCE.player.xpForLevel).toHaveLength(79);
|
|
expect(REFERENCE.player.xpForLevel.slice(0, 5)).toEqual([400, 900, 1_400, 2_100, 2_800]);
|
|
expect(REFERENCE.player.xpForLevel[78]).toBe(1_670_800);
|
|
expect(REFERENCE.player.xpForLevel.every((value) => Number.isInteger(value) && value > 0))
|
|
.toBe(true);
|
|
});
|
|
|
|
it("derives representative normal, elite, and boss stats at multiple levels", () => {
|
|
expect(creatureRuntimeBaseStats(385, 5)).toMatchObject({
|
|
health: 102,
|
|
armor: 111,
|
|
attackPower: 18,
|
|
averageAttackDamage: 28,
|
|
});
|
|
expect(creatureRuntimeBaseStats(385, 20)).toMatchObject({
|
|
health: 484,
|
|
armor: 836,
|
|
attackPower: 70,
|
|
averageAttackDamage: 157,
|
|
});
|
|
expect(creatureRuntimeBaseStats(1720, 25)).toMatchObject({
|
|
health: 2_097,
|
|
armor: 1_008,
|
|
averageAttackDamage: 71,
|
|
});
|
|
expect(creatureRuntimeBaseStats(1720, 40)).toMatchObject({
|
|
health: 4_572,
|
|
armor: 1_938,
|
|
averageAttackDamage: 111,
|
|
});
|
|
expect(creatureRuntimeBaseStats(10162, 60)).toMatchObject({
|
|
health: 280_830,
|
|
mana: 243_400,
|
|
armor: 3_748,
|
|
averageAttackDamage: 394,
|
|
});
|
|
expect(creatureRuntimeBaseStats(10162, 63)).toMatchObject({
|
|
health: 306_475,
|
|
mana: 262_000,
|
|
armor: 4_637,
|
|
averageAttackDamage: 416,
|
|
});
|
|
expect(creatureTemplateReference(1720)?.rank).toBe(2);
|
|
expect(creatureTemplateReference(10162)?.rank).toBe(3);
|
|
});
|
|
});
|