Files
ChronicleOfTheImmortalClan/src/renderer/ui/panels/LegacyPanel.tsx
T
Hermes Agent 5b40d9158c v0.1.5: 人物志(志向/渡劫/列传/契合/回声)
- 志向系统:七向(求道/镇族/承宗/商略/兵略/耕读/云游)成年礼自动定志,
  量化加成入修炼/战力/坊市/灵田/添丁/声望,云游减修但际遇更频
- 大境界渡劫:炼气→筑基起,晋升改走劫难事件三选——硬渡/请护法(100灵石)/
  压制来年;护法失败或受牵连;渡劫成败与心性、气血挂勾,高阶有小陨落率
- 灵根本命契合:功法元素×主灵根,契合者修炼+6%/战力+4%,详情页「本命」徽章
- 人物列传:自动列传生成器(生平/修行脉络/历险/姻缘/子嗣/大比/墓志铭),
  成员详情小传分页 + 春秋原列传长卷
- 四邻回声:每两年一桩——友好赠礼/敌视暗桩/中立传闻三态动态事件
- 修复:回声事件 npcId 解析错误、事件优先级(惯例>传闻)
- 测试 176→197(志向加成/契合/渡劫三轨/压制通道/列传结构/回声三态)
2026-08-23 09:08:16 +08:00

173 lines
7.2 KiB
TypeScript

import { useState } from 'react'
import { useGameStore } from '../store'
import { computeLegacy, resolveLegacy } from '../../game/core/legacy'
import { resolveLegacy as doesIt } from '../../game/core/legacy'
import { ResolveModal } from '../components/ResolveModal'
import { buildBiography } from '../../game/core/biography'
void doesIt
export default function LegacyPanel() {
const world = useGameStore((s) => s.world)
const revision = useGameStore((s) => s.revision)
void revision
const [showResolve, setShowResolve] = useState(false)
const [view, setView] = useState<'dims' | 'chronicle' | 'report' | 'scroll'>('dims')
if (!world) return null
const w = world
const s = w.state
const dims = computeLegacy(s)
const arch = resolveLegacy(s)
const resolved = !!s.family.flag['resolved']
const yearly = s.yearlyReports.slice().sort((a, b) => a.year - b.year)
const births = Object.values(s.members).filter((c) => c.bornYear >= 0)
const genMin = Math.min(...Object.values(s.members).map((c) => c.bornYear))
const genMax = Math.max(...Object.values(s.members).map((c) => c.deathYear ?? s.year))
return (
<div>
<div className="help-text">
春秋原:家族百年气数之图。望气可预演四维,落印后方入青册(不阻继续行旅)。
</div>
<div className="card" style={{ display: 'flex', alignItems: 'center', gap: 18, marginBottom: 12 }}>
<div className="legacy-peak" style={{ width: 86, height: 110 }}>
<div className="legacy-glyph"></div>
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: '1.04rem', marginBottom: 4 }}>
当前气数 · 预演称号 <b className="gold" style={{ fontSize: '1.2rem' }}>{arch.title}</b>
{resolved && <span className="tag head-t" style={{ marginLeft: 8 }}>已定局</span>}
</div>
<div className="dim2" style={{ fontSize: '0.85rem' }}>
存续{s.year} · {s.family.generation} · 声望峰值{s.stats.repPeak} · 丁口峰值{s.stats.popPeak}
{s.stats.tourneyBest ? ` · 大比最佳第${s.stats.tourneyBest}名` : ''}
</div>
</div>
<div style={{ display: 'flex', gap: 8 }}>
{!resolved && (
<button className="btn btn-primary" onClick={() => setShowResolve(true)}>
望气定鼎
</button>
)}
<button className="btn" onClick={() => setView('dims')}>四维</button>
<button className="btn" onClick={() => setView('report')}>年报</button>
<button className="btn" onClick={() => setView('chronicle')}>年表</button>
<button className="btn" onClick={() => setView('scroll')}>长卷</button>
</div>
</div>
{view === 'dims' && (
<div className="card">
<div className="card-title">气数四维</div>
{Object.entries(dims)
.filter(([k]) => k !== 'total')
.map(([k, v]) => (
<div key={k} className="resolve-dim" style={{ maxWidth: 620 }}>
<span className="dim">{dimName(k)}</span>
<div className="bar" style={{ flex: 1 }}>
<div style={{ width: `${Math.min(100, (v / 160) * 100)}%` }} />
</div>
<b>{v}</b>
</div>
))}
<div className="resolve-dim" style={{ maxWidth: 620 }}>
<span className="dim">总评</span>
<div className="bar" style={{ flex: 1 }}>
<div style={{ width: `${Math.min(100, (dims.total / 400) * 100)}%`, background: 'linear-gradient(90deg,#8e2f24,#c05a41,#e0c15e)' }} />
</div>
<b className="gold">{dims.total}</b>
</div>
<div className="dim2" style={{ marginTop: 8 }}>
人兴=代数·丁口峰值 · 道兴=最高境界·功法大成 · 威名=声望峰·大比最佳 · 香火=存续·飞升
</div>
</div>
)}
{view === 'report' && (
<div className="card">
<div className="card-title">岁末族簿汇编(逐年)</div>
<table className="market-table" style={{ width: '100%' }}>
<thead>
<tr>
<th></th><th>灵石盈亏</th><th>诞辰</th><th>辞世</th><th>声望</th><th>宗族战力</th>
</tr>
</thead>
<tbody>
{yearly.slice(-20).map((r) => (
<tr key={r.year}>
<td>{r.year}</td>
<td className={r.nets >= 0 ? 'good' : 'bad'}>{r.nets >= 0 ? `+${r.nets}` : r.nets}</td>
<td>+{r.births}</td>
<td className="bad">-{r.deaths}</td>
<td>{r.rep}</td>
<td>{r.power}</td>
</tr>
))}
</tbody>
</table>
<div className="dim2" style={{ marginTop: 6 }}> {yearly.length} 年度账册。</div>
</div>
)}
{view === 'scroll' && <ScrollScroll />}
{view === 'chronicle' && (
<div className="card">
<div className="card-title">生卒与突破年表</div>
<div className="dim" style={{ marginBottom: 6 }}>
族人生卒带(首倡 {genMin + s.year - s.year} ~ {genMax} 年,区间 {genMax - genMin} 载)
</div>
<div className="dim2" style={{ marginBottom: 10 }}>
大比记录:{s.stats.tourneyHistory.length === 0 ? '尚无参赛记录' : s.stats.tourneyHistory.map((t) => `${t.year}年第${t.rank}名`).join('、')}
</div>
<div className="dim2">
突破年表(按年份计数):
{breakdown(s).length === 0 ? '暂无' : breakdown(s).map((b) => `${b.year}年×${b.n}`).join(' · ')}
</div>
</div>
)}
{showResolve && <ResolveModal arch={arch} onClose={() => setShowResolve(false)} />}
</div>
)
}
function ScrollScroll() {
const world = useGameStore((s) => s.world)
if (!world) return null
const mem = Object.values(world.state.members).sort((a, b) => b.generation - a.generation || b.bornYear - a.bornYear)
return (
<div className="card">
<div className="card-title">列传长卷</div>
{mem.slice(0, 40).map((c) => {
const bio = buildBiography(world.state, c.id)
return (
<div key={c.id} className="bio-row">
<b>{c.name}</b>
<span className="dim" style={{ marginLeft: 8 }}>
{bio.map((l) => l.text).join(' ').slice(0, 90)}{bio.map((l) => l.text).join(' ').length > 90 ? '……' : ''}
</span>
</div>
)
})}
</div>
)
}
function dimName(k: string): string {
const map: Record<string, string> = { renXing: '人兴', daoXing: '道兴', weiMing: '威名', xiangHuo: '香火' }
return map[k] ?? k
}
function breakdown(s: { chronicle: { category: string; year: number }[] }): { year: number; n: number }[] {
const counts = new Map<number, number>()
for (const e of s.chronicle) {
if (e.category === 'breakthrough') {
const cur = counts.get(e.year) ?? 0
counts.set(e.year, cur + 1)
}
}
return [...counts.entries()].map(([year, n]) => ({ year, n })).sort((a, b) => a.year - b.year)
}