Skip to main content
Version: 1.21.4

全局掉落修饰器(Global Loot Modifiers)

全局掉落修饰器(Global Loot Modifiers,简称 GLMs)是一种基于数据驱动的方式,可以在无需覆盖大量原版掉落表(loot tables)的情况下修改掉落内容,或实现在不了解当前加载了哪些模组(mod)的前提下,与其他模组掉落表交互的效果。

GLM 的工作流程是:首先投掷(roll)关联的 掉落表,然后将 GLM 应用于掉落表投掷的结果。GLM 是可叠加的,而不是“最后加载者生效”(last-load-wins)的机制,这样可以让多个模组同时修改同一个掉落表,这一机制与 标签(tags) 类似。

要注册一个 GLM,你需要准备以下四项内容:

  • 一个 global_loot_modifiers.json 文件,位于 data/neoforge/loot_modifiers/global_loot_modifiers.json 路径下(注意,不在你的模组命名空间下)。这个文件告诉 NeoForge 应该应用哪些修饰器(modifier),以及它们的应用顺序。
  • 一个表示你掉落修饰器的 JSON 文件。这个文件包含了所有与掉落修改相关的数据,允许数据包(data pack)制作者调整你的效果。路径为 data/<namespace>/loot_modifiers/<path>.json
  • 一个实现了 IGlobalLootModifier 或继承自 LootModifier(后者本身实现了 IGlobalLootModifier)的类。这个类包含了让修饰器生效的具体代码。
  • 一个用于编码和解码你掉落修饰器类的 编解码器(codec)。通常,这会作为修饰器类中的 public static final 字段实现。

global_loot_modifiers.json

global_loot_modifiers.json 文件用于告诉 NeoForge 应该对掉落表应用哪些修饰器。该文件可能包含两个键:

  • entries 是一个应当被加载的修饰器列表。这里指定的 ResourceLocation 会指向其在 data/<namespace>/loot_modifiers/<path>.json 下的对应条目。该列表是有序的,意味着修饰器会按照指定顺序应用,这在处理模组兼容性问题时有时很重要。
  • replace 表示这些修饰器是否应当替换现有修饰器(true),还是仅仅添加到现有列表中(false)。它的工作方式类似于 标签(tags) 中的 replace 键,但与标签不同,这里的 replace 是必需的。通常情况下,模组开发者应始终使用 false;而 true 主要是为模组包(modpack)或数据包开发者准备的。

示例用法:

{
"replace": false, // 必须存在
"entries": [
// 表示 data/examplemod/loot_modifiers/example_glm_1.json 中的一个掉落修饰器
"examplemod:example_glm_1",
"examplemod:example_glm_2"
// ...
]
}

掉落修饰器 JSON 文件(The Loot Modifier JSON)

该文件包含了所有与修饰器相关的数值,例如应用几率、要添加哪些物品等。建议尽量避免硬编码数值,这样数据包作者可以根据需要调整平衡。一个掉落修饰器 JSON 至少必须包含两个字段,具体字段数量可根据实际需求增加:

  • type 字段包含了战利品修改器(loot modifier)的注册表名称(registry name)。
  • conditions 字段是一个战利品表条件(loot table conditions)的列表,用于判断此修改器是否激活。
  • 根据所用的编解码器(codec),可能还需要或可选其他属性。
tip

GLM(全局战利品修改器,Global Loot Modifier)的一个常见用途是为某个特定的战利品表添加额外掉落。为此,可以使用 neoforge:loot_table_id 条件

一个示例用法可能如下所示:

{
// 这是战利品修改器的注册表名称
"type": "examplemod:my_loot_modifier",
"conditions": [
// 这里填写战利品表条件
],
// 由编解码器指定的额外属性
"field1": "somestring",
"field2": 10,
"field3": "minecraft:dirt"
}

IGlobalLootModifierLootModifier

要实际将战利品修改器应用到战利品表,必须指定一个 IGlobalLootModifier 实现。在大多数情况下,你会希望使用 LootModifier 子类,因为它会帮你处理诸如条件(conditions)等内容。开始时,我们可以在自己的战利品修改器类中继承(extend)LootModifier

// 不能用 record,因为 record 不能继承其他类。
public class MyLootModifier extends LootModifier {
// 编解码器的实现见下文。
public static final MapCodec<MyLootModifier> CODEC = ...;
// 这里是我们的额外属性。
private final String field1;
private final int field2;
private final Item field3;

// 第一个构造参数是条件列表,后面是我们自定义的额外属性。
public MyLootModifier(LootItemCondition[] conditions, String field1, int field2, Item field3) {
super(conditions);
this.field1 = field1;
this.field2 = field2;
this.field3 = field3;
}

// 返回我们自定义的编解码器。
@Override
public MapCodec<? extends IGlobalLootModifier> codec() {
return CODEC;
}

// 这里是主要逻辑。如果需要可以用到你的额外属性。
// 参数分别是已有的掉落物和战利品上下文。
@Override
protected ObjectArrayList<ItemStack> doApply(ObjectArrayList<ItemStack> generatedLoot, LootContext context) {
// 在这里向 generatedLoot 添加你的物品。
return generatedLoot;
}
}
info

从一个修改器返回的掉落物列表会按照注册顺序传递给其他修改器。因此,被修改过的战利品可以并且应该预期会被其他战利品修改器再次修改。

战利品修改器编解码器(The Loot Modifier Codec)

为了让游戏识别我们自定义的战利品修改器,必须为其定义并注册一个编解码器。结合前面包含三个字段的例子,代码大致如下所示:

public static final MapCodec<MyLootModifier> CODEC = RecordCodecBuilder.mapCodec(inst -> 
// LootModifier#codecStart 添加了 conditions 字段。
LootModifier.codecStart(inst).and(inst.group(
Codec.STRING.fieldOf("field1").forGetter(e -> e.field1),
Codec.INT.fieldOf("field2").forGetter(e -> e.field2),
BuiltInRegistries.ITEM.byNameCodec().fieldOf("field3").forGetter(e -> e.field3)
)).apply(inst, MyLootModifier::new)
);

然后,我们将该 codec 注册 到注册表(registry)中:

public static final DeferredRegister<MapCodec<? extends IGlobalLootModifier>> GLOBAL_LOOT_MODIFIER_SERIALIZERS =
DeferredRegister.create(NeoForgeRegistries.Keys.GLOBAL_LOOT_MODIFIER_SERIALIZERS, ExampleMod.MOD_ID);

public static final Supplier<MapCodec<MyLootModifier>> MY_LOOT_MODIFIER =
GLOBAL_LOOT_MODIFIER_SERIALIZERS.register("my_loot_modifier", () -> MyLootModifier.CODEC);

内置战利品修改器(Builtin Loot Modifiers)

NeoForge 提供了一个可直接使用的战利品修改器(loot modifier):

neoforge:add_table

这个战利品修改器会额外 roll 一次指定的战利品表(loot table),并把结果添加到当前应用该修改器的战利品表中。

{
"type": "neoforge:add_table",
"conditions": [], // 所需的战利品条件
"table": "minecraft:chests/abandoned_mineshaft" // 需要额外 roll 的第二个表
}

数据生成(Datagen)

全局战利品修改器(GLM)可以通过 数据生成 的方式自动生成。这可以通过继承 GlobalLootModifierProvider 实现:

public class MyGlobalLootModifierProvider extends GlobalLootModifierProvider {
// 从 `GatherDataEvent` 获取参数。
public MyGlobalLootModifierProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries) {
super(output, registries, ExampleMod.MOD_ID);
}

@Override
protected void start() {
// 调用 #add 方法添加一个新的 GLM。这也会在 global_loot_modifiers.json 中添加对应条目。
this.add(
// 修改器的名称。这个名称会作为文件名。
"my_loot_modifier_instance",
// 要添加的战利品修改器。这里举例添加了一个天气条件(weather loot condition)。
new MyLootModifier(new LootItemCondition[] {
WeatherCheck.weather().setRaining(true).build()
}, "somestring", 10, Items.DIRT),
// 数据加载条件列表。注意这些条件与修改器自身指定的战利品条件无关。这里举例添加了一个模组加载条件(mod loaded condition)。
// #add 方法有重载版本,支持传入变长参数而不是列表。
List.of(new ModLoadedCondition("create"))
);
}
}

和所有数据提供器(data provider)一样,你必须将该 provider 注册到 GatherDataEvent

@SubscribeEvent
public static void onGatherData(GatherDataEvent.Client event) {
// 如果要添加数据包对象,需先调用 event.createDatapackRegistryObjects(...)

event.createProvider(MyGlobalLootModifierProvider::new);
}