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
@@ -165,15 +165,18 @@ export function resolveRaid(
): EncounterResult {
const npc = w.state.npcFamilies[npcId]
const def = npcById(npcId)
// 宗主实力分层:power 由年度成长+换代驱动,劫掠强度随之浮动(0.5↔1.8)
const npcPower = npc.power ?? 60
const strength = Math.min(1.8, Math.max(0.5, 0.5 + (npcPower / 120) * 0.5))
const enemy: EnemyDef = {
id: npcId,
name: `${npc.name}的劫掠队`,
realm: def.leaderRealm,
strength: 0.78,
strength,
icon: '袭',
desc: def.desc
}
const risk = 0.55
const risk = Math.min(0.75, 0.5 + (npcPower / 120) * 0.1)
const res = resolveEncounter(w, {
title: `${npc.name}来袭!`,
enemy,
@@ -198,23 +201,29 @@ export function resolveRaid(
return res
}
export function rollWarbooty(w: World, loot: LootDef): Record<string, number> {
export function rollWarbooty(w: World, loot: LootDef, qiRatio = 1): Record<string, number> {
const result: Record<string, number> = {}
for (const [k, r] of Object.entries(loot.resources)) {
const v = w.rng.int(r[0], r[1])
const v = Math.round(w.rng.int(r[0], r[1]) * qiRatio)
result[k] = v
w.state.family.inventory[k] = (w.state.family.inventory[k] ?? 0) + v
}
if (loot.artifactChance && w.rng.chance(loot.artifactChance)) {
const itemFactor = Math.min(1.4, Math.max(0.5, 0.6 + 0.4 * qiRatio))
if (loot.artifactChance && w.rng.chance(loot.artifactChance * itemFactor)) {
const pool = ['weapon-fan', 'weapon-qi', 'weapon-ling']
const a = w.rng.pick(pool)
w.state.family.inventory[a] = (w.state.family.inventory[a] ?? 0) + 1
result[a] = 1
}
if (loot.techniqueChance && w.rng.chance(loot.techniqueChance)) {
const t = w.rng.pick(pack().techniques)
w.state.family.techniques.push(t.id)
result['tech'] = 1
if (loot.techniqueChance && w.rng.chance(loot.techniqueChance * itemFactor)) {
const pool = loot.techGrades && loot.techGrades.length > 0
? pack().techniques.filter((t) => loot.techGrades!.includes(t.grade))
: pack().techniques
if (pool.length > 0) {
const t = w.rng.pick(pool)
w.state.family.techniques.push(t.id)
result['tech'] = 1
}
}
return result
}