mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-08 21:14:41 -04:00
Add tag aliases (#4198)
* 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:
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
README.mdbuild.gradlefabric.mod.json
gradle.propertiessettings.gradlesrc
main
java/net/fabricmc/fabric
api/tag/v1
impl/tag
SimpleRegistryExtension.javaTagAliasEnabledRegistryWrapper.javaTagAliasGroup.javaTagAliasLoader.javaTagInit.java
mixin/tag
resources
testmod
java/net/fabricmc/fabric/test/tag
resources
assets/fabric-tag-api-v1-testmod/lang
data/fabric-tag-api-v1-testmod
fabric/tag_alias
block
item
loot_table
worldgen/biome
tags
|
@ -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() {
|
||||
|
|
|
@ -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'
|
||||
])
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"tags": [
|
||||
"minecraft:flowers",
|
||||
"minecraft:flower_pots"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"tags": [
|
||||
"minecraft:flowers",
|
||||
"minecraft:flower_pots"
|
||||
]
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
17
fabric-tag-api-v1/README.md
Normal file
17
fabric-tag-api-v1/README.md
Normal 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.
|
14
fabric-tag-api-v1/build.gradle
Normal file
14
fabric-tag-api-v1/build.gradle
Normal 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',
|
||||
])
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 |
|
@ -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;
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"required": true,
|
||||
"package": "net.fabricmc.fabric.mixin.tag",
|
||||
"compatibilityLevel": "JAVA_21",
|
||||
"mixins": [
|
||||
"DataPackContentsMixin",
|
||||
"SimpleRegistryMixin",
|
||||
"SimpleRegistry2Mixin",
|
||||
"SimpleRegistry3Mixin",
|
||||
"SimpleRegistryTagLookup2Accessor"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
36
fabric-tag-api-v1/src/main/resources/fabric.mod.json
Normal file
36
fabric-tag-api-v1/src/main/resources/fabric.mod.json
Normal 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"
|
||||
}
|
||||
}
|
|
@ -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(", ")));
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"tags": [
|
||||
"fabric-tag-api-v1-testmod:bricks",
|
||||
"fabric-tag-api-v1-testmod:brick_blocks"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"tags": [
|
||||
"fabric-tag-api-v1-testmod:brick_blocks",
|
||||
"fabric-tag-api-v1-testmod:more_brick_blocks"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"tags": [
|
||||
"fabric-tag-api-v1-testmod:beetroots",
|
||||
"fabric-tag-api-v1-testmod:missing_beetroots"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"tags": [
|
||||
"fabric-tag-api-v1-testmod:expensive_rocks",
|
||||
"fabric-tag-api-v1-testmod:gems"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"tags": [
|
||||
"fabric-tag-api-v1-testmod:redstone_dusts",
|
||||
"fabric-tag-api-v1-testmod:redstone_powders"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"tags": [
|
||||
"fabric-tag-api-v1-testmod:nether_bricks_1",
|
||||
"fabric-tag-api-v1-testmod:nether_bricks_2"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"tags": [
|
||||
"fabric-tag-api-v1-testmod:classic",
|
||||
"fabric-tag-api-v1-testmod:traditional"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:nether_bricks"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:bricks",
|
||||
"minecraft:stone_bricks"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:red_nether_bricks"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:beetroot"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:emerald"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:diamond"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:redstone"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:blocks/nether_bricks"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:blocks/red_nether_bricks"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:plains"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:desert"
|
||||
]
|
||||
}
|
16
fabric-tag-api-v1/src/testmod/resources/fabric.mod.json
Normal file
16
fabric-tag-api-v1/src/testmod/resources/fabric.mod.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue