Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
StackmasterFix
[AI-generated] Two client-only patches for Stackmaster 1.2.0: (A) Quick Grab Materials never debited chests (duplication); (B) its new remembered-chest fallback refills chests you just emptied (intentional upstream, not a bug) - disabled.
CHANGELOG
Changelog
1.2.0
新增 B 项:拦截上游 1.2.0 的「记忆上次用过的箱子」回退 —— 快速堆叠不再把物品塞回你刚清空的箱子。
- 症状:把箱子取空 → 走到别的箱子按
Alt+E快速堆叠 → 刚取出来的东西又被塞回那个空箱子。 - 根因:上游 1.2.0(及 1.1.9 测试版)changelog 原话 ——
「Quick Stack remembers the last directly observed chest for each exact item type,
even after that chest reaches zero stock」,即箱子被清空也不会抹掉这条记忆。
随后
StorageTransferPlanner.PlanDeposits在「剩余量 > 0 且本次一件都没搬走」时, 用这张记忆表把物品回退进那个箱子。 - 判据 A(正常路由不会填空箱):
<>c__DisplayClass2_0::<PlanDeposits>b__1的整个筛选逻辑就是container.InitialCompatibilityKeys.Contains(source.CompatibilityKey)⇒ 空箱子的候选键集合为空 ⇒ 永远进不了正常候选列表。 ⇒ 所以「往清空的箱子塞东西」只有"记忆箱子回退"这一条通道。 - 判据 B(回退只在箱子已无同类物品时才会发生):见
PlanDepositsIL0x00FF-0x014A的四重条件。⇒「拦截回退」与「不再填空箱」是同一件事,不存在误伤正常堆叠的中间态。 - 修法:Postfix 打在
IReadOnlyDictionary<string,string> Stackmaster.RememberedChestDestinations.ResolveFallbacks(Player, InventorySnapshot, DiscoveryResult)上,把返回的StackId → ContainerId回退表换成空表 ⇒TryGetValue永远 miss。 - 影响面封闭(三条实证):
ResolveFallbacks全 DLL 只有 2 个调用点 (StorageAction.Begin/<FinishAfterOwnership>d__17.MoveNext,都是 Quick Stack 存料路径); 记忆数据的唯一读取点RememberedDestinationState::TryGet全 DLL 只被ResolveFallbacks调 1 次; 不涉及合成扣料与快速取料(A 项那两套类是另外的)。 - 刻意不做:不动上游的记忆存档(
ObserveDirect/PlayerPrefs原样保留) ⇒ 本修复可逆,DisableRememberedChests = false即恢复上游行为,不用重建记忆。 - 新配置:
DisableRememberedChests(默认true)。 - ⚠️ 已知副作用:上游 1.2.0 会对「合格但最终留在背包里」的物品临时打
!数量红框 (悬浮一次即清除)。本修复让这部分物品真的留在背包里 ⇒ 红框出现机会略增。 该机制是上游既有的,不是本补丁引入的。 - 结构改动:A / B 两个修复项改为独立安装 —— 任何一项配置关掉、或任何一项挂载失败, 都不再连累另一项(1.1.1 及更早是"任一早退就整体不挂载")。
- ⚠️ 适用性声明已更新:B 项不是上游 bug,上游是有意这么设计的; 本补丁只是按个人偏好关掉它(详见 README 顶部)。
上游 1.2.0 没有修 A 项(继续保留)
QuickGrabMaterialsAction 的扣减源仍是 detached ZDO 快照 ⇒ A 项补丁依然必需(1.1.1 已核对,本轮复核未变)。
1.1.1
修 bug:上游 1.2.0 改了内部类名,本补丁不改就会整体静默失效。
- 根因:
Stackmaster 1.2.0把ExpeditionKitAction改名为QuickGrabMaterialsAction(1.2.0 changelog:把用户可见的"存放/补货"动作改叫 Quick Stack,而 Quick Grab Materials 这个名字保留不变 —— 改的只是内部类名)。 本补丁原先写死FindType("Stackmaster.ExpeditionKitAction")⇒ 在 1.2.0 上永远拿到 null,TryInstall每秒空转、180 秒后报 "180 秒内没等到 Stackmaster 类型,补丁未安装" ⇒ 整条补丁一点作用都没有。 - 修正:改为先试新名、再回退旧名:
FindType("Stackmaster.QuickGrabMaterialsAction") ?? FindType("Stackmaster.ExpeditionKitAction")⇒ 1.2.0 与 1.1.5 及更早两代上游都能装上。 安装日志也会打印实际用到的类型名,方便一眼看出上游又换没换名字。 - 依赖:
JStack424-Stackmaster1.1.5 → 1.2.0。 - 配置键保持不变:仍叫
FixExpeditionKit(上游 1.2.0 之前管这个功能叫 expedition kit)。 改名会让老用户已写好的 cfg 失效 —— BepInEx 不会用新默认值覆盖已存在的键, 结果是"开关突然变回默认",得不偿失;故键名不动,只在说明里点明它对应上游的 Quick Grab Materials。
复核结论:没有可剔除的功能
同时按最新源码逐项复核了"上游是否已经自己修好这条路径",结论是没有:
QuickGrabMaterialsAction.cs:626-634(1.2.0)里仍然是var sourceItem = runtime.Inventory.GetItemAt(runtime.Item.m_gridPos.x, runtime.Item.m_gridPos.y);加ReferenceEquals(sourceItem, runtime.Item)校验;:655-660仍然是playerInventory.MoveItemToThis(runtime.Inventory, sourceItem, …)——runtime.Inventory依旧是那个 detached 快照;- 而它的来源
QuickGrabMaterialsAction.Finish()→CaptureForQuickGrab(player, true, true)→NearbyResources.Capture→ContainerDiscovery.Discover(…, resourceReadOnly: true)一字未改(NearbyResources.cs:1145,ContainerDiscovery.cs:387仍是new Inventory(true))。
⇒ 与 1.1.5 相比,上游这次只是把文件与类改了名字,行为完全没动
(对照 1.1.5 的 ExpeditionKitAction.cs:626/627/650/656/670,行号语义一一对应)。
本补丁依然必需。
验证
dotnet build -c Release→ 0 警告 0 错误;DLL 17,920 字节, sha25600bd424e96bcb56c5aaed0b15a080fa8e358e196248f57fa4f724dc1e3535f7c。- DLL 字节检索:
Stackmaster.QuickGrabMaterialsAction与Stackmaster.ExpeditionKitAction两个名字都在(双名查找);ExecuteAtomic/RuntimeResourceStack/ContainerHandle/FixExpeditionKit均在;版本常量与 Win32 版本资源均为1.1.1。 - 上游官方 1.2.0 DLL metadata 核对:
ExpeditionKitAction已不存在 ✓(印证本 bug);QuickGrabMaterialsAction.ExecuteAtomic6 参 ✓(与补丁的FindMethod(…, 6)一致);RuntimeResourceStack.get_Inventory/get_Item/get_Container、ContainerHandle.get_Container✓ 全在。 - 装机 DLL 与官方 1.2.0 包逐字节一致(316,416 B /
be2fc8941b1abd96)。
1.1.0
剔除冗余功能:上游 Stackmaster 1.1.5 已自行修好「合成/升级不扣料」,本补丁不再介入那一半。
- 依据(只读上游源码 + 官方包 metadata 核对):
- 1.1.5 changelog 首条声明该修复,并写明已在游戏内验证
(
prepared withdrawals are recaptured from each selected owned Container.GetInventory() rather than from detached ZDO inventory snapshots …)。 NearbyResources.cs:1288取Container.GetInventory()活库存,:1294-1313用它重建mutableHandle与runtimeStacks⇒ 执行期对象就是活库存;:1673-1681另有ReferenceEquals(runtime.Inventory, runtime.Container.Container.GetInventory())的 fail-closed 身份校验(注释直指 "grants a free crafted item"),并配ExactResourceDebit精确账本与逆序回滚。- ⇒ 本补丁针对
ExecuteWithRollback的 transpiler 已退化为恒等替换,属纯冗余。
- 1.1.5 changelog 首条声明该修复,并写明已在游戏内验证
(
- 移除:
ExecuteWithRollback的 transpiler + 写回 postfix 分支、FixCraftingFromStorage配置项、以及NearbyResourceService的类型查找。 DLL 内已确认FixCraftingFromStorage与ExecuteWithRollback零引用。 - 保留:
ExpeditionKitAction.ExecuteAtomic(远征包)。1.1.5 没有修这条路径 —— 它仍走CaptureForExpedition → Discover(…, resourceReadOnly: true)的 detached ZDO 快照, 而 crafting 专用的TryCaptureOwnedPlan/mutableCapture从未被它调用。故这一半原样保留。 - 依赖:
JStack424-Stackmaster1.1.4 → 1.1.5(不升会让 r2modman 要求把已升的上游降回去)。 - 日志改进:transpiler 不再拿「指令总数」当方法指纹(上游一改 IL 就会退化成
未知方法(指令数 N),纯误导),改用 Harmony 注入的__originalMethod直接取方法名。 - 阈值变化:
FixExpeditionKit=false时补丁不再尝试挂载,直接打日志说明并退出(等同于未安装)。
DLL 重新编译:16,896 字节,sha256 da4e8133d5e5b8ef991cc6f366f8e64ed31022d1d02bd27b0f497c8af1f5342e。
1.0.0
- 首个版本。修复上游
JStack424-Stackmaster1.1.4 的「用箱子材料合成/升级不扣料(复制)」问题。 - 根因:Stackmaster 为了「不拿所有权也能读箱子」,把容器的序列化库存从 ZDO 解进一个
临时 Inventory(
new Inventory(true),ContainerHandle.ResourceInventory), 但写入路径复用了同一个 capture ⇒ExecuteWithRollback实际是扣在只读副本上。 临时 Inventory 永不触发Inventory.Changed()⇒ 不会调用Container.Save()⇒ ZDO 里材料一点没少,而模组的自检在同一个副本上量差值必然判定成功 ⇒ 玩家白拿成品。 - 修法:transpiler 把写入方法里的
RuntimeResourceStack.get_Inventory/get_Item整体重定向到真实容器的活库存与活物品对象;事务成功后强制Container.Save()+ZDOMan.ForceSendZDO。拿不到安全写入权时 fail-closed(取消操作,不放行复制)。 - 覆盖:
NearbyResourceService.ExecuteWithRollback(合成/升级/建造)与ExpeditionKitAction.ExecuteAtomic(建造菜单 Alt+左键拉取远征包)。 - 实测(2026-09-19):修复后逐笔扣减量与配方需求精确吻合
(建造 20/10/2 = 32、合成 −3、另一组 4/1 = 5),且合成事务内首次出现
Container.OnContainerChanged与Container.Save(修复前为 0 条)。 - 配置项:
FixCraftingFromStorage、FixExpeditionKit、Verbose(均默认开)。