Files
ChronicleOfTheImmortalClan/src/renderer/ui/panels/TerritoryPanel.tsx
T

207 lines
8.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useGameStore } from '../store'
import { BUILDINGS, buildingById, listBuildingDefs, SPECIALTY_MULT, bonusOf, isSpecialty } from '../../game/data/buildings'
import { useMemo } from 'react'
export default function TerritoryPanel() {
const world = useGameStore((s) => s.world)
const bump = useGameStore((s) => s.bump)
const revision = useGameStore((s) => s.revision)
void revision
const ids = useMemo(() => listBuildingDefs().map((d) => d.id), [])
if (!world) return null
const fam = world.state.family
const levelLabel = (l: number) => '·'.repeat(l) + '。'.repeat(5 - l)
return (
<div>
<div className="help-text">
族产经营:建造建筑后每月自动产出;坊市、丹房等基础设施可解锁功能。
灵石与灵矿不足则无法建造或升级。
</div>
<MonumentList />
<div className="bld-grid">
{ids.map((id) => {
const def = buildingById(id)
if (!def) return null
const level = fam.buildings[id] ?? 0
const built = level > 0
const cost = built && level >= def.maxLevel ? null : def.upgradeCost(built ? level + 1 : 1)
const produce = def.produceTable ? def.produceTable(built ? level : 1) : null
const canUpgrade =
!!cost &&
fam.stones >= cost.stones &&
fam.inventory['lingkuang'] >= cost.lingkuang
const isSpecialtyBuilding = fam.flag['buildingSpecialty'] === id
return (
<div key={id} className={`bld-card ${built ? '' : 'uc'} ${isSpecialtyBuilding ? 'bld-specialty' : ''}`}>
<div className="bld-head">
<span className="bld-icon">{def.icon}</span>
<div>
<div>{def.name}{isSpecialtyBuilding && <span className="gold" style={{ marginLeft: 4, fontSize: '0.75rem' }}>【专精】</span>}</div>
<div className="dim2" style={{ fontSize: '0.75rem' }}>{def.kind === 'produce' ? '产出' : '功效'}</div>
</div>
<span className="bld-lv">{built ? `${level}级` : ''}</span>
</div>
<div className="bld-desc">{def.desc}</div>
{/* 0.1.41 建筑专精加成预览 */}
{built && def.extra && Object.keys(def.extra).length > 0 && (() => {
const specialtyId = fam.flag['buildingSpecialty'] as string | undefined
const currentLevel = level
const isThisSpecialty = isSpecialty(id, specialtyId)
const bonusLabels: { name: string; base: number; specialty: number }[] = []
for (const [key, expr] of Object.entries(def.extra)) {
void expr
const base = bonusOf(id, key, currentLevel, undefined)
const spec = bonusOf(id, key, currentLevel, id)
bonusLabels.push({ name: extraLabel(key), base, specialty: spec })
}
return (
<div className="bld-prod" style={{ fontSize: '0.72rem', color: '#a89878' }}>
{bonusLabels.map((b, i) => (
<span key={i} style={{ marginRight: 8 }}>
{b.name}: {b.base.toFixed(2)}
{isThisSpecialty && <span className="gold"> x{SPECIALTY_MULT} = {(b.base * SPECIALTY_MULT).toFixed(2)}</span>}
{!isThisSpecialty && specialtyId && <span className="dim2"> (设为专精可至 {(b.base * SPECIALTY_MULT).toFixed(2)})</span>}
</span>
))}
</div>
)
})()}
{produce && Object.keys(produce).length > 0 && (
<div className="bld-prod">
{Object.entries(produce)
.filter(([k]) => k !== 'beastcore' || (built && level >= 3))
.map(([k, v]) => `${itemLabel(k)} ×${v}`)
.join(' / ')}
/
</div>
)}
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 'auto' }}>
{built ? (
<>
{level < def.maxLevel ? (
<button
className="btn btn-sm"
disabled={!canUpgrade}
onClick={() => {
world.upgrade(id)
bump()
}}
>
升至{level + 1}级({cost?.stones ?? 0}灵石 + {cost?.lingkuang ?? 0}灵矿)
</button>
) : (
<span className="gold">已臻极致</span>
)}
{id === 'cangshu' && (
<button
className="btn btn-sm"
disabled={(fam.buildings['cangshu'] ?? 0) < 3 || fam.stones < 150 || world.state.year - ((fam.flag['sutraCD'] as number) ?? -9) < 2}
title={((fam.buildings['cangshu'] ?? 0) < 3 ? '需藏书阁3级' : fam.stones < 150 ? '需150灵石' : '两年一访')}
onClick={() => {
world.seekSutra()
bump()
}}
>
求经
</button>
)}
{id === 'zongci' && (
<button
className="btn btn-sm btn-primary"
disabled={world.state.year - ((fam.flag['lastRiteYear'] as number) ?? -9) < 2 || fam.stones < 150}
title="耗灵石150 · 每两年一回 · 声望+6 · 全族修为气血微升"
onClick={() => {
world.ancestralRite()
bump()
}}
>
祭祖{world.state.year - ((fam.flag['lastRiteYear'] as number) ?? -9) < 2 ? '(冷却)' : '150灵石'}
</button>
)}
{built && def.extra && Object.keys(def.extra).length > 0 && (
<button
className="btn btn-sm"
style={{ marginLeft: 4, fontSize: '0.7rem' }}
disabled={isSpecialtyBuilding ? false : fam.stones < 200}
title={isSpecialtyBuilding ? '取消专精(免费)' : `设为专精(耗200灵石)· extra 加成 ×${SPECIALTY_MULT}`}
onClick={() => {
if (isSpecialtyBuilding) {
delete fam.flag['buildingSpecialty']
} else {
if (fam.stones < 200) return
fam.stones -= 200
fam.flag['buildingSpecialty'] = id
}
bump()
}}
>
{isSpecialtyBuilding ? '取消专精' : '设为专精'}
</button>
)}
<span className="dim2" style={{ marginLeft: 'auto' }}>{levelLabel(level)}</span>
</>
) : (
<button
className="btn btn-sm btn-primary"
disabled={!canUpgrade}
onClick={() => {
world.build(id)
bump()
}}
>
营建({cost?.stones ?? 0}灵石 + {cost?.lingkuang ?? 0}灵矿)
</button>
)}
</div>
</div>
)
})}
</div>
</div>
)
}
function MonumentList() {
const world = useGameStore((s) => s.world)
const revision = useGameStore((s) => s.revision)
void revision
if (!world) return null
const monuments = world.state.chronicle.filter((e) => e.text.startsWith('功德碑')).slice().reverse()
if (monuments.length === 0) return null
return (
<div className="monument-strip">
<div className="card-title" style={{ fontSize: '0.98rem' }}>宗祠碑录</div>
{monuments.slice(0, 4).map((m) => (
<div key={m.id} className="dim" style={{ lineHeight: 1.7 }}>
<span className="ch-year" style={{ marginRight: 8 }}>{m.year}</span>
{m.text.slice(3)}
</div>
))}
</div>
)
}
function itemLabel(k: string): string {
const map: Record<string, string> = {
stone: '灵石',
lingcao: '灵草',
lingkuang: '灵矿',
beastcore: '兽核'
}
return map[k] ?? k
}
/** 0.1.41 建筑加成属性中文名 */
function extraLabel(key: string): string {
const map: Record<string, string> = {
craftChance: '成丹率',
unlockGrade: '解锁品阶',
expBonus: '修炼加成',
powerBonus: '战力加成',
repBonus: '声望'
}
return map[key] ?? key
}