掉落表(Loot Tables)
掉落表(Loot Tables)是一种数据文件,用于定义随机化的掉落物。一次掉落表的“投掷”操作会返回一个(可能为空的)物品堆栈(item stacks)列表。这个过程的输出依赖于(伪)随机性。掉落表文件位于 data/<mod_id>/loot_table/<name>.json 路径下。例如,泥土方块所用的掉落表 minecraft:blocks/dirt 存放在 data/minecraft/loot_table/blocks/dirt.json。
Minecraft 在游戏的多个环节使用掉落表,包括 方块 掉落、实体 掉落、箱子掉落、钓鱼掉落等。具体如何引用掉落表,取决于不同的上下文:
- 每个方块(block)默认都会关联一个掉落表,路径为
<block_namespace>:blocks/<block_name>。你可以通过在方块的Properties上调用#noLootTable来禁用这一行为,这样就不会生成掉落表,方块也不会掉落任何物品;这种做法主要用于类似空气或技术性方块。 - 每个实 体(entity),只要没有调用
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中找到。
一般来说,你只应为属于自己模组的内容创建掉落表。如果需要修改已有掉落表,应使用 全局掉落修改器(GLMs) 实现。
由于掉落表系统本身较为复杂,它由多个子系统组成,每个子系统都承担着不同的功能。
掉落条目(Loot Entry)
掉落条目(Loot Entry,或称掉落池条目),在代码中由抽象类 LootPoolEntryContainer 表示,是最基本的掉落元素。它可以指定一个或多个要掉落的物品。
原版(Vanilla)提供了总共 8 种不同类型的掉落条目。所有这些类型都继承自共同的 LootPoolEntryContainer 父类,并具备以下属性:
weight:权重值。默认为 1。用于控制某些物品比其他物品更常见的情况。例如,假设有两个战利品条目(loot entry),一个权重为 3,另一个权重为 1,那么第一个条目被选中的概率为 75%,第二个条目为 25%。quality:品质值。默认为 0。如果该值不为零,则会将此值与幸运值(在 战利品上下文 中设置)相乘,并在抽取战利品表时加到权重上。conditions:应用于此战利品条目的 战利品条件 列表。如果有一个条件未通过,则此条目会被视为不存在。functions:应用于该战利品条目输出结果的 战利品函数 列表。
战利品条目(loot entry)通常分为两类:单例(singletons,具有共同父类 LootPoolSingletonContainer)和复合(composites,具有共同父类 CompositeEntryBase),其中复合类型由多个单例组成。Minecraft 提供了以下几种单例类型:
minecraft:empty:一个空的战利品条目(loot entry),表示不会掉落任何物品。可以通过调用EmptyLootItem#emptyItem在代码中创建 。minecraft:item:单个战利品物品条目,抽取时会掉落指定的物品。可以通过调用LootItem#lootTableItem并传入目标物品,在代码中创建。- 设置堆叠数量、数据组件等操作可以通过战利品函数(loot functions)实现。
minecraft:tag:标签条目(tag entry),在抽取时会掉落指定标签下的所有物品。根据布尔属性expand的值有两种变体。如果expand为 true,会为标签中的每个物品生成一个独立条目,否则只使用一个条目来掉落所有物品。可以通过调用TagEntry#tagContents(对应expand=false)或TagEntry#expandTag(对应expand=true),并传入一个物品 标签键 参数来创建。- 例如,如果
expand为 true 且标签为#minecraft:planks,则会为每种木板类型生成一个条目(也就是原版 11 种木板各一个条目,加上每个模组木板各一个条目),每个条目都拥有指定的权重、品质和函数;而如果expand为 false,则只会生成一个条目,用于掉落所有木板。
- 例如,如果
minecraft:dynamic:引用动态掉落(dynamic drop)的战利品条目。动态掉落是一种无法提前在数据中指定、只能在代码中添加的战利品机制。一个动态掉落条目由一个 id 和一个Consumer<ItemStack>组成,后者用于实际添加物品。要添加动态掉落条目,需要用期望的 id 指定一个minecraft:dynamic条目,并在 战利品上下文 中添加对应的 consumer。可以通过DynamicLoot#dynamicEntry创建。minecraft:loot_table:一个会抽取另一个战利品表(loot table)的条目,将该战利品表 的结果作为单个条目加入。可通过 id 指定另一个战利品表,也可以整体内联(inline)一个表。在代码中可以通过调用NestedLootTable#lootTableReference并传入ResourceLocation参数,或调用NestedLootTable#inlineLootTable并传入LootTable对象参数(用于内联战利品表)来创建。
以下是 Minecraft 提供的复合类型(composite types):
minecraft:group:一个包含其他战利品条目(loot entry)列表的战利品条目,会按顺序依次执行。你可以通过调用EntryGroup#list方法,或者在另一个LootPoolSingletonContainer.Builder上调用#append方法并传入其他战利品条目构建器,在代码中创建它。minecraft:sequence:类似于minecraft:group,但只要某个子条目失败,整个战利品条目的执行就会停止,后面的所有条目都会被丢弃。你可以通过调用SequentialEntry#sequential方法,或者在另一个LootPoolSingletonContainer.Builder上调用#then方法并传入其他战利品条目构建器,在代码中创建它。minecraft:alternatives:有点像minecraft:sequence的反面,只要某个子条目成功,整个战利品条目的执行就会停止,后面的所有条目都会被丢弃。你可以通过调用AlternativesEntry#alternatives方法,或者在另一个LootPoolSingletonContainer.Builder上调用#otherwise方法并传入其他战利品条目构建器,在代码中创建它。
对于模组开发者(modder)来说,也可以定义自定义战利品条目类型。
战利品池(Loot Pool)
战利品池(loot pool)本质上是一个战利品条目(loot entry)的列表。一个战利品表(loot table)可以包含多个战利品池,每个战利品池的抽取(roll)都是相互独立的。
战利品池可以包含以下内容:
entries:战利品条目的列表。conditions:应用于该战利品池的战利品条件列表。如果有一个条件失败,该战利品池中的所有条目都不会被抽取。functions:应用于该战利品池所有战利品条目输出的战利品函数列表。rolls和bonus_rolls:两个数字提供器(number provider,详见下文),共同决定该战利品池会被抽取的次数。公式为 rolls + bonus_rolls * luck,其中 luck 值在战利品参数中设置。name:战利品池的名称。由 NeoForge 新增。这个名称可以被 GLMs 使用。如果未指定,则会使用战利品池的哈希值,前缀为custom#。
数字提供器(Number Provider)
数字提供器(number provider)是一种在数据包(datapack)环境下获取(伪)随机数的方式。它们主要被战利品表(loot table)使用,也会在其他场景中用到,例如世界生成(worldgen)。原版(vanilla)提供了以下六种数字提供器:
minecraft:constant:一个常量浮点值,在需要时会四舍五入为整数。通过ConstantValue#exactly创建。minecraft:uniform:均匀分布的随机整数或浮点值,可以设置最小值和最大值。所有介于最小值和最大值之间的数值出现的概率相同。通过UniformGenerator#between创建。minecraft:binomial:二项分布的随机整数值,可以设置 n 和 p 参数。关于这些参数的含义,详见 二项分布。通过BinomialDistributionGenerator#binomial创建。minecraft:score:给定一个实体目标、分数名称以及(可选的)缩放值,获取该实体目标在记分板上的指定分数,并将其与缩放值相乘(如果有提供)。通过ScoreboardValue#fromScoreboard创建。minecraft:storage:从命令存储中的指定 nbt 路径获取的数值。通过new StorageValue创建。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:lookup:接受一个List<Float>和一个备用的LevelBasedValue。会在列表中查找对应等级的数值(等级 1 对应列表第一个元素,等级 2 对应第二个元素,依此类推),如果某个等级没有对应值,则使用备用值。通过LevelBasedValue#lookup创建。
- 仅为一个常量值,无需指定类型。通过
模组开发者还可以根据需要注册 自定义数值提供器 和 自定义基于等级的数值。
掉落参数(Loot Parameters)
掉落参数,在内部称为 ContextKey<T>,是在结算掉落表时提供的参数,其中 T 表示参数的类型,例如 BlockPos 或 Entity。这些参数可以被 掉落条件 和 掉落函数 使用。例如,minecraft:killed_by_player 掉落条件会检查是否存在 minecraft:player 参数。
Minecraft 提供了以下掉落参数:
minecraft:origin:与战利品表(loot table)关联的位置,例如战利品箱的位置。可通过LootContextParams.ORIGIN访问。minecraft:tool:与战利品表关联的物品堆(item stack),例如用于破坏方块的物品。这里的物品不一定是工具。可通过LootContextParams.TOOL访问。minecraft:enchantment_level:附魔等级(enchantment level),用于附魔相关逻辑。可通过LootContextParams.ENCHANTMENT_LEVEL访问。minecraft:enchantment_active:用于判断所用物品是否有附魔,例如用于精准采集(silk touch)检查。可通过LootContextParams.ENCHANTMENT_ACTIVE访问。minecraft:block_state:与战利品表关联的方块状态(block state),例如被破坏方块的状态。可通过LootContextParams.BLOCK_STATE访问。minecraft:block_entity:与战利品表关联的方块实体(block entity),例如被破坏方块对应的方块实体。常用于潜影盒(shulker box)等保存其物品栏到掉落物。可通过LootContextParams.BLOCK_ENTITY访问。minecraft:explosion_radius:当前上下文中的爆炸半径(explosion radius)。主要用于对掉落物应用爆炸衰减。可通过LootContextParams.EXPLOSION_RADIUS访问。minecraft:this_entity:与战利品表关联的实体(entity),通常是被击杀的实体。可通过LootContextParams.THIS_ENTITY访问。minecraft:damage_source:与战利品表关联的 伤害来源,通常是击杀实体的伤害来源。可通过LootContextParams.DAMAGE_SOURCE访问。minecraft:attacking_entity:与战利品表关联的攻击实体(attacking entity),通常是击杀目标的实体。可通过LootContextParams.ATTACKING_ENTITY访问。minecraft:direct_attacking_entity:与战利品表关 联的直接攻击实体(direct attacking entity)。例如,如果攻击实体是骷髅(skeleton),那么直接攻击实体就是箭矢。可通过LootContextParams.DIRECT_ATTACKING_ENTITY访问。minecraft:last_damage_player:与战利品表关联的玩家(player),通常是最后一次攻击被击杀实体的玩家,即使击杀是间接完成的(例如:玩家碰了一下实体,实体随后被尖刺杀死)。常用于只允许玩家击杀时掉落的物品。可通过LootContextParams.LAST_DAMAGE_PLAYER访问。
自定义战利品参数可以通过调用 new ContextKey<T> 并指定所需的 id 来创建。由于这些参数只是资源位置(resource location)的包装器,因此无需注册。
实体目标(Entity Targets)
实体目标是战利品条件(loot condition)和函数(function)中使用的一种类型,在代码中由 LootContext.EntityTarget 枚举(enum)表示。它们用于在条件或函数上下文中指定要查询的实体战利品参数。有效值包括:
"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"参数。
例如,minecraft:entity_properties 战利品条件(loot condition)可以接受一个实体目标(entity target),从而允许你(作为战利品表作者)检查所有四个战利品参数(loot parameter),如果你需要这样做的话。
战利品参数集(Loot Parameter Sets)
战利品参数集(loot parameter sets),也被称为战利品表类型(loot table types),在代码中称为 ContextKeySet,它是一组必需和可选的战利品参数。尽管名字叫做“Set”,但它们实际上并不是 Set(甚至不是 Collection)。它们实际上是对两个 Set<ContextKey<?>> 的封装:一个保存必需参数(#required),另一个保存可选参数(#allowed)。这些参数集用于校验战利品参数的使用者只会使用那些可以预期可用的参数,并在投掷(roll)战利品表时验证所有必需参数都已提供。除此之外,参数集还用于进度(advancement)和附魔(enchantment)逻辑中。
原版(Vanilla)提供了以下战利品参数集(粗体为必需参数,斜体为可选参数;代码中的名称是 LootContextParamSets 常量):
| ID | 代码内名称(In-code name) | 指定掉落参数(Specified Loot Parameters) | 用途(Usage) |
|---|---|---|---|
minecraft:empty | EMPTY | n/a(无) | 作为兜底用途。 |
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: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:hit_block | HIT_BLOCK | minecraft:origin,minecraft:enchantment_level,minecraft:block_state,minecraft:this_entity | 通道附魔(channeling enchantment)。 |
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: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 | 进度奖励。 |
掉落上下文(Loot Context)
掉落上下文(loot context)是一个包含当前情景信息的对象,用于执行掉落表(loot table)的抽取操作。该对象包含以下信息:
- 当前掉落表所在的
ServerLevel。可通过#getLevel获取。 - 用于抽取掉落表的
RandomSource。可通过#getRandom获取。 - 掉落参数(loot parameters)。可以使用
#hasParameter检查参数是否存在,并通过#getParameter获取单个参数。 - 幸运值(luck value),用于计算额外抽取次数和品质值。通常由实体的幸运属性赋值。可通过
#getLuck获取。 - 动态掉落(dynamic drops)消费者。更多信息见上文。通过
#addDynamicDrops设置。没有对应的 getter 方法。
掉落表(Loot Table)
结合前面介绍的所有要素,最终我们得到了掉 落表(loot table)。掉落表的 JSON 文件可以包含以下字段:
pools:掉落池(loot pool)列表。neoforge:conditions:数据加载条件 列表。注意:这些是数据加载条件,而不是 掉落条件!functions:掉落函数 列表,将应用于该掉落表所有掉落条目的输出。type:掉落参数集(loot parameter set),用于校验掉落参数的正确使用。可选;如果缺省,将跳过校验。random_sequence:该掉落表的随机序列(random sequence),以资源位置(resource location)形式指定。随机序列由Level提供,用于在相同条件下确保掉落表抽取结果一致。通常使用掉落表自身的位置。
一个示例掉落表格式如下:
{
"type": "chest", // 掉落参数集
"neoforge:conditions": [
// 数据加载条件
],
"functions": [
// 整个表适用的掉落函数
],
"pools": [ // 掉落池列表
{
"rolls": 1, // 掉落表抽取次数,这里设为 5 会从池中抽取 5 次
"bonus_rolls": 0.5, // 额外抽取次数
"name": "my_pool",
"conditions": [
// 整个池适用的掉落条件
],
"functions": [
// 整个池适用的掉落函数
],
"entries": [ // 掉落条目列表
{
"type": "minecraft:item", // 掉落条目类型
"name": "minecraft:dirt", // 类型相关属性,例如物品名称
"weight": 3, // 条目权重
"quality": 1, // 条目品质
"conditions": [
// 条目适用的掉落条件
],
"functions": [
// 条目适用的掉落函数
]
}
]
}
]
}
抽取掉落表(Rolling a Loot Table)
要抽取一个掉落表,我们需要两个要素:掉落表本身,以及掉落上下文(loot context)。
首先,让我们获取战利品表(loot table)本身。你可以通过 level.getServer().reloadableRegistries().getLootTable(lootTableId) 来获取一个战利品表。由于战利品数据只在服务器端可用,这段逻辑必须在 逻辑服务器 上运行,而不是逻辑客户端。
Minecraft 内置的战利品表 ID 可以在 BuiltInLootTables 类中找到。方块的战利品表可以通过 BlockBehaviour#getLootTable 获取,实体的战利品表可以通过 EntityType#getDefaultLootTable 或 Entity#getLootTable 获取。
现在我们已经有了一个战利品表,接下来需要构建参数集。首先,创建一个 LootParams.Builder 实例:
// 请确保当前处于服务器环境,否则类型转换会失败。
LootParams.Builder builder = new LootParams.Builder((ServerLevel) level);
然后我们可以像下面这样添加战利品上下文参数:
// 根据需要使用任意上下文参数和值。原版参数可以在 LootContextParams 中找到。
builder.withParameter(LootContextParams.ORIGIN, position);
// 这个变体可以接受 null 作为值,此时会移除该参数已存在的值。
builder.withOptionalParameter(LootContextParams.ORIGIN, null);
// 添加一个动态掉落。
builder.withDynamicDrop(ResourceLocation.fromNamespaceAndPath("examplemod", "example_dynamic_drop"), stackAcceptor -> {
// 在这里编写你的逻辑
});
// 设置幸运值(luck)。假设当前有玩家对象。如果没有玩家,应该在这里使用 0。
builder.withLuck(player.getLuck());
最后,我们可以用 builder 创建出 LootParams,并用它来抽取战利品表内容:
// 如果需要,可以在这里指定一个战利品上下文参数集。
LootParams params = builder.create(LootContextParamSets.EMPTY);
// 获取战利品表。
LootTable table = level.getServer().reloadableRegistries().getLootTable(location);
// 实际抽取战利品。
List<ItemStack> list = table.getRandomItems(params);
// 如果你是为容器(如战利品箱子)抽取战利品,请使用这个方法。
// 这个方法会自动将战利品正确分配到容器格子中。
List<ItemStack> containerList = table.fill(container, params, someSeed);
LootTable 还额外提供了一个名为 #getRandomItemsRaw 的方法。与各种 #getRandomItems 变体不同,#getRandomItemsRaw 方法不会应用 全局战利品修饰符。只有在你明确知道自己在做什么时才使用这个方法。
数据生成(Datagen)
你可以通过注册一个 LootTableProvider 并在构造函数中提供一个 LootTableSubProvider 列表来实现 数据生成 战利品表:
@SubscribeEvent
public static void onGatherData(GatherDataEvent.Client event) {
// 如果要添加数据包对象,首先调用 event.createDatapackRegistryObjects(...)
event.createProvider((output, lookupProvider) -> new LootTableProvider(
output,
// 一组必须存在的表资源位置。之后会验证这些表是否存在。
// 通常不建议模组自行验证存在性,所以这里传入一个空集合。
Set.of(),
// 子提供者(sub provider)条目的列表。下面会介绍这里应使用的值。
List.of(...),
// 注册表访问器
lookupProvider
));
}
LootTableSubProvider
LootTableSubProvider 是实际生成战利品表(loot table)的地方。要开始使用,我们需要实现 LootTableSubProvider 接口,并重写 #generate 方法:
public class MyLootTableSubProvider implements LootTableSubProvider {
// 该参数由 lambda 提供(见下文)。可以保存下来,用于查找其他注册表(registry)条目。
public MyLootTableSubProvider(HolderLookup.Provider lookupProvider) {
// 将 lookupProvider 存储到字段中
}
@Override
public void generate(BiConsumer<ResourceLocation, LootTable.Builder> consumer) {
// LootTable.lootTable() 返回一个 loot table 构建器,可以用来添加战利品表。
consumer.accept(ResourceLocation.fromNamespaceAndPath(ExampleMod.MOD_ID, "example_loot_table"), LootTable.lootTable()
// 添加一个战利品表级别的函数(loot function)。本例使用了数字提供器(number provider),详见下文。
.apply(SetItemCountFunction.setCount(ConstantValue.exactly(5)))
// 添加一个战利品池(loot pool)。
.withPool(LootPool.lootPool()
// 添加一个战利品池级别的函数,和上面类似。
.apply(...)
// 添加一个战利品池级别的条件。此例仅在下雨时才会抽取该池。
.when(WeatherCheck.weather().setRaining(true))
// 分别设置抽取次数和额外抽取次数。
// 这两个方法都使用数字提供器(number provider)。
.setRolls(UniformGenerator.between(5, 9))
.setBonusRolls(ConstantValue.exactly(1))
// 添加一个战利品条目。本例返回一个物品条目。更多类型见下文。
.add(LootItem.lootTableItem(Items.DIRT))
)
);
}
}
有了自己的战利品表子提供者(loot table sub provider)后,需要像下面这样把它添加到 loot provider 的构造函数中:
new LootTableProvider(output, Set.of(), List.of(
new SubProviderEntry(
// 指向子提供者构造函数的引用。
// 这是一个 Function<HolderLookup.Provider, ? extends LootTableSubProvider>。
MyLootTableSubProvider::new,
// 关联的掉落上下文参数集。如果不确定用什么,使用 empty 即可。
LootContextParamSets.EMPTY
),
// 这里可以添加其他子提供者(如果需要)
), lookupProvider
);
BlockLootSubProvider
BlockLootSubProvider 是一个抽象辅助类,内置了许多用于创建常见方块掉落表(loot table)的工具方法,例如单个物品掉落(#createSingleItemTable)、掉落自身(#dropSelf)、仅能通过精准采集(Silk Touch)获得的掉落(#createSilkTouchOnlyTable)、类似台阶(slab)方块的掉落(#createSlabItemTable)等。遗憾的是,将 BlockLootSubProvider 应用于模组开发时,需要写更多样板代码(boilerplate):
public class MyBlockLootSubProvider extends BlockLootSubProvider {
// 如果本类作为你的掉落表(loot table)提供者的内部类,构造函数可以是 private。
// 该参数由 LootTableProvider 构造函数中的 lambda 提供。
public MyBlockLootSubProvider(HolderLookup.Provider lookupProvider) {
// 第一个参数是我们要为其生成掉落表的方块集合。这里不硬编码,直接用我们的方块注册表(registry),传空集合即可。
// 第二个参数是功能标志(feature flag)集合,除非你要添加自定义标志(超出本文范围),否则用默认即可。
super(Set.of(), FeatureFlags.DEFAULT_FLAGS, lookupProvider);
}
// 该 Iterable 的内容用于校验。
// 这里返回我们方块注册表中的所有值作为 Iterable。
@Override
protected Iterable<Block> getKnownBlocks() {
// 取自我们的 DeferredRegister 的内容。
return MyRegistries.BLOCK_REGISTRY.getEntries()
.stream()
// 这里需要强制转换为 Block,否则类型会是 ? extends Block,Java 会报错。
.map(e -> (Block) e.value())
.toList();
}
// 实际添加我们的掉落表。
@Override
protected void generate() {
// 等价于 调用 add(MyBlocks.EXAMPLE_BLOCK.get(), createSingleItemTable(MyBlocks.EXAMPLE_BLOCK.get()));
this.dropSelf(MyBlocks.EXAMPLE_BLOCK.get());
// 添加一个仅能通过精准采集获得掉落的掉落表。
this.add(MyBlocks.EXAMPLE_SILK_TOUCHABLE_BLOCK.get(),
this.createSilkTouchOnlyTable(MyBlocks.EXAMPLE_SILK_TOUCHABLE_BLOCK.get()));
// 这里可以添加其他掉落表
}
}
然后,我们像添加其他子提供者一样,把这个子提供者加入到掉落表(loot table)提供者的构造函数中:
new LootTableProvider(output, Set.of(), List.of(new SubProviderEntry(
MyBlockLootTableSubProvider::new,
LootContextParamSets.BLOCK // 这里使用 BLOCK 是合理的
)), lookupProvider
);
EntityLootSubProvider
与 BlockLootSubProvider 类似,EntityLootSubProvider 为实体掉落表(entity loot table)的生成提供了许多辅助方法。同样地,我们需要提供一个包含所有已知实体类型(EntityType<?>)的 Stream<EntityType<?>>,而不是之前的 Iterable<Block>。总体上,我们的实现方式与 BlockLootSubProvider 非常相似,只不过所有关于方块(block)的地方都换成了实体类型(entity type):
public class MyEntityLootSubProvider extends EntityLootSubProvider {
public MyEntityLootSubProvider(HolderLookup.Provider lookupProvider) {
// 与方块不同,这里不需要提供已知实体类型的集合。原版(vanilla)会在此处使用自定义检查。
super(FeatureFlags.DEFAULT_FLAGS, lookupProvider);
}
// 该类使用 Stream 而不是 Iterable,因此我们需要做一些调整。
@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());
// 在这里添加其他掉落表
}
}
同样,我们需要将自定义的 sub provider 添加到掉落表(loot table)provider 的构造函数中:
new LootTableProvider(output, Set.of(), List.of(new SubProviderEntry(
MyEntityLootTableSubProvider::new,
LootContextParamSets.ENTITY
)), lookupProvider
);