145 lines
5.4 KiB
JavaScript
145 lines
5.4 KiB
JavaScript
import { createHash } from "node:crypto";
|
|
import { spawn } from "node:child_process";
|
|
import { access, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
const projectRoot = path.resolve(scriptDirectory, "../..");
|
|
const manastormRoot = path.join(projectRoot, "public/assets/game/manastorm");
|
|
const dungeonRoot = path.join(projectRoot, "src/assets/game/dungeons");
|
|
const manastormRegistry = path.join(projectRoot, "src/game/generated/manastormAssetPackages.json");
|
|
|
|
async function exists(file) {
|
|
try {
|
|
await access(file);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function readJson(file) {
|
|
return JSON.parse(await readFile(file, "utf8"));
|
|
}
|
|
|
|
async function writeJson(file, value) {
|
|
await writeFile(file, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
}
|
|
|
|
async function sha256(file) {
|
|
return createHash("sha256").update(await readFile(file)).digest("hex");
|
|
}
|
|
|
|
async function refreshManastorm() {
|
|
const packages = [];
|
|
let visuals = 0;
|
|
for (const entry of await readdir(manastormRoot, { withFileTypes: true })) {
|
|
if (!entry.isDirectory()) continue;
|
|
const manifestFile = path.join(manastormRoot, entry.name, "stage-pack.json");
|
|
if (!await exists(manifestFile)) continue;
|
|
const manifest = await readJson(manifestFile);
|
|
for (const descriptor of manifest.visual ?? []) {
|
|
const relativeUrl = descriptor.url.replace(/^\/+/, "");
|
|
const file = path.join(projectRoot, "public", relativeUrl);
|
|
descriptor.byteLength = (await stat(file)).size;
|
|
descriptor.checksum = await sha256(file);
|
|
descriptor.compression = "meshopt+ktx2";
|
|
visuals += 1;
|
|
}
|
|
await writeJson(manifestFile, manifest);
|
|
packages.push(manifest);
|
|
}
|
|
packages.sort((left, right) => left.id.localeCompare(right.id));
|
|
await writeJson(manastormRegistry, { schemaVersion: 1, packages });
|
|
return { packages, visuals };
|
|
}
|
|
|
|
async function refreshCanonicalDungeonEnvironments(packages) {
|
|
const packageById = new Map(packages.map((assetPackage) => [assetPackage.id, assetPackage]));
|
|
const publicDungeonRoot = path.join(projectRoot, "public/assets/game/dungeons");
|
|
let manifests = 0;
|
|
for (const entry of await readdir(publicDungeonRoot, { withFileTypes: true })) {
|
|
if (!entry.isDirectory()) continue;
|
|
const manifestFile = path.join(publicDungeonRoot, entry.name, "dungeon-pack.json");
|
|
if (!await exists(manifestFile)) continue;
|
|
const manifest = await readJson(manifestFile);
|
|
const canonical = packageById.get(manifest.environment?.id);
|
|
if (!canonical) continue;
|
|
manifest.environment = canonical;
|
|
await writeJson(manifestFile, manifest);
|
|
manifests += 1;
|
|
}
|
|
return manifests;
|
|
}
|
|
|
|
function compileCampaignCatalog() {
|
|
return new Promise((resolve, reject) => {
|
|
const compiler = path.join(projectRoot, "scripts/dungeon-pipeline/compile-campaign-catalog.mjs");
|
|
const child = spawn(process.execPath, [compiler], {
|
|
cwd: projectRoot,
|
|
windowsHide: true,
|
|
stdio: "inherit",
|
|
});
|
|
child.once("error", reject);
|
|
child.once("exit", (code) => code === 0
|
|
? resolve()
|
|
: reject(new Error(`Campaign catalog compiler exited with ${code}.`)));
|
|
});
|
|
}
|
|
|
|
async function refreshDungeonImports() {
|
|
let imports = 0;
|
|
let optimizationReports = 0;
|
|
for (const entry of await readdir(dungeonRoot, { withFileTypes: true })) {
|
|
if (!entry.isDirectory()) continue;
|
|
const directory = path.join(dungeonRoot, entry.name);
|
|
const visualName = (await readdir(directory)).find((name) => /-visual\.glb$/i.test(name));
|
|
if (!visualName) continue;
|
|
const visualFile = path.join(directory, visualName);
|
|
const visualStat = await stat(visualFile);
|
|
const visualHash = await sha256(visualFile);
|
|
|
|
const importFile = path.join(directory, "import-metadata.json");
|
|
if (await exists(importFile)) {
|
|
const metadata = await readJson(importFile);
|
|
const descriptor = metadata.assets?.find((asset) => asset.role === "visual");
|
|
if (descriptor) {
|
|
descriptor.fileName = visualName;
|
|
descriptor.size = visualStat.size;
|
|
descriptor.sha256 = visualHash;
|
|
descriptor.compression = "meshopt+ktx2";
|
|
}
|
|
await writeJson(importFile, metadata);
|
|
imports += 1;
|
|
}
|
|
|
|
const optimizationFile = path.join(directory, "optimization-report.json");
|
|
if (await exists(optimizationFile)) {
|
|
const report = await readJson(optimizationFile);
|
|
report.optimized = {
|
|
...(report.optimized ?? {}),
|
|
size: visualStat.size,
|
|
checksum: visualHash,
|
|
};
|
|
report.compression = "meshopt+ktx2";
|
|
const command = "node scripts/asset-pipeline/compress-ktx2.mjs <optimized.glb> --concurrency 1 --jobs 2";
|
|
report.commands = [...new Set([...(report.commands ?? []), command])];
|
|
await writeJson(optimizationFile, report);
|
|
optimizationReports += 1;
|
|
}
|
|
}
|
|
return { imports, optimizationReports };
|
|
}
|
|
|
|
const manastorm = await refreshManastorm();
|
|
const canonicalDungeonEnvironments = await refreshCanonicalDungeonEnvironments(manastorm.packages);
|
|
const dungeons = await refreshDungeonImports();
|
|
await compileCampaignCatalog();
|
|
console.log(JSON.stringify({
|
|
status: "green",
|
|
manastorm: { packages: manastorm.packages.length, visuals: manastorm.visuals },
|
|
canonicalDungeonEnvironments,
|
|
dungeons,
|
|
}, null, 2));
|