84 lines
3.4 KiB
JavaScript
84 lines
3.4 KiB
JavaScript
// build-index.mjs — Node.js utility script
|
|
// Scans portraits/ and regenerates portraits/index.json
|
|
// Run with: node scripts/build-index.mjs
|
|
// (This script is NOT loaded by Foundry — it's a dev tool only.)
|
|
|
|
import { readdir, writeFile } from 'fs/promises';
|
|
import { join, extname, basename, dirname } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const MODULE_DIR = join(__dirname, '..');
|
|
const PORTRAITS = join(MODULE_DIR, 'portraits');
|
|
const MODULE_ID = 'npc-portrait-pack';
|
|
|
|
// ── Category classification ────────────────────────────────────────────
|
|
function classify(filename) {
|
|
const name = basename(filename, '.png');
|
|
|
|
// Named NPC: npc_NNNN_description
|
|
if (/^npc_\d+_/.test(name)) {
|
|
const parts = name.split('_');
|
|
const tags = [];
|
|
let gender = null;
|
|
for (const p of parts.slice(2)) {
|
|
if (p === 'male' || p === 'female') { gender = p; tags.unshift(p); }
|
|
else if (p === 'elderly') { tags.push('elderly'); }
|
|
else { tags.push(p); }
|
|
}
|
|
return { category: 'named_npc', race: 'human', gender, tags };
|
|
}
|
|
|
|
// Race + gender: race_gender_NN
|
|
const rg = name.match(/^(human|ebon|frother|shaktar|stormer|waister|wraith)_(male|female)_\d+$/);
|
|
if (rg) {
|
|
const [, race, gender] = rg;
|
|
return { category: race, race, gender, tags: [gender] };
|
|
}
|
|
|
|
if (/^creature_\d+$/.test(name)) return { category: 'creature', race: 'creature', gender: null, tags: ['creature'] };
|
|
if (/^mutant_\d+$/.test(name)) return { category: 'creature', race: 'mutant', gender: null, tags: ['mutant','creature'] };
|
|
if (/^serial_killer_\d+$/.test(name))return { category: 'serial_killer', race: 'human', gender: null, tags: ['serial_killer','threat'] };
|
|
if (/^portrait_\d+$/.test(name)) return { category: 'generic', race: 'human', gender: null, tags: [] };
|
|
|
|
return { category: 'generic', race: 'unknown', gender: null, tags: [] };
|
|
}
|
|
|
|
// ── Main ───────────────────────────────────────────────────────────────
|
|
async function buildIndex() {
|
|
const files = await readdir(PORTRAITS);
|
|
const pngFiles = files.filter(f => extname(f).toLowerCase() === '.png').sort();
|
|
|
|
const portraits = pngFiles.map(filename => {
|
|
const id = basename(filename, '.png');
|
|
const { category, race, gender, tags } = classify(filename);
|
|
return {
|
|
id,
|
|
path: `modules/${MODULE_ID}/portraits/${filename}`,
|
|
category,
|
|
race,
|
|
gender,
|
|
tags,
|
|
};
|
|
});
|
|
|
|
portraits.sort((a, b) => a.id.localeCompare(b.id));
|
|
|
|
const index = { version: '1.0.0', count: portraits.length, portraits };
|
|
const outputPath = join(PORTRAITS, 'index.json');
|
|
await writeFile(outputPath, JSON.stringify(index, null, 2), 'utf-8');
|
|
|
|
// Summary
|
|
const cats = portraits.reduce((acc, p) => {
|
|
acc[p.category] = (acc[p.category] ?? 0) + 1;
|
|
return acc;
|
|
}, {});
|
|
console.log(`✓ Built index: ${portraits.length} portraits → ${outputPath}`);
|
|
console.log('\nBy category:');
|
|
for (const [cat, count] of Object.entries(cats).sort()) {
|
|
console.log(` ${cat.padEnd(15)} ${count}`);
|
|
}
|
|
}
|
|
|
|
buildIndex().catch(err => { console.error('Error:', err); process.exit(1); });
|