Mixin 清单

基本信息

属性 值
Mixin 源码目录 src/main/java/com/rune580/sharedprospecting/mixins/
声明式枚举 mixins/Mixins.java(4 个枚举值,全部 Phase.LATE)
目标 mod 枚举 mixins/TargetedMod.java(VANILLA / VISUAL_PROSPECTING)
LateMixin 入口 com.rune580.sharedprospecting.SharedProspectingLateMixins(@LateMixin)
实际 @Mixin 类 6 个
配置 JSON mixins.sharedprospecting.json(早期,实质为空)+ mixins.sharedprospecting.late.json(后期)
IMixinConfigPlugin 无(gradle.properties 的 mixinPlugin 为空)
目标 modid visualprospecting(Visual Prospecting)

全部 6 个 mixin 都只针对 Visual Prospecting。 本 mod 没有任何针对 Minecraft 本体或其它 mod 的 mixin。

声明式清单 Mixins.java

Mixins 是个枚举,每个值带 5 个属性:描述字符串、side、phase、目标 mod 列表、排除 mod 列表。构造函数有两处硬校验:目标 mod 列表为空或 applyIf 为 null 都会 throw new RuntimeException。

枚举值 描述 Side Phase 目标 mod 挂载的类
LOAD_REVISION Load revisions after ClientCache is loaded CLIENT LATE VISUAL_PROSPECTING visualprospecting.MixinWorldIdNotification
CAPTURE_POSITIONS Capture Ore/Fluid positions BOTH LATE VISUAL_PROSPECTING visualprospecting.MixinVisualProspectingAPIServer、visualprospecting.MixinProspectingRequest、visualprospecting.ProspectingNotificationAccessor
WORLD_CACHE_ACCESSOR WorldCache Accessor BOTH LATE VISUAL_PROSPECTING visualprospecting.WorldCacheAccessor
SHARE_DEPLETED_VEINS Share depleted veins CLIENT LATE VISUAL_PROSPECTING visualprospecting.MixinOreVeinPosition

全部 4 项都没有 addExcludedMod,也没有 setApplyIf(沿用默认 () -> true)。TargetedMod.VISUAL_PROSPECTING 的 coreModClass 为 null、modId 为 "visualprospecting",所以 allModsLoaded 走的是 modid 判定分支。

getLateMixins(Set<String> loadedMods) 是实际生效的方法:按 phase == Phase.LATE 过滤,逐项跑 shouldLoad(= shouldLoadSide() && applyIf.get() && allModsLoaded(...) && noModsLoaded(...)),并把未加载的类名用 SharedProspectingMod.LOG.info("Not loading the following LATE mixins: {}", ...) 记进日志。

getEarlyMixins(Set<String> loadedCoreMods) 虽然也实现了,但全仓库没有任何调用点(grep -rn "getEarlyMixins" src/main/java 只命中定义处本身),源码注释写着 “This may be possible to handle differently or fix.”。因此 Phase.EARLY 实际上是不可达分支。

6 个 Mixin 逐个说明

全部使用 remap = false(不重映射目标方法名,因为目标是 mod 类而非 Minecraft 本体),类名中的 visualprospecting. 前缀对应 late JSON 的 package 设置。

1. MixinWorldIdNotification

项 值
目标类 com.sinthoras.visualprospecting.network.WorldIdNotification.Handler
注入器 @WrapOperation(MixinExtras),at = @At("RETURN") 隐含,取 Operation<Boolean>
目标方法 onMessage(Lcom/sinthoras/visualprospecting/network/WorldIdNotification;Lcpw/mods/fml/common/network/simpleimpl/MessageContext;)Lcpw/mods/fml/common/network/simpleimpl/IMessage;
目标调用 Lcom/sinthoras/visualprospecting/database/ClientCache;loadVeinCache(Ljava/lang/String;)Z
回调签名 private boolean sharedprospecting$loadClientRevision(ClientCache instance, String worldId, Operation<Boolean> original)
声明于 Mixins.LOAD_REVISION(Side.CLIENT)
boolean result = original.call(instance, worldId);
ClientRevision.onClientCacheLoad(new File(((WorldCacheAccessor) instance).callGetStorageDirectory(), worldId));
return result;

这是整条同步链的起点:在 VP 加载完世界缓存之后,把 <缓存目录>/<worldId> 交给 ClientRevision.onClientCacheLoad,由它读 revision.dat、补发 pendingRevision、首次上报枯竭矿脉。原返回值原样传回,不改变 VP 的行为。用 WrapOperation 而非 @Inject(at=RETURN) 是为了拿到 worldId 实参。

2. MixinVisualProspectingAPIServer

项 值
目标类 com.sinthoras.visualprospecting.VisualProspecting_API.LogicalServer
注入器 @Inject(method = "sendProspectionResultsToClient", at = @At("HEAD"))
回调签名 private static void sharedprospecting$addOresToTeam(EntityPlayerMP player, List<OreVeinPosition> oreVeins, List<UndergroundFluidPosition> undergroundFluids, CallbackInfo ci)
声明于 Mixins.CAPTURE_POSITIONS(Side.BOTH)

矿脉/流体数据入库的主入口。 VP 每次给玩家下发勘探结果时,本 mixin 在 HEAD 位置把矿脉和流体两个列表一起塞进发送者的队伍数据(SPTeamData.get(player) 为 null 则直接 return,即无队伍玩家不记录)。因为在 HEAD 注入,原方法照常执行、结果照常发给该玩家,本 mod 只是顺带抄一份给队伍。

3. MixinProspectingRequest

项 值
目标类 com.sinthoras.visualprospecting.network.ProspectingRequest.Handler
注入器 @ModifyReturnValue(MixinExtras),at = @At("RETURN")
目标方法 onMessage(Lcom/sinthoras/visualprospecting/network/ProspectingRequest;Lcpw/mods/fml/common/network/simpleimpl/MessageContext;)Lcpw/mods/fml/common/network/simpleimpl/IMessage;
回调签名 public IMessage sharedprospecting$captureOrePosition(IMessage original, @Local(argsOnly = true) MessageContext ctx)
声明于 Mixins.CAPTURE_POSITIONS(Side.BOTH)
if (original == null) return null;
SPTeamData data = SPTeamData.get(ctx.getServerHandler().playerEntity);
if (data != null) {
    ProspectingNotificationAccessor message = (ProspectingNotificationAccessor) original;
    data.addOreVeins(message.getOreVeins());
}
return original;

只捕获矿脉,不捕获流体。 因为 ProspectingNotificationAccessor 只暴露了 getOreVeins(),没有对应的流体访问器。这与 MixinVisualProspectingAPIServer 形成互补:那条路径矿脉+流体都拿,这条路径只拿矿脉(原封不动返回 original)。@Local(argsOnly = true) 抓取原始 MessageContext 以取到发包玩家。

4. MixinOreVeinPosition

项 值
目标类 com.sinthoras.visualprospecting.database.OreVeinPosition
注入器 @Inject(method = "toggleDepleted", at = @At("TAIL"))
回调签名 private void sharedprospecting$shareDepletion(CallbackInfo ci)
@Shadow private boolean depleted、@Final public int chunkX、@Final public int chunkZ
声明于 Mixins.SHARE_DEPLETED_VEINS(Side.CLIENT)
new MessageSendDepleted(Long2BooleanMaps.singleton(
    RevisionUtil.getOreVeinKey(chunkX, chunkZ), depleted)).sendToServer();

玩家挖空一个矿脉 → 立刻单条上报。三个 @Shadow 字段用于在 TAIL 处读到刚被翻转的状态。它挂在 toggleDepleted 方法尾部,所以任何调用该方法的路径都会触发,这也是为什么它被限制在 Side.CLIENT——否则服务端 updateDepletions / setDepleted 里的 toggleDepleted 会形成回环。

单条构造把维度设为哨兵值 Integer.MIN_VALUE,服务端收到后替换为玩家当前所在维度。

5. ProspectingNotificationAccessor

项 值
目标类 com.sinthoras.visualprospecting.network.ProspectingNotification
类型 interface mixin
暴露成员 @Accessor List<OreVeinPosition> getOreVeins()
声明于 Mixins.CAPTURE_POSITIONS(Side.BOTH)

调用方只有 MixinProspectingRequest((ProspectingNotificationAccessor) original)。没有流体访问器,也没有任何其它方法。

6. WorldCacheAccessor

项 值
目标类 com.sinthoras.visualprospecting.database.WorldCache
类型 interface mixin
暴露成员 @Accessor boolean getIsLoaded()、@Invoker File callGetStorageDirectory()
声明于 Mixins.WORLD_CACHE_ACCESSOR(Side.BOTH)

两个成员的调用方:

成员 调用方
getIsLoaded() ClientRevision.updateRevision(判断是否要暂存 pendingRevision)
callGetStorageDirectory() MixinWorldIdNotification(拼 revision.dat 路径)、SPTeamData.importOldData(确认旧目录存在)

ClientRevision 静态块里取 WorldCache.dimensions 那个 private 字段用的是 ReflectionHelper 裸反射,没有走这个 Accessor——Accessor 只能生成方法访问器,够不到字段。

配置 JSON

mixins.sharedprospecting.json(早期)

{
  "required": true,
  "minVersion": "0.8.5-GTNH",
  "refmap": "mixins.sharedprospecting.refmap.json",
  "target": "@env(DEFAULT)",
  "compatibilityLevel": "JAVA_8"
}

既没有 package 也没有 mixins 数组——gradle.properties 的 mixinsPackage = mixins 决定了包根,但没有任何 mixin 被声明在这个配置里。配合 getEarlyMixins 无调用点,整个早期阶段是空的。

mixins.sharedprospecting.late.json(后期)

{
  "required": true,
  "minVersion": "0.8.5-GTNH",
  "package": "com.rune580.sharedprospecting.mixins.late",
  "refmap": "mixins.sharedprospecting.refmap.json",
  "target": "@env(DEFAULT)",
  "compatibilityLevel": "JAVA_8"
}

也没有 mixins 数组。 全部 mixin 在运行时由 SharedProspectingLateMixins(实现 GTNHLib ILateMixinLoader)注入:

@LateMixin
public class SharedProspectingLateMixins implements ILateMixinLoader {
    @Override public String getMixinConfig() { return "mixins.sharedprospecting.late.json"; }
    @Override public List<String> getMixins(Set<String> loadedMods) { return Mixins.getLateMixins(loadedMods); }
}

两个配置共用同一个 refmap 文件。gradle.properties 里 usesMixins = true、usesMixinDebug = true、forceEnableMixins = false、containsMixinsAndOrCoreModOnly = false。

Mixin 相关构建配置

键 值 含义
usesMixins true 启用 Mixin
usesMixinDebug true 开启 mixin 调试参数与 class 导出
mixinsPackage mixins mixin 包根
mixinPlugin (空) 不实现 IMixinConfigPlugin,无自定义兼容开关
coreModClass (空) 不是 core mod
containsMixinsAndOrCoreModOnly false 有正常的 @Mod 类
separateMixinSourceSet (空) mixin 与主代码同在 src/main
accessTransformersFile sharedprospecting_at.cfg 全 mod 唯一一条 AT(NBTTagList.field_74747_a)
enableModernJavaSyntax jabel 用现代语法编 Java 8 字节码(var、switch 表达式、record 模式匹配等)

相关条目