v0.1.1: 养成闭环 + 管理自主 + 存档安全

- 指婚/续弦:成员详情可主动找人结亲(血亲自动排除、鳏寡可再醮)
- 装备 UI:法宝从府库装备/替换,战力实时反馈
- 御敌点将:劫掠事件可自选迎战阵容(默认 Top4 可改)
- 宗祠祭祖:每两年一次,灵石150 → 声望+6/全族修为小升
- 回档时间轴:每季快照列表,一键回卷(回卷前自动保当前档)
- 战报匣子:史书页全文战报留存可翻阅
- 年度族簿纸笺:每年初弹收支/人丁/战力简报
- 媒人提示条 + 寻衅一年冷却 + 出生率与劫掠频率微调
- 单测 19→29(storage mock roundtrip/回档/指婚血亲/祭祖/寻衅/装备/账本)
This commit is contained in:
Hermes Agent
2026-08-23 08:16:40 +08:00
parent ebef6c1609
commit de491c997f
24 changed files with 734 additions and 26 deletions
+54 -1
View File
@@ -1,5 +1,5 @@
import { useGameStore } from '../store'
import { Character } from '../../game/types/domain'
import { Character, GameState } from '../../game/types/domain'
import { describeRealm, nextRealm } from '../../game/data/realms'
import { describeRoots, ROOT_GRADE_NAMES } from '../../game/data/elements'
import { TRAITS } from '../../game/data/traits'
@@ -8,6 +8,12 @@ import { ITEMS, ARTIFACT_POWER } from '../../game/data/items'
import { combatPowerOf } from '../../game/engine/systems/combat'
import { lifespanOf } from '../../game/engine/systems/lifecycle'
function equipableItems(member: Character, s: GameState): string[] {
const inv = Object.keys(ARTIFACT_POWER).filter((ai) => (s.family.inventory[ai] ?? 0) > 0)
if (member.equipment && !inv.includes(member.equipment)) inv.push(member.equipment)
return inv
}
export function MemberModal({ member }: { member: Character }) {
const world = useGameStore((s) => s.world)
const setSelected = useGameStore((s) => s.setSelected)
@@ -153,6 +159,53 @@ export function MemberModal({ member }: { member: Character }) {
</div>
)}
{member.alive && (
<div className="mm-sec">
<div className="dim" style={{ marginBottom: 6 }}></div>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
{equipableItems(member, s).length === 0 && <span className="dim2"></span>}
{equipableItems(member, s).map((ai) => (
<button
key={ai}
className="btn btn-sm"
style={{
borderColor: member.equipment === ai ? 'var(--vermilion)' : undefined,
color: member.equipment === ai ? 'var(--vermilion-bright)' : undefined
}}
onClick={() => {
w.equip(member.id, ai)
bump()
}}
>
<span className="s-icon" style={{ marginRight: 4 }}>{ITEMS[ai].icon}</span>
{ITEMS[ai].name}(+{Math.round((ARTIFACT_POWER[ai] ?? 0) * 100)}%){member.equipment === ai ? ' · 已佩' : ''}
</button>
))}
</div>
</div>
)}
{member.alive && w.canMarry(member.id) && (
<div className="mm-sec">
<div className="dim" style={{ marginBottom: 6 }}></div>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
{w.marriageCandidatesOf(member.id).slice(0, 6).map((cand) => (
<button
key={cand.id}
className="btn btn-sm"
onClick={() => {
w.marryTo(member.id, cand.id)
bump()
}}
>
{cand.name} · {describeRealm(cand.realm)} · {w.ageOf(cand)}
</button>
))}
{w.marriageCandidatesOf(member.id).length === 0 && <span className="dim2"></span>}
</div>
</div>
)}
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 }}>
{member.children.length > 0 && (
<span className="dim2">