Arcane Workbench 覆盖(自动摆放与换杖)

基本信息

属性 值
类型 NEI GUI 覆盖(IStackPositioner + IOverlayHandler)
源码类 ArcaneOverlayHandler、ArcaneSlotPositioner
目标 GUI thaumcraft.client.gui.GuiArcaneWorkbench(奥法工作台)
基类 NEI DefaultOverlayHandler
注册时机 NEIConfig.loadConfig()(客户端)

功能:NEI 界面上的"自动摆放"按钮对奥法工作台生效——点击后把材料按正确位置放进 3×3 合成格,并自动替换 vis 不足的魔杖。

注册的 4 组覆盖

NEIConfig.loadConfig() 对同一个 GuiArcaneWorkbench 注册了 4 个 overlay 标识:

Overlay 标识 归属 handler 定位器 覆盖处理
thaumcraft.arcane.shaped Shaped Arcane ArcaneSlotPositioner ArcaneOverlayHandler
thaumcraft.arcane.shapeless Shapeless Arcane 同上 同上
thaumcraft.wands Wand Crafting 同上 同上
"crafting" NEI 内建普通合成 同上 同上

全部 4 个共用同一个 ArcaneSlotPositioner 实例和同一个 ArcaneOverlayHandler 实例。第 4 项 "crafting" 是为了让普通合成配方在奥法工作台里也能自动摆放。

ArcaneSlotPositioner

实现 NEI IStackPositioner,作用是让 NEI 摆放时按本模组的网格坐标重排材料。

positionStacks 对每个非 null 的 PositionedStack:

  • relx = 40 + (i % 3) * 24
  • rely = 40 + (i / 3) * 24

即 3 列 × N 行、间距 24 px 的紧凑网格(左上角 40,40)。

getSlotIndex(PositionedStack) 反向算出该材料属于第几个真实槽位,判别方式是两套坐标系的模运算:

布局 判据 换算
奥法配方(本模组) (relx - XPOS[0]) % 28 == 0 且 (rely - YPOS[0]) % 27 == 0 row = (relx - XPOS[0]) / 28,col = (rely - YPOS[0]) / 27
原版合成 否则 row = (relx - 25) / 18,col = (rely - 6) / 18

其中 XPOS[0] = 47、YPOS[0] = 38(见 Shaped Arcane)。源码注释分别是 // Arcane recipes 与 // Vanilla recipes。

最终返回 col * 3 + row。

为什么需要模 28 / 27:ShapedArcaneRecipeHandler 把材料放在 XPOS = {47,75,103}、YPOS = {38,65,92},间距是 28 / 27,而定位器输出的间距是 24 / 24。用模运算可以同时容纳两套布局。

ArcaneOverlayHandler

槽位范围

getCraftMatrixSlots(GuiContainer, IRecipeHandler):只返回 gui.inventorySlots 的下标 2 到 10(共 9 格,for 循环 i < 11)。

这与 mapIngredSlots 里的 2 + slotIndex 偏移一致——槽位 0、1 是工作台自身的两个特殊槽。

mapIngredSlots

对每个 PositionedStack,用 ArcaneSlotPositioner.getSlotIndex(stack) 得到索引,再取 gui.inventorySlots.inventorySlots.get(2 + slotIndex),每个材料包成单元素 Slot[]。

魔杖自动替换(核心特性)

transferRecipe(GuiContainer, IRecipeHandler, int recipeIndex, int multiplier):

  1. 取 gui.inventorySlots.inventorySlots.get(1)——槽位 1 是魔杖槽。
  2. getVisCost(handler, recipeIndex):遍历 handler.getOtherStacks(recipeIndex),对每个 instanceof ItemAspect 的栈 aspects.add(元素, stackSize * 100)。
    • 乘 100 是关键:元素物品的 stackSize 是 vis 点数,NEI 把它们打包显示时会缩放,这里放大回来得到真实需求。
  3. isValidWand(slot, cost) 为假 → tryReplaceWand(gui, slot, cost)。
  4. 最后交给 super.transferRecipe(...) 执行实际摆放。

isValidWand(Slot, AspectList):槽位有物、物品是 ItemWandCasting,且 wand.consumeAllVis(stack, thePlayer, vis, false, true)——注意最后两个参数是 false, true,源码语义为试算不实际扣除 + 忽略物品本身。这是一次 dry-run。

tryReplaceWand(GuiContainer, Slot, AspectList vis):从下标 11 扫到容器末尾找可用的 ItemWandCasting;找到就先用 FastTransferManager.clickSlot(gui, slot.slotNumber, 0, 1) 把原魔杖取出(取出后槽位有东西就直接放弃,避免物品丢失),再用 clickSlot(gui, i, 0, 1) 放入新魔杖。

因此实际行为是:不够 vis → 先腾出魔杖槽 → 换一把 vis 够的 → 再由父类完成材料摆放。

前置条件

  • 目标 GUI 必须是 GuiArcaneWorkbench(注册时限定)。
  • 目标物品必须继承 ItemWandCasting——这让该特性自动覆盖所有 ItemWandCasting 子类(README 举例 Thaumic Bases 的手环)。
  • 玩家 thePlayer 必须非 null(Minecraft.getMinecraft().thePlayer 直接使用,无判空)。

依赖的 open 字段

tryReplaceWand 与 isValidWand 不需要 AT;但 Mixin 里的元素渲染排除逻辑需要 GuiContainer 的 protected 字段,见 Access Transformer。

相关条目