= 创作分享 =
编程开发
(搞七捻三)邪修是如何制作Minecraft模组的 ...
让子弹飞

(搞七捻三)邪修是如何制作Minecraft模组的

让子弹飞 于 2026-8-2 00:42 ( 11天前 ) [复制链接] [显示全部楼层] [打印]
本帖最后由 Author87668 于 2026-8-3 14:41 编辑

没人看准备删了,没意思 :(


作者声明:文章创作过程中使用了AI辅助

本文以搞怪风格为主,所述"邪修"技术纯属编程技巧的文学化表达,文中所有比喻(包括但不限于"修仙"、"炼丹"、"走火入魔"、"渡劫"等)均为修辞手法,请勿当真尝试在现实中修炼。

本文内容不代表Mojang Studios、Microsoft或任何模组加载器开发团队的官方立场、建议或认可。所有观点仅代表作者个人经验总结。

本文同步发布于:https://blog.star-dust.link/post ... ds-dark-cultivation
本文的正经风格版本:https://blog.star-dust.link/posts/note/fabric-mod-reflection-multi-version-compatibility

免责声明
作者郑重声明:
1. 本文内容仅供学习和参考,不构成任何形式的建议、保证或承诺
2. 作者不对因使用本文所述技术导致的任何直接或间接损失承担责任,包括但不限于:
   - 数据丢失或损坏
   - 服务器崩溃或服务中断
   - 性能下降或用户体验恶化
   - 法律纠纷或合规问题
   - 时间浪费和精神损失
   - 任何其他形式的损害
3. 用户使用本文所述技术的风险完全由用户自行承担
4. 作者保留随时修改、更新或删除本文内容的权利,恕不另行通知
5. 本文可能包含错误、遗漏或不准确的信息,请以实际测试为准
6. 本文提及的所有商标、产品名称均属于其各自所有者



本文最后更新于2026年8月,适用于Minecraft Java Edition 1.18-26.2。技术日新月异,请以实际情况为准。



第一章:为什么要当邪修?

因为正道太累了啊!

你看那些名门正派的Fabric弟子,每天对着Mixin注解磕头拜佛,AccessWidener写得比情书还长,Gradle配置调得比炼丹炉还复杂。结果呢?MC一更新,全部白给!1.21写的模组到26.1直接原地飞升,连个全尸都不留。

而我们邪修不一样。我们没有信仰,没有底线,没有import net.minecraft.*。我们只有一样东西——

反射。

这玩意儿就像修仙界的"万能钥匙",虽然脏、虽然慢、虽然随时可能炸膛,但它能用。编译期看不到MC的类?没关系,运行时硬抢!方法名改了?没关系,挨个试!参数类型不对?没关系,动态代理糊脸!

邪修的核心理念就八个字:活着就行,管他姿势。



第二章:邪修三件套

2.1 常量层:你的"通缉令"

把所有版本里变来变去的类名、方法名、字段名,全部写进一个巨大的常量表里。这张表就是你的"仇人名单",上面记满了MC每次更新欠下的债。

  1. // 邪修の仇人录
  2. static final String C_LEVEL       = "net.minecraft.world.level.Level";
  3. static final String C_COMPONENT   = "net.minecraft.network.chat.Component";
  4. static final String M_GET_UUID    = im("Entity", "getUUID", "()Ljava/util/UUID;", "method_5667");
  5. static final String M_LITERAL     = im("Component", "literal", "(Ljava/lang/String;)LComponent;", "method_43470");
复制代码


记住:业务代码里绝对不能出现任何硬编码的MC类名。一旦出现,你就破了戒,离走火入魔不远了。

2.2 反射层:你的"作案工具"

这一层封装所有脏活累活。Class.forName、Method.invoke、Field.get……这些见不得光的操作全部藏在这里,对外只提供人话接口。

关键技巧:
  • 缓存一切:反射调用一次就够了,第二次还用就是浪费生命。用ConcurrentHashMap存起来,找不到的也要存个NOT_FOUND哨兵,不然每次都重新扫描,JVM会骂你。
  • 描述符必须带:找方法不带描述符等于裸奔。getUUID()混淆后可能是method_5667也可能是method_5845,一个返回UUID一个返回String,猜错了当场社死。
  • 重载方法要遍历:别傻乎乎只缓存第一个匹配到的方法。把所有同名方法都捞出来,调用时用isInstance逐个验身,谁对得上就用谁。


2.3 业务层:你的"伪装身份"

这一层看起来和正常模组一模一样。getBlockState()、sendMessage()、checkPermission()……全是人话,干干净净。

但只有你自己知道,这些人话底下藏着多少反射。



第三章:邪修实战骚操作

3.1 环境探测:先搞清楚自己在哪

Fabric生产环境用intermediary名,MC 26+又改回named名。你得先知道自己身处哪个平行宇宙:

  1. // 使用神识探查一下当前是不是 named 运行时
  2. static final boolean IS_NAMED = tryLoad("net.minecraft.world.level.Level") != null;
复制代码


然后查表回退:先试named名,不行就翻"仇人录"找intermediary名。intermediary名跨版本稳定,是你最后的保命符:

  1. static Class<?> forName(String name) {
  2.     // 1. 缓存命中直接返回(含 NOT_FOUND 哨兵)
  3.     Object cached = CACHE.get(name);
  4.     if (cached != null) return unwrap(cached);

  5.     // 2. 先试 named 名,再试 intermediary 兜底
  6.     Class<?> cls = tryLoad(name);
  7.     if (cls == null) cls = tryLoad(Constants.toInter(name));

  8.     // 3. 存结果,找不到也要存哨兵防无限重试
  9.     CACHE.put(name, cls != null ? cls : NOT_FOUND);
  10.     return cls;
  11. }
复制代码


3.2 事件监听:动态代理捏脸术

Fabric的事件回调接口里全是MC原生类,编译期根本import不了。怎么办?不implements了,直接捏!

用JDK动态代理在运行时现场造一个假对象塞进去。代理对象收到回调后,把参数打包成Object[]扔给业务层慢慢拆:

  1. // 运行时捏个假监听器,彻底斩断编译期类型依赖
  2. Object proxy = Proxy.newProxyInstance(loader, new Class[]{listenerIfc}, (p, m, args) ->
  3.     m.equals(targetMethod) ? handler.handle(args) : handleObjectMethods(p, m, args)
  4. );
  5. registerMethod.invoke(eventObj, proxy);
复制代码


这招的精髓在于:只要我不承认依赖存在,依赖就不存在。我们把它叫做薛定谔的类型安全。

3.3 多策略兜底:一条路走到黑不如多条路走到亮

获取方块ID为例:

  1. static String getBlockId(Object state) {
  2.     Object block = call(state, "getBlock");
  3.     if (block == null) return "minecraft:air";

  4.     // 策略1:toString 硬抠(全版本通用保底)
  5.     String s = block.toString(); // Block{minecraft:diorite}
  6.     int b = s.indexOf('{'), e = s.indexOf('}');
  7.     if (b >= 0 && e > b) return s.substring(b + 1, e);

  8.     // 策略2:反射查注册表(新旧API自动兼容)
  9.     String id = getFromRegistry(block);
  10.     return id != null ? id : "minecraft:air";
  11. }
复制代码


先试新API BuiltInRegistries,抛异常了?换老API Registries,又抛了?用toString()硬抠字符串(Block{minecraft:diorite} → minecraft:diorite),还不行?返回minecraft:air,假装什么都没发生。

邪修的信条:优雅地失败 > 粗暴地崩溃。

3.4 文本组件兼容:新旧两开花

老版本new TextComponent("hello"),新版本Component.literal("hello")。写个兼容方法:

  1. // 新版 Component.literal() 失败就降级旧版 new TextComponent()
  2. static Object text(String s) {
  3.     try { return invokeStatic("Component", "literal", s); }
  4.     catch (Exception e) { return newInstance("TextComponent", s); }
  5. }
复制代码


注意:这里Component_literal和new_TextComponent都是反射拿到的,编译期依然干干净净。邪修的代码,永远不能被编译器抓到把柄。



第四章:邪修禁忌

  • 忌到处散落反射调用:不封装的反射就像没穿衣服上街,迟早出事。
  • 忌信任单一API:MC的承诺比渣男的誓言还不靠谱,永远准备Plan BCD。
  • 忌忽略性能:不做缓存的反射修士,活不过筑基期。
  • 忌忘记哨兵:ConcurrentHashMap不能存null,不设NOT_FOUND占位符,你会陷入无限重试的地狱轮回。
  • 忌跟正道修士争论:他们不懂你的苦,你也不必解释。默默写完兼容代码,看他们的模组在下个版本集体暴毙,然后微微一笑。




终章:邪修的归宿

这套体系搭完,你就能实现:

✅ 编译期零MC依赖
✅ 单JAR通吃1.18到26+
✅ 不用Mixin、不用AW、不看官方脸色
✅ 在别人模组集体阵亡时独自存活

代价是什么?

❌ 代码可读性约等于天书
❌ 调试时堆栈信息像乱码
❌ 新人接手时会诅咒你十八代祖宗
❌ 你永远无法理直气壮地说"我写的是正经模组"

但没关系。

邪修不需要认可,只需要运行。

当你看到自己的模组在数个大版本更新后依然顽强地加载成功,而隔壁正道修士还在Discord里哭诉Mixin冲突时——

那一刻,你就知道,这条路,走对了。




(啊免责被截断了只能写这么多)
欢迎各路大神留下评论
我,是,我。
发表于 2026-8-2 00:42:07 | 显示全部楼层 |阅读模式

回复 | 举报

该帖共收到 2 条回复!
光波-光粒
“AI辅助”

评分

参与人数 1RF +5 收起 理由
Author87668 + 5 头一个留下评论的,必须递分

查看全部评分

发表于 2026-8-3 10:53:19 | 显示全部楼层

回复 | 举报

让子弹飞
本帖最后由 Author87668 于 2026-8-3 14:34 编辑

“:)”
我,是,我。
发表于 2026-8-3 14:24:35 | 显示全部楼层

回复 | 举报

百科目前不允许匿名发帖哦~ 请先 [ 登陆 ][ 注册 ] 吧~

本版积分规则

发新帖
  • 回复
  • 点评
  • 评分

[ MC百科(mcmod.cn) 除另有声明,所有开放公共编辑的内容均使用 BY-NC-SA 3.0 协议 ]

Minecraft百科CC协议
快速回复 返回顶部 返回列表