神人MC模组代码大赏

本文最后更新于 2026年8月22日 下午

神人MC模组代码大赏

记录在做 Kumo O Tagayasu Everbloom 过程中遇到的神人模组……

预备知识

一个模组的分包正常由以下部分构成,不同的版本、不同的作者可能会有所不同,但是基本是一致的:

  • 主类:一个 <模组名称>.java 的文件,用来声明模组、定义 MODID 和 LOGGER、注册模组内的一系列东西。
  • init:注册模块,枚举模组中需要注册的物品,之后在主类统一注册。一些比较旧的模组可能会把这些都拆分到各自的模块中。
  • client :客户端模块,模型渲染屏幕按键都在这里。
  • data:数据生成模块。手写数据包很容易出错,并且不利于版本的迁移,因此通常会用代码生成。
  • compat:联动模块,放置其他模组的联动代码。
  • config:配置,有的作者喜欢放到根目录下。
  • event:事件,监听 Forge 提供的事件(比如方块破坏、玩家死亡)并进行操作。
  • mixin:事件提供的接口不够的时候,会使用 Mixin 强行修改代码,这些用来改代码的代码放在这里。
  • block/item/entity/container/tag/worldgen…:字面意思的各种内容

不理解也没关系,最关键的一点是:模组的结构应该是条理清晰、便于维护的,而不是和印度料理一样变成糊糊。

中文混淆大师

项目地址:Admsgenter/Convenient-Storage: The source of Minecraft Mod Convenient Storage

是的,所有的package、class、field和method都是中文!但是我说实话,这已经是这些模组里面最正常的一个了。分包非常清晰,唯一的问题是把 Event 全堆到了主类,很难不让人怀疑他是为了不让 AI 看懂故意用中文混淆的()

image-20260619222733531

image-20260619223043276

手写数据包的 King

项目地址:ribs498/VintageDelight-master

在我尝试迁移这个模组之前, Pink_Cats 已经做了非常多工作了:duckgun13476/VintageDelight-master: Port version for this mod.。Ta 还原了 1.0.6 的源代码,并且将其迁移到了 NeoForge 1.21.1,只是留下了一些 Bug 没修。我寻思,这个模组内容也不多,使用量也大,就修复一点点旧 Bug,应该不会有多难吧?然后我就看到了这个:

image-20260619223817785

datagen 一共三个类,第一个类不是 datagen,第二个类是入口,第三个类只写了腌渍罐。

难怪一大堆数据包相关的 Bug,合着你数据包和资源包全 TM 是手写的是吧!

image-20260619223954925

我让 Codex 跑了两天两夜,才终于把这一大堆数据包资源包转为用代码生成;而且由于资源包书写不规范,Codex 还出现了不少错误,经过反馈后三天才搞定。

你以为这就结束了吗?不不不,这个模组的逆天之处远不及如此:

image-20260619224407689

image-20260619224447074

17 个一模一样的,没有实现任何功能的类!按照摸里傻的说法,只有 MCr 模组会这样写;要不是这个模组没有 MCr 的影子并且开源,真得怀疑这个模组究竟怎么写出来的了。

这个模组还有一个经典 Bug,在背包全部装满时,手持厨师帽右键交换头盔,会直接吞掉之前的头盔:

image-20260619225002838

那么这个模组究竟干了什么呢?让我们来看看:

image-20260619225608883

它先将玩家的头盔脱下,塞进背包,然后再装上厨师帽。这个过程没有检查背包是否已满,因此如果背包是满的,之前的头盔就会塞不进背包而直接吞掉。

但是问题又来了:原版的 ArmorItem 本来就有右键换掉身上装备的功能,这样写不是纯纯没事找事?于是我往上看……

1
public class ChefHatItem extends Item {

?神金啊?为什么不继承 ArmorItem ?然后我让 Codex 改成继承 ArmorItem,模型变成了基础皮革套,我才想起来如果写盔甲,模型是需要用代码定义的。于是我又让 Codex 按照原本的模型改成代码,炸了。于是我去看了这个模型:

image-20260619230600676

乍一看一切正常对吧?如果我们放到 Blockbench 里面去看呢?

image-20260619230757664

我草,怎么不是箱型 UV 啊?合着你就为了省点贴图大小,包了这么一顿饺子是吧?

不仅于此,为了实现手上的贴图和戴在头上的模型不同,Ribs 还干了这么一茬事:

image-20260619231026050

不是,"loader": "forge:separate_transforms" 是什么鬼东西啊?为了在不同渲染状态下渲染不同模型,这是从什么犄角旮旯里挖出来的特性啊?

我修了一晚上这个东西,无果,遂将其删除。拉倒吧您!

Vintaged Delight 四宗罪

1 所有数据包全部手写,一大堆标签战利品表错误,标签破坏者

2 大量一模一样一字不改的 class,人机感如同 MCr

3 盔甲和方块物品统统继承 Item,Mojang 提供的基本类一个不用,非要自己搓轮子,整出吞头盔等神奇 Bug

4 为了缩减贴图大小,所有模型全部用逐面材质,能复用的贴图全部复用,想改都没法改

Lambda 仙人

项目地址:Cassian 的源代码留档,原作者 Compasses

这个模组作者本身也值得一说。让我们有请模组界的左慈,转生三次的金仙,拥有十八化身之人,Compasses!第一次因为巴以问题销号 CurseForge,第二次则因为某个整合包不发给 Ta 创造飞行而删库跑路,第三次更是一言不发直接跑路。尽管三个号几乎看不出关联,但是名字的内在逻辑还是能证明三位一体。

我们的关注对象是这个主类:compasses.expandedstorage.impl.CommonMain。虽说这已经是旧版本的代码了,但是目前最常用的版本依然是这个。一个正常的主类是什么样的呢?我在前面已经说过了,应该只包含一些常量和一些内容、事件和配置的注册。而这个主类干了什么呢……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
CommonMain.registerMutationBehaviour(isChestBlock, MutationMode.MERGE, (context, level, state, pos, stack) -> {
Player player = context.getPlayer();
if (player == null) {
return ToolUsageResult.fail();
}
if (state.getValue(AbstractChestBlock.CURSED_CHEST_TYPE) == EsChestType.SINGLE) {
CompoundTag tag = stack.getOrCreateTag();
if (tag.contains("pos")) {
BlockPos otherPos = NbtUtils.readBlockPos(tag.getCompound("pos"));
BlockState otherState = level.getBlockState(otherPos);
BlockPos delta = otherPos.subtract(pos);
Direction direction = Direction.fromDelta(delta.getX(), delta.getY(), delta.getZ());

if (direction != null) {
if (state.getBlock() == otherState.getBlock()) {
if (otherState.getValue(AbstractChestBlock.CURSED_CHEST_TYPE) == EsChestType.SINGLE) {
if (state.getValue(BlockStateProperties.HORIZONTAL_FACING) == otherState.getValue(BlockStateProperties.HORIZONTAL_FACING)) {
boolean firstIsDinnerbone = level.getBlockEntity(pos) instanceof OpenableBlockEntity blockEntity && blockEntity.isDinnerbone();
boolean secondIsDinnerbone = level.getBlockEntity(otherPos) instanceof OpenableBlockEntity blockEntity && blockEntity.isDinnerbone();
if (firstIsDinnerbone == secondIsDinnerbone) {
if (!level.isClientSide()) {
EsChestType chestType = AbstractChestBlock.getChestType(state.getValue(BlockStateProperties.HORIZONTAL_FACING), direction);
level.setBlockAndUpdate(pos, state.setValue(AbstractChestBlock.CURSED_CHEST_TYPE, chestType));
// note: other state is updated via neighbour update
tag.remove("pos");
//noinspection ConstantConditions
player.displayClientMessage(Component.translatable("tooltip.expandedstorage.storage_mutator.merge_end"), true);
}
return ToolUsageResult.slowSuccess();
}
player.displayClientMessage(Component.translatable("tooltip.expandedstorage.storage_mutator.merge_wrong_block"), true);
} else {
//noinspection ConstantConditions
player.displayClientMessage(Component.translatable("tooltip.expandedstorage.storage_mutator.merge_wrong_facing"), true);
}
} else {
//noinspection ConstantConditions
player.displayClientMessage(Component.translatable("tooltip.expandedstorage.storage_mutator.merge_already_double_chest"), true);
}
} else {
//noinspection ConstantConditions
player.displayClientMessage(Component.translatable("tooltip.expandedstorage.storage_mutator.merge_wrong_block"), true);
}
} else {
//noinspection ConstantConditions
player.displayClientMessage(Component.translatable("tooltip.expandedstorage.storage_mutator.merge_not_adjacent"), true);
}
tag.remove("pos");
} else {
if (!level.isClientSide()) {
tag.put("pos", NbtUtils.writeBlockPos(pos));
//noinspection ConstantConditions
player.displayClientMessage(Component.translatable("tooltip.expandedstorage.storage_mutator.merge_start", Utils.ALT_USE), true);
}
return ToolUsageResult.fastSuccess();
}
}
return ToolUsageResult.fail();
});

很明显这是一个 Method,但是他被写成了 Lambda,注册为箱子切换器的行为,而且还是在主类里面……这个主类把一大堆应该放在initclient 以及物品类本身的逻辑全混在了主类,搞出了个3k行的主类,耦合程度极高。

我在尝试修复其与机械动力的兼容时候,也想过是否修缮这部分;但是这个主类写的实在是过于错综复杂,重构的难度比重新写一个模组还高。在尝试了多次依然没有得到很好的结果后,我选择就此摆烂……

我后来才知道,Cassian 也是经常接手模组的开发者,维护了相当多现在常用的模组;但是连 Cassian 也是对这个模组束手无策。

那么这玩意后来改善了吗?在 Expanded Storage 26.1.2 的代码中,这个主类已经被大幅度修剪,基本就是个普通的主类了……你问我在哪里看 Expanded Storage 26.1.2 的代码?那当然是删库了!

反射大师

项目地址:Backpack Side GUI

2026-06-08_03.33.13.png

我第一眼看到这个模组,一下子就爱上了:这是个好东西啊!让背包方便了不少。放入整合包,一跑 Spark,20% 占用,全是 getMethod() 。吓得我赶紧掏出我的 Jadx 反编译:

f0e770b4a880a90554c396054af6a92a

……好家伙,虽然这个模组是 Sophisticated Backpack 的附属,并联动了 CuriosAPI 与 JEI,但是!所有代码中!没有一个其他模组的类!统统是反射!

(当场被气晕。)

这个模组有这样几个包:

  • client:1个 ClientBootstrap 类,1个 SideBackpackClient类;
  • compat:1个 JeiReflectionCompat类;
  • network:20+ 个网络包;
  • server:1个 ServerBackpackAccess 类;
  • 1个配置文件类,1个主类。

是不是感觉有什么不对?这么复杂的模组却只有个位数类?没错 ,SideBackpackClient 1900行,ServerBackpackAccess 2670 行…… 写 Python 都不带这么写的。


神人MC模组代码大赏
https://gldym.github.io/posts/shit-code/
作者
Polaris_Light
发布于
2026年6月13日
更新于
2026年8月22日
许可协议