diff options
| author | shedaniel <daniel@shedaniel.me> | 2022-06-26 03:33:14 +0800 |
|---|---|---|
| committer | shedaniel <daniel@shedaniel.me> | 2022-06-26 03:33:14 +0800 |
| commit | 81b82416abb2a94c12638fdbb08df2ac0f83254f (patch) | |
| tree | 31379d04269c77a764d127685621a7cc6ac7136f /default-plugin/src/main | |
| parent | 685dde03d71bbf18442564575e11a8247f401c0d (diff) | |
| download | RoughlyEnoughItems-81b82416abb2a94c12638fdbb08df2ac0f83254f.tar.gz RoughlyEnoughItems-81b82416abb2a94c12638fdbb08df2ac0f83254f.tar.bz2 RoughlyEnoughItems-81b82416abb2a94c12638fdbb08df2ac0f83254f.zip | |
WIP on Tag Category, add Overflow Widget
Diffstat (limited to 'default-plugin/src/main')
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 5e514af3d..7f1a2926d 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); + } +} |
