配方适配器
本 mod 带 7 个 IAdapter 实现,用来把第三方 mod 的 NEI 配方处理器翻译成
本 mod 认识的「原料 / 产物 / 催化剂」三组标签。每个适配器只在对应 mod 存在时才加载。
基本信息
| 属性 | 值 |
|---|---|
| 接口 | me.towdium.jecalculation.nei.adapter.IAdapter |
| 实现数量 | 7(Forestry / AE2 / GregTech6 / GregTech / GTPP / Avaritia / EnderIO / Thaum 共 8 个类,但 GregTech 与 GregTech6 互斥,实际最多同时 7 个) |
| 容器 | Adapter.adapters(public static List<IAdapter>) |
| 初始化 | Adapter.init(),由 NEICalculatorConfig.loadConfig() 调用 |
| 转换入口 | Adapter.handleRecipe(recipe, index, inputs, outputs) |
| 接口默认 | getAllOverlayIdentifier() 默认返回 Collections.emptySet() |
准确地说:
adapter/包下共 8 个IAdapter实现类,但Adapter.init()里GregTech与GregTech6是if / else互斥分支 (由GregTech6.isGT6()决定),所以同时最多注册 7 个。
加载条件(Adapter.init())
| 适配器 | 判定条件 | 检测日志 |
|---|---|---|
Forestry |
Loader.isModLoaded("Forestry") |
Forestry detected |
AE2 |
Loader.isModLoaded("appliedenergistics2") |
ae2 detected |
GregTech6 |
Loader.isModLoaded("gregtech") 且 GregTech6.isGT6() 为真 |
gregtech6 detected |
GregTech |
Loader.isModLoaded("gregtech") 且 !isGT6() |
gregtech5 detected |
GTPP |
Loader.isModLoaded("miscutils")(包在 try/catch 里,失败打 Init error with gt++) |
gt++ detected |
Avaritia |
Loader.isModLoaded("Avaritia") |
Avaritia detected |
EnderIO |
Loader.isModLoaded("EnderIO") |
EnderIO detected |
Thaum |
Loader.isModLoaded("thaumcraftneiplugin") |
Thaum detected |
init() 首尾各打一条日志(=====Just Enough Calculation Init Start===== /
… Finish=====)。
GregTech6.isGT6() 的判定
Class<?> buildInfoclz = Class.forName("gregtech.BuildInfo");
String version = (String) buildInfoclz.getField("version").get(null);
return version.startsWith("6.");
反射读 gregtech.BuildInfo.version,判断是否以 "6." 开头。
任何 ClassNotFoundException / NoSuchFieldException / IllegalAccessException
都吞掉并返回 false(即当作 GT5 处理)。
overlay 标识符
getAllOverlayIdentifier() 的返回值直接被 NEICalculatorConfig.loadConfig()
拿去 API.registerGuiOverlay 注册。汇总如下:
| 适配器 | handleRecipe 实现类数 |
overlay 标识符来源 |
|---|---|---|
AE2 |
1(空实现) | 硬编码 Arrays.asList("inscriber", "grindstone") |
Thaum |
1 | 硬编码 5 个 |
EnderIO |
1 | 硬编码 6 个 |
Avaritia |
1(空实现) | 硬编码 Arrays.asList("extreme_compression", "extreme") |
Forestry |
1 | 空集(未覆写,走接口默认 Collections.emptySet()) |
GTPP |
1 | 反射 + 硬编码 |
GregTech(GT5) |
1 | 反射 |
GregTech6 |
1 | 反射 |
Forestry是唯一一个不提供任何 overlay 标识符的适配器——它只做handleRecipe的数据转换,不会让 Forestry 机器的配方页多出一个计算器按钮。 Forestry 配方能被处理,是因为 Forestry 的处理器大多继承TemplateRecipeHandler且未覆写标识符,从而被loadConfig()里那个null通配标识符覆盖。
overlay 标识符逐个枚举
| 适配器 | 全部标识符 |
|---|---|
AE2 |
"inscriber"、"grindstone"(2 个) |
Thaum |
"arcaneshapedrecipes"、"arcaneshapelessrecipes"、"aspectsRecipe"、"cruciblerecipe"、"infusionCrafting"(5 个) |
EnderIO |
"EnderIOAlloySmelter"、"EIOEnchanter"、"EnderIOSagMill"、"EnderIOSliceAndSplice"、"EnderIOSoulBinder"、"EnderIOVat"(6 个) |
Avaritia |
"extreme_compression"、"extreme"(2 个) |
Forestry |
空集 |
GTPP |
反射 GTPP_Recipe$GTPP_Recipe_Map.sMappings + GTPP_Recipe$GTPP_Recipe_Map_Internal.sMappingsEx,再硬编码补 "GTPP_Decayables"、"Decayables" |
GregTech(GT5) |
isPreRecipeRefactor 为真 → 反射 gregtech.api.util.GT_Recipe$GT_Recipe_Map.sMappings;否则反射 gregtech.api.recipe.RecipeMap.ALL_RECIPE_MAPS |
GregTech6 |
反射 gregapi.recipes.Recipe$RecipeMap 的静态字段 RECIPE_MAPS,遍历取值读其 mNameNEI 字段 |
GT6 的反射实现对每个 map 单独 try/catch (IllegalAccessException) 跳过失败的项;
Class.forName 整体失败则 printStackTrace 并返回空集合(此时 GregTech
机器的配方页不会有计算器按钮,但不会崩)。
handleRecipe 的通用流程
Adapter.handleRecipe 先无条件收集原始数据:
// raw inputs
recipe.getIngredientStacks(index).stream()
.map(ps -> (Object[]) ps.items)
.forEach(inputs::add);
// raw outputs
PositionedStack resultStack = recipe.getResultStack(index);
if (resultStack != null) outputs.add(resultStack.items);
然后把同一个 recipe 依次喂给所有已加载的适配器:
try {
for (IAdapter adapter : adapters) {
adapter.handleRecipe(recipe, index, inputs, outputs);
}
} catch (Exception e) {
Utilities.addChatMessage(Utilities.ChatMessage.RECIPE_TRANSFER_ERROR);
JustEnoughCalculation.logger.error("Exception when handling recipe: " + recipe.getClass().getName());
e.printStackTrace();
}
整个循环被一个 try/catch 包住:任何一个适配器抛异常,后续适配器全部被跳过,
并向聊天栏发一条 RECIPE_TRANSFER_ERROR。
各适配器额外做的事
| 适配器 | handleRecipe 实际做什么 |
|---|---|
AE2 |
空实现 {} —— 纯粹只为贡献 inscriber / grindstone 两个 overlay 标识符 |
Avaritia |
空实现 {} —— 纯粹只为贡献 extreme_compression / extreme 两个 overlay 标识符 |
Forestry |
8 个工厂处理器逐一识别,从 RecipeHandlerBase.CachedBaseRecipe.getFluidTanks() 里挖出流体(见下表) |
Thaum |
命中 5 个神秘学处理器时,把「奥义(aspect)」物品从原料里剔除 |
EnderIO |
命中 SagMillRecipeHandler 时把 getOtherStacks() 全部塞进 outputs;命中 VatRecipeHandler 时反射挖流体 |
GTPP / GregTech / GregTech6 |
处理 GregTech 的虚拟机器(VM)配方结构 |
Thaum — 剔除奥义
静态块反射加载 5 个处理器类(全限定名前缀
com.djgiannuzz.thaumcraftneiplugin.nei.recipehandler.):
ArcaneShapedRecipeHandler、ArcaneShapelessRecipeHandler、AspectRecipeHandler、
CrucibleRecipeHandler、InfusionRecipeHandler
命中任一后遍历 inputs,把注册名为 thaumcraftneiplugin:Aspect 的 ItemStack
从原料列表中 it.remove() 掉——奥义是神秘学的「元数据」而非真实材料,
留在原料里会让计算结果完全失真。
Class.forName的ClassNotFoundException在这里是ignored静默吞掉的 (与 EnderIO 的printStackTrace不同)。
EnderIO — 轧机与槽罐
静态块反射加载(同样 ClassNotFoundException 只 printStackTrace):
| 用途 | 类名 |
|---|---|
defaultHandlers(唯一一项) |
crazypants.enderio.nei.SagMillRecipeHandler |
vat |
crazypants.enderio.nei.VatRecipeHandler |
- 命中
defaultHandlers:outputs.addAll(getOtherStacks(index))——碎矿机的副产物。 - 否则若
vat != null && vat.isInstance(recipe):反射crazypants.enderio.nei.VatRecipeHandler$InnerVatRecipe的arecipes字段取出CachedRecipe,再反射其result(→ outputs)与inFluid(→ inputs)两个字段。 反射前后用setAccessible(true/false)成对切换。
static块在类加载时执行,此时 EnderIO 若未安装,defaultHandlers为空、vat为null。但由于Adapter.init()只在isModLoaded("EnderIO")时才new EnderIO(),这不会造成实际故障。
Forestry 的槽位坐标判定
Forestry 适配器大量依赖 PositionedFluidTank.position.x 和
PositionedStack.relx 来区分「原料」和「产物」:
| 判定 | 含义 |
|---|---|
tank.position.x == 30 |
该流体槽是原料 → inputs.add(...) |
| 其他 x 值 | 该流体槽是产物 → outputs.add(...) |
positionedStack.relx == 34 |
该物品是原料 → inputs.add(items) |
| 其他 relx | 该物品是产物 → outputs.add(items) |
具体到 8 个工厂处理器:
| 处理器 | 提取方式 |
|---|---|
NEIHandlerBottler |
getFluids() 的第 0 组 → inputs |
NEIHandlerCarpenter |
getFluids() 的第 0 组 → inputs |
NEIHandlerCentrifuge |
getOtherStacks() → outputs |
NEIHandlerFabricator |
getFluids() 的第 0 组 → inputs |
NEIHandlerFermenter |
按 position.x == 30 分流 |
NEIHandlerMoistener |
流体第 0 组 → inputs;getOtherStacks() 按 relx == 34 分流 |
NEIHandlerSqueezer |
getFluids() 的第 0 组 → outputs |
NEIHandlerStill |
按 position.x == 30 分流 |
getFluids 有一句源码注释警告类型问题:It won't check the type——它直接强转
RecipeHandlerBase.CachedBaseRecipe,不做 instanceof 校验。
Adapter.convertFluid
一个静态辅助方法(不走 adapters 列表):
if (Loader.isModLoaded("gregtech")) {
if (GregTech6.isGT6()) return GregTech6.convertFluid(itemStack);
else return GregTech.convertFluid(itemStack);
}
return itemStack;
即装了 GregTech 时把「流体显示物品」转成 GregTech 流体,否则原样返回。
它被 NEIPlugin.getLabelUnderMouse() 和 NEIPlugin.openRecipeGui() 调用。
相关条目
- NEI 集成 - overlay 注册与三个钩子的实现
- 配方消歧 - 转换完成后导入界面的消歧浮层
- 本 mod 没有的东西 - 本 mod 不提供的数值维度