Files
ChronicleOfTheImmortalClan/src/renderer/ui/panels/MarketPanel.tsx
T
thzxx 4dfa07cc9f v0.1.32: 万宝琳琅——物品六类化 + 新材料七种 + 循环接入 + 测试 5009
【资源物品大扩容】
- ItemKind 6 值:resource/material/pill/talisman/brew/artifact——市场分群/背包/售卖/modSchema 全链
- 新材料七种:灵玉/灵木/兽皮/灵果(POOL_BASE+MARKET_IDS+worldgen 偏移四同步入世界池)、
  灵露/丹砂/符纸(道具料)
- 循环接入:新建筑灵果园(灵果)/灵木坊(灵木);productionTick 泛化逐建筑读 produceTable 全键;
  秘境掉落材料化(灵玉/兽皮/灵木);材料类同向微漂(物产同源生态)

【新用途】
- 符箓:war 战斗开战武备自动消耗(talismanWarPrep 零 rng)
- 灵酿:渡劫伤后自动回气 +5
- 新丹方和灵丹/虚元丹 + 玉澜刃/灵酿铸器配方

【测试 5009】86 套件(+1000:matrix-treasure-a/b 950 + 断链修整);
【金钟罩】0.1.32 基线重算(物品/池/生产泛化受控变更);build 通过
2026-08-23 21:05:03 +08:00

231 lines
10 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 { ITEMS } from '../../game/data/items'
import { TECHNIQUES } from '../../game/data/techniques'
import { techniqueGradeName } from '../../game/data/realms'
import { marketPrice, buyItem, sellItem, buyTechnique, techniquePrice } from '../../game/engine/sim/Market'
import { PILL_RECIPES, FORGE_RECIPES } from '../../game/data/items'
import { bonusOf } from '../../game/data/buildings'
import { useMemo, useState } from 'react'
export default function MarketPanel() {
const world = useGameStore((s) => s.world)
const bump = useGameStore((s) => s.bump)
const revision = useGameStore((s) => s.revision)
void revision
const [tab, setTab] = useState<'goods' | 'tech' | 'craft'>('goods')
if (!world) return null
const w = world
const fam = w.state.family
const inv = fam.inventory
const goods = useMemo(
() => Object.values(ITEMS).filter((i) => ['resource', 'material', 'pill', 'talisman', 'brew', 'artifact'].includes(i.kind)),
[]
)
const artifacts = useMemo(() => Object.values(ITEMS).filter((i) => i.kind === 'artifact'), [])
const marketTech = TECHNIQUES.filter((t) => t.grade <= 3)
const cangshuLv = fam.buildings['cangshu'] ?? 0
const price = (id: string) => marketPrice(w, id)
return (
<div>
<div className="tabs" style={{ padding: 0, background: 'none', border: 'none', marginBottom: 10 }}>
<div className={`tab ${tab === 'goods' ? 'sel' : ''}`} onClick={() => setTab('goods')}>货栈</div>
<div className={`tab ${tab === 'tech' ? 'sel' : ''}`} onClick={() => setTab('tech')}>法帖</div>
<div className={`tab ${tab === 'craft' ? 'sel' : ''}`} onClick={() => setTab('craft')}>定制</div>
</div>
{tab === 'goods' && (
<>
<div className="help-text">
集市物价随季节波动(当前行情 {Math.round(((fam.flag['priceMult'] as number) ?? 1) * 100)}%)。
仓库寄存的灵草、灵矿、兽核均可在此出手。
</div>
<table className="market-table">
<thead>
<tr>
<th>品名</th>
<th>说明</th>
<th>行价</th>
<th>库存/持有</th>
<th>买卖</th>
</tr>
</thead>
<tbody>
{[...goods, ...artifacts].map((item) => {
const have = ['resource', 'material', 'pill', 'talisman', 'brew', 'artifact'].includes(item.kind)
? inv[item.id] ?? 0
: 0
const stock = item.kind === 'artifact' ? 3 : ['pill', 'talisman', 'brew'].includes(item.kind) ? 5 : 9999
const count = item.kind === 'artifact' ? 1 : 5
return (
<tr key={item.id}>
<td>
<b><span className="s-icon">{item.icon}</span> {item.name}</b>
</td>
<td className="dim">{item.desc}</td>
<td>{price(item.id)} 灵石</td>
<td className="dim">{have}</td>
<td>
<button
className="btn btn-sm"
disabled={fam.stones < price(item.id) * count}
title={fam.stones < price(item.id) * count ? '灵石不足' : ''}
onClick={() => {
buyItem(w, item.id, count)
bump()
}}
>
{count}
</button>{' '}
{(['resource', 'material', 'pill'].includes(item.kind) && have >= count) && (
<button
className="btn btn-sm"
onClick={() => {
sellItem(w, item.id, count)
bump()
}}
>
{count}
</button>
)}
{item.kind === 'artifact' && have > 0 && (
<span className="dim2"> 已购藏</span>
)}
</td>
</tr>
)
})}
</tbody>
</table>
</>
)}
{tab === 'tech' && (
<>
<div className="help-text">
藏书阁录存法帖,族人可修习。藏书阁等级决定可在坊市搜购的品阶(当前 {cangshuLv} 级,可购至{techniqueGradeName(Math.min(4, Math.max(0, cangshuLv + 1)))})。
若藏书阁未建或等级不足,只可见基础法帖。
</div>
<table className="market-table">
<thead>
<tr>
<th>法帖</th>
<th>品阶/路子</th>
<th>妙用</th>
<th>修为加成</th>
<th>战力加成</th>
<th></th>
</tr>
</thead>
<tbody>
{marketTech
.filter((t) => t.grade <= 1 + cangshuLv)
.filter((t) => t.grade <= 3)
.map((t) => {
const owned = fam.techniques.includes(t.id)
const p = t.grade <= 1 + cangshuLv
const priceT = techniquePrice(t.id)
return (
<tr key={t.id}>
<td><b>{t.name}</b></td>
<td className="dim">{techniqueGradeName(t.grade)} · {t.path}</td>
<td className="dim">{t.desc}</td>
<td>+{Math.round((t.expBonus) * 100)}%</td>
<td>+{Math.round((t.powerBonus) * 100)}%</td>
<td>
{owned ? (
<span className="good">已录</span>
) : (
<button
className="btn btn-sm"
disabled={!p || fam.stones < priceT}
onClick={() => {
buyTechnique(w, t.id, priceT)
bump()
}}
>
{priceT}灵石
</button>
)}
</td>
</tr>
)
})}
</tbody>
</table>
</>
)}
{tab === 'craft' && (
<>
<div className="help-text">
丹房依「灵草+兽核+灵石」配比丹药,成数随丹房等级;破境丹需丹房三级。炼器以灵矿兽核+灵石相合,无建筑门槛,只看家底。
</div>
<h4 className="dim" style={{ margin: '8px 0 4px' }}>丹房炼制(当前 {fam.buildings['danfang'] ?? 0} · 成率 {Math.round(bonusOf('danfang', 'craftChance', fam.buildings['danfang'] ?? 0) * 100)}%</h4>
<table className="market-table">
<thead>
<tr><th>丹方</th><th>药性</th><th>配方</th><th>门槛</th><th></th></tr>
</thead>
<tbody>
{PILL_RECIPES.map((r) => {
const lvl = fam.buildings['danfang'] ?? 0
const enough = (inv['lingcao'] ?? 0) >= r.lingcao && (inv['beastcore'] ?? 0) >= r.beastcore && fam.stones >= r.stones
return (
<tr key={r.output}>
<td><b><span className="s-icon"></span> {r.name}</b></td>
<td className="dim">{r.desc}</td>
<td className="dim">{r.lingcao}·核{r.beastcore}·灵石{r.stones}</td>
<td className="dim">{r.danfangLevel}级丹房</td>
<td>
<button
className="btn btn-sm"
disabled={lvl < r.danfangLevel || !enough}
title={lvl < r.danfangLevel ? `丹房需升到 ${r.danfangLevel} 级` : enough ? '炼制(失败折半退料)' : '材料不足'}
onClick={() => { w.craftPill(r.output.replace('pill-', '') as 'qiyuan' | 'ningyuan' | 'pojing'); bump() }}
></button>
</td>
</tr>
)
})}
</tbody>
</table>
<h4 className="dim" style={{ margin: '14px 0 4px' }}>铸器坊(灵矿+兽核+灵石 兵刃)</h4>
<table className="market-table">
<thead>
<tr><th>兵刃</th><th>妙用</th><th>配方</th><th></th></tr>
</thead>
<tbody>
{FORGE_RECIPES.map((r) => {
const enough = (inv['lingkuang'] ?? 0) >= r.lingkuang && (inv['beastcore'] ?? 0) >= r.beastcore && fam.stones >= r.stones
return (
<tr key={r.output}>
<td><b><span className="s-icon"></span> {r.name}</b></td>
<td className="dim">{r.desc}</td>
<td className="dim">{r.lingkuang}·核{r.beastcore}·灵石{r.stones}</td>
<td>
<button
className="btn btn-sm"
disabled={!enough}
title={enough ? '淬火铸成' : '材料不足'}
onClick={() => { w.forgeArtifact(r.output as 'weapon-qi' | 'weapon-ling' | 'weapon-fa'); bump() }}
></button>
</td>
</tr>
)
})}
</tbody>
</table>
{(fam.buildings['danfang'] ?? 0) > 0 && (
<div className="dim2" style={{ marginTop: 8 }}>
破境丹亦可等拍卖会落槌(需要时留神百年拍卖)。
</div>
)}
</>
)}
<div className="dim2" style={{ marginTop: 10 }}>
灵矿不足时可上坊市民间收购;妖丹于探秘与灵兽园中可得。
</div>
</div>
)
}