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") 语义等价,只是写成了正向形式。

命中后在自建线程里执行:

  1. Thread.sleep(mainMenuSettleMillis())(默认 500 ms)
  2. onMainMenuReached() —— 仅首次返回 true,写主菜单 marker
  3. Thread.sleep(markerCooldownMillis())(默认 0)
  4. awaitMainMenuGate() —— 等 headlessnh.gate.mainmenu
  5. pollMainMenuAction() —— 取 "singleplayer" 或 "multiplayer",每个至多一次
  6. 投递主线程: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。

相关条目