v0.1.34b: 插件新卡+MOD 功法效果口/patch 层/冲突日志
【插件】SYSTEM_DEFS + worldview 观察卡/tech 功法卡(14);PluginContext + addTechniqueDef(全字段入 PACK 数组, grade≤3 自动池) + onWorldSimTick 观察回调(零 rng 零时序——proxy 追踪注销); 【MOD】ModPackData.techniques 补 4 流派效果字段;新增 patch 层(target 数值 mult 0.5~2/add——install 应用 uninstall 复原,validate 规则进预检);同 id 功法冲突 warn 日志 【门面】Runtime 未动;卸载回滚栈 patchUndo
This commit is contained in:
@@ -52,6 +52,10 @@ export interface PluginContext {
|
|||||||
addPostDef: (def: import('../../data/posts').PostDef) => void
|
addPostDef: (def: import('../../data/posts').PostDef) => void
|
||||||
addAspirationDef: (def: import('../../data/aspirations').AspirationDef) => void
|
addAspirationDef: (def: import('../../data/aspirations').AspirationDef) => void
|
||||||
addFormationDef: (def: import('../../data/formations').FormationDef) => void
|
addFormationDef: (def: import('../../data/formations').FormationDef) => void
|
||||||
|
/** 0.1.34 功法定义注册(全字段——含流派效果;grade≤3 入求学/秘境池) */
|
||||||
|
addTechniqueDef: (def: import('../../data/realms').TechniqueDef) => void
|
||||||
|
/** 0.1.34 世界观察回调(每世界 tick 后——只读记录,禁止写时轮/序;MOD 数值洞察口) */
|
||||||
|
onWorldSimTick: (fn: (year: number, month: number, gameState: unknown) => void) => () => void
|
||||||
addTraitDef: (def: import('../../data/traits').TraitDef) => void
|
addTraitDef: (def: import('../../data/traits').TraitDef) => void
|
||||||
addCapability: (cap: { id: string; name: string; version: string; desc: string }) => void
|
addCapability: (cap: { id: string; name: string; version: string; desc: string }) => void
|
||||||
removeCapability: (id: string) => void
|
removeCapability: (id: string) => void
|
||||||
|
|||||||
@@ -178,6 +178,8 @@ export class World {
|
|||||||
clock: GameClock
|
clock: GameClock
|
||||||
systems: Record<string, { enabled: boolean }>
|
systems: Record<string, { enabled: boolean }>
|
||||||
plugins: PluginManager
|
plugins: PluginManager
|
||||||
|
/** 0.1.34 世界观察回调(只读洞察) */
|
||||||
|
simTickObservers: Array<(year: number, month: number, state: unknown) => void> = []
|
||||||
private eventPools = new Map<string, EventDef[]>()
|
private eventPools = new Map<string, EventDef[]>()
|
||||||
kernel?: import('../kernel/Kernel').Kernel
|
kernel?: import('../kernel/Kernel').Kernel
|
||||||
|
|
||||||
@@ -243,6 +245,14 @@ export class World {
|
|||||||
addAspirationDef: (def) => registerAspirationDef(def),
|
addAspirationDef: (def) => registerAspirationDef(def),
|
||||||
addFormationDef: (def) => registerFormationDef(def),
|
addFormationDef: (def) => registerFormationDef(def),
|
||||||
addTraitDef: (def) => registerTraitDef(def),
|
addTraitDef: (def) => registerTraitDef(def),
|
||||||
|
addTechniqueDef: (def) => {
|
||||||
|
// 入数据包(MISSIONS 式 mergeAdd 通道——同 id 跳过;grade≤3 自动进求学/秘境池)
|
||||||
|
PACK.mergeAdd('techniques', [def])
|
||||||
|
},
|
||||||
|
onWorldSimTick: (fn) => {
|
||||||
|
self.simTickObservers.push(fn)
|
||||||
|
return () => { self.simTickObservers = self.simTickObservers.filter((f) => f !== fn) }
|
||||||
|
},
|
||||||
addCapability: (cap) => {
|
addCapability: (cap) => {
|
||||||
// 0.1.23:能力卡注册入 World 实例(防跨档全局泄漏);UI 全局清单读 SYSTEM_DEFS 展示不受影响
|
// 0.1.23:能力卡注册入 World 实例(防跨档全局泄漏);UI 全局清单读 SYSTEM_DEFS 展示不受影响
|
||||||
if (!self.systems[cap.id]) self.systems[cap.id] = { enabled: true }
|
if (!self.systems[cap.id]) self.systems[cap.id] = { enabled: true }
|
||||||
@@ -458,6 +468,11 @@ export class World {
|
|||||||
this.trackStats()
|
this.trackStats()
|
||||||
this.clampState()
|
this.clampState()
|
||||||
this.pruneYearFlags()
|
this.pruneYearFlags()
|
||||||
|
// 0.1.34 世界观察回调(插件/MOD 只读洞察——零 rng 零时序影响)
|
||||||
|
if (this.simTickObservers.length > 0) {
|
||||||
|
const obs = this.simTickObservers
|
||||||
|
for (const fn of obs) fn(s.year, s.month, s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyEventChoice(eventId: string, idx: number): boolean {
|
applyEventChoice(eventId: string, idx: number): boolean {
|
||||||
@@ -901,6 +916,36 @@ export class World {
|
|||||||
return Math.round(top * bonus)
|
return Math.round(top * bonus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 0.1.34 MOD 数值 patch(mult 0.5~2 / add 实数——作用于数据包表项;卸载复原) */
|
||||||
|
applyModPatch(entries: Array<{ target: 'technique' | 'item' | 'building'; id: string; mult?: number; add?: number }>): void {
|
||||||
|
const undo: Array<() => void> = []
|
||||||
|
for (const p of entries) {
|
||||||
|
if (p.target === 'technique') {
|
||||||
|
const arr = pack().techniques.filter((x) => x.id === p.id)
|
||||||
|
if (arr.length === 0) continue
|
||||||
|
const t = arr[0] as unknown as Record<string, number>
|
||||||
|
const vals: Array<[keyof typeof t, number]> = []
|
||||||
|
for (const key of ['expBonus', 'powerBonus', 'critChance', 'guardBonus', 'tribBonus', 'secretBonus'] as const) {
|
||||||
|
const v = t[key]
|
||||||
|
if (typeof v !== 'number') continue
|
||||||
|
vals.push([key as never, v])
|
||||||
|
t[key as never] = Math.max(-5, Math.min(2, p.mult !== undefined ? v * p.mult : v + (p.add ?? 0)))
|
||||||
|
}
|
||||||
|
for (const [key, v] of vals) undo.push(() => { t[key as never] = v })
|
||||||
|
} else if (p.target === 'item') {
|
||||||
|
const it = (pack().items as Record<string, { basePrice?: number }>)[p.id]
|
||||||
|
if (!it || typeof it.basePrice !== 'number') continue
|
||||||
|
const orig = it.basePrice
|
||||||
|
undo.push(() => { it.basePrice = orig })
|
||||||
|
it.basePrice = Math.max(1, Math.round(it.basePrice * (p.mult ?? 1) + (p.add ?? 0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.patchUndo.push(...undo)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 撤销栈(MOD 卸载时由 modManager uninstall 调用) */
|
||||||
|
patchUndo: Array<() => void> = []
|
||||||
|
|
||||||
ancestralRite(): boolean {
|
ancestralRite(): boolean {
|
||||||
const fam = this.state.family
|
const fam = this.state.family
|
||||||
const last = (fam.flag['lastRiteYear'] as number | undefined) ?? -999
|
const last = (fam.flag['lastRiteYear'] as number | undefined) ?? -999
|
||||||
|
|||||||
@@ -21,5 +21,7 @@ export const SYSTEM_DEFS: SystemDef[] = [
|
|||||||
{ id: 'tournament', name: '太虚大比', version: '1.0', desc: '五年一会的太虚仙盟争锋。' },
|
{ id: 'tournament', name: '太虚大比', version: '1.0', desc: '五年一会的太虚仙盟争锋。' },
|
||||||
{ id: 'tribulation', name: '渡劫', version: '1.0', desc: '大境界天劫与护法之仪。' },
|
{ id: 'tribulation', name: '渡劫', version: '1.0', desc: '大境界天劫与护法之仪。' },
|
||||||
{ id: 'apprentice', name: '寄读', version: '1.0', desc: '宗门子弟外修与求经台。' },
|
{ id: 'apprentice', name: '寄读', version: '1.0', desc: '宗门子弟外修与求经台。' },
|
||||||
{ id: 'season', name: '时节', version: '1.0', desc: '春夏秋冬之乘气(春耕夏修秋市冬闭)。' }
|
{ id: 'season', name: '时节', version: '1.0', desc: '春夏秋冬之乘气(春耕夏修秋市冬闭)。' },
|
||||||
|
{ id: 'worldsim', name: '世界演衍', version: '1.0', desc: '世界自演化观察/响应(0.1.34:战争疲劳/断供应急/新秘境灵机)。' },
|
||||||
|
{ id: 'tech', name: '功法百汇', version: '1.0', desc: '功法流派效果(0.1.34:暴击/受创减免/渡劫/秘境——万象归元)。' }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { EventDef } from '../../data/events'
|
|||||||
import { findEvent } from './Systems/events'
|
import { findEvent } from './Systems/events'
|
||||||
import { World } from './World'
|
import { World } from './World'
|
||||||
import { WorldGenPools } from '../sim/worldgen'
|
import { WorldGenPools } from '../sim/worldgen'
|
||||||
|
import { pack as dataPack } from '../../data/registry'
|
||||||
import { removeCalamityByName, overrideWorldNum, resetWorldNum } from '../sim/worldsim-data'
|
import { removeCalamityByName, overrideWorldNum, resetWorldNum } from '../sim/worldsim-data'
|
||||||
import { removePillRecipe, removeForgeRecipe } from '../../data/items'
|
import { removePillRecipe, removeForgeRecipe } from '../../data/items'
|
||||||
import { registerBuildingDef } from '../../data/buildings'
|
import { registerBuildingDef } from '../../data/buildings'
|
||||||
@@ -112,7 +113,12 @@ export function modToPlugin(pack: ModPack): CotycPlugin {
|
|||||||
for (const it of pack.data.items) base[it.id] = it
|
for (const it of pack.data.items) base[it.id] = it
|
||||||
ctx.overridePack({ items: base as never })
|
ctx.overridePack({ items: base as never })
|
||||||
}
|
}
|
||||||
if (pack.data.techniques?.length) ctx.mergeAddPack('techniques', pack.data.techniques as never)
|
if (pack.data.techniques?.length) {
|
||||||
|
// 语义冲突日志:同 id 功法(mergeAdd 跳过内置——提示玩家谁改了什么)
|
||||||
|
const seenT = new Set((dataPack().techniques as Array<{ id: string }>).map((x) => x.id))
|
||||||
|
for (const t of pack.data.techniques) if (seenT.has(t.id)) ctx.world.log('info', `[MOD:${pack.id}] 功法 ${t.id} 同 id 已存在(内置优先,新增跳过)。`)
|
||||||
|
ctx.mergeAddPack('techniques', pack.data.techniques as never)
|
||||||
|
}
|
||||||
for (const b of pack.data.buildings ?? []) ctx.addBuildingDef(toBuildingDef(b))
|
for (const b of pack.data.buildings ?? []) ctx.addBuildingDef(toBuildingDef(b))
|
||||||
for (const po of pack.data.posts ?? []) ctx.addPostDef({
|
for (const po of pack.data.posts ?? []) ctx.addPostDef({
|
||||||
id: po.id, name: po.id, effect: { type: po.effectType, value: po.value }, max: po.max, desc: po.desc
|
id: po.id, name: po.id, effect: { type: po.effectType, value: po.value }, max: po.max, desc: po.desc
|
||||||
@@ -120,8 +126,11 @@ export function modToPlugin(pack: ModPack): CotycPlugin {
|
|||||||
for (const a of pack.data.aspirations ?? []) ctx.addAspirationDef(a as never)
|
for (const a of pack.data.aspirations ?? []) ctx.addAspirationDef(a as never)
|
||||||
for (const f of pack.data.formations ?? []) ctx.addFormationDef({ ...f, retreatWound: f.retreatWound ?? 1 } as never)
|
for (const f of pack.data.formations ?? []) ctx.addFormationDef({ ...f, retreatWound: f.retreatWound ?? 1 } as never)
|
||||||
for (const t of pack.data.traits ?? []) ctx.addTraitDef({ ...t, danger: t.bonus?.danger ?? 0 } as never)
|
for (const t of pack.data.traits ?? []) ctx.addTraitDef({ ...t, danger: t.bonus?.danger ?? 0 } as never)
|
||||||
|
// 0.1.34 MOD 数值 patch 层(仅新档生效;卸载复原——mult 0.5~2 / add 实数)
|
||||||
|
ctx.world.applyModPatch(pack.data.patch ?? [])
|
||||||
},
|
},
|
||||||
uninstall(ctx2) {
|
uninstall(ctx2) {
|
||||||
|
ctx2.world.patchUndo.splice(0).forEach((f) => f())
|
||||||
// 0.1.28 精确回滚:词库池/灾因/配方/世界数值按来源摘除(其余 MOD 共享词条保留)
|
// 0.1.28 精确回滚:词库池/灾因/配方/世界数值按来源摘除(其余 MOD 共享词条保留)
|
||||||
WorldGenPools.removePool(`mod-${pack.id}`)
|
WorldGenPools.removePool(`mod-${pack.id}`)
|
||||||
for (const c of pack.data.calamities ?? []) removeCalamityByName(c.name)
|
for (const c of pack.data.calamities ?? []) removeCalamityByName(c.name)
|
||||||
|
|||||||
@@ -29,8 +29,10 @@ export interface ModPackData {
|
|||||||
forges?: Array<{ output: string; name: string; stones: number; lingkuang: number; beastcore: number; desc: string }>
|
forges?: Array<{ output: string; name: string; stones: number; lingkuang: number; beastcore: number; desc: string }>
|
||||||
/** 0.1.31 内容面总攻:物品/功法(PACK 合入)、建筑/职事/志向/阵型/性格(双源注册) */
|
/** 0.1.31 内容面总攻:物品/功法(PACK 合入)、建筑/职事/志向/阵型/性格(双源注册) */
|
||||||
items?: Array<{ id: string; name: string; kind: 'resource' | 'material' | 'pill' | 'talisman' | 'brew' | 'artifact'; basePrice: number; desc: string; icon: string }>
|
items?: Array<{ id: string; name: string; kind: 'resource' | 'material' | 'pill' | 'talisman' | 'brew' | 'artifact'; basePrice: number; desc: string; icon: string }>
|
||||||
techniques?: Array<{ id: string; name: string; grade: number; element: string; path: string; expBonus: number; powerBonus: number; desc: string }>
|
techniques?: Array<{ id: string; name: string; grade: number; element: string; path: string; expBonus: number; powerBonus: number; desc: string; critChance?: number; guardBonus?: number; tribBonus?: number; secretBonus?: number }>
|
||||||
buildings?: Array<{ id: string; name: string; icon: string; desc: string; kind: 'produce' | 'function'; maxLevel: number; produceExpr?: string; upgradeStones?: number; upgradeMineral?: number }>
|
buildings?: Array<{ id: string; name: string; icon: string; desc: string; kind: 'produce' | 'function'; maxLevel: number; produceExpr?: string; upgradeStones?: number; upgradeMineral?: number }>
|
||||||
|
/** 0.1.34 数值 patch 层(mult 0.5~2 / add 实数——仅新开档生效;老档世界形成为档记录) */
|
||||||
|
patch?: Array<{ target: 'technique' | 'item' | 'building'; id: string; mult?: number; add?: number }>
|
||||||
posts?: Array<{ id: string; name: string; effectType: string; value: number; max: number; desc: string }>
|
posts?: Array<{ id: string; name: string; effectType: string; value: number; max: number; desc: string }>
|
||||||
aspirations?: Array<{ id: string; name: string; desc: string; effectType: string; value: number }>
|
aspirations?: Array<{ id: string; name: string; desc: string; effectType: string; value: number }>
|
||||||
formations?: Array<{ id: string; name: string; icon: string; desc: string; atk: number; def: number; retreatWound: number }>
|
formations?: Array<{ id: string; name: string; icon: string; desc: string; atk: number; def: number; retreatWound: number }>
|
||||||
@@ -157,5 +159,13 @@ export function validateMod(pack: ModPack): ModValidation {
|
|||||||
for (const r of pack.data.forges ?? []) {
|
for (const r of pack.data.forges ?? []) {
|
||||||
if ([r.stones, r.lingkuang, r.beastcore].some((v) => v < 0)) warnings.push(`配方 ${r.output} 含负成本。`)
|
if ([r.stones, r.lingkuang, r.beastcore].some((v) => v < 0)) warnings.push(`配方 ${r.output} 含负成本。`)
|
||||||
}
|
}
|
||||||
|
for (const t of pack.data.techniques ?? []) {
|
||||||
|
if (!t.grade || t.grade < 1 || t.grade > 4) warnings.push(`功法 ${t.id} grade 建议 1~4。`)
|
||||||
|
if (Math.abs(t.expBonus) > 0.5 || Math.abs(t.powerBonus) > 1) warnings.push(`功法 ${t.id} 数值强度提醒(expBonus≤0.5/powerBonus≤1)。`)
|
||||||
|
}
|
||||||
|
for (const p of pack.data.patch ?? []) {
|
||||||
|
if (!(p.mult === undefined || (p.mult >= 0.5 && p.mult <= 2))) warnings.push(`patch ${p.target}:${p.id} mult 限 0.5~2。`)
|
||||||
|
if (p.mult !== undefined && p.add !== undefined) warnings.push(`patch ${p.target}:${p.id} mult/add 二选一(勿同给)。`)
|
||||||
|
}
|
||||||
return { ok: warnings.length === 0, warnings }
|
return { ok: warnings.length === 0, warnings }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,13 @@ export class PluginManager {
|
|||||||
return unsub
|
return unsub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (prop === 'onWorldSimTick') {
|
||||||
|
return (fn: Parameters<PluginContext['onWorldSimTick']>[0]) => {
|
||||||
|
const unsub = (target.onWorldSimTick as PluginContext['onWorldSimTick'])(fn)
|
||||||
|
rt.unsubscribers.push(unsub)
|
||||||
|
return unsub
|
||||||
|
}
|
||||||
|
}
|
||||||
if (prop === 'addEventPool') {
|
if (prop === 'addEventPool') {
|
||||||
return (pid: string, events: EventDef[]) => {
|
return (pid: string, events: EventDef[]) => {
|
||||||
;(target.addEventPool as (pid: string, ev: EventDef[]) => void)(pid, events)
|
;(target.addEventPool as (pid: string, ev: EventDef[]) => void)(pid, events)
|
||||||
|
|||||||
Reference in New Issue
Block a user