You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
130 lines
4.5 KiB
JavaScript
130 lines
4.5 KiB
JavaScript
/**
|
|
* Ensures one south-facing enemy PNG per DB template (enemies.type).
|
|
* 1) Downloads archetype reference sprites from PixelLab MCP URLs (manifest pixellabObjectId).
|
|
* 2) Copies the right archetype PNG to enemies/enemy.<slug>.south.png for every row in 000006b_enemy_data.sql.
|
|
* 3) Merges manifest.json entries for all enemy.<slug>.south keys.
|
|
*
|
|
* For per-slug PixelLab generations (init_image + unique prompt), use pixellab-enemy-south-v2.mjs with PIXELLAB_API_TOKEN.
|
|
*/
|
|
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const ROOT = path.join(__dirname, '..');
|
|
const SQL = path.join(ROOT, 'backend/migrations/000006b_enemy_data.sql');
|
|
const MANIFEST = path.join(ROOT, 'frontend/public/assets/game/manifest.json');
|
|
const ASSETS_ENEMIES = path.join(ROOT, 'frontend/assets/enemies');
|
|
|
|
const ARCHETYPE_REF = {
|
|
wolf: 'enemy.wolf',
|
|
boar: 'enemy.boar',
|
|
zombie: 'enemy.zombie',
|
|
spider: 'enemy.spider',
|
|
orc: 'enemy.orc',
|
|
skeleton: 'enemy.skeleton_archer',
|
|
battle_lizard: 'enemy.battle_lizard',
|
|
element: 'enemy.water_element',
|
|
demon: 'enemy.fire_demon',
|
|
skeleton_king: 'enemy.skeleton_king',
|
|
forest_warden: 'enemy.forest_warden',
|
|
titan: 'enemy.lightning_titan',
|
|
bandit: 'enemy.orc',
|
|
cultist: 'enemy.skeleton_archer',
|
|
golem: 'enemy.ice_guardian',
|
|
wraith: 'enemy.zombie',
|
|
treant: 'enemy.forest_warden',
|
|
basilisk: 'enemy.battle_lizard',
|
|
wyvern: 'enemy.battle_lizard',
|
|
harpy: 'enemy.spider',
|
|
manticore: 'enemy.battle_lizard',
|
|
shade: 'enemy.zombie',
|
|
};
|
|
|
|
function parseEnemyRows(sqlText) {
|
|
const re =
|
|
/VALUES\s*\(\d+,\s*'([^']+)',\s*'([^']+)',\s*'([^']+)',\s*'([^']*)'/g;
|
|
const rows = [];
|
|
let m;
|
|
while ((m = re.exec(sqlText))) {
|
|
rows.push({ type: m[1], archetype: m[2], biome: m[3], name: m[4] });
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
async function downloadFile(url, destPath) {
|
|
const res = await fetch(url, { redirect: 'follow' });
|
|
if (!res.ok) throw new Error(`GET ${url} -> ${res.status}`);
|
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
fs.writeFileSync(destPath, buf);
|
|
}
|
|
|
|
async function main() {
|
|
const sql = fs.readFileSync(SQL, 'utf8');
|
|
const rows = parseEnemyRows(sql);
|
|
if (rows.length !== 220) {
|
|
throw new Error(`Expected 220 enemy rows, got ${rows.length}`);
|
|
}
|
|
|
|
const manifest = JSON.parse(fs.readFileSync(MANIFEST, 'utf8'));
|
|
const textures = manifest.textures;
|
|
|
|
const refKeys = [...new Set(Object.values(ARCHETYPE_REF))];
|
|
for (const refKey of refKeys) {
|
|
const entry = textures[refKey];
|
|
if (!entry?.pixellabObjectId || !entry?.file) {
|
|
throw new Error(`Manifest missing reference ${refKey}`);
|
|
}
|
|
const dest = path.join(ROOT, 'frontend/assets', entry.file);
|
|
if (fs.existsSync(dest) && fs.statSync(dest).size > 0) continue;
|
|
const url = `https://api.pixellab.ai/mcp/map-objects/${entry.pixellabObjectId}/download`;
|
|
console.log('Download', refKey, '->', entry.file);
|
|
await downloadFile(url, dest);
|
|
}
|
|
|
|
fs.mkdirSync(ASSETS_ENEMIES, { recursive: true });
|
|
|
|
for (const row of rows) {
|
|
const refManifestKey = ARCHETYPE_REF[row.archetype];
|
|
if (!refManifestKey) {
|
|
throw new Error(`No ARCHETYPE_REF for archetype ${row.archetype}`);
|
|
}
|
|
const refEntry = textures[refManifestKey];
|
|
const refAbs = path.join(ROOT, 'frontend/assets', refEntry.file);
|
|
if (!fs.existsSync(refAbs)) throw new Error(`Missing ref file ${refAbs}`);
|
|
|
|
const southFile = `enemies/enemy.${row.type}.south.png`;
|
|
const southAbs = path.join(ROOT, 'frontend/assets', southFile);
|
|
const texKey = `enemy.${row.type}.south`;
|
|
const existing = textures[texKey];
|
|
|
|
if (existing?.pixellabObjectId) {
|
|
const url = `https://api.pixellab.ai/mcp/map-objects/${existing.pixellabObjectId}/download`;
|
|
await downloadFile(url, southAbs);
|
|
} else {
|
|
fs.copyFileSync(refAbs, southAbs);
|
|
}
|
|
|
|
textures[texKey] = {
|
|
file: southFile,
|
|
kind: 'map_object',
|
|
rotation: 'south',
|
|
...(existing?.pixellabObjectId
|
|
? { pixellabObjectId: existing.pixellabObjectId }
|
|
: {}),
|
|
};
|
|
}
|
|
|
|
manifest.version = (manifest.version ?? 0) + 1;
|
|
|
|
fs.writeFileSync(MANIFEST, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
console.log('Wrote manifest version', manifest.version, 'with', Object.keys(textures).filter((k) => k.endsWith('.south') && k.startsWith('enemy.')).length, 'enemy south keys');
|
|
}
|
|
|
|
main().catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
});
|