feat(0.1.16-P1): 资源闭环引擎(修为耗草/炼丹概率化/铸器/灵脉收益/NPC战力/灾变落效)+ C13C14

【A 经济闭环】
- 修为经济化:练气以上修行者月耗灵草(闭关2普通1),无草速修0.6~1.0衰减+缺草年告警
- 炼丹等级化:bonusOf 表达式引擎(白名单安全求值),craftChance 转正;丹方单源 PILL_RECIPES(聚气L1/凝元L2/破境L3)+ 失败折半退料
- 炼器筑宝:FORGE_RECIPES(法器/灵器/法宝,灵矿+兽核+灵石)
- 秘境灵脉:secretQiOf→rollWarbooty 收益门槛(0.5~1.3折算);掉落按秘境 techGrades 过滤
- NPC 实力参战:raid 强度/风险随 npc.power 浮动(0.5~1.8)
- 灾年落效:疫病/兽潮一次性袭击 + CALAMITY_FAMILY 月度减产乘子

【C 治理】
- C13 破境丹渡劫修正:大境界时药力→tribBoost 加成(+12%)入渡劫事件,不再跳过三选
- C14 存储防线:loadState 补 migrate;MigrationError(TOO_NEW/未知版本)提示升级

【UI 半批】
- 坊市新增 定制tab(丹方/铸器);ChroniclePanel 手写史书输入条;MemberModal 册立家主钮
- 年报摘要(要闻5条+辞世讣告);EventModal 选项收益预览
- 结盟引擎:setAlliance(relation≥40)/盟友岁差不降/年利15灵石
This commit is contained in:
2026-08-23 14:10:42 +08:00
parent c5fa2a3357
commit de46808188
23 changed files with 409 additions and 81 deletions
+71 -18
View File
@@ -9,10 +9,12 @@ import {
YearlyReport
} from '../../types/domain'
import { BUILDINGS } from '../../data/buildings'
import { BUILDINGS, bonusOf } from '../../data/buildings'
import { itemById, pillRecipeByOutput, forgeRecipeByOutput } from '../../data/items'
import { POSTS } from '../../data/posts'
import { aspirationById as aspirationOf } from '../../data/aspirations'
import { computeLegacy, resolveLegacy, peakRealmIndex, grandTechniqueCount, LegacyArch } from '../narrative/legacy'
import { needsTribulation, tribulationEventId } from './Systems/tribulation'
import { createWorldState, findInheritor } from './creation'
import { SYSTEM_DEFS, SystemDef } from './capabilities'
import { emptyClock } from './clocks'
@@ -445,6 +447,26 @@ export class World {
// ==================== player actions ====================
setAlliance(npcId: string, on: boolean): boolean {
const npc = this.state.npcFamilies[npcId]
if (!npc) return false
if (on) {
if (npc.relation < 40) return false
npc.allied = true
npc.alliedSinceYear = this.state.year
this.log('good', `${npc.name}结成同盟——盟誓既立,互不犯边。`)
this.chronicle('diplomacy', `本族与${npc.name}缔结同盟。`, undefined, true)
return true
}
if (npc.allied) {
npc.allied = false
npc.relation = Math.max(-100, npc.relation - 20)
this.log('bad', `${npc.name}的盟约破裂——关系转恶。`)
this.chronicle('diplomacy', `${npc.name}结盟破裂。`, undefined, true)
}
return true
}
assignHead(id: Id, silent = false): void {
const c = this.memberById(id)
if (!c.alive) return
@@ -498,7 +520,16 @@ export class World {
inv[pill] = inv[pill]! - 1
if (pill === 'pill-pojing') {
if (c.realmProgress >= 100) {
this.resolveBottleneck(c, 0.22)
if (needsTribulation(c) && this.sysEnabled('tribulation')) {
// C13:大境界渡劫不可跳过——丹药转为天劫助益
c.tribBoost = (c.tribBoost ?? 0) + 0.12
c.realmProgress = 100
this.pendingEvent(tribulationEventId(c))
this.state.pendingEvent = tribulationEventId(c)
this.log('info', `${c.name} 服下破境丹,丹力浑厚——雷云受感而聚,九霄震动。`)
} else {
this.resolveBottleneck(c, 0.22)
}
} else {
this.memberById(memberId).realmProgress = Math.min(100, c.realmProgress + 20)
this.log('info', `${c.name} 服下破境丹,灵力充盈。`)
@@ -548,22 +579,44 @@ export class World {
return true
}
craftPill(kind: 'qiyuan' | 'ningyuan'): boolean {
craftPill(kind: 'qiyuan' | 'ningyuan' | 'pojing'): boolean {
const fam = this.state.family
const lvl = fam.buildings['danfang']
if (!lvl) return false
const cost = kind === 'qiyuan'
? { lingcao: 15, beastcore: 0, stones: 20 }
: { lingcao: 25, beastcore: 4, stones: 60 }
if ((fam.inventory['lingcao'] ?? 0) < cost.lingcao) return false
if ((fam.inventory['beastcore'] ?? 0) < cost.beastcore) return false
if (fam.stones < cost.stones) return false
fam.inventory['lingcao'] -= cost.lingcao
fam.inventory['beastcore'] -= cost.beastcore
fam.stones -= cost.stones
fam.inventory[kind === 'qiyuan' ? 'pill-qiyuan' : 'pill-ningyuan'] =
(fam.inventory[kind === 'qiyuan' ? 'pill-qiyuan' : 'pill-ningyuan'] ?? 0) + 1
this.log('info', `丹房炼成一枚${kind === 'qiyuan' ? '聚气丹' : '凝元丹'}`)
const r = pillRecipeByOutput(`pill-${kind}`)
if (!lvl || !r) return false
if (lvl < r.danfangLevel) {
this.log('info', `丹房不足(需 ${r.danfangLevel} 级方可炼${r.name})。`)
return false
}
const inv = fam.inventory
if ((inv['lingcao'] ?? 0) < r.lingcao || (inv['beastcore'] ?? 0) < r.beastcore || fam.stones < r.stones) return false
inv['lingcao'] -= r.lingcao
inv['beastcore'] -= r.beastcore
fam.stones -= r.stones
const chance = bonusOf('danfang', 'craftChance', lvl)
if (this.rng.chance(chance)) {
inv[`pill-${kind}`] = (inv[`pill-${kind}`] ?? 0) + 1
this.log('good', `丹房炼成一枚${r.name}`)
return true
}
inv['lingcao'] += Math.ceil(r.lingcao / 2)
inv['beastcore'] += Math.ceil(r.beastcore / 2)
fam.stones += Math.ceil(r.stones / 2)
this.log('bad', `${r.name}炼废了——丹火失控,药材折半。`)
return false
}
forgeArtifact(kind: 'weapon-qi' | 'weapon-ling' | 'weapon-fa'): boolean {
const fam = this.state.family
const r = forgeRecipeByOutput(kind)
if (!r) return false
const inv = fam.inventory
if ((inv['lingkuang'] ?? 0) < r.lingkuang || (inv['beastcore'] ?? 0) < r.beastcore || fam.stones < r.stones) return false
inv['lingkuang'] -= r.lingkuang
inv['beastcore'] -= r.beastcore
fam.stones -= r.stones
inv[kind] = (inv[kind] ?? 0) + 1
this.log('good', `炉火淬炼,得一${r.name}`)
return true
}
@@ -611,8 +664,8 @@ export class World {
const fam = this.state.family
const bonus =
1 +
(fam.buildings['yanwu'] ?? 0) * 0.04 +
(fam.buildings['lingshou'] ?? 0) * 0.05 +
bonusOf('yanwu', 'powerBonus', fam.buildings['yanwu'] ?? 0) +
bonusOf('lingshou', 'powerBonus', fam.buildings['lingshou'] ?? 0) +
this.postBonus('battlePower')
const top = this.aliveMembers()
.map((c) => combatPowerOf(this, c))