战利品表

战利品表

战利品表是用于定义随机掉落物的数据文件。抽取战利品表会返回一个物品堆叠列表,列表可能为空。这个过程的输出取决于伪随机数。战利品表位于 data/<mod_id>/loot_table/<name>.json。例如,泥土方块使用的 minecraft:blocks/dirt 战利品表位于 data/minecraft/loot_table/blocks/dirt.json。

Minecraft 在方块掉落、实体掉落、箱子战利品、钓鱼战利品等许多游戏环节使用战利品表。引用方式取决于使用场景:

  • 每个方块默认都有一个关联的战利品表,位置为 <block_namespace>:blocks/<block_name>。在方块的 Properties 上调用 #noLootTable 可以禁用它,此时不会创建战利品表,方块也不会掉落任何东西。这主要用于类似空气的方块或技术性方块。
  • 每个没有调用 EntityType.Builder#noLootTable 的实体(通常是 MobCategory#MISC 中的实体)默认都有一个关联的战利品表,位置为 <entity_namespace>:entities/<entity_name>。可以通过重写 #getLootTable 更改。例如,羊据此根据羊毛颜色抽取不同的战利品表。
  • 结构内的箱子在方块实体数据中指定战利品表。Minecraft 将所有箱子战利品表存放在 minecraft:chests/<chest_name>;建议模组遵循此惯例,但并非强制。
  • 袭击结束后村民可能扔给玩家的礼物,其战利品表由 neoforge:raid_hero_gifts 数据映射定义。
  • 其他战利品表(例如钓鱼战利品表)在需要时通过 level.getServer().reloadableRegistries().getLootTable(lootTableKey) 获取。所有原版战利品表的位置可在 BuiltInLootTables 中找到。

警告 一般只应为属于自己模组的内容创建战利品表。修改现有战利品表时,应改用全局战利品修改器(GLM)。

战利品表系统较为复杂,由多个各司其职的子系统组成。

战利品条目

战利品条目(或战利品池条目)在代码中由抽象类 LootPoolEntryContainer 表示,是一个独立的战利品元素。它可以指定掉落一个或多个物品。

战利品条目通常分为单项条目(共同父类为 LootPoolSingletonContainer)与组合条目(共同父类为 CompositeEntryBase),后者由多个单项条目组成。Minecraft 提供以下单项类型:

  • minecraft:empty:空条目,表示不产生物品。在代码中调用 EmptyLootItem#emptyItem 创建。
  • minecraft:item:单个物品条目,抽取时掉落指定物品。在代码中向 LootItem#lootTableItem 传入所需物品来创建。
    • 堆叠数量、数据组件等可以通过战利品函数设置。
  • minecraft:tag:标签条目,抽取时掉落指定标签中的所有物品。根据布尔属性 expand 的值分为两种变体。expand 为 true 时,为标签中的每个物品生成独立条目;否则用一个条目掉落所有物品。分别调用 TagEntry#tagContents(expand=false)或 TagEntry#expandTag(expand=true)创建,两者都接收物品标签键参数。
    • 例如,expand 为 true 且标签为 #minecraft:planks 时,为每种木板生成一个条目(即 11 种原版木板对应 11 个条目,再加上每种模组木板各一个),每个条目都有指定的权重、品质和函数;expand 为 false 时,则使用一个条目掉落所有木板。
  • minecraft:slots:引用任意物品栏槽位(例如实体、物品等)的战利品条目。minecraft:slot_range 来源可以指向实体和方块实体;minecraft:contents 可以指向具有基于内容物的数据组件的物品,前提是这些组件定义并注册了 ContainerComponentManipulator。
  • minecraft:dynamic:引用动态掉落的条目。动态掉落用于向战利品表加入无法预先指定、需要在代码中添加的内容。动态掉落条目由 ID 和真正添加物品的 Consumer<ItemStack> 组成。添加时,先指定具有所需 ID 的 minecraft:dynamic 条目,再在战利品上下文中添加对应 consumer。使用 DynamicLoot#dynamicEntry 创建。
  • minecraft:loot_table:抽取另一个战利品表,并将其结果作为单个条目加入。另一个表既可通过 ID 指定,也可完整内联。代码中调用带 Identifier 参数的 NestedLootTable#lootTableReference 创建引用,或调用带 LootTable 对象参数的 NestedLootTable#inlineLootTable 创建内联表。

Minecraft 提供以下组合类型:

  • minecraft:group:包含其他条目列表,并按顺序执行。在代码中调用 EntryGroup#list,或对另一个 LootPoolSingletonContainer.Builder 调用 #append 创建,参数都是其他战利品条目构建器。
  • minecraft:sequence:类似 minecraft:group,但一旦某个子条目失败就停止执行,丢弃其后的所有条目。在代码中调用 SequentialEntry#sequential,或对另一个 LootPoolSingletonContainer.Builder 调用 #then 创建,参数都是其他条目构建器。
  • minecraft:alternatives:在某种意义上与 minecraft:sequence 相反,一旦某个子条目成功就停止执行(而不是失败时停止),丢弃其后的所有条目。在代码中调用 AlternativesEntry#alternatives,或对另一个 LootPoolSingletonContainer.Builder 调用 #otherwise 创建,参数都是其他条目构建器。

通过共同父类 LootPoolEntryContainer,所有条目都有 conditions 属性,用于提供应用到此条目的战利品条件列表。任一条件失败时,就将该条目视为不存在。

继承 LootPoolSingletonContainer 的单项条目还具有:

  • weight:权重,默认为 1。用于让某些物品比其他物品更常见。例如,两个条目的权重分别为 3 和 1,则第一个条目有 75% 的概率被选中,第二个有 25%。
  • quality:品质值,默认为 0。非零时,在抽取战利品表时将其与幸运值(在战利品上下文中设置)相乘,再加到权重上。
  • functions:应用于此条目输出的战利品函数列表。

模组开发者也可以定义自定义战利品条目类型。

战利品池

战利品池本质上是战利品条目的列表。战利品表可以包含多个池,各池独立抽取。

战利品池可包含以下内容:

  • entries:战利品条目列表。
  • conditions:应用于此池的战利品条件列表。任一条件失败,就不会抽取池中的任何条目。
  • functions:应用于此池所有条目输出的战利品函数列表。
  • rolls 和 bonus_rolls:两个数字提供器(下文介绍),共同决定此池的抽取次数。公式为 rolls + bonus_rolls * luck,其中幸运值在战利品参数中设置。
  • name:池的名称,由 NeoForge 添加,可供 GLM 使用。未指定时,使用以 custom# 为前缀的池哈希码。

数字提供器

数字提供器用于在数据包环境下获取伪随机数字。它们主要用于战利品表,也用于世界生成等其他场景。原版提供以下六种数字提供器:

  • minecraft:constant:恒定浮点值,需要时取整。通过 ConstantValue#exactly 创建。

  • minecraft:uniform:设定最小值、最大值的均匀分布随机整数或浮点数。两者之间所有值出现的概率相同。通过 UniformGenerator#between 创建。

  • minecraft:binomial:设定 n 和 p 值的二项分布随机整数。这些值的含义见二项分布。通过 BinomialDistributionGenerator#binomial 创建。

  • minecraft:score:给定实体目标、计分项名称以及可选的缩放值,获取目标实体的对应计分板值,并乘以缩放值(如果提供)。通过 ScoreboardValue#fromScoreboard 创建。

  • minecraft:storage:从命令存储中指定的 NBT 路径获取值。通过 new StorageValue 创建。

  • minecraft:sum:将其他数字提供器的值相加。通过 new Sum 创建。

  • minecraft:enchantment_level:为各附魔等级提供值。通过 EnchantmentLevelProvider#forEnchantmentLevel 创建,并传入 LevelBasedValue。有效的 LevelBasedValue 包括:

    • 不指定类型的常量值。通过 LevelBasedValue#constant 创建。
    • minecraft:linear:随附魔等级线性增长的值,可加上常量基础值。通过 LevelBasedValue#perLevel 创建。
    • minecraft:levels_squared:将附魔等级平方,再加上可选基础值。通过 new LevelBasedValue.LevelsSquared 创建。
    • minecraft:fraction:接收另外两个 LevelBasedValue,用它们构成分数。通过 new LevelBasedValue.Fraction 创建。
    • minecraft:clamped:接收另一个 LevelBasedValue 以及最小、最大值。计算该 LevelBasedValue,再将结果限制在范围内。通过 new LevelBasedValue.Clamped 创建。
    • minecraft:exponent:接收另外两个 LevelBasedValue,计算第一个值的第二个值次幂。通过 new LevelBasedValue.Exponent 创建。
    • minecraft:lookup:接收 List<Float> 和后备 LevelBasedValue。从列表查找所需值(等级 1 对应第一个元素,等级 2 对应第二个,以此类推),某等级缺少值时使用后备值。通过 LevelBasedValue#lookup 创建。
  • minecraft:environment_attribute:获取当前位置或维度中环境属性的数值。通过 new EnvironmentAttributeValue 创建。

模组开发者还可以按需注册自定义数字提供器和自定义基于等级的值。

战利品参数

战利品参数在内部称为 ContextKey<T>,是抽取战利品表时提供的参数。T 为参数类型,例如 BlockPos 或 Entity。战利品条件和战利品函数可以使用它们。例如,minecraft:killed_by_player 战利品条件会检查 minecraft:player 参数是否存在。

Minecraft 提供以下战利品参数:

  • minecraft:this_entity:与表关联的实体,通常是被击杀的实体。通过 LootContextParams.THIS_ENTITY 访问。
  • minecraft:interacting_entity:正在与战利品表交互的实体,例如正在挖掘方块的玩家。通过 LootContextParams.INTERACTING_ENTITY 访问。
  • minecraft:target_entity:与表关联的实体,通常是某次交互的目标。通过 LootContextParams.TARGET_ENTITY 访问。
  • minecraft:last_damage_player:与表关联的玩家,通常是最后攻击被击杀实体的玩家,即使击杀是间接的(例如玩家碰了一下实体,实体随后被尖刺杀死)。可用于仅限玩家击杀的掉落。通过 LootContextParams.LAST_DAMAGE_PLAYER 访问。
  • minecraft:damage_source:与表关联的伤害来源,通常是击杀实体的伤害来源。通过 LootContextParams.DAMAGE_SOURCE 访问。
  • minecraft:attacking_entity:与表关联的攻击实体,通常是击杀者。通过 LootContextParams.ATTACKING_ENTITY 访问。
  • minecraft:direct_attacking_entity:与表关联的直接攻击实体。例如,攻击实体为骷髅时,直接攻击实体就是箭。通过 LootContextParams.DIRECT_ATTACKING_ENTITY 访问。
  • minecraft:origin:与表关联的位置,例如战利品箱的位置。通过 LootContextParams.ORIGIN 访问。
  • minecraft:block_state:与表关联的方块状态,例如被破坏方块的状态。通过 LootContextParams.BLOCK_STATE 访问。
  • minecraft:block_entity:与表关联的方块实体,例如被破坏方块的方块实体。潜影盒等用它将物品栏保存到掉落物中。通过 LootContextParams.BLOCK_ENTITY 访问。
  • minecraft:tool:与表关联的物品实例,例如破坏方块时使用的物品。它不一定是工具。通过 LootContextParams.TOOL 访问。
  • minecraft:explosion_radius:当前上下文的爆炸半径,主要用于对掉落物应用爆炸衰减。通过 LootContextParams.EXPLOSION_RADIUS 访问。
  • minecraft:enchantment_level:附魔等级,用于附魔逻辑。通过 LootContextParams.ENCHANTMENT_LEVEL 访问。
  • minecraft:enchantment_active:所用物品是否具有附魔,例如用于精准采集检查。通过 LootContextParams.ENCHANTMENT_ACTIVE 访问。
  • minecraft:additional_cost_component_allowed:允许村民交易在其交易元数据要求时产生额外费用。

向 new ContextKey<T> 传入所需 ID 即可创建自定义战利品参数。它们只是资源位置的包装,无需注册。

实体目标

实体目标是战利品条件和函数使用的一种类型,在代码中由 LootContext.EntityTarget 枚举表示。它用于指定在条件或函数上下文中查询哪个实体战利品参数。有效值包括:

  • "this" 或 LootContext.EntityTarget.THIS:表示 "minecraft:this_entity" 参数。

  • "attacker" 或 LootContext.EntityTarget.ATTACKER:表示 "minecraft:attacking_entity" 参数。

  • "direct_attacker" 或 LootContext.EntityTarget.DIRECT_ATTACKER:表示 "minecraft:direct_attacking_entity" 参数。

  • "attacking_player" 或 LootContext.EntityTarget.ATTACKING_PLAYER:表示 "minecraft:last_damage_player" 参数。

  • "target_entity" 或 LootContext.EntityTarget.TARGET_ENTITY:表示 "minecraft:target_entity" 参数。

  • "interacting_entity" 或 LootContext.EntityTarget.INTERACTING_ENTITY:表示 "minecraft:interacting_entity" 参数。

例如,minecraft:entity_properties 战利品条件接收实体目标,因此战利品表作者可以按需要检查这四个战利品参数中的任意一个。

战利品参数集

战利品参数集也称为战利品表类型,在代码中称为 ContextKeySet,是必需和可选战利品参数的集合。尽管名称如此,它们并不是 Set(甚至也不是 Collection),而是两个 Set<ContextKey<?>> 的包装:一个保存必需参数(#required),另一个保存可选参数(#allowed)。它们用于验证参数使用方只使用预期可用的参数,并在抽取表时验证必需参数是否存在。此外,它们还用于进度和附魔逻辑。

原版提供以下战利品参数集(必需参数以粗体表示,可选参数以_斜体_表示;代码名称是 LootContextParamSets 中的常量):

ID 代码名称 指定的战利品参数 用途
minecraft:empty EMPTY 不适用 后备用途。
minecraft:generic ALL_PARAMS minecraft:origin, minecraft:tool, minecraft:block_state, minecraft:block_entity, minecraft:explosion_radius, minecraft:this_entity, minecraft:damage_source, minecraft:attacking_entity, minecraft:direct_attacking_entity, minecraft:last_damage_player 验证。
minecraft:command COMMAND minecraft:origin, minecraft:this_entity 命令。
minecraft:selector SELECTOR minecraft:origin, minecraft:this_entity 命令中的实体选择器。
minecraft:villager_trade VILLAGER_TRADE minecraft:origin, minecraft:this_entity, minecraft:additional_cost_component_allowed, 村民交易。
minecraft:block BLOCK minecraft:origin, minecraft:tool, minecraft:block_state, minecraft:block_entity, minecraft:explosion_radius, minecraft:this_entity 破坏方块。
minecraft:block_use BLOCK_USE minecraft:origin, minecraft:block_state, minecraft:this_entity 原版未使用。
minecraft:block_interact BLOCK_INTERACT minecraft:block_state, minecraft:block_entity, minecraft:interacting_entity, minecraft:tool 方块交互。
minecraft:hit_block HIT_BLOCK minecraft:origin, minecraft:enchantment_level, minecraft:block_state, minecraft:this_entity 引雷附魔。
minecraft:chest CHEST minecraft:origin, minecraft:this_entity, minecraft:attacking_entity 战利品箱及类似容器、运输矿车中的战利品。
minecraft:archaeology ARCHAEOLOGY minecraft:origin, minecraft:this_entity, minecraft:tool 考古。
minecraft:vault VAULT minecraft:origin, minecraft:this_entity, minecraft:tool 试炼密室宝库奖励。
minecraft:entity ENTITY minecraft:origin, minecraft:this_entity, minecraft:damage_source, minecraft:attacking_entity, minecraft:direct_attacking_entity, minecraft:last_damage_player 击杀实体。
minecraft:entity_interact ENTITY_INTERACT minecraft:target_entity, minecraft:tool, minecraft:interacting_entity 实体交互。
minecraft:shearing SHEARING minecraft:origin, minecraft:this_entity, minecraft:tool 剪取羊等实体。
minecraft:equipment EQUIPMENT minecraft:origin, minecraft:this_entity 僵尸等实体的装备。
minecraft:gift GIFT minecraft:origin, minecraft:this_entity 村庄英雄礼物。
minecraft:barter PIGLIN_BARTER minecraft:this_entity 猪灵以物易物。
minecraft:fishing FISHING minecraft:origin, minecraft:tool, minecraft:this_entity, minecraft:attacking_entity 钓鱼。
minecraft:enchanted_item ENCHANTED_ITEM minecraft:tool, minecraft:enchantment_level 多种附魔。
minecraft:enchanted_entity ENCHANTED_ENTITY minecraft:origin, minecraft:enchantment_level, minecraft:this_entity 多种附魔。
minecraft:enchanted_damage ENCHANTED_DAMAGE minecraft:origin, minecraft:enchantment_level, minecraft:this_entity, minecraft:damage_source, minecraft:attacking_entity, minecraft:direct_attacking_entity 伤害和保护附魔。
minecraft:enchanted_location ENCHANTED_LOCATION minecraft:origin, minecraft:enchantment_level, minecraft:enchantment_active, minecraft:this_entity 冰霜行者和灵魂疾行附魔。
minecraft:advancement_entity ADVANCEMENT_ENTITY minecraft:origin, minecraft:this_entity 多种进度判据。
minecraft:advancement_location ADVANCEMENT_LOCATION minecraft:origin, minecraft:tool, minecraft:block_state, minecraft:this_entity 多种进度触发器。
minecraft:advancement_reward ADVANCEMENT_REWARD minecraft:origin, minecraft:this_entity 进度奖励。

战利品上下文

战利品上下文包含抽取战利品表时的情境信息,包括:

  • 抽取表所在的 ServerLevel,通过 #getLevel 获取。
  • 抽取表使用的 RandomSource,通过 #getRandom 获取。
  • 战利品参数。用 #hasParameter 检查是否存在,用 #getParameter 获取单个参数。
  • 用于计算额外抽取次数和品质值的幸运值,通常来自实体的幸运属性。通过 #getLuck 获取。
  • 动态掉落 consumer。更多信息见上文。通过 #addDynamicDrops 设置,没有 getter。

战利品表

将上述元素组合起来,就得到了战利品表。战利品表 JSON 可指定以下值:

  • pools:战利品池列表。
  • neoforge:conditions:数据加载条件列表。警告:这些是数据加载条件,不是战利品条件!
  • functions:应用于此表全部条目输出的战利品函数列表。
  • type:用于验证战利品参数使用是否正确的参数集。可选,省略时跳过验证。
  • random_sequence:此表的随机序列,以资源位置形式指定。随机序列由 Level 提供,用于在相同条件下获得一致的抽取结果。通常使用表自身的位置。

战利品表示例格式如下:

{
    "type": "chest", // loot parameter set
    "neoforge:conditions": [
        // data load conditions
    ],
    "functions": [
        // table-wide loot functions
    ],
    "pools": [ // list of loot pools
        {
            "rolls": 1, // amount of rolls of the loot table, using 5 here will yield 5 results from the pool
            "bonus_rolls": 0.5, // amount of bonus rolls
            "name": "my_pool",
            "conditions": [
                // pool-wide loot conditions
            ],
            "functions": [
                // pool-wide loot functions
            ],
            "entries": [ // list of loot table entries
                {
                    "type": "minecraft:item", // loot entry type
                    "name": "minecraft:dirt", // type-specific properties, for example the name of the item
                    "weight": 3, // weight of an entry
                    "quality": 1, // quality of an entry
                    "conditions": [
                        // entry-wide loot conditions
                    ],
                    "functions": [
                        // entry-wide loot functions
                    ]
                }
            ]
        }
    ]
}

抽取战利品表

抽取战利品表需要两个东西:战利品表本身,以及战利品上下文。

先获取战利品表。可以使用 level.getServer().reloadableRegistries().getLootTable(lootTableId)。由于战利品数据只能通过服务端访问,这段逻辑必须运行在逻辑服务端,而非逻辑客户端。

建议 Minecraft 内置战利品表 ID 位于 BuiltInLootTables 类中。方块战利品表可通过 BlockBehaviour#getLootTable 获取,实体战利品表可通过 EntityType#getDefaultLootTable 或 Entity#getLootTable 获取。

有了战利品表,接着构建参数集。首先创建 LootParams.Builder 实例:

// Make sure that you are on a server, otherwise the cast will fail.
LootParams.Builder builder = new LootParams.Builder((ServerLevel) level);

然后添加战利品上下文参数:

// Use whatever context parameters and values you need. Vanilla parameters can be found in LootContextParams.
builder.withParameter(LootContextParams.ORIGIN, position);
// This variant can accept null as the value, in which case an existing value for that parameter will be removed.
builder.withOptionalParameter(LootContextParams.ORIGIN, null);
// Add a dynamic drop.
builder.withDynamicDrop(Identifier.fromNamespaceAndPath("examplemod", "example_dynamic_drop"), stackAcceptor -> {
    // some logic here
});
// Set our luck value. Assumes that a player is available. Contexts without a player should use 0 here.
builder.withLuck(player.getLuck());

最后从构建器创建 LootParams,用它抽取战利品表:

// Specify a loot context param set here if you want.
LootParams params = builder.create(LootContextParamSets.EMPTY);
// Get the loot table.
LootTable table = level.getServer().reloadableRegistries().getLootTable(location);
// Actually roll the loot table.
List<ItemStack> list = table.getRandomItems(params);
// Use this instead if you are rolling the loot table for container contents, e.g. loot chests.
// This method takes care of properly splitting the loot items across the container.
List<ItemStack> containerList = table.fill(container, params, someSeed);

重要警告 LootTable 还公开了 #getRandomItemsRaw 方法。与各种 #getRandomItems 变体不同,#getRandomItemsRaw 不会应用全局战利品修改器。只有明确了解其影响时才应使用此方法。

数据生成

注册 LootTableProvider,并在构造函数中提供 LootTableSubProvider 列表,即可生成战利品表数据:

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

    event.createProvider((output, lookupProvider) -> new LootTableProvider(
        output,
        // A set of required table resource locations. These are later verified to be present.
        // It is generally not recommended for mods to validate existence,
        // therefore we pass in an empty set.
        Set.of(),
        // A list of sub provider entries. See below for what values to use here.
        List.of(...),
        // The registry access
        lookupProvider
    ));
}

LootTableSubProvider

实际生成工作由 LootTableSubProvider 完成。首先实现 LootTableSubProvider 并重写 #generate:

public class MyLootTableSubProvider implements LootTableSubProvider {
    // The parameter is provided by the lambda (see below). It can be stored and used to lookup other registry entries.
    public MyLootTableSubProvider(HolderLookup.Provider lookupProvider) {
        // Store the lookupProvider in a field
    }

    @Override
    public void generate(BiConsumer<ResourceKey<LootTable>, LootTable.Builder> consumer) {
        // LootTable.lootTable() returns a loot table builder we can add loot tables to.
        consumer.accept(
                ResourceKey.create(
                    Registries.LOOT_TABLE,
                    Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "example_loot_table")
                ),
                LootTable.lootTable()
                // Add a loot table-level loot function. This example uses a number provider (see below).
                .apply(SetItemCountFunction.setCount(ConstantValue.exactly(5)))
                // Add a loot pool.
                .withPool(LootPool.lootPool()
                        // Add a loot pool-level function, similar to above.
                        .apply(...)
                        // Add a loot pool-level condition. This example only rolls the pool if it is raining.
                        .when(WeatherCheck.weather().setRaining(true))
                        // Set the amount of rolls and bonus rolls, respectively.
                        // Both of these methods utilize a number provider.
                        .setRolls(UniformGenerator.between(5, 9))
                        .setBonusRolls(ConstantValue.exactly(1))
                        // Add a loot entry. This example returns an item loot entry. See below for more loot entries.
                        .add(LootItem.lootTableItem(Items.DIRT))
                )
        );
    }
}

有了子提供器后,将它加入战利品提供器的构造函数:

new LootTableProvider(output, Set.of(), List.of(
        new SubProviderEntry(
                // A reference to the sub provider's constructor.
                // This is a Function<HolderLookup.Provider, ? extends LootTableSubProvider>.
                MyLootTableSubProvider::new,
                // An associated loot context set. If you're unsure what to use, use empty.
                LootContextParamSets.EMPTY
        ),
        // other sub providers here (if applicable)
    ), lookupProvider
);

BlockLootSubProvider

BlockLootSubProvider 是抽象辅助类,包含许多生成常见方块战利品表的辅助方法,例如单个物品掉落(#createSingleItemTable)、掉落表所属方块自身(#dropSelf)、仅精准采集掉落(#createSilkTouchOnlyTable)、台阶类方块掉落(#createSlabItemTable)等。不过,为模组配置 BlockLootSubProvider 需要更多样板代码:

public class MyBlockLootSubProvider extends BlockLootSubProvider {
    // The constructor can be private if this class is an inner class of your loot table provider.
    // The parameter is provided by the lambda in the LootTableProvider's constructor.
    public MyBlockLootSubProvider(HolderLookup.Provider lookupProvider) {
        // The first parameter is a set of blocks we are creating loot tables for. Instead of hardcoding,
        // we use our block registry and just pass an empty set here.
        // The second parameter is the feature flag set, this will be the default flags
        // unless you are adding custom flags (which is beyond the scope of this article).
        super(Set.of(), FeatureFlags.DEFAULT_FLAGS, lookupProvider);
    }

    // The contents of this Iterable are used for validation.
    // We return an Iterable over our block registry's values here.
    @Override
    protected Iterable<Block> getKnownBlocks() {
        // The contents of our DeferredRegister.
        return MyRegistries.BLOCK_REGISTRY.getEntries()
                .stream()
                // Cast to Block here, otherwise it will be a ? extends Block and Java will complain.
                .map(e -> (Block) e.value())
                .toList();
    }

    // Actually add our loot tables.
    @Override
    protected void generate() {
        // Equivalent to calling add(MyBlocks.EXAMPLE_BLOCK.get(), createSingleItemTable(MyBlocks.EXAMPLE_BLOCK.get()));
        this.dropSelf(MyBlocks.EXAMPLE_BLOCK.get());
        // Add a table with a silk touch only loot table.
        this.add(MyBlocks.EXAMPLE_SILK_TOUCHABLE_BLOCK.get(),
                this.createSilkTouchOnlyTable(MyBlocks.EXAMPLE_SILK_TOUCHABLE_BLOCK.get()));
        // other loot table additions here
    }
}

然后与其他子提供器一样,将它加入战利品表提供器的构造函数:

new LootTableProvider(output, Set.of(), List.of(new SubProviderEntry(
        MyBlockLootTableSubProvider::new,
        LootContextParamSets.BLOCK // it makes sense to use BLOCK here
    )), lookupProvider
);

EntityLootSubProvider

类似 BlockLootSubProvider,EntityLootSubProvider 为实体战利品表生成提供许多辅助方法。也同样需要提供该提供器所知实体的 Stream<EntityType<?>>(取代前面的 Iterable<Block>)。总体实现与 BlockLootSubProvider 很相似,只需将方块相关内容替换为实体类型:

public class MyEntityLootSubProvider extends EntityLootSubProvider {
    public MyEntityLootSubProvider(HolderLookup.Provider lookupProvider) {
        // Unlike with blocks, we do not provide a set of known entity types. Vanilla instead uses custom checks here.
        super(FeatureFlags.DEFAULT_FLAGS, lookupProvider);
    }

    // This class uses a Stream instead of an Iterable, so we need to adjust this slightly.
    @Override
    protected Stream<EntityType<?>> getKnownEntityTypes() {
        return MyRegistries.ENTITY_TYPES.getEntries()
                .stream()
                .map(e -> (EntityType<?>) e.value());
    }

    @Override
    protected void generate() {
        this.add(MyEntities.EXAMPLE_ENTITY.get(), LootTable.lootTable());
        // other loot table additions here
    }
}

然后同样将子提供器加入战利品表提供器的构造函数:

new LootTableProvider(output, Set.of(), List.of(new SubProviderEntry(
        MyEntityLootTableSubProvider::new,
        LootContextParamSets.ENTITY
    )), lookupProvider
);

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