可以,而且按 MineColonies 目前的代码结构,这个功能实际上只需要改 **`Restaur...
Tạo vào: 10 tháng 8, 2026
Trả lời bằng GPT-5.6 Thinking bởi Chat01
Tạo vào: 10 tháng 8, 2026
Trả lời bằng GPT-5.6 Thinking bởi Chat01
可以,而且按 MineColonies 目前的代码结构,这个功能实际上只需要改 RestaurantMenuModuleWindow,JEI 那边基本不用再写新的适配。
当前餐厅“食物”页面对应:
textsrc/main/java/com/minecolonies/core/client/gui/modules/building/ RestaurantMenuModuleWindow.java
这个窗口里已经有两个你关心的列表:
menuList:已经加入餐厅菜单的食物resourceList:可以添加到菜单的食物两边每一行都已经把对应的 ItemStack 塞进了 ItemIcon。(GitHub)
更重要的是,MineColonies 已经存在现成的 JEI 接口:
javaCompatibility.jeiProxy.showRecipes(Collection<ItemStack>)
它内部已经使用 RecipeIngredientRole.OUTPUT 打开 JEI,所以语义正好就是“这个物品怎么制作”。(GitHub)
在:
javaRestaurantMenuModuleWindow.java
增加两个 import:
javaimport com.minecolonies.api.compatibility.Compatibility; import net.minecraft.client.gui.screens.Screen;
然后在这个类中加入下面两个方法:
java@Override public boolean click(final double mx, final double my) { // 防止拿到上一次点击留下的 Pane lastClickedPane = null; // 先让 BlockUI 正常处理这次点击。 // 点击 ItemIcon 时,它会把自己记录到 lastClickedPane。 final boolean handled = super.click(mx, my); // 只处理 Ctrl + 左键 if (!Screen.hasControlDown()) { return handled; } // 只处理物品图标 if (!(lastClickedPane instanceof final ItemIcon itemIcon)) { return handled; } // 只允许餐厅的“已选食物”和“可选食物”两个列表, // 不处理下面的 ingredientslist。 if (!isRestaurantFoodIcon(itemIcon)) { return handled; } final ItemStack stack = itemIcon.getItem(); if (stack == null || stack.isEmpty()) { return handled; } // MineColonies 已经封装好的 JEI 接口: // 按 OUTPUT 查询,也就是“如何制作这个物品” if (Compatibility.jeiProxy.showRecipes(List.of(stack.copy()))) { return true; } return handled; } /** * 判断点击的 ItemIcon 是否属于餐厅食物列表。 */ private boolean isRestaurantFoodIcon(final Pane pane) { for (Pane current = pane; current != null; current = current.getParent()) { if (current == menuList || current == resourceList) { return true; } } return false; }
因为这个文件本身已经:
javaimport java.util.*;
所以 List.of(...) 不需要额外 import。
这跟 BlockUI 的实现有关。
BlockUI 的所有 GUI 元素最终都继承自 Pane。Pane 中本身存在:
javaprotected static Pane lastClickedPane;
而且每一个 Pane 收到鼠标点击时都会执行:
javapublic boolean click(final double mx, final double my) { setLastClickedPane(this); return handleClick(mx - x, my - y); }
因此点击餐厅中的物品图标时,最后真正收到点击的是那个 ItemIcon,于是:
javalastClickedPane instanceof ItemIcon
就能拿到它。(GitHub)
ItemIcon 本身又已经保存了对应的 ItemStack,并提供:
javapublic ItemStack getItem()
所以直接:
javafinal ItemStack stack = itemIcon.getItem();
就取得当前鼠标点击的食物。(GitHub)
整个流程就是:
textCtrl + 左键 ↓ RestaurantMenuModuleWindow.click() ↓ BlockUI View.click() ↓ 找到鼠标下最深层 Pane ↓ ItemIcon.click() ↓ lastClickedPane = 当前 ItemIcon ↓ itemIcon.getItem() ↓ ItemStack ↓ Compatibility.jeiProxy.showRecipes(...) ↓ JEI 配方界面
例如你可能原本想这么写:
javaimport mezz.jei.api.recipe.RecipeIngredientRole; import mezz.jei.api.constants.VanillaTypes; import mezz.jei.api.runtime.IJeiRuntime;
然后自己调用:
javajei.getRecipesGui().show(...);
这里其实不推荐。
原因是 MineColonies 把 JEI 作为可选兼容处理,已经专门设计了:
javaIJeiProxy
默认实现:
javapublic interface IJeiProxy { default boolean isLoaded() { return false; } default boolean showRecipes(final Collection<ItemStack> stacks) { return false; } }
而:
javaCompatibility.jeiProxy
默认就是一个空实现;JEI 真正加载之后,MineColonies 的 JEIPlugin 才把它替换成真正的 JEI 实现。(GitHub)
所以你写:
javaCompatibility.jeiProxy.showRecipes(...)
有两个好处:
text安装 JEI ↓ 打开 JEI 配方 没安装 JEI ↓ 返回 false ↓ 什么都不发生 ↓ 不会因为餐厅 GUI 直接引用 JEI 类而出问题
这就是这个项目原本设计好的兼容层。
当前 JEIPlugin 中对应实现实际上就是:
java@Override public boolean showRecipes(final Collection<ItemStack> stacks) { final IJeiRuntime jei = JEIPlugin.this.jei; if (jei != null && !stacks.isEmpty()) { final IFocusFactory focusFactory = jei.getJeiHelpers().getFocusFactory(); final List<IFocus<?>> focuses = stacks.stream() .<IFocus<?>>map(stack -> focusFactory.createFocus( RecipeIngredientRole.OUTPUT, VanillaTypes.ITEM_STACK, stack)) .toList(); jei.getRecipesGui().show(focuses); return true; } return false; }
也就是说你提出的功能:
Ctrl + 鼠标左键一个食物 → 打开 JEI → 查看这个食物的做法
JEI 部分实际上已经 100% 存在。(GitHub)
你现在缺的只是:
餐厅界面 → 捕获 Ctrl+左键 → 把当前 ItemStack 传给
showRecipes()
JEI 的 focus 有不同角色。
这里 MineColonies 已经使用:
javaRecipeIngredientRole.OUTPUT
意义是:
text我点击:面包 OUTPUT focus ↓ 查找“输出面包”的配方 ↓ 小麦 → 面包
这正符合“这个食物怎么做”。
反过来,如果使用:
javaRecipeIngredientRole.INPUT
语义就会变成:
text这个物品能拿来做什么?
例如点小麦可能显示:
text小麦 → 面包 小麦 → 蛋糕 小麦 → ...
JEI 官方 API 的 focus factory 就是通过 RecipeIngredientRole + ingredient type + ingredient 创建这种查询。(GitHub)
isRestaurantFoodIcon() 为什么有必要这是一个容易踩坑的地方。
RestaurantMenuModuleWindow 里除了:
javamenuList resourceList
还有一个:
javaingredientslist
而它们里面的物品图标都使用 ItemIcon。当前代码里,已加入菜单的食物通过:
javarowPane.findPaneOfTypeByID( RESOURCE_ICON, ItemIcon.class ).setItem(resource);
赋值;右边的可选择食物也是同样做法。下方原材料统计也同样放了 ItemIcon。(GitHub)
所以如果你只判断:
javalastClickedPane instanceof ItemIcon
结果就会变成:
textCtrl + 点击食物 → JEI Ctrl + 点击食材统计 → JEI
实际上这个功能也不坏,甚至可能更方便。
那代码还能再简单一点,直接删除:
javaif (!isRestaurantFoodIcon(itemIcon)) { return handled; }
以及整个:
javaprivate boolean isRestaurantFoodIcon(...)
这样餐厅页面的:
全部支持 Ctrl + 左键 → JEI。
我反而比较推荐这种行为。
因为下面的食材列表本来就是告诉玩家:
餐厅大概需要这些原材料
此时 Ctrl+点胡萝卜、面包、调味品继续查它的配方,非常自然。
如果让我实际给 MineColonies 提这个 PR,我第一版会保持非常小:
javaimport com.minecolonies.api.compatibility.Compatibility; import net.minecraft.client.gui.screens.Screen;
然后:
java@Override public boolean click(final double mx, final double my) { lastClickedPane = null; final boolean handled = super.click(mx, my); if (!Screen.hasControlDown() || !(lastClickedPane instanceof final ItemIcon itemIcon)) { return handled; } final ItemStack stack = itemIcon.getItem(); if (stack == null || stack.isEmpty()) { return handled; } return Compatibility.jeiProxy.showRecipes(List.of(stack.copy())) || handled; }
这只有十几行核心代码。
效果就是:
text正常左键食物 → 保持 MineColonies 原行为 Ctrl + 左键食物图标 → JEI:显示该食物的制作配方 Ctrl + 左键食材图标 → JEI:显示该食材的制作配方 没安装 JEI → 不打开任何东西,也不会崩
而且不需要修改 JEIPlugin、不需要新建 JEI Category,也不需要自己保存 IJeiRuntime,因为 MineColonies 现在已经把这些工作做好了。(GitHub)
这个简单版本验证成功后,其实可以把它抽象成 MineColonies 全局功能:
text所有 MineColonies GUI 中的 ItemIcon Ctrl + 左键 → 查看 recipes / how to make Ctrl + 右键 → 查看 uses / what can this make
甚至不用每一个建筑窗口重复写。
例如:
text餐厅食物 仓库最低库存 建造请求 工人请求物品 铁匠/木匠配方 大学研究材料 快递员请求 建筑所需资源
都可以统一接到 Compatibility.jeiProxy。
不过第一步我建议只改 RestaurantMenuModuleWindow,先验证 BlockUI → ItemIcon → ItemStack → JEI 整条链路,这是最小、最好调试、也最适合作为第一个 commit 的实现。当前 BlockUI 的点击分发和 lastClickedPane 机制支持这种做法。(GitHub)
如果你是在直接修改 MineColonies 源码/准备给官方提交 PR,上面这版就非常适合;如果你实际想做的是一个独立 addon mod,不修改 MineColonies 本体,实现方式会改成 Mixin/GUI event 注入,我可以直接按你的 Minecraft 版本(1.20.1 或 1.21.1)+ Forge/NeoForge 给你写出完整的 addon 工程结构和 Mixin 代码。
帮我做成一个独立 addon mod,兼容minecolonies-1.20.1-1.1.1197
已经做成一个独立 Forge 1.20.1 addon 源码工程,目标版本锁定为 MineColonies 1.20.1-1.1.1197。
核心实现是对 BlockUI 的 Pane.click(DD)Z 做客户端 Mixin,然后过滤:
javathis instanceof ItemIcon && pane.getWindow() instanceof RestaurantMenuModuleWindow && Screen.hasControlDown()
因此实际效果是:
false,不崩溃也不产生额外行为MineColonies 1.1.1197 本身使用 Java 17、Forge 47.1.3、BlockUI 1.20.1-1.0.190-snapshot 和 JEI 15.1.0.19,工程已经按这些版本配置。(GitHub) MineColonies 现有的 showRecipes 确实使用 JEI 的 RecipeIngredientRole.OUTPUT,所以 addon 没有直接依赖 JEI API。(GitHub) 餐厅窗口本身也确实给列表中的 ItemIcon 填入了对应 ItemStack。(GitHub)
关键文件也可以直接看:
PaneMixin.java
build.gradle
mods.toml
README.md
我已经做过 Java 17 静态编译检查、Mixin JSON 校验以及展开后的 mods.toml TOML 校验,全部通过。BlockUI 1.0.190 的 BOScreen 只有左键才调用 window.click(),右键走独立的 rightClick(),所以这个注入不会误处理 Ctrl+右键。(GitHub)
目前没有附一个冒充“可运行”的预编译 JAR:当前执行环境没有 Gradle/Gradle Wrapper,无法实际运行 ForgeGradle 的 reobfJar。你本地装 Java 17 + Gradle 8.1.1 后,在项目目录执行:
bashgradle build
成品会在:
textbuild/libs/minecoloniesjeiclick-1.0.0.jar
然后只需要把这个 JAR 放进客户端的 mods 文件夹即可。