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) * 24rely = 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):
- 取
gui.inventorySlots.inventorySlots.get(1)——槽位 1 是魔杖槽。 getVisCost(handler, recipeIndex):遍历handler.getOtherStacks(recipeIndex),对每个instanceof ItemAspect的栈aspects.add(元素, stackSize * 100)。- 乘 100 是关键:元素物品的
stackSize是 vis 点数,NEI 把它们打包显示时会缩放,这里放大回来得到真实需求。
- 乘 100 是关键:元素物品的
isValidWand(slot, cost)为假 →tryReplaceWand(gui, slot, cost)。- 最后交给
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。
相关条目
- Shaped Arcane Crafting Handler - 网格坐标常量与 vis 来源
- Shapeless Arcane Crafting Handler - 同坐标系的另一个使用者
- Wand Crafting Handler - vis 成本的计算方
- Aspect 物品 - vis 栈的载体
- Access Transformer - 相关 GUI 字段开放