v0.1.0: 仙途家族志 · Chronicle of the Immortal Clan
家族模拟器首版:修仙/经营/战斗/外交/叙事全套系统 - Electron + React + TS + MetonaSqlark(aria+OPFS) - 水墨中国风 UI - 引擎种子随机、确定性可回放 - 19 项单元测试、端到端冒烟验证
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
import { useGameStore } from '../store'
|
||||
import { Character } 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'
|
||||
import { TECHNIQUES } from '../../game/data/techniques'
|
||||
import { ITEMS, ARTIFACT_POWER } from '../../game/data/items'
|
||||
import { combatPowerOf } from '../../game/engine/systems/combat'
|
||||
import { lifespanOf } from '../../game/engine/systems/lifecycle'
|
||||
|
||||
export function MemberModal({ member }: { member: Character }) {
|
||||
const world = useGameStore((s) => s.world)
|
||||
const setSelected = useGameStore((s) => s.setSelected)
|
||||
const bump = useGameStore((s) => s.bump)
|
||||
if (!world) return null
|
||||
const w = world
|
||||
const s = w.state
|
||||
const next = nextRealm(member.realm)
|
||||
const nextName = next ? describeRealm(next) : '已至巅峰'
|
||||
const tech = TECHNIQUES.find((t) => t.id === member.techniqueId)
|
||||
const power = member.alive ? combatPowerOf(w, member) : 0
|
||||
const lifespan = lifespanOf(w, member)
|
||||
|
||||
return (
|
||||
<div className="modal-outer" onClick={() => setSelected(undefined)}>
|
||||
<div className="modal member-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal-title" style={{ fontSize: '1.4rem' }}>
|
||||
{member.name}
|
||||
{!member.alive && <span className="bad" style={{ fontSize: '1rem' }}> · 殁</span>}
|
||||
</div>
|
||||
<div className="modal-cat">
|
||||
第{member.generation}代 · {member.gender === 'male' ? '男' : '女'} · {w.ageOf(member)}岁 · 寿数约{lifespan} · {describeRoots(member.roots)}
|
||||
</div>
|
||||
|
||||
<div className="mm-grid">
|
||||
<div className="mm-line"><span>境界进度</span><b>{describeRealm(member.realm)} {Math.floor(member.realmProgress)}%</b></div>
|
||||
<div className="mm-line"><span>下一关</span><b className={member.realmProgress >= 100 ? 'gold' : ''}>{nextName}</b></div>
|
||||
<div className="mm-line"><span>战力</span><b>{Math.round(power)}</b></div>
|
||||
<div className="mm-line"><span>状态</span><b>{member.state === 'meditation' ? '闭关' : member.state === 'expedition' ? '外出' : member.state === 'wounded' ? '养伤' : '无事'}</b></div>
|
||||
<div className="mm-line"><span>气血</span><b>{Math.round(member.health)}</b></div>
|
||||
<div className="mm-line"><span>功法</span><b>{tech ? `${tech.name}(${['黄', '玄', '地', '天', '仙'][tech.grade]}阶)` : member.realm.major === 'mortal' ? '—' : '未习功法'}</b></div>
|
||||
<div className="mm-line"><span>法宝</span><b>{member.equipment ? `${ITEMS[member.equipment].name}(+${Math.round((ARTIFACT_POWER[member.equipment] ?? 0) * 100)}%)` : '—'}</b></div>
|
||||
<div className="mm-line"><span>姻亲</span><b>{member.spouseId ? s.members[member.spouseId]?.name ?? '' : member.spouseHouse ?? '未婚'}</b></div>
|
||||
</div>
|
||||
|
||||
<div className="bar bar-red" style={{ marginTop: 10 }} title="气血">
|
||||
<div style={{ width: `${member.health}%` }} />
|
||||
</div>
|
||||
|
||||
<div className="mm-sec">
|
||||
<div className="mm-grid">
|
||||
<div className="mm-line"><span>悟性</span><b>{member.perception}/10</b></div>
|
||||
<div className="mm-line"><span>根骨</span><b>{member.physique}/10</b></div>
|
||||
<div className="mm-line"><span>心性</span><b>{member.mind}/10</b></div>
|
||||
<div className="mm-line"><span>魅力</span><b>{member.charm}/10</b></div>
|
||||
<div className="mm-line"><span>气运</span><b>{member.fortune}/12</b></div>
|
||||
<div className="mm-line"><span>品性</span><b>{member.fortune ? (member.fortune > 7 ? '俊逸' : '端正') : '—'}</b></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mm-sec">
|
||||
<div className="dim" style={{ marginBottom: 4 }}>秉性</div>
|
||||
{member.traits.map((t) => (
|
||||
<span key={t} className="tag" style={{ marginRight: 4 }}>
|
||||
{TRAITS[t]?.name ?? t}
|
||||
</span>
|
||||
))}
|
||||
{member.traits.length === 0 && <span className="dim2">平淡无奇</span>}
|
||||
</div>
|
||||
|
||||
<div className="mm-sec">
|
||||
<div className="dim" style={{ marginBottom: 6 }}>起居</div>
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{member.alive && member.state !== 'expedition' && (
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => {
|
||||
w.setMeditation(member.id, member.state !== 'meditation')
|
||||
bump()
|
||||
}}
|
||||
>
|
||||
{member.state === 'meditation' ? '出关' : '闭关'}
|
||||
</button>
|
||||
)}
|
||||
{member.alive && member.realmProgress >= 100 && member.realm.major !== 'spirit' && (
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={() => {
|
||||
w.assistedBreakthrough(member.id)
|
||||
bump()
|
||||
}}
|
||||
>
|
||||
冲击{nextName}
|
||||
</button>
|
||||
)}
|
||||
{member.alive && (
|
||||
<>
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => {
|
||||
w.takePill(member.id, 'pill-qiyuan')
|
||||
bump()
|
||||
}}
|
||||
>
|
||||
服聚气丹
|
||||
</button>
|
||||
{member.realm.major !== 'mortal' && (
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => {
|
||||
w.takePill(member.id, 'pill-ningyuan')
|
||||
bump()
|
||||
}}
|
||||
>
|
||||
服凝元丹
|
||||
</button>
|
||||
)}
|
||||
{member.realmProgress >= 100 && member.realm.major !== 'mortal' && (
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => {
|
||||
w.takePill(member.id, 'pill-pojing')
|
||||
bump()
|
||||
}}
|
||||
>
|
||||
服破境丹
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{member.alive && (
|
||||
<div className="mm-sec">
|
||||
<div className="dim" style={{ marginBottom: 6 }}>传功(藏书阁所录)</div>
|
||||
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
||||
{s.family.techniques.map((tid) => (
|
||||
<button
|
||||
key={tid}
|
||||
className="btn btn-sm"
|
||||
style={{ borderColor: member.techniqueId === tid ? 'var(--gold)' : 'var(--line)' }}
|
||||
onClick={() => {
|
||||
w.giveTechnique(member.id, tid)
|
||||
bump()
|
||||
}}
|
||||
>
|
||||
{TECHNIQUES.find((t) => t.id === tid)?.name ?? tid}
|
||||
</button>
|
||||
))}
|
||||
{s.family.techniques.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">
|
||||
子女:{member.children.map((cid) => s.members[cid]?.name ?? '?').join('、')}
|
||||
</span>
|
||||
)}
|
||||
<button className="btn" onClick={() => setSelected(undefined)}>阖上</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user