容器兼容(InventoryUtility 注册)

[!INFO] Git Commit: 93da3e1 | Updated: 2026-10-01

本 mod 的第一大职责:把其它 mod 的容器注册进 StructureLib 的 com.gtnewhorizon.structurelib.util.InventoryUtility,使多方块自动放置 / 生存建造时能从这些 容器里自动取料,而不必把物品塞进玩家背包或箱子。

两个扩展点

InventoryUtility 提供两类注册入口:

  • registerInventoryProvider(String, InventoryProvider) — 按玩家取容器。StructureLib 在需要取料 时回调,返回 InventoryIterable<IInventory>(Iterable + 一个 getInventory() 用于标脏)。适用于 "玩家身上/身上某件装备里"的东西。
  • registerStackExtractor(String, ItemStackExtractor) — 按物品取容器。适用于"右键某个背包物品 就能展开成容器"的东西。详见 AE2 物品提取器。

7 条注册记录

标识符的数字前缀决定注册顺序(StructureLib 按该字符串排序,数字小的先注册)。 注意两个 0999-*-need-before-ae2 的命名明确要求排在 1000-ae2-* 之前。

注册标识符 类型 目标内容 源码
0999-ae2wct-need-before-ae2 stackExtractor WCT 无线合成终端 CompatWCT.java:18
0999-ae2fc-need-before-ae2 stackExtractor AE2FC 无线终端 CompatAE2FC.java:25
1000-ae2-wireless stackExtractor AE2 原版无线终端 CompatAppliedEnergistics.java:24
1000-ae2-portable-cell stackExtractor AE2 便携式存储单元 CompatAppliedEnergistics.java:31
1000-forestry-backpack stackExtractor 林业背包物品 CompatForestry.java:15
1000-adventure-backpack stackExtractor 探险背包物品 CompatAdventureBackpack.java:16
5001-baubles inventoryProvider 饰品栏 CompatBaubles.java:15
6000-adventure-backpack inventoryProvider 穿戴中的探险背包 CompatAdventureBackpack.java:12

5001 与 6000 是仅有的两个 provider,编号远高于提取器,符合"provider 在提取器之后补充"的排序意图。

饰品栏(CompatBaubles)

InventoryUtility.registerInventoryProvider("5001-baubles",
    player -> new InventoryIterable<>(BaublesApi.getBaubles(player)));

markDirty 直接透传给 inv.getInventory().markDirty()(CompatBaubles.java:25-27)——注意这是整个 饰品栏容器一次性标脏,而非按被取用的槽位精确标脏。

林业背包(CompatForestry)——ItemInventoryBackpack 的空 parent 补丁

林业自己的 ItemInventoryBackpack 在构造时需要一个 parent ItemStack,但从物品直接展开时没有 parent。 本 mod 用一个私有子类绕过:

private static class MyItemInventoryBackpack extends ItemInventoryBackpack {
    protected ItemStack getParent() {
        // this might be called during <init>. give it some dummy itemstack to not NPE
        return source == null ? new ItemStack(Items.feather) : source;
    }
}

CompatForestry.java:23-37。构造时先把 source 存为 null 传入 super(null, 背包尺寸, source),靠 getParent() 的空值分支返回 Items.feather(羽毛)避免 <init> 期间 NPE,随后在构造完成后回填真实 source。源码注释自称 hacky wacky, but works, for now.。背包容量取自 ((ItemBackpack) source.getItem()).getBackpackSize(),随林业背包类型变化,不是固定值。

探险背包(CompatAdventureBackpack)

同一个 mod 同时注册了 provider 与 extractor(CompatAdventureBackpack.java:12-20):

  • provider:仅当 Wearing.isWearingBackpack(player) 为真时返回 new InventoryBackpack(Wearing.getWearingBackpack(player)),否则返回 null;
  • extractor:对 ItemAdventureBackpack 物品返回 new InventoryBackpack(source)。

末影背包门控(CompatBackpackMod)

InventoryUtility.registerEnableEnderCondition(
    player -> Arrays.stream(player.inventory.mainInventory)
        .anyMatch(stack -> stack != null && stack.getItem() instanceof ItemBackpackBase
            && BackpackUtil.isEnderBackpack(stack)));

CompatBackpackMod.java:17-20。这是唯一不走 provider/extractor 的注册,作用是限制而非提供。 源码注释解释了原因:逐个读/写每一个背包都要多次磁盘 IO,自动访问会导致严重卡顿,因此只对末影背包 开放——因为末影背包是存在玩家数据 NBT 里的,访问不涉及磁盘。

事件拦截:InventoryBogoSorter

CompatBogoSorter 是唯一不调用 InventoryUtility 的 compat,改为注册一个 Forge 事件处理器 (CompatBogoSorter.java:19):

@SubscribeEvent @SideOnly(Side.CLIENT)
public void onBeforeSortInGui(BeforeSortEvent event) {
    if (!event.isFromKeybind()) return;
    if (event.getSortKeyCode() != MOUSE_MIDDLE) return;   // MOUSE_MIDDLE = -98
    ItemStack item = event.isInGui() ? event.getPlayer().inventory.getItemStack()
                                    : event.getPlayer().getHeldItem();
    if (item == null || !(item.getItem() instanceof ItemConstructableTrigger)) return;
    event.setCanceled(true);
}

作用是阻止误触:Bogo Sorter 默认绑中键整理背包,而中键同样是 StructureLib 构造触发器 的取通道热键。玩家手持/光标 悬停在构造触发器上时按下中键,会被取消以避免物品被整理掉。该处理只在客户端生效(@SideOnly(Side.CLIENT)), 且只在事件来自按键绑定时生效。

判定用的是 StructureLib 的 ItemConstructableTrigger(即 mod 依赖的具体原因之一),MOUSE_MIDDLE 是硬编码常量 -98(CompatBogoSorter.java:16),对应 1.7.10 中 LWJGL 之外的 Forge 键码约定。