v0.1.0: 仙途家族志 · Chronicle of the Immortal Clan

家族模拟器首版:修仙/经营/战斗/外交/叙事全套系统
- Electron + React + TS + MetonaSqlark(aria+OPFS)
- 水墨中国风 UI
- 引擎种子随机、确定性可回放
- 19 项单元测试、端到端冒烟验证
This commit is contained in:
2026-08-23 00:04:16 +08:00
commit adb22f0558
69 changed files with 14305 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
import { useGameStore } from '../store'
import { npcById } from '../../game/data/npcs'
import { giftNpc, makePeace, marryNpcFamily } from '../../game/engine/systems/diplomacy'
import { useMemo } from 'react'
import { MAJOR_NAMES } from '../../game/data/realms'
const REL_TYPE = (r: number) =>
r >= 80 ? ['死生之交', 'good'] : r >= 50 ? ['通好之势', 'good'] : r >= 25 ? ['友善', 'warn'] : r >= 10 ? ['温和', 'warn'] : r > -10 ? ['中立', ''] : r > -30 ? ['嫌隙', ''] : r > -50 ? ['敌对', 'bad'] : ['仇雠', 'bad']
export default function DiplomacyPanel() {
const world = useGameStore((s) => s.world)
const bump = useGameStore((s) => s.bump)
const revision = useGameStore((s) => s.revision)
void revision
if (!world) return null
const w = world
const npcs = Object.values(w.state.npcFamilies)
const sorted = useMemo(() => npcs, [npcs])
return (
<div>
<div className="help-text">
</div>
{sorted.map((npc) => {
const def = npcById(npc.id)
const [relLabel, relCls] = REL_TYPE(npc.relation)
const married = npc.allied
return (
<div key={npc.id} className="npc-row">
<div className="npc-name">
<div style={{ fontSize: '1.15rem' }}>{npc.name}</div>
<div className="dim2" style={{ fontSize: '0.78rem' }}>{def.style} · {npc.region}</div>
</div>
<div className="relation-bar">
<div className="bar" style={{ borderColor: npc.relation >= 0 ? '#2e5a2e' : '#6e2f2f' }}>
<div
style={{
width: `${(npc.relation + 100) / 2}%`,
background: npc.relation >= 0 ? '#5a8a44' : '#8a4444'
}}
/>
</div>
<div className={`dim ${relCls}`} style={{ marginTop: 4, fontSize: '0.82rem' }}>
{npc.relation} · {relLabel} · {npc.power}
</div>
</div>
<div className="relation-num">
<span className="dim2">{MAJOR_NAMES[def.leaderRealm]}</span>
</div>
<div style={{ display: 'flex', gap: 6 }}>
<button className="btn btn-sm" onClick={() => { giftNpc(w, npc.id, 120); bump() }}>
120
</button>
<button
className="btn btn-sm"
disabled={npc.relation < 25 || married}
onClick={() => { marryNpcFamily(w, npc.id); bump() }}
>
{married ? '已联姻' : '联姻'}
</button>
<button className="btn btn-sm" onClick={() => { (npc.relation = Math.max(-100, npc.relation - 20)); bump() }}>
</button>
{npc.relation < -30 && (
<button className="btn btn-sm" onClick={() => { makePeace(w, npc.id); bump() }}>
</button>
)}
</div>
</div>
)
})}
<div className="dim2" style={{ marginTop: 12 }}>
</div>
</div>
)
}