v0.1.35a: 缺陷歼灭——wonder 崩链/boss 卡队/patch 泄漏/拒单门/衰减/数值平衡 17 条
【高】wonder 全链修复(sendMission/settle/missionById wonders 回退+ExpeditionPanel 合并列表); boss 战败 releaseSquad(整队不再永困 expedition);patchUndo 按插件归属 Map+rollbackPatch(跨 MOD/跨世界不再泄漏) 【中】buyItem 拒单线>池底(0.35>0.25——断供真断供);warFatigue 年首×0.9 衰减(长局不永久冷降); MOD 功法注入 trackedTech 卸载摘除;patch 缺失键 warn;guard>1 钳制 Math.max(0,1-guard); transition 12 处逗号简写→每属性独立时长(0.1.33 动画实际恢复);t-shanling/t-zidian 越级下调;土系恒应(×1.02) 【低】断供计数封顶12/播报 month 修正/era 权重下界 0.05/新贵 id 碰撞 7 位/新字段 normalize 补/ go() 停时器/saveNow 并发锁 【金钟罩】0.1.35 基线重算(warFatigue衰减/池底分离/功法平衡受控变更) 61 例绿
This commit is contained in:
@@ -19,11 +19,12 @@ export const TECHNIQUES: TechniqueDef[] = [
|
||||
{ id: 't-xiantian', name: '先天混元功', grade: 4, element: '木', path: '体修', expBonus: 0.28, powerBonus: 0.38, desc: '混元一气,万法不侵。' },
|
||||
// 0.1.34 万象归元:流派效果示范(crit/guard/trib/secret——功法类型与效果丰富)
|
||||
{ id: 't-xuanbing', name: '玄冰凝甲功', grade: 1, element: '水', path: '体修', expBonus: 0.11, powerBonus: 0.14, guardBonus: 0.2, desc: '寒甲覆体,攻守两全。' },
|
||||
{ id: 't-zidian', name: '紫电惊雷诀', grade: 1, element: '金', path: '剑修', expBonus: 0.1, powerBonus: 0.22, critChance: 0.12, desc: '雷霆既至,一击定音。' },
|
||||
{ id: 't-zidian', name: '紫电惊雷诀', grade: 1, element: '金', path: '剑修', expBonus: 0.1, powerBonus: 0.18, critChance: 0.1, desc: '雷霆既至,一击定音。' },
|
||||
{ id: 't-danding', name: '丹鼎护道妙典', grade: 2, element: '火', path: '丹修', expBonus: 0.17, powerBonus: 0.1, tribBonus: 0.06, desc: '丹香护鼎,大劫犹安。' },
|
||||
{ id: 't-fengyu', name: '风遁青冥诀', grade: 2, element: '木', path: '符修', expBonus: 0.13, powerBonus: 0.2, secretBonus: 0.2, desc: '青冥风引,探幽得宝。' },
|
||||
{ id: 't-shanling', name: '山灵镇岳录', grade: 2, element: '土', path: '体修', expBonus: 0.14, powerBonus: 0.26, guardBonus: 0.3, critChance: 0.06, desc: '山灵加护,崩山击岳。' },
|
||||
{ id: 't-lanhai', name: '澜海听涛经', grade: 3, element: '水', path: '符修', expBonus: 0.19, powerBonus: 0.24, tribBonus: 0.09, secretBonus: 0.15, desc: '听涛悟澜,化水藏锋。' }
|
||||
{ id: 't-shanling', name: '山灵镇岳录', grade: 2, element: '土', path: '体修', expBonus: 0.14, powerBonus: 0.22, guardBonus: 0.2, critChance: 0.05, desc: '山灵加护,崩山击岳。' },
|
||||
{ id: 't-lanhai', name: '澜海听涛经', grade: 3, element: '水', path: '符修', expBonus: 0.19, powerBonus: 0.24, tribBonus: 0.09, secretBonus: 0.15, desc: '听涛悟澜,化水藏锋。' },
|
||||
{ id: 't-shiling', name: '时灵应候诀', grade: 2, element: '土', path: '符修', expBonus: 0.15, powerBonus: 0.18, secretBonus: 0.1, desc: '土应时气,候至灵生。' }
|
||||
]
|
||||
|
||||
export function techniqueById(id: string | undefined): TechniqueDef | null {
|
||||
|
||||
@@ -135,13 +135,13 @@ export function resolveEncounter(
|
||||
w.chronicle('death', `${c.name} 战殁于${opts.enemy.name},一身所学俱付尘烟。`, c.id, true)
|
||||
w.emitFx('ripple', `death:${c.id}`)
|
||||
} else if (severity < 0.45) {
|
||||
const woundAmt = Math.round((40 + w.rng.int(0, 25)) * form.retreatWound * (talisman === 'shan' ? 0.5 : 1) * (1 - guard))
|
||||
const woundAmt = Math.round((40 + w.rng.int(0, 25)) * form.retreatWound * (talisman === 'shan' ? 0.5 : 1) * Math.max(0, 1 - guard))
|
||||
c.health = Math.max(1, c.health - woundAmt)
|
||||
c.state = 'wounded'
|
||||
loss.push(`${c.name} 重伤`)
|
||||
}
|
||||
} else if (w.rng.chance(0.12)) {
|
||||
c.health = Math.max(1, c.health - Math.round((20 + w.rng.int(0, 15)) * (1 - guard)))
|
||||
c.health = Math.max(1, c.health - Math.round((20 + w.rng.int(0, 15)) * Math.max(0, 1 - guard)))
|
||||
if (c.health < 35) c.state = 'wounded'
|
||||
loss.push(`${c.name} 轻伤`)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,11 @@ export function monthlyRate(w: World, c: Character, herbFactor = 1): number {
|
||||
if (tech && c.realm.major !== 'mortal') {
|
||||
rate *= 1 + tech.expBonus + (c.techniqueRank ?? 0) * 0.05
|
||||
// 0.1.34 万象归元:功法元素与时令共鸣(春木夏火秋金冬水——身合自然)
|
||||
if (w.sysEnabled('season') && SEASON_ELEMENT[seasonOf(st.month)] === tech.element) rate *= 1.04
|
||||
// 0.1.35 土系应四时(长夏为农——恒微应 +2%,土不再因映射缺位而失色)
|
||||
if (w.sysEnabled('season')) {
|
||||
if (SEASON_ELEMENT[seasonOf(st.month)] === tech.element) rate *= 1.04
|
||||
else if (tech.element === '土') rate *= 1.02
|
||||
}
|
||||
} else if (c.realm.major !== 'mortal') {
|
||||
rate *= 0.65
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ export function missionTick(w: World): void {
|
||||
m.done = true
|
||||
m.result = res.draw ? 'stalemate' : 'retreat'
|
||||
m.log = [m.log.join(' ')]
|
||||
releaseSquad(w, m) // 0.1.35 A-3:不释放则整队永困 expedition
|
||||
w.chronicle('exploration', `${def.name}探路不遂,${resultText(m)}。`, undefined, false)
|
||||
}
|
||||
}
|
||||
@@ -148,8 +149,12 @@ function qiRatioOf(w: World, missionId: string): number {
|
||||
return Math.min(1.5, Math.max(0.5, 0.5 + qi / 125))
|
||||
}
|
||||
|
||||
function defOf(w: World, defId: string): MissionDef {
|
||||
return (w.state.worldSim?.wonders?.[defId] as MissionDef | undefined) ?? missionById(defId)
|
||||
}
|
||||
|
||||
export function sendMission(w: World, defId: string, members: string[], formation?: FormationId): boolean {
|
||||
const def = missionById(defId)
|
||||
const def = defOf(w, defId)
|
||||
if (!canSendMission(w, def, members)) return false
|
||||
const m: MissionState = {
|
||||
id: w.seq(),
|
||||
@@ -198,7 +203,7 @@ function releaseSquad(w: World, m: MissionState): void {
|
||||
|
||||
/** 月度巡查:亡者除名、重伤者离队疗伤;人数不足则提前收队 */
|
||||
function settleSquad(w: World, m: MissionState): void {
|
||||
const def = missionById(m.defId)
|
||||
const def = defOf(w, m.defId)
|
||||
let removed = 0
|
||||
for (let i = m.memberIds.length - 1; i >= 0; i--) {
|
||||
const c = w.memberById(m.memberIds[i])
|
||||
|
||||
@@ -141,6 +141,10 @@ 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.warFatigue !== 'number') state.worldSim.warFatigue = 0
|
||||
if (!state.worldSim.shortage) state.worldSim.shortage = {}
|
||||
if (!state.worldSim.shortageWarned) state.worldSim.shortageWarned = {}
|
||||
if (!state.worldSim.wonders) state.worldSim.wonders = {}
|
||||
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
|
||||
@@ -923,7 +927,7 @@ export class World {
|
||||
}
|
||||
|
||||
/** 0.1.34 MOD 数值 patch(mult 0.5~2 / add 实数——作用于数据包表项;卸载复原) */
|
||||
applyModPatch(entries: Array<{ target: 'technique' | 'item' | 'building'; id: string; mult?: number; add?: number }>): void {
|
||||
applyModPatch(entries: Array<{ target: 'technique' | 'item' | 'building'; id: string; mult?: number; add?: number }>, ownerId = 'mod'): void {
|
||||
const undo: Array<() => void> = []
|
||||
for (const p of entries) {
|
||||
if (p.target === 'technique') {
|
||||
@@ -933,7 +937,10 @@ export class World {
|
||||
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
|
||||
if (typeof v !== 'number') {
|
||||
this.log('info', `[patch] 功法 ${p.id} 无字段 ${key}(patch 跳过——需 MOD 定义该效果字段方可调整)。`)
|
||||
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)))
|
||||
}
|
||||
@@ -946,11 +953,29 @@ export class World {
|
||||
it.basePrice = Math.max(1, Math.round(it.basePrice * (p.mult ?? 1) + (p.add ?? 0)))
|
||||
}
|
||||
}
|
||||
this.patchUndo.push(...undo)
|
||||
this.patchUndos.set(ownerId, this.patchUndos.get(ownerId) ?? [])
|
||||
this.patchUndos.get(ownerId)!.push(...undo)
|
||||
}
|
||||
|
||||
/** 撤销栈(MOD 卸载时由 modManager uninstall 调用) */
|
||||
patchUndo: Array<() => void> = []
|
||||
/** 按插件撤销栈(0.1.35:逐个插件归属——卸载只弹本家,互不侵扰) */
|
||||
patchUndos: Map<string, Array<() => void>> = new Map()
|
||||
|
||||
/** 0.1.35 A-6:MOD 功法注入行摘除(卸载时按 id 从数据包数组移除) */
|
||||
removeInjectedTechniques(ids: string[]): void {
|
||||
const arr = pack().techniques
|
||||
for (const id of ids) {
|
||||
const idx = arr.findIndex((t) => t.id === id)
|
||||
if (idx >= 0) arr.splice(idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
rollbackPatch(ownerId: string): void {
|
||||
const stack = this.patchUndos.get(ownerId)
|
||||
if (stack) {
|
||||
stack.forEach((f) => f())
|
||||
this.patchUndos.delete(ownerId)
|
||||
}
|
||||
}
|
||||
|
||||
ancestralRite(): boolean {
|
||||
const fam = this.state.family
|
||||
|
||||
@@ -117,6 +117,9 @@ export function modToPlugin(pack: ModPack): CotycPlugin {
|
||||
// 语义冲突日志:同 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 已存在(内置优先,新增跳过)。`)
|
||||
// 0.1.35 A-6:注入即追踪(与插件绑拆——卸载按 id 摘除注入行)
|
||||
const injected = pack.data.techniques.filter((t) => !seenT.has(t.id) && !(pack as unknown as { trackedTech?: Array<string> }).trackedTech?.includes(t.id))
|
||||
;(pack as unknown as { trackedTech: string[] }).trackedTech = [...((pack as unknown as { trackedTech?: string[] }).trackedTech ?? []), ...injected.map((t) => t.id)]
|
||||
ctx.mergeAddPack('techniques', pack.data.techniques as never)
|
||||
}
|
||||
for (const b of pack.data.buildings ?? []) ctx.addBuildingDef(toBuildingDef(b))
|
||||
@@ -127,10 +130,12 @@ export function modToPlugin(pack: ModPack): CotycPlugin {
|
||||
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)
|
||||
// 0.1.34 MOD 数值 patch 层(仅新档生效;卸载复原——mult 0.5~2 / add 实数)
|
||||
ctx.world.applyModPatch(pack.data.patch ?? [])
|
||||
ctx.world.applyModPatch(pack.data.patch ?? [], `mod-${pack.id}`)
|
||||
},
|
||||
uninstall(ctx2) {
|
||||
ctx2.world.patchUndo.splice(0).forEach((f) => f())
|
||||
ctx2.world.rollbackPatch(`mod-${pack.id}`)
|
||||
const td = (pack as unknown as { trackedTech?: string[] }).trackedTech ?? []
|
||||
if (td.length) ctx2.world.removeInjectedTechniques(td)
|
||||
// 0.1.28 精确回滚:词库池/灾因/配方/世界数值按来源摘除(其余 MOD 共享词条保留)
|
||||
WorldGenPools.removePool(`mod-${pack.id}`)
|
||||
for (const c of pack.data.calamities ?? []) removeCalamityByName(c.name)
|
||||
|
||||
@@ -131,6 +131,9 @@ export class WorldSim {
|
||||
driftMarket(s, rng.next(), this.w) // 波动项(保留噪声弹性)
|
||||
|
||||
|
||||
// ---- B-0.5 战争疲劳休养(0.1.35:年首 ×0.9 回落——天下止戈则元气复) ----
|
||||
if (this.w.state.month === 1 && s.warFatigue) s.warFatigue = clamp(s.warFatigue * 0.9, 0, 1)
|
||||
|
||||
// ---- B0. 新秘境灵机(0.1.34 万象归元:种子派生伪随机——零 rng 消耗,确定性天生) ----
|
||||
if (this.w.state.month === 1) {
|
||||
const era2 = s.era === 'luanshi' ? 2 : 1
|
||||
@@ -462,11 +465,13 @@ function driftMarket(s: WorldSimState, noise: number, w: World): void {
|
||||
if (s.shortage[id] === worldNum('shortageMonths')) {
|
||||
s.shortageWarned = s.shortageWarned ?? {}
|
||||
s.shortageWarned[id] = w.state.year
|
||||
s.newsFeed.push({ year: w.state.year, month: 6, src: '行商', text: `${itemNameOf(id)}断货数月——坊市惜售,价腾三成。`, kind: 'annal' })
|
||||
s.newsFeed.push({ year: w.state.year, month: w.state.month, src: '行商', text: `${itemNameOf(id)}断货数月——坊市惜售,价腾三成。`, kind: 'annal' })
|
||||
}
|
||||
} else if (s.shortage?.[id]) {
|
||||
delete s.shortage[id]
|
||||
}
|
||||
// 0.1.35 计数封顶(防断供期无限拉估)
|
||||
if (s.shortage?.[id]) s.shortage[id] = Math.min(12, s.shortage[id] ?? 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,7 +667,7 @@ function spawnNewbornDynasty(w: World, s: WorldSimState): void {
|
||||
// P1-10 id 掺 seed 指纹(防跨档碰撞)
|
||||
let seedHash = 0
|
||||
for (let i = 0; i < w.state.seed.length; i++) seedHash = (seedHash * 31 + w.state.seed.charCodeAt(i)) >>> 0
|
||||
const id = `n-new-${(seedHash % 4096).toString(36)}-${rng.int(1, 9999)}`
|
||||
const id = `n-new-${(seedHash % 4096).toString(36)}-${rng.int(1, 9999999)}` // 0.1.35 A-10 碰撞窗口 9999→9999999
|
||||
const name = `${region.slice(0, 2)}${NEWBORN_NAME_SUFFIX[rng.int(0, NEWBORN_NAME_SUFFIX.length - 1)]}`
|
||||
const def = {
|
||||
id,
|
||||
|
||||
@@ -188,8 +188,8 @@ export const WORLDSIM = {
|
||||
worldSupplyRate: 0.02, // 世界侧月供给(base 比例;潮涨时 ×1.3)
|
||||
worldDemandRate: 0.015, // 世界侧月需求(坊市/宗门常驻消耗)
|
||||
npcTradeRate: 0.008, // 每 NPC 月贸易量(base 比例;上桌食量)
|
||||
tradeFloorPct: 0.35, // 池低于此比例时玩家买入拒单(断供告急)——与 drift 池底一致
|
||||
poolFloorPct: 0.35, // driftMarket 下限(拒绝“断供后还能买到”悖论带)
|
||||
tradeFloorPct: 0.35, // 拒单线:池低于此比例时玩家买入拒单(断供告急)
|
||||
poolFloorPct: 0.25, // driftMarket 池底(0.1.35 调低——地板与拒单线分离,断供期间真买不到料)
|
||||
tradeCeilPct: 2.4, // 池上限(玩家大量卖出后价格封顶)
|
||||
tideSupplySpan: 0.3, // 潮汐对供给的浮动幅度(tide±0.35 时 ×(1∓0.3))
|
||||
// —— 灾年持续 ——
|
||||
|
||||
Reference in New Issue
Block a user