Custom Main Menu 适配
基本信息
| 属性 | 值 |
|---|---|
| 文件 | net/sxmaa/headlessnh/mixins/late/GuiCustomMixin.java |
| 目标类 | lumien.custommainmenu.gui.GuiCustom(remap = false) |
| 注入点 | func_73866_w_ @TAIL(@Dynamic) |
| Mixin 集 | JOIN_WORLD_ON_MAINMENU_LOAD_CMM(LATE) |
| 条件 | 必须安装 Custom Main Menu |
为什么需要它
MinecraftMixin 驱动流程的方式是合成 GuiButton 并派发给 actionPerformed——这是原版 GuiMainMenu 的接口。lumien 的 Custom Main Menu 换掉了整个主菜单实现,GuiMainMenu 的按钮事件不再存在,因此只要装了这个 mod,原版那条路就会失效。
GuiCustomMixin 是同一条逻辑的替代实现,挂在 CMM 自己的主菜单类上,改用 CMM 的动作系统。
触发条件与流程
注入点是 func_73866_w_(GuiScreen.initGui 的 SRG 名,即主菜单初始化完成)的 TAIL。守卫是 if (IntegrationTestController.isActive()) return;,与 MinecraftMixin 的 !Boolean.getBoolean("headlessnh.active") 语义等价,只是写成了正向形式。
命中后在自建线程里执行:
Thread.sleep(mainMenuSettleMillis())(默认 500 ms)onMainMenuReached()—— 仅首次返回 true,写主菜单 markerThread.sleep(markerCooldownMillis())(默认 0)awaitMainMenuGate()—— 等headlessnh.gate.mainmenupollMainMenuAction()—— 取"singleplayer"或"multiplayer",每个至多一次- 投递主线程:
new ActionOpenGUI(action).perform(null, (GuiCustom) ((Object) this))
this 在非 GuiCustom 上下文中没有静态类型,所以用 (GuiCustom) ((Object) this) 的双重转型绕过编译期检查。
与原版路径的差异
| MinecraftMixin(无 CMM) | GuiCustomMixin(有 CMM) | |
|---|---|---|
| 开关写法 | if (!Boolean.getBoolean("headlessnh.active")) return; |
if (isActive()) return; |
| 触发动作 | mainMenu.actionPerformed(new GuiButton(id, 0, 0, null)) |
new ActionOpenGUI(action).perform(null, guiCustom) |
| 后续流程 | 由 CMM 决定 | 同样由后续世界检测 Mixin 接手 |
[!WARNING] 本 Mixin 位于 LATE 阶段且标了
@Dynamic+remap = false,但GuiCustom与ActionOpenGUI是编译期直接 import 的(文件头部有import lumien.custommainmenu.gui.GuiCustom;)。也就是说编译这个 Mixin 时 Custom Main Menu 必须在 classpath 上;@Dynamic只能保护运行期不匹配的情况。若目标 mod 未安装,本 Mixin 不会加载——Mixins.java里的addRequiredMod(TargetedMod.CUSTOM_MAIN_MENU)保证了这一点。
加载顺序约束
Mixins.java 把本 Mixin 放在 LATE 阶段,而世界加载检测的 Celeritas 分支也在 LATE。LATE 阶段在 Angelica 的 tweaker 加载之后运行,此时 CMM 已完成类加载,才能安全地操作 GuiCustom。
相关条目
- MinecraftMixin - 无 CMM 时走的那条路径
- 世界加载检测 - 世界加载后由谁接手
- Mixin 加载与 Access Transformer - early/late 两阶段划分