标签(Tags)
标签(tag)简单来说,就是一组同类型已注册对象的列表。它们通过数据文件加载, 并可用于判断对象是否属于某个集合。例如,合成木棍时可以接受任意种类的木板(即被打上 minecraft:planks 标签的物品)。标签通常通过在前面加上 # 与“普通”对象区分(比如 #minecraft:planks,而不是 minecraft:oak_planks)。
任何 注册表 都可以拥有标签文件——虽然方块和物品是最常见的用例,但其他注册表(如流体、实体类型或伤害类型)也常常用到标签。如果有需要,你也可以自定义标签。
对于 Minecraft 注册表,标签文件位于 data/<tag_namespace>/tags/<registry_path>/<tag_path>.json;对于非 Minecraft 注册表,则位于 data/<tag_namespace>/tags/<registry_namespace>/<registry_path>/<tag_path>.json。比如,要修改 minecraft:planks 物品标签,你应将标签文件放在 data/minecraft/tags/item/planks.json。
与大多数 NeoForge 数据文件不同,NeoForge 新增的标签通常不会使用 neoforge 命名空间,而是采用 c 命名空间(如 c:ingots/gold)。这是因为标签在 NeoForge 与 Fabric mod 加载器之间是统一的,许多同时在多个加载器上开发的模组作者提出了这样的需求。
当然,也有一些例外,比如某些与 NeoForge 系统紧密相关的标签。这包括许多 伤害类型 的标签等。
覆盖标签文件时,通常是“增量式”的,而不是完全替换。这意味着,如果两个数据包指定了相同 id 的标签文件,那么这两个文件的内容会被合并(除非另有说明)。这种行为使标签与大多数其他数据文件区分开来——后者通常会直接替换所有已有值。
标签文件格式(Tag File Format)
标签文件的语法如下:
{
// 标签的值。
"values": [
// 一个值对象。必须指定要添加对象的 id,以及该对象是否为必需项。
// 如果该条目为必需项,但对象不存在,则该标签不会加载。"required" 字段
// 技术上是可选的,但如果省略,该条目等价于下面的简写方式。
{
"id": "examplemod:example_ingot",
"required": false
}
// {"id": "minecraft:gold_ingot", "required": true} 的简写方式,即必需条目。
"minecraft:gold_ingot",
// 一个标签对象。通过前导 # 区分于普通条目。在本例中,所有 planks
// 都会被视为该标签的条目。与普通条目一样,也可以使用 "id"/"required" 格式。
// 警告:循环的标签依赖会导致数据包无法加载!
"#minecraft:planks"
],
// 在添加你自己的条目前,是否移除所有已有条目(true),或只是添加你的条目(false)。
// 通常应为 false,允许设置为 true 主要是为数据包开发者准备的选项。
"replace": false,
// 更细粒度地移除标签中的条目(如果存在)。可选,NeoForge 新增功能。
// 条目语法与 "values" 数组相同。
"remove": [
"minecraft:iron_ingot"
]
}
查找与命名标签(Finding and Naming Tags)
当你需要查找一个已有标签时,通常建议按照以下步骤操作:
- 首先查看 Minecraft 的标签,看看你要找的标签是否已存在。Minecraft 的标签可以在
BlockTags、ItemTags、EntityTypeTags等处找到。 - 如果没有,再查看 NeoForge 的标签,看你要找 的标签是否存在。NeoForge 的标签可以在
Tags.Blocks、Tags.Items、Tags.EntityTypes等处找到。 - 如果都没有,可以认为该标签在 Minecraft 或 NeoForge 中未被定义,因此你需要自己创建一个标签。
在创建自定义标签时,你应当思考以下问题:
- 这个标签会影响我的模组(mod)行为吗?如果是,标签应放在你的模组命名空间下。(例如:用于“某物能否在此方块上生成”这类标签。)
- 其他模组也可能想使用这个标签吗?如果是,标签应放在
c命名空间下。(例如:用于新金属或宝石类型。) - 否则,使用你的模组命名空间。
标签的命名本身也有一些约定:
- 使用复数形式。例如:
minecraft:planks、c:ingots。 - 对于同类多对象,使用文件夹区分,并为每个文件夹提供一个总标签。例如:
c:ingots/iron、c:ingots/gold,以及包含两者的c:ingots。(注意:这是 NeoForge 的约定,Minecraft 大多数标签并不遵循此约定。)
使用标签(Using Tags)
在代码中引用标签时,你必须创建一个 TagKey<T>,其中 T 是标签的类型(如 Block、Item、EntityType<?> 等),需要使用 注册表键 和 资源位置:
public static final TagKey<Block> MY_TAG = TagKey.create(
// 注册表键。注册表的类型必须与标签的泛型类型一致。
Registries.BLOCK,
// 标签的位置。本例会把我们的标签放在 data/examplemod/tags/blocks/example_tag.json。
ResourceLocation.fromNamespaceAndPath("examplemod", "example_tag")
);
由于 TagKey 是一个 record,它的构造函数是 public 的。但不建议直接使用该构造函数,否则在查找标签条目等操作时可能会导致各种问题。
接下来,我们可以使用我们的标签(tag)来进行各种操作。让我们从最常见的需求开始:判断某个对象是否在该标签内。以下示例假设我们在操作方块标签(block tag),但除非特别说明,所有类型的标签功能都是完全一样的:
// 检查 dirt 是否在我们的标签中。
// 假设能访问到 Level level
boolean isInTag = level.registryAccess().lookupOrThrow(BuiltInRegistries.BLOCK).getOrThrow(MY_TAG).stream().anyMatch(holder -> holder.is(Items.DIRT));
由于这种写法非常冗长,尤其是频繁使用时,BlockState 和 ItemStack —— 这两种最常用的标签系统用户 —— 都定义了一个 #is 辅助方法,可以这样使用:
// 检查 blockState 的方块是否在我们的标签中。
boolean isInBlockTag = blockState.is(MY_TAG);
// 检查 itemStack 的物品是否在我们的标签中。假设已存在 MY_ITEM_TAG 作为 TagKey<Item>。
boolean isInItemTag = itemStack.is(MY_ITEM_TAG);
如果需要,我们也可以获取标签条目的集合以便进行流式处理,如下所示:
// 假设能访问到 Level level
Stream<Holder<Block>> blocksInTag = level.registryAccess().lookupOrThrow(BuiltInRegistries.BLOCK).getOrThrow(MY_TAG).stream();
引导阶段静态注册表的标签(Tags for Static Registries During Bootstrap)
有时,你需要在注册表处理过程中访问一个 HolderSet。在这种情况下,仅针对静态注册表(static registries),你可以通过 BuiltInRegistries#acquireBootstrapRegistrationLookup 获取所需的 HolderGetter:
// 假设能访问到 Level level
HolderSet<Block> blockTag = BuiltInRegistries.acquireBootstrapRegistrationLookup(BuiltInRegistries.BLOCK).getOrThrow(MY_TAG);
数据生成(Datagen)
和许多其他 JSON 文件一样,标签(tag)也可 以通过 数据生成 自动生成。每种类型的标签都有自己的数据生成基类 —— 方块标签一个类,物品标签一个类,等等 —— 因此,我们也需要为每种标签类型各写一个类。这些类都继承自 TagsProvider<T> 基类,其中 T 依然是标签的类型(如 Block、Item 等)。下表展示了针对不同对象的标签提供器(tag provider)列表:
| 类型(Type) | 标签提供者类(Tag Provider Class) |
|---|---|
BannerPattern | BannerPatternTagsProvider |
Biome | BiomeTagsProvider |
Block | BlockTagsProvider |
CatVariant | CatVariantTagsProvider |
DamageType | DamageTypeTagsProvider |
Enchantment | EnchantmentTagsProvider |
EntityType | EntityTypeTagsProvider |
FlatLevelGeneratorPreset | FlatLevelGeneratorPresetTagsProvider |
Fluid | FluidTagsProvider |
GameEvent | GameEventTagsProvider |
Instrument | InstrumentTagsProvider |
Item | ItemTagsProvider |
PaintingVariant | PaintingVariantTagsProvider |
PoiType | PoiTypeTagsProvider |
Structure | StructureTagsProvider |
WorldPreset | WorldPresetTagsProvider |
需要特别注意的是,IntrinsicHolderTagsProvider<T> 类是 TagsProvider<T> 的子类,并且是 BlockTagsProvider、ItemTagsProvider、FluidTagsProvider、EntityTypeTagsProvider 和 GameEventTagsProvider 这些类的通用父类。这些类(下文为方便起见统称为 intrinsic providers)在数据生成方面拥有一些额外的功能,稍后会详细介绍。
为了举例说明,假设我们现在要生成方块标签(block tags)。其他类型的标签提供者的用法与此类似,只是对应的标签类型不同。
public class MyBlockTagsProvider extends BlockTagsProvider {
// 从某个 `GatherDataEvent` 获取参数。
public MyBlockTagsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider) {
super(output, lookupProvider, ExampleMod.MOD_ID);
}
// 在这里添加你的标签(tag)条目。
@Override
protected void addTags(HolderLookup.Provider lookupProvider) {
// 为我们的标签创建一个标签构建器。这里同样可以是原版(vanilla)或 NeoForge 的标签。
this.tag(MY_TAG)
// 添加条目。这是一个可变参数(vararg)。
// 非内在(non-intrinsic)提供者必须在这里提供 ResourceKey,而不是实际对象。
.add(Blocks.DIRT, Blocks.COBBLESTONE)
// 添加可选条目,如果不存在则会被忽略。此示例使用了 Botania 的 Pure Daisy。
// 与 #add 不同,这里不是可变参数。
.addOptional(ResourceLocation.fromNamespaceAndPath("botania", "pure_daisy"))
// 添加一个标签条目。
.addTag(BlockTags.PLANKS)
// 添加多个标签条目。这是一个可变参数。
// 可能会导致未检查的警告,可以安全地忽略。
.addTags(BlockTags.LOGS, BlockTags.WOODEN_SLABS)
// 添加一个可选的标签条目,如果不存在则会被忽略。
.addOptionalTag(ResourceLocation.fromNamespaceAndPath("c", "ingots/tin"))
// 添加多个可选的标签条目。这是一个可变参数。
// 可能会导致未检查的警告,可以安全地忽略。
.addOptionalTags(ResourceLocation.fromNamespaceAndPath("c", "nuggets/tin"), ResourceLocation.fromNamespaceAndPath("c", "storage_blocks/tin"))
// 将 replace 属性设置为 true。
.replace()
// 将 replace 属性重新设置为 false。
.replace(false)
// 移除条目。这是一个可变参数。可接受资源位置(resource location)、资源键(resource key)、
// 标签键(tag key),或(仅限内在提供者)直接值。
// 可能会导致未检查的警告,可以安全地忽略。
.remove(ResourceLocation.fromNamespaceAndPath("minecraft", "crimson_slab"), ResourceLocation.fromNamespaceAndPath("minecraft", "warped_slab"));
}
}
该示例将生成如下标签(tag)JSON:
像所有的数据提供器(data provider)一样,需要将每个标签提供器(tag provider)添加到 GatherDataEvent 事件中:
@SubscribeEvent
public static void gatherData(GatherDataEvent.Client event) {
// 如果要添加数据包对象,需优先调用 event.createDatapackRegistryObjects(...)
event.createProvider(MyBlockTagsProvider::new);
}
ItemTagsProvider 额外提供了一个名为 #copy 的辅助方法。这个方法用于处理物品标签(item tag)与方块标签(block tag)常常需要保持同步的场景:
// 在 ItemTagsProvider 的 #addTags 方法中,假设两个参数的类型分别为 TagKey<Block> 和 TagKey<Item>。
this.copy(EXAMPLE_BLOCK_TAG, EXAMPLE_ITEM_TAG);
自定义标签提供器(Custom Tag Providers)
如果你需要为自定义的 注册表 创建标签提供器,或者为原版或 NeoForge 某些默认没有标签提供器的注册表创建,也可以像下面这样自定义标签提供器(以配方类型标签 为例):
public class MyRecipeTypeTagsProvider extends TagsProvider<RecipeType<?>> {
// 从 `GatherDataEvent` 事件中获取参数。
public MyRecipeTypeTagsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider) {
// 第二个参数是我们要为其生成标签的注册表键(registry key)。
super(output, Registries.RECIPE_TYPE, lookupProvider, ExampleMod.MOD_ID);
}
@Override
protected void addTags(HolderLookup.Provider lookupProvider) { /*...*/ }
}
如果需要且适用,也可以继承 IntrinsicHolderTagsProvider<T> 而不是 TagsProvider<T>,这样可以直接传入对象本身,而不只是它们的资源键(resource key)。这还需要一个函数参数,用于根据对象返回其资源键。以下以属性标签(attribute tag)为例:
public class MyAttributeTagsProvider extends IntrinsicHolderTagsProvider<Attribute> {
// 从 `GatherDataEvent` 获取参数。
public MyAttributeTagsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider) {
super(output,
Registries.ATTRIBUTE,
lookupProvider,
// 一个函数,接收一个 Attribute,返回一个 ResourceKey<Attribute>。
attribute -> BuiltInRegistries.ATTRIBUTE.getResourceKey(attribute).orElseThrow(),
ExampleMod.MOD_ID
);
}
// 现在可以直接在这里使用 Attribute,而不仅仅是它们的资源键。
@Override
protected void addTags(HolderLookup.Provider lookupProvider) { /*...*/ }
}
TagsProvider 还暴露了 #getOrCreateRawBuilder 方法,它会返回一个 TagBuilder。TagBuilder 允许你向标签中添加原始的 ResourceLocation,在某些场景下这会很有用。TagsProvider.TagAppender<T> 类(即 TagsProvider#tag 方法返回的类型)其实只是 TagBuilder 的一个包装器。