可以,而且按 MineColonies 目前的代码结构,这个功能实际上只需要改 **`Restaur...

बनाया गया: 10 अगस्त 2026

का उपयोग करके उत्तर दिया गया GPT-5.6 Thinking द्वारा Chat01

प्रश्न

可以,而且按 MineColonies 目前的代码结构,这个功能实际上只需要改 RestaurantMenuModuleWindow,JEI 那边基本不用再写新的适配。

当前餐厅“食物”页面对应:

text
src/main/java/com/minecolonies/core/client/gui/modules/building/ RestaurantMenuModuleWindow.java

这个窗口里已经有两个你关心的列表:

  • menuList:已经加入餐厅菜单的食物
  • resourceList:可以添加到菜单的食物

两边每一行都已经把对应的 ItemStack 塞进了 ItemIcon。(GitHub)

更重要的是,MineColonies 已经存在现成的 JEI 接口

java
Compatibility.jeiProxy.showRecipes(Collection<ItemStack>)

它内部已经使用 RecipeIngredientRole.OUTPUT 打开 JEI,所以语义正好就是“这个物品怎么制作”。(GitHub)


1. 最小实现方案

在:

java
RestaurantMenuModuleWindow.java

增加两个 import:

java
import 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; }

因为这个文件本身已经:

java
import java.util.*;

所以 List.of(...) 不需要额外 import。


2. 为什么这个方法能知道你点的是哪个物品?

这跟 BlockUI 的实现有关。

BlockUI 的所有 GUI 元素最终都继承自 PanePane 中本身存在:

java
protected static Pane lastClickedPane;

而且每一个 Pane 收到鼠标点击时都会执行:

java
public boolean click(final double mx, final double my) { setLastClickedPane(this); return handleClick(mx - x, my - y); }

因此点击餐厅中的物品图标时,最后真正收到点击的是那个 ItemIcon,于是:

java
lastClickedPane instanceof ItemIcon

就能拿到它。(GitHub)

ItemIcon 本身又已经保存了对应的 ItemStack,并提供:

java
public ItemStack getItem()

所以直接:

java
final ItemStack stack = itemIcon.getItem();

就取得当前鼠标点击的食物。(GitHub)

整个流程就是:

text
Ctrl + 左键 RestaurantMenuModuleWindow.click() BlockUI View.click() 找到鼠标下最深层 Pane ItemIcon.click() lastClickedPane = 当前 ItemIcon itemIcon.getItem() ItemStack Compatibility.jeiProxy.showRecipes(...) JEI 配方界面

3. 为什么不要在这里直接 import JEI

例如你可能原本想这么写:

java
import mezz.jei.api.recipe.RecipeIngredientRole; import mezz.jei.api.constants.VanillaTypes; import mezz.jei.api.runtime.IJeiRuntime;

然后自己调用:

java
jei.getRecipesGui().show(...);

这里其实不推荐

原因是 MineColonies 把 JEI 作为可选兼容处理,已经专门设计了:

java
IJeiProxy

默认实现:

java
public interface IJeiProxy { default boolean isLoaded() { return false; } default boolean showRecipes(final Collection<ItemStack> stacks) { return false; } }

而:

java
Compatibility.jeiProxy

默认就是一个空实现;JEI 真正加载之后,MineColonies 的 JEIPlugin 才把它替换成真正的 JEI 实现。(GitHub)

所以你写:

java
Compatibility.jeiProxy.showRecipes(...)

有两个好处:

text
安装 JEI 打开 JEI 配方 没安装 JEI 返回 false 什么都不发生 不会因为餐厅 GUI 直接引用 JEI 类而出问题

这就是这个项目原本设计好的兼容层。


4. MineColonies 甚至已经替你写好了真正的 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()


5. 为什么用 OUTPUT,而不是 INPUT

JEI 的 focus 有不同角色。

这里 MineColonies 已经使用:

java
RecipeIngredientRole.OUTPUT

意义是:

text
我点击:面包 OUTPUT focus 查找“输出面包”的配方 小麦 → 面包

这正符合“这个食物怎么做”。

反过来,如果使用:

java
RecipeIngredientRole.INPUT

语义就会变成:

text
这个物品能拿来做什么?

例如点小麦可能显示:

text
小麦 → 面包 小麦 → 蛋糕 小麦 → ...

JEI 官方 API 的 focus factory 就是通过 RecipeIngredientRole + ingredient type + ingredient 创建这种查询。(GitHub)


6. isRestaurantFoodIcon() 为什么有必要

这是一个容易踩坑的地方。

RestaurantMenuModuleWindow 里除了:

java
menuList resourceList

还有一个:

java
ingredientslist

而它们里面的物品图标都使用 ItemIcon。当前代码里,已加入菜单的食物通过:

java
rowPane.findPaneOfTypeByID( RESOURCE_ICON, ItemIcon.class ).setItem(resource);

赋值;右边的可选择食物也是同样做法。下方原材料统计也同样放了 ItemIcon。(GitHub)

所以如果你只判断:

java
lastClickedPane instanceof ItemIcon

结果就会变成:

text
Ctrl + 点击食物 → JEI Ctrl + 点击食材统计 → JEI

实际上这个功能也不坏,甚至可能更方便。

如果你希望所有物品图标都支持 JEI

那代码还能再简单一点,直接删除:

java
if (!isRestaurantFoodIcon(itemIcon)) { return handled; }

以及整个:

java
private boolean isRestaurantFoodIcon(...)

这样餐厅页面的:

  • 已选食物
  • 可选食物
  • 食材需求统计

全部支持 Ctrl + 左键 → JEI

我反而比较推荐这种行为。

因为下面的食材列表本来就是告诉玩家:

餐厅大概需要这些原材料

此时 Ctrl+点胡萝卜、面包、调味品继续查它的配方,非常自然。


7. 我建议最终先写成这个版本

如果让我实际给 MineColonies 提这个 PR,我第一版会保持非常小:

java
import 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)


8. 后续可以继续做成更完整的 JEI 交互

这个简单版本验证成功后,其实可以把它抽象成 MineColonies 全局功能:

text
所有 MineColonies GUI 中的 ItemIcon Ctrl + 左键 → 查看 recipes / how to make Ctrl + 右键 → 查看 uses / what can this make

甚至不用每一个建筑窗口重复写。

例如:

text
餐厅食物 仓库最低库存 建造请求 工人请求物品 铁匠/木匠配方 大学研究材料 快递员请求 建筑所需资源

都可以统一接到 Compatibility.jeiProxy

不过第一步我建议只改 RestaurantMenuModuleWindow,先验证 BlockUI → ItemIconItemStack → 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

विचार

Searched 1 website

Worked for 8m 35s

उत्तर

已经做成一个独立 Forge 1.20.1 addon 源码工程,目标版本锁定为 MineColonies 1.20.1-1.1.1197

下载完整 addon 工程 ZIP

核心实现是对 BlockUI 的 Pane.click(DD)Z 做客户端 Mixin,然后过滤:

java
this instanceof ItemIcon && pane.getWindow() instanceof RestaurantMenuModuleWindow && Screen.hasControlDown()

因此实际效果是:

  • 普通左键:保持原 MineColonies 行为
  • Ctrl + 左键餐厅里的 ItemIcon:打开 JEI,查询该物品的制作配方
  • 包括已选食物、可选食物以及餐厅页面里的食材图标
  • 没装 JEI:MineColonies 的 JEI proxy 返回 false,不崩溃也不产生额外行为
  • 服务端不需要安装这个 addon

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 后,在项目目录执行:

bash
gradle build

成品会在:

text
build/libs/minecoloniesjeiclick-1.0.0.jar

然后只需要把这个 JAR 放进客户端mods 文件夹即可。

इस प्रश्नोत्तर को साझा करें