知) */ Game.prototype.rebellion = function (violated) { if (!violated) { this.rebellionCounter = Math.max(0, this.rebellionCounter - 1); return null; } this.rebellionCounter++; if (this.rebellionCounter < 3) return null; this.rebellionCounter = 0; var r = this.rng, pc = this.pc, lv = r(); if (lv < 0.5) { pc.money = Math.max(0, pc.money * 0.8); return { level: '微反噬', txt: N.backlashTxt('micro') }; } else if (lv < 0.85) { var dmg = Math.round(pc.combat * 0.3); pc.hp = Math.max(1, pc.hp - dmg); pc.pollution = clamp(pc.pollution + 1, 0, 4); return { level: '中反噬', txt: N.backlashTxt('mid') }; } else { var ks = this.karmas.filter(function (k) { return k.status === 'active'; }); if (ks.length) { var k2 = ks[Math.floor(r() * ks.length)]; k2.strength *= 2; } return { level: '大反噬', txt: N.backlashTxt('big') }; } }; /* ============================================================ SECTION: cost 代价状态机 ============================================================ */ Game.prototype.addCost = function (level, what) { var pc = this.pc; pc.costLevel = clamp(Math.max(pc.costLevel, level), 0, 4); if (level >= 3) pc.pollution = clamp(pc.pollution + 1, 0, 4); return N.costTxt(pc.costLevel, what); }; Game.prototype.reliefCost = function (way) { var pc = this.pc; if (pc.costLevel <= 0) return null; pc.costLevel = Math.max(0, pc.costLevel - 1); return { way: way, txt: '【代价缓解·' + way + '】代价降了一级。不是消失了,是被' + (way === '被记住' ? '有人记着' : way) + '分担了一点。' }; }; /* 道伤 */ Game.prototype.addInjury = function (type, lv, why) { var pc = this.pc; pc.injury[type] = clamp(Math.max(pc.injury[type] || 0, lv), 0, 5); pc.combat = this.calcPower(pc); return { type: type, lv: pc.injury[type], name: D.INJURY_LEVELS[pc.injury[type]], why: why }; }; /* ============================================================ SECTION: cultivate 修炼与突破 ============================================================ */ Game.prototype.cultivate = function (opts) { opts = opts || {}; var pc = this.pc, r = this.rng; var realm = D.REALMS[pc.realmIdx]; var gain = 6 * pc.cultMult * (1 + (pc.six['灵性'] - 3) * 0.06) * (pc.worldQi || 1); if (opts.years) gain *= opts.years * 8; if (pc.crack) gain *= 1.15; /* 裂缝里能长出别的东西 */ gain = Math.round(gain); pc.qi += gain; /* 污染积累 */ if (pc.pollRate && chance(r, pc.pollRate * 0.5) && !pc.mindShield) { pc.pollution = clamp(pc.pollution + 1, 0, 4); } var log = ['修炼所得道蕴 +' + gain + '(累计 ' + Math.floor(pc.qi) + ' / ' + pc.qiNeed + ')']; /* 岁月流逝 */ var yr = opts.years || (chance(r, 0.3) ? 1 : 0); if (yr) log = log.concat(this.passYears(yr, '闭关')); /* 突破判定 */ var brk = null; if (pc.qi >= pc.qiNeed) brk = this.tryBreakthrough(); return { gain: gain, log: log, years: yr, breakthrough: brk }; }; Game.prototype.tryBreakthrough = function () { var pc = this.pc, r = this.rng; var next = D.REALMS[Math.min(pc.realmIdx + 1, D.REALMS.length - 1)]; var cur = D.REALMS[pc.realmIdx]; /* 小境界(层)提升 */ if (pc.realmSub < cur.sub - 1 && pc.realmIdx > 0) { pc.realmSub++; pc.qi = 0; pc.qiNeed = Math.round(next.need * (0.35 + pc.realmSub * 0.12)); pc.combat = this.calcPower(pc); pc.lifeMax = Math.max(pc.lifeMax, cur.life); return { ok: true, minor: true, realm: cur.name, sub: pc.realmSub + 1, txt: N.breakthroughTxt(cur.name, pc.realmSub + 1, 0.7) }; } /* 大境界突破 */ var rate = 0.45 + (pc.six['悟性'] - 3) * 0.05; rate *= pc.breakMult; rate *= (1 - pc.injury['道基'] * 0.12); var ls = this.lifespanStage(pc); rate *= ls.fx.break; if (pc.goal && pc.goal.fx && pc.goal.fx.breakMult) rate *= pc.goal.fx.breakMult; /* 天劫 */ var tribLv = next.trib; var noTrib = pc.noTrib || this.opening.states.some(function (s) { return s.id === 'shuimian'; }); var txts = []; if (tribLv > 0 && !noTrib) { var tribRate = clamp(rate * (1 - tribLv * 0.045) / pc.tribMult, 0.05, 0.95); var d = this.dice(tribRate, { attr: '心性' }); if (d.result === 'fail' || d.result === 'greatfail') { /* 渡劫失败 */ var dmg = d.result === 'greatfail' ? 3 : 2; this.addInjury('肉身', Math.min(5, pc.injury['肉身'] + dmg), '渡劫失败'); this.addInjury('道基', Math.min(5, pc.injury['道基'] + 1), '渡劫失败'); pc.hp = Math.max(1, Math.round(pc.hp * (d.result === 'greatfail' ? 0.2 : 0.45))); pc.qi = Math.round(pc.qi * 0.5); pc.pollution = clamp(pc.pollution + 1, 0, 4); txts.push('【天劫·' + tribLv + '重】' + (d.result === 'greatfail' ? '第九道雷落下来的时候,你已经不站着了。' : '你扛过了前' + tribLv + '道,第' + (tribLv + 1) + '道把你打回了原形。') + ' 渡劫失败。'); return { ok: false, txt: txts.join(' '), tribFail: true, dice: d }; } txts.push('【天劫·' + tribLv + '重】' + (tribLv >= 7 ? '天上的东西不像是雷,像是有人在拿它砸你。' : '雷落下来的时候,' + this.place() + '方圆十里的草木同时伏倒。') + ' 你活下来了。'); } else if (noTrib && tribLv > 0) { txts.push('【无天劫】天道睡着了。这一次突破没有人管你——也没有人护你。'); } var d2 = this.dice(clamp(rate, 0.05, 0.95), { attr: '悟性' }); if (d2.result === 'fail' || d2.result === 'greatfail') { pc.qi = Math.round(pc.qi * 0.7); this.addInjury('道基', Math.min(5, pc.injury['道基'] + 1), '突破失败'); txts.push('突破失败。' + (d2.result === 'greatfail' ? '差得不是一点,是差了一整个境界的理解。' : '临门那一脚,踩空了。') + ' 道基受损。'); return { ok: false, txt: txts.join(' '), dice: d2 }; } /* 成功 */ pc.realmIdx = Math.min(D.REALMS.length - 1, pc.realmIdx + 1); pc.realmSub = 0; var nr = D.REALMS[pc.realmIdx]; pc.qi = 0; pc.qiNeed = D.REALMS[Math.min(pc.realmIdx + 1, D.REALMS.length - 1)].need || Infinity; pc.lifeMax = Math.max(pc.lifeMax, nr.life); pc.age = Math.min(pc.age, Math.round(nr.life * 0.3)); pc.combat = this.calcPower(pc); pc.daoyun += 30 + pc.realmIdx * 20; var q = d2.result === 'great' ? 0.95 : (d2.result === 'ok' ? 0.7 : 0.5); txts.push(N.breakthroughTxt(nr.name, null, q) + ' ' + nr.desc); if (d2.result === 'great') txts.push('【根基圆满】这一境你走得比所有人都干净。干净是有代价的——天道会记得你欠它一次磨难。'); if (q < 0.6) { this.addInjury('道基', 1, '突破勉强'); txts.push('【根基有瑕】勉强算成。裂缝在里面,你以后会常常想起它。'); } /* 世界回响(禁止爽文式全服通告) */ if (pc.realmIdx >= 4) txts.push('你突破的消息没有传遍天下。只有' + ['三两个人', '你所在的那座山', '一个恰好路过的散修'][Math.floor(r() * 3)] + '知道了。世界没有为你停下,也没有为你欢呼——它只是把你记进了另一档。'); return { ok: true, txt: txts.join(' '), realm: nr.name, dice: d2, quality: q }; }; /* ============================================================ SECTION: time 岁月流逝(全量结算协议) ============================================================ */ Game.prototype.passYears = function (years, reason) { var r = this.rng, pc = this.pc, logs = [], npcChanges = [], deaths = []; pc.age += years; /* 六司随岁月自然增减(后天可变):老年根骨/魅力衰减,阅历使悟性/心性增长 */ var lsNow = this.lifespanStage(pc); var sixChange = []; if (years >= 1) { var decay = (lsNow.name === '老年' || lsNow.name === '暮年' || lsNow.name === '将死'); if (decay && years >= 3) { if (pc.six['根骨'] > 1) { var d1 = Math.min(years >= 10 ? 2 : 1, pc.six['根骨'] - 1); pc.six['根骨'] -= d1; sixChange.push('根骨 -' + d1 + '(气血衰败)'); } if (pc.six['魅力'] > 1) { var d2 = Math.min(1, pc.six['魅力'] - 1); pc.six['魅力'] -= d2; sixChange.push('魅力 -' + d2 + '(容颜老去)'); } } if (years >= 5 && pc.six['悟性'] < (D.SIX_HARD_MAX || 15)) { var u1 = years >= 20 ? 2 : 1; pc.six['悟性'] = Math.min(D.SIX_HARD_MAX || 15, pc.six['悟性'] + u1); sixChange.push('悟性 +' + u1 + '(阅历沉淀)'); } if (years >= 8 && pc.six['心性'] < (D.SIX_HARD_MAX || 15)) { var u2 = 1; pc.six['心性'] = Math.min(D.SIX_HARD_MAX || 15, pc.six['心性'] + u2); sixChange.push('心性 +' + u2 + '(心境渐稳)'); } /* 淬炼过的六司可能被天道改回(锻上去的不稳) */ if ((pc.sixForged || 0) > 0 && chance(r, 0.15)) { var keys = Object.keys(pc.six).filter(function (k) { return (pc.sixBonus[k] || 0) > 0; }); if (keys.length) { var kk = keys[Math.floor(r() * keys.length)]; pc.six[kk] = Math.max(1, pc.six[kk] - 1); pc.sixBonus[kk] = Math.max(0, (pc.sixBonus[kk] || 0) - 1); sixChange.push(kk + ' -1(淬炼的成果松动了——天道记着这笔账)'); } } if (sixChange.length) { pc.combat = this.calcPower(pc); logs.push('【六司变化】' + sixChange.join(';')); } } /* ① NPC 全量结算 */ this.npcs.forEach(function (n) { if (!n.alive) return; n.age += years; /* 境界变化 */ if (chance(r, 0.12 * years * (n.isQiyun ? 2 : 1))) { if (n.realmIdx < D.REALMS.length - 2) { n.realmIdx++; n.power = this._npcPower(n); npcChanges.push(n.fullName + ' 突破至 ' + D.REALMS[n.realmIdx].name + ',' + (n.factionName === '无门无派' ? '依然是散修,但没人敢再叫他散修' : '在' + n.factionName + '的地位不一样了')); } } /* 关系演变 */ if (n.met) { if (n.att['仇恨'] > 0) { n.att['仇恨'] = Math.max(0, n.att['仇恨'] - years * 0.5); if (n.att['仇恨'] < 10) npcChanges.push(n.fullName + ' 对你的恨淡了些。淡了不等于没了。'); } if (n.att['感恩'] > 0) { n.att['感恩'] = Math.min(100, n.att['感恩'] + years * 0.8); } } /* 生死判定 */ if (n.age >= n.lifeMax) { this.npcDeathCheck(n, '寿元耗尽'); deaths.push(this.deathNarrative(n)); } else if (chance(r, 0.02 * years)) { var way = ['被仇家找上门', '闭关走火入魔', '在一次探索中没有回来', '死于一场谁也没料到的意外', '被人下毒,毒是自己人下的'][Math.floor(r() * 5)]; this.npcDeathCheck(n, way); deaths.push(this.deathNarrative(n)); } }, this); /* ② 大事件日历强制推进(入队,由 doMontage / doAction 统一领取展示) */ this.turn += years; var stAdv = this.advanceStage(); stAdv.forEach(function (s) { logs.push('【大劫推进·' + s.name + '】' + s.desc); }); this.checkBigEvents(); /* ③ 伏笔到期检查 */ var due = this.checkForeshadow(); due.forEach(function (f) { if (f.discarded) return; logs.push('【伏笔到期】' + f.text + '——' + (f.forceFire ? '它没有等你,自己发生了。' : '期限到了。这条线现在必须有个结果。')); }); /* ④ 玩家寿元检查 */ if (pc.age >= pc.lifeMax) logs.push('【寿元将尽】你的时间到了。可选择:转世、化鬼、成诡、入道、归墟。'); var priceReason = []; if (years >= 3) priceReason = this.updatePrices(); if (priceReason.length) logs.push('【物价】' + priceReason.join(';') + '。'); this._lastSettle = { npcChanges: npcChanges, deaths: deaths, foreshadow: logs, brief: this.worldBrief(), years: years, reason: reason, bigEvents: [] }; return logs; }; Game.prototype.worldBrief = function () { var trib = D.TRIBULATION[this.stage]; var parts = []; parts.push('当前大劫阶段:' + trib.name + '(' + trib.desc + ')'); var alive = this.npcs.filter(function (n) { return n.alive; }); parts.push('重要人物:存活 ' + alive.length + ' 位,已故 ' + (this.npcs.length - alive.length) + ' 位'); if (this.warPair) { var a = this.factions.filter(function (f) { return f.id === this.warPair[0]; }, this)[0]; var b = this.factions.filter(function (f) { return f.id === this.warPair[1]; }, this)[0]; if (a && b) parts.push('战事:' + a.name + ' 与 ' + b.name + ' 仍在交手'); } var unres = this.foreshadow.filter(function (f) { return !f.resolved; }); parts.push('未解伏笔:' + unres.length + ' 条' + (unres.length ? '(' + unres.slice(0, 3).map(function (f) { return f.text; }).join('、') + (unres.length > 3 ? ' 等' : '') + ')' : '')); parts.push('你的声誉:' + this.reputationWord()); return parts.join(' | '); }; Game.prototype.reputationWord = function () { var v = this.pc ? (this.pc.reputation || 0) : 0; if (v >= 60) return '声名远播(坊市打折,宗门愿见)'; if (v >= 25) return '颇有名望(路人愿意搭话)'; if (v >= -24) return '默默无闻(没人认识你,这有时候是好事)'; if (v >= -59) return '名声不佳(坊市涨价,宗门拒绝)'; return '人人喊打(有人专门在等你)'; }; /* 声誉对交易价格的影响 */ Game.prototype.priceModifier = function () { var rep = this.pc.reputation || 0; if (rep >= 60) return 0.9; if (rep >= 25) return 0.95; if (rep >= -24) return 1.0; if (rep >= -59) return 1.12; return 1.3; }; /* ============================================================ SECTION: combat 战斗 ============================================================ */ Game.prototype.fight = function (target, opts) { opts = opts || {}; var pc = this.pc, r = this.rng; var chk = this.checkChallenge(pc, target.power); if (!chk.ok) return { ok: false, msg: chk.note, blocked: true }; if (chk.needCond && !opts.condition) return { ok: false, msg: chk.note + ' 你需要先创造一个条件。', needCond: true }; var rate = clamp(0.5 * (pc.combat / Math.max(1, target.power)) - (chk.pen || 0), 0.05, 0.92); var d = this.dice(rate, { attr: '根骨' }); var out = { dice: d, chk: chk, rounds: [] }; var myHp = pc.hp, theirHp = target.power * 0.6; var rounds = 1 + Math.floor(r() * 4); for (var i = 0; i < rounds; i++) { out.rounds.push(['你出手了,' + ['剑走偏锋', '正面对撼', '借力打力', '以伤换伤'][Math.floor(r() * 4)], target.fullName + '接下了,' + ['退了两步', '纹丝不动', '反手一击', '嘴角有血'][Math.floor(r() * 4)]][Math.floor(r() * 2)]); } if (d.result === 'great') { out.win = true; out.txt = '你赢了,赢得比预想的干净。' + target.fullName + '没能站起来。'; this.npcDeathCheck(target, '死于你手'); this.addBp('杀', 3.0, '斩杀 ' + target.fullName); pc.daoyun += 20; pc.reputation = clamp(pc.reputation - 8, -100, 100); this.npcAttitude(target, '仇恨', 100, '你杀了他'); /* 口碑:灭门级传播 */ this.spreadRumorAbout(target, '仇恨', 40, '杀了 ' + target.fullName); out.loot = this._loot(target); } else if (d.result === 'ok') { out.win = true; out.txt = '你赢了。' + target.fullName + '退了,退得很不情愿,走之前看了你一眼——那一眼你以后会想起来。'; pc.hp = Math.max(1, pc.hp - Math.round(pc.hpMax * (0.1 + r() * 0.2))); this.addBp('杀', 2.0, '击败 ' + target.fullName); this.npcAttitude(target, '忌惮', 35, '你打赢了他'); out.loot = chance(r, 0.5) ? this._loot(target) : null; } else if (d.result === 'barely') { out.win = true; out.barely = true; out.txt = '你赢了,但赢得不算。' + target.fullName + '也走了,走得不急。你们都清楚这不是结束。'; pc.hp = Math.max(1, pc.hp - Math.round(pc.hpMax * (0.3 + r() * 0.25))); this.addInjury('肉身', Math.min(5, pc.injury['肉身'] + 1), '苦战'); this.addBp('杀', 1.5, '险胜'); this.npcAttitude(target, '仇恨', 25, '你伤了他'); } else { out.win = false; pc.hp = Math.max(0, pc.hp - Math.round(pc.hpMax * (d.result === 'greatfail' ? 0.85 : 0.5))); this.addInjury('肉身', Math.min(5, pc.injury['肉身'] + (d.result === 'greatfail' ? 2 : 1)), '战败'); out.txt = d.result === 'greatfail' ? '你输了,输得彻底。' + target.fullName + '没有立刻杀你——这比杀了你更麻烦。' : '你输了。' + target.fullName + '收手了,收得比你想的早,理由你不知道。'; this.npcAttitude(target, '仇恨', 15, '交过手'); this.npcAttitude(target, '忌惮', -10, '你打不过他'); if (pc.hp <= 0) out.dead = true; } pc.combat = this.calcPower(pc); return out; }; Game.prototype._loot = function (npc) { var r = this.rng; var m = Math.round(npc.power * (0.5 + r())); this.pc.money += m; var item = chance(r, 0.5) ? { n: D.ITEMS[Math.floor(r() * D.ITEMS.length)].replace('rusted ', '锈迹斑斑的'), lv: 1 + Math.floor(npc.realmIdx / 2) } : null; if (item) this.pc.items.push(item); return { money: m, item: item }; }; /* ============================================================ SECTION: desperate 绝境逢生 ============================================================ */ Game.prototype.desperate = function (choiceIdx) { var pc = this.pc, c = D.DESPERATE_COSTS[choiceIdx]; if (!c) return null; switch (c.fx) { case 'realmDown': if (pc.realmIdx > 0) { pc.realmIdx--; pc.realmSub = 0; pc.qi = 0; pc.qiNeed = D.REALMS[Math.min(pc.realmIdx + 1, D.REALMS.length - 1)].need || 60; } break; case 'baseBroken': pc.breakMult *= 0.7; this.addInjury('道基', Math.min(5, pc.injury['道基'] + 2), '绝境逢生'); break; case 'bindKarma': var k = D.KARMA_POOL.filter(function (x) { return x.level === '天道级'; })[0]; this.karmas.push({ id: 'dk' + this.turn, name: k.name, level: '天道级', power: 10, strength: 10, trigger: k.trigger, endings: k.endings, desc: k.desc, realRevealed: true, status: 'active', createdTurn: this.turn, unbreakable: true }); break; case 'sacrifice': if (pc.items.length) pc.items.pop(); else { var alive = this.npcs.filter(function (n) { return n.alive && n.met; }); if (alive.length) { var v = alive[0]; this.npcDeathCheck(v, '替你死了'); this.sacrificed = v.fullName; } } break; } pc.hp = Math.round(pc.hpMax * 0.35); pc.combat = this.calcPower(pc); this.karmas.push({ id: 'dk2' + this.turn, name: '绝境之债', level: '因果级', power: 8, strength: 8, trigger: '你本该死在那一天', endings: ['偿还', '再欠一次', '成为债本身'], desc: '你从必死的局面里走了出来,代价是' + c.txt + '。天道记了这笔账。', realRevealed: true, status: 'active', createdTurn: this.turn }); return { cost: c, txt: '【绝境逢生】你活了。代价:' + c.txt + '。这不是恩赐,是延期付款。' }; }; /* ============================================================ SECTION: death 死亡与转世 ============================================================ */ Game.prototype.die = function (cause) { var pc = this.pc; this.over = true; this.overReason = cause; pc.deathCount++; this.deathInfo = { cause: cause, age: pc.age, realm: D.REALMS[pc.realmIdx].name, turn: this.turn }; return D.DEATH_FORMS; }; Game.prototype.reincarnate = function (formId, mode) { var pc = this.pc, r = this.rng; var form = D.DEATH_FORMS.filter(function (f) { return f.id === formId; })[0] || D.DEATH_FORMS[0]; if (form.end) { this.finalEnd = true; return { end: true, txt: N.deathTxt('guixu', pc.age) }; } /* 跨档继承:伏笔 30% 权重,因果网继承关联节点,必须付代价 */ var inheritedKarma = []; var unresolved = this.foreshadow.filter(function (f) { return !f.resolved; }); unresolved.forEach(function (f) { if (chance(r, form.keep)) inheritedKarma.push({ id: f.id + '_i', text: f.text, level: this._upPriority(f.level), weight: 0.3, inherited: true }); }, this); var newPc = JSON.parse(JSON.stringify(pc)); newPc.name = this._genName(); newPc.age = 0; newPc.deathCount = pc.deathCount; newPc.realmIdx = formId === 'huagui' ? pc.realmIdx : Math.max(0, pc.realmIdx - (mode === 'suhui' ? 0 : 2)); newPc.realmSub = 0; newPc.qi = 0; newPc.qiNeed = D.REALMS[Math.min(newPc.realmIdx + 1, D.REALMS.length - 1)].need || 60; newPc.hp = newPc.hpMax; /* 继承代价 */ if (mode === 'suhui') { newPc.memory = pc.memory; newPc.lifeMax = Math.round(pc.lifeMax * 0.8); newPc.daoyun = Math.round(pc.daoyun * 0.4); newPc.injury['神魂'] = clamp(pc.injury['神魂'] + 1, 0, 5); } else if (mode === 'curse') { newPc.pollution = 4; newPc.lifeMax = Math.round(pc.lifeMax * 0.6); for (var k in newPc.injury) newPc.injury[k] = clamp(newPc.injury[k] + 1, 0, 5); } else if (mode === 'karma') { newPc.daoyun = Math.round(pc.daoyun * 0.2); newPc.reputation = Math.round(pc.reputation * 0.5); } else { newPc.daoyun = 10; newPc.items = []; newPc.bp = {}; newPc.bpStage = {}; newPc.arts = {}; newPc.reputation = 0; } newPc.form = formId; newPc.costLevel = 0; newPc.combat = this.calcPower(newPc); var oldName = pc.name; this.pc = newPc; this.over = false; this.overReason = ''; this.prevLife = this.prevLife || []; this.prevLife.push({ name: oldName, realm: D.REALMS[pc.realmIdx].name, age: pc.age, cause: this.overReason || '未知', turn: this.turn }); /* 跨档因果:前世 NPC 与今生的关联 */ var crossLinks = []; var alive = this.npcs.filter(function (n) { return n.alive && n.met; }); alive.slice(0, 3).forEach(function (n) { var rel = ['仇敌', '道侣', '师徒', '同门', '宿敌'][Math.floor(r() * 5)]; crossLinks.push({ npc: n.fullName, rel: rel, txt: n.fullName + ' 在今生的你身上认出了一点旧东西。他不确定,但他会查。你们之间的关系是:' + rel + '。' }); n.att['忌惮'] = clamp((n.att['忌惮'] || 0) + 20, 0, 100); }); /* 世界继续运转 */ this.passYears(1 + Math.floor(r() * 18), '转世'); return { form: form, mode: mode, txt: N.deathTxt(formId, pc.age), inherited: inheritedKarma, crossLinks: crossLinks, brief: this.worldBrief(), newName: newPc.name }; }; Game.prototype._upPriority = function (lv) { var order = ['日常级', '个人级', '因果级', '天道级']; var i = order.indexOf(lv); return order[Math.min(order.length - 1, i + 1)]; }; /* ============================================================ SECTION: karma 因果转化(不可撤销,只可转化/承受/覆盖) ============================================================ */ Game.prototype.transformKarma = function (karmaId, way) { var k = this.karmas.filter(function (x) { return x.id === karmaId; })[0]; if (!k) return null; if (k.level === '天道级') return { ok: false, msg: '天道级因果不可转化,只能承受。这是铁律,不是规定。' }; if (k.unbreakable) return { ok: false, msg: '这条线是你自己在绝境里绑上的。它不给解。' }; var pc = this.pc, r = this.rng; var cost = { '赎罪': '道蕴 -' + Math.round(k.strength * 8) + ',且必须做一件具体的补偿', '补偿': '资源 -' + Math.round(k.strength * 30) + ' 灵石', '传承': '把这条因果交给一个愿意接的人(他会记你一辈子)', '牺牲': '献祭一件核心之物或一段羁绊', '斩断': '气运下降,且断掉的部分会以"业障"潜伏' }[way]; if (way === '赎罪') pc.daoyun = Math.max(0, pc.daoyun - k.strength * 8); if (way === '补偿') pc.money = Math.max(0, pc.money - k.strength * 30); if (way === '斩断') { pc.luckFlat = (pc.luckFlat || 0) - 8; k.latent = true; } if (way === '牺牲' && pc.items.length) pc.items.pop(); k.strength = Math.max(1, Math.round(k.strength * (way === '斩断' ? 0.3 : 0.5))); k.transformed = way; /* 方向可能随机改变 */ if (chance(r, 0.3)) { k.directionChanged = true; k.strength += 3; } return { ok: true, msg: '【因果转化·' + way + '】' + (cost || '') + '。这条线没有消失——它变成了另一种形状。' + (k.directionChanged ? '而且方向变了,变得对你不太有利。' : ''), k: k }; }; /* ============================================================ SECTION: daoTitle 道号与万古道碑 ============================================================ */ Game.prototype.grantDaoTitle = function () { var pc = this.pc, r = this.rng; if (pc.daoTitle) return null; /* 需行为达到特定境界(第三境以上) */ var top = null, topV = 0; for (var k in pc.bp) if ((pc.bpStage[k] || 0) >= 3 && pc.bp[k] > topV) { top = k; topV = pc.bp[k]; } if (!top) return null; var p = D.DAO_PATHS.filter(function (x) { return x.key === top; })[0] || { id: top }; var title = D.TITLE_PRE[Math.floor(r() * D.TITLE_PRE.length)] + D.TITLE_POST[Math.floor(r() * D.TITLE_POST.length)]; /* 道碑不可重复 */ var guard = 0; while (this.daotitles.some(function (d) { return d.title === title; }) && guard < 30) { title = D.TITLE_PRE[Math.floor(r() * D.TITLE_PRE.length)] + D.TITLE_POST[Math.floor(r() * D.TITLE_POST.length)]; guard++; } pc.daoTitle = title; var stele = { title: title, name: pc.name, path: p.id, realm: D.REALMS[pc.realmIdx].name, turn: this.turn, age: pc.age, deed: this._summarizeDeed(pc, top), world: pc.world.name, era: this.opening.era.name, seed: this.seed }; this.daotitles.unshift(stele); this.daotitles = this.daotitles.slice(0, 30); return stele; }; Game.prototype._summarizeDeed = function (pc, topKey) { var r = this.rng; var traces = (this.daoTraces || []).filter(function (t) { return t.path.indexOf(topKey) >= 0 || t.path === topKey; }); var bank = [ '以' + topKey + '立身,行过 ' + Math.round(pc.bp[topKey] || 0) + ' 点行为积分,在 ' + (traces[0] ? traces[0].place : this.place()) + ' 留下道痕。', '一生所行,皆在' + topKey + '。' + (pc.injury['道基'] ? '道基有裂,裂而未废。' : '道基无缺,无缺亦无奇。'), '杀过 ' + (this.npcs.filter(function (n) { return !n.alive && n.deathWay === '死于你手'; }).length) + ' 人,救过一些人,欠了一些账。最后停在 ' + D.REALMS[pc.realmIdx].name + '。' ]; return bank[Math.floor(r() * bank.length)]; }; /* ============================================================ SECTION: lineage 道统建立 ============================================================ */ Game.prototype.foundLineage = function () { var pc = this.pc; var top = null, topV = 0; for (var k in pc.arts) if ((pc.arts[k].level || 0) >= 3 && pc.arts[k].bp > topV) { top = k; topV = pc.arts[k].bp; } if (!top) return { ok: false, msg: '道统建立需要百艺达到"创艺"以上(BP≥1500)。你现在还只是一个手熟的人。' }; var followers = this.npcs.filter(function (n) { return n.alive && n.met && (n.att['敬畏'] >= 40 || n.att['感恩'] >= 40); }); if (followers.length < 2) return { ok: false, msg: '建立道统需要至少两名追随者。愿意跟着你的人,现在还不够两个。' }; if (pc.daoyun < 200) return { ok: false, msg: '道蕴不足(需 200)。开创一派不是喊出来的,是拿东西垫出来的。' }; pc.daoyun -= 200; this.daoLineage = { name: top + '·' + (pc.daoTitle || pc.name) + '一脉', art: top, founded: this.turn, followers: followers.map(function (f) { return f.fullName; }), brand: null, realm: this.place() }; return { ok: true, msg: '【道统建立】' + this.daoLineage.name + ' 立。首批追随者:' + this.daoLineage.followers.join('、') + '。从今日起,有人以你的手法为准绳——也有人把你当成了眼中钉。', lineage: this.daoLineage }; }; /* ============================================================ SECTION: brand 世界刻印 ============================================================ */ Game.prototype.createBrand = function (brandId) { var pc = this.pc; var b = D.WORLD_BRANDS.filter(function (x) { return x.name === brandId; })[0]; if (!b) return null; /* 条件:道途第三境以上 + 道蕴充沛 + 特定材料 + 特定时机 */ var stage = 0; for (var k in pc.bp) stage = Math.max(stage, pc.bpStage[k] || 0); if (stage < 3) return { ok: false, msg: '世界刻印需要道途第三境「道途初凝」以上。你现在的行为还留不下能改变世界的痕。' }; if (pc.daoyun < 300) return { ok: false, msg: '道蕴不足(需 300)。刻印是拿道蕴往世界底层里凿字。' }; if (!pc.items.length) return { ok: false, msg: '需要一件特定材料作为刻印之媒。你身上没有够格的东西。' }; var timingOk = (this.turn % 50 < 8) || this.stage >= 3; if (!timingOk) return { ok: false, msg: '时机不对。世界刻印只能在天地法则松动的窗口期落笔——大劫阶段推进前后,或地脉异动之时。' }; pc.daoyun -= 300; var used = pc.items.pop(); this.worldBrand = { name: b.name, art: b.art, desc: b.desc, place: this.place(), turn: this.turn, medium: used.n, owner: pc.name }; this.brands.push(this.worldBrand); /* 因果:刻印会吸引后人,也会引来觊觎 */ this.karmas.push({ id: 'brand' + this.turn, name: '刻印之责', level: '天道级', power: 9, strength: 9, trigger: '你在世界底层留下了字', endings: ['被继承', '被摧毁', '被篡改'], desc: '「' + b.name + '」刻于' + this.worldBrand.place + '。它会比你能活的时间长得多。后人来感悟它,就是得道;来吞噬它,就是夺舍——而被反噬的可能是你留在里面的那点意志。', realRevealed: true, status: 'active', createdTurn: this.turn }); return { ok: true, msg: '【世界刻印】' + b.name + ' 落成于' + this.worldBrand.place + ',以「' + used.n + '」为媒。' + b.desc + ' 这地方从此和你绑在一起了。', brand: this.worldBrand }; }; /* ============================================================ SECTION: zhengdao 证道 ============================================================ */ Game.prototype.canAscend = function () { var pc = this.pc; var stage = 0; for (var k in pc.bp) stage = Math.max(stage, pc.bpStage[k] || 0); var conds = [ { name: '道途圆满(第五境·道行)', ok: stage >= 5 }, { name: '因果了结(无活跃天道级因果)', ok: !this.karmas.some(function (k) { return k.status === 'active' && k.level === '天道级'; }) }, { name: '道心无瑕(污染为无尘)', ok: pc.pollution === 0 }, { name: '世界刻印(已留下刻印)', ok: !!this.worldBrand } ]; return { conds: conds, ok: conds.every(function (c) { return c.ok; }) }; }; Game.prototype.ascend = function (choice) { var bank = { '飞升上界': '你走了。走之前回头看了一眼,那一眼让下界下了三天的雨。上界不是终点,是另一套规矩的起点——那里的蝼蚁,比这里的蝼蚁大一点。', '隐世': '你留下来,但不再露面。几十年后有人在' + this.place() + '见过一个扫地的人,扫得很慢,扫完之后地上的灰会自己排成一列。', '化道': '你散开了。散开之后,这一带的法则里多了一点你的东西。后来在这里修炼' + (this.pc.daoBias || '本道') + '的人,效率都莫名高一些,他们不知道是谁给的。', '继续游戏': '你没有走。证道不是终点,是换了一副担子。世界还在转,你还在里面。' }; return bank[choice] || bank['继续游戏']; }; /* ============================================================ SECTION: options 抉择生成(风险收益矩阵) ============================================================ */ Game.prototype.makeOptions = function (scene) { var r = this.rng, pc = this.pc, out = []; var alive = this.npcs.filter(function (n) { return n.alive; }); var place = this.place(); var R = function (risk) { return { '低': 'low', '中': 'mid', '高': 'high', '必死': 'dead', '未知': 'unk' }[risk] || 'mid'; }; var mk = function (o) { o.riskCls = R(o.risk); o.tag = o.main ? '主线抉择' : '支线行动'; return o; }; /* 依据当前处境动态生成,禁止模糊选项 */ if (scene === 'opening') { out.push(mk({ id: 'face', text: '直面眼前这件事,把该问的话问出来,把该动的东西动一下', main: true, risk: '中', gain: ['情报', '道心'], cost: ['时间', '因果'], act: 'face' })); out.push(mk({ id: 'invest', text: '先不动,花几天时间把这里的底细摸清楚再说', risk: '低', gain: ['情报', '安全'], cost: ['时间'], act: 'investigate' })); out.push(mk({ id: 'leave', text: '离开这个地方,往' + place + '方向走,先把命保住', risk: '中', gain: ['安全', '资源'], cost: ['因果', '时间'], act: 'leave' })); out.push(mk({ id: 'cult', text: '就地打坐,先把体内那点乱掉的灵气理顺,再谈别的', risk: '低', gain: ['修为'], cost: ['时间'], act: 'cultivate' })); } else { var pool = []; pool.push(mk({ id: 'cult', text: '寻一处安静地方闭关修炼,把这一段时间用来推高境界', risk: '低', gain: ['修为', '道心'], cost: ['时间', '寿元'], act: 'cultivate', years: 1 + Math.floor(r() * 3) })); pool.push(mk({ id: 'explore', text: '前往' + place + '探查那处异动,看看能不能捡到别人不敢捡的东西', risk: '中', gain: ['资源', '情报', '修为'], cost: ['道伤', '因果'], act: 'explore' })); if (alive.length) { var n = alive[Math.floor(r() * alive.length)]; pool.push(mk({ id: 'meet', text: '去见' + n.fullName + ',把话摊开说,看他到底想要什么', risk: '未知', gain: ['人脉', '情报'], cost: ['人情', '因果'], act: 'meet', npc: n.id, main: n.met })); } if (this.warPair) { var wa = this.factions.filter(function (f) { return f.id === this.warPair[0]; }, this)[0]; pool.push(mk({ id: 'war', text: '插手' + (wa ? wa.name : '这场') + '战事,站在赢面小但道理正的那一边', main: true, risk: '高', gain: ['人脉', '道心', '资源'], cost: ['道伤', '寿命', '因果'], act: 'war' })); } var k = this.karmas.filter(function (x) { return x.status === 'active'; })[Math.floor(r() * Math.max(1, this.karmas.filter(function (x) { return x.status === 'active'; }).length))]; if (k) pool.push(mk({ id: 'karma', text: '处理【' + k.name + '】这条线,用' + ['赎罪', '补偿', '传承', '斩断'][Math.floor(r() * 4)] + '的方式把它往前推一步', main: true, risk: '中', gain: ['道心', '安全'], cost: ['资源', '因果', '道心污染'], act: 'karma', karma: k.id })); pool.push(mk({ id: 'art', text: '花时间打磨一门百艺(' + (Object.keys(pc.arts)[0] || '炼丹术') + '),把技术往上顶一顶', risk: '低', gain: ['资源', '修为'], cost: ['时间', '材料'], act: 'art' })); pool.push(mk({ id: 'trade', text: '去坊市走一趟,看看现在什么价格,能不能低买高卖赚一笔', risk: '低', gain: ['资源'], cost: ['时间', '人情'], act: 'trade' })); pool.push(mk({ id: 'dao', text: '静坐修心,把识海里那点脏东西清一清,稳固道心', risk: '低', gain: ['道心'], cost: ['时间', '资源'], act: 'mind' })); if (pc.pollution >= 2) pool.push(mk({ id: 'taboo', text: '走一条天道不许的路,用禁忌技艺换一次爆发', risk: '必死', gain: ['修为', '资源'], cost: ['道心污染', '因果', '天道监察'], act: 'taboo' })); if (this.foreshadow.some(function (f) { return !f.resolved && f.dueTurn && f.dueTurn - this.turn <= 8; }, this)) { pool.push(mk({ id: 'rush', text: '那条快到期的线不能再拖了,立刻动身去把它了结', main: true, risk: '高', gain: ['道心', '安全'], cost: ['道伤', '寿命'], act: 'rush' })); } /* 打乱并取 4 个 + 自由输入 */ pool.sort(function () { return r() - 0.5; }); out = pool.slice(0, 4); /* 保证至少一个主线 */ if (!out.some(function (o) { return o.main; })) { var m = pool.filter(function (o) { return o.main; })[0]; if (m) out[0] = m; } } out.push({ id: 'free', text: '自由输入:写下你想做的任何事(天道将进行语义解析与合理性校验)', risk: '未知', gain: ['未知'], cost: ['未知'], act: 'free', free: true, riskCls: 'unk', tag: '自由行动' }); return out; }; /* ============================================================ SECTION: freeinput 自由输入语义解析(四步) ============================================================ */ Game.prototype.parseFree = function (text) { var pc = this.pc, r = this.rng; var t = String(text || '').trim(); if (!t) return { ok: false, msg: '什么都没写。天道不猜。' }; /* 第一步:意图提取(核心动词 + 目标对象) */ var VERBS = { '杀': ['杀', '斩', '砍', '刺', '屠', '灭', '除掉', '干掉', '出手'], '医': ['救', '治', '医', '护', '挡', '疗'], '修': ['修炼', '闭关', '打坐', '参悟', '吐纳', '悟道', '推演'], '探': ['探查', '查看', '寻找', '搜', '潜入', '跟踪', '观察', '去'], '谈': ['谈', '说服', '劝', '问', '交涉', '拜师', '结盟', '交易', '骗', '威胁'], '艺': ['炼丹', '炼器', '布阵', '画符', '制', '炼', '刻', '画'], '商': ['买', '卖', '收购', '出售', '运', '经商'], '谋': ['布局', '算计', '设局', '操纵', '借刀'], '避': ['逃', '躲', '离开', '撤退', '隐', '跑'], '破': ['突破', '破开', '强行', '硬闯', '越阶'], '诡': ['契约', '召唤', '通灵', '献祭', '与诡异'] }; var found = null, fkey = null; for (var k in VERBS) { for (var i = 0; i < VERBS[k].length; i++) { if (t.indexOf(VERBS[k][i]) >= 0) { found = VERBS[k][i]; fkey = k; break; } } if (found) break; } /* 对象提取 */ var obj = null; var npcMatch = this.npcs.filter(function (n) { return n.alive && t.indexOf(n.name) >= 0; })[0]; if (npcMatch) obj = npcMatch.fullName; else { var fMatch = this.factions.filter(function (f) { return t.indexOf(f.name) >= 0; })[0]; if (fMatch) obj = fMatch.name; else { var pMatch = D.PLACES.filter(function (p) { return t.indexOf(p) >= 0; })[0]; if (pMatch) obj = pMatch; else { var iMatch = pc.items.filter(function (it) { return t.indexOf(it.n) >= 0; })[0]; if (iMatch) obj = iMatch.n; } } } if (!obj) { /* 从文本里截取一个疑似宾语 */ var m = t.match(/(?:向|对|把|给|在|去|到|找|杀|救|买|卖)\s*([\u4e00-\u9fa5A-Za-z0-9]{2,8})/); obj = m ? m[1] : (t.length > 8 ? t.slice(0, 8) : t); } /* 第二步:语境权重计算 */ var ctx = []; var qiFx = (pc.worldQi || 1); ctx.push({ k: '环境灵气浓度', v: qiFx >= 1 ? '+10%' : '-10%' }); var mindFx = pc.pollution >= 3 ? -15 : (pc.costLevel >= 3 ? -10 : (pc.pollution === 0 ? 10 : 0)); ctx.push({ k: '当前心境', v: (mindFx >= 0 ? '+' : '') + mindFx + '%' }); ctx.push({ k: '道途境界压制', v: (pc.realmIdx >= 3 ? '+15%' : '-15%') }); ctx.push({ k: '大劫阶段', v: D.TRIBULATION[this.stage].name }); ctx.push({ k: '天道监察', v: D.MONITOR[pc.monitor].lv + '(成功率-' + Math.round(D.MONITOR[pc.monitor].pen * 100) + '%)' }); /* 第三步:合理性校验(战力天花板 / 境界认知) */ var violated = false, reject = null, warn = null; if (fkey === '破' || /越阶|强行突破|一击必杀|秒杀|直接成仙|无敌/.test(t)) { violated = true; reject = '这个行动违背了你当前的境界认知。' + D.REALMS[pc.realmIdx].name + '做不到你描述的这件事——不是不让做,是物理上做不到。'; } if (fkey === '杀' && npcMatch && npcMatch.power > pc.combat * 2.5) { violated = true; reject = npcMatch.fullName + '的战力是 ' + npcMatch.power + ',你是 ' + pc.combat + '。这不是战斗,是他花一点时间处理你。'; } if (/^我是仙帝|开局即仙帝|给我.*无限|直接满级/.test(t)) { reject = '天道拒绝了。无代价的力量不被允许——你若坚持,就得配套等量代价:记忆全失、寿元仅剩三天、或被天道追杀。'; } if (pc.noCombat && fkey === '杀') warn = '你是纯辅助型角色,没有战斗能力。你可以动手,但结果不会好看。'; /* 第四步:反馈生成 */ var verdict = reject ? 'reject' : (violated ? 'cost' : 'ok'); return { ok: !reject, verb: found || '未识别', verbKey: fkey, obj: obj, npc: npcMatch ? npcMatch.id : null, ctx: ctx, reject: reject, warn: warn, verdict: verdict, raw: t, violated: violated, reply: N.freeParseReply(found, obj, verdict) + (warn ? ' ' + warn : ''), /* 映射到内部行为 */ act: reject ? null : this._mapAct(fkey), bpKey: { '杀': '杀', '医': '医', '修': '无', '探': '谋', '谈': '情', '艺': null, '商': '商', '谋': '谋', '避': '无', '破': '无', '诡': '诡' }[fkey] || null, bpAmount: fkey === '杀' || fkey === '破' ? 2.5 : (fkey === '修' ? 1.5 : (fkey === '谈' ? 1.0 : 0.8)) }; }; Game.prototype._mapAct = function (k) { return { '杀': 'fight', '医': 'heal', '修': 'cultivate', '探': 'explore', '谈': 'meet', '艺': 'art', '商': 'trade', '谋': 'scheme', '避': 'flee', '破': 'breakthrough', '诡': 'taboo' }[k] || 'explore'; }; /* 组合选择解析:"134" */ Game.prototype.parseCombo = function (input, options) { var s = String(input || '').trim(); if (!/^\d+$/.test(s)) return null; var idx = s.split('').map(function (c) { return parseInt(c, 10) - 1; }).filter(function (i) { return i >= 0 && i < options.length; }); if (idx.length < 2) return null; var chosen = idx.map(function (i) { return options[i]; }).filter(Boolean); /* 资源合理性判定 */ var pc = this.pc; var need = chosen.length; var ok = true, msg = ''; if (pc.energy < need * 34) { ok = false; msg = '体力不支,无法同时执行 ' + chosen.map(function (c) { return c.id; }).join(' 和 ') + '。请选择其一,或调整顺序。'; } if (chosen.some(function (c) { return c.main; }) && chosen.filter(function (c) { return c.main; }).length > 1) { ok = false; msg = '两条主线抉择在同一回合并行,因果会打结。请只保留一条主线。'; } return { ok: ok, msg: msg, chosen: chosen, count: chosen.length }; }; /* ============================================================ SECTION: turn 回合主流程 ============================================================ */ Game.prototype.doAction = function (opt, freeText) { var pc = this.pc, r = this.rng; if (this.over) return { blocked: true, msg: '你已经死了。先选择死亡转化形态。' }; var res = { turn: this.turn + 1, parts: [], settle: null, options: [], monitor: null }; this.turn++; pc.timeLeft = 3; var kind = 'explore', subject = null, baideObj = null; var settlement = { karma: [], npc: [], world: [], daoyun: 0, injury: [], bp: [], arts: [] }; /* ---- 自由输入 ---- */ if (opt && opt.act === 'free') { var p = this.parseFree(freeText); pc.freeCount++; res.parts.push({ cls: 'sys', html: '
【动态语义解析】
核心行为:「' + (p.verb || '未识别') + '」 对象:「' + p.obj + '」
' +
'语境权重:' + p.ctx.map(function (c) { return c.k + ' ' + c.v; }).join(' ') + '
' + p.reject + '
' }); var reb = this.rebellion(true); if (reb) { res.parts.push({ cls: 'backlash', html: '【天道反噬·' + reb.level + '】' + reb.txt + '
' }); settlement.karma.push('天道反噬·' + reb.level); } res.options = this.makeOptions(); this.settlements.unshift({ turn: this.turn, s: settlement }); return res; } res.parts.push({ cls: 'sys', html: '' + p.reply + '
' }); if (p.violated) this.rebellion(true); else this.rebellion(false); opt = { act: p.act, text: p.raw, id: 'free', risk: '未知', gain: ['未知'], cost: ['未知'], npc: p.npc, bpKey: p.bpKey, bpAmount: p.bpAmount, free: true }; res.freeParse = p; } else { this.rebellion(false); } /* ---- 执行行为 ---- */ var act = opt ? opt.act : 'cultivate'; var mainTxt = '', costTxt = '', echoTxt = '', blankTxt = ''; var years = 0; if (act === 'cultivate') { kind = 'cultivate'; years = (opt && opt.years) || (1 + Math.floor(r() * 2)); var c = this.cultivate({ years: years }); mainTxt = c.log.join(' '); if (years) { var yl = this.passYears(years, '闭关'); mainTxt += (yl.length ? '' + c.breakthrough.txt + '
' }); if (c.breakthrough.ok) { pc.daoyun += 20; settlement.daoyun += 20; } } var biasKey = '无'; if (pc.daoBias) { var bp0 = D.DAO_PATHS.filter(function (x) { return x.id === pc.daoBias; })[0]; if (bp0) biasKey = bp0.key; } this.addBp(biasKey, 1.2, '修炼'); settlement.bp.push('修炼行为 +1.2 BP'); subject = '膝上摊开的功法'; baideObj = '那本翻了太多次的功法'; pc.energy = Math.max(0, pc.energy - 25); } else if (act === 'explore' || act === 'investigate' || act === 'face' || act === 'leave' || act === 'rush' || act === 'scheme' || act === 'flee') { kind = 'explore'; var d = this.dice(0.55 + (act === 'investigate' ? 0.15 : 0), { attr: '悟性' }); var place = this.place(); var finds = [ { txt: '你在' + place + '找到了不该在这里的东西。', item: true, money: Math.round(50 + r() * 400), bp: '谋' }, { txt: place + '的暗格里塞着一样东西,塞得很仓促,像是走的人以为自己还会回来。', item: true, bp: '谋' }, { txt: '有人在这里落下了钱袋。钱袋是新的,人不是。', money: Math.round(120 + r() * 600), bp: '商' }, { txt: place + '里有人比你先到,走得比你早,留下的东西还没冷。', karma: true, bp: '谋' }, { txt: '你摸清了这里的底细,也摸清了自己在这里排第几。', info: true, bp: '谋' }, { txt: '什么都没找到。但你看见了一件不该看见的事,那件事现在也看见你了。', bad: true, bp: '诡' }, { txt: '一具尸首靠在墙角,死了不到一日。你翻到了他怀里的东西,也翻到了他为什么死。', item: true, karma: true, money: Math.round(30 + r() * 120), bp: '谋' }, { txt: place + '已经有主了。守在这里的人没有赶你,只是看着你,看到你自己走。', info: true, bad: true, bp: '谋' }, { txt: '你数了一遍墙上的刻痕,四十七道。转身再看,四十八道。', bad: true, bp: '诡' }, { txt: '你什么都没拿到,只在那扇门前站了很久。门后面有呼吸声,和你的节奏一样。', bad: true, bp: '诡' } ]; var f = null; var pickOf = function (arr) { return arr[Math.floor(r() * arr.length)]; }; if (d.result === 'great') f = pickOf([finds[0], finds[6], finds[1]]); else if (d.result === 'ok') f = pickOf([finds[0], finds[1], finds[2], finds[4], finds[7]]); else if (d.result === 'barely') f = pickOf([finds[3], finds[4], finds[2], finds[7]]); else f = pickOf([finds[5], finds[8], finds[9]]); mainTxt = f.txt; var okRun = d.result !== 'fail' && d.result !== 'greatfail'; if (f.item && okRun) { var gotItem = D.ITEMS[Math.floor(r() * D.ITEMS.length)]; pc.items.push({ n: gotItem, lv: 1 }); mainTxt += pickOf([ '你把它收进了怀里——' + gotItem + '。', '你带走了' + gotItem + ',走的时候没有回头。', gotItem + '。你捏了捏,是硬的,是真的。' ]); settlement.world.push('获得物品:' + gotItem); } if (f.money && okRun) { pc.money += f.money; mainTxt += pickOf([ ' 顺带得了 ' + f.money + ' 灵石。', ' 灵石 ' + f.money + ' 枚,你数了一遍,没多数。', ' 你把 ' + f.money + ' 枚灵石塞进袖袋,袖袋沉了一点。' ]); } if (f.karma) { var nk = D.KARMA_POOL[Math.floor(r() * D.KARMA_POOL.length)]; this.karmas.push({ id: 'k' + this.turn, name: nk.name, level: nk.level, power: nk.power, strength: Math.max(2, Math.round(nk.power * 0.6)), trigger: nk.trigger, endings: nk.endings, desc: nk.desc, surface: nk.hidden && chance(r, 0.5) ? this._surfaceMask(nk) : null, realRevealed: !(nk.hidden && chance(r, 0.5)), status: 'active', createdTurn: this.turn, relatedNpc: alive0(this) }); mainTxt += ' 一条新的因果线系上了:【' + nk.name + '】。'; settlement.karma.push('新增因果线:' + nk.name + '(' + nk.level + ')'); this.foreshadow.push({ id: 'fs' + this.turn, text: nk.name, level: nk.level, trigger: nk.trigger, dueTurn: chance(r, 0.5) ? this.turn + 30 + Math.floor(r() * 60) : null, createdTurn: this.turn, resolved: false }); } if (f.info) { var rm = N.makeRumor(this); rm.read = true; this.rumors.unshift(rm); mainTxt += ' 你得到一条消息(可信度' + rm.pct + '%,来源' + rm.source + '):' + rm.text; } if (f.bad) { pc.pollution = clamp(pc.pollution + 1, 0, 4); costTxt = this.addCost(2, '看见了不该看的'); settlement.injury.push('道心污染 +1 阶(' + D.POLLUTION[pc.pollution].name + ')'); blankTxt = '那件事没有追上来。它只是记住了你。'; } /* 第三方截胡 */ if (f.item && chance(r, 0.22)) { var aliveN = this.npcs.filter(function (n) { return n.alive; }); if (aliveN.length) { var nn = aliveN[Math.floor(r() * aliveN.length)]; var stolen = pc.items.pop(); mainTxt += '【战力校验】你 ' + pc.combat + ' vs ' + tgt.fullName + ' ' + tgt.power + ' ' + chk.note + (chk.pen ? ' 成功率惩罚 -' + Math.round(chk.pen * 100) + '%' : '') + '
' }); if (!chk.ok) { mainTxt = chk.note; costTxt = this.addCost(3, '强行出手的后果'); this.addInjury('肉身', Math.min(5, pc.injury['肉身'] + 2), '越界'); pc.hp = Math.max(1, Math.round(pc.hp * 0.3)); settlement.injury.push('肉身道伤 → ' + D.INJURY_LEVELS[pc.injury['肉身']]); this.rebellion(true); } else { var fr = this.fight(tgt, { condition: chk.needCond ? true : null }); mainTxt = fr.txt || fr.msg || '交手结束。'; if (fr.dice) { res.parts.push({ cls: 'sys', html: '掷骰:成功率 ' + Math.round(fr.dice.rate * 100) + '% 骰值 ' + (fr.dice.roll * 100).toFixed(1) + ' 结果【' + ({ great: '大成功', ok: '成功', barely: '勉强成功', fail: '失败', greatfail: '大失败' })[fr.dice.result] + '】
' }); } if (fr.win) { pc.daoyun += 15; settlement.daoyun += 15; if (fr.loot) { settlement.world.push('战利品:' + fr.loot.money + ' 灵石' + (fr.loot.item ? '、' + fr.loot.item.n : '')); } settlement.karma.push('与 ' + tgt.fullName + ' 的因果已改写'); } if (!fr.win) { settlement.injury.push('肉身道伤 → ' + D.INJURY_LEVELS[pc.injury['肉身']]); costTxt = this.addCost(fr.dice.result === 'greatfail' ? 4 : 2, '战败'); } if (fr.dead) { this.die('死于 ' + tgt.fullName + ' 之手'); } pc.energy = Math.max(0, pc.energy - 45); } baideObj = '那把还在震的剑'; /* 子弹时间 */ res.bullet = N.bullet(this, '剑锋相交的那一瞬被拉长了。你能看见对方瞳孔里那个很小的自己,能听见血从袖口滴到石板上的声音——一滴,两滴。第三滴落地之前,一切都结束了。'); } } else if (act === 'art') { kind = 'art'; var artIds = Object.keys(pc.arts); if (!artIds.length) { artIds = ['炼丹术']; pc.arts['炼丹术'] = { bp: 0, level: 0 }; } var artId = artIds[Math.floor(r() * artIds.length)]; var artDef = D.ARTS.filter(function (a) { return a.id === artId; })[0] || D.ARTS[0]; /* 动态判定四步 */ var base = 0.4 + (pc.arts[artId].level || 0) * 0.1 + (pc.six['悟性'] - 3) * 0.04; var wEnv = (pc.worldQi || 1) >= 1 ? 0.1 : -0.1; var wMind = pc.pollution >= 3 ? -0.15 : (pc.costLevel >= 3 ? -0.1 : 0.1); var wMat = pc.items.length ? 0.15 : -0.25; var wRealm = pc.realmIdx >= 3 ? 0.12 : -0.1; var total = clamp(base + wEnv + wMind + wMat + wRealm, 0.05, 0.95); res.parts.push({ cls: 'sys', html: '【百艺动态判定·' + artId + '】
环境灵气 ' + (wEnv >= 0 ? '+' : '') + Math.round(wEnv * 100) + '% 心境 ' + (wMind >= 0 ? '+' : '') + Math.round(wMind * 100) + '% 材料 ' + (wMat >= 0 ? '+' : '') + Math.round(wMat * 100) + '% 境界压制 ' + (wRealm >= 0 ? '+' : '') + Math.round(wRealm * 100) + '%
综合成功率 ' + Math.round(total * 100) + '%
【天道监察 ' + D.MONITOR[mr.from].lv + ' → ' + D.MONITOR[mr.to].lv + '】' + mr.txt + '
' }); } } } mainTxt += '【天道监察 ' + D.MONITOR[ur.monitor.from].lv + ' → ' + D.MONITOR[ur.monitor.to].lv + '】' + ur.monitor.txt + '
' }); settlement.world.push('天道监察升至 ' + D.MONITOR[ur.monitor.to].lv + '(' + ur.monitor.to === 5 ? '诛灭' : ur.monitor.to === 4 ? '惩戒' : '注视' + ')'); } pc.daoyun += Math.round(80 * tb.power); pc.pollution = clamp(pc.pollution + 2, 0, 4); costTxt = this.addCost(3, tb.name); mainTxt += '【寿元耗尽】你的时间到了。' + this.lifespanStage(pc).fx.desc + '
' }); this.die('寿元耗尽'); } if (pc.hp <= 0 && !this.over) this.die('重伤不治'); /* ---- 后台:世界 TICK(≥3 件) ---- */ var ticks = this.worldTick(); res.ticks = ticks; /* ---- 每十回合:风闻 + 价格 + 记忆压缩 ---- */ if (this.turn % 10 === 0) { var rm3 = N.makeRumor(this); this.rumors.unshift(rm3); var big = N.makeBigForecast(this); this.rumors.unshift(big); this.bigEvents.push({ id: 'bep' + this.turn, kind: big.eventType, turn: big.dueTurn, desc: big.text, done: false, weight: big.weight, fromForecast: true }); this.bigEvents.sort(function (x, y) { return x.turn - y.turn; }); res.priceReason = this.updatePrices(); res.compress = this.compressMemory(); res.rumorPush = [big, rm3]; } /* 伏笔检查 */ res.dueForesight = this.checkForeshadow(); /* 大劫阶段推进(每 50 回合) + 大事件触发:从待领取队列统一取出 */ this.advanceStage(); this.checkBigEvents(); res.stageUp = this.takeStages(); res.bigFired = this.takeBigEvents(); /* 记忆衰减 */ this.decayMemory(); /* BP 衰减 */ res.decayLog = this.decayBp(); /* 道号授予 */ res.daoTitle = this.grantDaoTitle(); /* 模因捕食者 */ if (this.pendingThreat) { res.threat = this.pendingThreat; this.pendingThreat = null; } /* 能量恢复 */ pc.energy = Math.min(pc.energyMax, pc.energy + 12); /* 余炽线(高潮后强制缓冲) */ if (kind === 'battle' || (res.stageUp && res.stageUp.length) || (res.bigFired && res.bigFired.length)) { this.emberDue = 1 + Math.floor(r() * 3); this.highStreak = 0; } else this.highStreak++; if (this.emberDue > 0) { res.ember = N.ember(this, this.emberDue); this.emberDue--; } /* 禁止连续 3 回合高压 */ if (this.highStreak >= 3 && this.emberDue === 0) { res.ember = N.ember(this, 1); this.highStreak = 0; } /* ---- 因果结算表 ---- */ if (!settlement.world.length && ticks.length) settlement.world.push('世界 TICK:' + ticks.length + ' 件后台事件'); res.settlement = settlement; this.settlements.unshift({ turn: this.turn, s: settlement }); /* 铁律·绝对不遗忘:久远记录压缩为摘要而非丢弃 */ if (this.settlements.length > 300) { this.settleArchive = this.settleArchive || []; var overflow = this.settlements.splice(300); overflow.forEach(function (rec) { var bits = []; ['karma', 'npc', 'world', 'injury', 'bp', 'arts'].forEach(function (k) { if (rec.s[k] && rec.s[k].length) bits.push(rec.s[k].length + '项' + k); }); if (rec.s.daoyun) bits.push('道蕴' + (rec.s.daoyun > 0 ? '+' : '') + rec.s.daoyun); this.settleArchive.unshift({ turn: rec.turn, brief: bits.join(',') || '无显著变化' }); }, this); this.settleArchive = this.settleArchive.slice(0, 2000); } /* ---- 岁月史书 ---- */ var ctx = { kind: kind, subject: subject, place: this.pc.lastPlace || (this.pc.lastPlace = this.place()), timeStr: ['子时', '丑时', '寅时', '卯时', '辰时', '黄昏', '入夜', '五更'][this.turn % 8], sensePool: this.sensePool(), usedSenses: this.usedSenses, goldenCache: this.goldenCache, rng_i: this.turn * 17 + 3, baideObj: baideObj, resultTxt: mainTxt ? mainTxt.replace(/') : null, costTxt: costTxt, blank: blankTxt, echo: ticks.filter(function (t) { return !t.hidden; }).slice(0, 2).map(function (t) { return t.text; }).join(' '), event: null }; var ch = N.chronicle(ctx); res.chronicle = ch; res.parts.push({ cls: 'chronicle', html: ch.html }); if (res.bullet) res.parts.push({ cls: 'bullet', html: res.bullet }); if (res.ember) res.parts.push({ cls: 'ember', html: res.ember }); if (res.stageUp && res.stageUp.length) { res.stageUp.forEach(function (s) { res.parts.push({ cls: 'stage', html: '
【大劫推进·' + s.name + '】
' + s.desc + '
' }); }); } if (res.bigFired && res.bigFired.length) { var self = this; res.bigFired.forEach(function (e) { res.parts.push({ cls: 'world', html: N.worldNarrative(self, e) }); }); } if (res.threat) res.parts.push({ cls: 'threat', html: '【' + res.threat.kind + '】' + res.threat.txt + '
' }); if (res.daoTitle) res.parts.push({ cls: 'title', html: '【道号凝结】你的行为凝成了一个名号:「' + res.daoTitle.title + '」。已录入万古道碑。
' + res.daoTitle.deed + '
【伏笔到期】' + f.text + '
' }); }); } if (res.compress && res.compress.length) res.parts.push({ cls: 'sys', html: '【记忆压缩】' + res.compress.join(' ') + '
' }); if (res.decayLog && res.decayLog.length) res.parts.push({ cls: 'sys', html: '【道蕴衰减】' + res.decayLog.join(' ') + '
' }); res.options = this.over ? [] : this.makeOptions(); res.over = this.over; res.deathForms = this.over ? D.DEATH_FORMS : null; return res; }; function alive0(g) { var a = g.npcs.filter(function (n) { return n.alive; }); return a.length ? a[Math.floor(Math.random() * a.length)].id : null; } /* ============================================================ SECTION: save 存档码(含扩展字段) ============================================================ */ Game.prototype.makeSaveCode = function () { var payload = { v: 21, seed: this.seed, seedChain: this.seedChain, turn: this.turn, stage: this.stage, pc: this.pc, opening: { identity: this.opening.identity, era: this.opening.era.name, states: this.opening.states.map(function (s) { return s.id; }), costs: this.opening.costs.map(function (c) { return c.txt; }), tribulation: this.opening.tribulation }, /* 因果网快照 */ karmaNet: this.karmas.map(function (k) { return { id: k.id, name: k.name, level: k.level, strength: k.strength, status: k.status, npc: k.relatedNpc, transformed: k.transformed, unbreakable: k.unbreakable, surface: k.surface, revealed: k.realRevealed }; }), /* NPC 关系网 */ npcNet: this.npcs.map(function (n) { return { id: n.id, name: n.fullName, fac: n.factionName, realm: n.realmIdx, age: n.age, life: n.lifeMax, alive: n.alive, death: n.deathWay, att: n.att, bond: n.bond, met: n.met, mem: n.memory.slice(0, 8), motive: n.motiveRevealed ? n.hiddenMotive : null, qy: n.isQiyun, log: n.actionLog.slice(0, 4), goal: n.goal, pers: n.personality, power: n.power }; }), factions: this.factions, /* 世界大事件日历(未来一百回合) */ calendar: this.bigEvents.filter(function (e) { return !e.done; }).slice(0, 20), /* 当前大劫阶段 */ tribStage: { idx: this.stage, next: D.TRIBULATION[Math.min(D.TRIBULATION.length - 1, this.stage + 1)].turn }, foreshadow: this.foreshadow, rumors: this.rumors.slice(0, 20), prices: this.prices, basePrices: this.basePrices, ticks: this.ticks.slice(0, 20), settlements: this.settlements.slice(0, 60), settleArchive: (this.settleArchive || []).slice(0, 400), daoTraces: (this.daoTraces || []).slice(0, 20), brands: this.brands, daotitles: this.daotitles, lineage: this.daoLineage, usedSenses: this.usedSenses, goldenCache: this.goldenCache, prevLife: this.prevLife, warPair: this.warPair, obituary: this.obituary, field: this.field, over: this.over, overReason: this.overReason, deathInfo: this.deathInfo }; var json = JSON.stringify(payload); /* 简易编码:Base64(中文安全) */ try { var code = 'DDWJ21-' + btoa(encodeURIComponent(json).replace(/%([0-9A-F]{2})/g, function (_, p1) { return String.fromCharCode(parseInt(p1, 16)); })); return { code: code, size: json.length, payload: payload }; } catch (e) { return { code: null, error: String(e), payload: payload, json: json }; } }; Game.loadFromCode = function (code) { try { var raw = String(code).replace(/^DDWJ21-/, ''); var json = decodeURIComponent(Array.prototype.map.call(atob(raw), function (c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); var p = JSON.parse(json); var g = new Game(p.seed); g.seedChain = p.seedChain || [p.seed]; g.turn = p.turn; g.stage = p.stage; g.pc = p.pc; g.opening = { identity: p.opening.identity, era: { name: p.opening.era, desc: [] }, states: (p.opening.states || []).map(function (id) { return D.WORLD_STATES.filter(function (s) { return s.id === id; })[0]; }).filter(Boolean), costs: (p.opening.costs || []).map(function (t) { return { txt: t }; }), karmas: [], tribulation: p.opening.tribulation, anti: [] }; g.opening.era.law = { k: '', v: 1, txt: '' }; g.opening.era.res = { k: '', v: 1, txt: '' }; g.karmas = (p.karmaNet || []).map(function (k) { return { id: k.id, name: k.name, level: k.level, power: k.strength, strength: k.strength, status: k.status, relatedNpc: k.npc, transformed: k.transformed, unbreakable: k.unbreakable, surface: k.surface, realRevealed: k.revealed, desc: '', trigger: '', endings: [], createdTurn: 0 }; }); g.npcs = (p.npcNet || []).map(function (n) { return { id: n.id, name: n.name, fullName: n.name, title: '', factionName: n.fac, faction: null, realmIdx: n.realm, age: n.age, lifeMax: n.life, alive: n.alive, deathWay: n.death, att: n.att || {}, bond: n.bond || 0, met: n.met, memory: n.mem || [], motiveRevealed: !!n.motive, hiddenMotive: n.motive || '', isQiyun: n.qy, actionLog: n.log || [], goal: n.goal, personality: n.pers, power: n.power, betrayalBase: 0.15, talkSpread: 1, luck: 20 }; }); g.factions = p.factions || []; g.bigEvents = p.calendar || []; g.foreshadow = p.foreshadow || []; g.rumors = p.rumors || []; g.prices = p.prices || {}; g.basePrices = p.basePrices || g.prices; g.ticks = p.ticks || []; g.settlements = p.settlements || []; g.settleArchive = p.settleArchive || []; g.daoTraces = p.daoTraces || []; g.brands = p.brands || []; g.daotitles = p.daotitles || []; g.daoLineage = p.lineage || null; g.usedSenses = p.usedSenses || {}; g.goldenCache = p.goldenCache || []; g.prevLife = p.prevLife || []; g.warPair = p.warPair || null; g.obituary = p.obituary || []; g.field = p.field || null; g.over = !!p.over; g.overReason = p.overReason || ''; g.deathInfo = p.deathInfo || null; /* 六司快照向后兼容:旧存档无 sixBase/sixBonus 时用当前值重建 */ if (!g.pc.sixBase) { g.pc.sixBase = {}; for (var _bk in g.pc.six) g.pc.sixBase[_bk] = g.pc.six[_bk]; } if (!g.pc.sixBonus) { g.pc.sixBonus = {}; for (var _bq in g.pc.six) g.pc.sixBonus[_bq] = 0; } g.nctx = { usedSenses: g.usedSenses, goldenCache: g.goldenCache, rng_i: g.turn + 1 }; g.pc.combat = g.calcPower(g.pc); return g; } catch (e) { return { error: String(e) }; } }; return { Game: Game, mulberry32: mulberry32, clamp: clamp }; })();