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 的判定:
- 用
KeyManager.getKeyCode("recipe.recipe")取 NEI 自己的配方键,失败或为KEY_NONE时回落到Keyboard.KEY_R;用途键同理回落Keyboard.KEY_U - 两者都没按 →
false GuiContainerManager.getStackMouseOver(gui)拿到悬停物品,不是ModItems.recipePanel或没有 snapshot →false- 否则调
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) 的顺序:
RecipeSnapshot.parseRecipeId解析失败 → 直接返回id.getResult()为null→ 返回usage = true→GuiUsageRecipe.openRecipeGui("item", result)返回- 试
GuiCraftingRecipe.openRecipeGui("recipeId", result, id),成功就返回 - 兜底:
GuiCraftingRecipe.createRecipeGui("item", true, result)打开该产物的全部配方,再gui.openTargetRecipe(id)跳到存下来的那一条
有三级降级是必要的——其他 mod 的 handler 可能已经变了(配方被删、handler 重写、NEI 版本不兼容)。