Mixin 加载阶段
基本信息
| 属性 | 值 |
|---|---|
| 类型 | 特殊机制(Mixin 挂载与目标判定) |
| Mixin 清单 | com.caedis.freecam.mixins.Mixins(enum,实现 IMixins) |
| 目标 mod 判定 | com.caedis.freecam.mixins.TargetedMod(enum,实现 ITargetMod) |
| 早期加载器 | core/FreecamCore.java——IFMLLoadingPlugin + IEarlyMixinLoader |
| 后期加载器 | mixins/LateMixinsLoader.java——@LateMixin + ILateMixinLoader |
| coremod 声明 | gradle.properties 中 coreModClass = core.FreecamCore |
| Mixin 基础设施 | GTNHLib 风格的 IMixins / MixinBuilder / TargetModBuilder,refmap mixins.freecam-gtnh.refmap.json |
三个 Mixin 配置文件
| 配置文件 | 声明位置 | 加载者 |
|---|---|---|
mixins.freecam-gtnh.early.json |
FreecamCore.getMixinConfig() |
早期阶段 |
mixins.freecam-gtnh.late.json |
LateMixinsLoader.getMixinConfig() |
后期阶段 |
mixins.freecam-gtnh.json |
— | 无任何代码引用(grep "mixins.freecam-gtnh.json" src/main/java 无结果) |
三者共同字段:required: true、minVersion: "0.8.3-GTNH"、refmap: "mixins.freecam-gtnh.refmap.json"、
target: "@env(DEFAULT)"、compatibilityLevel: "JAVA_8"。
early / late 两个配置额外带 package(分别是 com.caedis.freecam.mixins.early 与
com.caedis.freecam.mixins.late)与 mixinextras: {minVersion: "0.5.0"}。
只有 early 配置带 "injectors": {"maxShiftBy": 2},允许注入点在 2 字节内做位移调整。
mixins.freecam-gtnh.json 没有 package、也没有 mixins 数组,是 GTNH 构建脚本生成的
基础配置,仅由上面两个配置的 refmap 与 minVersion 约束共享。
早期 vs 后期
| 维度 | 早期(EARLY) | 后期(LATE) |
|---|---|---|
| 清单项 | Mixins.FREECAM |
Mixins.WAILA、Mixins.THAUMCRAFT、Mixins.HOLOINVENTORY |
| 加载类 | FreecamCore |
LateMixinsLoader(@LateMixin 注解) |
| 接口 | IEarlyMixinLoader |
ILateMixinLoader |
| 取混合的方法 | IMixins.getEarlyMixins(Mixins.class, loadedCoreMods) |
IMixins.getLateMixins(Mixins.class, loadedMods) |
| 目标 | 原版 Minecraft 客户端类 | 第三方 mod 的类 |
| 条件 | 无 | 每个清单项都 addRequiredMod(TargetedMod.X) |
remap |
默认(需要 SRG 重映射) | 全部 remap = false |
| 目标包 | com.caedis.freecam.mixins.early.minecraft |
com.caedis.freecam.mixins.late.* |
阶段划分的可观察依据:早期清单项不带 addRequiredMod 且只针对原版类,任何游戏实例都必须加载;
后期清单项每一项都带 addRequiredMod,且目标类带 remap = false(第三方类名未混淆,
不存在 SRG 映射可重映射)。源码中未直接写出阶段划分的文字理由,上述差异均为从代码读出的事实。
TargetedMod:目标 mod 判定
TargetedMod(TargetedMod.java)三个取值全部把 coreModClass 传成 null,
即只按 modId 判定,不按核心类判定:
| 枚举值 | coreModClass |
modId |
|---|---|---|
WAILA |
null |
Waila |
THAUMCRAFT |
null |
Thaumcraft |
HOLOINVENTORY |
null |
holoinventory |
⚠️ modId 字面量大小写不统一:Waila、Thaumcraft 用首字母大写,holoinventory 全小写。
这直接写进了 setModId(modId),没有做任何规范化。
注意这套机制与 兼容模组 里的 Mods 枚举是两套彼此独立的第三方 mod 判定方式:
Mods 走 Loader.isModLoaded + class 文件存在性探测,用于运行时行为分支;
TargetedMod 走 TargetModBuilder 传进 MixinBuilder.addRequiredMod,用于加载期决定是否应用 Mixin。
全部 Mixin 枚举项
| 枚举项 | 阶段 | 目标类(@Mixin) |
注入点数量 | 门控条件 |
|---|---|---|---|---|
FREECAM |
EARLY | Minecraft、EntityRenderer、EntityLivingBase、MovementInputFromOptions |
1×@Redirect + 4×@Inject(Minecraft)、1×@Redirect + 1×@Redirect + 1×@Inject + 1×@ModifyExpressionValue + 1×@Inject(EntityRenderer)、1×@Inject(EntityLivingBase)、1×@Inject(MovementInputFromOptions) |
无条件 |
WAILA |
LATE | mcp.mobius.waila.overlay.OverlayRenderer |
1×@ModifyReturnValue |
TargetedMod.WAILA |
THAUMCRAFT |
LATE | thaumcraft.client.lib.RenderEventHandler |
4×@Inject |
TargetedMod.THAUMCRAFT |
HOLOINVENTORY |
LATE | net.dries007.holoInventory.client.Renderer |
1×@ModifyExpressionValue |
TargetedMod.HOLOINVENTORY |
各 Mixin 具体打的是什么补丁,见下表。
Mixin 逐条枚举
| Mixin 类 | 目标方法 | 注入器 | 补丁内容 |
|---|---|---|---|
minecraft.MixinMinecraft |
Minecraft.runTick |
@Redirect 字段 PUTFIELD GameSettings.thirdPersonView |
已激活时强制写回 0,屏蔽 F5 视角切换 |
minecraft.MixinMinecraft |
func_147115_a |
@Inject HEAD + cancellable |
取消(开发者命名为 cancelLeftClick) |
minecraft.MixinMinecraft |
func_147116_af |
@Inject HEAD + cancellable |
取消(cancelMiddleClick) |
minecraft.MixinMinecraft |
func_147121_ag |
@Inject HEAD + cancellable |
取消(cancelRightClick) |
minecraft.MixinMinecraft |
func_147112_ai |
@Inject HEAD + cancellable |
取消(cancelPickBlock) |
minecraft.MixinMinecraft |
@Unique freecam$shouldCancel() |
私有辅助方法 | isActive() && !isPlayerControlled() 的统一判定 |
minecraft.MixinEntityRenderer |
EntityRenderer.updateCameraAndRender |
@Redirect INVOKE EntityClientPlayerMP.setAngles(FF)V |
鼠标 yaw/pitch 改写到 renderViewEntity 而非玩家 |
minecraft.MixinEntityRenderer |
EntityRenderer.setupFog |
@Redirect INVOKE ActiveRenderInfo.getBlockAtEntityViewpoint |
配合 禁用浸没雾,水/岩浆返回 Blocks.air |
minecraft.MixinEntityRenderer |
EntityRenderer.getMouseOver |
@Inject HEAD + cancellable |
玩家操控时改用 thePlayer.rayTrace(reach, partialTicks) 覆写 objectMouseOver |
minecraft.MixinEntityRenderer |
EntityRenderer.updateLightmap |
@ModifyExpressionValue 字段 GETFIELD GameSettings.gammaSetting |
配合 全亮度 返回 100.0F |
minecraft.MixinEntityRenderer |
EntityRenderer.renderHand |
@Inject HEAD + cancellable |
配合 显示手部 取消手部渲染 |
minecraft.MixinEntityLivingBase |
EntityLivingBase.setHealth |
@Inject HEAD |
配合 受伤时禁用 排程退出 |
minecraft.MixinMovementInputFromOptions |
updatePlayerMoveState |
@Inject RETURN |
清零 moveStrafe/moveForward/jump/sneak |
waila.MixinOverlayRenderer |
OverlayRenderer.isOverlayVisible |
@ModifyReturnValue RETURN |
original && !RevealGate.shouldHide() |
thaumcraft.MixinRenderEventHandler |
drawTagsOnContainer |
@Inject HEAD + cancellable |
取消护目镜容器标签 |
thaumcraft.MixinRenderEventHandler |
drawTextInAir |
@Inject HEAD + cancellable |
取消护目镜悬浮文字 |
thaumcraft.MixinRenderEventHandler |
showScannedBlocks |
@Inject HEAD + cancellable |
取消魔能检测器扫描脉冲 |
thaumcraft.MixinRenderEventHandler |
renderMarkedBlocks |
@Inject HEAD + cancellable |
取消傀儡放置/铃标记 |
holoinventory.MixinRenderer |
Renderer.renderEvent |
@ModifyExpressionValue 字段 GETFIELD Renderer.enabled:Z |
original && !RevealGate.shouldHide() |
三个后期 Mixin 的目标类全部带 remap = false。holoinventory 那条的源码注释说明了为何用
@ModifyExpressionValue 而不是写字段:enabled 由 KeyManager 拥有并会持久化到配置,
改写读值才不会与用户的开关按键冲突。
访问变换器
META-INF/freecam_at.cfg 只有一行:
public net.minecraft.client.audio.SoundManager$SoundSystemStarterThread。
这让 Mixin 补丁后的相机在移动时会正常触发音效线程(相机继承 EntityPlayer,
原版 Entity.stepSound 会调用 playStepSound,SoundSystemStarterThread 需要 public 才能被跨线程启动)。