压缩与拆分(Compress / Decompress)

基本信息

属性 值
热键 压缩物品、拆分物品
服务端入口 net.blay09.mods.craftingtweaks.net.HandlerCompress(封包 id 6)
客户端入口 ClientProvider.compress / ClientProvider.decompress
配方查询 CraftingManager.getInstance().findMatchingRecipe(...)
辅助容器 InventoryCraftingCompress、InventoryCraftingDecompress
门控配置 compressAnywhere(默认 false)

注意:本 mod 不注册任何配方。它只是读取 CraftingManager 中已存在的 配方,把物品按配方形状摆进一个临时的合成矩阵再触发合成。

两个辅助 InventoryCrafting

类 构造签名 尺寸 填充方式
InventoryCraftingCompress (Container container, int size, ItemStack itemStack) size × size 每一个槽都填 itemStack.copy()
InventoryCraftingDecompress (Container container, ItemStack itemStack) 3 × 3 只有槽 0 填 itemStack.copy()

两者都调用 super(container, w, h) 把真实的 Container 传进去, 使 InventoryCrafting 的配方依赖检查能拿到正确的 world。

InventoryCraftingCompress 因此天然表达「把同一种物品铺满 N×N」的配方形状, InventoryCraftingDecompress 表达「一个产物放在左上角」的还原形状。

压缩(compress)

服务端路径(HandlerCompress)

先做 4 层校验:

  1. container != null
  2. slotNumber 在 container.inventorySlots 范围内
  3. mouseSlot.inventory instanceof InventoryPlayer(只允许操作玩家背包槽)
  4. mouseStack != null
  5. CraftingTweaksConfig.compressAnywhere || provider != null

然后按方向分支。

压缩方向,先按 provider 网格大小分派(provider == null 时按 9 处理):

条件 尝试的配方形状 每次消耗
size == 9 && stackSize >= 9 先试 3×3 全铺 decrStackSize(9)
↑ 失败后 再试 2×2(4 格) decrStackSize(4)
size >= 4 && stackSize >= 4 只试 2×2 decrStackSize(4)

即 9 格网格优先 3×3 配方,匹配不上才退 2×2;4 格网格只能 2×2。

每次循环:产物 addItemStackToInventory(result.copy()) 成功才 decrStackSize(n),背包满则 break;循环条件 compressAll && mouseSlot.getHasStack() && stackSize >= n。

客户端路径(ClientProvider.compress)

额外要求 canClear(...),即 !provider.requiresServerSide()。

固定流程:

  1. clearGrid(provider, player, container, 0, forced=false) —— 先清场
  2. 逐槽校验网格已空,有残留就整体中止
  3. 取出 mouseSlot 的物品到鼠标上
  4. 用**拖拽分配(drag splitting)**把物品铺进网格: windowClick(-999, 0, 0, 5) 开始 → 对每个目标槽 windowClick(slot, 1, 0, 5) 加一个 → windowClick(-999, 2, 0, 5) 结束
  5. 找到第一个有物品的 SlotCrafting, windowClick(slot, 0, compressAll ? 1 : 0)(clickType 1 = Shift 点击 = 全部合成)
  6. dropOffMouseStack(..., mouseSlot.slotNumber) 归位
  7. 残留物品逐槽放回 mouseSlot,最后再 dropOffMouseStack

拖拽按钮号由 getDragSplittingButton(id, limit) = id & 3 | (limit & 3) << 2 算出,压缩路径固定用 id = 0/1/2、limit = 0,即按钮 0。

⚠️ 客户端路径的 3×3 分支依次点 start .. start+8 全部 9 槽; 2×2 分支只点 start、start+1、start+3、start+4 四个。

拆分(decompress)

服务端(HandlerCompress 的 isDecompress 分支):

result = findMatchingRecipe(new InventoryCraftingDecompress(container, mouseStack), world)
if (result != null && mouseStack.stackSize >= 1):
    do:
        addItemStackToInventory(result.copy())  →  mouseSlot.decrStackSize(1)
    while (compressAll && mouseSlot.getHasStack() && mouseSlot.getStack().stackSize >= 1)

每次只消耗 1 个产物物品,循环条件只看 stackSize >= 1, 所以「拆分」始终是 1→N 的还原方向,不存在 3×3/2×2 分派。

客户端(ClientProvider.decompress)流程:

  1. clearGrid(..., forced=false) + 空网格校验
  2. windowClick(mouseSlot) 拿起 → windowClick(start) 放进网格 槽 0
  3. 找到第一个有物品的 SlotCrafting 后 windowClick(slot, 0, decompressAll ? 1 : 0)
  4. dropOffMouseStack(..., mouseSlot.slotNumber)
  5. windowClick(start) + windowClick(mouseSlot) 把剩余物品取回

前后端一致性

对比项 服务端 HandlerCompress 客户端 ClientProvider
网格来源 provider,或 null 时按 9 必须 provider 非 null
清场 不清场 强制 clearGrid 且校验为空
铺货方式 直接构造 InventoryCrafting 模拟拖拽点击
触发合成 直接 addItemStackToInventory windowClick 到 SlotCrafting
compressAnywhere ✅ 生效 ❌ 不读该配置

⚠️ 因此 compressAnywhere 只在服务端有意义,与配置注释 「only works if installed on server」完全一致。

相关条目