全局战利品修改器
全局战利品修改器(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。