- 指婚/续弦:成员详情可主动找人结亲(血亲自动排除、鳏寡可再醮) - 装备 UI:法宝从府库装备/替换,战力实时反馈 - 御敌点将:劫掠事件可自选迎战阵容(默认 Top4 可改) - 宗祠祭祖:每两年一次,灵石150 → 声望+6/全族修为小升 - 回档时间轴:每季快照列表,一键回卷(回卷前自动保当前档) - 战报匣子:史书页全文战报留存可翻阅 - 年度族簿纸笺:每年初弹收支/人丁/战力简报 - 媒人提示条 + 寻衅一年冷却 + 出生率与劫掠频率微调 - 单测 19→29(storage mock roundtrip/回档/指婚血亲/祭祖/寻衅/装备/账本)
97 lines
3.6 KiB
TypeScript
97 lines
3.6 KiB
TypeScript
import { useState } from 'react'
|
|
import { useGameStore } from '../store'
|
|
import { applyEventChoice, rankPower } from '../../game/engine/systems/events'
|
|
import { eventCategoryName } from '../../game/data/events'
|
|
import { describeRealm } from '../../game/data/realms'
|
|
import { Character } from '../../game/types/domain'
|
|
import { combatPowerOf } from '../../game/engine/systems/combat'
|
|
|
|
export function EventModal() {
|
|
const pendingEventId = useGameStore((s) => s.pendingEventId)
|
|
const pendingEventDef = useGameStore((s) => s.pendingEventDef)
|
|
const world = useGameStore((s) => s.world)
|
|
const bump = useGameStore((s) => s.bump)
|
|
const setSpeed = useGameStore((s) => s.setSpeed)
|
|
const [squadPicking, setSquadPicking] = useState(false)
|
|
const [squadSel, setSquadSel] = useState<string[]>([])
|
|
if (!pendingEventId || !pendingEventDef || !world) return null
|
|
|
|
const raidIdx = pendingEventDef.options.findIndex((o) => o.eff.raid)
|
|
const squadPool: Character[] =
|
|
raidIdx >= 0
|
|
? world.aliveMembers()
|
|
.filter((c) => world.ageOf(c) >= 16 && c.state !== 'expedition')
|
|
.sort((a, b) => rankPower(world, b) - rankPower(world, a))
|
|
: []
|
|
|
|
const closeModal = () => {
|
|
useGameStore.setState({ pendingEventId: undefined, pendingEventDef: undefined })
|
|
setSquadPicking(false)
|
|
setSquadSel([])
|
|
bump()
|
|
}
|
|
|
|
const choose = (idx: number) => {
|
|
const isRaid = raidIdx >= 0 && raidIdx === idx
|
|
if (isRaid && !squadPicking) {
|
|
setSquadPicking(true)
|
|
setSquadSel(squadPool.slice(0, 4).map((c) => c.id))
|
|
return
|
|
}
|
|
applyEventChoice(world, pendingEventId, idx, isRaid ? squadSel : undefined)
|
|
closeModal()
|
|
const sp = useGameStore.getState().speed
|
|
if (sp > 1) setSpeed(1)
|
|
}
|
|
|
|
const toggleSquad = (id: string) => {
|
|
setSquadSel((old) =>
|
|
old.includes(id) ? old.filter((x) => x !== id) : old.length >= 4 ? [...old.slice(1), id] : [...old, id]
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="modal-outer">
|
|
<div className="modal">
|
|
<div className="modal-title">{pendingEventDef.name}</div>
|
|
<div className="modal-cat">
|
|
{eventCategoryName(pendingEventDef.category)}
|
|
{pendingEventDef.once && ' · 仅此一次'}
|
|
</div>
|
|
<div className="modal-text">{pendingEventDef.text}</div>
|
|
|
|
{squadPicking && (
|
|
<div className="modal-squad" style={{ marginBottom: 14 }}>
|
|
<div className="dim" style={{ marginBottom: 6 }}>迎战点将(默认四人,可改动)</div>
|
|
<div className="squad-picker">
|
|
{squadPool.map((c) => (
|
|
<div
|
|
key={c.id}
|
|
className={`squadian ${squadSel.includes(c.id) ? 'sel' : ''}`}
|
|
onClick={() => toggleSquad(c.id)}
|
|
>
|
|
{c.name}·{describeRealm(c.realm)}·战力{Math.round(combatPowerOf(world, c))}
|
|
</div>
|
|
))}
|
|
{squadPool.length === 0 && <span className="dim2">无可出战之人</span>}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="modal-opts">
|
|
{pendingEventDef.options.map((o, i) => {
|
|
const isRaid = raidIdx === i
|
|
return (
|
|
<button key={i} className="opt-btn" onClick={() => choose(i)}>
|
|
{isRaid && squadPicking ? '出战!(点将已毕)' : o.label}
|
|
{isRaid && !squadPicking && <span className="hint">点将迎战 · 可自选阵容</span>}
|
|
{!isRaid && o.hint && <span className="hint">{o.hint}</span>}
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|