v0.1.5: 人物志(志向/渡劫/列传/契合/回声)

- 志向系统:七向(求道/镇族/承宗/商略/兵略/耕读/云游)成年礼自动定志,
  量化加成入修炼/战力/坊市/灵田/添丁/声望,云游减修但际遇更频
- 大境界渡劫:炼气→筑基起,晋升改走劫难事件三选——硬渡/请护法(100灵石)/
  压制来年;护法失败或受牵连;渡劫成败与心性、气血挂勾,高阶有小陨落率
- 灵根本命契合:功法元素×主灵根,契合者修炼+6%/战力+4%,详情页「本命」徽章
- 人物列传:自动列传生成器(生平/修行脉络/历险/姻缘/子嗣/大比/墓志铭),
  成员详情小传分页 + 春秋原列传长卷
- 四邻回声:每两年一桩——友好赠礼/敌视暗桩/中立传闻三态动态事件
- 修复:回声事件 npcId 解析错误、事件优先级(惯例>传闻)
- 测试 176→197(志向加成/契合/渡劫三轨/压制通道/列传结构/回声三态)
This commit is contained in:
2026-08-23 09:08:16 +08:00
parent 98b60076f0
commit 106db18017
17 changed files with 791 additions and 7 deletions
@@ -8,6 +8,8 @@ import { npcById } from '../../data/npcs'
import { resolveRaid } from './combat'
import { sendMission } from './missions'
import { findInheritor } from '../creation'
import { resolveTribulation } from './tribulation'
import { nextRealm, describeRealm } from '../../data/realms'
const ALL_EVENTS: EventDef[] = [...EVENTS]
@@ -80,6 +82,60 @@ export function dynamicEventFor(id: string, w?: World): EventDef | undefined {
]
}
}
if (id.startsWith('ev-echo-')) {
const m = id.match(/^ev-echo-(.+)-\d+$/)
const npcId = m ? m[1] : ''
const npc = w?.state.npcFamilies[npcId]
if (!npc) return undefined
const rel = npc.relation
if (rel > 40) {
return {
id,
name: '四邻来使',
category: 'daily',
weight: 0,
text: `${npc.name}遣使携礼来会:称去年宗主冬狩得灵材,念两家通好,特分润相赠。`,
options: [{ label: '收下并回礼', hint: '灵石+80', eff: { res: { stones: 80 }, rep: 2 } }, { label: '婉谢盛情', hint: '关系+3', eff: { relation: { [npcId]: 3 } } }]
}
}
if (rel < -40) {
return {
id,
name: '四邻诡音',
category: 'daily',
weight: 0,
text: `近日族中子弟夜猎,屡遭马失前蹄——痕迹指向${npc.name}的暗桩。`,
options: [{ label: '斥资戒备', hint: '灵石-40', eff: { res: { stones: -40 }, rep: 2 } }, { label: '按兵不动', hint: '声望-4', eff: { rep: -4 } }]
}
}
return {
id,
name: '四邻传闻',
category: 'daily',
weight: 0,
text: `商道上传来消息:${npc.name}有人丁、家声微变,坊市行情或生波澜。`,
options: [{ label: '记下了', hint: '无', eff: {} }]
}
}
if (id.startsWith('ev-trib-')) {
const parts = id.replace('ev-trib-', '').split('-')
const memberId = parts[0]
const member = w?.state.members[memberId]
if (!member || !member.alive) return undefined
const next = describeNext(member)
return {
id,
name: '天劫',
category: 'major',
weight: 0,
text: `${member.name} 欲冲击【${next}】之关,天上已有雷云积聚。此劫一渡,百尺竿头再进一步;一步踏错,则伤损难料。族中当如何?`,
options: [
{ label: '硬渡!', hint: '心境不减,成败由天', eff: { trib: { memberId, mode: 'rash' } } },
{ label: '请护法(灵石100', hint: '成算大增,护法或受牵连', eff: { trib: { memberId, mode: 'guard' } } },
{ label: '压制一年', hint: '养精蓄锐,来年再渡', eff: { trib: { memberId, mode: 'delay' } } }
]
}
}
if (id === 'ev-recruit') {
return {
id,
@@ -110,6 +166,11 @@ export function dynamicEventFor(id: string, w?: World): EventDef | undefined {
return undefined
}
function describeNext(c: { realm: { major: string; minor: number } }): string {
const next = nextRealm(c.realm as never)
return next ? describeRealm(next) : '临峰'
}
function asFlagString(flag: Record<string, number | boolean | string> | undefined, key: string): string | null {
if (!flag) return null
const v = flag[key]
@@ -194,6 +255,17 @@ export function eventRoll(w: World): void {
fire(w, `ev-tournament-${tourneyNext}`)
return
}
// 四邻回声(每两年左右一桩)
if (s.year % 2 === 0) {
const key = `echoDone-${s.year}`
if (!s.family.flag[key] && w.rng.chance(0.7)) {
const npc = w.rng.pick(Object.values(s.npcFamilies))
s.family.flag[key] = true
fire(w, `ev-echo-${npc.id}-${s.year}`)
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
@@ -336,6 +408,13 @@ function applyEffect(w: World, eff: EffectDef, squad?: string[]): void {
finalizeApprentice(w, stayId, 'stay')
delete fam.flag['apprenticeStay']
}
if (eff.trib) {
const c = w.state.members[eff.trib.memberId]
if (c?.alive) {
resolveTribulation(w, c, eff.trib.mode)
}
return
}
if (eff.feisheng) {
const s2 = w.state
const immortal = w