Add tag aliases ()

* Add tag aliases

* Document and rename tag alias internals

* Make the tag alias directory singular to match Mojang's recent style

* Add a note about tag aliases to client tag documentation

* Support missing tags in alias groups

* Support tag aliases for dynamic and reloadable registries

* TagAliasGroup: Document naming conventions for c tag alias groups

* Add tag alias test mod

* Fix inline return checkstyle

* Add test for tag alias data generation

* Fix checkstyle (again)

* Add tag translations to tag API testmod

* Uncomment accidentally commented out code

* SimpleRegistryMixin: Improve a log message

* TagAliasTest: Improve assertion messages

* Fix tag aliases for dynamic registries not applying on /reload

* Clean up log message once again

* Address review feedback

* Make missing interfaces throw CCEs

* Add README

* Move TagAliasGroup into the impl package

(cherry picked from commit a730659c14)
This commit is contained in:
Juuz 2024-12-12 16:16:55 +00:00 committed by modmuss50
parent b1caf1e9d6
commit 20ea1e23ee
50 changed files with 1256 additions and 2 deletions
fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/api/tag/client/v1
fabric-data-generation-api-v1
build.gradle
src
main
java/net/fabricmc/fabric
api/datagen/v1/provider
impl/datagen
mixin/datagen
resources
testmod
generated/data
fabric-data-gen-api-v1-testmod/fabric/tag_aliases/block
other_namespace/fabric/tag_aliases/block
java/net/fabricmc/fabric/test/datagen
fabric-tag-api-v1
gradle.propertiessettings.gradle

View file

@ -37,6 +37,9 @@ import net.fabricmc.fabric.impl.tag.client.ClientTagsImpl;
* <p>Client Tags resolve that issue by lazily reading the tag json files within the mods on the side of the caller,
* directly, allowing for mods to query tags such as {@link net.fabricmc.fabric.api.tag.convention.v2.ConventionalBlockTags}
* even when connected to a vanilla server.
*
* <p>Note that locally read client tags don't currently support Fabric's tag aliases. The aliasing system is only
* implemented on servers.
*/
public final class ClientTags {
private ClientTags() {

View file

@ -7,6 +7,7 @@ moduleDependencies(project, [
'fabric-networking-api-v1',
'fabric-resource-conditions-api-v1',
'fabric-item-group-api-v1',
'fabric-tag-api-v1',
'fabric-recipe-api-v1'
])

View file

@ -16,6 +16,11 @@
package net.fabricmc.fabric.api.datagen.v1.provider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@ -70,6 +75,9 @@ import net.fabricmc.fabric.impl.datagen.ForcedTagEntry;
* @see EntityTypeTagProvider
*/
public abstract class FabricTagProvider<T> extends TagProvider<T> {
private final FabricDataOutput output;
private final Map<Identifier, AliasGroupBuilder> aliasGroupBuilders = new HashMap<>();
/**
* Constructs a new {@link FabricTagProvider} with the default computed path.
*
@ -80,6 +88,7 @@ public abstract class FabricTagProvider<T> extends TagProvider<T> {
*/
public FabricTagProvider(FabricDataOutput output, RegistryKey<? extends Registry<T>> registryKey, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture) {
super(output, registryKey, registriesFuture);
this.output = output;
}
/**
@ -116,6 +125,34 @@ public abstract class FabricTagProvider<T> extends TagProvider<T> {
return new FabricTagBuilder(super.getOrCreateTagBuilder(tag));
}
/**
* Gets an {@link AliasGroupBuilder} with the given ID.
*
* @param groupId the group ID
* @return the alias group builder
*/
protected AliasGroupBuilder aliasGroup(Identifier groupId) {
return aliasGroupBuilders.computeIfAbsent(groupId, key -> new AliasGroupBuilder());
}
/**
* Gets an {@link AliasGroupBuilder} with the given ID.
*
* @param group the group name
* @return the alias group builder
*/
protected AliasGroupBuilder aliasGroup(String group) {
Identifier groupId = Identifier.of(output.getModId(), group);
return aliasGroupBuilders.computeIfAbsent(groupId, key -> new AliasGroupBuilder());
}
/**
* {@return a read-only map of alias group builders by the alias group ID}.
*/
public Map<Identifier, AliasGroupBuilder> getAliasGroupBuilders() {
return Collections.unmodifiableMap(aliasGroupBuilders);
}
/**
* Extend this class to create {@link Block} tags in the "/blocks" tag directory.
*/
@ -396,4 +433,52 @@ public abstract class FabricTagProvider<T> extends TagProvider<T> {
return this;
}
}
/**
* A builder for tag alias groups.
*/
public final class AliasGroupBuilder {
private final List<TagKey<T>> tags = new ArrayList<>();
private AliasGroupBuilder() {
}
/**
* {@return a read-only list of the tags in this alias group}.
*/
public List<TagKey<T>> getTags() {
return Collections.unmodifiableList(tags);
}
public AliasGroupBuilder add(TagKey<T> tag) {
if (tag.registryRef() != registryRef) {
throw new IllegalArgumentException("Tag " + tag + " isn't from the registry " + registryRef);
}
this.tags.add(tag);
return this;
}
@SafeVarargs
public final AliasGroupBuilder add(TagKey<T>... tags) {
for (TagKey<T> tag : tags) {
add(tag);
}
return this;
}
public AliasGroupBuilder add(Identifier tag) {
this.tags.add(TagKey.of(registryRef, tag));
return this;
}
public AliasGroupBuilder add(Identifier... tags) {
for (Identifier tag : tags) {
this.tags.add(TagKey.of(registryRef, tag));
}
return this;
}
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.datagen;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import net.minecraft.data.DataOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.DataWriter;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.tag.TagAliasGroup;
public final class TagAliasGenerator {
public static String getDirectory(RegistryKey<? extends Registry<?>> registryKey) {
String directory = "fabric/tag_aliases/";
Identifier registryId = registryKey.getValue();
if (!Identifier.DEFAULT_NAMESPACE.equals(registryId.getNamespace())) {
directory += registryId.getNamespace() + '/';
}
return directory + registryId.getPath();
}
public static <T> CompletableFuture<?> writeTagAlias(DataWriter writer, DataOutput.PathResolver pathResolver, RegistryKey<? extends Registry<T>> registryRef, Identifier groupId, List<TagKey<T>> tags) {
Path path = pathResolver.resolveJson(groupId);
return DataProvider.writeCodecToPath(writer, TagAliasGroup.codec(registryRef), new TagAliasGroup<>(tags), path);
}
}

View file

@ -16,18 +16,48 @@
package net.fabricmc.fabric.mixin.datagen;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.data.DataOutput;
import net.minecraft.data.DataWriter;
import net.minecraft.data.tag.TagProvider;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.tag.TagBuilder;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider;
import net.fabricmc.fabric.impl.datagen.FabricTagBuilder;
import net.fabricmc.fabric.impl.datagen.TagAliasGenerator;
@Mixin(TagProvider.class)
public class TagProviderMixin {
public class TagProviderMixin<T> {
@Shadow
@Final
protected RegistryKey<? extends Registry<T>> registryRef;
@Unique
private DataOutput.PathResolver tagAliasPathResolver;
@Inject(method = "<init>(Lnet/minecraft/data/DataOutput;Lnet/minecraft/registry/RegistryKey;Ljava/util/concurrent/CompletableFuture;Ljava/util/concurrent/CompletableFuture;)V", at = @At("RETURN"))
private void initPathResolver(DataOutput output, RegistryKey<? extends Registry<T>> registryRef, CompletableFuture<?> registriesFuture, CompletableFuture<?> parentTagLookupFuture, CallbackInfo info) {
tagAliasPathResolver = output.getResolver(DataOutput.OutputType.DATA_PACK, TagAliasGenerator.getDirectory(registryRef));
}
@ModifyArg(method = "method_27046", at = @At(value = "INVOKE", target = "Lnet/minecraft/registry/tag/TagFile;<init>(Ljava/util/List;Z)V"), index = 1)
private boolean addReplaced(boolean replaced, @Local TagBuilder tagBuilder) {
if (tagBuilder instanceof FabricTagBuilder fabricTagBuilder) {
@ -36,4 +66,23 @@ public class TagProviderMixin {
return replaced;
}
@SuppressWarnings("unchecked")
@WrapOperation(method = "method_49659", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;allOf([Ljava/util/concurrent/CompletableFuture;)Ljava/util/concurrent/CompletableFuture;"))
private CompletableFuture<Void> addTagAliasGroupBuilders(CompletableFuture<?>[] futures, Operation<CompletableFuture<Void>> original, @Local(argsOnly = true) DataWriter writer) {
if ((Object) this instanceof FabricTagProvider<?>) {
// Note: no pattern matching instanceof so that we can cast directly to FabricTagProvider<T> instead of a wildcard
Map<Identifier, FabricTagProvider<T>.AliasGroupBuilder> builders = ((FabricTagProvider<T>) (Object) this).getAliasGroupBuilders();
CompletableFuture<?>[] newFutures = Arrays.copyOf(futures, futures.length + builders.size());
int index = futures.length;
for (Map.Entry<Identifier, FabricTagProvider<T>.AliasGroupBuilder> entry : builders.entrySet()) {
newFutures[index++] = TagAliasGenerator.writeTagAlias(writer, tagAliasPathResolver, registryRef, entry.getKey(), entry.getValue().getTags());
}
return original.call((Object) newFutures);
} else {
return original.call((Object) futures);
}
}
}

View file

@ -16,7 +16,8 @@
"FabricMC"
],
"depends": {
"fabricloader": ">=0.16.9"
"fabricloader": ">=0.16.9",
"fabric-tag-api-v1": "*"
},
"description": "Allows for automatic data generation.",
"mixins": [

View file

@ -0,0 +1,6 @@
{
"tags": [
"minecraft:flowers",
"minecraft:flower_pots"
]
}

View file

@ -0,0 +1,6 @@
{
"tags": [
"minecraft:flowers",
"minecraft:flower_pots"
]
}

View file

@ -306,6 +306,11 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
getOrCreateTagBuilder(BlockTags.FIRE).setReplace(true).add(SIMPLE_BLOCK);
getOrCreateTagBuilder(BlockTags.DIRT).add(SIMPLE_BLOCK);
getOrCreateTagBuilder(BlockTags.ACACIA_LOGS).forceAddTag(BlockTags.ANIMALS_SPAWNABLE_ON);
aliasGroup("flowers")
.add(BlockTags.FLOWERS, BlockTags.FLOWER_POTS);
aliasGroup(Identifier.of("other_namespace", "flowers"))
.add(BlockTags.FLOWERS, BlockTags.FLOWER_POTS);
}
}

View file

@ -0,0 +1,17 @@
# Fabric Tag API (v1)
This module contains APIs for working with data pack tags.
## Tag aliases
*Tag alias groups* merge tags that refer to the same set of registry entries.
The contained tags will be linked together and get the combined set of entries
of all the aliased tags in a group.
Tag alias groups can be defined in data packs in the `data/<mod namespace>/fabric/tag_alias/<registry>`
directory. `<registry>` is the path of the registry's ID, prefixed with `<registry's namespace>/` if it's
not `minecraft`.
The JSON format of tag alias groups is an object with a `tags` list containing plain tag IDs.
See the module javadoc for more information about tag aliases.

View file

@ -0,0 +1,14 @@
version = getSubprojectVersion(project)
loom {
accessWidenerPath = file('src/main/resources/fabric-tag-api-v1.accesswidener')
}
moduleDependencies(project, [
'fabric-api-base',
'fabric-resource-loader-v0'
])
testDependencies(project, [
':fabric-lifecycle-events-v1',
])

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* The Fabric Tag API for working with {@linkplain net.minecraft.registry.tag.TagKey tags}.
*
* <h1>Aliasing tags</h1>
* <dfn>Tag alias groups</dfn> are lists of tags that refer to the same set of registry entries.
* The contained tags will be linked together and get the combined set of entries
* of all the aliased tags in a group.
*
* <p>Tag alias groups can be defined in data packs in the {@code data/<mod namespace>/fabric/tag_alias/<registry>}
* directory. {@code <registry>} is the path of the registry's ID, prefixed with {@code <registry's namespace>/} if it's
* not {@value net.minecraft.util.Identifier#DEFAULT_NAMESPACE}. For example, an alias group for block tags would be placed
* in {@code data/<mod namespace>/fabric/tag_alias/block/}.
*
* <p>The JSON format of tag alias groups is an object with a {@code tags} list. The list contains plain tag IDs with
* no {@code #} prefix.
*
* <p>If multiple tag alias groups include a tag, the groups will be combined and each tag will be an alias
* for the same contents.
*
* <h2>Tag aliases in the {@code c} namespace</h2>
*
* <p>For the names of shared {@code c} tag alias groups, it's important that you use a short and descriptive name.
* A good way to do this is reusing the name of a contained {@code c} tag that follows the naming conventions.
* For example, if the tag alias group contains the tags {@code c:flowers/tall} and {@code minecraft:tall_flowers},
* the tag alias file should be named {@code flowers/tall.json}, like the contained {@code c} tag.
*
* <p>Tag alias groups in the {@code c} namespace are primarily intended for merging a {@code c} tag
* with an equivalent vanilla tag with no potentially unwanted gameplay behavior. If a vanilla tag affects
* game mechanics (such as the water tag affecting swimming), don't alias it as a {@code c} tag.
*
* <p>If you want to have the contents of a {@code c} tag in your own tag, prefer including the {@code c} tag
* in your tag file directly. That way, data packs can modify your tag separately. Tag aliases make their contained
* tags almost fully indistinguishable since they get the exact same content, and you have to override the alias group
* in a higher-priority data pack to unlink them.
*/
package net.fabricmc.fabric.api.tag.v1;

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.tag;
public interface SimpleRegistryExtension {
void fabric_applyPendingTagAliases();
void fabric_refreshTags();
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.tag;
import java.util.Map;
import java.util.Set;
import net.minecraft.registry.tag.TagKey;
/**
* Implemented on {@code RegistryWrapper.Impl} instances used during data loading
* to give access to the underlying registry.
*/
public interface TagAliasEnabledRegistryWrapper {
void fabric_loadTagAliases(Map<TagKey<?>, Set<TagKey<?>>> aliasGroups);
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.tag;
import java.util.List;
import com.mojang.serialization.Codec;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.tag.TagKey;
/**
* A wrapper record for tag alias groups.
*
* @param tags the tags in the group, must be from the same registry
* @param <T> the type of registry entries in the tags
*/
public record TagAliasGroup<T>(List<TagKey<T>> tags) {
/**
* Creates a codec for tag alias groups in the specified registry.
*
* @param registryKey the key of the registry where the tags are from
* @param <T> the entry type
* @return the codec
*/
public static <T> Codec<TagAliasGroup<T>> codec(RegistryKey<? extends Registry<T>> registryKey) {
return TagKey.unprefixedCodec(registryKey)
.listOf()
.fieldOf("tags")
.xmap(TagAliasGroup::new, TagAliasGroup::tags)
.codec();
}
}

View file

@ -0,0 +1,177 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.tag;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.Lifecycle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.registry.CombinedDynamicRegistries;
import net.minecraft.registry.DynamicRegistryManager;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.resource.Resource;
import net.minecraft.resource.ResourceFinder;
import net.minecraft.resource.ResourceManager;
import net.minecraft.resource.SinglePreparationResourceReloader;
import net.minecraft.util.Identifier;
import net.minecraft.util.profiler.Profiler;
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
public final class TagAliasLoader extends SinglePreparationResourceReloader<Map<RegistryKey<? extends Registry<?>>, List<TagAliasLoader.Data>>> implements IdentifiableResourceReloadListener {
public static final Identifier ID = Identifier.of("fabric-tag-api-v1", "tag_alias_groups");
private static final Logger LOGGER = LoggerFactory.getLogger("fabric-tag-api-v1");
private final RegistryWrapper.WrapperLookup registries;
public TagAliasLoader(RegistryWrapper.WrapperLookup registries) {
this.registries = registries;
}
@Override
public Identifier getFabricId() {
return ID;
}
@SuppressWarnings("unchecked")
@Override
protected Map<RegistryKey<? extends Registry<?>>, List<TagAliasLoader.Data>> prepare(ResourceManager manager, Profiler profiler) {
Map<RegistryKey<? extends Registry<?>>, List<TagAliasLoader.Data>> dataByRegistry = new HashMap<>();
Iterator<RegistryKey<? extends Registry<?>>> registryIterator = registries.streamAllRegistryKeys().iterator();
while (registryIterator.hasNext()) {
RegistryKey<? extends Registry<?>> registryKey = registryIterator.next();
ResourceFinder resourceFinder = ResourceFinder.json(getDirectory(registryKey));
for (Map.Entry<Identifier, Resource> entry : resourceFinder.findResources(manager).entrySet()) {
Identifier resourcePath = entry.getKey();
Identifier groupId = resourceFinder.toResourceId(resourcePath);
try (Reader reader = entry.getValue().getReader()) {
JsonElement json = JsonParser.parseReader(reader);
Codec<TagAliasGroup<Object>> codec = TagAliasGroup.codec((RegistryKey<? extends Registry<Object>>) registryKey);
switch (codec.parse(JsonOps.INSTANCE, json)) {
case DataResult.Success(TagAliasGroup<Object> group, Lifecycle unused) -> {
var data = new Data(groupId, group);
dataByRegistry.computeIfAbsent(registryKey, key -> new ArrayList<>()).add(data);
}
case DataResult.Error<?> error -> {
LOGGER.error("[Fabric] Couldn't parse tag alias group file '{}' from '{}': {}", groupId, resourcePath, error.message());
}
}
} catch (IOException | JsonParseException e) {
LOGGER.error("[Fabric] Couldn't parse tag alias group file '{}' from '{}'", groupId, resourcePath, e);
}
}
}
return dataByRegistry;
}
private static String getDirectory(RegistryKey<? extends Registry<?>> registryKey) {
String directory = "fabric/tag_alias/";
Identifier registryId = registryKey.getValue();
if (!Identifier.DEFAULT_NAMESPACE.equals(registryId.getNamespace())) {
directory += registryId.getNamespace() + '/';
}
return directory + registryId.getPath();
}
@Override
protected void apply(Map<RegistryKey<? extends Registry<?>>, List<TagAliasLoader.Data>> prepared, ResourceManager manager, Profiler profiler) {
for (Map.Entry<RegistryKey<? extends Registry<?>>, List<Data>> entry : prepared.entrySet()) {
Map<TagKey<?>, Set<TagKey<?>>> groupsByTag = new HashMap<>();
for (Data data : entry.getValue()) {
Set<TagKey<?>> group = new HashSet<>(data.group.tags());
for (TagKey<?> tag : data.group.tags()) {
Set<TagKey<?>> oldGroup = groupsByTag.get(tag);
// If there's an old group...
if (oldGroup != null) {
// ...merge all of its tags into the current group...
group.addAll(oldGroup);
// ...and replace the recorded group of each tag in the old group with the new group.
for (TagKey<?> other : oldGroup) {
groupsByTag.put(other, group);
}
}
groupsByTag.put(tag, group);
}
}
// Remove any groups of one tag, we don't need to apply them.
groupsByTag.values().removeIf(tags -> tags.size() == 1);
RegistryWrapper.Impl<?> wrapper = registries.getOrThrow(entry.getKey());
if (wrapper instanceof TagAliasEnabledRegistryWrapper aliasWrapper) {
aliasWrapper.fabric_loadTagAliases(groupsByTag);
} else {
throw new ClassCastException("[Fabric] Couldn't apply tag aliases to registry wrapper %s (%s) since it doesn't implement TagAliasEnabledRegistryWrapper"
.formatted(wrapper, entry.getKey().getValue()));
}
}
}
public static <T> void applyToDynamicRegistries(CombinedDynamicRegistries<T> registries, T phase) {
Iterator<DynamicRegistryManager.Entry<?>> registryEntries = registries.get(phase).streamAllRegistries().iterator();
while (registryEntries.hasNext()) {
Registry<?> registry = registryEntries.next().value();
if (registry instanceof SimpleRegistryExtension extension) {
extension.fabric_applyPendingTagAliases();
// This is not needed in the static registry code path as the tag aliases are applied
// before the tags are refreshed. Dynamic registry loading (including tags) takes place earlier
// than the rest of a data reload, so we need to refresh the tags manually.
extension.fabric_refreshTags();
} else {
throw new ClassCastException("[Fabric] Couldn't apply pending tag aliases to registry %s (%s) since it doesn't implement SimpleRegistryExtension"
.formatted(registry, registry.getClass().getName()));
}
}
}
protected record Data(Identifier groupId, TagAliasGroup<?> group) {
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.tag;
import net.minecraft.resource.ResourceType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
public final class TagInit implements ModInitializer {
@Override
public void onInitialize() {
ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(TagAliasLoader.ID, TagAliasLoader::new);
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.tag;
import java.util.List;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.registry.CombinedDynamicRegistries;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.registry.ServerDynamicRegistryType;
import net.minecraft.resource.featuretoggle.FeatureSet;
import net.minecraft.server.DataPackContents;
import net.minecraft.server.command.CommandManager;
import net.fabricmc.fabric.impl.tag.TagAliasLoader;
/**
* Applies pending tag aliases to dynamic (including reloadable) registries.
* The priority is 999 because it must apply the injection to applyPendingTagLoads before the tag loaded lifecycle event.
*/
@Mixin(value = DataPackContents.class, priority = 999)
abstract class DataPackContentsMixin {
@Unique
private CombinedDynamicRegistries<ServerDynamicRegistryType> dynamicRegistriesByType;
@Inject(method = "<init>", at = @At("RETURN"))
private void storeDynamicRegistries(CombinedDynamicRegistries<ServerDynamicRegistryType> dynamicRegistries, RegistryWrapper.WrapperLookup registries, FeatureSet enabledFeatures, CommandManager.RegistrationEnvironment environment, List<?> pendingTagLoads, int functionPermissionLevel, CallbackInfo info) {
dynamicRegistriesByType = dynamicRegistries;
}
@Inject(method = "applyPendingTagLoads", at = @At("RETURN"))
private void applyDynamicTagAliases(CallbackInfo info) {
// Note: when using /reload, dynamic registry tag reloading goes through the same system that is also used
// for static registries. Luckily, it doesn't break anything to run the code below even in that case,
// since the map of pending tag alias groups is cleared after they're applied in the first round.
// This code also needs to run after the vanilla code so the pending tag reloads don't override
// the alias groups for dynamic registries.
TagAliasLoader.applyToDynamicRegistries(dynamicRegistriesByType, ServerDynamicRegistryType.WORLDGEN);
TagAliasLoader.applyToDynamicRegistries(dynamicRegistriesByType, ServerDynamicRegistryType.RELOADABLE);
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.tag;
import java.util.Map;
import java.util.Set;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.registry.tag.TagKey;
import net.fabricmc.fabric.impl.tag.TagAliasEnabledRegistryWrapper;
/**
* Adds tag alias support to {@code SimpleRegistry$2}, which is the wrapper used
* for (TODO: only?) static registries during world creation and world/data reloading.
*/
@Mixin(targets = "net.minecraft.registry.SimpleRegistry$2")
abstract class SimpleRegistry2Mixin<T> implements TagAliasEnabledRegistryWrapper {
// returns SimpleRegistry.this, which implements TagAliasEnabledRegistry
@Shadow
public abstract RegistryWrapper.Impl<T> getBase();
@Override
public void fabric_loadTagAliases(Map<TagKey<?>, Set<TagKey<?>>> aliasGroups) {
((TagAliasEnabledRegistryWrapper) getBase()).fabric_loadTagAliases(aliasGroups);
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.tag;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.registry.SimpleRegistry;
import net.fabricmc.fabric.impl.tag.SimpleRegistryExtension;
/**
* This is a mixin to the Registry.PendingTagLoad implementation in SimpleRegistry.
* It applies pending tag aliases to static registries when data packs are loaded
* and to dynamic registries when data packs are reloaded using the {@code /reload} command.
* (Tags run on their own data loading system separate from resource reloaders, so we need to inject them
* once the tag and resource reloads are done, which is here.)
*/
@Mixin(targets = "net.minecraft.registry.SimpleRegistry$3")
abstract class SimpleRegistry3Mixin {
@Shadow
@Final
SimpleRegistry<?> field_53689;
@Inject(method = "apply", at = @At(value = "INVOKE", target = "Lnet/minecraft/registry/SimpleRegistry;refreshTags()V"))
private void applyTagAliases(CallbackInfo info) {
((SimpleRegistryExtension) field_53689).fabric_applyPendingTagAliases();
}
}

View file

@ -0,0 +1,124 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.tag;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.SimpleRegistry;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.registry.entry.RegistryEntryList;
import net.minecraft.registry.tag.TagKey;
import net.fabricmc.fabric.impl.tag.SimpleRegistryExtension;
import net.fabricmc.fabric.impl.tag.TagAliasEnabledRegistryWrapper;
/**
* Adds tag alias support to {@code SimpleRegistry}, the primary registry implementation.
*
* <p>Additionally, the {@link TagAliasEnabledRegistryWrapper} implementation is for dynamic registry tag loading.
*/
@Mixin(SimpleRegistry.class)
abstract class SimpleRegistryMixin<T> implements SimpleRegistryExtension, TagAliasEnabledRegistryWrapper {
@Unique
private static final Logger LOGGER = LoggerFactory.getLogger("fabric-tag-api-v1");
@Unique
private Map<TagKey<?>, Set<TagKey<?>>> pendingTagAliasGroups;
@Shadow
@Final
private RegistryKey<? extends Registry<T>> key;
@Shadow
SimpleRegistry.TagLookup<T> tagLookup;
@Shadow
protected abstract RegistryEntryList.Named<T> createNamedEntryList(TagKey<T> tag);
@Shadow
abstract void refreshTags();
@Shadow
public abstract RegistryKey<? extends Registry<T>> getKey();
@Override
public void fabric_loadTagAliases(Map<TagKey<?>, Set<TagKey<?>>> aliasGroups) {
pendingTagAliasGroups = aliasGroups;
}
@SuppressWarnings("unchecked")
@Override
public void fabric_applyPendingTagAliases() {
if (pendingTagAliasGroups == null) return;
Set<Set<TagKey<?>>> uniqueAliasGroups = Sets.newIdentityHashSet();
uniqueAliasGroups.addAll(pendingTagAliasGroups.values());
for (Set<TagKey<?>> aliasGroup : uniqueAliasGroups) {
Set<RegistryEntry<T>> entries = Sets.newIdentityHashSet();
// Fetch all entries from each tag.
for (TagKey<?> tag : aliasGroup) {
RegistryEntryList.Named<T> entryList = tagLookup.getOptional((TagKey<T>) tag).orElse(null);
if (entryList != null) {
entries.addAll(entryList.entries);
} else {
LOGGER.info("[Fabric] Creating a new empty tag {} for unknown tag used in a tag alias group in {}", tag.id(), tag.registryRef().getValue());
Map<TagKey<T>, RegistryEntryList.Named<T>> tagMap = ((SimpleRegistryTagLookup2Accessor<T>) tagLookup).fabric_getTagMap();
if (!(tagMap instanceof HashMap<?, ?>)) {
// Unfreeze the backing map.
tagMap = new HashMap<>(tagMap);
((SimpleRegistryTagLookup2Accessor<T>) tagLookup).fabric_setTagMap(tagMap);
}
tagMap.put((TagKey<T>) tag, createNamedEntryList((TagKey<T>) tag));
}
}
List<RegistryEntry<T>> entriesAsList = List.copyOf(entries);
// Replace the old entry list contents with the merged list.
for (TagKey<?> tag : aliasGroup) {
RegistryEntryList.Named<T> entryList = tagLookup.getOptional((TagKey<T>) tag).orElseThrow();
entryList.entries = entriesAsList;
}
}
LOGGER.debug("[Fabric] Loaded {} tag alias groups for {}", uniqueAliasGroups.size(), key.getValue());
pendingTagAliasGroups = null;
}
@Override
public void fabric_refreshTags() {
refreshTags();
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.tag;
import java.util.Map;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.registry.entry.RegistryEntryList;
import net.minecraft.registry.tag.TagKey;
@Mixin(targets = "net.minecraft.registry.SimpleRegistry$TagLookup$2")
public interface SimpleRegistryTagLookup2Accessor<T> {
@Accessor("field_53694")
Map<TagKey<T>, RegistryEntryList.Named<T>> fabric_getTagMap();
@Accessor("field_53694")
@Mutable
void fabric_setTagMap(Map<TagKey<T>, RegistryEntryList.Named<T>> tagMap);
}

Binary file not shown.

After

(image error) Size: 1.5 KiB

View file

@ -0,0 +1,3 @@
accessWidener v2 named
accessible class net/minecraft/registry/SimpleRegistry$TagLookup
accessible field net/minecraft/registry/entry/RegistryEntryList$Named entries Ljava/util/List;

View file

@ -0,0 +1,15 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.tag",
"compatibilityLevel": "JAVA_21",
"mixins": [
"DataPackContentsMixin",
"SimpleRegistryMixin",
"SimpleRegistry2Mixin",
"SimpleRegistry3Mixin",
"SimpleRegistryTagLookup2Accessor"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,36 @@
{
"schemaVersion": 1,
"id": "fabric-tag-api-v1",
"name": "Fabric Tag API (v1)",
"version": "${version}",
"environment": "*",
"license": "Apache-2.0",
"icon": "assets/fabric-tag-api-v1/icon.png",
"contact": {
"homepage": "https://fabricmc.net",
"irc": "irc://irc.esper.net:6667/fabric",
"issues": "https://github.com/FabricMC/fabric/issues",
"sources": "https://github.com/FabricMC/fabric"
},
"authors": [
"FabricMC"
],
"depends": {
"fabricloader": ">=0.16.9",
"fabric-api-base": "*",
"fabric-resource-loader-v0": "*"
},
"entrypoints": {
"main": [
"net.fabricmc.fabric.impl.tag.TagInit"
]
},
"description": "Hooks for working with tags.",
"mixins": [
"fabric-tag-api-v1.mixins.json"
],
"accessWidener": "fabric-tag-api-v1.accesswidener",
"custom": {
"fabric-api:module-lifecycle": "stable"
}
}

View file

@ -0,0 +1,148 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.tag;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.item.Item;
import net.minecraft.item.Items;
import net.minecraft.loot.LootTable;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryEntryLookup;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.registry.entry.RegistryEntryList;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.util.Identifier;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeKeys;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.CommonLifecycleEvents;
public final class TagAliasTest implements ModInitializer {
private static final Logger LOGGER = LoggerFactory.getLogger(TagAliasTest.class);
// Test 1: Alias two non-empty tags
public static final TagKey<Item> GEMS = tagKey(RegistryKeys.ITEM, "gems");
public static final TagKey<Item> EXPENSIVE_ROCKS = tagKey(RegistryKeys.ITEM, "expensive_rocks");
// Test 2: Alias a non-empty tag and an empty tag
public static final TagKey<Item> REDSTONE_DUSTS = tagKey(RegistryKeys.ITEM, "redstone_dusts");
public static final TagKey<Item> REDSTONE_POWDERS = tagKey(RegistryKeys.ITEM, "redstone_powders");
// Test 3: Alias a non-empty tag and a missing tag
public static final TagKey<Item> BEETROOTS = tagKey(RegistryKeys.ITEM, "beetroots");
public static final TagKey<Item> MISSING_BEETROOTS = tagKey(RegistryKeys.ITEM, "missing_beetroots");
// Test 4: Given tags A, B, C, make alias groups A+B and B+C. They should get merged.
public static final TagKey<Block> BRICK_BLOCKS = tagKey(RegistryKeys.BLOCK, "brick_blocks");
public static final TagKey<Block> MORE_BRICK_BLOCKS = tagKey(RegistryKeys.BLOCK, "more_brick_blocks");
public static final TagKey<Block> BRICKS = tagKey(RegistryKeys.BLOCK, "bricks");
// Test 5: Merge tags from a world generation dynamic registry
public static final TagKey<Biome> CLASSIC_BIOMES = tagKey(RegistryKeys.BIOME, "classic");
public static final TagKey<Biome> TRADITIONAL_BIOMES = tagKey(RegistryKeys.BIOME, "traditional");
// Test 6: Merge tags from a reloadable registry
public static final TagKey<LootTable> NETHER_BRICKS_1 = tagKey(RegistryKeys.LOOT_TABLE, "nether_bricks_1");
public static final TagKey<LootTable> NETHER_BRICKS_2 = tagKey(RegistryKeys.LOOT_TABLE, "nether_bricks_2");
private static <T> TagKey<T> tagKey(RegistryKey<? extends Registry<T>> registryRef, String name) {
return TagKey.of(registryRef, Identifier.of("fabric-tag-api-v1-testmod", name));
}
@Override
public void onInitialize() {
CommonLifecycleEvents.TAGS_LOADED.register((registries, client) -> {
LOGGER.info("Running tag alias tests on the {}...", client ? "client" : "server");
assertTagContent(registries, List.of(GEMS, EXPENSIVE_ROCKS), TagAliasTest::getItemKey,
Items.DIAMOND, Items.EMERALD);
assertTagContent(registries, List.of(REDSTONE_DUSTS, REDSTONE_POWDERS), TagAliasTest::getItemKey,
Items.REDSTONE);
assertTagContent(registries, List.of(BEETROOTS, MISSING_BEETROOTS), TagAliasTest::getItemKey,
Items.BEETROOT);
assertTagContent(registries, List.of(BRICK_BLOCKS, MORE_BRICK_BLOCKS, BRICKS), TagAliasTest::getBlockKey,
Blocks.BRICKS, Blocks.STONE_BRICKS, Blocks.NETHER_BRICKS, Blocks.RED_NETHER_BRICKS);
assertTagContent(registries, List.of(CLASSIC_BIOMES, TRADITIONAL_BIOMES),
BiomeKeys.PLAINS, BiomeKeys.DESERT);
// The loot table registry isn't synced to the client.
if (!client) {
assertTagContent(registries, List.of(NETHER_BRICKS_1, NETHER_BRICKS_2),
Blocks.NETHER_BRICKS.getLootTableKey().orElseThrow(),
Blocks.RED_NETHER_BRICKS.getLootTableKey().orElseThrow());
}
LOGGER.info("Tag alias tests completed successfully!");
});
}
private static RegistryKey<Block> getBlockKey(Block block) {
return block.getRegistryEntry().registryKey();
}
private static RegistryKey<Item> getItemKey(Item item) {
return item.getRegistryEntry().registryKey();
}
@SafeVarargs
private static <T> void assertTagContent(RegistryWrapper.WrapperLookup registries, List<TagKey<T>> tags, Function<T, RegistryKey<T>> keyExtractor, T... expected) {
Set<RegistryKey<T>> keys = Arrays.stream(expected)
.map(keyExtractor)
.collect(Collectors.toSet());
assertTagContent(registries, tags, keys);
}
@SafeVarargs
private static <T> void assertTagContent(RegistryWrapper.WrapperLookup registries, List<TagKey<T>> tags, RegistryKey<T>... expected) {
assertTagContent(registries, tags, Set.of(expected));
}
private static <T> void assertTagContent(RegistryWrapper.WrapperLookup registries, List<TagKey<T>> tags, Set<RegistryKey<T>> expected) {
RegistryEntryLookup<T> lookup = registries.getOrThrow(tags.getFirst().registryRef());
for (TagKey<T> tag : tags) {
RegistryEntryList.Named<T> tagEntryList = lookup.getOrThrow(tag);
Set<RegistryKey<T>> actual = tagEntryList.entries
.stream()
.map(entry -> entry.getKey().orElseThrow())
.collect(Collectors.toSet());
if (!actual.equals(expected)) {
throw new AssertionError("Expected tag %s to have contents %s, but it had %s instead"
.formatted(tag, expected, actual));
}
}
LOGGER.info("Tags {} / {} were successfully aliased together", tags.getFirst().registryRef().getValue(), tags.stream()
.map(TagKey::id)
.map(Identifier::toString)
.collect(Collectors.joining(", ")));
}
}

View file

@ -0,0 +1,15 @@
{
"tag.block.fabric-tag-api-v1-testmod.bricks": "Bricks",
"tag.block.fabric-tag-api-v1-testmod.brick_blocks": "Brick Blocks",
"tag.block.fabric-tag-api-v1-testmod.more_brick_blocks": "More Brick Blocks",
"tag.item.fabric-tag-api-v1-testmod.beetroots": "Beetroots",
"tag.item.fabric-tag-api-v1-testmod.expensive_rocks": "Expensive Rocks",
"tag.item.fabric-tag-api-v1-testmod.gems": "Gems",
"tag.item.fabric-tag-api-v1-testmod.missing_beetroots": "Missing Beetroots",
"tag.item.fabric-tag-api-v1-testmod.redstone_dusts": "Redstone Dusts",
"tag.item.fabric-tag-api-v1-testmod.redstone_powders": "Redstone Powders",
"tag.loot_table.fabric-tag-api-v1-testmod.nether_bricks_1": "Nether Bricks 1",
"tag.loot_table.fabric-tag-api-v1-testmod.nether_bricks_2": "Nether Bricks 2",
"tag.worldgen.biome.fabric-tag-api-v1-testmod.classic": "Classic",
"tag.worldgen.biome.fabric-tag-api-v1-testmod.traditional": "Traditional"
}

View file

@ -0,0 +1,6 @@
{
"tags": [
"fabric-tag-api-v1-testmod:bricks",
"fabric-tag-api-v1-testmod:brick_blocks"
]
}

View file

@ -0,0 +1,6 @@
{
"tags": [
"fabric-tag-api-v1-testmod:brick_blocks",
"fabric-tag-api-v1-testmod:more_brick_blocks"
]
}

View file

@ -0,0 +1,6 @@
{
"tags": [
"fabric-tag-api-v1-testmod:beetroots",
"fabric-tag-api-v1-testmod:missing_beetroots"
]
}

View file

@ -0,0 +1,6 @@
{
"tags": [
"fabric-tag-api-v1-testmod:expensive_rocks",
"fabric-tag-api-v1-testmod:gems"
]
}

View file

@ -0,0 +1,6 @@
{
"tags": [
"fabric-tag-api-v1-testmod:redstone_dusts",
"fabric-tag-api-v1-testmod:redstone_powders"
]
}

View file

@ -0,0 +1,6 @@
{
"tags": [
"fabric-tag-api-v1-testmod:nether_bricks_1",
"fabric-tag-api-v1-testmod:nether_bricks_2"
]
}

View file

@ -0,0 +1,6 @@
{
"tags": [
"fabric-tag-api-v1-testmod:classic",
"fabric-tag-api-v1-testmod:traditional"
]
}

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"minecraft:nether_bricks"
]
}

View file

@ -0,0 +1,7 @@
{
"replace": false,
"values": [
"minecraft:bricks",
"minecraft:stone_bricks"
]
}

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"minecraft:red_nether_bricks"
]
}

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"minecraft:beetroot"
]
}

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"minecraft:emerald"
]
}

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"minecraft:diamond"
]
}

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"minecraft:redstone"
]
}

View file

@ -0,0 +1,5 @@
{
"replace": false,
"values": [
]
}

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"minecraft:blocks/nether_bricks"
]
}

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"minecraft:blocks/red_nether_bricks"
]
}

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"minecraft:plains"
]
}

View file

@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"minecraft:desert"
]
}

View file

@ -0,0 +1,16 @@
{
"schemaVersion": 1,
"id": "fabric-tag-v1-testmod",
"name": "Fabric Tag API (v1) Test Mod",
"version": "1.0.0",
"environment": "*",
"license": "Apache-2.0",
"depends": {
"fabric-tag-api-v1": "*"
},
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.tag.TagAliasTest"
]
}
}

View file

@ -53,6 +53,7 @@ fabric-resource-loader-v0-version=3.0.10
fabric-screen-api-v1-version=2.0.37
fabric-screen-handler-api-v1-version=1.3.106
fabric-sound-api-v1-version=1.0.32
fabric-tag-api-v1-version=1.0.0
fabric-transfer-api-v1-version=5.4.6
fabric-transitive-access-wideners-v1-version=6.3.2
fabric-convention-tags-v1-version=2.1.6

View file

@ -63,6 +63,7 @@ include 'fabric-resource-loader-v0'
include 'fabric-screen-api-v1'
include 'fabric-screen-handler-api-v1'
include 'fabric-sound-api-v1'
include 'fabric-tag-api-v1'
include 'fabric-transfer-api-v1'
include 'fabric-transitive-access-wideners-v1'