全局战利品修改器

全局战利品修改器

全局战利品修改器以数据驱动方式修改采集掉落,无需覆盖几十乃至数百个原版战利品表,也能在不知道加载了哪些 Mod 的情况下处理与其他 Mod 战利品表相关的效果。它们像标签一样可以叠加,而非后加载者覆盖前者。

全局战利品修改器顺序示意:原始掉落经修改器 A 再经 B 得到最终结果;全局 entries 是有序列表,其文件必须位于 data/forge/loot_modifiers/global_loot_modifiers.json。

原创说明图,依据 Forge 1.21.x 官方文档 绘制。

注册全局战利品修改器

需要四项内容:

  1. 创建 global_loot_modifiers.json,告知 Forge 你的修改器,工作方式类似标签。
  2. 一个表示修改器的序列化 JSON,包含修改数据,允许数据包调整效果。
  3. 实现 IGlobalLootModifier 的类,包含使修改器工作的操作代码。大多数开发者可以继承提供基础功能的 LootModifier。
  4. 编码和解码操作类的 codec,像其他 IForgeRegistryEntry 一样注册。

global_loot_modifiers.json

此文件表示要加载到游戏中的所有战利品修改器,必须位于 data/forge/loot_modifiers/global_loot_modifiers.json。它只从 forge 命名空间读取,放在 Mod 自己的命名空间下会被忽略。

entries 是待加载修改器的有序列表。ResourceLocation 指向 data/<namespace>/loot_modifiers/<path>.json 中对应条目,主要供数据包作者解决不同 Mod 修改器的冲突。

replace 为 true 时,不再追加修改器,而是完全替换全局列表。为了兼容其他 Mod,开发者通常应使用 false;数据包作者可能希望使用 true 进行覆盖。

带注释和省略号的示例用于说明结构;实际 JSON 文件需移除注释并补全内容。

{
  "replace": false, // 必须提供
  "entries": [
    // 表示 data/examplemod/loot_modifiers/example_glm.json 中的修改器
    "examplemod:example_glm",
    "examplemod:example_glm2" // ...
  ]
}

序列化 JSON

此文件包含修改器的所有潜在变量,包括修改战利品前必须满足的条件。应尽可能避免硬编码,使数据包作者能够按需调整平衡。

type 表示用于读取 JSON 的 codec 的注册表名称,必须存在。conditions 表示修改器激活所需的战利品表条件,也必须存在;应避免硬编码,给数据包作者调整条件的灵活性。

重要:只有使用 Forge 附带类时,conditions 才按上述方式表示激活条件。继承 LootModifier 时,所有条件以 AND 组合并检查,以决定是否应用修改器。

还可以指定由序列化器读取、由修改器定义的其他属性。

// 在 data/examplemod/loot_modifiers/example_glm.json 中
{
  "type": "examplemod:example_loot_modifier",
  "conditions": [
    // 普通战利品表条件
    // ...
  ],
  "prop1": "val1",
  "prop2": 10,
  "prop3": "minecraft:dirt"
}

IGlobalLootModifier

要提供全局战利品修改器的功能,必须实现 IGlobalLootModifier。序列化器每次从 JSON 解码信息并将其提供给对象时都会生成实例。

新修改器需要定义 #apply 和 #codec。#apply 接收当前将生成的战利品,以及当前世界或额外参数等上下文信息,返回要生成的掉落物列表。一个修改器返回的列表按注册顺序传给其他修改器,因此修改后的战利品还可能被另一个修改器修改。#codec 返回用于在 JSON 和修改器之间编码、解码的已注册 codec。

LootModifier 子类

LootModifier 是 IGlobalLootModifier 的抽象实现,提供大多数开发者可轻松继承的基础功能。它定义 #apply 检查条件,决定是否修改生成的战利品。

子类中需注意两点:构造函数必须接收 LootItemCondition 数组,以及 #doApply 方法。条件数组定义修改前必须满足的条件,所有条件以 AND 组合,即全部必须为真。#doApply 与 #apply 类似,但只在所有条件为真时执行。

public class ExampleModifier extends LootModifier {
  public ExampleModifier(LootItemCondition[] conditionsIn, String prop1, int prop2, Item prop3) {
    super(conditionsIn);
    // 保存其余参数
  }

  @NotNull
  @Override
  protected ObjectArrayList<ItemStack> doApply(ObjectArrayList<ItemStack> generatedLoot, LootContext context) {
    // 修改战利品并返回新掉落物
  }

  @Override
  public Codec<? extends IGlobalLootModifier> codec() {
    // 返回用于编码和解码此修改器的 codec
  }
}

战利品修改器 Codec

JSON 与 IGlobalLootModifier 实例通过 Codec 连接,T 表示所用修改器类型。

为方便使用,LootModifier#codecStart 提供战利品条件 codec,以便加入类似 record 的 codec。它用于对应修改器的数据生成。

// 对于某个 DeferredRegister<Codec<? extends IGlobalLootModifier>> REGISTRAR
public static final RegistryObject<Codec<ExampleModifier>> EXAMPLE_CODEC = REGISTRAR.register("example_codec", () ->
  RecordCodecBuilder.create(
    inst -> LootModifier.codecStart(inst).and(
      inst.group(
        Codec.STRING.fieldOf("prop1").forGetter(m -> m.prop1),
        Codec.INT.fieldOf("prop2").forGetter(m -> m.prop2),
        ForgeRegistries.ITEMS.getCodec().fieldOf("prop3").forGetter(m -> m.prop3)
      )
    ).apply(inst, ExampleModifier::new)
  )
);

原文示例中的字段声明缺少变量名,译文补为 EXAMPLE_CODEC。Forge Git 仓库中有示例,包括精准采集和烧炼效果;原文链接指向 1.20.x。

原文:Global Loot Modifiers。