102 lines
4.3 KiB
TypeScript
102 lines
4.3 KiB
TypeScript
import { existsSync, readFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { FORSAKEN_ABBEY_DEFINITION } from "./dungeons/forsakenAbbey";
|
|
import {
|
|
FORSAKEN_ABBEY_BOSSES,
|
|
FORSAKEN_ABBEY_ENTITIES,
|
|
FORSAKEN_ABBEY_POPULATION_PROVENANCE,
|
|
FORSAKEN_ABBEY_STATIC_SPAWNS,
|
|
} from "./generated/forsakenAbbeyPopulation.generated";
|
|
|
|
const projectFile = (...parts: string[]) => path.join(process.cwd(), ...parts);
|
|
const snapshot = JSON.parse(readFileSync(projectFile(
|
|
"scripts",
|
|
"runewaker-pipeline",
|
|
"snapshots",
|
|
"forsaken-abbey-zone-102.json",
|
|
), "utf8"));
|
|
const report = JSON.parse(readFileSync(projectFile(
|
|
"src",
|
|
"assets",
|
|
"game",
|
|
"dungeons",
|
|
"forsaken-abbey",
|
|
"population-import-report.json",
|
|
), "utf8"));
|
|
|
|
describe("Forsaken Abbey portable RuneWaker population", () => {
|
|
it("accounts for every active Zone 102 source row reproducibly", () => {
|
|
expect(snapshot.rowCount).toBe(251);
|
|
expect(snapshot.rows).toHaveLength(251);
|
|
expect(new Set(snapshot.rows.map((row: { spawnId: number }) => row.spawnId)).size).toBe(251);
|
|
expect(snapshot.contentSha256)
|
|
.toBe("13ef6edd3df6e71389aa92d01201889148c0794cf9ecaafdbaf90d3145d2bf7b");
|
|
|
|
const classifications = snapshot.rows.reduce(
|
|
(counts: Record<string, number>, row: { classification: string }) => {
|
|
counts[row.classification] = (counts[row.classification] ?? 0) + 1;
|
|
return counts;
|
|
},
|
|
{},
|
|
);
|
|
expect(classifications).toEqual({
|
|
combat: 142,
|
|
boss: 8,
|
|
"deferred-object": 101,
|
|
});
|
|
});
|
|
|
|
it("generates unique static combat spawns without AzerothCore id collisions", () => {
|
|
expect(FORSAKEN_ABBEY_STATIC_SPAWNS).toHaveLength(150);
|
|
expect(new Set(FORSAKEN_ABBEY_STATIC_SPAWNS.map((spawn) => spawn.id)).size).toBe(150);
|
|
expect(Object.keys(FORSAKEN_ABBEY_ENTITIES)).toHaveLength(15);
|
|
for (const entity of Object.values(FORSAKEN_ABBEY_ENTITIES)) {
|
|
expect(entity.id).toMatch(/^rw-fa-[0-9]+$/);
|
|
expect(entity.combat).not.toHaveProperty("serverEntry");
|
|
expect(entity.visual.model.url.startsWith("/assets/creatures/forsaken-abbey/")).toBe(true);
|
|
expect(entity.visual.model.url.endsWith(".glb")).toBe(true);
|
|
expect(entity.visual.model.animationMode).toBe("native");
|
|
expect(existsSync(projectFile("public", entity.visual.model.url.slice(1)))).toBe(true);
|
|
}
|
|
for (const spawn of FORSAKEN_ABBEY_STATIC_SPAWNS) {
|
|
expect(spawn.id).toMatch(/^rw-fa-spawn-[0-9]+$/);
|
|
expect(spawn.position.every(Number.isFinite)).toBe(true);
|
|
expect(Number.isFinite(spawn.yaw)).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("classifies the authoritative five boss templates into eight objectives", () => {
|
|
const bossTemplateIds = [...new Set<number>(snapshot.rows
|
|
.filter((row: { classification: string }) => row.classification === "boss")
|
|
.map((row: { templateId: number }) => row.templateId))]
|
|
.sort((a, b) => a - b);
|
|
expect(bossTemplateIds).toEqual([100070, 100074, 100561, 100562, 100563]);
|
|
expect(FORSAKEN_ABBEY_BOSSES).toHaveLength(8);
|
|
expect(FORSAKEN_ABBEY_BOSSES.filter((boss) => boss.name.startsWith("Necromage Keeper")))
|
|
.toHaveLength(4);
|
|
for (const boss of FORSAKEN_ABBEY_BOSSES) {
|
|
const spawn = FORSAKEN_ABBEY_STATIC_SPAWNS.find((candidate) => candidate.id === boss.id);
|
|
expect(spawn).toBeDefined();
|
|
expect(FORSAKEN_ABBEY_ENTITIES[spawn!.entityId].kind).toBe("boss");
|
|
}
|
|
});
|
|
|
|
it("projects every source row and keeps all combat spawns within two meters", () => {
|
|
expect(report.records).toHaveLength(251);
|
|
expect(report.excluded).toHaveLength(101);
|
|
expect(FORSAKEN_ABBEY_POPULATION_PROVENANCE.navmeshProjection.maximumCombatDistance)
|
|
.toBeLessThanOrEqual(2);
|
|
expect(report.records.every((row: { projectedPosition: number[] }) => (
|
|
row.projectedPosition.length === 3 && row.projectedPosition.every(Number.isFinite)
|
|
))).toBe(true);
|
|
});
|
|
|
|
it("is the packaged population used by the playable dungeon definition", () => {
|
|
expect(FORSAKEN_ABBEY_DEFINITION.availability).toBe("available");
|
|
expect(FORSAKEN_ABBEY_DEFINITION.entities).toBe(FORSAKEN_ABBEY_ENTITIES);
|
|
expect(FORSAKEN_ABBEY_DEFINITION.staticSpawns).toBe(FORSAKEN_ABBEY_STATIC_SPAWNS);
|
|
expect(FORSAKEN_ABBEY_DEFINITION.bosses).toBe(FORSAKEN_ABBEY_BOSSES);
|
|
});
|
|
});
|