'use strict'; /* ============================================================ MAIN — boot, data validation ============================================================ */ function Data_validate() { const problems = []; const chkItem = (id, where) => { if (!ITEMS[id]) problems.push(`missing item ${id} (${where})`); }; // shops for (const nid of Object.keys(NPCS)) { const n = NPCS[nid]; if (n.shop) n.shop.stock.forEach(i => chkItem(i, 'shop ' + nid)); (n.teach || []).forEach(t => { if (t.tech && !TECHNIQUES[t.tech]) problems.push(`npc ${nid} teaches missing tech ${t.tech}`); if (t.internal && !INTERNALS[t.internal]) problems.push(`npc ${nid} teaches missing internal ${t.internal}`); }); if (n.dlg && !DIALOGUES[n.dlg]) problems.push(`npc ${nid} dialogue missing: ${n.dlg}`); } // locations for (const lid of Object.keys(LOCS)) { LOCS[lid].npcs.forEach(nid => { if (!NPCS[nid]) problems.push(`loc ${lid} npc missing ${nid}`); }); } TRAVEL.forEach(([a, b]) => { if (!LOCS[a]) problems.push('travel missing loc ' + a); if (!LOCS[b]) problems.push('travel missing loc ' + b); }); // quests referenced in dialogues (rough scan) const refs = JSON.stringify(DIALOGUES).match(/q_[a-z0-9_]+/g) || []; [...new Set(refs)].forEach(q => { if (!QUESTS[q]) problems.push('dialogue references unknown quest ' + q); }); // techniques referenced as manuals Object.values(ITEMS).filter(i => i.type === 'manual').forEach(m => { if (!TECHNIQUES[m.teach] && !INTERNALS[m.teach] && !LIGHTNESS[m.teach]) problems.push(`manual ${m.id} teaches unknown ${m.teach}`); }); // origins items/techs Object.entries(ORIGINS).forEach(([oid, o]) => { (o.items || []).forEach(i => chkItem(i, 'origin ' + oid)); (o.techs || []).forEach(t => { if (!TECHNIQUES[t]) problems.push(`origin ${oid} tech ${t} missing`); }); }); // upgrades / recipes Object.entries(SMITH_UPGRADES).forEach(([k, up]) => { chkItem(k, 'smith'); chkItem(up.to, 'smith->'); Object.keys(up.mats).forEach(m => chkItem(m, 'smith mat')); }); ALCHEMY_RECIPES.forEach(r => { chkItem(r.out, 'alch'); Object.keys(r.mats).forEach(m => chkItem(m, 'alch mat')); }); // dialogues nodes integrity for (const did of Object.keys(DIALOGUES)) { const D = DIALOGUES[did]; for (const nid of Object.keys(D.nodes)) { const opts = typeof D.nodes[nid].opts === 'function' ? [] : (D.nodes[nid].opts || []); opts.forEach(o => { if (o.next && !D.nodes[o.next]) problems.push(`${did}.${nid} -> missing node ${o.next}`); }); } } if (problems.length) { console.warn('[Data] issues found:'); problems.forEach(p => console.warn(' -', p)); } else { console.log('[Data] validation passed ✔'); } return problems; } window.addEventListener('load', () => { Data_validate(); UI.init(); UI.renderTitleMenu(); Log.add('The jianghu stirs\u2026', 'sys'); });