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 的本地定义是死代码。
相关条目
- 配置文件 -
Blacklist Gui Class Name的默认值与类型 - 网络协议 -
SetAE2FakeSlot包体字段与握手 - NEI 接口实现总表 -
INEIGuiAdapter在接口清单中的位置 - 叠加层与 GUI 挂钩 - 同一套槽位命中算法
- Addon 架构与加载顺序 - 反射失败时 addon 保持 inactive