兼容门控
TaskNH 的每个可选依赖都用 Loader.isModLoaded 或类存在性检查隔离,没有装对应模组时相关代码完全不参与类加载。integration/ 包共 4 个类。
依赖声明
TaskNHMod 的 @Mod 注解:
dependencies = "required-after:gtnhlib@[0.11.51,);required-after:modularui2@[2.3.91,);"
+ "after:navigator;after:betterquesting;after:NotEnoughItems@[2.8.150,)"
| 模组 | 门控方式 | 门控内容 |
|---|---|---|
| gtnhlib | required-after:gtnhlib@[0.11.51,) 硬依赖 |
队伍系统(TeamManager)、网络层(NetworkChannel)、ColorResource |
| modularui2 | required-after:modularui2@[2.3.91,) 硬依赖 |
整个 GUI 与主题系统 |
| navigator | after:navigator + Loader.isModLoaded |
地图图层,见 Navigator 地图图层 |
| betterquesting | after:betterquesting + 类存在性 |
任务右键菜单,见下 |
| NotEnoughItems | after:NotEnoughItems@[2.8.150,) + ModularUI.Mods.NEI.isLoaded() |
槽位配方查询、多方块预览 |
| serverutilities | 无注解声明,Loader.isModLoaded |
权限等级,见 权限 |
| blockrenderer6343 | 无注解声明,Loader.isModLoaded |
多方块预览按钮 |
源码注释解释了 NEI 为什么用 after: 而不是 required-after::「FML only checks the version of an after: mod when it is installed, so NEI stays optional.」
gradle.properties 里 usesMixins = false、coreModClass =(空),mcmod.info 的 dependencies 与 requiredMods 均为空数组。
BetterQuesting 集成(2 个类)
integration/BQIntegration.java + integration/BetterQuestingIntegration.java。
双重门控:
Loader.isModLoaded("betterquesting")Class.forName("betterquesting.api2.client.gui.context.IQuestContextMenuEntry", false, ...)—— 即IQuestContextMenuEntry类存在,注释标注该 API 是「BetterQuesting 3.8.72-GTNH 新增的」
用 Class.forName 的第二个参数 false(不初始化)。BQIntegration 的类注释强调:它绝不能直接引用任何 betterquesting 类,否则没装 BQ 时这个类本身就加载不了。检查失败只打一条 info 日志(缓存结果,不重复检查)。
ClientProxy.init() 里的门控位置也有讲究,注释写明:「Checked here rather than in the integration class, so that class never loads without the mods it uses.」
功能:在 BQ 的任务右键菜单注册一项 “Add to TaskNH”,点开后把该 BQ 任务转成 TaskNH 任务:
- 标题取
NativeProps.NAME,图标取NativeProps.ICON(toIconStack复制一份并把stackSize置 1) - 遍历
quest.getTasks(),把每个TaskRetrieval类型的子任务的requiredItems(BigItemStack)转成清单项,标题是"<数量>x <物品名>",并设上trackItem/trackItemCount,如果required.hasOreDict()就设trackOre——注释说「一个要求标签的任务接受该标签下任何物品,所以清单项也这样」 - 这是
trackOre唯一的写入来源(见 任务数据模型) - 完成后走
TaskNHNetwork.sendEditToServer(task, new CreateTaskPacket(task))——即按普通新建任务的路径提交,客户端聊天回一条绿色的 “TaskNH: task … created.”
NEI 集成
integration/NEIRecipeIntegration.java,@SideOnly(Side.CLIENT)。类注释:「References NEI classes directly, so callers must check isAvailable() first」——所以调用方必须自己判 ModularUI.Mods.NEI.isLoaded()。
isAvailable() 就是 ModularUI.Mods.NEI.isLoaded()。showRecipes(ItemStack) 转发到 GuiCraftingRecipe.openRecipeGui("item", stack),返回 false 表示该物品没有配方。槽位左键行为见 主界面 GUI。
多方块预览按钮
integration/MultiblockTaskIntegration.java,@SideOnly(Side.CLIENT)。这是消费其他模组的多方块,不是自身提供多方块——见分类页「本 mod 没有多方块结构」一节。
门控在 ClientProxy.init(),两个条件都要满足:
ModularUI.Mods.NEI.isLoaded() && Loader.isModLoaded("blockrenderer6343")
实现:监听 BlockRenderer6343 在 NEI 中展示多方块预览时发出的 GuiRecipeButton.UpdateRecipeButtonsEvent.Post,if (!(ref.handler instanceof MultiblockHandler)) return;,然后在预览右上角(x = Math.min(166, event.width) - BUTTON_WIDTH, y = 0)加一个 + 按钮。事件优先级是 EventPriority.LOW,注释解释:BlockRenderer6343 会在默认优先级清空这个列表,若先加就会被抹掉。
按钮 ID 从 200 起(BUTTON_ID_START + ref.recipeIndex),注释说是为了避开 NEI 自己分配的配方按钮 id。
单层保护:isSingleLayer() 反射读 GuiMultiblockHandler 的 protected 字段 layerIndex(-1 表示全部)。预览的 Layer 控件若选中某一层,部件列表就只有那一层,会做出缺件、可能丢掉控制器的任务,因此直接拒绝并提示「Set Layer to All first」。findLayerIndex() 反射失败时返回 null,此时 isSingleLayer() 返回 false——注释:「A newer BlockRenderer6343 without the field keeps the button working rather than dead.」
生成任务的规则:
- 标题取
handler.getFullRecipeName(),图标取parts.get(0).item(NEI 把控制方排在最前,注释说明) - 每个部件生成一条清单项,标题
"<数量>x <物品名>",设trackItem与trackItemCount BRUtil.hatchFilter.test(stack)命中的部件被跳过——注释:「Any tier of hatch fits its slot, so tracking the one the preview shows would miss the others.」即不把任何等级的输出仓做成追踪项- 「超过追踪上限的部件,完整数量仍保留在标题里」(
clampTrackItemCount夹取,标题用的是未夹取的stack.stackSize) - 部件列表为空则直接返回
- 同样走
sendEditToServer+CreateTaskPacket
提示文案:tasknh.chat.multiblock_created(成功)、tasknh.chat.multiblock_single_layer(单层被拒)、tooltip 里的 tasknh.gui.multiblock.single_layer。
Navigator 集成
见 Navigator 地图图层。门控是 ClientProxy.init() 里的 Loader.isModLoaded("navigator")。
ServerUtilities 集成
见 权限。未在 @Mod 注解里声明,纯靠 Loader.isModLoaded("serverutilities") 静态常量加一个桥接类 TaskNHPermissionsSU 隔离。