Skip to main content
Version: 1.21.4

可扩展枚举(Extensible Enums)

可扩展枚举(Extensible Enums)是对特定原版(Vanilla)枚举类型的增强,允许你向其中添加新的枚举项。这一机制通过在运行时修改枚举的已编译字节码来实现,从而动态添加新的元素。

IExtensibleEnum

所有支持扩展的枚举类型都会实现 IExtensibleEnum 接口。该接口作为标记,便于 RuntimeEnumExtender 启动插件服务识别哪些枚举需要被转换和增强。

warning

不应该在自己的枚举类型上实现该接口。根据实际需求,请优先使用映射(map)或注册表(registry)。
没有被官方补丁处理、实现该接口的枚举类型,无法通过 mixin 或 coremod 等方式后补添加该接口,因为转换器的执行顺序不允许这样做。

创建枚举项(Enum Entry)

要创建新的枚举项,需要新建一个 JSON 文件,并在 neoforge.mods.toml[[mods]] 块中通过 enumExtensions 条目进行引用。指定的路径必须是相对于 resources 目录的相对路径:

# 在 neoforge.mods.toml 中:
[[mods]]
## 文件路径相对于资源输出目录,或者编译后 jar 包内的根路径
## 'resources' 目录即资源的根输出目录
enumExtensions="META-INF/enumextensions.json"

条目的定义包括目标枚举的类名、新字段的名称(必须以 mod ID 为前缀)、用于构造该枚举项的构造方法描述符(descriptor),以及传递给该构造方法的参数。

{
"entries": [
{
// 要添加条目的枚举类
"enum": "net/minecraft/world/item/ItemDisplayContext",
// 新条目的字段名,必须以模组 ID 为前缀
"name": "EXAMPLEMOD_STANDING",
// 要使用的构造方法
"constructor": "(ILjava/lang/String;Ljava/lang/String;)V",
// 直接提供的常量参数
"parameters": [ -1, "examplemod:standing", null ]
},
{
"enum": "net/minecraft/world/item/Rarity",
"name": "EXAMPLEMOD_CUSTOM",
"constructor": "(ILjava/lang/String;Ljava/util/function/UnaryOperator;)V",
// 要使用的参数,通过给定类中的一个 EnumProxy<Rarity> 字段引用提供
"parameters": {
"class": "example/examplemod/MyEnumParams",
"field": "CUSTOM_RARITY_ENUM_PROXY"
}
},
{
"enum": "net/minecraft/world/damagesource/DamageEffects",
"name": "EXAMPLEMOD_TEST",
"constructor": "(Ljava/lang/String;Ljava/util/function/Supplier;)V",
// 要使用的参数,通过给定类中的一个方法引用提供
"parameters": {
"class": "example/examplemod/MyEnumParams",
"method": "getTestDamageEffectsParameter"
}
}
]
}
public class MyEnumParams {
public static final EnumProxy<Rarity> CUSTOM_RARITY_ENUM_PROXY = new EnumProxy<>(
Rarity.class, -1, "examplemod:custom", (UnaryOperator<Style>) style -> style.withItalic(true)
);

public static Object getTestDamageEffectsParameter(int idx, Class<?> type) {
return type.cast(switch (idx) {
case 0 -> "examplemod:test";
case 1 -> (Supplier<SoundEvent>) () -> SoundEvents.DONKEY_ANGRY;
default -> throw new IllegalArgumentException("Unexpected parameter index: " + idx);
});
}
}

构造方法(Constructor)

构造方法(constructor)必须以 方法描述符 的形式指定,并且只能包含源代码中可见的参数,省略掉隐藏的常量名和序号参数。
如果某个构造方法被 @ReservedConstructor 注解标记,则该构造方法不能用于模组添加的枚举常量。

参数(Parameters)

参数可以通过三种方式指定,不同方式对参数类型有不同的限制:

  • 直接在 JSON 文件中以内联数组的形式传递常量(仅允许原始值、字符串(String),以及为任何引用类型传递 null)
  • 作为对模组(mod)中某个类的 EnumProxy<TheEnum> 类型字段的引用(参见上文中的 EnumProxy 示例)
    • 第一个参数指定目标枚举(enum),后续参数将传递给枚举的构造函数
  • 作为返回 Object 的方法引用,该方法的返回值将作为参数值使用。该方法必须严格接收两个参数,类型分别为 int(参数索引)和 Class<?>(参数期望的类型)
    • 应使用 Class<?> 对象进行类型转换(Class#cast()),以保证在模组代码中能捕获到 ClassCastException 异常。
warning

作为参数值来源的字段和/或方法应放在单独的类中,以避免过早地意外加载模组类。

某些参数有额外的规则:

  • 如果参数是与枚举上的 @IndexedEnum 注解相关的 int ID 参数,则该参数会被忽略,并由条目的序号(ordinal)替代。如果该参数在 JSON 中以内联方式指定,则必须填为 -1,否则会抛出异常。
  • 如果参数是与枚举上的 @NamedEnum 注解相关的字符串(String)名称参数,则必须以 namespace:path 格式(即 ResourceLocation 中常见的格式)加上模组 ID 作为前缀,否则会抛出异常。

获取生成的常量(Retrieving the Generated Constant)

生成的枚举常量可以通过 TheEnum.valueOf(String) 方法获取。如果参数是通过字段引用提供的,也可以通过 EnumProxy 对象的 EnumProxy#getValue() 方法获取该常量。

参与 NeoForge 开发(Contributing to NeoForge)

要向 NeoForge 添加一个新的可扩展枚举(extensible enum),至少需要做以下两件事:

  • 让该枚举实现 IExtensibleEnum 接口,以标记该枚举应由 RuntimeEnumExtender 进行转换。
  • 添加一个 getExtensionInfo 方法,并返回 ExtensionInfo.nonExtended(TheEnum.class)

根据枚举的具体情况,可能还需要进一步的操作:

  • 如果枚举(enum)有一个 int 类型的 ID 参数,并且该参数应与条目的序号(ordinal)保持一致,那么应为该枚举添加 @NumberedEnum 注解。如果 ID 不是第一个参数,则注解的值应为 ID 参数的索引。
  • 如果枚举有一个 String 类型的名称参数用于序列化(serialization),并且因此需要带命名空间(namespaced),那么应为该枚举添加 @NamedEnum 注解。如果名称不是第一个参数,则注解的值应为名称参数的索引。
  • 如果该枚举会通过网络发送,则应为其添加 @NetworkedEnum 注解,注解的参数用于指定允许发送的方向(clientbound、serverbound 或 bidirectional)。
  • 如果枚举的构造函数(constructor)无法被模组(mod)使用(例如需要注册表对象,而这些对象可能在模组注册之前就已初始化),则应为其添加 @ReservedConstructor 注解。
note

如果该枚举实际被扩展添加了条目,则 getExtensionInfo 方法会在运行时被转换为动态生成的 ExtensionInfo 实例。

// 这是一个示例,不是 Vanilla 中的实际枚举
public enum ExampleEnum implements net.neoforged.fml.common.asm.enumextension.IExtensibleEnum {
// VALUE_1 这里代表名称参数
VALUE_1(0, "value_1", false),
VALUE_2(1, "value_2", true),
VALUE_3(2, "value_3");

ExampleEnum(int arg1, String arg2, boolean arg3) {
// ...
}

ExampleEnum(int arg1, String arg2) {
this(arg1, arg2, false);
}

public static net.neoforged.fml.common.asm.enumextension.ExtensionInfo getExtensionInfo() {
// 返回未扩展的 ExtensionInfo
return net.neoforged.fml.common.asm.enumextension.ExtensionInfo.nonExtended(ExampleEnum.class);
}
}