全局战利品修改器

全局战利品修改器

全局战利品修改器(Global Loot Modifier,简称 GLM)是一种数据驱动的掉落修改方式。它无需覆盖几十甚至几百个原版战利品表,也能处理需要与其他模组战利品表交互、却不预先知道加载了哪些模组的效果。

GLM 先抽取相关的战利品表,再将修改器应用到抽取结果。GLM 会叠加,而非采用最后加载者覆盖的方式,因此多个模组可以修改同一张战利品表,这与标签类似。

注册 GLM 需要三部分:

  • 表示战利品修改器的 JSON 文件。它包含修改所需的全部数据,让数据包可以调整效果。文件位于 data/<namespace>/loot_modifiers/<path>.json。
  • 实现 IGlobalLootModifier 或继承 LootModifier 的类(后者也实现了 IGlobalLootModifier)。它包含使修改器生效的代码。
  • 用于编码、解码修改器类的 map codec。通常将其实现为修改器类中的 public static final 字段。

战利品修改器 JSON

此文件包含修改器的所有相关值,例如生效概率、添加哪些物品等。JSON 位于 data/<namespace>/loot_modifiers/<path>.json,其中 <namespace> 和 <path> 是唯一 Identifier 的组成部分。建议尽可能避免硬编码值,让数据包作者可以按需调整平衡。战利品修改器至少必须包含两个字段,具体情况下还可以有更多:

  • type:战利品修改器的注册名称。
  • conditions:激活此修改器所需的战利品表条件列表。
  • 根据所用 codec,还可能有其他必需或可选属性。

建议 GLM 的常见用途是向某个特定战利品表添加额外战利品。可以使用 neoforge:loot_table_id 条件实现。

使用示例如下:

{
    // This is the registry name of the loot modifier
    "type": "examplemod:my_loot_modifier",
    "conditions": [
        // Loot table conditions here
    ],
    // An optional property typically provided by loot modifiers
    // to denote the order that the modifiers should be applied,
    // from highest to lowest.
    // Typically defaults to 1000.
    "priority": 900,
    // Extra properties specified by the codec
    "field1": "somestring",
    "field2": 10,
    "field3": "minecraft:dirt"
}

IGlobalLootModifier 与 LootModifier

要真正将修改器应用到战利品表,必须提供 IGlobalLootModifier 的实现。多数情况下,应使用 LootModifier 子类,它会处理条件、优先级等内容。首先在自己的修改器类中继承 LootModifier:

// We cannot use a record because records cannot extend other classes.
public class MyLootModifier extends LootModifier {
    // See below for how the codec works.
    public static final MapCodec<MyLootModifier> CODEC = ...;
    // Our extra properties.
    private final String field1;
    private final int field2;
    private final Item field3;
    
    // First constructor parameter is the list of conditions. The rest is our extra properties.
    public MyLootModifier(LootItemCondition[] conditions, int priority, String field1, int field2, Item field3) {
        super(conditions, priority);
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
    }
    
    // Return our codec here.
    @Override
    public MapCodec<? extends IGlobalLootModifier> codec() {
        return CODEC;
    }
    
    // This is where the magic happens. Use your extra properties here if needed.
    // Parameters are the existing loot, and the loot context.
    @Override
    protected ObjectArrayList<ItemStack> doApply(ObjectArrayList<ItemStack> generatedLoot, LootContext context) {
        // Add your items to generatedLoot here.
        return generatedLoot;
    }
}

说明 修改器返回的掉落列表,会按照 priority 从高到低的顺序传入其他修改器。因此,应预期已经修改过的战利品还会被其他修改器继续修改。

战利品修改器 Codec

为了让游戏知道修改器的存在,必须为它定义并注册一个 codec。继续前面包含三个字段的例子:

public static final MapCodec<MyLootModifier> CODEC = RecordCodecBuilder.mapCodec(inst -> 
        // LootModifier#codecStart adds the conditions field.
        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 注册到注册表:

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);

内置战利品修改器

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

neoforge:add_table

此修改器抽取第二张战利品表,并将结果加入当前应用修改器的战利品表。

{
    "type": "neoforge:add_table",
    "conditions": [], // the required loot conditions
    "priority": 1000, // the optional priority of execution
    "table": "minecraft:chests/abandoned_mineshaft" // the second table to roll
}

数据生成

GLM 可以通过数据生成创建。继承 GlobalLootModifierProvider 即可:

public class MyGlobalLootModifierProvider extends GlobalLootModifierProvider {
    // Get the parameters from the `GatherDataEvent`s.
    public MyGlobalLootModifierProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries) {
        super(output, registries, ExampleMod.MOD_ID);
    }
    
    @Override
    protected void start() {
        // Call #add to add a new GLM. This also adds a corresponding entry in global_loot_modifiers.json.
        this.add(
                // The name of the modifier. This will be the file name.
                "my_loot_modifier_instance",
                // The loot modifier to add. For the sake of example, we add a weather loot condition.
                new MyLootModifier(new LootItemCondition[] {
                        WeatherCheck.weather().setRaining(true).build()
                }, 900, "somestring", 10, Items.DIRT),
                // A list of data load conditions. Note that these are unrelated to the loot conditions
                // specified on the modifier itself. For the sake of example, we add a mod loaded condition.
                // An overload of #add is available that accepts a vararg of conditions instead of a list.
                List.of(new ModLoadedCondition("create"))
        );
    }
}

与所有数据提供器一样,必须在 GatherDataEvent 事件中注册提供器:

@SubscribeEvent // on the mod event bus
public static void onGatherData(GatherDataEvent.Client event) {
    // Call event.createDatapackRegistryObjects(...) first if adding datapack objects

    event.createProvider(MyGlobalLootModifierProvider::new);
}

来源: NeoForged/Documentation(26.1)。中文翻译依据提交 4a680cdd4d15。