枚举扩展
枚举扩展是一项 Mixin 功能,可用于可靠地向枚举添加新的条目。
在以 Minecraft 枚举为目标时,你可以将 mixin 与类调整结合使用,使新的枚举条目显示在反编译源码中。 如果该修改被设置为传递性的,那么依赖你的模组的其他模组也能看到你添加的条目。
WARNING
枚举扩展要求至少使用 Loader 0.19.0 以获得 Mixin 支持,并且至少使用 Loom 1.16 以获得类调整器支持。
除此之外,若要使用枚举扩展,类调整器文件头部必须将版本指定为 v2。
创建 Mixin
在创建 Mixin 类之前,请确保你的 fabric.mod.json 文件中显式依赖了 Loader 0.19.0 或更高版本:
json
...
"depends": {
...
"fabricloader": ">=0.19.0"
...
}
...即使你已经在 Gradle 依赖中使用了正确的 Loader 版本,也必须显式声明至少依赖 0.19.0,才能启用这一 Mixin 功能。
若要创建枚举扩展,请在你的 mixin 包中创建一个 enum,为其添加 @Mixin 注解,并像向目标枚举类本身添加常量一样,在其中添加你的常量。 例如,我们向 RecipeBookType 添加一个新条目:
java
@Mixin(RecipeBookType.class)
enum RecipeBookTypeMixin {
EXAMPLE_MOD_RECIPE_BOOK_TYPE
}1
2
3
4
2
3
4
重要
你应当始终为自己添加的枚举常量加上你的模组 ID 前缀,以确保其唯一性。 在这些文档中,我们将使用 EXAMPLE_MOD_。
传入构造函数参数
如果目标枚举没有默认构造函数,你必须 shadow 一个目标类的构造函数,并在声明新增条目时传入所需参数。
例如,我们来添加一个新的 RecipeCategory 条目。 创建一个与目标类中所需构造函数匹配的构造函数,并使用 @Shadow 标注它。
java
@Mixin(RecipeCategory.class)
enum RecipeCategoryMixin {
EXAMPLE_MOD_RECIPE_CATEGORY("exampleModRecipeFolderName");
@Shadow
RecipeCategoryMixin(String recipeFolderName) {
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
实现抽象方法
若要实现目标枚举中的抽象方法,请先 shadow 该抽象方法,然后在你添加的条目中重写并实现它。 例如,我们来添加一个新的 ConversionType 条目:
java
@Mixin(ConversionType.class)
enum ConversionTypeMixin {
EXAMPLE_MOD_CONVERSION_TYPE(false) {
@Override
void convert(Mob from, Mob to, ConversionParams params) {
/* ... */
}
};
@Shadow
abstract void convert(Mob from, Mob to, ConversionParams params);
@Shadow
ConversionTypeMixin(final boolean discardAfterConversion) {
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
创建类调整器条目
如果你的目标是 Minecraft 枚举,可以使用类调整器条目在反编译源码中可见地修改目标枚举。
若要启用这一功能,请记得使用 Loom 1.16 或更高版本,并将文件头部版本设置为 v2。
枚举扩展条目的语法如下:
extend-enum <targetClassName> <ENUM_CONSTANT_NAME>在类调整中,类使用它们的内部名称。
例如,对于我们在 Mixin 章节中添加的 RecipeBookType 常量,其类调整器条目如下:
classtweaker
extend-enum net/minecraft/world/inventory/RecipeBookType EXAMPLE_MOD_RECIPE_BOOK_TYPE应用更改
在反编译源码中看到你添加的枚举条目之前,你需要刷新 Gradle 项目并重新生成源码。 如果修改没有出现,可以尝试验证该文件,并检查是否有错误。
现在,你可以在代码中使用该枚举常量:
java
void exampleRecipeBookTypeMethod(RecipeBookType recipeBookType) {
if (recipeBookType == RecipeBookType.EXAMPLE_MOD_RECIPE_BOOK_TYPE) {
/* ... */
}
}1
2
3
4
5
2
3
4
5
如果你只是通过 Mixin 添加了该常量,而它并不存在于反编译源码中,则可以通过比较名称来判断它:
java
void exampleRecipeBookTypeMethodNoCT(RecipeBookType recipeBookType) {
if (recipeBookType.name().equals("EXAMPLE_MOD_RECIPE_BOOK_TYPE")) {
/* ... */
}
}1
2
3
4
5
2
3
4
5
如果你需要在多个地方使用该常量,可以调用 valueOf 获取它,并将结果存储在一个字段中:
java
public static final RecipeBookType ADDED_RECIPE_BOOK_TYPE = RecipeBookType.valueOf("EXAMPLE_MOD_RECIPE_BOOK_TYPE");1
注意事项
枚举扩展无法保证你添加的条目不会破坏任何内容。
你有责任检查目标枚举的使用情况,并尽可能避免问题。 如果某些问题无法解决并导致崩溃,最好完全不要使用枚举扩展。
本节会介绍一些在扩展枚举时需要留意并尽量避免的模式,但并不涵盖所有情况。
switch 表达式
switch 语句常用于处理枚举常量。 因此,如果某个 switch 表达式没有处理其他模组添加的条目,就可能发生崩溃。 例如,假设我们有以下 switch 表达式:
java
void exampleProblematicSwitch(RecipeBookType recipeBookType) {
String s = switch (recipeBookType) {
case SMOKER -> "smoker";
case CRAFTING -> "crafting";
case FURNACE -> "furnace";
case BLAST_FURNACE -> "blast_furnace";
case EXAMPLE_MOD_RECIPE_BOOK_TYPE -> "example_mod_recipe_book_type";
};
/* ... */
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
请注意,这里没有 default 分支。 即使我们已经处理了原版枚举中的所有值以及我们自己添加的值,如果另一个模组添加了不同的条目,这里仍然会抛出异常。
如何避免这种情况? 并不存在一种通用方法可以避免所有崩溃,你的处理方式应根据具体情况调整。 不过一般来说:
- 如果
switch表达式位于原版方法中,可以使用 Mixin 对其进行修改 - 如果
switch表达式来自某个模组,应尝试联系其开发者,共同制定兼容方案。 否则,你可能需要为另一个模组创建 Mixin
被序列化的枚举
某些枚举的条目会被自动序列化。 Axolotl 类中的 Variants 枚举就是一个例子。
扩展这些枚举会使你的自定义条目在 Minecraft 的命名空间下被序列化;在某些版本中,这甚至可能基于数字 ID 进行。 这并不理想,因为它可能会影响所有其他条目的索引。
如果某个枚举的条目会以这种方式被序列化,最好完全避免扩展该枚举。 相反,你可以考虑寻找可用的 API。






