diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/api/tag/v1/FabricTagEntry.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/api/tag/v1/FabricTagEntry.java new file mode 100644 index 0000000000..4d3a3321ea --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/api/tag/v1/FabricTagEntry.java @@ -0,0 +1,23 @@ +/* + * 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; + +public interface FabricTagEntry { + default boolean isRemoved() { + return false; + } +} diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/FabricTagEntryImpl.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/FabricTagEntryImpl.java new file mode 100644 index 0000000000..4b0483fd7f --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/FabricTagEntryImpl.java @@ -0,0 +1,77 @@ +/* + * 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.function.Supplier; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Decoder; +import com.mojang.serialization.DynamicOps; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.registry.tag.TagEntry; +import net.minecraft.util.Unit; + +import net.fabricmc.fabric.impl.tag.util.WrapperCodec; + +public final class FabricTagEntryImpl { + public static final Codec REMOVED_ENTRY_CODEC = new WrapperCodec<>( + TagEntry.CODEC, + new WrapperCodec.Wrapper<>() { + @Override + public DataResult> decode(DynamicOps ops, T input, Decoder wrapped) { + return FabricTagEntryImpl.withRemovedValue(true, () -> wrapped.decode(ops, input)); + } + } + ); + + /** + * A Fake Argument to the {@link TagEntry} constructor representing whether the entry will have it's {@code removed} flag set. + */ + private static final ThreadLocal REMOVED = new ThreadLocal<>(); + + private FabricTagEntryImpl() { + throw new UnsupportedOperationException(); + } + + public static T withRemovedValue(boolean value, Supplier action) { + @Nullable + Unit initialValue = REMOVED.get(); + + try { + if (value) { + REMOVED.set(Unit.INSTANCE); + } else { + REMOVED.remove(); + } + + return action.get(); + } finally { + if (initialValue == null) { + REMOVED.remove(); + } else { + REMOVED.set(initialValue); + } + } + } + + public static boolean getCurrentRemovedValue() { + return REMOVED.get() != null; + } +} diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/util/WrapperCodec.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/util/WrapperCodec.java new file mode 100644 index 0000000000..cf74448588 --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/util/WrapperCodec.java @@ -0,0 +1,45 @@ +/* + * 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.util; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Decoder; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.Encoder; + +public record WrapperCodec(Codec wrapped, Wrapper wrapper) implements Codec { + @Override + public DataResult> decode(DynamicOps ops, T input) { + return wrapper.decode(ops, input, wrapped); + } + + @Override + public DataResult encode(A input, DynamicOps ops, T prefix) { + return wrapper.encode(input, ops, prefix, wrapped); + } + + public interface Wrapper { + default DataResult encode(final A input, final DynamicOps ops, final T prefix, Encoder wrapped) { + return wrapped.encode(input, ops, prefix); + } + default DataResult> decode(final DynamicOps ops, final T input, Decoder wrapped) { + return wrapped.decode(ops, input); + } + } +} diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/TagEntryMixin.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/TagEntryMixin.java new file mode 100644 index 0000000000..6a59f2dd47 --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/TagEntryMixin.java @@ -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.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.Unique; + +import net.minecraft.registry.tag.TagEntry; + +import net.fabricmc.fabric.api.tag.v1.FabricTagEntry; +import net.fabricmc.fabric.impl.tag.FabricTagEntryImpl; + +@Mixin(TagEntry.class) +public class TagEntryMixin implements FabricTagEntry { + @Shadow + @Final + private boolean required; + + @Unique + private final boolean removed; + + public TagEntryMixin() { } + + { + removed = FabricTagEntryImpl.getCurrentRemovedValue(); + required = required && !removed; + } + + @Override + public boolean isRemoved() { + return this.removed; + } +} diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/TagFileMixin.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/TagFileMixin.java new file mode 100644 index 0000000000..9d41d6d815 --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/TagFileMixin.java @@ -0,0 +1,125 @@ +/* + * 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 java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import com.google.common.collect.Streams; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Decoder; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.Encoder; +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.ModifyArg; +import org.spongepowered.asm.mixin.injection.Slice; + +import net.minecraft.registry.tag.TagEntry; +import net.minecraft.registry.tag.TagFile; + +import net.fabricmc.fabric.api.tag.v1.FabricTagEntry; +import net.fabricmc.fabric.impl.tag.FabricTagEntryImpl; +import net.fabricmc.fabric.impl.tag.util.WrapperCodec; + +@Mixin(TagFile.class) +public class TagFileMixin { + @Unique + private static final ThreadLocal> REMOVE_ENTRIES = ThreadLocal.withInitial(List::of); + + @Shadow + @Final + public static Codec CODEC; + + @ModifyArg( + method = "method_43950", + at = @At( + value = "INVOKE", + target = "Lcom/mojang/datafixers/Products$P2;apply(Lcom/mojang/datafixers/kinds/Applicative;Ljava/util/function/BiFunction;)Lcom/mojang/datafixers/kinds/App;" + ) + ) + private static BiFunction, Boolean, TagFile> modify(BiFunction, Boolean, TagFile> instance) { + return (entries, replace) -> instance.apply( + Streams.concat(entries.stream(), REMOVE_ENTRIES.get().stream()).toList(), + replace + ); + } + + @ModifyArg( + method = "method_43950", + at = @At( + value = "INVOKE:FIRST", + target = "Lcom/mojang/serialization/MapCodec;forGetter(Ljava/util/function/Function;)Lcom/mojang/serialization/codecs/RecordCodecBuilder;" + ), + slice = @Slice(from = @At(value = "CONSTANT", args = "stringValue=values")) + ) + private static Function> wrapGetter(Function> getter) { + return getter.andThen(list -> list.stream().filter(Predicate.not(entry -> ((FabricTagEntry) entry).isRemoved())).toList()); + } + + static { + Codec> removeEntryCodec = FabricTagEntryImpl.REMOVED_ENTRY_CODEC + .listOf() + .lenientOptionalFieldOf("c:remove", List.of()) + .codec(); + + CODEC = new WrapperCodec<>(CODEC, new WrapperCodec.Wrapper<>() { + @Override + public DataResult encode(TagFile input, DynamicOps ops, T prefix, Encoder wrapped) { + return wrapped.encode(input, ops, prefix).flatMap( + result -> removeEntryCodec.encode( + List.copyOf( + input.entries() + .stream() + .filter(entry -> ((FabricTagEntry) entry).isRemoved()) + .toList() + ), + ops, + result + ) + ); + } + + @Override + public DataResult> decode(DynamicOps ops, T input, Decoder wrapped) { + return removeEntryCodec.decode(ops, input).flatMap( + result -> withRemovedEntries(result.getFirst(), () -> wrapped.decode(ops, input)) + ); + } + }); + } + + @Unique + private static T withRemovedEntries(List removed, Supplier action) { + List initialValue = REMOVE_ENTRIES.get(); + + try { + REMOVE_ENTRIES.set(removed); + return action.get(); + } finally { + REMOVE_ENTRIES.set(initialValue); + } + } +} diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/TagGroupLoaderMixin.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/TagGroupLoaderMixin.java new file mode 100644 index 0000000000..42f4aed3ca --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/TagGroupLoaderMixin.java @@ -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.mixin.tag; + +import java.util.SequencedSet; +import java.util.function.Consumer; + +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.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import net.minecraft.registry.tag.TagEntry; +import net.minecraft.registry.tag.TagGroupLoader; + +import net.fabricmc.fabric.api.tag.v1.FabricTagEntry; + +@Mixin(TagGroupLoader.class) +public class TagGroupLoaderMixin { + @WrapOperation( + method = "resolveAll", + at = @At(value = "INVOKE", target = "Lnet/minecraft/registry/tag/TagEntry;resolve(Lnet/minecraft/registry/tag/TagEntry$ValueGetter;Ljava/util/function/Consumer;)Z") + ) + private boolean swapRemovalIdConsumer(TagEntry instance, TagEntry.ValueGetter valueGetter, Consumer idConsumer, Operation original, @Local SequencedSet sequencedSet) { + return original.call( + instance, + valueGetter, + ((FabricTagEntry) instance).isRemoved() + ? (Consumer) sequencedSet::remove + : idConsumer + ); + } +} diff --git a/fabric-tag-api-v1/src/main/resources/fabric-tag-api-v1.mixins.json b/fabric-tag-api-v1/src/main/resources/fabric-tag-api-v1.mixins.json index c9b582b157..c9ec489e5c 100644 --- a/fabric-tag-api-v1/src/main/resources/fabric-tag-api-v1.mixins.json +++ b/fabric-tag-api-v1/src/main/resources/fabric-tag-api-v1.mixins.json @@ -4,10 +4,13 @@ "compatibilityLevel": "JAVA_21", "mixins": [ "DataPackContentsMixin", - "SimpleRegistryMixin", "SimpleRegistry2Mixin", "SimpleRegistry3Mixin", - "SimpleRegistryTagLookup2Accessor" + "SimpleRegistryMixin", + "SimpleRegistryTagLookup2Accessor", + "TagEntryMixin", + "TagFileMixin", + "TagGroupLoaderMixin" ], "injectors": { "defaultRequire": 1 diff --git a/fabric-tag-api-v1/src/main/resources/fabric.mod.json b/fabric-tag-api-v1/src/main/resources/fabric.mod.json index 08cde39677..9ef75c34ab 100644 --- a/fabric-tag-api-v1/src/main/resources/fabric.mod.json +++ b/fabric-tag-api-v1/src/main/resources/fabric.mod.json @@ -34,6 +34,11 @@ ], "accessWidener": "fabric-tag-api-v1.accesswidener", "custom": { + "loom:injected_interfaces": { + "net/minecraft/class_91": [ + "net/fabricmc/fabric/api/tag/v1/FabricTagEntry" + ] + }, "fabric-api:module-lifecycle": "stable" } } diff --git a/fabric-tag-api-v1/src/testmod/java/net/fabricmc/fabric/test/tag/TagAliasTest.java b/fabric-tag-api-v1/src/testmod/java/net/fabricmc/fabric/test/tag/TagAliasTest.java index 511353e14c..6635664b25 100644 --- a/fabric-tag-api-v1/src/testmod/java/net/fabricmc/fabric/test/tag/TagAliasTest.java +++ b/fabric-tag-api-v1/src/testmod/java/net/fabricmc/fabric/test/tag/TagAliasTest.java @@ -16,11 +16,7 @@ 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; @@ -30,14 +26,8 @@ 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; @@ -48,53 +38,49 @@ 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 GEMS = tagKey(RegistryKeys.ITEM, "gems"); - public static final TagKey EXPENSIVE_ROCKS = tagKey(RegistryKeys.ITEM, "expensive_rocks"); + public static final TagKey GEMS = TagTestUtils.tagKey(RegistryKeys.ITEM, "gems"); + public static final TagKey EXPENSIVE_ROCKS = TagTestUtils.tagKey(RegistryKeys.ITEM, "expensive_rocks"); // Test 2: Alias a non-empty tag and an empty tag - public static final TagKey REDSTONE_DUSTS = tagKey(RegistryKeys.ITEM, "redstone_dusts"); - public static final TagKey REDSTONE_POWDERS = tagKey(RegistryKeys.ITEM, "redstone_powders"); + public static final TagKey REDSTONE_DUSTS = TagTestUtils.tagKey(RegistryKeys.ITEM, "redstone_dusts"); + public static final TagKey REDSTONE_POWDERS = TagTestUtils.tagKey(RegistryKeys.ITEM, "redstone_powders"); // Test 3: Alias a non-empty tag and a missing tag - public static final TagKey BEETROOTS = tagKey(RegistryKeys.ITEM, "beetroots"); - public static final TagKey MISSING_BEETROOTS = tagKey(RegistryKeys.ITEM, "missing_beetroots"); + public static final TagKey BEETROOTS = TagTestUtils.tagKey(RegistryKeys.ITEM, "beetroots"); + public static final TagKey MISSING_BEETROOTS = TagTestUtils.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 BRICK_BLOCKS = tagKey(RegistryKeys.BLOCK, "brick_blocks"); - public static final TagKey MORE_BRICK_BLOCKS = tagKey(RegistryKeys.BLOCK, "more_brick_blocks"); - public static final TagKey BRICKS = tagKey(RegistryKeys.BLOCK, "bricks"); + public static final TagKey BRICK_BLOCKS = TagTestUtils.tagKey(RegistryKeys.BLOCK, "brick_blocks"); + public static final TagKey MORE_BRICK_BLOCKS = TagTestUtils.tagKey(RegistryKeys.BLOCK, "more_brick_blocks"); + public static final TagKey BRICKS = TagTestUtils.tagKey(RegistryKeys.BLOCK, "bricks"); // Test 5: Merge tags from a world generation dynamic registry - public static final TagKey CLASSIC_BIOMES = tagKey(RegistryKeys.BIOME, "classic"); - public static final TagKey TRADITIONAL_BIOMES = tagKey(RegistryKeys.BIOME, "traditional"); + public static final TagKey CLASSIC_BIOMES = TagTestUtils.tagKey(RegistryKeys.BIOME, "classic"); + public static final TagKey TRADITIONAL_BIOMES = TagTestUtils.tagKey(RegistryKeys.BIOME, "traditional"); // Test 6: Merge tags from a reloadable registry - public static final TagKey NETHER_BRICKS_1 = tagKey(RegistryKeys.LOOT_TABLE, "nether_bricks_1"); - public static final TagKey NETHER_BRICKS_2 = tagKey(RegistryKeys.LOOT_TABLE, "nether_bricks_2"); - - private static TagKey tagKey(RegistryKey> registryRef, String name) { - return TagKey.of(registryRef, Identifier.of("fabric-tag-api-v1-testmod", name)); - } + public static final TagKey NETHER_BRICKS_1 = TagTestUtils.tagKey(RegistryKeys.LOOT_TABLE, "nether_bricks_1"); + public static final TagKey NETHER_BRICKS_2 = TagTestUtils.tagKey(RegistryKeys.LOOT_TABLE, "nether_bricks_2"); @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, + TagTestUtils.assertTagContent(LOGGER, "Tags {} / {} were successfully aliased together", registries, List.of(GEMS, EXPENSIVE_ROCKS), TagTestUtils::getItemKey, Items.DIAMOND, Items.EMERALD); - assertTagContent(registries, List.of(REDSTONE_DUSTS, REDSTONE_POWDERS), TagAliasTest::getItemKey, + TagTestUtils.assertTagContent(LOGGER, "Tags {} / {} were successfully aliased together", registries, List.of(REDSTONE_DUSTS, REDSTONE_POWDERS), TagTestUtils::getItemKey, Items.REDSTONE); - assertTagContent(registries, List.of(BEETROOTS, MISSING_BEETROOTS), TagAliasTest::getItemKey, + TagTestUtils.assertTagContent(LOGGER, "Tags {} / {} were successfully aliased together", registries, List.of(BEETROOTS, MISSING_BEETROOTS), TagTestUtils::getItemKey, Items.BEETROOT); - assertTagContent(registries, List.of(BRICK_BLOCKS, MORE_BRICK_BLOCKS, BRICKS), TagAliasTest::getBlockKey, + TagTestUtils.assertTagContent(LOGGER, "Tags {} / {} were successfully aliased together", registries, List.of(BRICK_BLOCKS, MORE_BRICK_BLOCKS, BRICKS), TagTestUtils::getBlockKey, Blocks.BRICKS, Blocks.STONE_BRICKS, Blocks.NETHER_BRICKS, Blocks.RED_NETHER_BRICKS); - assertTagContent(registries, List.of(CLASSIC_BIOMES, TRADITIONAL_BIOMES), + TagTestUtils.assertTagContent(LOGGER, "Tags {} / {} were successfully aliased together", 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), + TagTestUtils.assertTagContent(LOGGER, "Tags {} / {} were successfully aliased together", registries, List.of(NETHER_BRICKS_1, NETHER_BRICKS_2), Blocks.NETHER_BRICKS.getLootTableKey().orElseThrow(), Blocks.RED_NETHER_BRICKS.getLootTableKey().orElseThrow()); } @@ -102,47 +88,4 @@ public void onInitialize() { LOGGER.info("Tag alias tests completed successfully!"); }); } - - private static RegistryKey getBlockKey(Block block) { - return block.getRegistryEntry().registryKey(); - } - - private static RegistryKey getItemKey(Item item) { - return item.getRegistryEntry().registryKey(); - } - - @SafeVarargs - private static void assertTagContent(RegistryWrapper.WrapperLookup registries, List> tags, Function> keyExtractor, T... expected) { - Set> keys = Arrays.stream(expected) - .map(keyExtractor) - .collect(Collectors.toSet()); - assertTagContent(registries, tags, keys); - } - - @SafeVarargs - private static void assertTagContent(RegistryWrapper.WrapperLookup registries, List> tags, RegistryKey... expected) { - assertTagContent(registries, tags, Set.of(expected)); - } - - private static void assertTagContent(RegistryWrapper.WrapperLookup registries, List> tags, Set> expected) { - RegistryEntryLookup lookup = registries.getOrThrow(tags.getFirst().registryRef()); - - for (TagKey tag : tags) { - RegistryEntryList.Named tagEntryList = lookup.getOrThrow(tag); - Set> 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(", "))); - } } diff --git a/fabric-tag-api-v1/src/testmod/java/net/fabricmc/fabric/test/tag/TagEntryRemovalTest.java b/fabric-tag-api-v1/src/testmod/java/net/fabricmc/fabric/test/tag/TagEntryRemovalTest.java new file mode 100644 index 0000000000..aee9773d2f --- /dev/null +++ b/fabric-tag-api-v1/src/testmod/java/net/fabricmc/fabric/test/tag/TagEntryRemovalTest.java @@ -0,0 +1,53 @@ +/* + * 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.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.item.Item; +import net.minecraft.item.Items; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.tag.TagKey; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.CommonLifecycleEvents; + +public final class TagEntryRemovalTest implements ModInitializer { + private static final Logger LOGGER = LoggerFactory.getLogger(TagEntryRemovalTest.class); + + private final TagKey TEST_TAG = TagTestUtils.tagKey(RegistryKeys.ITEM, "tag_with_snowballs_but_not_bricks"); + + @Override + public void onInitialize() { + CommonLifecycleEvents.TAGS_LOADED.register((registries, client) -> { + if (client) { + return; + } + + LOGGER.info("Running tag entry removal tests..."); + TagTestUtils.assertTagContent(LOGGER, "Tag {} / {} Contains expected entries", registries, List.of(TEST_TAG), TagTestUtils::getItemKey, Items.SNOWBALL); + TagTestUtils.assertThrows( + () -> TagTestUtils.assertTagContent(LOGGER, "Tag {} Contains expected entries", registries, List.of(TEST_TAG), TagTestUtils::getItemKey, Items.BRICK), + "Expected %s not to contain bricks".formatted(TEST_TAG) + ); + LOGGER.info("Tag entry removal tests completed successfully!"); + }); + } +} diff --git a/fabric-tag-api-v1/src/testmod/java/net/fabricmc/fabric/test/tag/TagTestUtils.java b/fabric-tag-api-v1/src/testmod/java/net/fabricmc/fabric/test/tag/TagTestUtils.java new file mode 100644 index 0000000000..adfd923652 --- /dev/null +++ b/fabric-tag-api-v1/src/testmod/java/net/fabricmc/fabric/test/tag/TagTestUtils.java @@ -0,0 +1,99 @@ +/* + * 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.apache.commons.lang3.function.FailableRunnable; +import org.slf4j.Logger; + +import net.minecraft.block.Block; +import net.minecraft.item.Item; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryEntryLookup; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; + +public class TagTestUtils { + static TagKey tagKey(RegistryKey> registryRef, String name) { + return TagKey.of(registryRef, Identifier.of("fabric-tag-api-v1-testmod", name)); + } + + static RegistryKey getBlockKey(Block block) { + return block.getRegistryEntry().registryKey(); + } + + static RegistryKey getItemKey(Item item) { + return item.getRegistryEntry().registryKey(); + } + + static void assertThrows(FailableRunnable action, String message) { + boolean threw = false; + + try { + action.run(); + } catch (AssertionError err) { + threw = true; + } + + if (!threw) { + throw new AssertionError(message); + } + } + + @SafeVarargs + static void assertTagContent(Logger logger, String successFmtStr, RegistryWrapper.WrapperLookup registries, List> tags, Function> keyExtractor, T... expected) { + Set> keys = Arrays.stream(expected) + .map(keyExtractor) + .collect(Collectors.toSet()); + assertTagContent(logger, successFmtStr, registries, tags, keys); + } + + @SafeVarargs + static void assertTagContent(Logger logger, String successFmtStr, RegistryWrapper.WrapperLookup registries, List> tags, RegistryKey... expected) { + assertTagContent(logger, successFmtStr, registries, tags, Set.of(expected)); + } + + static void assertTagContent(Logger logger, String successFmtStr, RegistryWrapper.WrapperLookup registries, List> tags, Set> expected) { + RegistryEntryLookup lookup = registries.getOrThrow(tags.getFirst().registryRef()); + + for (TagKey tag : tags) { + RegistryEntryList.Named tagEntryList = lookup.getOrThrow(tag); + Set> 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(successFmtStr, tags.getFirst().registryRef().getValue(), tags.stream() + .map(TagKey::id) + .map(Identifier::toString) + .collect(Collectors.joining(", "))); + } +} diff --git a/fabric-tag-api-v1/src/testmod/resources/data/fabric-tag-api-v1-testmod/tags/item/tag_with_snowballs_but_not_bricks.json b/fabric-tag-api-v1/src/testmod/resources/data/fabric-tag-api-v1-testmod/tags/item/tag_with_snowballs_but_not_bricks.json new file mode 100644 index 0000000000..d63ea1b61e --- /dev/null +++ b/fabric-tag-api-v1/src/testmod/resources/data/fabric-tag-api-v1-testmod/tags/item/tag_with_snowballs_but_not_bricks.json @@ -0,0 +1,10 @@ +{ + "replace": false, + "c:remove": [ + "brick" + ], + "values": [ + "snowball", + "brick" + ] +} diff --git a/fabric-tag-api-v1/src/testmod/resources/fabric.mod.json b/fabric-tag-api-v1/src/testmod/resources/fabric.mod.json index e99ea63ed9..b872c00c41 100644 --- a/fabric-tag-api-v1/src/testmod/resources/fabric.mod.json +++ b/fabric-tag-api-v1/src/testmod/resources/fabric.mod.json @@ -10,7 +10,8 @@ }, "entrypoints": { "main": [ - "net.fabricmc.fabric.test.tag.TagAliasTest" + "net.fabricmc.fabric.test.tag.TagAliasTest", + "net.fabricmc.fabric.test.tag.TagEntryRemovalTest" ], "client": [ "net.fabricmc.fabric.test.tag.client.v1.ClientTagTest"