Mixin 与 LATE 注入

基本信息

属性 值
类型 构建 / 加载期设定
gradle 声明 gradle.properties 中 usesMixins = true、mixinsPackage = mixins
核心 Mod 类 无(coreModClass 为空,不是 core mod)
Mixin 框架 GTNHLib Mixins(gtnhmixins)的 @LateMixin / ILateMixinLoader + UniMixins
注入数量 2(全部为客户端、全部为 LATE 阶段)

gradle.properties 相关字段实际取值

字段 值 含义
usesMixins true 启用 mixin(UniMixins 随之成为必需依赖)
mixinsPackage mixins mixin 类所在包,必须与 json 的 package 匹配
coreModClass 空 不使用 core mod,没有 IFMLLoadingPlugin 实现类
mixinPlugin 空 不使用自定义 IMixinConfigPlugin
separateMixinSourceSet 空 mixin 与主代码同在 src/main
usesMixinDebug false 不导出 mixin 类与映射
containsMixinsAndOrCoreModOnly false 本模组是普通 mod(存在 @Mod 类)
forceEnableMixins false 不为依赖方强制开启
accessTransformersFile aspectrecipeindex_at.cfg 见 Access Transformer

两个 mixin 配置文件

文件 内容
mixins.aspectrecipeindex.json required: true,minVersion: 0.8.5-GTNH,target: @env(DEFAULT),compatibilityLevel: JAVA_8,没有任何 mixins 数组 —— 即普通阶段注入数为 0
mixins.aspectrecipeindex.late.json 同上的公共字段,加 package: com.gtnewhorizons.aspectrecipeindex.mixins.late,mixins 数组列出下面 2 项

两者共用同一个 refmap:mixins.aspectrecipeindex.refmap.json。

加载器链

  1. AspectRecipeIndexLateMixins 实现 ILateMixinLoader,带 @LateMixin 注解。
  2. getMixinConfig() 返回 "mixins.aspectrecipeindex.late.json"。
  3. getMixins(Set<String> loadedMods) 委托给 IMixins.getLateMixins(Mixins.class, loadedMods)。
  4. Mixins 枚举只有一个常量 MAPPING_THREAD_HOOK,声明:两个 addClientMixins、requiredMod = TargetedMod.THAUMCRAFT、Phase.LATE。
  5. TargetedMod 枚举只有一个常量 THAUMCRAFT("Thaumcraft")。

门控条件:只有 Thaumcraft 加载时才会注入;Thaumcraft 缺失时 getMixins 返回空集。本模组本来就把 Thaumcraft 声明为 required-after,所以这条门控实际上是双保险。

注入点 1:MappingThreadMixin

属性 值
目标类 thaumcraft.client.gui.MappingThread
目标方法 run()
声明 @Mixin(value = MappingThread.class, remap = false)
阴影字段 @Shadow Map<String, Integer> idMappings;

remap = false 表示目标方法名是非混淆名,不做映射处理——Thaumcraft 是非官方 mod,方法名不会被混淆。

三个注入全部服务于同一个目的:给 “Items Containing Aspect” handler 提供加载进度。

注入方法 位置 动作
retrieveTotalToLoad @At("HEAD") ThaumcraftHooks.setTotalToLoad(idMappings.size())
onLoopIteration @At(value = "INVOKE", target = "Ljava/util/Iterator;next()Ljava/lang/Object;", ordinal = 0) ThaumcraftHooks.incrementLoadedItems()
onAllDataLoaded @At("TAIL") ThaumcraftHooks.setAllDataLoaded()

ordinal = 0 表示只挂在第一个 Iterator.next() 调用点上,避免循环里的其他迭代器重复计数。

注入点 2:ClientTickEventsFMLMixin

属性 值
目标类 thaumcraft.client.lib.ClientTickEventsFML
目标方法 renderAspectsInGui(GuiContainer, EntityPlayer)
声明 @Mixin(ClientTickEventsFML.class),注入处 remap = false
注入点 @At("HEAD"),cancellable = true

作者注释说明意图:“Render aspects for NEI.”——把"已扫描物品的元素图标"渲染功能抢过来接管。

流程:

  1. GuiContainerManager.getStackMouseOver(gui) 取鼠标下的物品栈;为 null 直接返回。
  2. 若当前 GUI 是 GuiRecipe 且其 handler 是 ItemsContainingAspectHandler,再用鼠标坐标与 guiLeft/guiTop/xSize/ySize 做矩形命中测试;命中就 return,让原方法继续跑(不重复渲染)。
  3. 否则用 ScanManager.generateItemHash(item, damage) 生成 hash,在 Thaumcraft.proxy.getScannedObjects().get(玩家名) 里查 @hash 或 #hash。
  4. 命中则调用本 mixin 的 @Unique 静态方法 aspectRecipeIndex$renderAspects,然后 ci.cancel() 完全阻止 Thaumcraft 原版渲染。

接管后的渲染逻辑(@Unique 方法):

  • 用 ThaumcraftCraftingManager.getObjectTags + getBonusTags 取 AspectList。
  • GL11.glDisable(GL_LIGHTING) / GL11.glDisable(GL_DEPTH_TEST),配对 glPushAttrib(GL_ALL_ATTRIB_BITS) / glPopAttrib()。
  • 每个元素前先画 textures/aspects/_back.png 底板,缩放 1.25,位移 (-2, -2)。
  • 已发现的画 UtilsFX.drawTag,未发现的画 textures/aspects/_unknown.png。
  • 元素排列:X = 鼠标X + 17 + 序号 × 18,Y = 鼠标Y + 7 - 33。

矩形命中测试用到的四个字段即 Access Transformer 提升可见性的那四个。

事件总线

没有任何 Forge 事件订阅。全仓 grep MinecraftForge.EVENT_BUS 与 @SubscribeEvent 均无结果;@Mod.EventBusSubscriber 同样不存在。唯一的"钩子"就是上面 2 个 LATE mixin,加上 NEI 的 IConfigureNEI 回调。

相关条目