v0.1.17: 天下为市——世界真炉膛(市场实体化+NPC博弈+灾年持续)
【A 市场实体化(真库存循环)】 - 世界呼吸:池月供给 × 潮汐span - 常驻需求(worldSupplyRate 2% vs worldDemandRate 1.5%) - NPC 上桌:def.sells/buys 逐月入池出池——池浅采不到→power-15%,池深抛货→市场暖意 - 玩家交易回写:buy 浅池价涨/sell 盈池价跌(tradeSettle 0.35 份额粒度);池深<35% 断供拒单(12% 是枯井) - marketPrice 统一走 marketMultFor;本地 POOL_BASE/TECH 复刻清除(worldsim-data 单源) 【B 世界格局博弈】 - 战争伤骨:劫掠胜负回写 power(败-18%/胜+6%)+ dead 字段 raidCount/warCooldownYear 启用 - 社交回响:赠礼回礼(30% 灵石/30% 灵草)+ power 微涨;联姻/和约/结盟皆涨 power - NPC 关系网:relationsWithOthers 填实(同风格亲30~55/异风格隙-35~15)+ 年首±5 漂移;世仇<-50 年首互攻(败方-18%) - 灾年共苦:灾年 NPC 贸易 ×0.7;tide 对供给 ±0.3 弹性 - 灾年持续 6 月渐退;灾起/灾散快讯(kind 标记) 【UI】 - 天下面板:池深百分比(含断供阈值 tooltip)/本族天下排位/灾年余月 - 快讯响应化:行情见顶抛售5/见底吃进5/灾年救济(50灵石→关系8) - MarketPanel 法帖价格改 techniquePrice()(硬编码清除) 【测试】994 全绿(38 套件,新增世界炉膛 8 例);金钟罩 0.1.17 基线固化
This commit is contained in:
@@ -2,7 +2,7 @@ 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 } from '../../game/engine/sim/Market'
|
||||
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'
|
||||
@@ -122,7 +122,7 @@ export default function MarketPanel() {
|
||||
.map((t) => {
|
||||
const owned = fam.techniques.includes(t.id)
|
||||
const p = t.grade <= 1 + cangshuLv
|
||||
const priceT = techniqueGradeName(t.grade) === '?' ? 300 : [120, 300, 700, 1600][t.grade] ?? 300
|
||||
const priceT = techniquePrice(t.id)
|
||||
return (
|
||||
<tr key={t.id}>
|
||||
<td><b>《{t.name}》</b></td>
|
||||
|
||||
@@ -210,7 +210,7 @@ export default function SettingsPanel() {
|
||||
· 「外交」与四邻结好联姻;仇雠之族隔岁来犯,打得赢名望大涨,打不赢蚀钱伤丁。<br />
|
||||
· 「史书」自动记述繁华与凋零——百年之后,后人翻开这一卷家族志,见代代薪火、历历雪泥。
|
||||
</div>
|
||||
<div className="dim2" style={{ marginTop: 8 }}>版本 0.1.16 · Chronicle of the Immortal Clan</div>
|
||||
<div className="dim2" style={{ marginTop: 8 }}>版本 0.1.17 · Chronicle of the Immortal Clan</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { useGameStore } from '../store'
|
||||
import { npcById } from '../../game/data/npcs'
|
||||
import { MAJOR_NAMES } from '../../game/data/realms'
|
||||
import { POOL_BASE } from '../../game/engine/sim/worldsim-data'
|
||||
import type { WorldSimState } from '../../game/engine/sim/worldsim-data'
|
||||
import { POOL_BASE, WORLDSIM } from '../../game/engine/sim/worldsim-data'
|
||||
import { combatPowerOf } from '../../game/engine/runtime/Systems/combat'
|
||||
import { sellItem, buyItem, marketPrice } from '../../game/engine/sim/Market'
|
||||
import { useState } from 'react'
|
||||
|
||||
const RES_LABEL: Record<string, string> = { lingcao: '灵草', lingkuang: '灵矿', beastcore: '兽核' }
|
||||
@@ -15,7 +18,7 @@ export default function WorldPanel() {
|
||||
const [view, setView] = useState<string | null>(null)
|
||||
if (!world) return null
|
||||
const w = world
|
||||
const ws = w.state.worldSim
|
||||
const ws = w.state.worldSim as WorldSimState | undefined
|
||||
if (!ws) return <div className="help-text">天机未显……(世界演化尚未展开)</div>
|
||||
|
||||
const tide = ws.tide ?? 0.5
|
||||
@@ -23,6 +26,13 @@ export default function WorldPanel() {
|
||||
const pool = ws.marketPool ?? {}
|
||||
const news = [...(ws.newsFeed ?? [])].slice().reverse()
|
||||
const calamity = ws.calamity
|
||||
const calamityLeft = ws.calamityLeft ?? 0
|
||||
const myPower = Object.values(w.state.members)
|
||||
.filter((c) => c.alive)
|
||||
.reduce((a, c) => a + combatPowerOf(w, c), 0)
|
||||
const allPowers = Object.entries(w.state.npcFamilies).map(([id, n]) => ({ id, name: n.name, power: n.power }))
|
||||
const myRank = allPowers.filter((x) => x.power > myPower).length + 1
|
||||
void myPower
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -33,15 +43,18 @@ export default function WorldPanel() {
|
||||
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginBottom: 10 }}>
|
||||
<span className="tag gold-t">灵潮 {tideLabel}({Math.round(tide * 100)}%)</span>
|
||||
{calamity
|
||||
? <span className="tag" style={{ borderColor: '#a33', color: '#e8a08a' }}>灾年·{calamity}(灵植减产,市价将行)</span>
|
||||
? <span className="tag" style={{ borderColor: '#a33', color: '#e8a08a' }}>灾年·{calamity}(约{calamityLeft}月后散去)</span>
|
||||
: <span className="tag">风调雨顺</span>}
|
||||
<span className="tag">本族雄基 {myRank}/{Object.keys(w.state.npcFamilies).length + 1}</span>
|
||||
{Object.entries(pool).map(([k, v]) => {
|
||||
const base = POOL_BASE[k as keyof typeof POOL_BASE] ?? 100
|
||||
const r = ((v as number) / base) * 100
|
||||
const cls = r > 115 ? 'bad' : r < 85 ? 'good' : 'dim'
|
||||
const cls = r > 115 ? 'bad' : r < WORLDSIM.tradeFloorPct * 100 ? 'good' : 'dim'
|
||||
const dir = r > 105 ? '↑' : r < 95 ? '↓' : '→'
|
||||
return (
|
||||
<span key={k} className="tag"><span className={cls}>{RES_LABEL[k] ?? k}{dir}{Math.round(r)}%</span></span>
|
||||
<span key={k} className="tag" title={`池深 ${Math.round(r)}%(低于${Math.round(WORLDSIM.tradeFloorPct * 100)}%坊市断供告急)`}>
|
||||
<span className={cls}>{RES_LABEL[k] ?? k}{dir}{Math.round(r)}%</span>
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
@@ -50,12 +63,42 @@ export default function WorldPanel() {
|
||||
<h4 className="dim" style={{ margin: '4px 0 6px' }}>天下快讯</h4>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, maxHeight: 380, overflowY: 'auto' }}>
|
||||
{news.length === 0 && <div className="dim2">风平浪静,尚无消息。</div>}
|
||||
{news.map((row, i) => (
|
||||
{news.map((row, i) => {
|
||||
const canBuy = row.kind === 'quote' && row.itemId && (w.state.family.inventory[row.itemId] ?? 0) >= 5
|
||||
const canSell = row.kind === 'quote' && row.itemId && w.state.family.stones >= marketPrice(w, row.itemId) * 5
|
||||
const isCrisis = row.kind === 'calamity'
|
||||
return (
|
||||
<div key={i} className="ch-item" style={{ padding: '5px 8px' }}>
|
||||
<span className="ch-month">{row.year}年{row.month}月</span>
|
||||
<span>{row.text}</span>
|
||||
{isCrisis && w.state.family.stones >= 50 && (
|
||||
<span style={{ marginLeft: 8 }}>
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
title="引粥施药,换两家之谊"
|
||||
onClick={() => {
|
||||
const npc = Object.values(w.state.npcFamilies)[w.rng.int(0, 3)]
|
||||
npc.relation = Math.min(100, npc.relation + 8)
|
||||
w.state.family.stones -= 50
|
||||
w.log('info', `开仓济世(灵石50),${npc.name} 感念恩义,关系+8。`)
|
||||
bump()
|
||||
}}
|
||||
>救济</button>
|
||||
</span>
|
||||
)}
|
||||
{canBuy && (
|
||||
<span style={{ marginLeft: 8 }}>
|
||||
<button className="btn btn-sm" title="行情见底,出手吃进(买5)" onClick={() => { buyItem(w, row.itemId!, 5); bump() }}>吃进5</button>
|
||||
</span>
|
||||
)}
|
||||
{canSell && !canBuy && (
|
||||
<span style={{ marginLeft: 8 }}>
|
||||
<button className="btn btn-sm" title="行情见顶,集市抛售(卖5)" onClick={() => { sellItem(w, row.itemId!, 5); bump() }}>抛售5</button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user