export type NpcStance = 'guardian' | 'expand' | 'endure' | 'ally' export interface NpcDynamics { /** 景气指数(0~100):月结余/断供受挫/灾年支出;驱动 power 微调 */ prosperity: number /** 战略姿态:守成/扩张/隐忍/结盟(年首评估) */ stance: NpcStance stanceSinceYear: number /** 衰微连续年数(power<58 起计) */ declineYears?: number /** 互攻败仗累计(0.1.27 残衰减判定) */ warLosses?: number /** 宗主姓名快照(换代时更新) */ leaderName: string leaderRealmIdx: number leaderAge: number /** 演化旗标 */ lastEvent: string lastEventYear: number relationsWithOthers: Record } export interface SecretQi { qi: number peak: number } export interface WorldSimState { /** 世界库存池(资源循环) */ marketPool: Record /** NPC 动力学 */ npcDyn: Record /** 秘境灵气条 */ secretQi: Record /** 灵气潮汐(0-100 全局系数) */ tide: number tideTicks: number /** 灾年(当前灾因 id;持续 calamityMonths 个月) */ calamity?: string calamityYear: number /** 灾年剩余月数(递减,归 0 时散去) */ calamityLeft?: number /** 天下快讯(滚动 N=120;itemId/kind 供 UI 响应按钮与区分) */ 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 /** 超卖潮播报年份(谷贱伤农,一年一次) */ overfluxYear?: number /** 盟友求援(最近一次:互攻失利波及盟我之族) */ distress?: { id: string; year: number; month: number } /** 战争疲劳(0~1):NPC 互攻/玩家 raid 逐年累积——世界温度上浮,乱世加速 */ warFatigue?: number /** 断供监测:货源id→连续低于警戒线月数(≥3 触发断供告警×1.5 价) */ shortage?: Record /** 断供已播年(id→年——仅始发广播,持续断供不刷屏) */ shortageWarned?: Record /** 0.1.34 新秘境定义(世界状态内——每档独立,绝无全局跨世界污染) */ wonders?: Record /** 天下史书(十年一鉴独立留档,免 newsFeed 裁剪;上限 80 页) */ worldAnnals?: { year: number; text: string }[] /** 世鉴(50/100 年宏观:开局 vs 现在/era 路径/最强家更替——独立数组零漂) */ sagaAnnals?: { year: number; kind: 'quinquagenary' | 'centennial'; text: string }[] /** 0.1.37 世界奇观(当前活跃奇观;年首种子派生判定——零 rng 消耗) */ wonder?: WorldWonder } export function makeWorldSimState(): WorldSimState { return { marketPool: { lingcao: 600, lingkuang: 300, beastcore: 80, 'pill-qiyuan': 90, 'pill-ningyuan': 40 }, npcDyn: {}, secretQi: {}, tide: 0.5, tideTicks: 0, calamityYear: -99, calamityLeft: 0, newsFeed: [], lastNewsMonth: -99, npcSuccessions: 0, era: 'pingshi', eraStartYear: 1, overfluxYear: -99, worldAnnals: [], warFatigue: 0, shortage: {}, shortageWarned: {}, wonders: {} } } /** 市场基准库存(单一权威:WorldSim 再平衡 / Market 行情 / brief 图标 三方共用) */ export const POOL_BASE: Record = { lingcao: 600, lingkuang: 300, beastcore: 80, lingyu: 120, lingmu: 220, shoupi: 160, lingguo: 260, 'pill-qiyuan': 90, '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.45], ['luanshi', 0.15]] }, pingshi: { name: '平世', desc: '四海无波,仙凡相安。', calamityMult: 1.0, supplyMult: 1.0, demandMult: 1.0, auctionMult: 1.0, tideBias: 0, flow: [['shengshi', 0.22], ['luanshi', 0.28], ['mofa', 0.1]] }, luanshi: { name: '乱世', desc: '群雄相逐,烽烟四起,灾祸连年。', calamityMult: 1.6, supplyMult: 0.85, demandMult: 1.05, auctionMult: 0.75, tideBias: -0.04, flow: [['pingshi', 0.25], ['mofa', 0.35]] }, mofa: { name: '末法', desc: '灵机衰微,大能隐迹,仙路将绝。', calamityMult: 1.2, supplyMult: 0.7, demandMult: 0.85, auctionMult: 0.6, tideBias: -0.09, flow: [['pingshi', 0.4], ['shengshi', 0.2]] } } const ERA_DURA_OVERRIDES = new Map() export function overrideEraDura(era: string, dur: [number, number]): void { if (era in ERA_DURA) ERA_DURA_OVERRIDES.set(era, dur) } export function resetEraDura(era?: string): void { if (era) ERA_DURA_OVERRIDES.delete(era) else ERA_DURA_OVERRIDES.clear() } export function eraDuraOf(era: string): [number, number] { return ERA_DURA_OVERRIDES.get(era) ?? ERA_DURA[era as keyof typeof ERA_DURA] ?? [20, 40] } /** era 持续年数区间(转移时机) */ export const ERA_DURA: Record<'shengshi' | 'pingshi' | 'luanshi' | 'mofa', [number, number]> = { shengshi: [25, 45], pingshi: [20, 40], luanshi: [15, 30], mofa: [20, 40] } const REGION_TIDE_OVERRIDES = new Map>() export function overrideRegionTide(npcId: string, byEra: Record): void { REGION_TIDE_OVERRIDES.set(npcId, byEra) } export function resetRegionTide(npcId?: string): void { if (npcId) REGION_TIDE_OVERRIDES.delete(npcId) else REGION_TIDE_OVERRIDES.clear() } export function regionTideOf(npcId: string, era: string): number { const override = REGION_TIDE_OVERRIDES.get(npcId)?.[era] if (override !== undefined) return override const base = REGION_TIDE[npcId] if (!base) return 0 return base[era as keyof typeof base] ?? 0 } /** 区域灵势(NPC id × era → 修正量 +-0.06~0.1):乱世北岳凶、盛世东丘旺…… */ export const REGION_TIDE: Record> = { 'n-xuanying': { shengshi: 0.06, pingshi: 0, luanshi: -0.08, mofa: -0.06 }, 'n-danxin': { shengshi: 0.04, pingshi: 0, luanshi: -0.04, mofa: -0.02 }, 'n-sihai': { shengshi: 0.05, pingshi: 0.01, luanshi: -0.06, mofa: -0.04 }, 'n-nulei': { shengshi: 0.02, pingshi: 0, luanshi: 0.05, mofa: -0.03 } } /** 0.1.27 世界数值 MOD 口:允许子集覆写(默认表不变→零漂;任意时刻可恢复) */ const WORLD_OVERRIDES: Record = {} export function overrideWorldNum(key: string, value: number): boolean { if (key in WORLDSIM) { WORLD_OVERRIDES[key] = value return true } return false } export function resetWorldNum(key?: string): void { if (key) delete WORLD_OVERRIDES[key] else for (const k of Object.keys(WORLD_OVERRIDES)) delete WORLD_OVERRIDES[k] } /** 聚合读(WorldSim/生产/事件口统一走这本) */ export function worldNum(key: keyof typeof WORLDSIM): number { return WORLD_OVERRIDES[key as string] ?? ((WORLDSIM as unknown as Record)[key as string] ?? 0) } /** 世界演化参数表(全部可调) */ export const WORLDSIM = { marketDriftRate: 0.03, marketRebalance: 0.025, // 0.1.20 弱锚定:让供需/era/灾年真实驱动价格(0.1.19 为 0.1 硬锚死) priceFloor: 0.55, priceCeil: 2.3, // —— 0.1.17 真库存循环 —— worldSupplyRate: 0.02, // 世界侧月供给(base 比例;潮涨时 ×1.3) worldDemandRate: 0.015, // 世界侧月需求(坊市/宗门常驻消耗) npcTradeRate: 0.008, // 每 NPC 月贸易量(base 比例;上桌食量) tradeFloorPct: 0.35, // 拒单线:池低于此比例时玩家买入拒单(断供告急) poolFloorPct: 0.25, // driftMarket 池底(0.1.35 调低——地板与拒单线分离,断供期间真买不到料) tradeCeilPct: 2.4, // 池上限(玩家大量卖出后价格封顶) tideSupplySpan: 0.3, // 潮汐对供给的浮动幅度(tide±0.35 时 ×(1∓0.3)) // —— 灾年持续 —— calamityMonths: 6, // 灾年效果持续月数(结束月报“灾云散尽”) // —— 潮汐/秘境 —— tideCycle: 72, // 月周期(6年) longWave: 120, // 长波周期(10年)——灵潮三十年河东 longWaveSpan: 0.08, // 长波振幅(±0.08 叠加) tideMin: 0.65, tideMax: 1.35, secretRecover: 2, // 灵气月恢复(潮高 ×2.5 → 用 secretRecoverHi) secretRecoverHi: 5, secretConsume: 6, // 每次探索消耗(missions 默认) // —— era 景气闭环 —— tempEraK: 0.8, // 世界温度对 era 转移权重的调制强度(±0.8/单位温差) // —— 新贵补位 —— greatNewbornChance: 0.06, // 年首新贵出现率(乱世 ×2;上限 4 家) // —— 盟约连坐(加性) —— allianceGriefRaid: 0.012, // 每 世仇盟连加性 raid 概率 // —— 天下事件 —— calamityChance: 0.18, calamities: ['旱灾', '涝灾', '蝗灾', '疫病', '兽潮', '寒潮'] as const, npcEventChance: 0.05, // NPC 互攻年首诗签概率 newsEvery: 24, // 月 newsKeep: 120, // —— 0.1.34 万象归元(战争疲劳/断供/新秘境) —— warFatigueRise: 0.008, // 每次乱象(NPC 互攻)累积的战争疲劳 warFatigueTempK: 0.6, // 疲劳对世界温度的冷降幅度(打不动即荒年) shortageLine: 0.7, // 断供警戒线:池/基准 < 此值起计连续月 shortageMonths: 3, shortagePrice: 1.5, // 断供价乘 newSecretChance: 0.04, // 年首新秘境现身概率(乱世 ×2) // —— 0.1.37 因果/奇观 —— wonderChance: 0.08, // 世界奇观年首现身概率(种子派生——零 rng 消耗) karmaDecayRate: 0.1, // 功德年首衰减率(向中性靠拢——善行需持续) karmaHighThreshold: 30, // 功德深厚阈值(触发灾年 -15%/渡劫 +5%) karmaLowThreshold: -20 // 功德亏缺阈值(触发灾年 +15%/渡劫 -5%) } export type CalamityName = string // ==================== 0.1.37 世界奇观 ==================== /** 世界奇观类型(年首种子派生判定——零 rng 消耗) */ export type WorldWonderKind = 'tide_surge' | 'ancient_ruin' | 'heaven_bless' | 'qi_wane' | string export interface WorldWonder { kind: WorldWonderKind year: number /** 持续月数(灵气涌潮/灵脉枯竭有持续效果;古遗迹/天降祥瑞一次性) */ duration?: number /** 已过月数 */ elapsed?: number } /** 世界奇观年首现身概率(聚合读口——MOD 可经 worldNum('wonderChance') 覆写) */ export const WONDER_CHANCE = 0.08 /** 奇观名称映射 */ export const WONDER_NAMES: Record = { tide_surge: '灵气涌潮', ancient_ruin: '古遗迹现身', heaven_bless: '天降祥瑞', qi_wane: '灵脉枯竭' } /** 奇观描述 */ export const WONDER_DESCS: Record = { tide_surge: '天地灵气如潮涌至,万灵共鸣——修炼事半功倍。', ancient_ruin: '远古修士洞府现世——其中或藏功法丹材之珍。', heaven_bless: '祥瑞降世——风调雨顺,灾厄消弭。', qi_wane: '灵脉逆行——天地灵气骤衰,修行维艰。' } /** 奇观持续月数(灵气涌潮/灵脉枯竭持续 12 月;古遗迹/天降祥瑞一次性) */ export const WONDER_DURATION: Record = { tide_surge: 12, ancient_ruin: 0, heaven_bless: 0, qi_wane: 12 } /** 默认灾因集(聚合读口:默认+MOD/插件扩展——默认表和不变 → 基准世界指纹不漂) */ export const DEFAULT_CALAMITIES = ['旱灾', '涝灾', '蝗灾', '疫病', '兽潮', '寒潮'] as const /** 灾因 → 家族实际生产乘量(1 = 正常) */ export const CALAMITY_FAMILY: Record>> = { 旱灾: { lingcao: 0.7 }, 涝灾: { lingcao: 0.8, lingkuang: 0.95 }, 蝗灾: { lingcao: 0.55 }, 疫病: {}, 兽潮: { beastcore: 1.4 }, 寒潮: { lingcao: 0.85, lingkuang: 0.75 } } /** 灾因 → 资源方向 */ export const CALAMITY_EFFECT: Record>> = { 旱灾: { lingcao: -0.3 }, 涝灾: { lingcao: -0.2, lingkuang: -0.1 }, 蝗灾: { lingcao: -0.45 }, 疫病: { beastcore: -0.25 }, 兽潮: { beastcore: 0.3 }, 寒潮: { lingcao: -0.2, beastcore: -0.15 } } /** 扩展灾因注册表(MOD/插件 addCalamity)——名字唯一;与默认集聚合 */ const EXTENDED_CALAMITIES: string[] = [] const EXTENDED_FAMILY: Record>> = {} const EXTENDED_EFFECT: Record>> = {} const EXTENDED_BRIEF: Record = {} export function addCalamity( name: string, opts: { brief?: string; family?: Partial>; effect?: Partial> } ): boolean { if (DEFAULT_CALAMITIES.includes(name as never) || EXTENDED_CALAMITIES.includes(name)) return false EXTENDED_CALAMITIES.push(name) if (opts.brief) EXTENDED_BRIEF[name] = opts.brief if (opts.family) EXTENDED_FAMILY[name] = opts.family if (opts.effect) EXTENDED_EFFECT[name] = opts.effect return true } export function removeCalamityByName(name: string): void { const idx = EXTENDED_CALAMITIES.indexOf(name) if (idx >= 0) EXTENDED_CALAMITIES.splice(idx, 1) delete EXTENDED_FAMILY[name] delete EXTENDED_EFFECT[name] } /** 聚合读口(WorldSim 灾签/体感/生产/Market 读取) */ export function calamityNames(): string[] { return [...DEFAULT_CALAMITIES, ...EXTENDED_CALAMITIES] } export function calamityFamilyOf(name: string): Partial> { return EXTENDED_FAMILY[name] ?? CALAMITY_FAMILY[name] ?? {} } export function calamityEffectOf(name: string): Partial> { return EXTENDED_EFFECT[name] ?? CALAMITY_EFFECT[name] ?? {} } /** 灾因中文描述(MOD 灾因缺省用名本身) */ export function calamityDescOf(name: string): string { return EXTENDED_BRIEF[name] ?? `${name}——灵植减产,市价将行。` } // ==================== 0.1.37 奇观类型扩展注册表(MOD 口) ==================== /** 扩展奇观类型定义(MOD 可注入自定义奇观——零 rng 消耗,种子派生选取) */ export interface ExtendedWonderDef { /** 奇观类型 id(唯一——与内置不冲突) */ kind: string /** 中文名称 */ name: string /** 描述文本 */ desc: string /** 持续月数(0 = 一次性) */ duration: number /** 持续效果类型:tide_up = 灵气增益 / tide_down = 灵气衰减 / none = 无持续效果 */ effect: 'tide_up' | 'tide_down' | 'none' /** 一次性效果:bless = 灾年消弭+声望 / loot = 资源获取 / none = 无 */ oneshot: 'bless' | 'loot' | 'none' } const EXTENDED_WONDERS: ExtendedWonderDef[] = [] /** 内置奇观定义(供聚合读口统一访问) */ const BUILTIN_WONDERS: ExtendedWonderDef[] = [ { kind: 'tide_surge', name: '灵气涌潮', desc: '天地灵气如潮涌至,万灵共鸣——修炼事半功倍。', duration: 12, effect: 'tide_up', oneshot: 'none' }, { kind: 'ancient_ruin', name: '古遗迹现身', desc: '远古修士洞府现世——其中或藏功法丹材之珍。', duration: 0, effect: 'none', oneshot: 'loot' }, { kind: 'heaven_bless', name: '天降祥瑞', desc: '祥瑞降世——风调雨顺,灾厄消弭。', duration: 0, effect: 'none', oneshot: 'bless' }, { kind: 'qi_wane', name: '灵脉枯竭', desc: '灵脉逆行——天地灵气骤衰,修行维艰。', duration: 12, effect: 'tide_down', oneshot: 'none' } ] /** MOD/插件注册奇观类型(kind 唯一——与内置+已注册不冲突) */ export function addWonderType(def: Omit & Partial>): boolean { if (!def.kind || BUILTIN_WONDERS.some((w) => w.kind === def.kind) || EXTENDED_WONDERS.some((w) => w.kind === def.kind)) return false EXTENDED_WONDERS.push({ kind: def.kind, name: def.name ?? '异变', desc: def.desc ?? '天地异变,灵机暗涌。', duration: def.duration ?? 0, effect: def.effect ?? 'none', oneshot: def.oneshot ?? 'none' }) return true } /** 卸载时按 kind 摘除扩展奇观 */ export function removeWonderType(kind: string): void { const idx = EXTENDED_WONDERS.findIndex((w) => w.kind === kind) if (idx >= 0) EXTENDED_WONDERS.splice(idx, 1) } /** 聚合读口:全部奇观类型(内置 + 扩展) */ export function allWonderKinds(): ExtendedWonderDef[] { return [...BUILTIN_WONDERS, ...EXTENDED_WONDERS] } /** 聚合读口:按 kind 查奇观定义 */ export function wonderDefOf(kind: string): ExtendedWonderDef | undefined { return BUILTIN_WONDERS.find((w) => w.kind === kind) ?? EXTENDED_WONDERS.find((w) => w.kind === kind) }