diff options
| author | shedaniel <daniel@shedaniel.me> | 2022-06-26 03:33:14 +0800 |
|---|---|---|
| committer | shedaniel <daniel@shedaniel.me> | 2022-06-28 03:21:12 +0800 |
| commit | 57a1b48efff20922a4f2cbe3561f1034da2db0fb (patch) | |
| tree | dd59f5c693083a3fa709970982482246656e7597 /default-plugin/src/main/java/me/shedaniel/rei/plugin/common | |
| parent | 493334f3e4afcb83b1e8080ac4276eb14854ae14 (diff) | |
| download | RoughlyEnoughItems-57a1b48efff20922a4f2cbe3561f1034da2db0fb.tar.gz RoughlyEnoughItems-57a1b48efff20922a4f2cbe3561f1034da2db0fb.tar.bz2 RoughlyEnoughItems-57a1b48efff20922a4f2cbe3561f1034da2db0fb.zip | |
WIP on Tag Category, add Overflow Widget
Diffstat (limited to 'default-plugin/src/main/java/me/shedaniel/rei/plugin/common')
4 files changed, 442 insertions, 0 deletions
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); + } +} |
