AE2 物品提取器

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

MEInventoryStackExtractor(86 行)与 WirelessTerminalStackExtractor(246 行)是本 mod 最复杂的 两个基类,负责从 AE2 无线终端 / 便携单元 这类"没有真实容器、只有网格访问权"的物品里取料。 三个 AE 系 compat(CompatAppliedEnergistics、CompatWCT、CompatAE2FC)都只是这两个基类的匿名子类。

继承链

InventoryUtility.ItemStackExtractor  (StructureLib 接口)
  └── MEInventoryStackExtractor        抽象:AE2 存储 + 能量预算
        └── WirelessTerminalStackExtractor  抽象:无线终端的网格定位 + 距离校验
              ├── CompatWCT   (IWirelessCraftingTerminalItem)
              └── CompatAE2FC  (ItemBaseWirelessTerminal)

CompatAppliedEnergistics 的两个注册不走无线分支:1000-ae2-wireless 用 WirelessTerminalStackExtractor,1000-ae2-portable-cell 直接用 MEInventoryStackExtractor。

MEInventoryStackExtractor

能量预算钳制

取料前先用模拟模式算出电力最多支持多少个:

count = Math.min(count, (int) energy.extractAEPower(count, Actionable.SIMULATE, PowerMultiplier.ONE));

MEInventoryStackExtractor.java:57。注释说明意图是"把取料数量限制在电力上限内,这样后面就不必反复检查 电力限制"。PowerMultiplier.ONE 表示按 1:1 换算,AE2 中 1 AE = 1 RF。

逐项提取

遍历 getAvailableItems() 的结果,对每个匹配的 ItemStack 计算剩余额度并尝试提取 (MEInventoryStackExtractor.java:59-69),累计到 found >= count 就提前返回。模拟模式走 inv.extractItems(..., Actionable.SIMULATE, ...),实际模式走 AEApi.instance().storage().poweredExtraction(source, inv, toExtract, actionSource) (poweredExtraction 会同时扣除电力并取出物品)。

声明支持的 API 类型

public boolean isAPIImplemented(APIType type) {
    return type == APIType.EXTRACT_ONE_STACK || type == APIType.MAIN;
}

MEInventoryStackExtractor.java:40-42。EXTRACT_ONE_STACK(取指定物品)由 getItem 实现; MAIN 由 takeFromStack 实现。其余 APIType 一律返回 false,StructureLib 遇到不支持的类型会跳过该来源。

抽象方法 fromItem

protected abstract Pair<IEnergySource, IMEInventoryHandler<IAEItemStack>> fromItem(ItemStack source, EntityPlayerMP player);

返回左能量源、右存储处理器。返回 null 表示"这个物品不适用",takeFromStack / getItem 都会直接返回 0。

便携单元的能量源是本 mod 自造的

1000-ae2-portable-cell 的 fromItem 里内联了一个 lambda 实现的 IEnergySource (CompatAppliedEnergistics.java:39-45),并附有吐槽注释:

// copied from appeng.items.contents.PortableCellViewer.extractAEPower
// seriously why this isn't part of ae itself?

行为是:按 usePowerMultiplier 调整额度,模拟模式返回 usePowerMultiplier.divide(min(amt, 当前电量)), 实际模式返回 usePowerMultiplier.divide(item.extractAEPower(source, amt))。注意模拟分支返回的是 比值而非绝对量,这是从 AE2 原样抄来的行为(注释已声明来源),并非本 mod 引入。

WirelessTerminalStackExtractor

fromItem 的解析链条较长,任何一步失败都返回 null(:51-87):

  1. AEApi.instance().registries().wireless().isWirelessTerminal(source) 必须为真;
  2. 取 IWirelessTermHandler,为 null 则放弃;
  3. wh.getEncryptionKey(source) 解析为 long(NumberFormatException 捕获后放弃);
  4. getLocatableBy(serial) 得到的对象必须是 IGridHost;
  5. 取 IGridNode(ForgeDirection.UNKNOWN)→ IGrid,两者任一为 null 则放弃;
  6. grid.getCache(IStorageGrid.class) → sg.getItemInventory() 非空;
  7. rangeCheck(source, player, grid) 必须通过;
  8. 用 new WirelessTerminalEnergySource(wh, source, player) 包装能量源;
  9. 用 getViewCellStorage(source) 取出视图单元,构建 IPartitionList 过滤表。

若过滤表为 null(第 8 步抽象方法返回 null),直接 Pair.of(power, rawInventory) 暴露整个网格; 否则包装成 ExtractionFilteredMEInventoryHandler。

距离校验

for (final IGridNode n : targetGrid.getMachines(TileWireless.class)) {
    final IWirelessAccessPoint wap = (IWirelessAccessPoint) n.getMachine();
    DimensionalCoord loc = wap.getLocation();
    if (!loc.isInWorld(player.getEntityWorld())) continue;
    double range = wap.getRange();
    double distX = ..., distY = ..., distZ = ...;
    if (dist < range * range) return true;
}

WirelessTerminalStackExtractor.java:89-102。遍历目标网格上所有 TileWireless 访问点,任意一个在玩家 所在维度内且平方距离小于 range² 即通过。注意 loc.isInWorld(player.getEntityWorld()) 这一句: 跨维度的访问点被直接跳过,因此玩家在别的维度时即使离得近也不算数。

两个子类各自覆盖了 rangeCheck 来放行各自的"增强版":

  • CompatWCT.java:26-29 — ((IWirelessCraftingTerminalItem) source.getItem()).checkForBooster(source) 为真则跳过距离校验(WCT 增幅器);
  • CompatAE2FC.java:40-42 — hasInfinityBoosterCard(source) 为真则跳过(AE2FC 无限增幅卡)。

WirelessTerminalEnergySource

WirelessTerminalStackExtractor.java:106-128。内部类,实现 IEnergySource.extractAEPower: 模拟模式返回 wth.hasPower(player, amt, is) ? amt : 0,实际模式返回 wth.usePower(...) ? amt : 0。 若 wth 或 is 为 null 直接返回 0.0。这让无线终端的耗电走 AE2 自己的权限与增幅器判定, 而不是本 mod 自己算距离。

ExtractionFilteredMEInventoryHandler

WirelessTerminalStackExtractor.java:130-245,一个 115 行的 IMEInventoryHandler 装饰器,作用是 把视图单元的过滤表套在网格取料上:

  • extractItems:if (!filter.isListed(request)) return null;——未列在视图单元里的物品一律不取(:179);
  • getAvailableItems:返回一个匿名 IItemList 包装,addStorage / addCrafting / addRequestable 三个写入方法全部先过 filter.isListed,其余方法(getFirstItem、size、iterator、findPrecise、 findFuzzy 等)直接委托给内部列表(:184-239);
  • injectItems:不加过滤,直接透传(:172-175,注释 // inject is not filtered)——本 mod 只管取料, 不干涉存入;
  • 其余 getAccess、isPrioritized、canAccept、getPriority、getSlot、validForPass、getChannel 全部透传。

三个子类的 getViewCellStorage 实现各不相同:

compat 返回的视图单元存储
CompatAppliedEnergistics () -> new WirelessTerminalViewCells(is)
CompatWCT () -> new WCTInventoryViewCell(is)
CompatAE2FC () -> new WirelessTerminalViewCells(is)