NEI 集成

[!INFO] 硬依赖 required-after:NotEnoughItems(mcmod.info 写明 NotEnoughItems@[2.8.150,))| 源码 client/、NEIRecipePanelsConfig.java

这是本 mod 的核心用途:把 NEI 配方界面里的配方变成挂在墙上的面板。下面按"实现了哪些接口"和"调用了哪些 NEI API"两条线分别列出。

实现的 NEI 接口(共 2 个)

接口 实现类 注册方式
codechicken.nei.api.IConfigureNEI NEIRecipePanelsConfig 见下方说明
codechicken.nei.guihook.IContainerInputHandler PanelInputHandler ClientProxy.init → GuiContainerManager.addInputHandler(new PanelInputHandler())

IConfigureNEI — NEIRecipePanelsConfig

方法 返回 实现
loadConfig() void 只打一行日志 NEI Recipe Panels 的 “NEI plugin loaded”
getName() String 常量 "NEI Recipe Panels"
getVersion() String Tags.VERSION(由 Gradle 生成的版本 token)

注意这个类是惰性的:loadConfig 不做任何注册,也不读取本 mod 自己的配置文件(本 mod 的配置走 Forge Configuration,与 NEI 的 ini 配置是两套系统)。另外 src/main/resources 下没有 assets/neirecipepanels/plugins/ 之类目录,源码里也没有任何 registerRecipeHandler / registerOverlayHandler / registerCraftingHandler 调用,所以这个实现类的发现完全依赖 NEI 侧的插件扫描,本仓库只提供类本身。

IContainerInputHandler — PanelInputHandler

在物品栏/容器界面里,对准 配方面板物品 按 NEI 的配方/用途键即可打开它编码的配方。

方法 返回 实现
lastKeyTyped true 唯一有实际逻辑的方法
keyTyped false 空实现
onKeyTyped void 空实现
mouseClicked / mouseClicked false 空实现
mouseUp void 空实现
mouseScrolled false 空实现
onMouseScrolled void 空实现
mouseDragged void 空实现

lastKeyTyped 的判定:

  1. 用 KeyManager.getKeyCode("recipe.recipe") 取 NEI 自己的配方键,失败或为 KEY_NONE 时回落到 Keyboard.KEY_R;用途键同理回落 Keyboard.KEY_U
  2. 两者都没按 → false
  3. GuiContainerManager.getStackMouseOver(gui) 拿到悬停物品,不是 ModItems.recipePanel 或没有 snapshot → false
  4. 否则调 PanelRecipeOpener.open(snapshot, usage) 并返回 true

KeyManager.getKeyCode 整个包在 try/catch (Throwable) 里,因为该绑定 id 在某些 NEI 构建上不存在。

调用的 NEI API(未实现,仅消费)

API 用途
IRecipeHandler RecipeSnapshot.capture 抓取刻印;PanelFboManager 解析并调用 drawBackground / drawForeground;ResolvedRecipe.of 读取 getResultStack / getIngredientStacks / getOtherStacks / getRecipeName
Recipe.RecipeId 面板真正持久化的东西,序列化成一个 JSON 字符串;of(handler, index) / of(JsonObject) / toJsonObject() / getResult()
RecipeHandlerRef RecipeHandlerRef.of(recipeId) 把存下来的 id 重新定位到当前还活着的 handler 与配方序号
PositionedStack 每个材料格的相对坐标 relx/rely、物品列表 items、概率 getChance()
StackInfo StackInfo.getAmount(stack) 判断输入格是否为 0 数量的 “NC”(不消耗)标记
GuiRecipe 兜底打开该产物的全部配方后跳转
GuiCraftingRecipe openRecipeGui("recipeId", result, id) / createRecipeGui("item", true, result)
GuiUsageRecipe openRecipeGui("item", result) 打开用途页
GuiRecipeButton 面板按钮的基类 + GuiRecipeButton.UpdateRecipeButtonsEvent.Post 事件
GuiRecipeTab getHandlerInfo(handler) 取画布尺寸与 y 偏移
HandlerInfo getWidth / getHeight / getYShift / DEFAULT_WIDTH / DEFAULT_HEIGHT
Badge 概率与 NC 角标的绘制
KeyManager 取 NEI 的配方/用途键位
GuiContainerManager 注册输入处理器、悬停物品查询、物品与角标绘制

配方界面的面板按钮

GuiRecipeButtonHandler 订阅 GuiRecipeButton.UpdateRecipeButtonsEvent.Post,在 NEI 配方界面右侧按钮列上方插入一个按钮:

  • 列位置 x:event.buttonList 为空时用 Math.min(166, event.width) - 12,否则沿用最后一个按钮的 xPosition
  • 行位置 y:event.height - 18 - 13 * event.buttonList.size(),即在收藏键 / 覆盖层按钮上面

GuiRecipePanelButton 继承 GuiRecipeButton:

项 值
按钮 ID 44251001
字面 "P"
可用性 update() 里检查玩家背包(含光标栈)是否有 配方蓝图
点击 RecipeSnapshot.capture(handlerRef.handler, handlerRef.recipeIndex).writeToNBT() → MakeRecipePanelMessage 发给服务端
提示 有蓝图 neirecipepanels.button.imprint = Imprint a recipe panel;无蓝图 neirecipepanels.button.needBlueprint = Needs a Recipe Blueprint

按钮的可用性是纯客户端判断,不看服务端的 panelMode,所以在 DISABLED 模式下按钮依然可点、只是点了没反应(服务端静默丢弃)。handleHotkeys / lastKeyTyped / drawItemOverlay 全部空实现,即这个按钮不吃收藏热键、不画物品角标。

打开已刻面板的配方

PanelRecipeOpener.open(snapshot, usage) 的顺序:

  1. RecipeSnapshot.parseRecipeId 解析失败 → 直接返回
  2. id.getResult() 为 null → 返回
  3. usage = true → GuiUsageRecipe.openRecipeGui("item", result) 返回
  4. 试 GuiCraftingRecipe.openRecipeGui("recipeId", result, id),成功就返回
  5. 兜底:GuiCraftingRecipe.createRecipeGui("item", true, result) 打开该产物的全部配方,再 gui.openTargetRecipe(id) 跳到存下来的那一条

有三级降级是必要的——其他 mod 的 handler 可能已经变了(配方被删、handler 重写、NEI 版本不兼容)。

相关条目