119 lines
4.7 KiB
JavaScript
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);
|
|
});
|