全局战利品修改器

全局战利品修改器

全局战利品修改器(Global Loot Modifier,简称 GLM)是一种数据驱动的掉落物修改方式。它无需覆盖数十乃至数百张原版战利品表,也能在不知道加载了哪些 Mod 的情况下,实现需要修改其他 Mod 战利品表的效果。

GLM 会先对关联的战利品表进行抽取,再修改所得结果。GLM 的效果可以叠加,而非仅保留最后加载的一项,因此多个 Mod 可以修改同一张表;这一点与标签类似。

注册 GLM 需要四项内容:

  • global_loot_modifiers.json,位于 data/neoforge/loot_modifiers/global_loot_modifiers.json(不是 Mod 自己的命名空间)。它告诉 NeoForge 应应用哪些修改器及其顺序。
  • 描述修改器的 JSON 文件,包含全部修改数据,让数据包可以调整效果。路径为 data/<namespace>/loot_modifiers/<path>.json。
  • 实现 IGlobalLootModifier 或继承 LootModifier 的类(后者也实现了该接口),包含实际修改逻辑。
  • 用于编码和解码该类的 map codec,通常作为修改器类中的 public static final 字段。

global_loot_modifiers.json

此文件告诉 NeoForge 要对战利品表应用哪些修改器,可包含两个键:

  • entries:待加载修改器的列表。所列 ResourceLocation 指向 data/<namespace>/loot_modifiers/<path>.json 中的对应条目。修改器按列表顺序应用,这在处理 Mod 兼容问题时可能很重要。
  • replace:是否替换旧修改器(true),还是追加到现有列表(false)。它类似标签的同名键,但此处是必填项。Mod 开发者通常应使用 false;true 主要面向整合包或数据包开发者。

示例:

{
    "replace": false, // must be present
    "entries": [
        // represents a loot modifier in data/examplemod/loot_modifiers/example_glm_1.json
        "examplemod:example_glm_1",
        "examplemod:example_glm_2"
        // ...
    ]
}

修改器 JSON

此文件保存修改器的所有值,例如生效概率、要添加的物品等。应尽量避免硬编码,让数据包作者可以按需调整平衡。修改器至少必须包含两个字段,也可能包含其他字段:

  • 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
    ],
    // Extra properties specified by the codec
    "field1": "somestring",
    "field2": 10,
    "field3": "minecraft:dirt"
}

IGlobalLootModifier 与 LootModifier

要实际修改战利品表,必须提供 IGlobalLootModifier 实现。大多数情况下应使用 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, String field1, int field2, Item field3) {
        super(conditions);
        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;
    }
}

说明 修改器返回的掉落物列表会按注册顺序传入其他修改器。因此,应预期已修改的战利品仍可能被另一个修改器继续修改。

修改器 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
    "table": "minecraft:chests/abandoned_mineshaft" // the second table to roll
}

数据生成

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

public class MyGlobalLootModifierProvider extends GlobalLootModifierProvider {
    // Get the PackOutput from GatherDataEvent.
    public MyGlobalLootModifierProvider(PackOutput output) {
        super(output, 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.
        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()
                }, "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 event) {
    event.getGenerator().addProvider(event.includeServer(), MyGlobalLootModifierProvider::new);
}

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