Applied Energistics 2 集成

基本信息

属性 值
类型 NEI 拖拽适配 + 一条 C2S 网络包(Applied Energistics 2 addon)
@Mod 类 net.bdew.neiaddons.appeng.AddonAppeng,modid NEIAddons|AppEng
目标 mod appliedenergistics2(裸 modid,只查存在性)
侧 双端 init();loadClient() 注册 GUI 适配器
NEI 接口 INEIGuiAdapter(AppEngGuiHandler)
开关 Addons 的 Applied Energistics 2 + 分类内的 Blacklist Gui Class Name
反射字段 clsBaseGui、clsBaseContainer、clsSlotFake、mSlotFakeIsEnabled 四个静态缓存

目标类(全部反射获取)

init() 里用 Utils.getAndCheckClass 加载,任一失败被外层 catch (Throwable t) 捕获并打 "Error finding cell workbench classes",此时 active 保持 false。

缓存字段 类名 断言基类 何时加载
clsBaseContainer appeng.container.AEBaseContainer Container 双端
clsSlotFake appeng.container.slot.SlotFake Slot 双端
mSlotFakeIsEnabled SlotFake.getMethod("isEnabled") — 双端(方法句柄,非类)
clsBaseGui appeng.client.gui.AEBaseGui GuiContainer 仅客户端(if (side == Side.CLIENT))

双端都执行 ServerHandler.registerHandler(setWorkbenchCommand, new SetFakeSlotCommandHandler())——服务端必须装 NEI Addons,客户端的拖拽才有服务端可发包。

⚠️ clsBaseContainer 的实参写的是 appeng.container.AEBaseContainer(大写 E),而 clsBaseGui 写的是 appeng.client.gui.AEBaseGui。两个名字的驼峰风格在同一个类里不一致,均照抄自目标 mod 的实际类名。

拖拽行为(INEIGuiAdapter.handleDragNDrop)

前置条件全部满足才接管:clsBaseGui.isInstance(gui) 且 !checkBlacklist(gui) 且 AddonAppeng.clsSlotFake.isInstance(命中槽位) 且 SlotHelper.isSlotEnabled(命中槽位)。

命中槽位由 getSlotAtPosition 遍历 gui.inventorySlots.inventorySlots 找第一个几何命中的槽位得到(纯坐标,16×16,容差 ±1,见 开发者工具 的同一套算法)。

三种鼠标键对应三种语义:

键 行为 replace 堆叠数处理
左键(button == 0) 填满该槽 true min(stackSize, 127)
右键(button == 1) 放 1 个 false 强制 1
中键(button == 2) 填满并从鼠标上扣掉 true 填入 min(stackSize, 127) 后 draggedStack.stackSize -= 填入数

处理成功时一律 return true(告诉 NEI 事件已被消费),失败路径 return super.handleDragNDrop(...)。

stackSize 的 127 上限在源码里有注释说明:if (stack.stackSize > 127) { // add this check if no patch this in AE encoder ——AE2 的编码器不处理超过 127 的堆叠,是已知的硬上限。

服务端未安装时的降级

ClientHandler.enabledCommands 里没有 SetAE2FakeSlot(即服务端没装本 mod,或版本不匹配导致握手被丢弃)时:

  • 往聊天栏发一条 ChatComponentTranslation("bdew.neiaddons.noserver"),配色 EnumChatFormatting.RED。
  • en_US 文案为 "This function requires NEI Addons to be installed on the server"。
  • 仍然 return true(在 if 块之后统一返回),也就是说拖拽事件被吞掉但什么都没发生。

发送这条消息的分支在源码里位于 if (enabledCommands.contains(...)) 的 else,而最外层 return super.handleDragNDrop(...) 只在槽位类型/启用状态不匹配时到达。

SetAE2FakeSlot 的服务端处理

SetFakeSlotCommandHandler.handle:

步骤 行为
1 ItemStack.loadItemStackFromNBT(data.getCompoundTag("item")) 还原物品
2 int slotNum = data.getInteger("slot")
3 player.openContainer 必须是 clsBaseContainer 的实例,否则整段跳过
4 cont.getSlot(slotNum) 必须是 clsSlotFake 实例且 isEnabled,否则跳过
5 目标已有物品 且 replace == false 且 isItemEqual → 累加 stackSize,上限 127
6 slot.putStack(stack)

第 5 步的累加只在「同物品、非替换」时发生,即右键叠 1 个的行为在服务端会被合并。127 的上限同样带注释(// add this check if no patch this in AE encoder)。

⚠️ slotNum 直接喂给 cont.getSlot(slotNum),没有越界检查。客户端的槽位号来自当前 GUI,理论上构造异常包可使索引越界;ServerHandler.processCommand 的 catch (Throwable) 会兜住并打日志。

GUI 黑名单

AppEngGuiHandler.checkBlacklist 对 gui.getClass().getName() 与配置列表做全等字符串比较。

默认值(config/NEIAddons.cfg 的 Applied Energistics 2 分类):

索引 类名
0 com.glodblock.github.client.gui.GuiFluidPatternTerminal
1 com.glodblock.github.client.gui.GuiFluidPatternTerminalEx

两者都是 Glodblock 的 AE2 流体样板终端扩展(不属 AE2 本体)。注释原文:"These Gui won't have the NEI drag item handler from NEI addon."

因为是全等匹配,这些 GUI 的子类不会被黑名单——若 Glodblock 后续改名或包结构变动,黑名单会静默失效。

未被使用的私有方法

AppEngGuiHandler.isMouseOverSlot(GuiContainer, Slot, int, int) 存在但无调用点:getSlotAtPosition 里调的是同名的 INEIGuiAdapter 继承来的方法。源码里 isMouseOverSlot 的本地定义是死代码。

相关条目