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 TemplateRecipeHandler without override the getOverlayIdentifier function

即用 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。

相关条目