Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tag aliases #4198

Merged
merged 22 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
738bc5d
Add tag aliases
Juuxel Nov 1, 2024
aed325c
Document and rename tag alias internals
Juuxel Nov 10, 2024
0169e53
Make the tag alias directory singular to match Mojang's recent style
Juuxel Nov 10, 2024
598ac0a
Add a note about tag aliases to client tag documentation
Juuxel Nov 10, 2024
062ca52
Support missing tags in alias groups
Juuxel Nov 10, 2024
1bb3e93
Support tag aliases for dynamic and reloadable registries
Juuxel Nov 10, 2024
8ef1e40
TagAliasGroup: Document naming conventions for c tag alias groups
Juuxel Nov 10, 2024
75d2b4b
Add tag alias test mod
Juuxel Nov 10, 2024
597dd0b
Fix inline return checkstyle
Juuxel Nov 10, 2024
c4799d4
Add test for tag alias data generation
Juuxel Nov 10, 2024
f06f3f8
Fix checkstyle (again)
Juuxel Nov 10, 2024
bf98da9
Add tag translations to tag API testmod
Juuxel Nov 10, 2024
a479beb
Uncomment accidentally commented out code
Juuxel Nov 10, 2024
ad89dbc
SimpleRegistryMixin: Improve a log message
Juuxel Nov 10, 2024
c6949bd
TagAliasTest: Improve assertion messages
Juuxel Nov 10, 2024
9a464a1
Fix tag aliases for dynamic registries not applying on /reload
Juuxel Nov 10, 2024
67c243f
Clean up log message once again
Juuxel Nov 10, 2024
3a0a020
Address review feedback
Juuxel Nov 26, 2024
d3f5e0b
Make missing interfaces throw CCEs
Juuxel Nov 26, 2024
c035032
Merge remote-tracking branch 'upstream/1.21.3' into tag-aliases
Juuxel Nov 26, 2024
e01b48e
Add README
Juuxel Nov 26, 2024
cb5f5a7
Move TagAliasGroup into the impl package
Juuxel Dec 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
* <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() {
Expand Down
1 change: 1 addition & 0 deletions fabric-data-generation-api-v1/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -70,6 +75,9 @@
* @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.
*
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -116,6 +125,34 @@ protected FabricTagBuilder getOrCreateTagBuilder(TagKey<T> tag) {
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 getOrCreateAliasGroupBuilder(Identifier groupId) {
Juuxel marked this conversation as resolved.
Show resolved Hide resolved
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 getOrCreateAliasGroupBuilder(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.
*/
Expand Down Expand Up @@ -396,4 +433,49 @@ public final FabricTagBuilder add(RegistryKey<T>... registryKeys) {
return this;
}
}

/**
* A builder for tag alias groups.
*/
public final class AliasGroupBuilder {
Juuxel marked this conversation as resolved.
Show resolved Hide resolved
private final List<TagKey<T>> tags = new ArrayList<>();

/**
* {@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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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.api.tag.v1.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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.server.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) {
Expand All @@ -36,4 +66,23 @@ private boolean addReplaced(boolean replaced, @Local TagBuilder tagBuilder) {

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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"FabricMC"
],
"depends": {
"fabricloader": ">=0.16.8"
"fabricloader": ">=0.16.8",
"fabric-tag-api-v1": "*"
},
"description": "Allows for automatic data generation.",
"mixins": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"tags": [
"minecraft:flowers",
"minecraft:flower_pots"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"tags": [
"minecraft:flowers",
"minecraft:flower_pots"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,11 @@ protected void configure(RegistryWrapper.WrapperLookup registries) {
getOrCreateTagBuilder(BlockTags.FIRE).setReplace(true).add(SIMPLE_BLOCK);
getOrCreateTagBuilder(BlockTags.DIRT).add(SIMPLE_BLOCK);
getOrCreateTagBuilder(BlockTags.ACACIA_LOGS).forceAddTag(BlockTags.ANIMALS_SPAWNABLE_ON);

getOrCreateAliasGroupBuilder("flowers")
.add(BlockTags.FLOWERS, BlockTags.FLOWER_POTS);
getOrCreateAliasGroupBuilder(Identifier.of("other_namespace", "flowers"))
.add(BlockTags.FLOWERS, BlockTags.FLOWER_POTS);
}
}

Expand Down
14 changes: 14 additions & 0 deletions fabric-tag-api-v1/build.gradle
Original file line number Diff line number Diff line change
@@ -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',
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* 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.api.tag.v1;

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 group of tags that refer to the same set of registry entries.
Juuxel marked this conversation as resolved.
Show resolved Hide resolved
* 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}.
*
* <p>The JSON format of tag alias groups is an object with a {@code tags} list containing plain tag IDs.
*
* <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.
*
* @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) {
Juuxel marked this conversation as resolved.
Show resolved Hide resolved
/**
* 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();
}
}
Loading
Loading