标签
简单来说,标签是由同一种类型的已注册对象组成的列表。它们从数据文件加载,可用于检查对象是否属于某个集合。例如,合成木棍时可以使用任意组合的木板(带有 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 模组加载器统一使用这些标签。少数与 NeoForge 系统紧密关联的标签例外,例如许多伤害类型 标签。
标签文件的覆盖通常采用追加而非替换的方式。这意味着,如果两个数据包指定了 ID 相同的标签文件,两个文件的内容将会合并(除非另有指定)。这与大多数其他数据文件不同,后者会替换所有已有值。
标签文件格式
标签文件使用以下语法:
{
// The values of the tag.
"values": [
// A value object. Must specify the id of the object to add, and whether it is required.
// If the entry is required, but the object is not present, the tag will not load. The "required" field
// is technically optional, but when removed, the entry is equivalent to the shorthand below.
{
"id": "examplemod:example_ingot",
"required": false
}
// Shorthand for {"id": "minecraft:gold_ingot", "required": true}, i.e. a required entry.
"minecraft:gold_ingot",
// A tag object. Distinguished from regular entries by the leading #. In this case, all planks
// will be considered entries of the tag. Like normal entries, this can also have the "id"/"required" format.
// Warning: Circular tag dependencies will lead to a datapack not being loaded!
"#minecraft:planks"
],
// Whether to remove all pre-existing entries before adding your own (true) or just add your own (false).
// This should generally be false, the option to set this to true is primarily aimed at pack developers.
"replace": false,
// A finer-grained way to remove entries from the tag again, if present. Optional, NeoForge-added.
// Entry syntax is the same as in the "values" array.
"remove": [
"minecraft:iron_ingot"
]
}
查找和命名标签
查找已有标签时,通常建议按以下步骤进行:
- 查看 Minecraft 的标签,确认是否已有所需标签。Minecraft 的标签可以在
BlockTags、ItemTags、EntityTypeTags等类中找到。 - 如果没有,再查看 NeoForge 的标签。它们位于
Tags.Blocks、Tags.Items、Tags.EntityTypes等类中。 - 如果仍然没有,就可以认为 Minecraft 或 NeoForge 尚未定义这个标签,需要自行创建。
创建标签时,应考虑以下问题:
- 它是否用于调整自己模组的行为?如果是,应使用模组自己的命名空间。(例如“我的某种对象可以在这些方块上生成”一类的标签通常如此。)
- 其他模组是否也可能使用该标签?如果是,应使用
c命名空间。(例如新的金属或宝石标签通常如此。) - 其他情况下,使用模组自己的命名空间。
标签本身的命名也有一些约定:
- 使用复数形式,例如
minecraft:planks、c:ingots。 - 同一类型的多个对象使用文件夹组织,并为每个文件夹提供一个总标签。例如
c:ingots/iron、c:ingots/gold,以及包含这两者的c:ingots。(注意:这是 NeoForge 的约定,Minecraft 的大多数标签不遵循此约定。)
使用标签
要在代码中引用标签,必须使用注册表键 和标识符 创建 TagKey<T>,其中 T 是标签的类型(Block、Item、EntityType<?> 等):
public static final TagKey<Block> MY_TAG = TagKey.create(
// The registry key. The type of the registry must match the generic type of the tag.
Registries.BLOCK,
// The location of the tag. This example will put our tag at data/examplemod/tags/blocks/example_tag.json.
Identifier.fromNamespaceAndPath("examplemod", "example_tag")
);
警告 由于
TagKey是 record,它的构造方法是公开的。不过,不应直接使用构造方法,否则可能在查询标签条目等操作中导致各种问题。
然后便可以使用标签执行各种操作。先从最直观的操作开始:检查对象是否在标签中。以下示例使用方块标签,但所有类型的标签功能都完全相同(除非另有说明):
// Check whether dirt is in our tag.
// Assume access to Level level
boolean isInTag = level.registryAccess().lookupOrThrow(BuiltInRegistries.BLOCK).getOrThrow(MY_TAG).stream().anyMatch(holder -> holder.is(Items.DIRT));
这样的语句相当冗长,频繁使用时尤其如此。因此,标签系统最常见的两个使用者 BlockState 和 ItemStack 都定义了 #is 辅助方法,用法如下:
// Check whether the blockState's block is in our tag.
boolean isInBlockTag = blockState.is(MY_TAG);
// Check whether the itemStack's item is in our tag. Assumes the existence of MY_ITEM_TAG as a TagKey<Item>.
boolean isInItemTag = itemStack.is(MY_ITEM_TAG);
如果需要,也可以获取标签条目集合并对其进行流操作:
// Assume access to Level level
Stream<Holder<Block>> blocksInTag = level.registryAccess().lookupOrThrow(BuiltInRegistries.BLOCK).getOrThrow(MY_TAG).stream();
引导期间的静态注册表标签
有时需要在注册过程中访问 HolderSet。在数据组件上下文 中,初始化器会提供 HolderLookup.Provider,供解析引用:
Item.Properties props = new Item.Properties().delayedComponent(
// The component to initialize
DataComponents.DAMAGE_RESISTANT,
// The initializer function, typically provides at least the registry lookup
registries -> new DamageResistant(
// Get the HolderSet from the TagKey
registries.getOrThrow(DamageTypeTags.IS_FIRE)
)
);
在数据组件上下文之外,仅对静态注册表,可以通过 BuiltInRegistries#acquireBootstrapRegistrationLookup 获取所需的 HolderGetter:
// Assume access to Level level
HolderSet<Block> blockTag = BuiltInRegistries.acquireBootstrapRegistrationLookup(BuiltInRegistries.BLOCK).getOrThrow(MY_TAG);
数据生成
与许多其他 JSON 文件一样,标签也可以通过数据生成 创建。每种标签都有自己的数据生成基类,例如方块标签和物品标签各有一个,因此也需要为每种标签分别创建类。这些类都继承 TagsProvider<T> 基类,其中 T 仍然是标签类型(Block、Item 等)。TagsProvider 进一步分为两大类:IntrinsicHolderTagsProvider<T> 通常用于静态注册表对象,允许直接将对象传入标签;KeyTagProvider 通常用于数据包注册表对象,允许传入对象的 ResourceKey。此外,还有针对由 Holder 包装的静态注册表对象的 HolderTagProvider<T>,不过原版仅将其用于药水标签。
下表列出了不同对象对应的标签提供器:
| 类型 | 标签提供器类 | 提供器类型 |
|---|---|---|
BannerPattern |
BannerPatternTagsProvider |
KeyTagProvider |
Biome |
BiomeTagsProvider |
KeyTagProvider |
Block |
BlockTagsProvider* |
IntrinsicHolderTagsProvider |
ConfiguredFeature |
FeatureTagsProvider |
KeyTagProvider |
DamageType |
DamageTypeTagsProvider |
KeyTagProvider |
Dialog |
DialogTagsProvider |
KeyTagProvider |
Enchantment |
EnchantmentTagsProvider |
KeyTagProvider |
EntityType |
EntityTypeTagsProvider |
IntrinsicHolderTagsProvider |
FlatLevelGeneratorPreset |
FlatLevelGeneratorPresetTagsProvider |
IntrinsicHolderTagsProvider |
Fluid |
FluidTagsProvider |
KeyTagProvider |
GameEvent |
GameEventTagsProvider |
KeyTagProvider |
Instrument |
InstrumentTagsProvider |
KeyTagProvider |
Item |
ItemTagsProvider* |
IntrinsicHolderTagsProvider |
PaintingVariant |
PaintingVariantTagsProvider |
KeyTagProvider |
PoiType |
PoiTypeTagsProvider |
KeyTagProvider |
Potion |
PotionTagsProvider |
HolderTagProvider |
Structure |
StructureTagsProvider |
KeyTagProvider |
Timeline |
TimelineTagsProvider |
KeyTagProvider |
VillagerTrade |
VillagerTradesTagsProvider |
KeyTagProvider |
WorldPreset |
WorldPresetTagsProvider |
KeyTagProvider |
* 这些提供器由 NeoForge 提供。
以生成方块标签(使用内在持有者)为例:
public class MyBlockTagsProvider extends BlockTagsProvider {
// Get parameters from one of the `GatherDataEvent`s.
public MyBlockTagsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider) {
super(output, lookupProvider, ExampleMod.MOD_ID);
}
// Add your tag entries here.
@Override
protected void addTags(HolderLookup.Provider lookupProvider) {
// Create a TagAppender of registry objects for our tag. This could also be e.g. a vanilla or NeoForge tag.
this.tag(MY_TAG)
// Add entries. This is a vararg parameter.
// Key tag providers must provide ResourceKeys here instead of the actual objects.
.add(Blocks.DIRT, Blocks.COBBLESTONE)
// Add optional entries that will be ignored if absent. This example uses Botania's Pure Daisy.
// This is not a vararg parameter.
.add(TagEntry.optionalElement(Identifier.fromNamespaceAndPath("botania", "pure_daisy")))
// Add a tag entry.
.addTag(BlockTags.PLANKS)
// Add multiple tag entries. This is a vararg parameter.
// Can cause unchecked warnings that can safely be suppressed.
.addTags(BlockTags.LOGS, BlockTags.WOODEN_SLABS)
// Add an optional tag entry that will be ignored if absent.
.addOptionalTag(ItemTags.create(Identifier.fromNamespaceAndPath("c", "ingots/tin")))
// Add multiple optional tag entries. This is a vararg parameter.
// Can cause unchecked warnings that can safely be suppressed.
.addOptionalTags(ItemTags.create(Identifier.fromNamespaceAndPath("c", "nuggets/tin")), ItemTags.create(Identifier.fromNamespaceAndPath("c", "storage_blocks/tin")))
// Set the replace property to true.
.replace()
// Set the replace property back to false.
.replace(false)
// Remove entries. This is a vararg parameter.
// Key tag providers must provide ResourceKeys here instead of the actual objects.
// Can cause unchecked warnings that can safely be suppressed.
.remove(Blocks.CRIMSON_SLAB, Blocks.WARPED_SLAB);
}
}
此示例会生成以下标签 JSON:
{
"values": [
"minecraft:dirt",
"minecraft:cobblestone",
{
"id": "botania:pure_daisy",
"required": false
},
"#minecraft:planks",
"#minecraft:logs",
"#minecraft:wooden_slabs",
{
"id": "c:ingots/tin",
"required": false
},
{
"id": "c:nuggets/tin",
"required": false
},
{
"id": "c:storage_blocks/tin",
"required": false
}
],
"remove": [
"minecraft:crimson_slab",
"minecraft:warped_slab"
]
}
与所有数据提供器一样,将每个标签提供器添加到 GatherDataEvent:
@SubscribeEvent // on the mod event bus
public static void gatherData(GatherDataEvent.Client event) {
// Call event.createDatapackRegistryObjects(...) first if adding datapack objects
event.createProvider(MyBlockTagsProvider::new);
}
自定义标签提供器
无论面向现有注册表 还是自定义注册表,创建自定义标签提供器只需继承 TagsProvider<T>,其中 T 是要为其生成标签的注册表对象类型。
public class MyRecipeTypeTagsProvider extends TagsProvider<RecipeType<?>> {
// Get parameters from the `GatherDataEvent`s.
public MyRecipeTypeTagsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider) {
// Second parameter is the registry key we are generating the tags for.
super(output, Registries.RECIPE_TYPE, lookupProvider, ExampleMod.MOD_ID);
}
@Override
protected void addTags(HolderLookup.Provider lookupProvider) { /*...*/ }
}
接下来,通过 getOrCreateRawBuilder 创建 TagBuilder,从而在提供器中生成标签。构建器包含根据 Identifier 添加或移除元素和标签的方法。此外,还可以通过 setReplace 指定 replace 属性:
public class MyRecipeTypeTagsProvider extends TagsProvider<RecipeType<?>> {
// ...
// Lets assume the following TagKey<RecipeType<?>> SMELTERS, CRAFTERS, SMITHERS
@Override
protected void addTags(HolderLookup.Provider lookupProvider) {
// Create a TagBuilder for `Identifier`s.
this.getOrCreateRawBuilder(MY_TAG)
// Add entries.
.addElement(Identifier.fromNamespaceAndPath("minecraft", "crafting"))
.addElement(Identifier.fromNamespaceAndPath("minecraft", "smelting"))
// Add optional entries that will be ignored if absent.
.addOptionalElement(Identifier.fromNamespaceAndPath("minecraft", "blasting"))
// Add a tag entry.
.addTag(SMELTERS.location())
// Add an optional tag entry that will be ignored if absent.
.addOptionalTag(CRAFTERS.location())
// Set the replace property to true.
.setReplace(true)
// Set the replace property back to false.
.setReplace(false)
// Remove entries.
.removeElement(Identifier.fromNamespaceAndPath("minecraft", "campfire_cooking"))
// Remove a tag entry.
.removeTag(SMITHERS.location());
}
}
目前,整个标签都由 Identifier 构建。不过,每次都指定原始标识符可能很繁琐,尤其是在已有 ResourceKey 或可直接使用对象时。这正是 TagAppender 的用途。TagAppender<E, T> 实质上是对 TagBuilder 的包装,它接受任意条目对象 E,并将其转换为针对注册表对象 T 的 TagBuilder 调用。只要能够将新对象类型转换为之前的条目对象 E,就可以通过 map 将 TagAppender 重新映射为任意对象。这基本上就是 KeyTagProvider 和 IntrinsicHolderTagsProvider 所做的事情:它们提供 tag 方法,创建 TagAppender,分别将 ResourceKey 或直接对象映射为 Identifier:
public class MyRecipeTypeTagsProvider extends TagsProvider<RecipeType<?>> {
// ...
// Let's assume we have the TagKey<RecipeType<?>>s SMELTERS, CRAFTERS, SMITHERS
@Override
protected void addTags(HolderLookup.Provider lookupProvider) {
// Create the TagAppender for `Identifier`s.
this.tag(MY_TAG)
// Replace property info
.replace()
// Handle any optional elements that may not be present
.addOptional(Identifier.fromNamespaceAndPath("examplemod", "example_type"))
// Can take in a TagKey
.addOptionalTag(CRAFTERS)
// Map to ResourceKey (KeyTagProvider)
.map((Function<ResourceKey<RecipeType<?>>, Identifier>) ResourceKey::location)
.add(BuiltInRegistries.RECIPE_TYPE.getResourceKey(RecipeType.CRAFTING).orElseThrow())
// Map to direct object (IntrinsicHolderTagsProvider)
.map((Function<RecipeType<?>, ResourceKey<RecipeType<?>>) type -> BuiltInRegistries.RECIPE_TYPE.getResourceKey(type).orElseThrow())
.add(RecipeType.SMELTING)
.addTag(SMELTERS)
.remove(RecipeType.CAMPFIRE_COOKING)
.remove(SMITHERS);
}
private TagAppender<Identifier, RecipeType<?>> tag(TagKey<RecipeType<?>> tag) {
// Create the builder
TagBuilder builder = this.getOrCreateRawBuilder(tag);
// Generate the appender (can use TagAppender#forBuilder) instead
return new TagAppender<Identifier, T>() {
@Override
public TagAppender<Identifier, T> add(Identifier element) {
builder.addElement(element);
return this;
}
@Override
public TagAppender<Identifier, T> addOptional(Identifier element) {
builder.addOptionalElement(element);
return this;
}
@Override
public TagAppender<Identifier, T> addTag(TagKey<T> tag) {
builder.addTag(tag.location());
return this;
}
@Override
public TagAppender<Identifier, T> addOptionalTag(TagKey<T> tag) {
builder.addOptionalTag(tag.location());
return this;
}
// For situations where you cannot access the current entry object
@Override
public TagAppender<Identifier, T> add(TagEntry entry) {
builder.add(entry);
return this;
}
@Override
public TagAppender<Identifier, T> replace(boolean value) {
builder.setReplace(value);
return this;
}
@Override
public TagAppender<Identifier, T> remove(Identifier element) {
builder.removeElement(element);
return this;
}
@Override
public TagAppender<ResourceKey<T>, T> remove(TagKey<T> tag) {
builder.removeTag(tag.location());
return this;
}
};
}
}
复制标签内容
NeoForge 提供了一种特殊的 IntrinsicHolderTagsProvider,名为 BlockTagCopyingItemTagProvider,用于让物品标签与对应方块标签的内容保持一致。此时不使用 TagAppender,而是调用 copy,传入要从中复制内容的方块标签以及目标物品标签。
public class ExampleBlockTagCopyingItemTagProvider extends BlockTagCopyingItemTagProvider {
public ExampleBlockTagCopyingItemTagProvider(
PackOutput output,
CompletableFuture<HolderLookup.Provider> lookupProvider,
CompletableFuture<TagLookup<Block>> blockTags // Obtained from BlockTagsProvider#contentsGetter
) {
super(output, lookupProvider, blockTags, ExampleMod.MOD_ID);
}
@Override
protected void addTags(HolderLookup.Provider lookupProvider) {
// Assuming types TagKey<Block> and TagKey<Item> for the two parameters
this.copy(EXAMPLE_BLOCK_TAG, EXAMPLE_ITEM_TAG);
// You can also add normal item tags here
}
}
与所有数据提供器一样,复制标签的提供器也必须添加到 GatherDataEvent:
@SubscribeEvent // on the mod event bus
public static void gatherData(GatherDataEvent.Client event) {
// Call event.createDatapackRegistryObjects(...) first if adding datapack objects
event.createBlockAndItemTags(MyBlockTagsProvider::new, ExampleBlockTagCopyingItemTagProvider::new);
}
来源: NeoForged/Documentation(26.1)。中文翻译依据提交
4a680cdd4d15。