【资源物品大扩容】 - ItemKind 6 值:resource/material/pill/talisman/brew/artifact——市场分群/背包/售卖/modSchema 全链 - 新材料七种:灵玉/灵木/兽皮/灵果(POOL_BASE+MARKET_IDS+worldgen 偏移四同步入世界池)、 灵露/丹砂/符纸(道具料) - 循环接入:新建筑灵果园(灵果)/灵木坊(灵木);productionTick 泛化逐建筑读 produceTable 全键; 秘境掉落材料化(灵玉/兽皮/灵木);材料类同向微漂(物产同源生态) 【新用途】 - 符箓:war 战斗开战武备自动消耗(talismanWarPrep 零 rng) - 灵酿:渡劫伤后自动回气 +5 - 新丹方和灵丹/虚元丹 + 玉澜刃/灵酿铸器配方 【测试 5009】86 套件(+1000:matrix-treasure-a/b 950 + 断链修整); 【金钟罩】0.1.32 基线重算(物品/池/生产泛化受控变更);build 通过
102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
import type { World } from '../World'
|
|
import { Character } from '../../../types/domain'
|
|
import { nextRealm, MAJOR_ORDER, realmDeathChance, describeRealm } from '../../../data/realms'
|
|
|
|
export const TRIB_MAJORS: string[] = ['foundation', 'core', 'nascent', 'spirit']
|
|
|
|
export function needsTribulation(c: Character): boolean {
|
|
const next = nextRealm(c.realm)
|
|
if (!next) return false
|
|
if (next.major === c.realm.major) return false
|
|
return TRIB_MAJORS.includes(next.major)
|
|
}
|
|
|
|
export function tribulationEventId(c: Character): string {
|
|
return `ev-trib-${c.id}-${c.realm.major}-${c.realm.minor}`
|
|
}
|
|
|
|
export function resolveTribulation(w: World, c: Character, mode: 'rash' | 'guard' | 'delay'): string {
|
|
if (mode !== 'delay' && (w.state.family.inventory['brew-niang'] ?? 0) > 0 && c.health < 40) {
|
|
// R4 灵酿:受创回气(每渡劫一次至多一瓶——疗伤佳酿)
|
|
w.state.family.inventory['brew-niang'] = w.state.family.inventory['brew-niang']! - 1
|
|
c.health = Math.min(100, c.health + 5)
|
|
}
|
|
const next = nextRealm(c.realm)
|
|
if (!next) return 'peak'
|
|
|
|
if (mode === 'delay') {
|
|
c.tribDelayYear = w.state.year + 1
|
|
c.tribBoost = 0
|
|
w.log('info', `${c.name} 按兵不动,引而不发,待来年再渡(丹力渐散)。`)
|
|
return 'delayed'
|
|
}
|
|
|
|
let successChance = perTribChance(w, c)
|
|
let guardian: Character | null = null
|
|
if (mode === 'guard') {
|
|
const fam = w.state.family
|
|
if (fam.stones < 100) {
|
|
w.log('bad', '护法之资不足,此行只得咬牙亲渡。')
|
|
} else {
|
|
fam.stones -= 100
|
|
successChance += 0.08
|
|
guardian = w
|
|
.aliveMembers()
|
|
.filter((m) => m.id !== c.id && MAJOR_ORDER.indexOf(m.realm.major) >= MAJOR_ORDER.indexOf(c.realm.major))
|
|
.sort((a, b) => MAJOR_ORDER.indexOf(b.realm.major) - MAJOR_ORDER.indexOf(a.realm.major))[0] ?? null
|
|
}
|
|
}
|
|
|
|
const p = Math.min(0.92, Math.max(0.06, successChance))
|
|
const s = w.state
|
|
if (w.rng.chance(p)) {
|
|
c.realm = next
|
|
c.realmProgress = 0
|
|
w.emitFx('spark', `trib:${c.id}`)
|
|
c.health = 100
|
|
c.lastBreakthroughAttempt = s.year * 12 + s.month
|
|
w.chronicle('breakthrough', `${c.name} 渡劫功成,踏入【${describeRealm(next)}】!`, c.id, true)
|
|
w.log('good', `✦ 天雷散尽,${c.name} 渡劫成功,晋阶【${describeRealm(next)}】!`)
|
|
if (next.major === 'spirit' && !s.flags['firstSpirit']) {
|
|
s.flags['firstSpirit'] = s.year
|
|
}
|
|
return 'success'
|
|
}
|
|
|
|
// 失败
|
|
c.lastBreakthroughAttempt = s.year * 12 + s.month
|
|
c.realmProgress = Math.max(0, 100 - 28 - w.rng.int(0, 12))
|
|
c.health = Math.max(1, c.health - 22 - w.rng.int(0, 12))
|
|
if (c.health < 20) c.state = 'wounded'
|
|
let text = `${c.name} 渡劫失败,肉身受创。`
|
|
const danger = realmDeathChance(c.realm, c.mind)
|
|
if (w.rng.chance(danger)) {
|
|
c.alive = false
|
|
c.deathYear = s.year
|
|
c.deathCause = '渡劫陨落'
|
|
w.chronicle('death', `${c.name} 天劫加身,灵石俱焚而陨。`, c.id, true)
|
|
w.log('bad', `☠ ${c.name} 渡劫陨落。`)
|
|
return 'dead'
|
|
}
|
|
if (guardian) {
|
|
const g = guardian
|
|
if (w.rng.chance(0.5)) {
|
|
g.health = Math.max(1, g.health - 15 - w.rng.int(0, 15))
|
|
if (g.health < 25) g.state = 'wounded'
|
|
w.log('bad', `${g.name} 为护法所伤。`)
|
|
}
|
|
}
|
|
w.log('bad', text)
|
|
return 'fail'
|
|
}
|
|
|
|
export function perTribChance(w: World, c: Character): number {
|
|
const base = ({
|
|
foundation: 0.6,
|
|
core: 0.5,
|
|
nascent: 0.36,
|
|
spirit: 0.26
|
|
} as Record<string, number>)[c.realm.major] ?? 0.9
|
|
return Math.min(0.92, base + c.mind * 0.01 + c.health / 400 + (c.tribBoost ?? 0))
|
|
}
|