Files
ChronicleOfTheImmortalClan/src/renderer/game/storage/migrate.ts
T
thzxx de46808188 feat(0.1.16-P1): 资源闭环引擎(修为耗草/炼丹概率化/铸器/灵脉收益/NPC战力/灾变落效)+ C13C14
【A 经济闭环】
- 修为经济化:练气以上修行者月耗灵草(闭关2普通1),无草速修0.6~1.0衰减+缺草年告警
- 炼丹等级化:bonusOf 表达式引擎(白名单安全求值),craftChance 转正;丹方单源 PILL_RECIPES(聚气L1/凝元L2/破境L3)+ 失败折半退料
- 炼器筑宝:FORGE_RECIPES(法器/灵器/法宝,灵矿+兽核+灵石)
- 秘境灵脉:secretQiOf→rollWarbooty 收益门槛(0.5~1.3折算);掉落按秘境 techGrades 过滤
- NPC 实力参战:raid 强度/风险随 npc.power 浮动(0.5~1.8)
- 灾年落效:疫病/兽潮一次性袭击 + CALAMITY_FAMILY 月度减产乘子

【C 治理】
- C13 破境丹渡劫修正:大境界时药力→tribBoost 加成(+12%)入渡劫事件,不再跳过三选
- C14 存储防线:loadState 补 migrate;MigrationError(TOO_NEW/未知版本)提示升级

【UI 半批】
- 坊市新增 定制tab(丹方/铸器);ChroniclePanel 手写史书输入条;MemberModal 册立家主钮
- 年报摘要(要闻5条+辞世讣告);EventModal 选项收益预览
- 结盟引擎:setAlliance(relation≥40)/盟友岁差不降/年利15灵石
2026-08-23 14:10:42 +08:00

77 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { GameState } from '../types/domain'
import { normalizeGameState } from '../engine/runtime/World'
export const CURRENT_SCHEMA = 2
export const APP_ID = 'cotyc'
export class MigrationError extends Error {
code: 'TOO_NEW' | 'UNKNOWN_VERSION'
version: number
constructor(code: 'TOO_NEW' | 'UNKNOWN_VERSION', version: number, message: string) {
super(message)
this.code = code
this.version = version
}
}
/**
* 迁移链:obj.from 升到 obj.to 逐级执行(任何一步失败即中断抛出,原状态不被写入)。
* v1(≤0.1.8)→ v20.1.9):补 stats/finance/annals 字段并打上 schema 版本戳。
*/
const MIGRATIONS: Array<{ from: number; to: number; fn: (s: GameState) => GameState }> = [
{
from: 1,
to: 2,
fn: (s) => {
normalizeGameState(s)
s.schemaVersion = 2
return s
}
}
]
export function migrateIfNeeded(state: GameState): { ok: true; state: GameState } | { ok: false; error: MigrationError } {
const version = typeof state.schemaVersion === 'number' ? state.schemaVersion : 1
if (version > CURRENT_SCHEMA) {
return {
ok: false,
error: new MigrationError('TOO_NEW', version, `存档版本 ${version} 高于当前游戏支持(${CURRENT_SCHEMA}),请升级游戏。`)
}
}
let cur = state
let v = version
let guard = 0
while (v < CURRENT_SCHEMA && guard < 20) {
const step = MIGRATIONS.find((m) => m.from === v)
if (!step) {
return { ok: false, error: new MigrationError('UNKNOWN_VERSION', v, `未知存档版本 ${v},无法迁移。`) }
}
try {
cur = step.fn(cur)
} catch (e) {
return { ok: false, error: new MigrationError('UNKNOWN_VERSION', v, `迁移失败:${String(e)}`) }
}
v = step.to
guard++
}
return { ok: true, state: cur }
}
export function exportEnvelope(state: GameState): string {
return JSON.stringify({ app: APP_ID, schemaVersion: state.schemaVersion ?? CURRENT_SCHEMA, payload: state })
}
export function parseImportEnvelope(text: string): { ok: true; state: GameState } | { ok: false; error: string } {
try {
const raw = JSON.parse(text) as { app?: string; schemaVersion?: number; payload?: GameState; state?: GameState }
if (raw.app !== undefined && raw.app !== APP_ID) return { ok: false, error: '非本作存档(app 标识不符)。' }
const state = raw.payload ?? raw.state
if (!state || typeof state !== 'object') return { ok: false, error: '存档内容为空或损坏。' }
const migrated = migrateIfNeeded(state as GameState)
if (migrated.ok) return { ok: true, state: migrated.state }
return { ok: false, error: migrated.error.message }
} catch (e) {
return { ok: false, error: `解析失败:${String(e)}` }
}
}