v0.1.25: 乾坤一统——MOD 系统首版(1040 全绿,金钟罩不漂)
【P0 修复(0.1.24 遗留)】worldGen.npcs defs 落档 + normalize 幂等重注册——读档后
动态家族(n-g-*)npcById 永命中;MOD 卸载后存档世界仍完整(存档世界永稳)
【MOD 系统四层】
- M2 内容层:modSchema.ts(ModPack 全 JSON:manifest+data{events/npcs/worldgen/pills/forges};
modParse 纯解析校验 id/name/version/事件模板合法性)
- M3 管理器:modToPlugin(MOD→CotycPlugin 一等子类——插件持久化/双闸/启停/回滚全复用)
- M1 传输层:main IPC mods:scan/read(userData/mods/*.json|*.cotymod)+ preload + env.d.ts
+ store refreshMods/installModByName/installModText
- M4 UI:SettingsPanel「MOD 层」卡(扫描目录/候选列表/安装);内置示例包通路
【世界/插件协同】
- WorldGenPools 聚合词库池(默认不变→基准世界不变;reset() 供隔离);MOD 于新档参与生成
- items.ts 配方聚合注册表(PILL/FORGE base+扩展);PluginContext addPillRecipe/addForgeRecipe/addWorldGenPool
【边界(明注)】灾因/世界数值表注入归平衡版;脚本型 MOD 不做;MOD 只影响新档世界(老档不变形)
【测试】1040 全绿(46 套件):worldGen 存档 1 例 + mod-system 5 例(解析/转换/模板/依赖/基准指纹);
金钟罩 0.1.24 基线未漂(世界生成默认池不变);build 通过
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* MOD 包 schema(0.1.25《乾坤一统》)
|
||||
* MOD = 可分发的外部内容包(JSON 单文件/文件)——Manifest + 纯数据。
|
||||
* MOD 经 ModManager 转换为 CotycPlugin 进标准插件管线(持久化/双闸/回滚免费获得)。
|
||||
*/
|
||||
import { EventDef } from '../../data/events'
|
||||
import { NpcFamilyDef } from '../../data/npcs'
|
||||
|
||||
export interface ModPackData {
|
||||
/** 追加事件(需与既有 id 无冲突) */
|
||||
events?: EventDef[]
|
||||
/** 世界模板(worldgen 池候选——新档生成时参与) */
|
||||
npcs?: NpcFamilyDef[]
|
||||
/** 世界生成词库注入 */
|
||||
worldgen?: {
|
||||
styles?: string[]
|
||||
regions?: string[]
|
||||
fams?: string[]
|
||||
suffixes?: string[]
|
||||
}
|
||||
/** 丹方/铸器配方 */
|
||||
pills?: Array<{ output: string; name: string; danfangLevel: number; stones: number; lingcao: number; beastcore: number; desc: string }>
|
||||
forges?: Array<{ output: string; name: string; stones: number; lingkuang: number; beastcore: number; desc: string }>
|
||||
}
|
||||
|
||||
export interface ModPack {
|
||||
id: string
|
||||
name: string
|
||||
version: string
|
||||
gameVersion?: string
|
||||
author?: string
|
||||
description?: string
|
||||
depends?: string[]
|
||||
conflicts?: string[]
|
||||
data: ModPackData
|
||||
}
|
||||
|
||||
export type ModParseResult = { ok: true; pack: ModPack } | { ok: false; error: string }
|
||||
|
||||
const ID_RE = /^[a-z0-9][a-z0-9-_]{1,31}$/i
|
||||
|
||||
/** 纯解析(任何文本 → ModPack 或错误);校验层供 vitest 直测 */
|
||||
export function modParse(text: string): ModParseResult {
|
||||
let raw: unknown
|
||||
try {
|
||||
raw = JSON.parse(text)
|
||||
} catch {
|
||||
return { ok: false, error: 'MOD 文件不是合法 JSON。' }
|
||||
}
|
||||
if (!raw || typeof raw !== 'object') return { ok: false, error: 'MOD 内容为空。' }
|
||||
const p = raw as Record<string, unknown>
|
||||
if (typeof p.id !== 'string' || !ID_RE.test(p.id)) return { ok: false, error: '缺失/非法的 MOD id。' }
|
||||
if (typeof p.name !== 'string' || !p.name.trim()) return { ok: false, error: '缺失 MOD 名称。' }
|
||||
if (typeof p.version !== 'string' || !p.version.trim()) return { ok: false, error: '缺失 MOD 版本号。' }
|
||||
const data = (p.data ?? {}) as Record<string, unknown>
|
||||
if (!data || typeof data !== 'object') return { ok: false, error: 'MOD data 段缺失。' }
|
||||
if (Array.isArray(data.events)) {
|
||||
for (const e of data.events) {
|
||||
if (!e || typeof e !== 'object') return { ok: false, error: 'MOD 事件表含非法行。' }
|
||||
const ev = e as Record<string, unknown>
|
||||
if (typeof ev.id !== 'string' || typeof ev.name !== 'string' || !Array.isArray(ev.options)) {
|
||||
return { ok: false, error: `MOD 事件 ${String(ev.id ?? '?')} 缺 id/name/options。` }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(data.npcs)) {
|
||||
for (const n of data.npcs) {
|
||||
const nn = n as Record<string, unknown>
|
||||
if (typeof nn.id !== 'string' || typeof nn.name !== 'string' || typeof nn.region !== 'string') {
|
||||
return { ok: false, error: 'MOD 模板表含非法行。' }
|
||||
}
|
||||
}
|
||||
}
|
||||
return { ok: true, pack: raw as ModPack }
|
||||
}
|
||||
Reference in New Issue
Block a user