Files
dwroller/scripts/import-40krpgtools-hordes.js

119 lines
4.7 KiB
JavaScript

#!/usr/bin/env node
/*
* Import horde entries from the staged 40k RPG Tools index as compact
* reviewable bestiary rows. These are derived entries, not PDF-parsed
* statblocks, so they are flagged as hordes and kept intentionally small.
*/
const fs = require('fs');
const path = require('path');
const { bestiaryHelpers, initializationPromise } = require('../database/mariadb');
const ROOT = path.join(__dirname, '..');
const INDEX_PATH = path.join(ROOT, 'database', 'remote-extract', '40krpgtools-bestiary-index.json');
const PUBLIC_PATH = path.join(ROOT, 'public', 'deathwatch-bestiary-extracted.json');
const DB_PATH = path.join(ROOT, 'database', 'deathwatch-bestiary-extracted.json');
const HORDE_SPECS = {
'Mutant Horde': { magnitude: 20, profile: { ws: 30, bs: 20, s: 30, t: 30, ag: 30, int: 20, per: 30, wp: 30, fel: 20 }, damage: '1d10+3 I', pen: 0, armour: 3, tb: 3 },
'PDF Guardsman Horde': { magnitude: 20, profile: { ws: 30, bs: 35, s: 30, t: 30, ag: 30, int: 30, per: 30, wp: 30, fel: 25 }, damage: '1d10+3 I', pen: 0, armour: 4, tb: 3 },
'Rebel Horde': { magnitude: 20, profile: { ws: 30, bs: 30, s: 30, t: 30, ag: 30, int: 25, per: 30, wp: 30, fel: 25 }, damage: '1d10+3 I', pen: 0, armour: 4, tb: 3 },
'Kroot Carnivore Horde': { magnitude: 15, profile: { ws: 40, bs: 20, s: 35, t: 30, ag: 40, int: 20, per: 35, wp: 30, fel: 10 }, damage: '1d10+4 R', pen: 0, armour: 3, tb: 3 },
'Regressed Kroot Carnivore Horde': { magnitude: 15, profile: { ws: 40, bs: 20, s: 35, t: 30, ag: 40, int: 20, per: 35, wp: 30, fel: 10 }, damage: '1d10+4 R', pen: 0, armour: 3, tb: 3 },
'Hromagaunt Horde': { magnitude: 20, profile: { ws: 45, bs: 20, s: 35, t: 30, ag: 55, int: 10, per: 40, wp: 30, fel: 5 }, damage: '1d10+5 R', pen: 0, armour: 3, tb: 3 },
'Termagaunt Horde': { magnitude: 20, profile: { ws: 30, bs: 30, s: 30, t: 30, ag: 30, int: 10, per: 30, wp: 30, fel: 5 }, damage: '1d10+4 R', pen: 0, armour: 3, tb: 3 },
'Gargoyle Horde': { magnitude: 20, profile: { ws: 30, bs: 33, s: 32, t: 30, ag: 40, int: 10, per: 40, wp: 30, fel: 0 }, damage: '1d10+5 R', pen: 3, armour: 3, tb: 3 },
};
function loadJson(file) {
const raw = JSON.parse(fs.readFileSync(file, 'utf8'));
const arr = Array.isArray(raw) ? raw : (raw.results || raw.entries || raw.items || []);
if (!Array.isArray(arr)) throw new Error(`Unsupported bestiary JSON shape: ${file}`);
return { raw, arr };
}
function norm(value) {
return String(value || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
}
function appendHordeEntries(arr, indexRows) {
const existing = new Set(arr.map(entry => norm(entry.bestiaryName || entry.name)).filter(Boolean));
const added = [];
for (const row of indexRows.filter(r => /horde/i.test(String(r.name || '')))) {
const key = norm(row.name);
if (existing.has(key)) continue;
const spec = HORDE_SPECS[row.name] || {
magnitude: 20,
profile: { ws: 30, bs: 25, s: 30, t: 30, ag: 30, int: 20, per: 30, wp: 30, fel: 20 },
damage: '1d10+3 I',
pen: 0,
armour: 3,
tb: 3,
};
const stats = {
horde: true,
magnitude: spec.magnitude,
profile: spec.profile,
wounds: spec.magnitude,
toughnessBonus: spec.tb,
armour: spec.armour,
statSource: 'index-horde',
needsReview: true,
sourceIndex: '40krpgtools-horde-index',
threatTier: 'Horde',
weapons: `Massed attacks (${spec.damage}; Pen ${spec.pen})`,
};
arr.push({
bestiaryName: row.name,
type: row.type,
affiliation: row.affiliation,
book: row.book,
page: row.page,
sourceUrl: row.sourceUrl,
wounds: spec.magnitude,
stats,
horde: true,
magnitude: spec.magnitude,
});
existing.add(key);
added.push(row.name);
}
return added;
}
async function main() {
const index = JSON.parse(fs.readFileSync(INDEX_PATH, 'utf8'));
const hordeRows = (index.entries || []).filter(row => /horde/i.test(String(row.name || '')));
const { raw, arr } = loadJson(PUBLIC_PATH);
const added = appendHordeEntries(arr, hordeRows);
let payload;
if (Array.isArray(raw)) {
payload = arr;
} else {
raw.results = arr;
raw.count = arr.length;
raw.generatedAt = new Date().toISOString();
payload = raw;
}
const serialized = JSON.stringify(payload, null, 2);
for (const file of [PUBLIC_PATH, DB_PATH]) {
if (fs.existsSync(file)) fs.writeFileSync(file + '.pre-horde.' + Date.now() + '.json', fs.readFileSync(file));
fs.writeFileSync(file, serialized);
}
await initializationPromise;
await bestiaryHelpers.replaceAll(arr);
console.log(`Imported ${added.length} horde entries into bestiary`);
added.forEach(name => console.log(` + ${name}`));
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});