129 lines
4.3 KiB
JavaScript
129 lines
4.3 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const dbApi = require('../db.js');
|
|
const { parseMarkdownFile } = require('../lib/markdownTableParser.js');
|
|
|
|
const TABLES_DIR = path.join(__dirname, '..', 'tables');
|
|
|
|
const NPC_CORE_COLUMNS = [
|
|
'first name', 'last name', 'brief description', 'wants and needs', 'secret or obstacle', 'also carrying',
|
|
];
|
|
|
|
const NPC_ATTRIBUTE_PATTERNS = [
|
|
[/\bpronoun/i, 'npc_pronouns'],
|
|
[/\bage\b/i, 'npc_age'],
|
|
[/\bintelligence\b/i, 'npc_intelligence'],
|
|
[/^hair$/i, 'npc_hair'],
|
|
[/^build$/i, 'npc_build'],
|
|
[/^race$/i, 'npc_race'],
|
|
];
|
|
|
|
const LAYER1_LINKS = [
|
|
[/weather turns/i, 'Layer 2C: Weather Turns (d20)'],
|
|
[/encounter a person/i, 'Layer 2A: Person Encountered (d20)'],
|
|
[/encounter a group/i, 'Layer 2B: Group Encountered (d20)'],
|
|
[/find something/i, 'Layer 2D: Find Something (d100)'],
|
|
[/natural hazard/i, 'Layer 2E: Natural Hazard (d20)'],
|
|
[/signs of recent violence/i, 'Layer 2F: Signs of Recent Violence (d20)'],
|
|
[/pursuit or being followed/i, 'Layer 2G: Pursuit \/ Being Followed (d20)'],
|
|
[/physical hardship/i, 'Layer 2H: Physical Hardship (d20)'],
|
|
[/something uncanny/i, 'Layer 2I: Something Uncanny (d20)'],
|
|
];
|
|
|
|
function isNpcCoreTable(columns) {
|
|
const norm = columns.map((c) => c.toLowerCase().trim());
|
|
return NPC_CORE_COLUMNS.every((req) => norm.includes(req));
|
|
}
|
|
|
|
function detectNpcRole(headingText, columns, insideNpcSection) {
|
|
if (isNpcCoreTable(columns)) return 'npc_core';
|
|
if (!insideNpcSection) return null;
|
|
const trimmed = headingText.trim();
|
|
for (const [pattern, role] of NPC_ATTRIBUTE_PATTERNS) {
|
|
if (pattern.test(trimmed)) return role;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const stats = { files: 0, nodes: 0, tables: 0, rows: 0, links: 0 };
|
|
|
|
function insertNode(node, sourceFile, parentId, sortOrder, insideNpcSection) {
|
|
const treeNodeId = dbApi.tables.insertTreeNode({
|
|
source_file: sourceFile,
|
|
parent_id: parentId,
|
|
heading_text: node.headingText,
|
|
heading_level: node.level,
|
|
sort_order: sortOrder,
|
|
});
|
|
stats.nodes++;
|
|
|
|
const childInsideNpc = insideNpcSection || /random npc generator/i.test(node.headingText || '');
|
|
|
|
if (node.table) {
|
|
const npcRole = detectNpcRole(node.headingText, node.table.columns, childInsideNpc);
|
|
const tableId = dbApi.tables.insertTable({
|
|
tree_node_id: treeNodeId,
|
|
dice_notation: node.table.diceNotation,
|
|
table_type: node.table.tableType,
|
|
npc_role: npcRole,
|
|
});
|
|
stats.tables++;
|
|
dbApi.tables.insertColumns(tableId, node.table.columns);
|
|
const rowIds = dbApi.tables.insertRows(tableId, node.table.rows);
|
|
stats.rows += rowIds.length;
|
|
}
|
|
|
|
node.children.forEach((child, idx) => insertNode(child, sourceFile, treeNodeId, idx, childInsideNpc));
|
|
}
|
|
|
|
function importFile(filePath) {
|
|
const sourceFile = path.basename(filePath);
|
|
const roots = parseMarkdownFile(filePath);
|
|
roots.forEach((node, idx) => insertNode(node, sourceFile, null, idx, false));
|
|
stats.files++;
|
|
console.log(`Imported ${sourceFile}`);
|
|
}
|
|
|
|
function resolveLayer1Links() {
|
|
const sourceFile = 'norse_encounter_tables.md';
|
|
const layer1Table = dbApi.tables.findTableByHeading(sourceFile, 'Layer 1: What Happens? (d20)');
|
|
if (!layer1Table) {
|
|
console.warn('Could not find Layer 1 table for link resolution');
|
|
return;
|
|
}
|
|
const rows = dbApi.tables.getRowsForTable(layer1Table.id);
|
|
for (const row of rows) {
|
|
const text = row.cells[0] || '';
|
|
const match = LAYER1_LINKS.find(([pattern]) => pattern.test(text));
|
|
if (!match) continue;
|
|
const [, targetHeading] = match;
|
|
const targetTable = dbApi.tables.findTableByHeading(sourceFile, targetHeading);
|
|
if (!targetTable) {
|
|
console.warn(`Layer 1 link target not found: "${targetHeading}" (row: "${text}")`);
|
|
continue;
|
|
}
|
|
dbApi.tables.insertLink({ table_id: layer1Table.id, row_id: row.id, target_table_id: targetTable.id });
|
|
stats.links++;
|
|
}
|
|
}
|
|
|
|
function main() {
|
|
dbApi.tables.clearImported();
|
|
|
|
const files = fs.readdirSync(TABLES_DIR).filter((f) => f.endsWith('.md'));
|
|
for (const file of files) {
|
|
importFile(path.join(TABLES_DIR, file));
|
|
}
|
|
|
|
resolveLayer1Links();
|
|
|
|
console.log('---');
|
|
console.log(`Files: ${stats.files}`);
|
|
console.log(`Tree nodes: ${stats.nodes}`);
|
|
console.log(`Tables: ${stats.tables}`);
|
|
console.log(`Rows: ${stats.rows}`);
|
|
console.log(`Layer links: ${stats.links}`);
|
|
}
|
|
|
|
main();
|