world: WZ.world && WZ.world.id, tianzi: WZ.tianzi && WZ.tianzi.id, origin: WZ.origin && WZ.origin.id, linggen: WZ.linggen && WZ.linggen.id, talents: WZ.talents.map(function (t) { return t.id; }), six: WZ.six, gender: WZ.gender, age: WZ.age, goal: WZ.goal.id })); toast('预设已存储到当前浏览器', 'ok'); } catch (e) { toast('预设存储失败:' + e.message, 'err'); } }); $('#wzLoadPreset').addEventListener('click', function () { var raw = localStorage.getItem('ddwj21_preset'); if (!raw) { toast('没有已存储的预设', 'err'); return; } try { var p = JSON.parse(raw); WZ.world = D.WORLDS.filter(function (x) { return x.id === p.world; })[0] || null; WZ.tianzi = D.TIANZI.filter(function (x) { return x.id === p.tianzi; })[0] || null; WZ.origin = D.ORIGINS.filter(function (x) { return x.id === p.origin; })[0] || null; WZ.linggen = D.LINGGEN.filter(function (x) { return x.id === p.linggen; })[0] || null; WZ.talents = (p.talents || []).map(function (id) { return D.TALENTS.filter(function (t) { return t.id === id; })[0]; }).filter(Boolean); if (p.six) for (var k in p.six) if (k in WZ.six) WZ.six[k] = p.six[k]; if (p.gender) WZ.gender = p.gender; if (p.age) WZ.age = p.age; if (p.goal) WZ.goal = D.GOALS.filter(function (g) { return g.id === p.goal; })[0] || WZ.goal; renderStep(); toast('预设已加载', 'ok'); } catch (e) { toast('预设读取失败', 'err'); } }); /* ============================================================ SECTION: start 开局 ============================================================ */ function startGame() { /* 自定义角色四维校验 */ if (WZ.mode === 'custom' && WZ.customText) { var chk = validateCustom(WZ.customText); if (!chk.ok) { showCustomAdvice(chk); return; } } if (wzRemain() < 0) { toast('天道点已超支,无法开档', 'err'); WZ.step = Math.max(0, WZ.step - 1); renderStep(); return; } var seed = (Date.now() ^ Math.floor(Math.random() * 0x7fffffff)) & 0x7fffffff; G = new E.Game(seed); G.poolSeed = WZ.poolSeed; var kw = WZ.keywords ? WZ.keywords.split(/[\s,,、]+/).filter(Boolean).slice(0, 3) : null; var custom = null; if (WZ.mode === 'custom' && WZ.customText) custom = parseCustom(WZ.customText); /* 半随机候选写入的身份维度强制覆盖 */ if (WZ.mode === 'semi' && WZ.semiPick != null) { var c = WZ.semiCands[WZ.semiPick]; if (c) { custom = { birth: c.birth || '人族', status: c.origin.name, tag: c.tag || '无名', plight: '身陷绝境' }; } } var engineMode = WZ.mode === 'fullrandom' ? 'random' : (WZ.mode === 'semi' ? 'custom' : WZ.mode); var carry = Math.max(0, wzRemain()); G.openFile({ mode: engineMode, world: WZ.world, tianzi: WZ.tianzi, origin: WZ.origin, linggen: WZ.linggen, talents: WZ.talents, flaws: WZ.flaws, six: WZ.six, daohao: WZ.daohao, gender: WZ.gender, age: WZ.age, systemId: WZ.systemId, goal: WZ.goal, keywords: kw, custom: custom }); /* 创角期剩余天道点带入游戏 */ G.pc.tianPoints = carry; /* 半随机候选的因果线注入 */ if (WZ.mode === 'semi' && WZ.semiPick != null && WZ.semiCands[WZ.semiPick]) { var ck = WZ.semiCands[WZ.semiPick].karmas || []; ck.forEach(function (k, i) { if (G.karmas.some(function (x) { return x.name === k.name; })) return; G.karmas.push({ id: 'semi' + i, name: k.name, level: k.level, power: k.power, strength: k.power, trigger: k.trigger, endings: k.endings, desc: k.desc, surface: k.hidden ? G._surfaceMask(k) : null, realRevealed: !k.hidden, status: 'active', createdTurn: 0, relatedNpc: null }); }); } $('#wizard').classList.remove('on'); $('#startScreen').style.display = 'none'; $('#game').classList.add('on'); curRound = 0; renderOpening(); autoSave(); } function validateCustom(txt) { var advice = []; var ok = true; /* 1 力量来源 */ if (!/血脉|传承|奇遇|赐|交换|代价|系统|重生|穿越|觉醒|封印|捡|得到|获得/.test(txt)) { advice.push('【力量来源校验】未说明初始力量从何而来(血脉 / 传承 / 奇遇 / 天道赐予 / 代价交换)。'); ok = false; } /* 2 代价限制 */ if (!/代价|但是|然而|失去|无法|不能|每|折损|诅咒|伤|寿元|残缺|限制|副作用/.test(txt)) { advice.push('【代价限制校验】无代价的力量不被天道允许。请补充道伤、因果、寿元、道心或血脉诅咒等限制。'); ok = false; } /* 3 软肋 */ if (!/软肋|弱点|怕|惧|执念|在乎|唯一|不能失去|牵挂|亲|师|友|爱/.test(txt)) { advice.push('【软肋校验】无敌的角色不修仙。请补充一个致命弱点:情感、执念、道基、因果或环境依赖。'); ok = false; } /* 4 成长路径 */ if (!/想|要|目标|为了|追求|最终|希望|证道|复仇|找回|建立|了结|长生/.test(txt)) { advice.push('【成长路径校验】没有方向的修仙者只是流浪的蝼蚁。请补充长期发展方向。'); ok = false; } /* 过强检测 */ if (/仙帝|无敌|最强|全能|不死不灭|秒杀一切/.test(txt) && !/代价|寿元|记忆全失|追杀|三天|十年/.test(txt)) { advice.push('【平衡性校验】设定过强且未配套等量代价。若坚持,需附加:记忆全失 / 寿元仅剩三天 / 被天道追杀 之一。'); ok = false; } return { ok: ok, advice: advice }; } function showCustomAdvice(chk) { modal('四维校验未通过', '

天道驳回了这份设定,但不是拒绝——它给了修改建议:

' + chk.advice.map(function (a) { return '

· ' + esc(a) + '

'; }).join('') + '

修仙的本质就是在脆弱中寻找大道。补上缺口,或者直接改用完全随机开档。

', [{ label: '返回修改', cls: 'btn-gold' }]); } function parseCustom(txt) { var birth = /妖|兽/.test(txt) ? '妖族' : /器|剑灵|物灵/.test(txt) ? '器物' : /诡|鬼|邪/.test(txt) ? '诡异' : /穿越|现代|都市/.test(txt) ? '跨界者' : '人族'; var status = /宗门|弟子/.test(txt) ? '宗门弟子' : /世家|族/.test(txt) ? '落魄世家子' : /商|贾|账/.test(txt) ? '账房' : /医|丹|药/.test(txt) ? '药师' : /剑/.test(txt) ? '剑客' : '散修'; var tag = /重生|重来/.test(txt) ? '重生者' : /穿越/.test(txt) ? '穿越者' : /系统|面板/.test(txt) ? '系统宿主' : /诅咒|血/.test(txt) ? '被诅咒之人' : /封印/.test(txt) ? '被封印之人' : /失忆|忘/.test(txt) ? '被遗忘之人' : '被遗忘之人'; var plight = /追杀/.test(txt) ? '被追杀' : /囚|牢/.test(txt) ? '被囚禁' : /背叛|叛/.test(txt) ? '被背叛' : /误解|冤/.test(txt) ? '被误解' : '身陷绝境'; return { birth: birth, status: status, tag: tag, plight: plight, text: txt }; } /* ============================================================ SECTION: opening 开档呈现 ============================================================ */ function foldBlock(label, html) { var d = el('div', 'fold'); var b = el('button', 'fold-head', '' + esc(label) + '展开'); var body = el('div', 'fold-body local-hidden'); body.innerHTML = html; b.addEventListener('click', function () { var opened = !body.classList.toggle('local-hidden'); b.querySelector('.fold-arrow').textContent = opened ? '收起' : '展开'; }); d.appendChild(b); d.appendChild(body); return d; } function renderOpening() { var o = G.opening, pc = G.pc; var story = $('#storyWrap'); story.innerHTML = ''; var rnd = el('div', 'round'); rnd.appendChild(headRow('第一章', '混沌种子 #' + G.seed)); var opts = G.makeOptions('opening'); var ch = N.openingChapter(G, opts); /* 章节头 */ var head = el('div', 'chap-head'); head.innerHTML = '
序章 · 开篇
' + '

' + esc(ch.title) + '

' + '
' + esc(pc.world.name) + ' · ' + esc(o.era.name) + ' · ' + esc(D.REALMS[pc.realmIdx].name) + '
'; rnd.appendChild(head); /* 三个问题:我是谁 / 我在哪 / 我要做什么 */ var goal = pc.goal ? (pc.goal.name + ' —— ' + pc.goal.desc) : '活下去'; var guide = el('div', 'guide'); guide.innerHTML = '
你是
' + '
你在' + esc(ch.place) + '
' + '
你要' + esc(goal) + '
' + '
你修' + esc(D.systemOf(pc).name + ' · ' + D.realmNameOf(pc, pc.realmIdx)) + '
'; rnd.appendChild(guide); /* 正文 */ var st = el('div', 'story chapter'); st.innerHTML = ch.paras.map(function (p) { return '

' + esc(p) + '

'; }).join('') + '

「' + esc(ch.golden) + '」

'; rnd.appendChild(st); /* 完整属性与时代:默认折叠,先读故事 */ var kv = function (k, v) { return '
' + esc(k) + '' + v + '
'; }; var flaws = G.activeFlaws ? G.activeFlaws() : []; var cardHtml = '

【人物道碑】

' + kv('身份', esc(o.identity.status + ' · ' + o.identity.birth + '之身')) + kv('标签', esc(o.identity.tag + ' | 处境:' + o.identity.plight)) + kv('境界', esc(D.REALMS[pc.realmIdx].name + (pc.realmSub ? ' ' + (pc.realmSub + 1) + '层' : ''))) + kv('时代', esc(o.era.name)) + kv('世界状态', o.states.map(function (s) { return '' + esc(s.name) + ''; }).join('')) + kv('反套路', '' + esc(o.identity.antiText || (o.anti && o.anti[0]) || '') + '') + kv('先天代价', o.costs.map(function (c) { return '' + esc(c.txt) + ''; }).join('')) + (flaws.length ? kv('命途缺陷', flaws.map(function (f) { return '' + esc(f.name) + ''; }).join('') + '
这些缺陷会周期性真实发作:血咒逢月圆索取、债务每二十回合催收、宿敌会上门、被夺舍的残识会接管身体。承受它们换来的天道点,可在「天道点」面板中兑换。
') : '') + kv('开档方式', esc({ random: '完全随机', fullrandom: '全随机', semi: '半随机', keyword: '关键词', custom: '自定义角色' }[WZ.mode] || WZ.mode || '完全随机') + (WZ.keywords ? ' · 关键词:' + WZ.keywords : '') + ' | 本局种子 #' + WZ.poolSeed) + kv('带入天道点', '' + (pc.tianPoints || 0) + ' 点') + kv('初始因果', o.karmas.map(function (k) { return '' + esc(k.name) + '·' + esc(k.level) + ''; }).join('')) + kv('眼前劫数', '' + esc(o.tribulation.txt) + '') + kv('终极目的', esc(pc.goal.name + ' ' + pc.goal.desc)); var eraHtml = '

【世界基底 · ' + esc(o.era.name) + '】

' + o.era.desc.filter(Boolean).map(function (d) { return '

· ' + esc(d) + '

'; }).join('') + (o.era.conflict ? '

· 逻辑自洽校验:' + esc(o.era.conflict) + '

' : '') + '

· ' + esc(pc.world.desc) + '

'; rnd.appendChild(foldBlock('人物道碑 · 完整属性', cardHtml)); rnd.appendChild(foldBlock('世界基底 · ' + o.era.name, eraHtml)); story.appendChild(rnd); renderChoices(opts); renderAll(); scrollStory(true); } function headRow(num, act) { var h = el('div', 'round-head'); h.innerHTML = '' + esc(num) + '' + (act ? '' + esc(act) + '' : '') + ''; return h; } /* ============================================================ SECTION: choices 抉择区渲染 ============================================================ */ var curOptions = []; var pickedCombo = {}; /* 此刻栏:每回合都回答"我在哪、在追什么、最要紧的是什么" */ function urgentLine() { var f = (G.foreshadow || []).filter(function (x) { return !x.resolved && x.dueTurn; }) .sort(function (a, b) { return a.dueTurn - b.dueTurn; })[0]; if (f) { var label = String(f.text || ''); if (label.length > 26) label = label.slice(0, 26) + '…'; return '【' + label + '】还有 ' + Math.max(0, f.dueTurn - G.turn) + ' 回合到期'; } var k = (G.karmas || []).filter(function (x) { return x.status === 'active'; }) .sort(function (a, b) { return (b.strength || 0) - (a.strength || 0); })[0]; if (k) return '【' + k.name + '】(' + k.level + ')还没了结'; if (G.opening && G.opening.tribulation && !G.opening.tribulation.resolved) { var t = String(G.opening.tribulation.txt || ''); return t.length > 52 ? t.slice(0, 52) + '…' : t; } return '暂无近忧——这在修仙界本身就是一种奢侈'; } function sitStrip() { var pc = G.pc; var d = el('div', 'sit'); d.innerHTML = '
此刻' + '在 ' + esc(pc.lastPlace || '不明之地') + '' + '·' + esc(pc.goal.name) + '' + '·灵石 ' + n2s(pc.money) + '
' + '
最要紧' + esc(urgentLine()) + '
'; return d; } function renderChoices(opts) { curOptions = opts || []; pickedCombo = {}; var w = $('#choiceWrap'); w.innerHTML = ''; if (!curOptions.length) { w.appendChild(el('div', 'empty', '你已经死了。请在右侧选择死亡转化形态。')); return; } w.appendChild(sitStrip()); var head = el('div', 'ch-head'); head.innerHTML = '局势推演与抉择' + '每个选项标注风险 / 收益 / 代价;可输入组合(如 13)同回合并行执行' + ''; w.appendChild(head); var list = el('div', 'ch-list'); curOptions.forEach(function (o, i) { var b = el('button', 'choice' + (o.main ? ' main' : '')); b.setAttribute('data-idx', String(i)); var gains = (o.gain || []).map(function (g) { return '收益·' + esc(g) + ''; }).join(''); var costs = (o.cost || []).map(function (c) { return '代价·' + esc(c) + ''; }).join(''); b.innerHTML = '' + (i + 1) + '' + '' + esc(o.text) + '' + '' + '' + esc(o.tag || (o.main ? '主线抉择' : '支线行动')) + '' + '风险·' + esc(o.risk || '中') + '' + gains + costs + ''; if (o.free) { b.addEventListener('click', function () { toggleFreeInput(); }); } else { b.addEventListener('click', function () { doChoice(i, null); }); } list.appendChild(b); }); w.appendChild(list); /* 自由输入区(默认隐藏,点击第5项展开) */ var fi = el('div', 'free-input'); fi.id = 'freeBox'; fi.style.display = 'none'; fi.innerHTML = '' + ''; w.appendChild(fi); $('#comboGo').addEventListener('click', function () { var v = $('#comboIn').value.trim(); if (!/^\d{2,4}$/.test(v)) { toast('组合选择请输入 2~4 位数字,如 13 或 134', 'err'); return; } var combo = G.parseCombo(v, curOptions); if (!combo) { toast('无法解析该组合', 'err'); return; } if (!combo.ok) { toast(combo.msg, 'err'); return; } doCombo(combo.chosen); }); $('#freeGo').addEventListener('click', function () { var t = $('#freeTxt').value.trim(); if (!t) { toast('请先写下你要做的事', 'err'); return; } var freeOpt = curOptions.filter(function (o) { return o.free; })[0]; doChoice(curOptions.indexOf(freeOpt), t); }); $('#freeTxt').addEventListener('keydown', function (e) { if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { $('#freeGo').click(); } }); } function toggleFreeInput() { var b = $('#freeBox'); if (!b) return; b.style.display = b.style.display === 'none' ? 'flex' : 'none'; if (b.style.display === 'flex') $('#freeTxt').focus(); } /* 组合并行执行:按时序编排,资源不足则提示 */ function doCombo(chosen) { if (G.over) { toast('你已经死了', 'err'); return; } var pc = G.pc; /* 时序编排:先低风险后高风险 */ var order = { cultivate: 0, mind: 1, art: 2, trade: 3, meet: 4, investigate: 5, explore: 6, karma: 7, war: 8, fight: 9, taboo: 10 }; chosen.sort(function (a, b) { return (order[a.act] || 5) - (order[b.act] || 5); }); var res = null; var comboTxt = '

【并行选择编排】本回合执行 ' + chosen.length + ' 项行动,按时序排列:' + chosen.map(function (c, i) { return (i + 1) + '.' + esc(c.text.slice(0, 18)) + '…'; }).join(' → ') + '

'; /* 逐个执行,但只渲染最后一个回合的完整叙事 */ var merged = null; chosen.forEach(function (c, i) { if (pc.energy < 20) { toast('体力不支,第 ' + (i + 1) + ' 项行动无法执行', 'err'); return; } var r = G.doAction(c, null); if (!merged) merged = r; else { /* 合并结算 */ r.parts.forEach(function (p) { if (p.cls !== 'chronicle') merged.parts.push(p); }); var ms = merged.settlement, rs = r.settlement; ['karma', 'npc', 'world', 'injury', 'bp', 'arts'].forEach(function (k) { ms[k] = ms[k].concat(rs[k]); }); ms.daoyun += rs.daoyun; merged.ticks = merged.ticks.concat(r.ticks); merged.chronicle = r.chronicle; merged.options = r.options; merged.over = r.over; merged.deathForms = r.deathForms; } }); if (!merged) return; appendRound(merged, comboTxt, '并行·' + chosen.length + '项'); renderAll(); } function doChoice(idx, freeText) { if (G.over) { toast('你已经死了,先选择死亡转化形态', 'err'); return; } var o = curOptions[idx]; if (!o) return; if (G.pc.energy < 15 && o.act !== 'mind') { toast('精力耗尽(' + Math.round(G.pc.energy) + '/' + G.pc.energyMax + ')。可选择静坐修心恢复,或用「岁月如梭」跳过一段时间。', 'err'); return; } var res = G.doAction(o, freeText); appendRound(res, null, freeText ? '自由行动:' + freeText.slice(0, 24) : (o.text.slice(0, 26) + (o.text.length > 26 ? '…' : ''))); maybeNarrate(o, res); renderAll(); autoSave(); } /* ============================================================ SECTION: ai 叙事(LLM 只重写事实,不参与判定) ============================================================ */ function aiSettleText(s) { if (!s) return '无'; var bits = []; if (s.karma && s.karma.length) bits.push('因果:' + s.karma.join(';')); if (s.npc && s.npc.length) bits.push('人物:' + s.npc.join(';')); if (s.world && s.world.length) bits.push('世界:' + s.world.join(';')); if (s.injury && s.injury.length) bits.push('道伤:' + s.injury.join(';')); if (s.daoyun) bits.push('道蕴 ' + (s.daoyun > 0 ? '+' : '') + s.daoyun); if (s.bp && s.bp.length) bits.push('塑道:' + s.bp.join(';')); return bits.join('|') || '无'; } function aiKarmaText() { var act = (G.karmas || []).filter(function (k) { return k.status === 'active'; }).slice(0, 4); if (!act.length) return '无'; return act.map(function (k) { return '[' + k.level + ']' + k.name; }).join('、'); } function aiNpcText(opt, res) { var names = []; if (opt && opt.npc) { var n = G.npcs.filter(function (x) { return x.id === opt.npc; })[0]; if (n) names.push(n.fullName + '(' + n.race + ',' + D.REALMS[Math.min(D.REALMS.length - 1, n.realmIdx || 0)].name + ',' + (n.person || '') + ')'); } return names.join('、') || '无'; } function buildAIContext(opt, res, storyEl) { var pc = G.pc; var facts = storyEl ? storyEl.innerText.replace(/\s+/g, ' ').trim() : ''; return { world: pc.world.name, worldTag: pc.world.tag, era: (G.opening && G.opening.era && G.opening.era.name) || '', realm: D.REALMS[pc.realmIdx].name, age: Math.round(pc.age), life: Math.round(pc.lifeMax), place: pc.lastPlace || '不知何处', action: (opt && opt.text) || '静观其变', facts: facts.slice(0, 900), settle: aiSettleText(res && res.settlement), karma: aiKarmaText(), npc: aiNpcText(opt, res), stage: D.TRIBULATION[G.stage].name, state: '气血 ' + Math.round(pc.hp) + '/' + pc.hpMax + ',道心污染 ' + (D.POLLUTION[pc.pollution] || D.POLLUTION[0]).name + ',天道监察 ' + (D.MONITOR[pc.monitor] || D.MONITOR[0]).lv + ',灵石 ' + Math.round(pc.money) }; } function maybeNarrate(opt, res) { if (!window.LLM || !LLM.available()) return; var c = LLM.cfg(); var major = !!(opt && opt.main) || !!(res && res.over) || ((res && res.parts) || []).some(function (p) { return p.cls === 'break' || p.cls === 'fail' || p.cls === 'battle' || p.cls === 'stage' || p.cls === 'monitor'; }) || !!(res && res.stageUp && res.stageUp.length) || !!(res && res.bigFired && res.bigFired.length); if (c.freq === 'major' && !major) return; var rounds = document.querySelectorAll('#storyWrap .round'); var last = rounds[rounds.length - 1]; if (!last) return; var story = last.querySelector('.story'); if (!story) return; var wrap = el('div', 'ai-wrap'); wrap.innerHTML = '
AI 叙事推演中…
'; last.insertBefore(wrap, story.nextSibling); story.classList.add('local-hidden'); var body = wrap.querySelector('.ai-body'); var status = wrap.querySelector('.ai-status'); var t0 = Date.now(); var ctx = buildAIContext(opt, res, story); LLM.narrate(ctx).then(function (r) { if (!r.ok) { wrap.remove(); story.classList.remove('local-hidden'); toast('AI 叙事失败:' + (r.error || '未知错误') + '(已回退本地推演)', 'err'); return; } body.innerHTML = r.text.split(/\n+/).filter(function (p) { return p.trim(); }) .map(function (p) { return '

' + esc(p.trim()) + '

'; }).join(''); var tok = r.usage ? ' ' + ((r.usage.total_tokens || r.usage.completion_tokens || 0)) + ' tokens' : ''; status.textContent = '已生成 · ' + (r.model || '') + ' · ' + ((Date.now() - t0) / 1000).toFixed(1) + 's' + tok; var tg = el('button', 'ai-toggle', '查看本地推演'); tg.addEventListener('click', function () { story.classList.toggle('local-hidden'); tg.textContent = story.classList.contains('local-hidden') ? '查看本地推演' : '收起本地推演'; }); wrap.querySelector('.ai-head').appendChild(tg); G.aiLog = G.aiLog || []; G.aiLog.push({ turn: G.turn, text: r.text, model: r.model }); if (G.aiLog.length > 200) G.aiLog.shift(); }); } function appendRound(res, extraHtml, actLabel) { curRound++; var story = $('#storyWrap'); var rnd = el('div', 'round'); rnd.appendChild(headRow('第 ' + G.turn + ' 回合', actLabel)); if (extraHtml) rnd.appendChild(el('div', '', extraHtml)); res.parts.forEach(function (p) { rnd.appendChild(el('div', p.cls === 'chronicle' ? 'story' : 'sysbox box-' + p.cls, p.html)); }); /* 因果结算表 */ if (res.settlement) rnd.appendChild(settleTable(res.settlement)); story.appendChild(rnd); /* 长局性能:手机只保留最近 26 回合的 DOM,更早的折叠(数据仍完整保存在 G 中) */ var rounds = story.querySelectorAll('.round'); if (rounds.length > 26) { var drop = rounds.length - 26; for (var i = 0; i < drop; i++) { rounds[i].remove(); } foldedRounds += drop; } renderFoldNote(); renderChoices(res.options); if (res.over) showDeath(res.deathForms); scrollStory(); } function renderFoldNote() { var w = $('#storyWrap'); var n = w.querySelector('.fold-note'); if (!foldedRounds) { if (n) n.remove(); return; } if (!n) { n = el('div', 'fold-note'); w.insertBefore(n, w.firstChild); } n.innerHTML = '更早的 ' + foldedRounds + ' 回合已折叠,以保持长局流畅。完整记录在「结算」与「存档」里。'; } function settleTable(s) { var rows = []; if (s.karma.length) rows.push(['因果网变化', s.karma.map(esc).join('
')]); if (s.npc.length) rows.push(['NPC关系变化', s.npc.map(esc).join('
')]); if (s.world.length) rows.push(['世界状态变化', s.world.map(esc).join('
')]); if (s.daoyun) rows.push(['道蕴消耗', (s.daoyun > 0 ? '+' : '') + s.daoyun]); if (s.injury.length) rows.push(['道伤变化', s.injury.map(esc).join('
')]); if (s.bp.length) rows.push(['行为塑道', s.bp.map(esc).join('
')]); if (s.arts.length) rows.push(['百艺进度', s.arts.map(esc).join('
')]); if (!rows.length) return el('div'); var d = el('div', 'sysbox'); d.innerHTML = '

【因果结算表】

' + '' + rows.map(function (r) { return ''; }).join('') + '
' + r[0] + '' + r[1] + '
'; return d; } function scrollStory(top) { var w = $('#storyWrap'); if (window.innerWidth <= 980) { var last = w.querySelector('.round:last-child'); var hdr = document.querySelector('.topbar'); var hh = hdr ? hdr.getBoundingClientRect().height : 80; var y = (top || !last) ? 0 : Math.max(0, last.getBoundingClientRect().top + window.scrollY - hh - 26); window.scrollTo({ top: y, behavior: top ? 'auto' : 'smooth' }); } else { if (top) { w.scrollTop = 0; } else { var last2 = w.querySelector('.round:last-child'); w.scrollTop = last2 ? last2.offsetTop - 8 : w.scrollHeight; } } } /* ============================================================ SECTION: left 人物道碑 ============================================================ */ function renderLeft() { var pc = G.pc, p = $('#leftPanel'); var realm = D.REALMS[pc.realmIdx]; var next = D.REALMS[Math.min(pc.realmIdx + 1, D.REALMS.length - 1)]; var ls = G.lifespanStage(pc); var pol = D.POLLUTION[pc.pollution] || D.POLLUTION[0]; var mon = D.MONITOR[pc.monitor] || D.MONITOR[0]; var injHtml = D.INJURY_TYPES.map(function (t) { var lv = pc.injury[t] || 0; return '' + esc(t) + '·' + esc(D.INJURY_LEVELS[lv]) + ''; }).join(''); /* 主道途 / 副道途 / 歧路 */ var paths = Object.keys(pc.bp).map(function (k) { return { k: k, v: pc.bp[k], st: pc.bpStage[k] || 0 }; }).sort(function (a, b) { return b.v - a.v; }); var mainP = paths[0], subP = paths[1], qiP = paths.filter(function (x) { return x.st === 0 && x.v > 0; }).slice(0, 2); var kv = function (k, v) { return '
' + esc(k) + '' + v + '
'; }; var pct = function (a, b) { return b > 0 && isFinite(b) ? Math.min(100, a / b * 100) : 0; }; var bioHtml = ''; if (pc.bio) { bioHtml = '
' + '
我是谁
' + '' + (pc.bio.originName ? '

来路:' + esc(pc.bio.originName) + '。' + esc(pc.bio.originDesc || '') + '

' : '') + '

' + esc(pc.bio.past || '') + '

' + '

为何在此:' + esc(pc.bio.why || '') + '

' + '

执念:' + esc(pc.goal.name) + '——' + esc(pc.goal.desc) + '

' + (pc.bio.secret ? '

未说出口:' + esc(pc.bio.secret) + '

' : '') + '
'; } p.innerHTML = '
' + '

' + esc(pc.name) + (pc.daoTitle ? ' 「' + esc(pc.daoTitle) + '」' : '') + '

' + '
' + esc(pc.status + '(' + pc.race + ') ' + pc.gender + ' ' + pc.age + ' 岁') + '
' + bioHtml + '
' + esc(realm.name) + '' + (realm.sub ? '' + (pc.realmSub + 1) + '/' + realm.sub + ' 层' : '') + '寿元 ' + pc.age + '/' + (isFinite(pc.lifeMax) ? pc.lifeMax : '∞') + '
' + esc(ls.name) + '
' + '
道蕴积累' + n2s(pc.qi) + ' / ' + n2s(pc.qiNeed) + '
' + '
' + '
气血' + Math.round(pc.hp) + ' / ' + Math.round(pc.hpMax) + '
' + '
' + '
精力' + Math.round(pc.energy) + ' / ' + pc.energyMax + '
' + '
' + '
道蕴储备' + n2s(pc.daoyun) + '
' + '
' + '
核心状态
' + kv('战力', '' + n2s(pc.combat) + ' (同境界上限 ' + n2s(realm.base * 1.15) + ')') + kv('灵石', n2s(pc.money)) + kv('本体状态', esc(pc.hp / pc.hpMax > 0.7 ? '健康' : pc.hp / pc.hpMax > 0.35 ? '受伤' : pc.hp > 0 ? '重伤' : '濒死')) + kv('寿元阶段', esc(ls.name + ' ' + ls.fx.desc)) + '
' + '
六司(先天资质 · 后天可增减)
' + D.SIX.map(function (s) { var v = pc.six[s.key] || 0; var base = (pc.sixBase && pc.sixBase[s.key]) != null ? pc.sixBase[s.key] : v; var per = (D.SIX_PER_POINT && D.SIX_PER_POINT[s.key]) || 0.02; var pct = Math.round(v * per * 100); var mark = v > base ? '↑' : (v < base ? '↓' : ''); return '
' + '' + esc(s.key) + '' + '' + v + mark + ' +' + pct + '%
'; }).join('') + '
' + '
道途(主 / 副 / 歧路)
' + kv('主道途', mainP ? esc(mainP.k) + ' ' + esc(D.BP_STAGE_NAMES[Math.max(0, mainP.st - 1)] || '道蕴微光') + ' ' + mainP.v.toFixed(1) + ' BP' : '尚未成形') + kv('副道途', subP ? esc(subP.k) + ' ' + subP.v.toFixed(1) + ' BP' : '无') + kv('歧路', qiP.length ? qiP.map(function (x) { return '' + esc(x.k) + ''; }).join('') : '无') + (pc.fusionDao ? kv('融合道途', '' + esc(pc.fusionDao) + '') : '') + (pc.conflictPct ? kv('道心冲突', '' + pc.conflictPct + '%') : '') + '
' + '
道伤分级
' + kv('', injHtml) + '
' + '
道心污染
' + kv('阶段', '' + esc(pol.name) + '') + '
' + esc(pol.desc) + '
' + (pc.pollution >= 4 ? '
即将转化,形态候选:' + D.POLLUTION_FORMS.join(' / ') + '
' : '') + '
' + '
天道监察 / 代价
' + kv('监察等级', '' + esc(mon.lv + ' ' + mon.name) + '') + kv('成功率惩罚', '-' + Math.round(mon.pen * 100) + '%') + kv('代价状态', '' + esc(D.COST_LEVELS[pc.costLevel].name) + '') + kv('天道点', '' + (pc.tianPoints || 0) + ' 点') + '
' + (function () { var flaws = G.activeFlaws ? G.activeFlaws() : []; if (!flaws.length) return ''; return '
命途缺陷(' + flaws.length + ')
' + '
' + flaws.map(function (f) { return '' + esc(f.name) + ''; }).join('') + '
' + '
这些缺陷会周期性真实发作。详情见「天道点」面板。
'; })() + (G.myForce ? '
自建势力
' + kv('名号', '' + esc(G.myForce.name) + '') + kv('驻地', esc(G.myForce.base)) + kv('成员', G.myForce.members.length + ' 人') + kv('规矩', G.myForce.rules.map(esc).join('、') || '未立') + '
' : '') + '
因果羁绊
' + (G.karmas.filter(function (k) { return k.status === 'active'; }).slice(0, 5).map(function (k) { return '
' + '' + esc(k.level) + '' + '' + esc(k.name) + ' 强度' + k.strength + '
'; }).join('') || '
暂无活跃因果
') + '
' + '
眼前劫数
' + '
' + esc(G.opening.tribulation.txt) + '
' + (function () { var due = G.foreshadow.filter(function (f) { return !f.resolved && f.dueTurn; }).sort(function (a, b) { return a.dueTurn - b.dueTurn; })[0]; return due ? '
最近期限:' + Math.max(0, due.dueTurn - G.turn) + ' 回合后 · ' + esc(due.text.slice(0, 22)) + '
' : ''; })() + '
' + '
设定
' + kv('世界', esc(pc.world.name)) + kv('时代', esc(G.opening.era.name)) + kv('天资 / 灵根', esc(pc.tianzi.name + ' / ' + pc.linggen.name)) + kv('终极目的', esc(pc.goal.name)) + kv('声誉', esc(G.reputationWord())) + (pc.gongfa ? kv('功法', esc(pc.gongfa)) : '') + (G.daoLineage ? kv('道统', esc(G.daoLineage.name)) : '') + '
' + '
随身之物(' + pc.items.length + ')
' + (pc.items.length ? pc.items.slice(0, 12).map(function (it) { return '
' + esc(it.n) + (it.qty ? ' ×' + it.qty : '') + ' Lv' + (it.lv || 1) + '
'; }).join('') : '
身无长物
') + '
' + '
'; } /* ============================================================ SECTION: topbar ============================================================ */ function renderTop() { var pc = G.pc, trib = D.TRIBULATION[G.stage]; var nextStage = D.TRIBULATION[Math.min(D.TRIBULATION.length - 1, G.stage + 1)]; var chips = []; chips.push('回合 ' + G.turn + ''); chips.push('所在 ' + esc(D.planeOf(pc).name) + ''); chips.push('大劫 ' + esc(trib.name) + '(' + Math.max(0, nextStage.turn - G.turn) + ' 回合后推进)'); chips.push('气血 ' + Math.round(pc.hp) + ''); chips.push('灵石 ' + n2s(pc.money) + ''); chips.push('道蕴 ' + n2s(pc.daoyun) + ''); chips.push('道心 ' + esc((D.POLLUTION[pc.pollution] || D.POLLUTION[0]).name) + ''); chips.push('监察 ' + esc((D.MONITOR[pc.monitor] || D.MONITOR[0]).lv) + ''); chips.push('精力 ' + Math.round(pc.energy) + ''); chips.push('天道点 ' + (pc.tianPoints || 0) + ''); var flawN = G.activeFlaws ? G.activeFlaws().length : 0; if (flawN) chips.push('命途缺陷 ' + flawN + ''); if (window.LLM && LLM.available()) chips.push('AI 叙事 ' + esc(LLM.cfg().model) + ''); var unres = G.foreshadow.filter(function (f) { return !f.resolved; }).length; chips.push('伏笔 ' + unres + ''); chips.push('存活NPC ' + G.npcs.filter(function (n) { return n.alive; }).length + ''); $('#tbChips').innerHTML = chips.join(''); } /* ============================================================ SECTION: tabs 右侧标签页 ============================================================ */ var TABS = [ { id: 'rumor', name: '风闻' }, { id: 'karma', name: '因果' }, { id: 'tian', name: '天道点' }, { id: 'settle', name: '结算' }, { id: 'npc', name: '人物' }, { id: 'realm', name: '境界' }, { id: 'world', name: '世界' }, { id: 'lore', name: '天地志' }, { id: 'art', name: '百艺' }, { id: 'market', name: '坊市' }, { id: 'stele', name: '道碑' }, { id: 'save', name: '存档' } ]; function renderTabs() { var t = $('#tabs'); t.innerHTML = ''; var unread = G.rumors.filter(function (r) { return !r.read; }).length; TABS.forEach(function (tb) { var b = el('button', 'tab' + (curTab === tb.id ? ' on' : ''), esc(tb.name) + (tb.id === 'rumor' && unread ? '' : '')); b.addEventListener('click', function () { curTab = tb.id; renderTabs(); renderTabBody(); }); t.appendChild(b); }); } function renderTabBody() { var b = $('#tabBody'); b.innerHTML = ''; if (curTab === 'rumor') tabRumor(b); else if (curTab === 'karma') tabKarma(b); else if (curTab === 'tian') tabTian(b); else if (curTab === 'settle') tabSettle(b); else if (curTab === 'npc') tabNpc(b); else if (curTab === 'realm') tabRealm(b); else if (curTab === 'world') tabWorld(b); else if (curTab === 'lore') tabLore(b); else if (curTab === 'art') tabArt(b); else if (curTab === 'market') tabMarket(b); else if (curTab === 'stele') tabStele(b); else if (curTab === 'save') tabSave(b); } /* --- 修仙界风闻 --- */ function tabRumor(b) { b.appendChild(secHead('修仙界风闻(可信度 + 来源标注)')); if (!G.rumors.length) { b.appendChild(el('div', 'empty', '暂无风闻。每十回合坊市与密探会带来新的消息。')); return; } G.rumors.slice(0, 24).forEach(function (r) { var d = el('div', 'rumor' + (r.big ? ' big' : '') + (r.read ? '' : ' unread')); d.innerHTML = '
' + esc(r.kind) + '' + '' + esc(r.trust) + '' + '第 ' + r.turn + ' 回合
' + '
' + esc(r.text) + '
' + '
来源:' + esc(r.source) + ' · 可信度 ' + r.pct + '% · ' + esc(r.hint) + '
'; var acts = el('div', 'ru-acts'); if (!r.revealed) { [['调查真相', '消耗时间与资源核实(可信度越高越准)'], ['提前布局', '前往涉及地点做准备,可能得先机也可能落入陷阱'], ['主动介入', '直接前往介入,高风险高回报,可能改变事件走向'], ['无视', '继续自己的计划,可能错过机缘也可能避开灾祸']].forEach(function (a) { var btn = el('button', 'btn btn-sm btn-ghost', a[0]); btn.title = a[1]; btn.addEventListener('click', function () { respondRumor(r, a[0]); }); acts.appendChild(btn); }); d.appendChild(acts); } else { d.appendChild(el('div', 'ru-truth', '你的应对:' + esc(r.response || '—') + (r.truthKnown != null ? ' | 核实结果:' + (r.real ? '属实' : '不实') : ''))); } r.read = true; b.appendChild(d); }); } function respondRumor(r, kind) { if (r.revealed) return; r.revealed = true; r.response = kind; var pc = G.pc, out = []; if (kind === '调查真相') { pc.energy = Math.max(0, pc.energy - 20); pc.money = Math.max(0, pc.money - 50); r.truthKnown = true; out.push('你花了时间和五十灵石去核实。' + (r.real ? '这条消息属实,细节大体对得上。' : '这条消息不实——有人故意放出来的,或者传话的人自己也没搞清楚。')); if (!r.real && r.big) out.push('更重要的是:放出这条消息的人,希望你去做某件事。'); G.addBp('谋', 0.8, '调查风闻'); } else if (kind === '提前布局') { pc.energy = Math.max(0, pc.energy - 25); if (r.real) { out.push('你提前去了。因为早到,你占了一个别人抢不走的位置。'); pc.daoyun += 25; pc.money += Math.round(100 + Math.random() * 300); out.push('道蕴 +25,另有资源进账。'); } else { out.push('你提前去了,但那里什么都没有。你为一个假消息花了三天,这三天里别的事在发生。'); pc.age += 0; } } else if (kind === '主动介入') { pc.energy = Math.max(0, pc.energy - 40); var d = G.dice(0.45, { attr: '气运' }); if (d.result === 'great' || d.result === 'ok') { out.push('你介入了,而且改变了走向。' + (r.real ? '这件事因为你的介入,结局和所有人预料的都不一样。' : '这件事本来就不该发生,你的介入让它彻底散了。')); pc.daoyun += 60; pc.reputation = E.clamp(pc.reputation + 10, -100, 100); G.addBp('谋', 3.0, '介入大事件'); out.push('道蕴 +60,声誉 +10,谋道 BP +3.0(重大抉择级)。'); } else { out.push('你介入了,但被卷进去了。' + (d.result === 'greatfail' ? '事情比你以为的大得多,而你站在最中间。' : '你没能改变什么,只是让自己成了这件事的一部分。')); pc.hp = Math.max(1, pc.hp - Math.round(pc.hpMax * (d.result === 'greatfail' ? 0.4 : 0.18))); G.addInjury('肉身', Math.min(5, pc.injury['肉身'] + 1), '介入风波'); G.addCost(2, '介入风波'); } } else { out.push('你选择不理会。' + (r.real ? '这件事后来真的发生了,你不在场。' : '这件事后来被证明是假的,你的判断对了——虽然你当时并不确定。')); pc.energy = Math.min(pc.energyMax, pc.energy + 5); } modal('风闻应对·' + kind, out.map(function (t) { return '

' + esc(t) + '

'; }).join('') + (r.big ? '

注:这是一条大事件预告。无论你是否介入,它都会在到期时触发,并插入世界级叙事,所有相关人物线同步推进。

' : ''), [{ label: '知道了', cls: 'btn-gold' }]); renderAll(); autoSave(); } /* --- 因果网 --- */ function tabKarma(b) { var act = G.karmas.filter(function (k) { return k.status === 'active'; }); var res = G.karmas.filter(function (k) { return k.status !== 'active'; }); b.appendChild(secHead('活跃因果线(' + act.length + ')')); if (!act.length) b.appendChild(el('div', 'empty', '身上没有活跃的因果线。这很少见,也不一定是好事。')); act.forEach(function (k) { var npc = k.relatedNpc ? G.npc(k.relatedNpc) : null; var d = el('div', 'karma'); d.innerHTML = '
' + esc(k.name) + '' + '' + esc(k.level) + '' + (k.unbreakable ? '不可转化' : '') + (k.transformed ? '已' + esc(k.transformed) + '' : '') + '强度 ' + k.strength + '
' + '
' + esc(k.realRevealed ? (k.desc || '') : ('【表象】' + (k.surface || '你只看得见这一头。'))) + '
' + '
触发:' + esc(k.trigger || '—') + ' | 可能结局:' + esc((k.endings || []).join(' / ') || '未知') + (npc ? ' | 关联:' + esc(npc.fullName) + (npc.alive ? '' : '(已故)') : '') + (k.dueTurn ? ' | 期限:第 ' + k.dueTurn + ' 回合' : '') + (k.latent ? ' | 业障潜伏中' : '') + '
'; var acts = el('div', 'ka-acts'); if (k.level !== '天道级' && !k.unbreakable) { ['赎罪', '补偿', '传承', '牺牲', '斩断'].forEach(function (w) { var btn = el('button', 'btn btn-sm btn-ghost', w); btn.addEventListener('click', function () { var r = G.transformKarma(k.id, w); if (!r) return; modal('因果转化·' + w, '

' + esc(r.msg) + '

铁律:已发生之因果不可撤销,只能被转化、被承受,或被更高级别的因果覆盖。时间之河不可逆流。

', [{ label: '承受', cls: 'btn-gold' }]); renderAll(); autoSave(); }); acts.appendChild(btn); }); } else { acts.appendChild(el('span', 'ka-meta', k.level === '天道级' ? '天道级因果不可转化,只能承受。' : '这条线是你在绝境里自己绑上的,它不给解。')); } d.appendChild(acts); b.appendChild(d); }); if (res.length) { b.appendChild(secHead('已了结 / 归档(' + res.length + ')')); res.slice(0, 12).forEach(function (k) { b.appendChild(el('div', 'karma', '
' + esc(k.name) + '' + esc(k.status === 'resolved' ? '已了结' : '已归档') + '
')); }); } /* 未解伏笔 */ var fs = G.foreshadow.filter(function (f) { return !f.resolved; }); b.appendChild(secHead('未解伏笔(' + fs.length + ')· 按优先级排序')); if (!fs.length) b.appendChild(el('div', 'empty', '没有未解伏笔。')); var order = { '天道级': 0, '因果级': 1, '个人级': 2, '日常级': 3, '背景设定': 4 }; fs.sort(function (a, c) { return (order[a.level] == null ? 9 : order[a.level]) - (order[c.level] == null ? 9 : order[c.level]); }) .slice(0, 20).forEach(function (f) { b.appendChild(el('div', 'karma', '
' + esc(f.text) + '' + '' + esc(f.level) + '' + '' + (f.dueTurn ? '剩 ' + Math.max(0, f.dueTurn - G.turn) + ' 回合' : '无期限') + '
' + '
触发条件:' + esc(f.trigger || '未知') + ' | ' + (f.level === '天道级' || f.level === '因果级' ? '永不压缩,仅摘要保留' : f.level === '个人级' ? '超 50 回合未触发将强制触发或废弃' : '超 30 回合未触发降级为背景设定') + '
')); }); } /* --- 天道点:残酷经济(越惨越有筹码) --- */ function tabTian(b) { var pc = G.pc; var banner = el('div', 'tp-banner'); banner.innerHTML = '' + (pc.tianPoints || 0) + '' + '当前持有天道点
天道不劝你,也不拦你——它只是记账。
'; b.appendChild(banner); /* 承受:获取途径 */ b.appendChild(secHead('承受(以自己的一部分换取天道点)')); b.appendChild(el('div', 'ka-meta', '这里没有免费的东西。你换到的每一点,都是从自己身上割下来的,且不会自动长回去。')); D.TIAN_EARN.forEach(function (e) { var d = el('div', 'tian-item'); d.innerHTML = '
' + esc(e.name) + '
' + '
' + esc(e.desc) + '
'; var acts = el('div', 'ti-act ka-acts'); /* 需要参数的途径提供选项 */ if (e.act === 'injury') { D.INJURY_TYPES.forEach(function (t) { var lv = pc.injury[t] || 0; var btn = el('button', 'btn btn-sm btn-ghost', t + '(' + D.INJURY_LEVELS[lv] + ')'); btn.disabled = lv >= 5; btn.addEventListener('click', function () { doTianEarn(e.act, t); }); acts.appendChild(btn); }); } else if (e.act === 'life') { [10, 30, 50].forEach(function (y) { var btn = el('button', 'btn btn-sm btn-ghost', y + ' 年'); btn.disabled = pc.age + y >= pc.lifeMax; btn.addEventListener('click', function () { doTianEarn(e.act, y); }); acts.appendChild(btn); }); } else if (e.act === 'art') { var aids = Object.keys(pc.arts || {}).filter(function (a) { return pc.arts[a].bp > 0; }); if (!aids.length) acts.appendChild(el('span', 'ka-meta', '你没有可自废的百艺。')); aids.forEach(function (a) { var btn = el('button', 'btn btn-sm btn-ghost', a + '(' + Math.round(pc.arts[a].bp) + ' BP)'); btn.addEventListener('click', function () { doTianEarn(e.act, a); }); acts.appendChild(btn); }); } else { var btn2 = el('button', 'btn btn-sm', '承受'); btn2.addEventListener('click', function () { doTianEarn(e.act); }); acts.appendChild(btn2); } d.appendChild(acts); b.appendChild(d); }); /* 兑换 */ b.appendChild(secHead('兑换(天道点可以买到的东西)')); b.appendChild(el('div', 'ka-meta', '每一项都附带后果。抵消的天劫会连本带利回来,洗白的因果只是暂时不疼,净化的污染记着你。')); D.TIAN_SHOP.forEach(function (it) { var d = el('div', 'tian-item'); var can = (pc.tianPoints || 0) >= it.cost; d.innerHTML = '
' + esc(it.name) + '' + it.cost + ' 点
' + '
' + esc(it.desc) + '
'; var acts = el('div', 'ti-act ka-acts'); if (it.id === 'six') { /* 淬炼六司:需选择具体属性 */ var PER = D.SIX_PER_POINT || {}; D.SIX.forEach(function (s) { var v = pc.six[s.key] || 0; var hardMax = D.SIX_HARD_MAX || 15; var sb = el('button', 'btn btn-sm' + (can && v < hardMax ? ' btn-ghost' : ' btn-ghost'), s.key + ' ' + v + (v > D.SIX_MAX ? '↑' : '') + '(+' + Math.round(v * (PER[s.key] || 0.02) * 100) + '%)'); sb.disabled = !can || v >= hardMax; sb.title = v >= hardMax ? '已达硬上限 ' + hardMax : '淬炼 ' + s.key + ' +1(突破初始上限 10 后天道会记账,可能被改回)'; sb.addEventListener('click', function () { modal('淬炼六司·' + s.key, '

' + esc(s.key) + ' ' + v + ' → ' + (v + 1) + ' -' + it.cost + ' 天道点

' + '

' + esc(D.SIX_MAIN_EFFECT ? D.SIX_MAIN_EFFECT[s.key] : s.affects) + '

' + (v >= D.SIX_MAX ? '

此项已超初始上限 10。天道记了一笔——改上去的东西,也可能在岁月里被改回来。

' : '

这不是恩赐,是延期付款。

'), [{ label: '再想想', cls: 'btn-ghost' }, { label: '淬炼', cls: 'btn-gold', act: function () { var r = G.tpBuy('six', s.key); if (!r.ok) { toast(r.msg, 'err'); return; } modal('淬炼完成', '

' + esc(r.txt) + '

', [{ label: '好', cls: 'btn-gold' }]); renderAll(); autoSave(); } }]); }); acts.appendChild(sb); }); } else { var btn = el('button', 'btn btn-sm' + (can ? ' btn-gold' : ' btn-ghost'), can ? '兑换' : '点数不足'); btn.disabled = !can; btn.addEventListener('click', function () { modal('确认兑换', '

' + esc(it.name) + ' -' + it.cost + ' 天道点

' + esc(it.desc) + '

这不是恩赐,是延期付款。

', [{ label: '再想想', cls: 'btn-ghost' }, { label: '我认这个代价', cls: 'btn-gold', act: function () { var r = G.tpBuy(it.id); if (!r.ok) { toast(r.msg, 'err'); return; } modal('兑换完成', '

' + esc(r.txt) + '

', [{ label: '承受', cls: 'btn-gold' }]); renderAll(); autoSave(); } }]); }); acts.appendChild(btn); } d.appendChild(acts); b.appendChild(d); }); /* 当前身上的负担总览 */ b.appendChild(secHead('你身上现在的负担')); var flaws = G.activeFlaws(); var burden = []; burden.push('道伤:' + D.INJURY_TYPES.map(function (t) { return t + '·' + D.INJURY_LEVELS[pc.injury[t] || 0]; }).join(' ')); burden.push('道心污染:' + (D.POLLUTION[pc.pollution] || D.POLLUTION[0]).name); burden.push('天道监察:' + (D.MONITOR[pc.monitor] || D.MONITOR[0]).lv + ' ' + (D.MONITOR[pc.monitor] || D.MONITOR[0]).name); burden.push('代价状态:' + D.COST_LEVELS[pc.costLevel || 0].name); burden.push('寿元:' + pc.age + ' / ' + (isFinite(pc.lifeMax) ? pc.lifeMax : '∞') + '(' + G.lifespanStage(pc).name + ')'); burden.push('声誉:' + pc.reputation + '(' + G.reputationWord() + ')'); burden.push('活跃因果:' + G.karmas.filter(function (k) { return k.status === 'active'; }).length + ' 条'); b.appendChild(el('div', 'sysbox', burden.map(function (x) { return '

· ' + esc(x) + '

'; }).join(''))); if (flaws.length) { b.appendChild(secHead('命途缺陷(' + flaws.length + ' 项,会周期性发作)')); flaws.forEach(function (f) { var cur = (f.fx || {}).curse; var evs = cur && D.CURSE_EVENTS[cur] ? D.CURSE_EVENTS[cur] : null; var d = el('div', 'tian-item'); d.innerHTML = '
' + esc(f.name) + '
' + '
' + esc(f.desc) + '
' + (evs ? '
已记录的发作形式(共 ' + evs.length + ' 种):
' + evs.map(function (x) { return '· ' + esc(x); }).join('
') + '
' : ''); b.appendChild(d); }); } } function doTianEarn(act, arg) { var r = G.tpEarn(act, arg); if (!r.ok) { toast(r.msg, 'err'); return; } modal('承受完成', '

' + esc(r.txt) + '

天道点 +' + r.gained + ' 当前持有 ' + r.total + '

' + '

这笔账记下了。它不会自己消失。

', [{ label: '我认', cls: 'btn-gold' }]); if (G.pc.hp <= 0 && !G.over) { G.die('死于自我承受'); showDeath(D.DEATH_FORMS); } renderAll(); autoSave(); } /* --- 因果结算表历史 --- */ function tabSettle(b) { b.appendChild(secHead('因果结算表·历史')); if (!G.settlements.length) { b.appendChild(el('div', 'empty', '尚无结算记录。')); return; } G.settlements.slice(0, 14).forEach(function (rec) { var s = rec.s, rows = []; if (s.karma.length) rows.push(['因果网', s.karma.join(';')]); if (s.npc.length) rows.push(['NPC关系', s.npc.join(';')]); if (s.world.length) rows.push(['世界状态', s.world.join(';')]); if (s.daoyun) rows.push(['道蕴', (s.daoyun > 0 ? '+' : '') + s.daoyun]); if (s.injury.length) rows.push(['道伤', s.injury.join(';')]); if (s.bp.length) rows.push(['行为塑道', s.bp.join(';')]); if (s.arts.length) rows.push(['百艺', s.arts.join(';')]); var d = el('div', 'karma'); d.innerHTML = '
第 ' + rec.turn + ' 回合
' + (rows.length ? '' + rows.map(function (r) { return ''; }).join('') + '
' + esc(r[0]) + '' + esc(r[1]) + '
' : '
本回合无显著变化。
'); b.appendChild(d); }); /* 久远记录:压缩为摘要但永不丢弃(铁律·绝对不遗忘) */ if (G.settleArchive && G.settleArchive.length) { b.appendChild(secHead('久远记录·摘要保留(' + G.settleArchive.length + ' 回合)')); b.appendChild(el('div', 'ka-meta', '每十回合触发一次记忆压缩。日常级信息降级为背景设定,但因果本身永久保留、永不丢弃。')); G.settleArchive.slice(0, 40).forEach(function (a) { b.appendChild(el('div', 'tick hidden-ev', '第' + a.turn + '回合' + esc(a.brief))); }); } } /* --- NPC --- */ function tabNpc(b) { var alive = G.npcs.filter(function (n) { return n.alive; }); var dead = G.npcs.filter(function (n) { return !n.alive; }); b.appendChild(secHead('存活人物(' + alive.length + ')· 六要素齐备')); if (!alive.length) b.appendChild(el('div', 'empty', '没有认识的人了。')); alive.sort(function (x, y) { return (y.met ? 1 : 0) - (x.met ? 1 : 0) || y.power - x.power; }).forEach(function (n) { var atts = D.ATTITUDES.filter(function (a) { return (n.att[a] || 0) >= 15; }); var d = el('div', 'npc'); d.addEventListener('click', function () { openNpc(n.id); }); d.innerHTML = '
' + esc(n.fullName) + '' + '' + esc(D.REALMS[n.realmIdx].name) + '' + (n.isQiyun ? '气运之子' : '') + (n.met ? '' : '未谋面') + '' + esc(n.factionName) + '
' + '
战力 ' + n.power + ' | ' + n.age + ' 岁 | 性格' + esc(n.personality) + ' | 目标:' + esc(n.goal) + (n.met ? '
羁绊:' + esc(D.BONDS[n.bond || 0]) + ' | 背叛概率:' + Math.round(n.betrayalBase * 100) + '%' : '') + (n.motiveRevealed ? '
隐藏动机:' + esc(n.hiddenMotive) + '' : '') + '
' + '
' + (atts.length ? atts.map(function (a) { return '' + a + ' ' + n.att[a] + ''; }).join('') : '漠视 ' + (n.att['漠视'] || 0) + '') + '
'; if (n.memory.length) { d.innerHTML += '
' + n.memory.slice(0, 4).map(function (m) { return '
· 第' + m.turn + '回合【' + esc(m.level) + '】' + esc(m.summary) + (m.fuzzy >= 0.5 ? '(记忆已模糊)' : '') + '
'; }).join('') + '
'; } if (n.actionLog && n.actionLog.length) { d.innerHTML += '
' + n.actionLog.slice(0, 2).map(function (a) { return '
· 第' + a.turn + '回合:' + esc(a.act) + '
'; }).join('') + '
'; } if (n.met && n.alive) { var acts = el('div', 'ka-acts'); [['交谈', 'meet'], ['赠礼', 'gift'], ['切磋', 'spar']].forEach(function (a) { var btn = el('button', 'btn btn-sm btn-ghost', a[0]); btn.addEventListener('click', function () { npcAct(n, a[1]); }); acts.appendChild(btn); }); d.appendChild(acts); } b.appendChild(d); }); if (dead.length) { b.appendChild(secHead('已故(' + dead.length + ')· 死亡不可跳过')); dead.forEach(function (n) { b.appendChild(el('div', 'npc dead', '
' + esc(n.fullName) + '第 ' + n.deathTurn + ' 回合
' + '
' + esc(n.deathWay) + ' | 享年 ' + n.age + '
')); }); } } function npcAct(n, kind) { var pc = G.pc, out = []; if (kind === 'meet') { pc.energy = Math.max(0, pc.energy - 15); var d = G.dice(0.45 + (pc.six['魅力'] - 3) * 0.06 + (n.att['感恩'] || 0) / 400 - (n.att['仇恨'] || 0) / 300, { attr: '魅力' }); if (d.result === 'great' || d.result === 'ok') { G.npcAttitude(n, '感恩', 10, '交谈'); n.bond = Math.min(pc.bondCap, (n.bond || 0) + 1); pc.bonds[n.id] = n.bond; out.push('谈得还行。' + (d.result === 'great' ? '他多说了一些本来不打算说的话。' : '他把该说的说了,不该说的没说。')); if (d.result === 'great' && !n.motiveRevealed && Math.random() < 0.4) { n.motiveRevealed = true; out.push('他透露了:' + n.hiddenMotive); } G.addBp('情', 1.0, '交谈'); } else { G.npcAttitude(n, '漠视', 8, '交谈无果'); out.push('话没谈拢。他找了个理由先走了,理由很体面。'); } } else if (kind === 'gift') { var cost = Math.min(pc.money, 200); if (cost < 50) { toast('灵石不足(至少需要 50)', 'err'); return; } pc.money -= cost; pc.energy = Math.max(0, pc.energy - 8); G.npcAttitude(n, '感恩', Math.round(cost / 12), '收到 ' + cost + ' 灵石的赠礼'); n.bond = Math.min(pc.bondCap, (n.bond || 0) + 1); pc.bonds[n.id] = n.bond; out.push('你送了他 ' + cost + ' 灵石。他收了,收得' + (cost > 150 ? '有点犹豫,犹豫不是因为嫌少' : '很自然') + '。'); out.push('感恩 +' + Math.round(cost / 12) + ',羁绊 → ' + D.BONDS[n.bond]); if (n.bond >= 4) out.push('羁绊已达「生死」。可触发羁绊技:替死、共鸣、传承。这不是白来的——它意味着他出事的时候,你也会痛。'); } else { pc.energy = Math.max(0, pc.energy - 25); var d2 = G.dice(0.5 * (pc.combat / Math.max(1, n.power)), { attr: '根骨' }); out.push(d2.result === 'great' || d2.result === 'ok' ? '你赢了这一场。他抱拳,笑得不太自然。' : '你输了。他没有追击,这算是给面子。'); G.npcAttitude(n, d2.result === 'great' ? '敬畏' : '忌惮', 12, '切磋'); pc.hp = Math.max(1, pc.hp - Math.round(pc.hpMax * 0.12)); G.addBp('剑', 1.5, '切磋'); } modal(n.fullName, out.map(function (t) { return '

' + esc(t) + '

'; }).join(''), [{ label: '好', cls: 'btn-gold' }]); renderAll(); autoSave(); } /* --- 世界动态 --- */ function tabWorld(b) { var trib = D.TRIBULATION[G.stage]; b.appendChild(secHead('大劫九阶段')); var tw = el('div', 'karma'); tw.innerHTML = '
' + esc(trib.name) + '第 ' + G.turn + ' 回合
' + '
' + esc(trib.desc) + '
' + '
' + D.TRIBULATION.map(function (s, i) { return '' + esc(s.name) + ''; }).join('') + '
' + '
每 50 回合推进一次;下一阶段:' + esc(D.TRIBULATION[Math.min(D.TRIBULATION.length - 1, G.stage + 1)].name) + '(第 ' + D.TRIBULATION[Math.min(D.TRIBULATION.length - 1, G.stage + 1)].turn + ' 回合)
'; b.appendChild(tw); b.appendChild(secHead('世界大事件日历(未来一百回合)')); var upcoming = G.bigEvents.filter(function (e) { return !e.done; }).slice(0, 8); if (!upcoming.length) b.appendChild(el('div', 'empty', '日历上没有已确定的节点。这不代表不会出事。')); upcoming.forEach(function (e) { b.appendChild(el('div', 'karma', '
' + esc(e.kind) + '' + '' + Math.max(0, e.turn - G.turn) + ' 回合后
' + '
' + esc(e.desc) + '
' + '
权重 ' + e.weight + ' | 触发时插入世界级叙事,所有相关人物线同步推进
')); }); b.appendChild(secHead('世界 TICK·后台事件(每回合 ≥3 件)')); b.appendChild(el('div', 'ka-meta', '这些是你在场之外发生的事。世界不因你的不在场而暂停。')); G.ticks.slice(0, 26).forEach(function (t) { b.appendChild(el('div', 'tick' + (t.hidden ? ' hidden-ev' : ''), '' + esc(t.type) + '' + esc(t.text) + (t.hidden ? '(你当时并不知情)' : ''))); }); b.appendChild(secHead('势力格局')); G.factions.forEach(function (f) { var war = f.warWith ? G.factions.filter(function (x) { return x.id === f.warWith; })[0] : null; var d = el('div', 'karma'); d.innerHTML = '
' + esc(f.name) + '' + '' + esc(f.kind) + '' + '实力 ' + f.power + '
' + '
' + esc(f.desc) + '
' + '
地盘:' + esc(f.territory) + (war ? ' | 交战中:' + esc(war.name) + '' : '') + '
'; b.appendChild(d); }); if (G.brands && G.brands.length) { b.appendChild(secHead('世界刻印(永久改变世界规则)')); G.brands.forEach(function (br) { b.appendChild(el('div', 'karma', '
' + esc(br.name) + '' + esc(br.place) + '
' + '
' + esc(br.desc) + '
刻于第 ' + br.turn + ' 回合,以「' + esc(br.medium) + '」为媒,刻印者:' + esc(br.owner) + '
')); }); } if ((G.daoTraces || []).length) { b.appendChild(secHead('道痕(行为模因,可被感知与传播)')); G.daoTraces.slice(0, 14).forEach(function (t) { b.appendChild(el('div', 'tick', '' + esc(t.path) + '' + esc(t.place) + '留下' + esc(t.color) + ',强度 ' + t.strength.toFixed(1) + (t.mutant ? ' 已变异为「' + esc(t.mutant) + '」' : '') + '(第 ' + t.turn + ' 回合)')); }); } } /* --- 天地志:这个世界本身的样子 --- */ function tabLore(b) { var pc = G.pc, w = pc.world; b.appendChild(secHead('脚下这块地方')); var wd = el('div', 'karma'); wd.innerHTML = '
' + esc(w.name) + '' + '' + esc(w.tag) + '
' + '
' + esc(w.desc) + '
' + '
灵气 ×' + w.qi + ' 法则 ×' + w.law + ' 凶险 ×' + w.danger + ' | ' + (w.states || []).map(function (s) { return esc(s); }).join('、') + '
'; b.appendChild(wd); b.appendChild(secHead('界域·可远行之处')); (D.WORLDS || []).slice(0, 10).forEach(function (x) { var here = x.id === w.id; b.appendChild(el('div', 'tick' + (here ? '' : '') , '' + (here ? '此地' : esc(x.tag)) + '' + '' + esc(x.name) + '' + ' 灵气×' + x.qi + ' 凶险×' + x.danger + '
' + esc(x.desc) + '')); }); var groups = {}; (D.LORE || []).forEach(function (e) { (groups[e.k] = groups[e.k] || []).push(e); }); Object.keys(groups).forEach(function (k) { b.appendChild(secHead(k)); groups[k].forEach(function (e) { b.appendChild(el('div', 'karma', '
' + esc(e.n) + '
' + '
' + esc(e.d) + '
')); }); }); b.appendChild(el('div', 'ka-meta', '天地志只记已确知的部分。界域之外的空白,要自己走过去才知道。')); } /* --- 境界体系 --- */ function tabRealm(b) { var pc = G.pc, sys = D.systemOf(pc), plane = D.planeOf(pc); b.appendChild(secHead('我走的道 · ' + sys.name)); var head = el('div', 'karma'); head.innerHTML = '
' + esc(sys.name) + '' + '' + esc(plane.name) + '
' + '
' + esc(sys.desc) + '
' + '
当前境界:' + esc(D.realmNameOf(pc, pc.realmIdx)) + ' | 下一境:' + esc(pc.realmIdx >= sys.ladder.length - 1 ? '已至尽头' : D.realmNameOf(pc, pc.realmIdx + 1)) + ' | 道蕴 ' + n2s(pc.qi) + ' / ' + n2s(pc.qiNeed) + '
'; b.appendChild(head); b.appendChild(secHead('境界阶梯(共 ' + sys.ladder.length + ' 阶,当前已标记)')); sys.ladder.forEach(function (r, i) { var cls = i === pc.realmIdx ? 'cur' : (i < pc.realmIdx ? 'past' : ''); var d = el('div', 'ladder-row ' + cls); d.innerHTML = '' + (i + 1) + '' + '' + esc(r[0]) + (i === pc.realmIdx ? '当前' : '') + '' + '' + esc(r[1]) + ''; b.appendChild(d); }); if (sys.custom) { b.appendChild(secHead('与原有境界的对应(战力同档)')); var baseR = D.systemById('qi'); sys.ladder.forEach(function (r, i) { b.appendChild(el('div', 'ladder-row', '' + (i + 1) + '' + esc(r[0]) + '= 气道 · ' + esc(baseR.ladder[i][0]) + '(战力系数 1.00)')); }); } b.appendChild(secHead('上界 · 位面')); b.appendChild(el('div', 'ka-meta', '每个初始世界按四条道各有一座上界;四座之上还有一处位面,所有飞升者最后都到那里。')); var wid = (pc.world && (pc.world.upperOf || pc.world.id)) || null; (wid ? D.upperRealmsFor(wid) : []).forEach(function (u) { var mine = u.systemId === sys.id; b.appendChild(el('div', 'karma', '
' + esc(u.name) + '' + '' + esc(D.systemById(u.systemId).name) + (mine ? '·本道' : '') + '
' + '
' + esc(u.desc) + '
' + '
灵气 ×' + u.qi + ' 危险 ×' + u.danger + '
')); }); b.appendChild(el('div', 'karma', '
' + esc(D.TOP_PLANE.name) + '' + '位面
' + esc(D.TOP_PLANE.desc) + '
')); } /* --- 百艺道途 --- */ function tabArt(b) { var pc = G.pc; b.appendChild(secHead('行为塑道·BP 阶跃(10/25/50/100/200)')); var paths = Object.keys(pc.bp).map(function (k) { var p = D.DAO_PATHS.filter(function (x) { return x.key === k; })[0] || { id: k, color: '#888', desc: '' }; return { k: k, v: pc.bp[k], st: pc.bpStage[k] || 0, p: p }; }).sort(function (a, c) { return c.v - a.v; }); if (!paths.length) b.appendChild(el('div', 'empty', '还没有形成任何道途。行为塑道不靠选择,靠累积——你做过什么,你就是什么。')); paths.forEach(function (x) { var nextT = D.BP_STAGES[x.st] || null; var prevT = x.st > 0 ? D.BP_STAGES[x.st - 1] : 0; var pctv = nextT ? Math.min(100, (x.v - prevT) / (nextT - prevT) * 100) : 100; var br = D.BEHAVIOR_REALMS[Math.min(D.BEHAVIOR_REALMS.length - 1, x.st)]; var d = el('div', 'art'); d.innerHTML = '
' + esc(x.p.id) + '' + '' + '' + esc(x.st ? D.BP_STAGE_NAMES[x.st - 1] : '未觉醒') + '
' + '
' + x.v.toFixed(1) + ' BP' + (nextT ? ' | 下一阶跃 ' + nextT + ' BP(' + esc(D.BP_STAGE_NAMES[x.st]) + ')' : ' | 已达圆满') + ' | 行为境界:' + esc(br.name) + '(效率 +' + Math.round(br.eff * 100) + '%)' + (pc.silent && pc.silent[x.k] ? ' 已寂灭' : '') + '
' + '
' + esc(x.p.desc) + '
' + '
' + esc(br.desc) + '
'; b.appendChild(d); }); if (pc.conflictPct) { b.appendChild(el('div', 'sysbox box-threat', '

【道心冲突 ' + pc.conflictPct + '%】' + (pc.conflictPct >= 90 ? '道心崩裂:心魔劫已至,必须立即解决,否则道途废弛。' : pc.conflictPct >= 60 ? '明显裂痕:效率下降 30%,有失败风险。' : '轻微不适:效率下降 10%。') + '

解决路径:① 选择一方(强化一道,另一道寂灭)② 寻求统一(需特殊契机)③ 承受分裂(每回合道心伤害)

')); } b.appendChild(secHead('修仙百艺(' + Object.keys(pc.arts).length + ' 门已入门)')); if (!Object.keys(pc.arts).length) b.appendChild(el('div', 'empty', '尚未修习任何百艺。选择「打磨一门百艺」可以开始。')); Object.keys(pc.arts).forEach(function (aid) { var a = pc.arts[aid]; var def = D.ARTS.filter(function (x) { return x.id === aid; })[0] || { levels: ['习艺', '悟艺', '化艺', '创艺', '道艺'], desc: '', stages: [] }; var nextT = D.ART_BP_STAGES[a.level] || null; var prevT = a.level > 0 ? D.ART_BP_STAGES[a.level - 1] : 0; var pctv = nextT ? Math.min(100, (a.bp - prevT) / (nextT - prevT) * 100) : 100; var d = el('div', 'art'); d.innerHTML = '
' + esc(aid) + '' + '' + esc(a.level ? def.levels[a.level - 1] : '未入门') + '' + '' + Math.round(a.bp) + ' BP
' + '
' + '
' + (nextT ? '下一阶跃:' + nextT + ' BP(' + esc(def.levels[a.level] || '—') + ')' : '已达道艺,举手投足皆含法则') + ' | 闲置 ' + (G.turn - (a.last || G.turn)) + ' 回合' + ((G.turn - (a.last || G.turn)) > 30 ? '(每日衰减 5%)' : '') + '
' + (def.desc ? '
' + esc(def.desc) + '
' : '') + (def.taboo ? '
禁忌:' + esc(def.taboo) + '(触发天道监察)
' : ''); b.appendChild(d); }); if (pc.fusionDao) { b.appendChild(el('div', 'sysbox box-break', '

【融合道途】' + esc(pc.fusionDao) + '

两种百艺在你手里互相说话。融合奖励:独特能力、特殊道途、世界刻印、道统建立。融合风险:道心冲突加剧、反噬叠加、道基不稳、被天道监察标记。

')); } b.appendChild(secHead('禁忌技艺(威力强大,必触发监察)')); D.TABOOS.forEach(function (t) { var used = (pc.tabooUsed || []).some(function (u) { return u.id === t.id; }); var d = el('div', 'karma'); d.innerHTML = '
' + esc(t.name) + '' + '监察 L' + t.monitor + '' + '威力 ×' + t.power + '
' + '
' + esc(t.desc) + '
' + '
' + (t.art ? '所属百艺:' + esc(t.art) + ' | ' : '') + '堕落道途:' + esc(t.dao) + (used ? ' 你已用过' : '') + '
'; if (!used) { var acts = el('div', 'ka-acts'); var btn = el('button', 'btn btn-sm', '修炼此禁忌'); btn.addEventListener('click', function () { modal('确认修炼禁忌', '

' + esc(t.name) + '

' + esc(t.desc) + '

' + '

后果:天道监察直接升至 L' + t.monitor + '(成功率 -' + Math.round(D.MONITOR[t.monitor].pen * 100) + '%),道心污染 +2 阶,并绑定一条不可转化的天道级因果「禁忌之债」。豁免不是免单,是记账以后一起算。

', [{ label: '再想想', cls: 'btn-ghost' }, { label: '我认这个代价', cls: 'btn-gold', act: function () { var ur = G.useTaboo(t.id); pc.daoyun += Math.round(80 * t.power); pc.pollution = E.clamp(pc.pollution + 2, 0, 4); G.addCost(3, t.name); toast('禁忌已成:道蕴 +' + Math.round(80 * t.power) + ',监察 L' + (ur && ur.monitor ? ur.monitor.to : t.monitor), 'err'); renderAll(); autoSave(); } }]); }); acts.appendChild(btn); d.appendChild(acts); } b.appendChild(d); }); b.appendChild(secHead('证道 / 道统 / 刻印')); var asc = G.canAscend(); var ad = el('div', 'karma'); ad.innerHTML = '
证道条件' + (asc.ok ? '已满足' : '未满足') + '
' + asc.conds.map(function (c) { return '
' + (c.ok ? '✓' : '✗') + ' ' + esc(c.name) + '
'; }).join('') + '
证道不是终点,而是新形态的开始。成功后可选择:飞升上界 / 隐世 / 化道 / 继续游戏。
'; var aa = el('div', 'ka-acts'); if (asc.ok) { ['飞升上界', '隐世', '化道', '继续游戏'].forEach(function (c) { var btn = el('button', 'btn btn-sm btn-gold', c); btn.addEventListener('click', function () { modal('证道·' + c, '

' + esc(G.ascend(c)) + '

', [{ label: '铭记', cls: 'btn-gold' }]); if (c !== '继续游戏') { G.over = true; G.overReason = '证道·' + c; } renderAll(); }); aa.appendChild(btn); }); } ad.appendChild(aa); b.appendChild(ad); var lb = el('button', 'btn btn-sm', '尝试建立道统'); lb.addEventListener('click', function () { var r = G.foundLineage(); modal('道统建立', '

' + esc(r.msg) + '

', [{ label: '知道了', cls: 'btn-gold' }]); renderAll(); autoSave(); }); b.appendChild(el('div', 'ka-acts')).appendChild(lb); b.appendChild(el('div', 'ka-acts')).appendChild((function () { var btn = el('button', 'btn btn-sm', '尝试创造世界刻印'); btn.addEventListener('click', function () { var list = D.WORLD_BRANDS.map(function (w, i) { return '' + (i + 1) + '. ' + esc(w.name) + '(' + esc(w.art) + ')'; }).join(''); modal('选择刻印类型', '

' + list + '

条件:道途第三境以上 + 道蕴 ≥300 + 一件材料 + 特定时机(大劫推进前后或地脉异动时)。刻印永久改变该地规则,并绑定一条天道级因果。

', D.WORLD_BRANDS.map(function (w) { return { label: w.name, cls: 'btn-sm', close: true, act: function () { var r = G.createBrand(w.name); modal('世界刻印', '

' + esc(r ? r.msg : '无法创造。') + '

', [{ label: '知道了', cls: 'btn-gold' }]); renderAll(); autoSave(); } }; })); }); return btn; })()); /* 斩道 */ var zd = el('button', 'btn btn-sm btn-ghost', '斩道(放弃当前道途)'); zd.addEventListener('click', function () { modal('斩道', '

斩道需付出:道基重铸、寿元献祭、因果断裂。斩道后道途积分清零,保留部分能力碎片,并触发「大道反噬」——随机失去一项感官、记忆或羁绊。

道途不可逆:一旦成型(第三境以上),逆转需要极端代价。禁止轻松转职。

', [{ label: '取消', cls: 'btn-ghost' }, { label: '斩', cls: 'btn-gold', act: function () { var pc2 = G.pc; var top = Object.keys(pc2.bp).sort(function (a, c) { return pc2.bp[c] - pc2.bp[a]; })[0]; if (top) { pc2.bp[top] = 0; pc2.bpStage[top] = 0; } pc2.lifeMax = Math.round(pc2.lifeMax * 0.85); G.addInjury('道基', Math.min(5, pc2.injury['道基'] + 2), '斩道'); var losses = ['一项感官', '一段记忆', '一段羁绊']; var lost = losses[Math.floor(Math.random() * losses.length)]; if (lost === '一段羁绊') { var bs = Object.keys(pc2.bonds); if (bs.length) { var bn = G.npc(bs[0]); if (bn) { bn.bond = 0; pc2.bonds[bs[0]] = 0; } } } pc2.combat = G.calcPower(pc2); toast('斩道完成。大道反噬:你失去了' + lost + '。', 'err'); renderAll(); autoSave(); } }]); }); b.appendChild(el('div', 'ka-acts')).appendChild(zd); } /* --- 坊市经济 --- */ function tabMarket(b) { var pc = G.pc; var pm = G.priceModifier(); var last = G.priceHist[0]; b.appendChild(secHead('修仙界经济(每十回合更新价格表)')); var mktReasonHtml = (last && last.reason && last.reason.length) ? '
本次变动的因果解释:
· ' + last.reason.map(esc).join('
· ') + '
' : '
价格变动必须有明确因果:供需变化 / 事件驱动 / 势力行为 / 天象影响。禁止无理由暴涨暴跌。
'; b.appendChild(el('div', 'mkt-note', '
你的持有:' + n2s(pc.money) + ' 灵石 | 声誉系数 ×' + pm.toFixed(2) + ' (' + esc(G.reputationWord()) + ')
' + mktReasonHtml)); b.appendChild(el('div', 'ka-meta', '经商四重判定:运输风险(劫修/妖兽/天灾)· 市场影响(大额买卖改变当地价格)· 引来觊觎(商会征税/黑市设局/正道通缉)· 口碑影响(声誉决定进价与商机)')); var prevPrices = G.priceHist[1] ? G.priceHist[1].prices : G.basePrices; D.MARKET.forEach(function (cat) { b.appendChild(el('div', 'mkt-cat', esc(cat.cat))); cat.items.forEach(function (it) { var cur = G.prices[it.n] || it.p; var before = prevPrices[it.n] || it.p; var shown = Math.max(1, Math.round(cur * pm)); var delta = before ? Math.round((cur - before) / before * 100) : 0; var own = pc.items.filter(function (x) { return x.n === it.n && x.qty; }).reduce(function (a, c) { return a + (c.qty || 0); }, 0); var row = el('div', 'mkt-row'); row.innerHTML = '' + esc(it.n) + '' + '' + (own ? '持' + own : '') + '' + '' + (delta > 0 ? '▲' : delta < 0 ? '▼' : '—') + Math.abs(delta) + '%' + '' + n2s(shown) + ''; var buy = el('button', 'btn btn-sm btn-ghost', '买'); buy.addEventListener('click', function () { tradeDlg(it.n, true, shown); }); var sell = el('button', 'btn btn-sm btn-ghost', '卖'); sell.disabled = !own; sell.addEventListener('click', function () { tradeDlg(it.n, false, shown); }); row.appendChild(buy); row.appendChild(sell); b.appendChild(row); }); }); } function tradeDlg(name, buy, price) { var qty = 1; var html = '

' + (buy ? '购入' : '售出') + ' ' + esc(name) + ' 单价 ' + n2s(price) + ' 灵石

' + '
' + '' + n2s(price) + ' 灵石
' + '

' + (buy ? '大量买入会推高当地价格。' : '大量卖出会压低当地价格;运输途中可能遭遇劫修、妖兽或天灾;大额交易会引来商会、黑市或正道宗门的注意。') + '

'; modal((buy ? '购入·' : '售出·') + name, html, [ { label: '取消', cls: 'btn-ghost' }, { label: buy ? '确认购入' : '确认售出', cls: 'btn-gold', act: function () { var q = Math.max(1, parseInt($('#tq') ? $('#tq').value : '1', 10) || 1); var r = G.trade(name, q, buy); modal('交易结果', '

' + esc(r.msg) + '

' + (r.ok ? '' : '

交易未达成。

'), [{ label: '好', cls: 'btn-gold' }]); renderAll(); autoSave(); } }]); var inp = $('#tq'); if (inp) inp.addEventListener('input', function () { var q = Math.max(1, parseInt(this.value, 10) || 1); var s = $('#tsum'); if (s) s.textContent = n2s(price * q) + ' 灵石'; }); } /* --- 万古道碑 --- */ function tabStele(b) { b.appendChild(secHead('万古道碑(道号一旦获得,永久记录)')); if (!G.daotitles.length) b.appendChild(el('div', 'empty', '尚无道碑。道号由自身行为自然凝结——需要某条道途达到第三境「道途初凝」以上。')); G.daotitles.forEach(function (s) { b.appendChild(el('div', 'karma', '
「' + esc(s.title) + '」' + '' + esc(s.path) + '' + esc(s.realm) + '
' + '
' + esc(s.deed) + '
' + '
' + esc(s.name) + ' | ' + esc(s.world + '·' + s.era) + ' | 第 ' + s.turn + ' 回合 | 种子 ' + s.seed + '
')); }); if (G.prevLife && G.prevLife.length) { b.appendChild(secHead('前世(跨档因果)')); G.prevLife.forEach(function (p) { b.appendChild(el('div', 'karma', '
' + esc(p.name) + '' + esc(p.realm) + '
' + '
享年 ' + p.age + ' | 第 ' + p.turn + ' 回合 | 死因:' + esc(p.cause) + '
')); }); } b.appendChild(secHead('种子链(确保跨档继承时世界状态可追溯)')); b.appendChild(el('div', 'ka-meta', G.seedChain.map(function (s, i) { return (i ? ' → ' : '') + s; }).join(''))); } /* --- 存档 --- */ function tabSave(b) { b.appendChild(secHead('天道存档码(含扩展字段)')); b.appendChild(el('div', 'ka-meta', '每次存档必含:因果网快照 · NPC关系网 · 世界大事件日历(未来一百回合)· 当前大劫阶段与下一阶段触发条件 · 随机种子链。')); var gen = el('button', 'btn btn-sm btn-blue', '生成存档码'); var ta = el('textarea', 'code'); ta.id = 'saveCode'; ta.placeholder = '点击「生成存档码」导出,或粘贴一段存档码后点击「读取」。'; var acts = el('div', 'ka-acts'); gen.addEventListener('click', function () { var r = G.makeSaveCode(); if (r.code) { ta.value = r.code; toast('存档码已生成(' + Math.round(r.size / 1024) + ' KB 数据)', 'ok'); } else toast('生成失败:' + r.error, 'err'); }); var load = el('button', 'btn btn-sm btn-gold', '从存档码读取'); load.addEventListener('click', function () { if (!ta.value.trim()) { toast('请先粘贴存档码', 'err'); return; } var g2 = E.Game.loadFromCode(ta.value.trim()); if (g2 && g2.error) { toast('读取失败:' + g2.error, 'err'); return; } if (!g2 || !g2.pc) { toast('读取失败:存档码无效', 'err'); return; } G = g2; curRound = 0; $('#storyWrap').innerHTML = ''; var rnd = el('div', 'round'); rnd.appendChild(headRow('读档', '第 ' + G.turn + ' 回合')); rnd.appendChild(el('div', 'sysbox box-break', '

【续前世因缘】存档码已解析,世界状态已恢复。

' + esc(G.worldBrief()) + '

')); $('#storyWrap').appendChild(rnd); renderAll(); renderChoices(G.makeOptions()); toast('读档成功', 'ok'); }); acts.appendChild(gen); acts.appendChild(load); b.appendChild(acts); b.appendChild(ta); b.appendChild(secHead('本地存档槽')); var saves = getSaves(); if (!saves.length) b.appendChild(el('div', 'empty', '当前浏览器尚无存档。')); saves.forEach(function (s, i) { var d = el('div', 'save-slot'); d.innerHTML = '
' + esc(s.name) + '' + esc(s.realm) + '' + esc(s.time) + '
' + '
第 ' + s.turn + ' 回合 | 大劫 ' + esc(s.stage) + ' | ' + esc(s.world) + ' | ' + esc(s.brief) + '
'; var a = el('div', 'ss-acts'); var ld = el('button', 'btn btn-sm btn-blue', '读取'); ld.addEventListener('click', function () { var g2 = E.Game.loadFromCode(s.code); if (g2 && g2.error) { toast('读取失败:' + g2.error, 'err'); return; } if (!g2 || !g2.pc) { toast('存档损坏', 'err'); return; } G = g2; curRound = 0; $('#storyWrap').innerHTML = ''; var rnd = el('div', 'round'); rnd.appendChild(headRow('读档', '第 ' + G.turn + ' 回合')); rnd.appendChild(el('div', 'sysbox box-break', '

【读档成功】' + esc(s.name) + ',' + esc(s.realm) + '。

' + esc(G.worldBrief()) + '

' + '

铁律·绝对不撤销:读档不能撤销已发生的因果。你回到的是这个时间点,不是那个时间点之前。

')); $('#storyWrap').appendChild(rnd); renderAll(); renderChoices(G.makeOptions()); toast('读档成功', 'ok'); }); var del = el('button', 'btn btn-sm btn-ghost', '删除'); del.addEventListener('click', function () { modal('删除存档', '

确定删除「' + esc(s.name) + '」的存档吗?此操作不可撤销。

', [ { label: '取消', cls: 'btn-ghost' }, { label: '删除', cls: 'btn-gold', act: function () { var arr = getSaves(); arr.splice(i, 1); setSaves(arr); renderAll(); toast('存档已删除'); } }]); }); a.appendChild(ld); a.appendChild(del); d.appendChild(a); b.appendChild(d); }); var ns = el('button', 'btn btn-sm btn-gold', '存入新槽位'); ns.addEventListener('click', function () { saveToSlot(); }); b.appendChild(el('div', 'ka-acts')).appendChild(ns); b.appendChild(secHead('自动存档')); b.appendChild(el('div', 'ka-meta', '每回合自动保存到当前浏览器的「自动存档」槽位。关闭页面不会丢失进度;清理浏览器数据会丢失。建议每十回合手动导出一份存档码另存。')); } function getSaves() { try { return JSON.parse(localStorage.getItem(LS_KEY) || '[]'); } catch (e) { return []; } } function setSaves(a) { try { localStorage.setItem(LS_KEY, JSON.stringify(a.slice(0, 12))); } catch (e) { toast('存档写入失败:浏览器存储空间可能已满', 'err'); } } function saveToSlot() { var r = G.makeSaveCode(); if (!r.code) { toast('存档码生成失败', 'err'); return; } var arr = getSaves(); arr.unshift({ name: G.pc.name + (G.pc.daoTitle ? '「' + G.pc.daoTitle + '」' : ''), realm: D.REALMS[G.pc.realmIdx].name, turn: G.turn, stage: D.TRIBULATION[G.stage].name, world: G.pc.world.name, brief: '因果' + G.karmas.filter(function (k) { return k.status === 'active'; }).length + '条 · 伏笔' + G.foreshadow.filter(function (f) { return !f.resolved; }).length + '条', time: new Date().toLocaleString('zh-CN'), code: r.code, auto: false }); setSaves(arr); renderAll(); toast('已存入新槽位', 'ok'); } function autoSave() { try { var r = G.makeSaveCode(); if (!r.code) return; var arr = getSaves().filter(function (s) { return !s.auto; }); arr.unshift({ name: G.pc.name + (G.pc.daoTitle ? '「' + G.pc.daoTitle + '」' : ''), realm: D.REALMS[G.pc.realmIdx].name, turn: G.turn, stage: D.TRIBULATION[G.stage].name, world: G.pc.world.name, brief: '自动存档', time: new Date().toLocaleString('zh-CN'), code: r.code, auto: true }); setSaves(arr); } catch (e) { /* 静默 */ } } function secHead(t) { return el('div', 'sec').appendChild(el('h5', null, esc(t))).parentNode; } /* ============================================================ SECTION: death 死亡转化 ============================================================ */ function showDeath(forms) { var info = G.deathInfo || {}; var html = '

' + esc(N.deathTxt('zhuanshi', G.pc.age)) + '

' + '

死因:' + esc(info.cause || '未知') + ' | 享年 ' + (info.age || G.pc.age) + ' | 境界 ' + esc(info.realm || '') + ' | 第 ' + (info.turn || G.turn) + ' 回合

' + '

死亡不是 Game Over,是形态转变。请选择:

'; var sel = null; var modeSel = 'suhui'; html += '
' + forms.map(function (f, i) { return '
' + esc(f.name) + (f.end ? '(终局)' : '') + '
' + esc(f.desc) + '
' + (f.end ? '' : '
继承权重 ' + Math.round(f.keep * 100) + '%
') + '
'; }).join('') + '
' + '
' + '

跨档继承:前世未解伏笔按 30% 权重继承,因果网自动继承关联节点,且继承的因果在新一档中自动提升一个优先级。继承必须付出对应代价。

'; modal('死亡·形态转变', html, [{ label: '取消', cls: 'btn-ghost' }, { label: '确认转化', cls: 'btn-gold', act: function () { if (sel == null) { toast('请选择一种转化形态', 'err'); return; } var mode = $('#selReMode') ? $('#sel