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 模式匹配等) |
相关条目
- 队伍数据模型 - mixin 捕获后数据流向何处
- OrderedDimCache - 矿脉键如何被折算与去重
- 版本号同步机制 -
MixinWorldIdNotification触发的加载流程 - 网络封包协议 -
MixinOreVeinPosition发出的封包 - 持久化与 NBT 结构 - Access Transformer 打开的字段