aboutsummaryrefslogtreecommitdiff
path: root/default-plugin/src/main
diff options
context:
space:
mode:
authorshedaniel <daniel@shedaniel.me>2022-06-26 03:33:14 +0800
committershedaniel <daniel@shedaniel.me>2022-06-28 03:21:12 +0800
commit57a1b48efff20922a4f2cbe3561f1034da2db0fb (patch)
treedd59f5c693083a3fa709970982482246656e7597 /default-plugin/src/main
parent493334f3e4afcb83b1e8080ac4276eb14854ae14 (diff)
downloadRoughlyEnoughItems-57a1b48efff20922a4f2cbe3561f1034da2db0fb.tar.gz
RoughlyEnoughItems-57a1b48efff20922a4f2cbe3561f1034da2db0fb.tar.bz2
RoughlyEnoughItems-57a1b48efff20922a4f2cbe3561f1034da2db0fb.zip
WIP on Tag Category, add Overflow Widget
Diffstat (limited to 'default-plugin/src/main')
-rw-r--r--default-plugin/src/main/java/me/shedaniel/rei/plugin/client/DefaultClientPlugin.java18
-rw-r--r--default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/tag/DefaultTagCategory.java138
-rw-r--r--default-plugin/src/main/java/me/shedaniel/rei/plugin/common/BuiltinPlugin.java3
-rw-r--r--default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/DefaultTagDisplay.java98
-rw-r--r--default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/TagNode.java135
-rw-r--r--default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/TagNodes.java206
6 files changed, 598 insertions, 0 deletions
diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/DefaultClientPlugin.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/DefaultClientPlugin.java
index 7f09ff929..f9539827c 100644
--- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/DefaultClientPlugin.java
+++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/DefaultClientPlugin.java
@@ -54,6 +54,7 @@ import me.shedaniel.rei.plugin.client.categories.beacon.DefaultBeaconBaseCategor
import me.shedaniel.rei.plugin.client.categories.beacon.DefaultBeaconPaymentCategory;
import me.shedaniel.rei.plugin.client.categories.cooking.DefaultCookingCategory;
import me.shedaniel.rei.plugin.client.categories.crafting.DefaultCraftingCategory;
+import me.shedaniel.rei.plugin.client.categories.tag.DefaultTagCategory;
import me.shedaniel.rei.plugin.client.exclusionzones.DefaultPotionEffectExclusionZones;
import me.shedaniel.rei.plugin.client.exclusionzones.DefaultRecipeBookExclusionZones;
import me.shedaniel.rei.plugin.client.favorites.GameModeFavoriteEntry;
@@ -71,6 +72,7 @@ import me.shedaniel.rei.plugin.common.displays.cooking.DefaultSmeltingDisplay;
import me.shedaniel.rei.plugin.common.displays.cooking.DefaultSmokingDisplay;
import me.shedaniel.rei.plugin.common.displays.crafting.DefaultCraftingDisplay;
import me.shedaniel.rei.plugin.common.displays.crafting.DefaultCustomDisplay;
+import me.shedaniel.rei.plugin.common.displays.tag.DefaultTagDisplay;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.screens.inventory.*;
@@ -160,6 +162,7 @@ public class DefaultClientPlugin implements REIClientPlugin, BuiltinClientPlugin
new DefaultWaxScrapingCategory(),
new DefaultOxidizingCategory(),
new DefaultOxidationScrapingCategory(),
+ new DefaultTagCategory(),
new DefaultInformationCategory()
);
@@ -222,6 +225,17 @@ public class DefaultClientPlugin implements REIClientPlugin, BuiltinClientPlugin
registry.registerRecipeFiller(UpgradeRecipe.class, RecipeType.SMITHING, DefaultSmithingDisplay::new);
registry.registerFiller(AnvilRecipe.class, DefaultAnvilDisplay::new);
registry.registerFiller(BrewingRecipe.class, DefaultBrewingDisplay::new);
+ registry.registerFiller(TagKey.class, tagKey -> {
+ if (tagKey.isFor(Registry.ITEM_REGISTRY)) {
+ return DefaultTagDisplay.ofItems(tagKey);
+ } else if (tagKey.isFor(Registry.BLOCK_REGISTRY)) {
+ return DefaultTagDisplay.ofItems(tagKey);
+ } else if (tagKey.isFor(Registry.FLUID_REGISTRY)) {
+ return DefaultTagDisplay.ofFluids(tagKey);
+ }
+
+ return null;
+ });
for (Map.Entry<Item, Integer> entry : AbstractFurnaceBlockEntity.getFuel().entrySet()) {
registry.add(new DefaultFuelDisplay(Collections.singletonList(EntryIngredients.of(entry.getKey())), Collections.emptyList(), entry.getValue()));
}
@@ -305,6 +319,10 @@ public class DefaultClientPlugin implements REIClientPlugin, BuiltinClientPlugin
} else {
registerForgePotions(registry, this);
}
+
+ for (Registry<?> reg : Registry.REGISTRY) {
+ reg.getTags().forEach(tagPair -> registry.add(tagPair.getFirst()));
+ }
}
@ExpectPlatform
diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/tag/DefaultTagCategory.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/tag/DefaultTagCategory.java
new file mode 100644
index 000000000..c79cc9eb3
--- /dev/null
+++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/tag/DefaultTagCategory.java
@@ -0,0 +1,138 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.plugin.client.categories.tag;
+
+import me.shedaniel.math.Point;
+import me.shedaniel.math.Rectangle;
+import me.shedaniel.rei.api.client.gui.Renderer;
+import me.shedaniel.rei.api.client.gui.widgets.DelegateWidget;
+import me.shedaniel.rei.api.client.gui.widgets.Widget;
+import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds;
+import me.shedaniel.rei.api.client.gui.widgets.Widgets;
+import me.shedaniel.rei.api.client.registry.display.DisplayCategory;
+import me.shedaniel.rei.api.common.category.CategoryIdentifier;
+import me.shedaniel.rei.api.common.util.EntryStacks;
+import me.shedaniel.rei.plugin.common.BuiltinPlugin;
+import me.shedaniel.rei.plugin.common.displays.tag.DefaultTagDisplay;
+import me.shedaniel.rei.plugin.common.displays.tag.TagNode;
+import me.shedaniel.rei.plugin.common.displays.tag.TagNodes;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiComponent;
+import net.minecraft.core.Registry;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.TextComponent;
+import net.minecraft.network.chat.TranslatableComponent;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.item.Items;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultTagCategory implements DisplayCategory<DefaultTagDisplay<?, ?>> {
+ @Override
+ public CategoryIdentifier<? extends DefaultTagDisplay<?, ?>> getCategoryIdentifier() {
+ return BuiltinPlugin.TAG;
+ }
+
+ @Override
+ public Component getTitle() {
+ return new TranslatableComponent("category.rei.tag");
+ }
+
+ @Override
+ public Renderer getIcon() {
+ return EntryStacks.of(Items.NAME_TAG);
+ }
+
+ @Override
+ public List<Widget> setupDisplay(DefaultTagDisplay<?, ?> display, Rectangle bounds) {
+ List<Widget> widgets = new ArrayList<>();
+
+ widgets.add(Widgets.createRecipeBase(bounds));
+ Rectangle innerBounds = new Rectangle(bounds.x + 6 + 14, bounds.y + 6, bounds.width - 12 - 14, bounds.height - 12);
+ widgets.add(Widgets.createSlotBase(innerBounds));
+
+ Widget[] delegate = new Widget[]{Widgets.noOp()};
+ TagNode<?>[] tagNode = new TagNode[]{null};
+ Rectangle overflowBounds = new Rectangle(innerBounds.x + 1, innerBounds.y + 1, innerBounds.width - 2, innerBounds.height - 2);
+ WidgetWithBounds inner = Widgets.withBounds(new DelegateWidget(Widgets.noOp()) {
+ @Override
+ protected Widget delegate() {
+ return delegate[0];
+ }
+ }, overflowBounds);
+ widgets.add(Widgets.withTranslate(Widgets.overflowed(overflowBounds, Widgets.withBounds(Widgets.createDrawableWidget((helper, matrices, mouseX, mouseY, delta) -> {
+ new GuiComponent() {
+ {
+ fillGradient(matrices, 0, 0, 1000, 1000, 0xff3489eb, 0xffc41868);
+ for (int x = 0; x < 20; x++) {
+ for (int y = 0; y < 20; y++) {
+ Widgets.createSlot(new Point(500 - 9 * 20 + x * 18, 500 - 9 * 20 + y * 18))
+ .entry(EntryStacks.of(Registry.ITEM.byId(x + y * 10 + 1)))
+ .disableBackground()
+ .render(matrices, mouseX, mouseY, delta);
+ }
+ }
+ }
+ };
+ }), new Rectangle(0, 0, 1000, 1000))), 0, 0, 20));
+
+ TagNodes.create(display.getKey(), dataResult -> {
+ if (dataResult.error().isPresent()) {
+ delegate[0] = Widgets.concat(
+ Widgets.createLabel(new Point(innerBounds.getCenterX(), innerBounds.getCenterY() - 8), new TextComponent("Failed to resolve tags!")),
+ Widgets.createLabel(new Point(innerBounds.getCenterX(), innerBounds.getCenterY() - 8), new TextComponent(dataResult.error().get().message()))
+ );
+ } else {
+ tagNode[0] = dataResult.result().get();
+ }
+ });
+
+ widgets.add(Widgets.createButton(new Rectangle(bounds.x + 5, bounds.y + 6, 13, 13), new TextComponent(""))
+ .onRender((poseStack, button) -> {
+ button.setEnabled(tagNode[0] != null);
+ })
+ .onClick(button -> {
+ })
+ .tooltipLine(new TranslatableComponent("text.rei.expand.view")));
+ widgets.add(Widgets.createButton(new Rectangle(bounds.x + 5, bounds.getMaxY() - 6 - 13, 13, 13), new TextComponent(""))
+ .onRender((poseStack, button) -> {
+ button.setEnabled(tagNode[0] != null);
+ })
+ .onClick(button -> {
+ TagNode<?> node = tagNode[0];
+
+ if (node != null) {
+ Minecraft.getInstance().keyboardHandler.setClipboard(node.asTree());
+ }
+ })
+ .tooltipLine(new TranslatableComponent("text.rei.tag.copy.clipboard")));
+ widgets.add(Widgets.withTranslate(Widgets.createTexturedWidget(new ResourceLocation("roughlyenoughitems", "textures/gui/expand.png"),
+ new Rectangle(bounds.x + 5 + 2, bounds.y + 6 + 2, 13 - 4, 13 - 4), 0, 0, 9, 9), 0, 0, 10));
+ widgets.add(Widgets.withTranslate(Widgets.createTexturedWidget(new ResourceLocation("roughlyenoughitems", "textures/gui/clipboard.png"),
+ new Rectangle(bounds.x + 5 + 2, bounds.getMaxY() - 6 - 13 + 2, 13 - 4, 13 - 4), 0, 0, 9, 9), 0, 0.5, 10));
+
+ return widgets;
+ }
+}
diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/BuiltinPlugin.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/BuiltinPlugin.java
index cd5797473..ad3cdb29a 100644
--- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/BuiltinPlugin.java
+++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/BuiltinPlugin.java
@@ -33,6 +33,7 @@ import me.shedaniel.rei.plugin.common.displays.cooking.DefaultBlastingDisplay;
import me.shedaniel.rei.plugin.common.displays.cooking.DefaultSmeltingDisplay;
import me.shedaniel.rei.plugin.common.displays.cooking.DefaultSmokingDisplay;
import me.shedaniel.rei.plugin.common.displays.crafting.DefaultCraftingDisplay;
+import me.shedaniel.rei.plugin.common.displays.tag.DefaultTagDisplay;
import org.jetbrains.annotations.ApiStatus;
public interface BuiltinPlugin {
@@ -60,5 +61,7 @@ public interface BuiltinPlugin {
CategoryIdentifier<DefaultOxidizingDisplay> OXIDIZING = CategoryIdentifier.of("minecraft", "plugins/oxidizing");
@ApiStatus.Experimental
CategoryIdentifier<DefaultOxidationScrapingDisplay> OXIDATION_SCRAPING = CategoryIdentifier.of("minecraft", "plugins/oxidation_scraping");
+ @ApiStatus.Experimental
+ CategoryIdentifier<DefaultTagDisplay<?, ?>> TAG = CategoryIdentifier.of("minecraft", "plugins/tag");
CategoryIdentifier<DefaultInformationDisplay> INFO = CategoryIdentifier.of("roughlyenoughitems", "plugins/information");
}
diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/DefaultTagDisplay.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/DefaultTagDisplay.java
new file mode 100644
index 000000000..09c453019
--- /dev/null
+++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/DefaultTagDisplay.java
@@ -0,0 +1,98 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.plugin.common.displays.tag;
+
+import dev.architectury.fluid.FluidStack;
+import me.shedaniel.rei.api.common.category.CategoryIdentifier;
+import me.shedaniel.rei.api.common.display.Display;
+import me.shedaniel.rei.api.common.entry.EntryIngredient;
+import me.shedaniel.rei.api.common.entry.EntryStack;
+import me.shedaniel.rei.api.common.util.CollectionUtils;
+import me.shedaniel.rei.api.common.util.EntryIngredients;
+import me.shedaniel.rei.api.common.util.EntryStacks;
+import me.shedaniel.rei.plugin.common.BuiltinPlugin;
+import net.minecraft.core.Holder;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.tags.TagKey;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.ItemLike;
+import net.minecraft.world.level.material.Fluid;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+@ApiStatus.Experimental
+public class DefaultTagDisplay<S, T> implements Display {
+ private final TagKey<S> key;
+ private final Function<Holder<S>, EntryStack<T>> mapper;
+ private final List<EntryIngredient> ingredients;
+
+ public DefaultTagDisplay(TagKey<S> key, Function<Holder<S>, EntryStack<T>> mapper) {
+ this.key = key;
+ this.mapper = mapper;
+ this.ingredients = CollectionUtils.map(EntryIngredients.ofTag(key, mapper), EntryIngredient::of);
+ }
+
+ public static DefaultTagDisplay<ItemLike, ItemStack> ofItems(TagKey<ItemLike> key) {
+ return new DefaultTagDisplay<>(key, DefaultTagDisplay::extractItem);
+ }
+
+ public static DefaultTagDisplay<Fluid, FluidStack> ofFluids(TagKey<Fluid> key) {
+ return new DefaultTagDisplay<>(key, DefaultTagDisplay::extractFluid);
+ }
+
+ private static EntryStack<ItemStack> extractItem(Holder<ItemLike> holder) {
+ return EntryStacks.of(holder.value());
+ }
+
+ private static EntryStack<FluidStack> extractFluid(Holder<Fluid> holder) {
+ return EntryStacks.of(holder.value());
+ }
+
+ @Override
+ public List<EntryIngredient> getInputEntries() {
+ return this.ingredients;
+ }
+
+ @Override
+ public List<EntryIngredient> getOutputEntries() {
+ return this.ingredients;
+ }
+
+ @Override
+ public CategoryIdentifier<?> getCategoryIdentifier() {
+ return BuiltinPlugin.TAG;
+ }
+
+ @Override
+ public Optional<ResourceLocation> getDisplayLocation() {
+ return Optional.of(key.location());
+ }
+
+ public TagKey<S> getKey() {
+ return key;
+ }
+}
diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/TagNode.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/TagNode.java
new file mode 100644
index 000000000..94b5b047d
--- /dev/null
+++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/TagNode.java
@@ -0,0 +1,135 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.plugin.common.displays.tag;
+
+import net.minecraft.core.Holder;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.tags.TagKey;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+@ApiStatus.Internal
+public abstract class TagNode<T> {
+ private final List<TagNode<T>> children;
+
+ public TagNode() {
+ this.children = new ArrayList<>();
+ }
+
+ public static <T> TagNode<T> ofValue(Holder<T> value) {
+ return new ValueTagNode<>(value);
+ }
+
+ public static <T> TagNode<T> ofReference(TagKey<T> key) {
+ return new ReferenceTagNode<>(key);
+ }
+
+ public List<TagNode<T>> children() {
+ return children;
+ }
+
+ public void addChild(TagNode<T> child) {
+ children.add(child);
+ }
+
+ public void addValueChild(Holder<T> child) {
+ children.add(ofValue(child));
+ }
+
+ public void addReferenceChild(TagKey<T> child) {
+ children.add(ofReference(child));
+ }
+
+ public String asTree() {
+ StringBuilder buffer = new StringBuilder(50);
+ printTree(buffer, "", "");
+ return buffer.toString();
+ }
+
+ private void printTree(StringBuilder buffer, String prefix, String childrenPrefix) {
+ buffer.append(prefix);
+ buffer.append(asText());
+ buffer.append('\n');
+ for (Iterator<TagNode<T>> it = children.iterator(); it.hasNext(); ) {
+ TagNode<T> next = it.next();
+ if (it.hasNext()) {
+ next.printTree(buffer, childrenPrefix + "├── ", childrenPrefix + "│ ");
+ } else {
+ next.printTree(buffer, childrenPrefix + "└── ", childrenPrefix + " ");
+ }
+ }
+ }
+
+ protected abstract String asText();
+
+ @Nullable
+ public Holder<T> getValue() {
+ return null;
+ }
+
+ @Nullable
+ public TagKey<T> getReference() {
+ return null;
+ }
+
+ private static class ValueTagNode<T> extends TagNode<T> {
+ private final Holder<T> value;
+
+ public ValueTagNode(Holder<T> value) {
+ this.value = value;
+ }
+
+ @Override
+ public Holder<T> getValue() {
+ return value;
+ }
+
+ @Override
+ protected String asText() {
+ return value.unwrapKey().map(ResourceKey::location).orElse(null) + "";
+ }
+ }
+
+ private static class ReferenceTagNode<T> extends TagNode<T> {
+ private final TagKey<T> key;
+
+ public ReferenceTagNode(TagKey<T> key) {
+ this.key = key;
+ }
+
+ @Override
+ public TagKey<T> getReference() {
+ return key;
+ }
+
+ @Override
+ protected String asText() {
+ return key.location() + "";
+ }
+ }
+}
diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/TagNodes.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/TagNodes.java
new file mode 100644
index 000000000..832249cc2
--- /dev/null
+++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/tag/TagNodes.java
@@ -0,0 +1,206 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.plugin.common.displays.tag;
+
+import com.mojang.serialization.DataResult;
+import dev.architectury.event.events.client.ClientLifecycleEvent;
+import dev.architectury.networking.NetworkManager;
+import dev.architectury.networking.transformers.SplitPacketTransformer;
+import dev.architectury.utils.Env;
+import dev.architectury.utils.EnvExecutor;
+import io.netty.buffer.Unpooled;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.Minecraft;
+import net.minecraft.core.Holder;
+import net.minecraft.core.Registry;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.tags.Tag;
+import net.minecraft.tags.TagKey;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
+
+@ApiStatus.Internal
+public class TagNodes {
+ public static final ResourceLocation REQUEST_TAGS_PACKET_C2S = new ResourceLocation("roughlyenoughitems", "request_tags_c2s");
+ public static final ResourceLocation REQUEST_TAGS_PACKET_S2C = new ResourceLocation("roughlyenoughitems", "request_tags_s2c");
+
+ public static final Map<String, ResourceKey<? extends Registry<?>>> TAG_DIR_MAP = new HashMap<>();
+ public static final ThreadLocal<String> CURRENT_TAG_DIR = new ThreadLocal<>();
+ public static final Map<String, Map<Tag<?>, RawTagData>> RAW_TAG_DATA_MAP = new ConcurrentHashMap<>();
+ public static final Map<ResourceKey<? extends Registry<?>>, Map<ResourceLocation, TagData>> TAG_DATA_MAP = new HashMap<>();
+ public static Map<ResourceKey<? extends Registry<?>>, Consumer<Consumer<DataResult<Map<ResourceLocation, TagData>>>>> requestedTags = new HashMap<>();
+
+ public record RawTagData(List<ResourceLocation> otherElements, List<ResourceLocation> otherTags) {
+ }
+
+ public record TagData(IntList otherElements, List<ResourceLocation> otherTags) {
+ private static TagData fromNetwork(FriendlyByteBuf buf) {
+ int count = buf.readVarInt();
+ IntList otherElements = new IntArrayList(count + 1);
+ for (int i = 0; i < count; i++) {
+ otherElements.add(buf.readVarInt());
+ }
+ count = buf.readVarInt();
+ List<ResourceLocation> otherTags = new ArrayList<>(count + 1);
+ for (int i = 0; i < count; i++) {
+ otherTags.add(buf.readResourceLocation());
+ }
+ return new TagData(otherElements, otherTags);
+ }
+
+ private void toNetwork(FriendlyByteBuf buf) {
+ buf.writeVarInt(otherElements.size());
+ for (int integer : otherElements) {
+ buf.writeVarInt(integer);
+ }
+ buf.writeVarInt(otherTags.size());
+ for (ResourceLocation tag : otherTags) {
+ writeResourceLocation(buf, tag);
+ }
+ }
+ }
+
+ private static void writeResourceLocation(FriendlyByteBuf buf, ResourceLocation location) {
+ if (location.getNamespace().equals("minecraft")) {
+ buf.writeUtf(location.getPath());
+ } else {
+ buf.writeUtf(location.toString());
+ }
+ }
+
+ public static void init() {
+ EnvExecutor.runInEnv(Env.CLIENT, () -> Client::init);
+
+ NetworkManager.registerReceiver(NetworkManager.c2s(), REQUEST_TAGS_PACKET_C2S, Collections.singletonList(new SplitPacketTransformer()), (buf, context) -> {
+ UUID uuid = buf.readUUID();
+ ResourceKey<? extends Registry<?>> resourceKey = ResourceKey.createRegistryKey(buf.readResourceLocation());
+ FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer());
+ newBuf.writeUUID(uuid);
+ Map<ResourceLocation, TagData> dataMap = TAG_DATA_MAP.getOrDefault(resourceKey, Collections.emptyMap());
+ newBuf.writeInt(dataMap.size());
+ for (Map.Entry<ResourceLocation, TagData> entry : dataMap.entrySet()) {
+ writeResourceLocation(newBuf, entry.getKey());
+ entry.getValue().toNetwork(newBuf);
+ }
+ NetworkManager.sendToPlayer((ServerPlayer) context.getPlayer(), REQUEST_TAGS_PACKET_S2C, newBuf);
+ });
+ }
+
+ @Environment(EnvType.CLIENT)
+ public static void requestTagData(ResourceKey<? extends Registry<?>> resourceKey, Consumer<DataResult<Map<ResourceLocation, TagData>>> callback) {
+ if (Minecraft.getInstance().getSingleplayerServer() != null) {
+ callback.accept(DataResult.success(TAG_DATA_MAP.get(resourceKey)));
+ } else if (!NetworkManager.canServerReceive(REQUEST_TAGS_PACKET_C2S)) {
+ callback.accept(DataResult.error("Cannot request tags from server"));
+ } else if (requestedTags.containsKey(resourceKey)) {
+ requestedTags.get(resourceKey).accept(callback);
+ callback.accept(DataResult.success(TAG_DATA_MAP.getOrDefault(resourceKey, Collections.emptyMap())));
+ } else {
+ FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
+ UUID uuid = UUID.randomUUID();
+ buf.writeUUID(uuid);
+ buf.writeResourceLocation(resourceKey.location());
+ Client.nextUUID = uuid;
+ Client.nextResourceKey = resourceKey;
+ List<Consumer<DataResult<Map<ResourceLocation, TagData>>>> callbacks = new CopyOnWriteArrayList<>();
+ callbacks.add(callback);
+ Client.nextCallback = mapDataResult -> {
+ requestedTags.put(resourceKey, c -> c.accept(mapDataResult));
+ for (Consumer<DataResult<Map<ResourceLocation, TagData>>> consumer : callbacks) {
+ consumer.accept(mapDataResult);
+ }
+ };
+ requestedTags.put(resourceKey, callbacks::add);
+ NetworkManager.sendToServer(REQUEST_TAGS_PACKET_C2S, buf);
+ }
+ }
+
+ private static class Client {
+ public static UUID nextUUID;
+ public static ResourceKey<? extends Registry<?>> nextResourceKey;
+ public static Consumer<DataResult<Map<ResourceLocation, TagData>>> nextCallback;
+
+ private static void init() {
+ ClientLifecycleEvent.CLIENT_LEVEL_LOAD.register(world -> {
+ requestedTags.clear();
+ });
+ NetworkManager.registerReceiver(NetworkManager.s2c(), REQUEST_TAGS_PACKET_S2C, Collections.singletonList(new SplitPacketTransformer()), (buf, context) -> {
+ UUID uuid = buf.readUUID();
+ if (nextUUID.equals(uuid)) {
+ Map<ResourceLocation, TagData> map = new HashMap<>();
+ int count = buf.readInt();
+ for (int i = 0; i < count; i++) {
+ map.put(buf.readResourceLocation(), TagData.fromNetwork(buf));
+ }
+
+ TAG_DATA_MAP.put(nextResourceKey, map);
+ nextCallback.accept(DataResult.success(map));
+
+ nextUUID = null;
+ nextResourceKey = null;
+ nextCallback = null;
+ }
+ });
+ }
+ }
+
+ public static <T> void create(TagKey<T> tagKey, Consumer<DataResult<TagNode<T>>> callback) {
+ Registry<T> registry = ((Registry<Registry<T>>) Registry.REGISTRY).get((ResourceKey<Registry<T>>) tagKey.registry());
+ requestTagData(tagKey.registry(), result -> {
+ callback.accept(result.flatMap(dataMap -> resolveTag(tagKey, registry, dataMap)));
+ });
+ }
+
+ private static <T> DataResult<TagNode<T>> resolveTag(TagKey<T> tagKey, Registry<T> registry, Map<ResourceLocation, TagData> tagDataMap) {
+ TagData tagData = tagDataMap.get(tagKey.location());
+ if (tagData == null) return DataResult.error("Tag Missing: " + tagKey.location());
+
+ TagNode<T> self = TagNode.ofReference(tagKey);
+ for (int element : tagData.otherElements()) {
+ Optional<Holder<T>> holder = registry.getHolder(element);
+ if (holder.isPresent()) {
+ self.addValueChild(holder.get());
+ }
+ }
+ for (ResourceLocation childTagId : tagData.otherTags()) {
+ TagKey<T> childTagKey = TagKey.create(tagKey.registry(), childTagId);
+ if (registry.getTag(childTagKey).isPresent()) {
+ DataResult<TagNode<T>> result = resolveTag(childTagKey, registry, tagDataMap);
+ if (result.error().isPresent()) return DataResult.error(result.error().get().message());
+ self.addChild(result.result().get());
+ }
+ }
+ return DataResult.success(self);
+ }
+}