diff --git a/package.json b/package.json index ef8b4c7..9d44e97 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "chronicle-of-the-immortal-clan", "productName": "仙途家族志", - "version": "0.1.18", + "version": "0.1.19", "description": "修仙 · 家族 · 经营 · 战斗 模拟器", "main": "./out/main/index.js", "author": "MetonaTeam", diff --git a/src/renderer/game/engine/kernel/fxqueue.ts b/src/renderer/game/engine/kernel/fxqueue.ts deleted file mode 100644 index fe1629b..0000000 --- a/src/renderer/game/engine/kernel/fxqueue.ts +++ /dev/null @@ -1,72 +0,0 @@ -export type FxKind = 'spark' | 'mist' | 'ripple' | 'blade' | 'pulse' | 'seasonShift' | 'drift' - -export type FxMode = 'soft' | 'std' | 'full' - -export interface FxEmit { - kind: FxKind - payload?: Record - at: number -} - -export interface FxBus { - emit(e: FxEmit): void -} - -/** 竞态安全发射闸:单循环、队列不覆盖、档位过滤、reduced-motion 降级 */ -export class FxGate { - private queue: FxEmit[] = [] - private draining = false - mode: FxMode = 'std' - preferReduced = false - - constructor(private bus: FxBus) {} - - setMode(m: FxMode): void { - this.mode = m - } - - setReduced(r: boolean): void { - this.preferReduced = r - } - - allowed(kind: FxKind): boolean { - if (this.mode === 'soft') return kind === 'ripple' || kind === 'pulse' || kind === 'drift' - if (this.mode === 'full') return true - return kind !== 'blade' // std:火花/雾/涟漪/脉动,刀光仅 full - } - - emit(kind: FxKind, payload?: FxEmit['payload']): void { - if (!this.allowed(kind) || this.preferReduced || this.draining) return - this.queue.push({ kind, payload, at: Date.now() }) - this.drain() - } - - private drain(): void { - if (this.draining) return - this.draining = true - // 单帧集中派发(不逐个 setTimeout,防止栈深+竞态覆盖) - const batch = this.queue - this.queue = [] - try { - for (const e of batch) this.bus.emit(e) - } finally { - this.draining = false - } - } - - pending(): number { - return this.queue.length + (this.draining ? 1 : 0) - } - - /** 纯逻辑:资源差量(防负值与 NaN,供漂字用) */ - static deltas(prev: Record, next: Record): Record { - const out: Record = {} - const keys = new Set([...Object.keys(prev), ...Object.keys(next)]) - for (const k of keys) { - const a = prev[k] ?? 0 - const b = next[k] ?? 0 - if (Number.isFinite(a) && Number.isFinite(b) && b !== a) out[k] = b - a - } - return out - } -} diff --git a/src/renderer/game/engine/kernel/timesense.ts b/src/renderer/game/engine/kernel/timesense.ts index f015518..f1add60 100644 --- a/src/renderer/game/engine/kernel/timesense.ts +++ b/src/renderer/game/engine/kernel/timesense.ts @@ -7,12 +7,6 @@ export const TIDES: string[] = [ '立冬', '小雪', '大雪', '冬至', '小寒', '大寒' ] -/** 季节 → 节气(每月两个节气:tideOf(month, phase) 返回 0..1 ) */ -export function tideOf(month: number, phase: 0 | 1): string { - const idx = Math.min(23, (month - 1) * 2 + phase) - return TIDES[idx] ?? '立春' -} - /** 年轮进度(0..1)——用于顶栏年环弧段 */ export function yearRing(month: number): number { return (month - 1) / 12 @@ -46,6 +40,3 @@ export function seasonCssVars(season: Season): Record { } } -export function tintLabel(season: Season): string { - return TINTS[season].name -} diff --git a/src/renderer/game/engine/runtime/ApiFacade.ts b/src/renderer/game/engine/runtime/ApiFacade.ts index 893e5d9..41f08d3 100644 --- a/src/renderer/game/engine/runtime/ApiFacade.ts +++ b/src/renderer/game/engine/runtime/ApiFacade.ts @@ -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.18', + version: '0.1.19', modules: this.world.systemList().length, systems: this.world.systemList().filter((s) => s.enabled).length, plugins: this.world.pluginList().length, diff --git a/src/renderer/game/engine/runtime/Systems/diplomacy.ts b/src/renderer/game/engine/runtime/Systems/diplomacy.ts index ece8d6f..1510b22 100644 --- a/src/renderer/game/engine/runtime/Systems/diplomacy.ts +++ b/src/renderer/game/engine/runtime/Systems/diplomacy.ts @@ -12,7 +12,17 @@ export function diplomacyTick(w: World): void { } if (npc.relation < -50) { const last = (w.state.family.flag[`raidCD-${npc.id}`] as number | undefined) ?? 0 - if (s.year - last >= 2 && w.rng.chance(0.03)) { + // 姿态调制:扩张好勇、隐忍避战、结盟之势休兵 + const stance = (w.state.worldSim?.npcDyn?.[npc.id] as { stance?: string } | undefined)?.stance + let raidMult = stance === 'expand' ? 2 : stance === 'endure' ? 0.4 : stance === 'ally' ? 0 : 1 + // 0.1.19 盟约连坐:与我结盟各家的世仇,更易扣边 + const dyns = (w.state.worldSim?.npcDyn ?? {}) as Record }> + for (const ally of Object.values(s.npcFamilies)) { + if (!ally.allied) continue + const rel = dyns[ally.id]?.relationsWithOthers?.[npc.id] ?? 0 + if (rel < -60) raidMult *= 2 + } + if (s.year - last >= 2 && w.rng.chance(0.03 * raidMult)) { fire(w, `ev-raid-${npc.id}`) } } @@ -47,7 +57,10 @@ export function giftNpc(w: World, npcId: string, stones: number): boolean { if (stones <= 0 || fam.stones < stones) return false fam.stones -= stones const npc = w.state.npcFamilies[npcId] - const gain = calcGiftGain(stones) + let gain = calcGiftGain(stones) + // 结盟亲善势:报之以加倍情面 + const stance = (w.state.worldSim?.npcDyn?.[npcId] as { stance?: string } | undefined)?.stance + if (stance === 'ally') gain = Math.round(gain * 1.3) npc.relation = Math.min(100, npc.relation + gain) npc.power = Math.min(900, npc.power + gain * 0.4) // 回礼:盛情难却,或赠灵石或赠灵草 diff --git a/src/renderer/game/engine/runtime/World.ts b/src/renderer/game/engine/runtime/World.ts index 9c235db..7477955 100644 --- a/src/renderer/game/engine/runtime/World.ts +++ b/src/renderer/game/engine/runtime/World.ts @@ -89,6 +89,18 @@ export function normalizeGameState(state: GameState): GameState { state.worldSim.secretQi = state.worldSim.secretQi ?? {} state.worldSim.newsFeed = state.worldSim.newsFeed ?? [] if (typeof state.worldSim.tide !== 'number') state.worldSim.tide = 0.5 + if (typeof state.worldSim.tideTicks !== 'number') state.worldSim.tideTicks = 0 + if (typeof state.worldSim.npcSuccessions !== 'number') state.worldSim.npcSuccessions = 0 + if (typeof state.worldSim.calamityLeft !== 'number') state.worldSim.calamityLeft = 0 + if (state.worldSim.era !== 'shengshi' && state.worldSim.era !== 'pingshi' && state.worldSim.era !== 'luanshi' && state.worldSim.era !== 'mofa') { + state.worldSim.era = 'pingshi' + state.worldSim.eraStartYear = 1 + } + for (const dyn of Object.values(state.worldSim.npcDyn)) { + if (dyn && typeof (dyn as { prosperity?: unknown }).prosperity !== 'number') { + (dyn as { prosperity: number }).prosperity = 50 + } + } } if (state.family.headId && !state.members[state.family.headId]) { const firstAlive = Object.values(state.members).find((c) => c.alive) @@ -451,11 +463,47 @@ export class World { // ==================== player actions ==================== + /** 盟友求援响应:捐灵石助其重整武备(power+15、关系+8) */ + assistAlly(npcId: string, cost = 200): boolean { + const fam = this.state.family + const ws = this.state.worldSim as { distress?: { id: string } } | undefined + if (!ws?.distress || ws.distress.id !== npcId) return false + if (fam.stones < cost) return false + fam.stones -= cost + const npc = this.state.npcFamilies[npcId] + if (!npc) return false + npc.power = Math.min(900, npc.power + 15) + npc.relation = Math.min(100, npc.relation + 8) + fam.reputation += 3 + ws.distress = undefined + this.log('good', `驰援${npc.name}——敌锋既退,盟谊愈坚(声望+3)。`) + this.chronicle('diplomacy', `助${npc.name}重整武备,同盟益固。`, undefined, true) + return true + } + + /** 调停战端:耗灵石五十,令两家罢兵三年(关系回暖至可容忍线) */ + mediateNpcs(npcA: string, npcB: string, cost = 50): boolean { + const fam = this.state.family + const dyns = this.state.worldSim?.npcDyn as Record }> | undefined + if (!dyns?.[npcA]?.relationsWithOthers || dyns[npcA].relationsWithOthers[npcB] === undefined) return false + if (fam.stones < cost) return false + fam.stones -= cost + for (const [a, b] of [[npcA, npcB], [npcB, npcA]] as const) { + const rel = dyns[a].relationsWithOthers[b] + if (rel < -30) dyns[a].relationsWithOthers[b] = -25 + } + fam.reputation += 2 + this.log('info', `调停${this.state.npcFamilies[npcA]?.name ?? npcA}与${this.state.npcFamilies[npcB]?.name ?? npcB}——干戈化玉帛,声望+2。`) + return true + } + setAlliance(npcId: string, on: boolean): boolean { const npc = this.state.npcFamilies[npcId] if (!npc) return false if (on) { - if (npc.relation < 40) return false + const stA = (this.state.worldSim?.npcDyn?.[npcId] as { stance?: string } | undefined)?.stance + const need = stA === 'ally' ? 30 : 40 + if (npc.relation < need) return false npc.allied = true npc.alliedSinceYear = this.state.year npc.power = Math.min(900, npc.power + 10) @@ -465,6 +513,19 @@ export class World { dyn.relationsWithOthers[npcId] = Math.max(-100, (dyn.relationsWithOthers[npcId] ?? 0) - 3) } } + // 0.1.19 盟约连坐:其世仇恼我结盟,两邦关系走冷 + const dyn = (this.state.worldSim?.npcDyn as Record }> | undefined)?.[npcId] + if (dyn) { + for (const [oid, rel] of Object.entries(dyn.relationsWithOthers)) { + if (rel < -50) { + const foeNpc = this.state.npcFamilies[oid] + if (foeNpc) { + foeNpc.relation = Math.max(-100, foeNpc.relation - 20) + this.log('info', `${foeNpc.name} 见我已与${npc.name}结盟,心怀芥蒂,关系-20。`) + } + } + } + } this.log('good', `与${npc.name}结成同盟——盟誓既立,互不犯边。`) this.chronicle('diplomacy', `本族与${npc.name}缔结同盟。`, undefined, true) return true diff --git a/src/renderer/game/engine/sim/WorldSim.ts b/src/renderer/game/engine/sim/WorldSim.ts index d45ba73..76429bb 100644 --- a/src/renderer/game/engine/sim/WorldSim.ts +++ b/src/renderer/game/engine/sim/WorldSim.ts @@ -1,6 +1,6 @@ /** WorldSim —— 世界自进化引擎(game/engine/sim/WorldSim.ts) */ import { World } from '../runtime/World' -import { WorldSimState, NpcDynamics, WORLDSIM, ERA_CONF, ERA_DURA, CALAMITY_EFFECT, CALAMITY_FAMILY, CalamityName, POOL_BASE } from './worldsim-data' +import { WorldSimState, NpcDynamics, WORLDSIM, ERA_CONF, ERA_DURA, makeWorldSimState, NpcStance, 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' @@ -196,6 +196,8 @@ function initSim(w: World): WorldSimState { for (const id of Object.keys(w.state.npcFamilies)) { s.npcDyn[id] = { prosperity: 50, + stance: 'guardian', + stanceSinceYear: 1, leaderName: '新任宗主', leaderRealmIdx: MAJOR_ORDER.indexOf(npcById(id).leaderRealm), leaderAge: 40 + w.rng.int(0, 29), @@ -209,18 +211,7 @@ function initSim(w: World): WorldSimState { } function empty(): WorldSimState { - return { - marketPool: { lingcao: 600, lingkuang: 300, beastcore: 80, 'pill-qiyuan': 90, 'pill-ningyuan': 40 }, - npcDyn: {}, - secretQi: {}, - tide: 0.5, - tideDir: 1, - tideTicks: 0, - calamityYear: -99, - newsFeed: [], - lastNewsMonth: -99, - npcSuccessions: 0 - } + return { ...makeWorldSimState() } } /** A2+A3:世界常驻供给与需求(潮汐乘化)——池有了呼吸 */ @@ -231,14 +222,25 @@ function worldBreath(w: World, s: WorldSimState): void { const supplySpan = Math.max(0.75, Math.min(1.35, span)) const eraSupply = era.supplyMult const eraDemand = era.demandMult + let overflow = false for (const id of MARKET_IDS) { const base = poolBase(id) const cur = s.marketPool[id] ?? base - const supply = base * WORLDSIM.worldSupplyRate * supplySpan * eraSupply + // 超卖潮:池过剩 > 1.8×base 时世界产能自发回调(谷贱伤农) + const saturated = cur > base * 1.8 + if (saturated) overflow = true + const supply = base * WORLDSIM.worldSupplyRate * supplySpan * eraSupply * (saturated ? 0.7 : 1) const demand = base * WORLDSIM.worldDemandRate * eraDemand s.marketPool[id] = clamp(cur + supply - demand, base * 0.12, base * WORLDSIM.tradeCeilPct) } - void w + // 超卖播报(年一次)——玩家能看到"别把市场玩崩" + if (overflow && s.overfluxYear !== w.state.year) { + s.overfluxYear = w.state.year + s.newsFeed.push({ + year: w.state.year, month: w.state.month, src: '坊市', text: '谷贱伤农——市面货丰价滞,灵田多有弃耕,天下产能暂歇。', kind: 'quote' + }) + if (w.state.totalTicks > 12) w.log('bad', '【坊市】谷贱伤农,世界产能回调。') + } } /** A4:NPC 按 def.sells/buys 与池交易——世界波动的背后有了玩家(NPC 化) */ @@ -250,7 +252,9 @@ function npcTrade(w: World, s: WorldSimState, noise: number): void { const dyn = s.npcDyn[id] ?? initDynFor(id) s.npcDyn[id] = dyn const isCalamity = !!s.calamity - const rate = WORLDSIM.npcTradeRate * (isCalamity ? 0.7 : 1) + const stance2 = (dyn.stance ?? 'guardian') as string + const tradeMult = stance2 === 'expand' ? 1.3 : stance2 === 'endure' ? 0.7 : 1 + const rate = WORLDSIM.npcTradeRate * (isCalamity ? 0.7 : 1) * tradeMult // 景气驱动(1-2):入不敷出则衰、仓廪常足则旺 let prosperity = dyn.prosperity ?? 50 // 卖:NPC 抛货 → 池增;有货自给,景气微涨 @@ -336,6 +340,16 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void { const lifespan = MAJORS[curRealm].lifespan if (w.state.month === 1) { dyn.leaderAge++ + // 姿态评估(era>景气>power>relation) + if (w.state.month === 1 && (w.state.year - (dyn.stanceSinceYear ?? 1)) >= 8) { + const newStance = assessStance(w, id, dyn) + if (newStance !== dyn.stance) { + dyn.stance = newStance + dyn.stanceSinceYear = w.state.year + pushNews(w, s, [id]) + w.log('info', `【天下】${def.name} 态度一变——${STANCE_NAME[newStance]}。`) + } + } // 关系漂移:年首各 ±5(邻近的讲合、世仇的愈深) for (const oid of ids) { if (oid === id) continue @@ -363,7 +377,10 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void { const foe = w.state.npcFamilies[oid] if (!foe) continue const rel = dyn.relationsWithOthers[oid] ?? 0 - if (rel < -50 && w.rng.chance(WORLDSIM.npcEventChance)) { + const foeStance = dyn.stance ?? 'guardian' + const hateBar = foeStance === 'expand' ? -40 : foeStance === 'endure' ? -70 : -50 + if (foeStance === 'ally') void hateBar + if (rel < hateBar && foeStance !== 'ally' && w.rng.chance(WORLDSIM.npcEventChance)) { // 1-3 蝴蝶效应:互攻按实力加权(强者越可能胜,弱者一败再败) const wSum = npc.power + foe.power const winner = w.rng.chance(wSum > 0 ? npc.power / wSum : 0.5) ? npc : foe @@ -384,6 +401,15 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void { text: `${winner.name} 与 ${loser.name} 起衅——痛挫其锋,势力大动。` }) w.log('info', `【天下】${winner.name} 击破 ${loser.name},气象一新。`) + // 盟友求援:下盟友处告急,请其同盟(玩家)出手 + if (loser.allied) { + s.distress = { id: loser.id, year, month: 1 } + s.newsFeed.push({ + year, month: 1, src: loser.name, + text: `${loser.name} 遭${winner.name}重创,遣使来吾族求援:请解囊相助。`, kind: 'npc' + }) + w.log('bad', `【天下】盟邦 ${loser.name} 遇袭告急!`) + } break } } @@ -391,10 +417,35 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void { } } +const STANCE_NAME: Record = { guardian: '守成', expand: '扩张', endure: '隐忍', ally: '结盟' } + +/** 姿态评估:乱世逼扩张、低谷存隐忍、盛世好结盟、元气足则守成 */ +function assessStance(w: World, id: string, dyn: NpcDynamics): NpcStance { + const npc = w.state.npcFamilies[id] + const era = ((w.state.worldSim as WorldSimState)?.era ?? 'pingshi') as string + const prosperity = dyn.prosperity ?? 50 + const power = npc?.power ?? 100 + const relation = npc?.relation ?? 0 + // 硬判据优先 + if (prosperity < 30 || power < 45) return 'endure' + if (relation >= 50) return 'ally' + if (era === 'luanshi' && power >= 120) return 'expand' + if (era === 'mofa') return 'endure' + // 软权重:乱世偏扩张、盛世偏守成、平世均衡 + const W: Record = { guardian: 3, expand: era === 'luanshi' ? 6 : 2, endure: era === 'mofa' ? 5 : 2, ally: era === 'shengshi' || era === 'pingshi' ? 4 : 1 } + const pick = (Object.keys(W) as NpcStance[]).sort((a, b) => W[b] - W[a]) + // 用幂等 rng 抽取(先取最高权)+ 30% 软翻转 + const top = pick[0]! + if (w.rng.chance(0.7)) return top + return pick[1]! +} + function initDynFor(id: string): NpcDynamics { const def = npcById(id) return { prosperity: 50, + stance: 'guardian', + stanceSinceYear: 1, leaderName: `${def.name.replace('氏', '')}氏宗主`, leaderRealmIdx: MAJOR_ORDER.indexOf(def.leaderRealm), leaderAge: 45, diff --git a/src/renderer/game/engine/sim/worldsim-data.ts b/src/renderer/game/engine/sim/worldsim-data.ts index 72a4c90..d4b77f6 100644 --- a/src/renderer/game/engine/sim/worldsim-data.ts +++ b/src/renderer/game/engine/sim/worldsim-data.ts @@ -1,6 +1,11 @@ +export type NpcStance = 'guardian' | 'expand' | 'endure' | 'ally' + export interface NpcDynamics { /** 景气指数(0~100):月结余/断供受挫/灾年支出;驱动 power 微调 */ prosperity: number + /** 战略姿态:守成/扩张/隐忍/结盟(年首评估) */ + stance: NpcStance + stanceSinceYear: number /** 宗主姓名快照(换代时更新) */ leaderName: string leaderRealmIdx: number @@ -25,7 +30,6 @@ export interface WorldSimState { secretQi: Record /** 灵气潮汐(0-100 全局系数) */ tide: number - tideDir: 1 | -1 tideTicks: number /** 灾年(当前灾因 id;持续 calamityMonths 个月) */ calamity?: string @@ -40,6 +44,10 @@ export interface WorldSimState { /** 世纪弧(时代状态机) */ era?: 'shengshi' | 'pingshi' | 'luanshi' | 'mofa' eraStartYear?: number + /** 超卖潮播报年份(谷贱伤农,一年一次) */ + overfluxYear?: number + /** 盟友求援(最近一次:互攻失利波及盟我之族) */ + distress?: { id: string; year: number; month: number } } export function makeWorldSimState(): WorldSimState { @@ -48,7 +56,6 @@ export function makeWorldSimState(): WorldSimState { npcDyn: {}, secretQi: {}, tide: 0.5, - tideDir: 1, tideTicks: 0, calamityYear: -99, calamityLeft: 0, @@ -56,7 +63,8 @@ export function makeWorldSimState(): WorldSimState { lastNewsMonth: -99, npcSuccessions: 0, era: 'pingshi', - eraStartYear: 1 + eraStartYear: 1, + overfluxYear: -99 } } diff --git a/src/renderer/game/types/domain.ts b/src/renderer/game/types/domain.ts index cb28f68..d344c68 100644 --- a/src/renderer/game/types/domain.ts +++ b/src/renderer/game/types/domain.ts @@ -186,14 +186,16 @@ export interface GameState { stats: FamilyStats worldSim?: { marketPool?: Record - npcDyn?: Record }> + npcDyn?: Record; prosperity: number; stance?: string; stanceSinceYear?: number }> secretQi?: Record tide?: number - tideDir?: number tideTicks?: number calamity?: string calamityYear?: number - newsFeed?: { year: number; month: number; src: string; text: string }[] + calamityLeft?: number + era?: string + eraStartYear?: number + newsFeed?: { year: number; month: number; src: string; text: string; itemId?: string; kind?: string }[] lastNewsMonth?: number npcSuccessions?: number } diff --git a/src/renderer/ui/fx.ts b/src/renderer/ui/fx.ts index cfe067e..58da77b 100644 --- a/src/renderer/ui/fx.ts +++ b/src/renderer/ui/fx.ts @@ -1,6 +1,76 @@ -import { FxGate, FxKind } from '../game/engine/kernel/fxqueue' import { seasonOf } from '../game/data/season' +export type FxKind = 'spark' | 'mist' | 'ripple' | 'blade' | 'pulse' | 'seasonShift' | 'drift' +export type FxMode = 'soft' | 'std' | 'full' + +export interface FxEmit { + kind: FxKind + payload?: Record + at: number +} + +export interface FxBus { + emit(e: FxEmit): void +} + +/** 竞态安全发射闸:单循环、队列不覆盖、档位过滤、reduced-motion 降级 */ +export class FxGate { + private queue: FxEmit[] = [] + private draining = false + mode: FxMode = 'std' + preferReduced = false + + constructor(private bus: FxBus) {} + + setMode(m: FxMode): void { + this.mode = m + } + + setReduced(r: boolean): void { + this.preferReduced = r + } + + allowed(kind: FxKind): boolean { + if (this.mode === 'soft') return kind === 'ripple' || kind === 'pulse' || kind === 'drift' + if (this.mode === 'full') return true + return kind !== 'blade' // std:火花/雾/涟漪/脉动,刀光仅 full + } + + emit(kind: FxKind, payload?: FxEmit['payload']): void { + if (!this.allowed(kind) || this.preferReduced || this.draining) return + this.queue.push({ kind, payload, at: Date.now() }) + this.drain() + } + + private drain(): void { + if (this.draining) return + this.draining = true + const batch = this.queue + this.queue = [] + try { + for (const e of batch) this.bus.emit(e) + } finally { + this.draining = false + } + } + + pending(): number { + return this.queue.length + (this.draining ? 1 : 0) + } +} + +/** 纯逻辑:资源差量(防负值与 NaN,供漂字用) */ +export function deltas(prev: Record, next: Record): Record { + const out: Record = {} + const keys = new Set([...Object.keys(prev), ...Object.keys(next)]) + for (const k of keys) { + const a = prev[k] ?? 0 + const b = next[k] ?? 0 + if (Number.isFinite(a) && Number.isFinite(b) && b !== a) out[k] = b - a + } + return out +} + let gate: FxGate | null = null export function fx(): FxGate | null { diff --git a/src/renderer/ui/panels/SettingsPanel.tsx b/src/renderer/ui/panels/SettingsPanel.tsx index 5cf94fa..53e7319 100644 --- a/src/renderer/ui/panels/SettingsPanel.tsx +++ b/src/renderer/ui/panels/SettingsPanel.tsx @@ -210,7 +210,7 @@ export default function SettingsPanel() { · 「外交」与四邻结好联姻;仇雠之族隔岁来犯,打得赢名望大涨,打不赢蚀钱伤丁。
· 「史书」自动记述繁华与凋零——百年之后,后人翻开这一卷家族志,见代代薪火、历历雪泥。 -
版本 0.1.18 · Chronicle of the Immortal Clan
+
版本 0.1.19 · Chronicle of the Immortal Clan
) diff --git a/src/renderer/ui/panels/WorldPanel.tsx b/src/renderer/ui/panels/WorldPanel.tsx index 8120102..4636b95 100644 --- a/src/renderer/ui/panels/WorldPanel.tsx +++ b/src/renderer/ui/panels/WorldPanel.tsx @@ -65,6 +65,31 @@ export default function WorldPanel() { ) })} + {ws.distress && w.state.npcFamilies[ws.distress.id] && ( +
+ 盟友 {w.state.npcFamilies[ws.distress.id].name} 遣使求援: + + + + +
+ )} {annals.length > 0 && (

史官十年鉴

@@ -155,6 +180,28 @@ export default function WorldPanel() {
宗主:{dyn?.leaderName ?? '未知'}({dyn ? MAJOR_NAMES[REALM_IDX[dyn.leaderRealmIdx] ?? 'qi'] : ''} · {dyn?.leaderAge ?? '?'}岁)
{def.desc}
新近之事:{dyn?.lastEvent ? `${dyn.lastEvent}(${dyn.lastEventYear ?? '?'}年)` : '暂无'}
+
+
恩怨网(此家眼中各家)
+ {Object.entries((dyn as { relationsWithOthers?: Record })?.relationsWithOthers ?? {}).map(([oid, rel]) => { + const od = w.state.npcFamilies[oid] + return od ? ( +
+ {od.name} + + 50 ? 'good' : 'dim'}>{rel} + {rel < -50 && ( + + )} + +
+ ) : null + })} +
{npc.alliedSinceYear &&
同盟自 {npc.alliedSinceYear} 年
}