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)[c.realm.major] ?? 0.9 return Math.min(0.92, base + c.mind * 0.01 + c.health / 400 + (c.tribBoost ?? 0)) }