v0.1.18: 时代绘卷 + 特效入引擎(双主线完成,1002 测试破千)
【主线一:世界自演进深化《时代绘卷》】 - 世纪弧 era 状态机:盛世/平世/乱世/末法四态(ERA_CONF 全参数化)。 灾年概率×0.5~1.6、世界供需×0.7~1.25、拍卖概率×0.6~1.3、tide 偏置 ±0.09; ERA_DURA 控制转换节奏(15~45年),转移时史官快讯+log。世界有大节律了 - NPC 景气仓廪:npcDyn.prosperity(8~100)月结余——买卖+0.5~0.8、断供-2、灾年-3; >75 景气 power+1、<30 衰闷 power-1;换代焕新(55~75)。NPC 在过日子 - 蝴蝶效应:互攻胜率 power 加权(强者恒强)、打残广播(败家邻望-8/胜家+4); 玩家劫掠胜利/结盟直接写 NPC·NPC 关系网(-6/-3)——玩家行动推进世界历史 - 天下十年一鉴:每十年史官综述(灾/换代/行情/灵潮聚合,kind='annal') - 灾年↔秘境:灾年灵脉恢复减半(末法感) - WorldPanel 天下志:era 徽标(悬停描述)/史官十年鉴/群雄势衡柱状图/衰微标注 【主线二:特效系统并入引擎(架构升级)】 - WorldEventBus.onFx + Kernel 转发 + World.emitFx 语义发射(零 rng 消耗) - 12 个语义发射点:突破/化神/渡劫/劫掠胜负/遭遇战/拍卖/大比/灾年起散/换代 —— 取代 UI log 文本正则猜测(spark/blade 双源锁死) - makeBus 桥接 FxGate(kind→gate.emit),gate 挡位/密度/reduced 逻辑保留在 UI 【测试】1002 全绿(40 套件):fxqueue-semantic 3 例 + era-world 5 例; 金钟罩 0.1.18 基线固化(era/prosperity/蝴蝶效应受控变更)
This commit is contained in:
@@ -9,6 +9,7 @@ export interface KernelBus {
|
||||
onPendingEvent: WorldEventBus['onPendingEvent']
|
||||
onGameOver: WorldEventBus['onGameOver']
|
||||
onYearPaper?: WorldEventBus['onYearPaper']
|
||||
onFx?: WorldEventBus['onFx']
|
||||
onSystemChange?: WorldEventBus['onSystemChange']
|
||||
onPluginChange?: WorldEventBus['onPluginChange']
|
||||
}
|
||||
@@ -31,7 +32,8 @@ export class Kernel {
|
||||
onGameOver: (reason, year) => this.busOut.forEach((o) => o.onGameOver(reason, year)),
|
||||
onYearPaper: (r) => this.busOut.forEach((o) => o.onYearPaper?.(r)),
|
||||
onSystemChange: (id, enabled) => this.busOut.forEach((o) => o.onSystemChange?.(id, enabled)),
|
||||
onPluginChange: (id, action) => this.busOut.forEach((o) => o.onPluginChange?.(id, action))
|
||||
onPluginChange: (id, action) => this.busOut.forEach((o) => o.onPluginChange?.(id, action)),
|
||||
onFx: (em) => this.busOut.forEach((o) => o.onFx?.(em))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ export class GameFacade {
|
||||
about(): { title: string; version: string; modules: number; systems: number; plugins: number; packFingerprint: string } {
|
||||
return {
|
||||
title: '仙途家族志',
|
||||
version: '0.1.17',
|
||||
version: '0.1.18',
|
||||
modules: this.world.systemList().length,
|
||||
systems: this.world.systemList().filter((s) => s.enabled).length,
|
||||
plugins: this.world.pluginList().length,
|
||||
|
||||
@@ -131,6 +131,7 @@ export function resolveEncounter(
|
||||
lines.push(`此战缴获:${Object.entries(loot).map(([k, v]) => `${itemName(k)} ×${v}`).join('、')}。`)
|
||||
}
|
||||
|
||||
if (win && opts.kind === 'war') w.emitFx('blade', 'battle')
|
||||
const result: EncounterResult = { win, draw, lines, loot, losses: loss }
|
||||
const log: BattleLog = {
|
||||
id: w.seq(),
|
||||
@@ -191,12 +192,20 @@ export function resolveRaid(
|
||||
npc.relation = Math.min(60, npc.relation + 25)
|
||||
w.state.family.reputation += 6
|
||||
// B5:战争伤骨——败方 power 重挫,次年不再来犯(warCooldownYear/raidCount 启用)
|
||||
w.emitFx('blade', `raid:${npcId}`)
|
||||
npc.power = Math.max(40, Math.round(npc.power * 0.82))
|
||||
npc.raidCount = (npc.raidCount ?? 0) + 1
|
||||
npc.warCooldownYear = w.state.year
|
||||
// 1-3 蝴蝶效应:玩家重创名声——邻家对败者关系趋冷
|
||||
for (const dyn of Object.values(w.state.worldSim?.npcDyn ?? {})) {
|
||||
if (dyn && dyn.relationsWithOthers && npcId in dyn.relationsWithOthers) {
|
||||
dyn.relationsWithOthers[npcId] = Math.max(-100, (dyn.relationsWithOthers[npcId] ?? 0) - 6)
|
||||
}
|
||||
}
|
||||
w.chronicle('battle', `击退${npc.name}的犯境,家族声威大振。`, undefined, true)
|
||||
} else if (!res.draw) {
|
||||
npc.relation = Math.max(-100, npc.relation - 15)
|
||||
w.emitFx('pulse', `raid-lose:${npcId}`)
|
||||
npc.raidCount = (npc.raidCount ?? 0) + 1
|
||||
npc.warCooldownYear = w.state.year
|
||||
npc.power = Math.min(900, Math.round(npc.power * 1.06))
|
||||
|
||||
@@ -190,7 +190,9 @@ export function resolveBreakthrough(w: World, c: Character, boost: number): void
|
||||
const desc = describeRealm(next)
|
||||
w.chronicle('breakthrough', `${c.name} 突破至【${desc}】。`, c.id, majorJump)
|
||||
w.log('good', `✦ ${c.name} 突破到 ${desc}!`)
|
||||
w.emitFx('spark', `break:${c.id}`)
|
||||
if (next.major === 'spirit') {
|
||||
w.emitFx('pulse', `spirit:${c.id}`)
|
||||
w.chronicle('breakthrough', `华夏震惊:${c.name} 踏入化神之列。`, c.id, true)
|
||||
if (!w.state.flags['firstSpirit']) {
|
||||
w.state.flags['firstSpirit'] = w.state.year
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { World } from '../World'
|
||||
import { worldSimOf } from '../World'
|
||||
import { Character } from '../../../types/domain'
|
||||
import { Cond, EffectDef, EventDef, EVENTS, MemberEffect } from '../../../data/events'
|
||||
import { MAJOR_ORDER } from '../../../data/realms'
|
||||
@@ -343,10 +344,11 @@ export function eventRoll(w: World): void {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 仙门拍卖(每年十月掷币)
|
||||
// 仙门拍卖(每年十月掷币;时代调制:盛世频仍/末法难逢)
|
||||
if (s.month === 10 && w.sysEnabled('production')) {
|
||||
const key = `auction-${s.year}`
|
||||
if (!s.family.flag[key] && w.rng.chance(0.55)) {
|
||||
const eraChance = w.state.worldSim ? Math.min(0.85, 0.55 * worldSimOf(w).eraAuctionMult()) : 0.55
|
||||
if (!s.family.flag[key] && w.rng.chance(eraChance)) {
|
||||
s.family.flag[key] = true
|
||||
fire(w, 'ev-auction')
|
||||
return
|
||||
@@ -557,6 +559,7 @@ export function applyEffect(w: World, eff: EffectDef, squad?: string[], _extra?:
|
||||
w.log('info', '拍卖会灵器已溢价转卖,回款150灵石。')
|
||||
}
|
||||
}
|
||||
w.emitFx('spark', 'auction')
|
||||
delete fam.flag['auctionWin']
|
||||
}
|
||||
if (eff.trib) {
|
||||
|
||||
@@ -79,6 +79,7 @@ export function runTournament(w: World, squad?: string[], formation?: FormationI
|
||||
const teamNames = team.map((c) => c.name).join('、')
|
||||
w.chronicle('battle', `太虚大比:${teamNames} 最终位列第${rank}名,赏灵石${rewards.stones}、声望+${rewards.rep}。`, undefined, true)
|
||||
w.log('good', `太虚大比落下帷幕,本族第${rank}名。`)
|
||||
w.emitFx(rank <= 3 ? 'spark' : 'ripple', 'tournament')
|
||||
}
|
||||
|
||||
export function apprenticeCandidates(w: World): Character[] {
|
||||
|
||||
@@ -47,6 +47,7 @@ export function resolveTribulation(w: World, c: Character, mode: 'rash' | 'guard
|
||||
if (w.rng.chance(p)) {
|
||||
c.realm = next
|
||||
c.realmProgress = 0
|
||||
w.emitFx('spark', `trib:${c.id}`)
|
||||
c.health = 100
|
||||
c.lastBreakthroughAttempt = s.year * 12 + s.month
|
||||
w.chronicle('breakthrough', `${c.name} 渡劫功成,踏入【${describeRealm(next)}】!`, c.id, true)
|
||||
|
||||
@@ -32,6 +32,9 @@ import { combatPowerOf } from './Systems/combat'
|
||||
|
||||
export type LogKind = LogItem['kind']
|
||||
|
||||
/** 特效出口(引擎语义发射;参数仅为语义锚点,随机散布归渲染层) */
|
||||
export type FxEmit = { kind: 'spark' | 'blade' | 'pulse' | 'ripple'; source?: string }
|
||||
|
||||
export interface WorldEventBus {
|
||||
onLog(kind: LogKind, text: string): void
|
||||
onChronicle(entry: ChronicleEntry, important: boolean): void
|
||||
@@ -41,6 +44,7 @@ export interface WorldEventBus {
|
||||
onYearPaper?(entry: YearlyReport): void
|
||||
onSystemChange?(id: string, enabled: boolean): void
|
||||
onPluginChange?(id: string, action: string): void
|
||||
onFx?(em: FxEmit): void
|
||||
}
|
||||
|
||||
import { WorldSim } from '../sim/WorldSim'
|
||||
@@ -455,6 +459,12 @@ export class World {
|
||||
npc.allied = true
|
||||
npc.alliedSinceYear = this.state.year
|
||||
npc.power = Math.min(900, npc.power + 10)
|
||||
// 1-3 扰动:盟约惹眼——邻家对此家略生嫌隙
|
||||
for (const dyn of Object.values(this.state.worldSim?.npcDyn ?? {})) {
|
||||
if (dyn && dyn.relationsWithOthers && npcId in dyn.relationsWithOthers) {
|
||||
dyn.relationsWithOthers[npcId] = Math.max(-100, (dyn.relationsWithOthers[npcId] ?? 0) - 3)
|
||||
}
|
||||
}
|
||||
this.log('good', `与${npc.name}结成同盟——盟誓既立,互不犯边。`)
|
||||
this.chronicle('diplomacy', `本族与${npc.name}缔结同盟。`, undefined, true)
|
||||
return true
|
||||
@@ -652,6 +662,12 @@ export class World {
|
||||
return true
|
||||
}
|
||||
|
||||
/** 语义特效发射(零随机消耗——渲染自由派生出随机分布) */
|
||||
emitFx(kind: 'spark' | 'blade' | 'pulse' | 'ripple', source?: string): void {
|
||||
const em: FxEmit = { kind, source }
|
||||
this.out.forEach((o) => o.onFx?.(em))
|
||||
}
|
||||
|
||||
postBonus(type: string): number {
|
||||
let sum = 0
|
||||
for (const c of this.aliveMembers()) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** WorldSim —— 世界自进化引擎(game/engine/sim/WorldSim.ts) */
|
||||
import { World } from '../runtime/World'
|
||||
import { WorldSimState, NpcDynamics, WORLDSIM, CALAMITY_EFFECT, CALAMITY_FAMILY, CalamityName, POOL_BASE } from './worldsim-data'
|
||||
import { WorldSimState, NpcDynamics, WORLDSIM, ERA_CONF, ERA_DURA, CALAMITY_EFFECT, CALAMITY_FAMILY, CalamityName, POOL_BASE } from './worldsim-data'
|
||||
import { pack } from '../../data/registry'
|
||||
import { ITEMS } from '../../data/items'
|
||||
import { npcById } from '../../data/npcs'
|
||||
@@ -30,23 +30,52 @@ export class WorldSim {
|
||||
s.tideTicks++
|
||||
const phase = (s.tideTicks % WORLDSIM.tideCycle) / WORLDSIM.tideCycle
|
||||
const sine = 0.5 + 0.5 * Math.sin(phase * Math.PI * 2)
|
||||
s.tide = WORLDSIM.tideMin + sine * (WORLDSIM.tideMax - WORLDSIM.tideMin)
|
||||
s.tide = Math.min(WORLDSIM.tideMax, WORLDSIM.tideMin + sine * (WORLDSIM.tideMax - WORLDSIM.tideMin) + ERA_CONF[s.era ?? 'pingshi'].tideBias)
|
||||
|
||||
// 秘境灵气(自动恢复 + 探索消耗由 missions 在探索时扣)
|
||||
// 秘境灵气(自动恢复 + 探索消耗由 missions 在探索时扣;灾年灵脉闭锁 0.5 恢复)
|
||||
const qiRecover = s.calamity ? WORLDSIM.secretRecover * 0.5 : s.tide > 0.85 ? WORLDSIM.secretRecoverHi : WORLDSIM.secretRecover
|
||||
for (const key of Object.keys(s.secretQi)) {
|
||||
s.secretQi[key] = clamp(s.secretQi[key] + (s.tide > 0.85 ? 5 : 2) - (s.secretQi[key] > 70 ? 2 : 0), 0, 100)
|
||||
s.secretQi[key] = clamp(s.secretQi[key] + qiRecover - (s.secretQi[key] > 70 ? 2 : 0), 0, 100)
|
||||
}
|
||||
|
||||
// ---- D0. 世纪弧转移(年首评估) ----
|
||||
if (this.w.state.month === 1) {
|
||||
const curEra = s.era ?? 'pingshi'
|
||||
const dur = ERA_DURA[curEra]
|
||||
const age = this.w.state.year - (s.eraStartYear ?? 1)
|
||||
if (age >= dur[0] && age < dur[1] + 5) {
|
||||
const flow = ERA_CONF[curEra].flow
|
||||
const roll = rng.next()
|
||||
let acc = 0
|
||||
for (const [next, wgt] of flow) {
|
||||
acc += wgt
|
||||
if (roll < acc) {
|
||||
s.era = next
|
||||
s.eraStartYear = this.w.state.year
|
||||
const conf = ERA_CONF[next]
|
||||
this.w.log('info', `【天下】世道更替——${conf.name}。${conf.desc}`)
|
||||
s.newsFeed.push({
|
||||
year: this.w.state.year, month: 1, src: '天道',
|
||||
text: `世道更替:${conf.name}来临。${conf.desc}`, kind: 'calamity'
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- D. 灾年(年签一掷 + 持续渐退;B12) ----
|
||||
// 剩数递减
|
||||
if ((s.calamityLeft ?? 0) > 0) s.calamityLeft = (s.calamityLeft ?? 0) - 1
|
||||
if (this.w.state.month === 1 && rng.chance(WORLDSIM.calamityChance)) {
|
||||
const calamityP = WORLDSIM.calamityChance * ERA_CONF[s.era ?? 'pingshi'].calamityMult
|
||||
if (this.w.state.month === 1 && rng.chance(calamityP)) {
|
||||
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.emitFx('pulse', `calamity:${cl}`)
|
||||
// B7 灾年落家族(一次性体感效果)
|
||||
if (cl === '疫病') {
|
||||
for (const c of this.w.aliveMembers()) {
|
||||
@@ -72,6 +101,7 @@ export class WorldSim {
|
||||
text: `灾云散尽,灵气复清,天下重归平宁。`, kind: 'calamity'
|
||||
})
|
||||
this.w.log('info', `【天下】灾云散尽,灵气复清。`)
|
||||
this.w.emitFx('ripple', 'calm')
|
||||
}
|
||||
|
||||
// ---- A. 资源循环市场(世界供给/需求 + NPC 上桌 + 再平衡) ----
|
||||
@@ -82,6 +112,11 @@ export class WorldSim {
|
||||
// ---- B. NPC 演化(换代 + 关系网 + 互攻;B7) ----
|
||||
evolveNpc(this.w, s, rng.next())
|
||||
|
||||
// ---- D2. 天下十年一鉴(史官综述) ----
|
||||
if (this.w.state.month === 1 && this.w.state.year % 10 === 0) {
|
||||
s.newsFeed.push({ year: this.w.state.year, month: 1, src: '史官', text: decadeChronicle(s, this.w.state.year), kind: 'annal' })
|
||||
}
|
||||
|
||||
// ---- E. 天下快讯(节流) ----
|
||||
if ((this.w.state.totalTicks % WORLDSIM.newsEvery) === 0) {
|
||||
pushNews(this.w, s, MARKET_IDS)
|
||||
@@ -133,6 +168,16 @@ export class WorldSim {
|
||||
return (s.marketPool[resKey] ?? base) / base
|
||||
}
|
||||
|
||||
/** 时代名(UI 与事件链读取) */
|
||||
era(): string {
|
||||
return ERA_CONF[(this.s().era ?? 'pingshi')].name
|
||||
}
|
||||
|
||||
/** 时代拍卖/大比加成(events 周期链调用) */
|
||||
eraAuctionMult(): number {
|
||||
return ERA_CONF[(this.s().era ?? 'pingshi')].auctionMult
|
||||
}
|
||||
|
||||
news(): WorldSimState['newsFeed'] {
|
||||
return this.s().newsFeed
|
||||
}
|
||||
@@ -150,6 +195,7 @@ function initSim(w: World): WorldSimState {
|
||||
const s = { ...empty() }
|
||||
for (const id of Object.keys(w.state.npcFamilies)) {
|
||||
s.npcDyn[id] = {
|
||||
prosperity: 50,
|
||||
leaderName: '新任宗主',
|
||||
leaderRealmIdx: MAJOR_ORDER.indexOf(npcById(id).leaderRealm),
|
||||
leaderAge: 40 + w.rng.int(0, 29),
|
||||
@@ -180,13 +226,16 @@ function empty(): WorldSimState {
|
||||
/** A2+A3:世界常驻供给与需求(潮汐乘化)——池有了呼吸 */
|
||||
function worldBreath(w: World, s: WorldSimState): void {
|
||||
const tide = s.tide
|
||||
const era = ERA_CONF[s.era ?? 'pingshi']
|
||||
const span = 1 - (tide - 1) * WORLDSIM.tideSupplySpan
|
||||
const supplySpan = Math.max(0.75, Math.min(1.35, span))
|
||||
const eraSupply = era.supplyMult
|
||||
const eraDemand = era.demandMult
|
||||
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
|
||||
const supply = base * WORLDSIM.worldSupplyRate * supplySpan * eraSupply
|
||||
const demand = base * WORLDSIM.worldDemandRate * eraDemand
|
||||
s.marketPool[id] = clamp(cur + supply - demand, base * 0.12, base * WORLDSIM.tradeCeilPct)
|
||||
}
|
||||
void w
|
||||
@@ -198,28 +247,39 @@ function npcTrade(w: World, s: WorldSimState, noise: number): void {
|
||||
for (const [id, npc] of Object.entries(w.state.npcFamilies)) {
|
||||
const per = n; n += 0.31
|
||||
const def = npcById(id)
|
||||
const dyn = s.npcDyn[id] ?? initDynFor(id)
|
||||
s.npcDyn[id] = dyn
|
||||
const isCalamity = !!s.calamity
|
||||
const rate = WORLDSIM.npcTradeRate * (isCalamity ? 0.7 : 1)
|
||||
// 卖:NPC 抛货 → 池增(量小到可忽略贸易盈亏,只表潮流)
|
||||
// 景气驱动(1-2):入不敷出则衰、仓廪常足则旺
|
||||
let prosperity = dyn.prosperity ?? 50
|
||||
// 卖: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)
|
||||
prosperity += 0.5
|
||||
}
|
||||
// 买:NPC 采购 → 池减;池太浅采不到 → NPC 繁荣受挫(power 微跌)
|
||||
// 买:NPC 采购 → 池减;池太浅采不到 → 断供受挫(景气大伤)
|
||||
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))
|
||||
prosperity -= 2
|
||||
} else {
|
||||
s.marketPool[itemId] = cur - vol
|
||||
npc.power = Math.min(900, npc.power + 0.5)
|
||||
prosperity += 0.8
|
||||
}
|
||||
}
|
||||
// 灾年支出(共苦):景气再挫,若已困顿伤其筋骨
|
||||
if (isCalamity) prosperity -= 3
|
||||
dyn.prosperity = clamp(prosperity, 8, 100)
|
||||
// 景气→power 联动:旺者增益、困者式微
|
||||
if (dyn.prosperity > 75) npc.power = Math.min(900, npc.power + 1)
|
||||
else if (dyn.prosperity < 30) npc.power = Math.max(42, npc.power - 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,12 +348,14 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void {
|
||||
dyn.leaderAge = 30 + w.rng.int(0, 25)
|
||||
dyn.leaderRealmIdx = Math.min(dyn.leaderRealmIdx + 1, 5)
|
||||
dyn.leaderName = `${def.name.replace('氏', '')}氏新主`
|
||||
dyn.prosperity = 55 + w.rng.int(0, 20)
|
||||
npc.power = Math.round(Math.max(40, npc.power + 15))
|
||||
dyn.lastEvent = '宗祧更替'
|
||||
dyn.lastEventYear = year
|
||||
s.npcSuccessions++
|
||||
pushNews(w, s, [id])
|
||||
w.log('info', `【天下】${def.name} 更易宗主,气象一新。`)
|
||||
w.emitFx('ripple', `succession:${id}`)
|
||||
}
|
||||
// B7 互攻:与世仇(关系<-50)年首相搏——败方伤筋动骨
|
||||
for (const oid of ids) {
|
||||
@@ -302,10 +364,21 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void {
|
||||
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
|
||||
// 1-3 蝴蝶效应:互攻按实力加权(强者越可能胜,弱者一败再败)
|
||||
const wSum = npc.power + foe.power
|
||||
const winner = w.rng.chance(wSum > 0 ? npc.power / wSum : 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))
|
||||
// 打残广播:天下敢弱必胜之——邻家对败者关系趋冷、对胜者暗生敬畏
|
||||
for (const [oid, otherDyn] of Object.entries(s.npcDyn)) {
|
||||
if (oid === loser.id) continue
|
||||
const rr = otherDyn.relationsWithOthers[loser.id] ?? 0
|
||||
otherDyn.relationsWithOthers[loser.id] = clamp(rr - 8, -100, 100)
|
||||
if (oid === winner.id) continue
|
||||
const wr = otherDyn.relationsWithOthers[winner.id] ?? 0
|
||||
otherDyn.relationsWithOthers[winner.id] = clamp(wr + 4, -100, 100)
|
||||
}
|
||||
s.newsFeed.push({
|
||||
year, month: 1, src: winner.name,
|
||||
text: `${winner.name} 与 ${loser.name} 起衅——痛挫其锋,势力大动。`
|
||||
@@ -321,6 +394,7 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void {
|
||||
function initDynFor(id: string): NpcDynamics {
|
||||
const def = npcById(id)
|
||||
return {
|
||||
prosperity: 50,
|
||||
leaderName: `${def.name.replace('氏', '')}氏宗主`,
|
||||
leaderRealmIdx: MAJOR_ORDER.indexOf(def.leaderRealm),
|
||||
leaderAge: 45,
|
||||
@@ -335,7 +409,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; itemId?: string; kind?: 'quote' | 'npc' | 'calamity' }
|
||||
let row: { year: number; month: number; src: string; text: string; itemId?: string; kind?: 'quote' | 'npc' | 'calamity' | 'annal' }
|
||||
if (id.startsWith('n-')) {
|
||||
const def = npcById(id)
|
||||
const dyn = s.npcDyn[id]
|
||||
@@ -364,6 +438,24 @@ function pushNews(w: World, s: WorldSimState, about: string[]): void {
|
||||
s.lastNewsMonth = w.state.month
|
||||
}
|
||||
|
||||
/** 十年一鉴:从近十年快讯聚合综述(史官体) */
|
||||
function decadeChronicle(s: WorldSimState, year: number): string {
|
||||
const from = year - 9
|
||||
const rows = s.newsFeed.filter((r) => r.year >= from && r.year < year)
|
||||
const calamities = rows.filter((r) => r.kind === 'calamity' && r.text.includes('灾')).length
|
||||
const successions = rows.filter((r) => r.kind === 'npc').length
|
||||
const quotes = rows.filter((r) => r.kind === 'quote').length
|
||||
const era = ERA_CONF[s.era ?? 'pingshi'].name
|
||||
const tideAvg = s.tide
|
||||
const parts: string[] = []
|
||||
parts.push(`${from}年~${year - 1}年,天下${era}之期`)
|
||||
if (calamities) parts.push(`灾年${calamities}现`)
|
||||
if (successions) parts.push(`宗祧更替${successions}家`)
|
||||
if (quotes) parts.push(`行情起落${quotes}波`)
|
||||
parts.push(`灵潮${tideAvg > 1 ? '善' : tideAvg < 0.8 ? '劣' : '平'}`)
|
||||
return `史官曰:${parts.join(',')}。`
|
||||
}
|
||||
|
||||
function clamp(v: number, lo: number, hi: number): number {
|
||||
return Math.max(lo, Math.min(hi, v))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export interface NpcDynamics {
|
||||
/** 景气指数(0~100):月结余/断供受挫/灾年支出;驱动 power 微调 */
|
||||
prosperity: number
|
||||
/** 宗主姓名快照(换代时更新) */
|
||||
leaderName: string
|
||||
leaderRealmIdx: number
|
||||
@@ -31,10 +33,13 @@ export interface WorldSimState {
|
||||
/** 灾年剩余月数(递减,归 0 时散去) */
|
||||
calamityLeft?: number
|
||||
/** 天下快讯(滚动 N=120;itemId/kind 供 UI 响应按钮与区分) */
|
||||
newsFeed: { year: number; month: number; src: string; text: string; itemId?: string; kind?: 'quote' | 'npc' | 'calamity' }[]
|
||||
newsFeed: { year: number; month: number; src: string; text: string; itemId?: string; kind?: 'quote' | 'npc' | 'calamity' | 'annal' }[]
|
||||
lastNewsMonth: number
|
||||
/** NPC 换代计数 */
|
||||
npcSuccessions: number
|
||||
/** 世纪弧(时代状态机) */
|
||||
era?: 'shengshi' | 'pingshi' | 'luanshi' | 'mofa'
|
||||
eraStartYear?: number
|
||||
}
|
||||
|
||||
export function makeWorldSimState(): WorldSimState {
|
||||
@@ -49,7 +54,9 @@ export function makeWorldSimState(): WorldSimState {
|
||||
calamityLeft: 0,
|
||||
newsFeed: [],
|
||||
lastNewsMonth: -99,
|
||||
npcSuccessions: 0
|
||||
npcSuccessions: 0,
|
||||
era: 'pingshi',
|
||||
eraStartYear: 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +69,32 @@ export const POOL_BASE: Record<string, number> = {
|
||||
'pill-ningyuan': 40
|
||||
}
|
||||
|
||||
/** 世纪弧:时代 → 世界调制量(0.1.18 大周期) */
|
||||
export const ERA_CONF: Record<'shengshi' | 'pingshi' | 'luanshi' | 'mofa', {
|
||||
name: string
|
||||
desc: string
|
||||
calamityMult: number
|
||||
supplyMult: number
|
||||
demandMult: number
|
||||
auctionMult: number
|
||||
tideBias: number
|
||||
/** 转移概势表:[下一态, 权重] */
|
||||
flow: Array<['shengshi' | 'pingshi' | 'luanshi' | 'mofa', number]>
|
||||
}> = {
|
||||
shengshi: { name: '盛世', desc: '灵气鼎盛,百业兴旺,天下升平。', calamityMult: 0.5, supplyMult: 1.25, demandMult: 1.15, auctionMult: 1.3, tideBias: 0.08, flow: [['pingshi', 0.7], ['luanshi', 0.3]] },
|
||||
pingshi: { name: '平世', desc: '四海无波,仙凡相安。', calamityMult: 1.0, supplyMult: 1.0, demandMult: 1.0, auctionMult: 1.0, tideBias: 0, flow: [['shengshi', 0.35], ['luanshi', 0.45], ['mofa', 0.2]] },
|
||||
luanshi: { name: '乱世', desc: '群雄相逐,烽烟四起,灾祸连年。', calamityMult: 1.6, supplyMult: 0.85, demandMult: 1.05, auctionMult: 0.75, tideBias: -0.04, flow: [['pingshi', 0.4], ['mofa', 0.6]] },
|
||||
mofa: { name: '末法', desc: '灵机衰微,大能隐迹,仙路将绝。', calamityMult: 1.2, supplyMult: 0.7, demandMult: 0.85, auctionMult: 0.6, tideBias: -0.09, flow: [['pingshi', 0.6], ['shengshi', 0.4]] }
|
||||
}
|
||||
|
||||
/** era 持续年数区间(转移时机) */
|
||||
export const ERA_DURA: Record<'shengshi' | 'pingshi' | 'luanshi' | 'mofa', [number, number]> = {
|
||||
shengshi: [25, 45],
|
||||
pingshi: [20, 40],
|
||||
luanshi: [15, 30],
|
||||
mofa: [20, 40]
|
||||
}
|
||||
|
||||
/** 世界演化参数表(全部可调) */
|
||||
export const WORLDSIM = {
|
||||
marketDriftRate: 0.03,
|
||||
|
||||
Reference in New Issue
Block a user