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。
加载器链
AspectRecipeIndexLateMixins实现ILateMixinLoader,带@LateMixin注解。getMixinConfig()返回"mixins.aspectrecipeindex.late.json"。getMixins(Set<String> loadedMods)委托给IMixins.getLateMixins(Mixins.class, loadedMods)。Mixins枚举只有一个常量MAPPING_THREAD_HOOK,声明:两个 addClientMixins、requiredMod =TargetedMod.THAUMCRAFT、Phase.LATE。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.”——把"已扫描物品的元素图标"渲染功能抢过来接管。
流程:
GuiContainerManager.getStackMouseOver(gui)取鼠标下的物品栈;为null直接返回。- 若当前 GUI 是
GuiRecipe且其 handler 是ItemsContainingAspectHandler,再用鼠标坐标与guiLeft/guiTop/xSize/ySize做矩形命中测试;命中就 return,让原方法继续跑(不重复渲染)。 - 否则用
ScanManager.generateItemHash(item, damage)生成 hash,在Thaumcraft.proxy.getScannedObjects().get(玩家名)里查@hash或#hash。 - 命中则调用本 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 回调。
相关条目
- Access Transformer - 为注入点 2 开放 GUI 字段
- 元素配方索引机制 - 进度三元组的消费方
- 兼容性 -
gtnhmixins与 Thaumcraft 的门控 - IMC 注册消息 - 模组唯一的 FML 跨模组通信