NEI 集成
本 mod 是 NotEnoughItems 的附属插件式整合(IConfigureNEI),提供 tooltip 钩子、
配方 overlay 钩子和 7 个第三方配方适配器。没有 REI / JEI 支持,也没有搜索提供者
(search provider)或自定义配方处理器(recipe handler)。
基本信息
| 属性 | 值 |
|---|---|
| 入口类 | me.towdium.jecalculation.nei.NEICalculatorConfig implements IConfigureNEI |
| 插件名 | Tags.MODNAME |
| 插件版本 | Tags.VERSION |
| tooltip 钩子 | JecaTooltipHandler implements IContainerTooltipHandler |
| overlay 钩子 | JecaOverlayHandler implements IOverlayHandler(@SideOnly(Side.CLIENT)) |
| 搜索提供者 | 无 |
| 自定义配方处理器 | 无(只消费别人的) |
| NEI 依赖 | @Mod 声明 dependencies = "required-after:NotEnoughItems"(硬依赖) |
NEICalculatorConfig 在本仓库内没有任何代码引用(grep -rn "NEICalculatorConfig"
只命中它自己的定义),它由 NEI 自己扫描模组 jar 找到并回调 loadConfig()。
硬依赖 NEI
主类声明:
@Mod(
modid = Tags.MODID, // jecalculation
name = Tags.MODNAME, // Just Enough Calculation
version = Tags.VERSION,
dependencies = "required-after:NotEnoughItems",
acceptedMinecraftVersions = "[1.7.10]")
required-after:NotEnoughItems 意味着没装 NEI 就根本进不了游戏,因此
ClientProxy.postInit() 里那句无条件的 NEIPlugin.init()(第 43 行)是安全的。
也因为 NEIPlugin.init() 内 getIndexedModList().get("NotEnoughItems") 之前
没有再做 isModLoaded 判断——那个判断由 FML 的 dependencies 代劳了。
NEICalculatorConfig 是唯一一个 @Mod 侧注册给 NEI 的类:
public String getName() { return Tags.MODNAME; }
public String getVersion() { return Tags.VERSION; }
⚠️
Tags.java里的四个常量都是构建期占位符(GRADLETOKEN_MODID等), 由 Gradle 的 token 替换(replaceGradleTokenInFile = Tags.java)在编译时填成真实值。 真实值来自gradle.properties:modId = jecalculation、modName = Just Enough Calculation。
1. loadConfig() — 注册 overlay
public void loadConfig() {
Adapter.init();
Set<String> baseOverlayIdentifiers = new HashSet<>(
Arrays.asList("crafting", "crafting2x2", "smelting", "fuel", "brewing", null));
Set<String> adapterIdentifiers = Adapter.adapters.stream()
.map(IAdapter::getAllOverlayIdentifier)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
Stream.concat(baseOverlayIdentifiers.stream(), adapterIdentifiers.stream())
.forEach(ident -> {
API.registerGuiOverlay(JecaGui.class, ident);
API.registerGuiOverlayHandler(JecaGui.class, new JecaOverlayHandler(), ident);
});
}
内置 overlay 标识符(6 个,含 1 个 null):
| 标识符 | 说明 |
|---|---|
"crafting" |
工作台 |
"crafting2x2" |
2×2 合成 |
"smelting" |
熔炉 |
"fuel" |
燃料 |
"brewing" |
酿造台 |
null |
所有继承 TemplateRecipeHandler 却没覆写 getOverlayIdentifier() 的处理器 |
null 那一条有源码注释说明:
null For the recipeHandlers which extend
TemplateRecipeHandlerwithout override thegetOverlayIdentifierfunction
即用 null 作通配符兜住一批没有显式标识符的处理器。适配器贡献的标识符见
配方适配器。
⚠️ 注意
loadConfig每次调用都Adapter.init(),而init()里是adapters.add(new X())而不是先clear()。NEI 若重复调用loadConfig()会导致适配器列表翻倍。
2. 催化剂的 NEI 版本门槛
NEIPlugin.init() 读取 NEI 版本并决定催化剂是否启用:
GuiContainerManager.addTooltipHandler(new JecaTooltipHandler());
String neiVersion = Loader.instance().getIndexedModList().get("NotEnoughItems").getVersion();
Version version = new Version(neiVersion);
if (version.isSuccess() && version.compareTo(CATALYST_NEI_VERSION) >= 0) {
NEIPlugin.catalystEnabled = true;
}
| 常量 | 值 |
|---|---|
CATALYST_NEI_VERSION |
new Version("2.1.0-GTNH") |
所以催化剂(catalyst)功能要求 NEI ≥ 2.1.0-GTNH。低于此版本时
isCatalystEnabled() 返回 false,JecaOverlayHandler.getCatalyst() 直接返回
Optional.empty(),配方不会带催化剂。
Loader.isModLoaded("NotEnoughItems")在getIndexedModList().get("NotEnoughItems")之前没有判断——这一点由@Mod的required-after:NotEnoughItems依赖声明兜住, 见上方「硬依赖 NEI」。
3. JecaTooltipHandler — 只做「记录鼠标下的标签」
三个方法里 两个是空实现(原样返回 currenttip):
| 方法 | 行为 |
|---|---|
handleTooltip(gui, mousex, mousey, currenttip) |
原样返回 currenttip(无改动) |
handleItemDisplayName(gui, itemstack, currenttip) |
原样返回 currenttip(无改动) |
handleItemTooltip(gui, itemstack, mousex, mousey, currenttip) |
若 gui instanceof JecaGui,调用 NEIPlugin.setLabelUnderMouse(itemstack);仍原样返回 |
本 mod 不往 tooltip 里添加任何文字。 它只是借这个回调把「鼠标当前悬停在哪个物品上」 记到
NEIPlugin.currentItemStack(一个private static ItemStack), 供界面上的「打开 NEI 配方 / Find It 搜索」按钮读取 (见NEIPlugin.getLabelUnderMouse())。
getLabelUnderMouse() 会先经过 Adapter.convertFluid(itemStack) 再 ILabel.Converter.from。
4. JecaOverlayHandler — 从 NEI 导入配方
overlayRecipe(GuiContainer firstGui, IRecipeHandler recipe, int recipeIndex, boolean shift):
if (firstGui instanceof JecaGui):
context = recipe.getClass()
if (gui.root instanceof GuiRecipe):
gui.root.transfer(convertRecipe(recipe, recipeIndex, context), context)
else:
guiRecipe = new GuiRecipe()
JecaGui.displayGui(true, true, guiRecipe) // ← 先切到配方编辑界面
guiRecipe.transfer(convertRecipe(...), context)
即:当前已经是配方编辑页就原地填充,否则先跳转到配方编辑页再填充。
convertRecipe 的分类
产出 EnumMap<Recipe.IO, List<Trio<ILabel, CostList, CostList>>>:
| 步骤 | 代码 |
|---|---|
| 收集原料 | Adapter.handleRecipe(recipe, recipeIndex, recipeInputs, recipeOutputs) |
| 原料入表 | recipeInputs.forEach(i -> merge(merged, Arrays.asList(i), context, Recipe.IO.INPUT)) |
| 产物入表 | recipeOutputs.forEach(o -> merge(merged, Arrays.asList(o), context, Recipe.IO.OUTPUT)) |
| 催化剂入表 | getCatalyst(recipe) 命中则 merge(merged, catalysts, context, Recipe.IO.CATALYST) |
merge 的代表标签选择
List<ILabel> list = objs.stream().map(ILabel.Converter::from).collect(toList());
if (list.isEmpty()) return;
ILabel rep = list.get(0).copy();
if (type == Recipe.IO.INPUT && list.size() != 1) rep = ILabel.CONVERTER.first(list, context);
只有 INPUT 且候选数 ≠ 1 时才走 CONVERTER.first 挑代表;OUTPUT 与
CATALYST 永远取第 0 个。这与 标签类型与合并 里
SUGGEST / FALLBACK 优先级的说明一致。
合并时若已有 Trio.three 与新算出的 CostList 相等,就用
ILabel.MERGER.merge(p.one, fin) 升级代表标签(让具体物品升格成矿物词典),
并把数量 strictMergeCostList 累加。
getCatalyst 的取法
if (!NEIPlugin.isCatalystEnabled()) return Optional.empty();
handlerName = handler.toString().split("@")[0] // ← 去掉 hash 后缀
handlerID = (handler instanceof TemplateRecipeHandler)
? ((TemplateRecipeHandler) handler).getOverlayIdentifier()
: null
info = GuiRecipeTab.getHandlerInfo(handlerName, handlerID)
return Optional.ofNullable(info == null ? null : info.getItemStack())
即催化剂 = 该 NEI 配方处理器自己的代表物品(NEI 处理器列表里显示的那个图标)。
handler.toString().split("@")[0] 是为了去掉 Class@hashcode 形式的类名后缀。
5. NEIPlugin 的两个辅助动作
| 方法 | 行为 |
|---|---|
openRecipeGui(Object rep, boolean usage) |
流体先用反射转成显示物品;ItemStack 走 GuiCraftingRecipe.openRecipeGui("item", rep),FluidStack 走 "liquid",usage 为真时改用 GuiUsageRecipe |
findItem(Object rep) |
走 Find It 的 FindItNetwork.CHANNEL.sendToServer(new FindItemRequest(...)) |
FluidStackToItemStack 内部类用 ReflectionHelper.getClass(...) 反射找
gregtech.api.util.GTUtility 或 gregtech.api.util.GT_Utility,再取
getFluidDisplayStack(FluidStack, boolean) 方法。找不到就静默返回 null。
注意
findItem里if (rep instanceof ItemStack) { ... }之后无条件return true, 即流体(未被反射转成物品时)也返回true。
相关条目
- 配方适配器 - 7 个
IAdapter各自贡献的 overlay 标识符与转换逻辑 - 配方消歧 -
transfer()导入后界面的消歧浮层 - 合成显示模式 -
Recipe.IO(3) 在 overlay 里的用法 - 本 mod 没有的东西 - 本 mod 不提供的数值维度