64 lines
2.6 KiB
JavaScript
64 lines
2.6 KiB
JavaScript
import { readdir, readFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
|
|
globalThis.self = globalThis;
|
|
globalThis.createImageBitmap = async () => ({ width: 1, height: 1, close() {} });
|
|
globalThis.ProgressEvent ??= class ProgressEvent {
|
|
constructor(type, init = {}) {
|
|
this.type = type;
|
|
Object.assign(this, init);
|
|
}
|
|
};
|
|
|
|
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
const assetDirectory = path.resolve(
|
|
scriptDirectory,
|
|
"../public/assets/creatures/wailing-caverns",
|
|
);
|
|
const files = (await readdir(assetDirectory))
|
|
.filter((file) => file.endsWith("-animated.glb"))
|
|
.sort();
|
|
|
|
if (files.length !== 16) throw new Error(`Expected 16 animated creature GLBs; found ${files.length}.`);
|
|
|
|
let nativeCombatModels = 0;
|
|
|
|
for (const file of files) {
|
|
const bytes = await readFile(path.join(assetDirectory, file));
|
|
const arrayBuffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
const gltf = await new Promise((resolve, reject) => {
|
|
new GLTFLoader().parse(arrayBuffer, "", resolve, reject);
|
|
});
|
|
const attack = gltf.animations.find((clip) => /^Attack(?!.*Ready)/i.test(clip.name));
|
|
const death = gltf.animations.find((clip) => /^(Death|Drown)\b/i.test(clip.name));
|
|
const run = gltf.animations.find((clip) => /^Run\b/i.test(clip.name));
|
|
const wound = gltf.animations.find((clip) => /^(?:StandWound|CombatWound|CombatCritical)\b/i.test(clip.name));
|
|
if (!attack || !death || !run || !wound) {
|
|
throw new Error(`${file}: missing a native Run, Attack, Wound, or Death clip.`);
|
|
}
|
|
nativeCombatModels += 1;
|
|
|
|
for (const clip of gltf.animations) {
|
|
// WoW includes metadata-only static states such as Lasher Submerged.
|
|
// Preserve those names; animated clips must still have positive duration.
|
|
if (clip.tracks.length > 0 && (!Number.isFinite(clip.duration) || clip.duration < 0)) {
|
|
throw new Error(`${file}: invalid duration on animation "${clip.name}".`);
|
|
}
|
|
for (const track of clip.tracks) {
|
|
if (!Array.from(track.times).every(Number.isFinite)
|
|
|| !Array.from(track.values).every(Number.isFinite)) {
|
|
throw new Error(`${file}: non-finite values in "${clip.name}".`);
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`${file}: ${gltf.animations.map((clip) => clip.name).join(", ")}`);
|
|
}
|
|
|
|
if (nativeCombatModels !== files.length) {
|
|
throw new Error(`Expected native combat clips on all ${files.length} models; found ${nativeCombatModels}.`);
|
|
}
|
|
console.log(`Validated ${files.length} GLBs with complete native locomotion/combat libraries.`);
|