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:
@@ -37,13 +37,16 @@ export class WorldSim {
|
||||
s.secretQi[key] = clamp(s.secretQi[key] + (s.tide > 0.85 ? 5 : 2) - (s.secretQi[key] > 70 ? 2 : 0), 0, 100)
|
||||
}
|
||||
|
||||
// ---- D. 灾变年签(每年首月一掷) ----
|
||||
// ---- D. 灾年(年签一掷 + 持续渐退;B12) ----
|
||||
// 剩数递减
|
||||
if ((s.calamityLeft ?? 0) > 0) s.calamityLeft = (s.calamityLeft ?? 0) - 1
|
||||
if (this.w.state.month === 1 && rng.chance(WORLDSIM.calamityChance)) {
|
||||
const cl = rng.pick([...WORLDSIM.calamities])
|
||||
s.calamity = cl
|
||||
s.calamityYear = this.w.state.year
|
||||
s.calamityLeft = WORLDSIM.calamityMonths
|
||||
applyCalamityToMarket(s, cl as CalamityName)
|
||||
this.w.log('bad', `【天下灾年】${cl}——灵植减产,市价将行。`)
|
||||
this.w.log('bad', `【天下灾年】${cl}——灵植减产,市价将行(约半年风雨)。`)
|
||||
// B7 灾年落家族(一次性体感效果)
|
||||
if (cl === '疫病') {
|
||||
for (const c of this.w.aliveMembers()) {
|
||||
@@ -60,14 +63,23 @@ export class WorldSim {
|
||||
inv(this.w)['beastcore'] = (inv(this.w)['beastcore'] ?? 0) + 2
|
||||
this.w.log('bad', `兽潮涌至,坊市稍有折损;猎得兽核数枚。`)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
// 灾年到期散去
|
||||
if (s.calamity && (s.calamityLeft ?? 0) <= 0) {
|
||||
s.calamity = undefined
|
||||
s.newsFeed.push({
|
||||
year: this.w.state.year, month: this.w.state.month, src: '天道',
|
||||
text: `灾云散尽,灵气复清,天下重归平宁。`, kind: 'calamity'
|
||||
})
|
||||
this.w.log('info', `【天下】灾云散尽,灵气复清。`)
|
||||
}
|
||||
|
||||
// ---- A. 资源循环市场(库存自然流向 + 再平衡 + 价格信号) ----
|
||||
driftMarket(s, rng.next())
|
||||
// ---- A. 资源循环市场(世界供给/需求 + NPC 上桌 + 再平衡) ----
|
||||
worldBreath(this.w, s) // A2+A3:常驻供给与需求(潮汐乘化)
|
||||
npcTrade(this.w, s, rng.next()) // A4:NPC 按 sells/buys 与池交易
|
||||
driftMarket(s, rng.next()) // 波动项(保留噪声弹性)
|
||||
|
||||
// ---- B. NPC 演化(聚合模拟) ----
|
||||
// ---- B. NPC 演化(换代 + 关系网 + 互攻;B7) ----
|
||||
evolveNpc(this.w, s, rng.next())
|
||||
|
||||
// ---- E. 天下快讯(节流) ----
|
||||
@@ -97,14 +109,30 @@ export class WorldSim {
|
||||
return this.s().tide
|
||||
}
|
||||
|
||||
/** 当前市场行情(价格乘子,反馈到 Market) */
|
||||
/** 当前市场行情(价格乘子,Market 唯一读口) */
|
||||
marketMultFor(id: string): number {
|
||||
const s = this.s()
|
||||
const pool = s.marketPool[id] ?? 50
|
||||
const pool = s.marketPool[id] ?? 60
|
||||
const base = poolBase(id)
|
||||
return clamp(pool / base, WORLDSIM.priceFloor, WORLDSIM.priceCeil)
|
||||
}
|
||||
|
||||
/** 玩家交易回写池:买 -delta / 卖 +delta(单件约 0.35 池份额波动) */
|
||||
tradeSettle(resKey: string, delta: number): void {
|
||||
const s = this.s()
|
||||
const base = poolBase(resKey)
|
||||
const cur = s.marketPool[resKey] ?? base
|
||||
const impact = delta * 0.35
|
||||
s.marketPool[resKey] = clamp(cur + impact, base * 0.12, base * WORLDSIM.tradeCeilPct)
|
||||
}
|
||||
|
||||
/** 池深比率(0.12~2.4):断供告急时 < tradeFloorPct */
|
||||
poolDepthOf(resKey: string): number {
|
||||
const s = this.s()
|
||||
const base = poolBase(resKey)
|
||||
return (s.marketPool[resKey] ?? base) / base
|
||||
}
|
||||
|
||||
news(): WorldSimState['newsFeed'] {
|
||||
return this.s().newsFeed
|
||||
}
|
||||
@@ -149,6 +177,52 @@ function empty(): WorldSimState {
|
||||
}
|
||||
}
|
||||
|
||||
/** A2+A3:世界常驻供给与需求(潮汐乘化)——池有了呼吸 */
|
||||
function worldBreath(w: World, s: WorldSimState): void {
|
||||
const tide = s.tide
|
||||
const span = 1 - (tide - 1) * WORLDSIM.tideSupplySpan
|
||||
const supplySpan = Math.max(0.75, Math.min(1.35, span))
|
||||
for (const id of MARKET_IDS) {
|
||||
const base = poolBase(id)
|
||||
const cur = s.marketPool[id] ?? base
|
||||
const supply = base * WORLDSIM.worldSupplyRate * supplySpan
|
||||
const demand = base * WORLDSIM.worldDemandRate
|
||||
s.marketPool[id] = clamp(cur + supply - demand, base * 0.12, base * WORLDSIM.tradeCeilPct)
|
||||
}
|
||||
void w
|
||||
}
|
||||
|
||||
/** A4:NPC 按 def.sells/buys 与池交易——世界波动的背后有了玩家(NPC 化) */
|
||||
function npcTrade(w: World, s: WorldSimState, noise: number): void {
|
||||
let n = noise
|
||||
for (const [id, npc] of Object.entries(w.state.npcFamilies)) {
|
||||
const per = n; n += 0.31
|
||||
const def = npcById(id)
|
||||
const isCalamity = !!s.calamity
|
||||
const rate = WORLDSIM.npcTradeRate * (isCalamity ? 0.7 : 1)
|
||||
// 卖:NPC 抛货 → 池增(量小到可忽略贸易盈亏,只表潮流)
|
||||
for (const itemId of def.sells ?? []) {
|
||||
if (!MARKET_IDS.includes(itemId)) continue
|
||||
const base = poolBase(itemId)
|
||||
const vol = base * rate * (0.6 + (per % 1) * 0.8)
|
||||
s.marketPool[itemId] = clamp((s.marketPool[itemId] ?? base) + vol, base * 0.12, base * WORLDSIM.tradeCeilPct)
|
||||
}
|
||||
// 买:NPC 采购 → 池减;池太浅采不到 → NPC 繁荣受挫(power 微跌)
|
||||
for (const itemId of def.buys ?? []) {
|
||||
if (!MARKET_IDS.includes(itemId)) continue
|
||||
const base = poolBase(itemId)
|
||||
const cur = s.marketPool[itemId] ?? base
|
||||
const vol = base * rate * (0.6 + ((per * 7) % 1) * 0.8)
|
||||
if (cur - vol < base * 0.12) {
|
||||
npc.power = Math.max(40, Math.round(npc.power * 0.985))
|
||||
} else {
|
||||
s.marketPool[itemId] = cur - vol
|
||||
npc.power = Math.min(900, npc.power + 0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function driftMarket(s: WorldSimState, noise: number): void {
|
||||
let n = noise
|
||||
for (const id of MARKET_IDS) {
|
||||
@@ -181,14 +255,33 @@ function poolBase(id: string): number {
|
||||
function evolveNpc(w: World, s: WorldSimState, noise: number): void {
|
||||
void noise
|
||||
const year = w.state.year
|
||||
const ids = Object.keys(w.state.npcFamilies)
|
||||
for (const [id, npc] of Object.entries(w.state.npcFamilies)) {
|
||||
const dyn = s.npcDyn[id] ?? initDynFor(id)
|
||||
s.npcDyn[id] = dyn
|
||||
if (!dyn.relationsWithOthers || Object.keys(dyn.relationsWithOthers).length === 0) {
|
||||
// 关系网初始化:同风格亲近(剑修→剑修 +30~55),异风格事仇(-35~+15)
|
||||
const mine = npcById(id)
|
||||
for (const oid of ids) {
|
||||
if (oid === id) continue
|
||||
const other = npcById(oid)
|
||||
const sameStyle = mine.style === other.style
|
||||
dyn.relationsWithOthers[oid] = sameStyle
|
||||
? 30 + w.rng.int(0, 25)
|
||||
: -35 + w.rng.int(0, 50)
|
||||
}
|
||||
}
|
||||
const def = npcById(id)
|
||||
const curRealm = MAJOR_ORDER[dyn.leaderRealmIdx] ?? def.leaderRealm
|
||||
const lifespan = MAJORS[curRealm].lifespan
|
||||
if (w.state.month === 1) {
|
||||
dyn.leaderAge++
|
||||
// 关系漂移:年首各 ±5(邻近的讲合、世仇的愈深)
|
||||
for (const oid of ids) {
|
||||
if (oid === id) continue
|
||||
const cur = dyn.relationsWithOthers[oid] ?? 0
|
||||
dyn.relationsWithOthers[oid] = clamp(cur + (w.rng.chance(0.5) ? 1 : -1) * 5, -100, 100)
|
||||
}
|
||||
// 换代评估:寿终阈值 或 年首小概率(退隐/遇害)——约 50 年一代
|
||||
const chanceNow = dyn.leaderAge > lifespan * 0.9 ? 0.5 : 0.02
|
||||
if (w.rng.chance(chanceNow) && !w.rng.chance(0.25)) {
|
||||
@@ -202,8 +295,26 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void {
|
||||
pushNews(w, s, [id])
|
||||
w.log('info', `【天下】${def.name} 更易宗主,气象一新。`)
|
||||
}
|
||||
// B7 互攻:与世仇(关系<-50)年首相搏——败方伤筋动骨
|
||||
for (const oid of ids) {
|
||||
if (oid === id) continue
|
||||
const foe = w.state.npcFamilies[oid]
|
||||
if (!foe) continue
|
||||
const rel = dyn.relationsWithOthers[oid] ?? 0
|
||||
if (rel < -50 && w.rng.chance(WORLDSIM.npcEventChance)) {
|
||||
const winner = w.rng.chance(0.5) ? npc : foe
|
||||
const loser = winner === npc ? foe : npc
|
||||
winner.power = Math.min(900, Math.round(winner.power * 1.06))
|
||||
loser.power = Math.max(40, Math.round(loser.power * 0.82))
|
||||
s.newsFeed.push({
|
||||
year, month: 1, src: winner.name,
|
||||
text: `${winner.name} 与 ${loser.name} 起衅——痛挫其锋,势力大动。`
|
||||
})
|
||||
w.log('info', `【天下】${winner.name} 击破 ${loser.name},气象一新。`)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
void npc
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +335,7 @@ function pushNews(w: World, s: WorldSimState, about: string[]): void {
|
||||
const idx = rng.int(0, about.length - 1)
|
||||
const id = about[idx]
|
||||
const tide = s.tide > 1.0 ? '灵潮上涨' : s.tide < 0.75 ? '灵潮回落' : '汐平'
|
||||
let row: { year: number; month: number; src: string; text: string }
|
||||
let row: { year: number; month: number; src: string; text: string; itemId?: string; kind?: 'quote' | 'npc' | 'calamity' }
|
||||
if (id.startsWith('n-')) {
|
||||
const def = npcById(id)
|
||||
const dyn = s.npcDyn[id]
|
||||
@@ -232,7 +343,8 @@ function pushNews(w: World, s: WorldSimState, about: string[]): void {
|
||||
year: w.state.year,
|
||||
month: w.state.month,
|
||||
src: def.name,
|
||||
text: dyn ? `灵潮气象:${dyn.leaderName} 宗主更替,势力重排。` : `${def.name} 世务新张。`
|
||||
text: dyn ? `灵潮气象:${dyn.leaderName} 宗主更替,势力重排。` : `${def.name} 世务新张。`,
|
||||
kind: 'npc'
|
||||
}
|
||||
} else {
|
||||
const pool = clamp(s.marketPool[id] ?? poolBase(id), POOL_BASE[id] * 0.5, POOL_BASE[id] * 1.8)
|
||||
@@ -243,7 +355,9 @@ function pushNews(w: World, s: WorldSimState, about: string[]): void {
|
||||
year: w.state.year,
|
||||
month: w.state.month,
|
||||
src: `世界·${itemName}`,
|
||||
text: pct !== 0 ? `${itemName}行情${pct > 0 ? '升' : '降'}${Math.abs(pct)}% (${tide})` : `${itemName}行情平稳 (${tide})`
|
||||
text: pct !== 0 ? `${itemName}行情${pct > 0 ? '升' : '降'}${Math.abs(pct)}% (${tide})` : `${itemName}行情平稳 (${tide})`,
|
||||
itemId: id,
|
||||
kind: 'quote'
|
||||
}
|
||||
}
|
||||
s.newsFeed.push(row)
|
||||
|
||||
Reference in New Issue
Block a user