v0.1.4: 百年定局(终局/大比/寄读/春秋原)

- 望气定鼎:stats 追踪四维(人兴/道兴/威名/香火)+ 十二称号分档 + 铭文诗
  春秋原页预演称号,落印定局(青册留档,不阻继续游历)
- 太虚大比:每五年征召,点将三关(初试/拔擢/问鼎)复用战斗结算,
  名次四档红利(声望/灵石/全场好感),史书战报自动落笔
- 宗门寄读:随机宗门收徒(适龄少年),寄读 2-4 年期间每季寄资源,
  期满三选:归来精进(可触发突破+随机机运)或续读深造;低概率择师不回
- 求经台:藏书阁≥3 解锁,两年一访得稀有功法
- 春秋原复盘页:气数四维图/岁末族簿汇编/生卒与突破年表/大比年表
- 修复:大比征召曾因月份条件错过整年;抢在百年庆典前的优先级倒置
- 测试 161→176(评分边界/称号分档/大比三轨/寄读全链/求经冷却)
This commit is contained in:
2026-08-23 09:00:17 +08:00
parent 09cbad6449
commit 98b60076f0
17 changed files with 909 additions and 17 deletions
+93 -5
View File
@@ -11,11 +11,11 @@ import { findInheritor } from '../creation'
const ALL_EVENTS: EventDef[] = [...EVENTS]
export function findEvent(id: string): EventDef | undefined {
return ALL_EVENTS.find((e) => e.id === id) ?? dynamicEventFor(id)
export function findEvent(id: string, world?: World): EventDef | undefined {
return ALL_EVENTS.find((e) => e.id === id) ?? dynamicEventFor(id, world)
}
export function dynamicEventFor(id: string): EventDef | undefined {
export function dynamicEventFor(id: string, w?: World): EventDef | undefined {
if (id.startsWith('ev-raid-')) {
const npcId = id.replace('ev-raid-', '')
const npc = npcById(npcId)
@@ -47,6 +47,52 @@ export function dynamicEventFor(id: string): EventDef | undefined {
]
}
}
if (id.startsWith('ev-tournament-')) {
const year = Number(id.replace('ev-tournament-', ''))
return {
id,
name: '太虚大比',
category: 'major',
weight: 0,
text: `太虚仙盟十年一会,新一期大比于祖庭开擂。观礼之众云集,百族竞锋——可遣嫡系赴赛,亦可称病让席。`,
options: [
{ label: '点将赴赛', hint: '战三关,位次定声望', eff: { tournament: true, flag: { [`tournamentYear-${year}`]: true } } },
{ label: '献礼买名', hint: '灵石-200,名次垫底,各族好感略升', eff: { res: { stones: -200 }, relation: { 'n-xuanying': 5, 'n-danxin': 5, 'n-sihai': 5, 'n-nulei': 5 } } },
{ label: '称病不出', hint: '声望小幅受挫', eff: { rep: -3 } }
]
}
}
if (id.startsWith('ev-apprentice-')) {
const rest = id.replace('ev-apprentice-', '')
const memberId = rest.split('-')[0]
const member = w?.state.members[memberId] ?? undefined
const sectName = member?.apprentice?.sect ?? '师门'
const name = member?.name ?? '弟子'
return {
id,
name: '寄读还乡',
category: 'major',
weight: 0,
text: `${name}${sectName}寄读期满。宗门遣人传书:或归家,或续练。`,
options: [
{ label: '接其归家', hint: '其悟道大进,或携功法而归', eff: { flag: { apprenticeRet: memberId } } },
{ label: '令其再拜师门', hint: '寄读二年,资源更厚', eff: { flag: { apprenticeStay: memberId } } }
]
}
}
if (id === 'ev-recruit') {
return {
id,
name: '宗门收徒',
category: 'major',
weight: 0,
text: '远方宗门遣师来访,观族中少年灵根不俗,欲收为记名弟子寄读。',
options: [
{ label: '送子寄读', hint: '其人外派,归时精进', eff: { apprentice: { build: true } } },
{ label: '婉拒', hint: '无', eff: {} }
]
}
}
if (id === 'ev-feisheng') {
return {
id,
@@ -64,6 +110,23 @@ export function dynamicEventFor(id: string): EventDef | undefined {
return undefined
}
function asFlagString(flag: Record<string, number | boolean | string> | undefined, key: string): string | null {
if (!flag) return null
const v = flag[key]
return typeof v === 'string' ? v : null
}
import {
runTournament as _runTournament,
buildApprentice as _buildApprentice,
finalizeApprentice as _finalizeApprentice,
checkApprenticeExpiry,
recruitCheck
} from './tournament'
export const runTournament = _runTournament
export const buildApprentice = _buildApprentice
export const finalizeApprentice = _finalizeApprentice
export function matchesCond(w: World, cond?: Cond): boolean {
if (!cond) return true
const s = w.state
@@ -112,7 +175,7 @@ export function eventRoll(w: World): void {
return
}
// 里程碑检测(优先于日常事件
// 命运里程碑最优先(百年庆典 > 飞升之路
if (s.year >= 100 && !s.completedEvents.includes('ev-centennial')) {
fire(w, 'ev-centennial')
return
@@ -122,6 +185,15 @@ export function eventRoll(w: World): void {
fire(w, 'ev-feisheng')
return
}
checkApprenticeExpiry(w)
recruitCheck(w)
// 五年一会的太虚大比(该年任一时刻)
const tourneyNext = Math.ceil(s.year / 5) * 5
if (s.year >= 10 && s.year === tourneyNext && !s.completedEvents.includes(`ev-tournament-${tourneyNext}`)) {
fire(w, `ev-tournament-${tourneyNext}`)
return
}
const roll = w.rng.next()
const category: 'daily' | 'major' | 'fate' | undefined = roll < 0.5 ? 'daily' : roll < 0.78 ? 'major' : roll < 0.86 ? 'fate' : undefined
@@ -151,7 +223,7 @@ export function fire(w: World, id: string): void {
export function applyEventChoice(w: World, eventId: string, optionIdx: number, squad?: string[]): void {
const s = w.state
const def = findEvent(eventId)
const def = findEvent(eventId, w)
if (!def) {
s.pendingEvent = undefined
return
@@ -248,6 +320,22 @@ function applyEffect(w: World, eff: EffectDef, squad?: string[]): void {
if (eff.addTech) {
if (!fam.techniques.includes(eff.addTech)) fam.techniques.push(eff.addTech)
}
if (eff.tournament) {
runTournament(w)
}
if (eff.apprentice?.build) {
buildApprentice(w)
}
const retId = asFlagString(eff.flag, 'apprenticeRet')
const stayId = asFlagString(eff.flag, 'apprenticeStay')
if (retId) {
finalizeApprentice(w, retId, 'return')
delete fam.flag['apprenticeRet']
}
if (stayId) {
finalizeApprentice(w, stayId, 'stay')
delete fam.flag['apprenticeStay']
}
if (eff.feisheng) {
const s2 = w.state
const immortal = w