From 6a8bc6a8c34af1e3ff15fe8a802ef5ece3c417d2 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Mon, 28 Aug 2023 13:16:57 +0800 Subject: Reworked the transfer api See https://www.craft.me/s/TVL01jO3OZarPE for the documentation of the new experimental simple transfer handle --- .../me/shedaniel/rei/RoughlyEnoughItemsCore.java | 4 +- .../rei/RoughlyEnoughItemsCoreClient.java | 2 + .../shedaniel/rei/RoughlyEnoughItemsNetwork.java | 63 +++++++- .../impl/client/gui/craftable/CraftableFilter.java | 11 ++ .../client/gui/widget/AutoCraftingEvaluator.java | 16 +- .../rei/impl/client/gui/widget/EntryWidget.java | 11 +- .../client/transfer/SimpleTransferHandlerImpl.java | 152 +++++++++++++++++++ .../shedaniel/rei/impl/client/view/ViewsImpl.java | 167 ++++++++++++--------- .../rei/impl/common/transfer/InputSlotCrafter.java | 80 +++------- .../common/transfer/LegacyInputSlotCrafter.java | 113 ++++++++++++++ .../impl/common/transfer/NewInputSlotCrafter.java | 134 +++++++++++++++++ .../common/transfer/SlotAccessorRegistryImpl.java | 105 +++++++++++++ .../autocrafting/DefaultCategoryHandler.java | 19 ++- .../common/runtime/DefaultRuntimePlugin.java | 52 +++++++ 14 files changed, 789 insertions(+), 140 deletions(-) create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/transfer/SimpleTransferHandlerImpl.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/LegacyInputSlotCrafter.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/NewInputSlotCrafter.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/SlotAccessorRegistryImpl.java (limited to 'runtime/src/main') diff --git a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java index 4104d5210..732ca00cd 100644 --- a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java +++ b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java @@ -55,6 +55,7 @@ import me.shedaniel.rei.impl.common.logging.performance.PerformanceLoggerImpl; import me.shedaniel.rei.impl.common.plugins.PluginManagerImpl; import me.shedaniel.rei.impl.common.registry.RecipeManagerContextImpl; import me.shedaniel.rei.impl.common.transfer.MenuInfoRegistryImpl; +import me.shedaniel.rei.impl.common.transfer.SlotAccessorRegistryImpl; import me.shedaniel.rei.impl.init.PluginDetector; import me.shedaniel.rei.impl.init.PrimitivePlatformAdapter; import net.minecraft.resources.ResourceLocation; @@ -142,7 +143,8 @@ public class RoughlyEnoughItemsCore { Internals.attachInstanceSupplier(new PluginManagerImpl<>( REIServerPlugin.class, view -> view.then(PluginView.getInstance()), - new MenuInfoRegistryImpl()), "serverPluginManager"); + new MenuInfoRegistryImpl(), + new SlotAccessorRegistryImpl()), "serverPluginManager"); } public static void _reloadPlugins(@Nullable ReloadStage stage) { diff --git a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java index 5d5d53d8f..5874799a3 100644 --- a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java +++ b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java @@ -81,6 +81,7 @@ import me.shedaniel.rei.impl.client.search.SearchProviderImpl; import me.shedaniel.rei.impl.client.search.SearchRuntime; import me.shedaniel.rei.impl.client.search.method.InputMethodRegistryImpl; import me.shedaniel.rei.impl.client.subsets.SubsetsRegistryImpl; +import me.shedaniel.rei.impl.client.transfer.SimpleTransferHandlerImpl; import me.shedaniel.rei.impl.client.transfer.TransferHandlerRegistryImpl; import me.shedaniel.rei.impl.client.view.ViewsImpl; import me.shedaniel.rei.impl.common.InternalLogger; @@ -159,6 +160,7 @@ public class RoughlyEnoughItemsCoreClient { ClientInternals.attachInstance((Function) QueuedTooltip.TooltipEntryImpl::new, "tooltipEntryProvider"); ClientInternals.attachInstance((BiFunction, FavoriteMenuEntry>) SubMenuEntry::new, "subMenuEntry"); ClientInternals.attachInstance((BiFunction) (text, value) -> ToggleMenuEntry.of(text, value::get, value), "toggleEntry"); + ClientInternals.attachInstanceSupplier(SimpleTransferHandlerImpl.INSTANCE, "simpleTransferHandler"); ClientInternals.attachInstance((Function<@Nullable Boolean, ClickArea.Result>) successful -> new ClickArea.Result() { private List> categories = Lists.newArrayList(); private BooleanSupplier execute = () -> { diff --git a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsNetwork.java b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsNetwork.java index 64a433dbe..5e8e49475 100644 --- a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsNetwork.java +++ b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsNetwork.java @@ -28,20 +28,35 @@ import dev.architectury.networking.transformers.SplitPacketTransformer; import io.netty.buffer.Unpooled; 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.entry.InputIngredient; +import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes; +import me.shedaniel.rei.api.common.transfer.info.stack.SlotAccessor; +import me.shedaniel.rei.api.common.transfer.info.stack.SlotAccessorRegistry; import me.shedaniel.rei.impl.common.transfer.InputSlotCrafter; +import me.shedaniel.rei.impl.common.transfer.LegacyInputSlotCrafter; +import me.shedaniel.rei.impl.common.transfer.NewInputSlotCrafter; import net.minecraft.ChatFormatting; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.Mth; import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.InventoryMenu; import net.minecraft.world.inventory.RecipeBookMenu; import net.minecraft.world.item.ItemStack; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; +import java.util.Map; public class RoughlyEnoughItemsNetwork { public static final ResourceLocation DELETE_ITEMS_PACKET = new ResourceLocation("roughlyenoughitems", "delete_item"); @@ -50,6 +65,7 @@ public class RoughlyEnoughItemsNetwork { public static final ResourceLocation CREATE_ITEMS_GRAB_PACKET = new ResourceLocation("roughlyenoughitems", "create_item_grab"); public static final ResourceLocation CREATE_ITEMS_MESSAGE_PACKET = new ResourceLocation("roughlyenoughitems", "ci_msg"); public static final ResourceLocation MOVE_ITEMS_PACKET = new ResourceLocation("roughlyenoughitems", "move_items"); + public static final ResourceLocation MOVE_ITEMS_NEW_PACKET = new ResourceLocation("roughlyenoughitems", "move_items_new"); public static final ResourceLocation NOT_ENOUGH_ITEMS_PACKET = new ResourceLocation("roughlyenoughitems", "og_not_enough"); public static void onInitialize() { @@ -122,7 +138,7 @@ public class RoughlyEnoughItemsNetwork { try { boolean shift = packetByteBuf.readBoolean(); try { - InputSlotCrafter crafter = InputSlotCrafter.start(category, container, player, packetByteBuf.readAnySizeNbt(), shift); + LegacyInputSlotCrafter crafter = LegacyInputSlotCrafter.start(category, container, player, packetByteBuf.readAnySizeNbt(), shift); } catch (InputSlotCrafter.NotEnoughMaterialsException e) { if (!(container instanceof RecipeBookMenu)) { return; @@ -147,5 +163,50 @@ public class RoughlyEnoughItemsNetwork { e.printStackTrace(); } }); + NetworkManager.registerReceiver(NetworkManager.c2s(), MOVE_ITEMS_NEW_PACKET, Collections.singletonList(new SplitPacketTransformer()), (packetByteBuf, context) -> { + ServerPlayer player = (ServerPlayer) context.getPlayer(); + CategoryIdentifier category = CategoryIdentifier.of(packetByteBuf.readResourceLocation()); + AbstractContainerMenu container = player.containerMenu; + InventoryMenu playerContainer = player.inventoryMenu; + try { + boolean shift = packetByteBuf.readBoolean(); + try { + CompoundTag nbt = packetByteBuf.readAnySizeNbt(); + List> inputs = readInputs(nbt.getCompound("Inputs")); + List input = readSlots(container, player, nbt.getList("InputSlots", Tag.TAG_COMPOUND)); + List inventory = readSlots(container, player, nbt.getList("InventorySlots", Tag.TAG_COMPOUND)); + NewInputSlotCrafter crafter = new NewInputSlotCrafter<>(container, input, inventory, inputs); + crafter.fillInputSlots(player, shift); + } catch (InputSlotCrafter.NotEnoughMaterialsException e) { + if (!(container instanceof RecipeBookMenu)) { + return; + } + } catch (IllegalStateException e) { + player.sendSystemMessage(Component.translatable(e.getMessage()).withStyle(ChatFormatting.RED)); + } catch (Exception e) { + player.sendSystemMessage(Component.translatable("error.rei.internal.error", e.getMessage()).withStyle(ChatFormatting.RED)); + e.printStackTrace(); + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + private static List readSlots(AbstractContainerMenu menu, Player player, ListTag tag) { + List slots = new ArrayList<>(); + for (Tag t : tag) { + slots.add(SlotAccessorRegistry.getInstance().read(menu, player, (CompoundTag) t)); + } + return slots; + } + + private static List> readInputs(CompoundTag tag) { + List> inputs = new ArrayList<>(); + for (Map.Entry entry : tag.tags.entrySet()) { + InputIngredient> stacks = InputIngredient.of(Integer.parseInt(entry.getKey()), EntryIngredient.read((ListTag) entry.getValue())); + inputs.add(InputIngredient.withType(stacks, VanillaEntryTypes.ITEM)); + } + return inputs; } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/craftable/CraftableFilter.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/craftable/CraftableFilter.java index 56dd69632..41063cdf4 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/craftable/CraftableFilter.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/craftable/CraftableFilter.java @@ -27,12 +27,15 @@ import it.unimi.dsi.fastutil.longs.Long2LongMap; import it.unimi.dsi.fastutil.longs.Long2LongMaps; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; import me.shedaniel.rei.impl.client.ClientHelperImpl; +import net.minecraft.client.Minecraft; +import net.minecraft.world.inventory.AbstractContainerMenu; public class CraftableFilter { public static final CraftableFilter INSTANCE = new CraftableFilter(); private boolean dirty = false; private Long2LongMap invStacks = new Long2LongOpenHashMap(); private Long2LongMap containerStacks = new Long2LongOpenHashMap(); + private long menuId = -2; public void markDirty() { dirty = true; @@ -49,6 +52,14 @@ public class CraftableFilter { public void tick() { if (dirty) return; + AbstractContainerMenu menu = Minecraft.getInstance().player.containerMenu; + long currentMenuId = menu == null ? -1 : menu.containerId; + if (currentMenuId != menuId) { + menuId = currentMenuId; + markDirty(); + } + if (dirty) return; + Long2LongMap currentStacks; try { currentStacks = ClientHelperImpl.getInstance()._getInventoryItemsTypes(); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/AutoCraftingEvaluator.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/AutoCraftingEvaluator.java index ba1379b02..665420cfb 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/AutoCraftingEvaluator.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/AutoCraftingEvaluator.java @@ -32,6 +32,7 @@ import me.shedaniel.rei.api.client.registry.transfer.TransferHandler; import me.shedaniel.rei.api.client.registry.transfer.TransferHandlerRegistry; import me.shedaniel.rei.api.client.registry.transfer.TransferHandlerRenderer; import me.shedaniel.rei.api.common.display.Display; +import me.shedaniel.rei.api.common.transfer.info.MenuTransferException; import me.shedaniel.rei.api.common.util.CollectionUtils; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; @@ -41,10 +42,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.resources.ResourceLocation; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Locale; +import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Supplier; @@ -99,7 +97,15 @@ public class AutoCraftingEvaluator { for (TransferHandler transferHandler : TransferHandlerRegistry.getInstance()) { try { - TransferHandler.Result transferResult = transferHandler.handle(context); + TransferHandler.ApplicabilityResult applicabilityResult = transferHandler.checkApplicable(context); + if (!applicabilityResult.isApplicable()) continue; + TransferHandler.Result transferResult; + + if (applicabilityResult.isSuccessful()) { + transferResult = transferHandler.handle(context); + } else { + transferResult = applicabilityResult.getError(); + } if (transferResult.isBlocking() && actuallyCrafting) { if (transferResult.isReturningToScreen()) { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java index 777b44d06..f5369814c 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java @@ -54,6 +54,7 @@ import me.shedaniel.rei.api.common.category.CategoryIdentifier; import me.shedaniel.rei.api.common.display.Display; import me.shedaniel.rei.api.common.entry.EntryStack; import me.shedaniel.rei.api.common.plugins.PluginManager; +import me.shedaniel.rei.api.common.transfer.info.MenuTransferException; import me.shedaniel.rei.api.common.util.FormattingUtils; import me.shedaniel.rei.impl.client.REIRuntimeImpl; import me.shedaniel.rei.impl.client.gui.InternalTextures; @@ -613,7 +614,15 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { if (handler != null) { AbstractContainerScreen containerScreen = REIRuntime.getInstance().getPreviousContainerScreen(); TransferHandler.Context context = TransferHandler.Context.create(true, Screen.hasShiftDown() || button == 1, containerScreen, display); - TransferHandler.Result transferResult = handler.handle(context); + TransferHandler.ApplicabilityResult applicabilityResult = handler.checkApplicable(context); + if (!applicabilityResult.isApplicable()) return false; + TransferHandler.Result transferResult; + + if (applicabilityResult.isSuccessful()) { + transferResult = handler.handle(context); + } else { + transferResult = applicabilityResult.getError(); + } if (transferResult.isBlocking()) { minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/transfer/SimpleTransferHandlerImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/transfer/SimpleTransferHandlerImpl.java new file mode 100644 index 000000000..ef7d59c79 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/transfer/SimpleTransferHandlerImpl.java @@ -0,0 +1,152 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 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.impl.client.transfer; + +import dev.architectury.networking.NetworkManager; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import me.shedaniel.rei.RoughlyEnoughItemsNetwork; +import me.shedaniel.rei.api.client.ClientHelper; +import me.shedaniel.rei.api.client.registry.transfer.TransferHandler; +import me.shedaniel.rei.api.client.registry.transfer.simple.SimpleTransferHandler; +import me.shedaniel.rei.api.common.entry.InputIngredient; +import me.shedaniel.rei.api.common.transfer.RecipeFinder; +import me.shedaniel.rei.api.common.transfer.info.stack.SlotAccessor; +import me.shedaniel.rei.api.common.transfer.info.stack.SlotAccessorRegistry; +import me.shedaniel.rei.api.common.util.CollectionUtils; +import me.shedaniel.rei.api.common.util.EntryIngredients; +import me.shedaniel.rei.impl.ClientInternals; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.gui.screens.recipebook.RecipeUpdateListener; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public enum SimpleTransferHandlerImpl implements ClientInternals.SimpleTransferHandler { + INSTANCE; + + @Override + public TransferHandler.Result handle(TransferHandler.Context context, SimpleTransferHandler.MissingInputRenderer missingInputRenderer, List> inputs, Iterable inputSlots, Iterable inventorySlots) { + AbstractContainerScreen containerScreen = context.getContainerScreen(); + List> missing = SimpleTransferHandlerImpl.hasItemsIndexed(context, inventorySlots, inputs); + + if (!missing.isEmpty()) { + IntSet missingIndices = new IntLinkedOpenHashSet(missing.size()); + for (InputIngredient ingredient : missing) { + missingIndices.add(ingredient.getDisplayIndex()); + } + return TransferHandler.Result.createFailed(Component.translatable("error.rei.not.enough.materials")) + .renderer((matrices, mouseX, mouseY, delta, widgets, bounds, d) -> { + missingInputRenderer.renderMissingInput(context, inputs, missing, missingIndices, matrices, mouseX, mouseY, delta, widgets, bounds); + }) + .tooltipMissing(CollectionUtils.map(missing, ingredient -> EntryIngredients.ofItemStacks(ingredient.get()))); + } + + if (!ClientHelper.getInstance().canUseMovePackets()) { + return TransferHandler.Result.createFailed(Component.translatable("error.rei.not.on.server")); + } + + if (!context.isActuallyCrafting()) { + return TransferHandler.Result.createSuccessful(); + } + + context.getMinecraft().setScreen(containerScreen); + if (containerScreen instanceof RecipeUpdateListener listener) { + listener.getRecipeBookComponent().ghostRecipe.clear(); + } + + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeResourceLocation(context.getDisplay().getCategoryIdentifier().getIdentifier()); + buf.writeBoolean(context.isStackedCrafting()); + + buf.writeNbt(save(context, inputs, inputSlots, inventorySlots)); + NetworkManager.sendToServer(RoughlyEnoughItemsNetwork.MOVE_ITEMS_NEW_PACKET, buf); + return TransferHandler.Result.createSuccessful(); + } + + private CompoundTag save(TransferHandler.Context context, List> inputs, Iterable inputSlots, Iterable inventorySlots) { + CompoundTag tag = new CompoundTag(); + tag.put("Inputs", saveInputs(inputs)); + tag.put("InventorySlots", saveSlots(context,inventorySlots)); + tag.put("InputSlots", saveSlots(context, inputSlots)); + return tag; + } + + private Tag saveSlots(TransferHandler.Context context, Iterable slots) { + ListTag tag = new ListTag(); + + for (SlotAccessor slot : slots) { + tag.add(SlotAccessorRegistry.getInstance().save(context.getMenu(), context.getMinecraft().player, slot)); + } + + return tag; + } + + private Tag saveInputs(List> inputs) { + CompoundTag tag = new CompoundTag(); + + for (InputIngredient input : inputs) { + tag.put(String.valueOf(input.getIndex()), EntryIngredients.ofItemStacks(input.get()).saveIngredient()); + } + + return tag; + } + + public static List> hasItemsIndexed(TransferHandler.Context context, Iterable inventorySlots, List> inputs) { + // Create a clone of player's inventory, and count + RecipeFinder recipeFinder = new RecipeFinder(); + for (SlotAccessor slot : inventorySlots) { + recipeFinder.addNormalItem(slot.getItemStack()); + } + List> missing = new ArrayList<>(); + for (InputIngredient possibleStacks : inputs) { + boolean done = possibleStacks.get().isEmpty(); + for (ItemStack possibleStack : possibleStacks.get()) { + if (!done) { + int invRequiredCount = possibleStack.getCount(); + int key = RecipeFinder.getItemId(possibleStack); + while (invRequiredCount > 0 && recipeFinder.contains(key)) { + invRequiredCount--; + recipeFinder.take(key, 1); + } + if (invRequiredCount <= 0) { + done = true; + break; + } + } + } + if (!done) { + missing.add(possibleStacks); + } + } + return missing; + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java index 36c84a396..54fe8ba5f 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java @@ -30,11 +30,14 @@ import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.longs.Long2LongMap; import it.unimi.dsi.fastutil.longs.Long2LongMaps; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; +import me.shedaniel.rei.api.client.REIRuntime; import me.shedaniel.rei.api.client.config.ConfigObject; import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; import me.shedaniel.rei.api.client.registry.display.DisplayCategory; import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; import me.shedaniel.rei.api.client.registry.display.DynamicDisplayGenerator; +import me.shedaniel.rei.api.client.registry.transfer.TransferHandler; +import me.shedaniel.rei.api.client.registry.transfer.TransferHandlerRegistry; import me.shedaniel.rei.api.client.view.ViewSearchBuilder; import me.shedaniel.rei.api.client.view.Views; import me.shedaniel.rei.api.common.category.CategoryIdentifier; @@ -60,9 +63,11 @@ import me.shedaniel.rei.impl.client.registry.display.DisplaysHolder; import me.shedaniel.rei.impl.client.util.CrashReportUtils; import me.shedaniel.rei.impl.common.InternalLogger; import me.shedaniel.rei.impl.display.DisplaySpec; +import me.shedaniel.rei.plugin.autocrafting.DefaultCategoryHandler; import net.minecraft.CrashReport; import net.minecraft.ReportedException; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.player.LocalPlayer; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.inventory.AbstractContainerMenu; @@ -429,86 +434,32 @@ public class ViewsImpl implements Views { AbstractContainerMenu menu = Minecraft.getInstance().player.containerMenu; Set> craftables = new HashSet<>(); - boolean onlyIncludeHasMenu = false; + AbstractContainerScreen containerScreen = REIRuntime.getInstance().getPreviousContainerScreen(); for (Map.Entry, List> entry : DisplayRegistry.getInstance().getAll().entrySet()) { - class InfoSerializationContext implements MenuSerializationContext { - @Override - public AbstractContainerMenu getMenu() { - return menu; - } - - @Override - public LocalPlayer getPlayerEntity() { - return Minecraft.getInstance().player; - } - - @Override - public CategoryIdentifier getCategoryIdentifier() { - return (CategoryIdentifier) entry.getKey(); - } - } - - InfoSerializationContext context = new InfoSerializationContext(); + MenuSerializationContext context = createLegacyContext(menu, entry.getKey().cast()); List displays = entry.getValue(); for (Display display : displays) { try { - MenuInfo info = menu != null ? - MenuInfoRegistry.getInstance().getClient(display, context, menu) - : null; + TransferHandler.Context transferContext = TransferHandler.Context.create(false, false, containerScreen, display); + boolean successful = matchesLegacyRequirements(menu, context, display); - if (onlyIncludeHasMenu && info == null) { - continue; - } - - Iterable inputSlots = info != null ? Iterables.concat(info.getInputSlots(context.withDisplay(display)), info.getInventorySlots(context.withDisplay(display))) : Collections.emptySet(); - int slotsCraftable = 0; - boolean containsNonEmpty = false; - List requiredInput = display.getRequiredEntries(); - Long2LongMap invCount = new Long2LongOpenHashMap(info == null ? CraftableFilter.INSTANCE.getInvStacks() : Long2LongMaps.EMPTY_MAP); - for (SlotAccessor inputSlot : inputSlots) { - ItemStack stack = inputSlot.getItemStack(); - - EntryDefinition definition; - try { - definition = VanillaEntryTypes.ITEM.getDefinition(); - } catch (NullPointerException e) { - break; - } - - if (!stack.isEmpty()) { - long hash = definition.hash(null, stack, ComparisonContext.FUZZY); - long newCount = invCount.get(hash) + Math.max(0, stack.getCount()); - invCount.put(hash, newCount); - } - } - for (EntryIngredient slot : requiredInput) { - if (slot.isEmpty()) { - slotsCraftable++; - continue; - } - for (EntryStack slotPossible : slot) { - if (slotPossible.getType() != VanillaEntryTypes.ITEM) continue; - ItemStack stack = slotPossible.castValue(); - long hashFuzzy = EntryStacks.hashFuzzy(slotPossible); - long availableAmount = invCount.get(hashFuzzy); - if (availableAmount >= stack.getCount()) { - invCount.put(hashFuzzy, availableAmount - stack.getCount()); - containsNonEmpty = true; - slotsCraftable++; - break; + if (!successful) { + for (TransferHandler handler : TransferHandlerRegistry.getInstance()) { + if (!(handler instanceof DefaultCategoryHandler) && handler.checkApplicable(transferContext).isSuccessful()) { + if (handler.handle(transferContext).isSuccessful()) { + successful = true; + break; + } } } } - if (slotsCraftable == display.getRequiredEntries().size() && containsNonEmpty) { - if (info != null && !onlyIncludeHasMenu) { - onlyIncludeHasMenu = true; - craftables.clear(); - } - - display.getOutputEntries().stream().flatMap(Collection::stream).collect(Collectors.toCollection(() -> craftables)); - } + + if (!successful) continue; + + display.getOutputEntries().stream().flatMap(Collection::stream) + .collect(Collectors.toCollection(() -> craftables)); } catch (Throwable t) { InternalLogger.getInstance().warn("Error while checking if display is craftable", t); } @@ -517,6 +468,82 @@ public class ViewsImpl implements Views { return craftables; } + private static MenuSerializationContext createLegacyContext(AbstractContainerMenu menu, CategoryIdentifier categoryIdentifier) { + class InfoSerializationContext implements MenuSerializationContext { + @Override + public AbstractContainerMenu getMenu() { + return menu; + } + + @Override + public LocalPlayer getPlayerEntity() { + return Minecraft.getInstance().player; + } + + @Override + public CategoryIdentifier getCategoryIdentifier() { + return categoryIdentifier; + } + } + + return new InfoSerializationContext(); + } + + private static boolean matchesLegacyRequirements(AbstractContainerMenu menu, + MenuSerializationContext context, + Display display) { + MenuInfo info = menu != null ? + MenuInfoRegistry.getInstance().getClient(display, context, menu) + : null; + + if (menu != null && info == null) { + return false; + } + + Iterable inputSlots = info != null ? Iterables.concat(info.getInputSlots(context.withDisplay(display)), info.getInventorySlots(context.withDisplay(display))) : Collections.emptySet(); + int slotsCraftable = 0; + boolean containsNonEmpty = false; + List requiredInput = display.getRequiredEntries(); + Long2LongMap invCount = new Long2LongOpenHashMap(info == null ? CraftableFilter.INSTANCE.getInvStacks() : Long2LongMaps.EMPTY_MAP); + for (SlotAccessor inputSlot : inputSlots) { + ItemStack stack = inputSlot.getItemStack(); + + EntryDefinition definition; + try { + definition = VanillaEntryTypes.ITEM.getDefinition(); + } catch (NullPointerException e) { + break; + } + + if (!stack.isEmpty()) { + long hash = definition.hash(null, stack, ComparisonContext.FUZZY); + long newCount = invCount.get(hash) + Math.max(0, stack.getCount()); + invCount.put(hash, newCount); + } + } + + for (EntryIngredient slot : requiredInput) { + if (slot.isEmpty()) { + slotsCraftable++; + continue; + } + for (EntryStack slotPossible : slot) { + if (slotPossible.getType() != VanillaEntryTypes.ITEM) continue; + ItemStack stack = slotPossible.castValue(); + long hashFuzzy = EntryStacks.hashFuzzy(slotPossible); + long availableAmount = invCount.get(hashFuzzy); + if (availableAmount >= stack.getCount()) { + invCount.put(hashFuzzy, availableAmount - stack.getCount()); + containsNonEmpty = true; + slotsCraftable++; + break; + } + } + } + + return slotsCraftable == display.getRequiredEntries().size() && containsNonEmpty; + } + private static boolean isStackWorkStationOfCategory(CategoryRegistry.CategoryConfiguration category, EntryStack stack) { for (EntryIngredient ingredient : category.getWorkstations()) { if (EntryIngredients.testFuzzy(ingredient, stack)) { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/InputSlotCrafter.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/InputSlotCrafter.java index 143c81783..7cbf4ebcd 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/InputSlotCrafter.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/InputSlotCrafter.java @@ -25,16 +25,11 @@ package me.shedaniel.rei.impl.common.transfer; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; -import me.shedaniel.rei.api.common.category.CategoryIdentifier; -import me.shedaniel.rei.api.common.display.Display; +import me.shedaniel.rei.api.common.entry.InputIngredient; import me.shedaniel.rei.api.common.transfer.RecipeFinder; -import me.shedaniel.rei.api.common.transfer.info.MenuInfo; -import me.shedaniel.rei.api.common.transfer.info.MenuInfoContext; -import me.shedaniel.rei.api.common.transfer.info.MenuInfoRegistry; import me.shedaniel.rei.api.common.transfer.info.stack.SlotAccessor; import me.shedaniel.rei.api.common.util.CollectionUtils; import net.minecraft.core.NonNullList; -import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.Container; import net.minecraft.world.inventory.AbstractContainerMenu; @@ -44,59 +39,53 @@ import org.jetbrains.annotations.Nullable; import java.util.Iterator; import java.util.List; -import java.util.Objects; -public class InputSlotCrafter implements MenuInfoContext { - protected CategoryIdentifier category; +public abstract class InputSlotCrafter { protected T container; - protected MenuInfo menuInfo; private Iterable inputStacks; private Iterable inventoryStacks; - private ServerPlayer player; + protected ServerPlayer player; - private InputSlotCrafter(CategoryIdentifier category, T container) { - this.category = category; + protected InputSlotCrafter(T container) { this.container = container; } - public void setMenuInfo(MenuInfo menuInfo) { - this.menuInfo = menuInfo; - } - - public static InputSlotCrafter start(CategoryIdentifier category, T menu, ServerPlayer player, CompoundTag display, boolean hasShift) { - InputSlotCrafter crafter = new InputSlotCrafter<>(category, menu); - MenuInfo menuInfo = Objects.requireNonNull(MenuInfoRegistry.getInstance().get(category, menu, crafter, display), "Container Info does not exist on the server!"); - crafter.setMenuInfo(menuInfo); - crafter.fillInputSlots(player, hasShift); - return crafter; - } - - private void fillInputSlots(ServerPlayer player, boolean hasShift) { + public void fillInputSlots(ServerPlayer player, boolean hasShift) { this.player = player; - this.inventoryStacks = this.menuInfo.getInventorySlots(this); - this.inputStacks = this.menuInfo.getInputSlots(this); + this.inventoryStacks = this.getInventorySlots(); + this.inputStacks = this.getInputSlots(); // Return the already placed items on the grid this.cleanInputs(); RecipeFinder recipeFinder = new RecipeFinder(); - this.menuInfo.getRecipeFinderPopulator().populate(this, recipeFinder); + this.populateRecipeFinder(recipeFinder); NonNullList ingredients = NonNullList.create(); - for (List itemStacks : this.menuInfo.getInputs(this, true)) { - ingredients.add(CollectionUtils.toIngredient(itemStacks)); + for (InputIngredient itemStacks : this.getInputs()) { + ingredients.add(CollectionUtils.toIngredient(itemStacks.get())); } if (recipeFinder.findRecipe(ingredients, null)) { this.fillInputSlots(recipeFinder, ingredients, hasShift); } else { this.cleanInputs(); - this.menuInfo.markDirty(this); + this.markDirty(); throw new NotEnoughMaterialsException(); } - this.menuInfo.markDirty(this); + this.markDirty(); } + protected abstract Iterable getInputSlots(); + + protected abstract Iterable getInventorySlots(); + + protected abstract List> getInputs(); + + protected abstract void populateRecipeFinder(RecipeFinder recipeFinder); + + protected abstract void markDirty(); + public void alignRecipeToGrid(Iterable inputStacks, Iterator recipeItemIds, int craftsAmount) { for (SlotAccessor inputStack : inputStacks) { if (!recipeItemIds.hasNext()) { @@ -155,9 +144,7 @@ public class InputSlotCrafter getCategoryIdentifier() { - return category; + public static class NotEnoughMaterialsException extends RuntimeException { } - - public static class NotEnoughMaterialsException extends RuntimeException {} } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/LegacyInputSlotCrafter.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/LegacyInputSlotCrafter.java new file mode 100644 index 000000000..d05256111 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/LegacyInputSlotCrafter.java @@ -0,0 +1,113 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 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.impl.common.transfer; + +import me.shedaniel.rei.api.common.category.CategoryIdentifier; +import me.shedaniel.rei.api.common.display.Display; +import me.shedaniel.rei.api.common.entry.InputIngredient; +import me.shedaniel.rei.api.common.transfer.RecipeFinder; +import me.shedaniel.rei.api.common.transfer.info.MenuInfo; +import me.shedaniel.rei.api.common.transfer.info.MenuInfoContext; +import me.shedaniel.rei.api.common.transfer.info.MenuInfoRegistry; +import me.shedaniel.rei.api.common.transfer.info.stack.SlotAccessor; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; + +import java.util.List; +import java.util.Objects; + +public class LegacyInputSlotCrafter extends InputSlotCrafter implements MenuInfoContext { + protected CategoryIdentifier category; + protected MenuInfo menuInfo; + + protected LegacyInputSlotCrafter(CategoryIdentifier category, T container) { + super(container); + this.category = category; + } + + public void setMenuInfo(MenuInfo menuInfo) { + this.menuInfo = menuInfo; + } + + public static LegacyInputSlotCrafter start(CategoryIdentifier category, T menu, ServerPlayer player, CompoundTag display, boolean hasShift) { + LegacyInputSlotCrafter crafter = new LegacyInputSlotCrafter<>(category, menu); + MenuInfo menuInfo = Objects.requireNonNull(MenuInfoRegistry.getInstance().get(category, menu, crafter, display), "Container Info does not exist on the server!"); + crafter.setMenuInfo(menuInfo); + crafter.fillInputSlots(player, hasShift); + return crafter; + } + + @Override + protected Iterable getInputSlots() { + return this.menuInfo.getInputSlots(this); + } + + @Override + protected Iterable getInventorySlots() { + return this.menuInfo.getInventorySlots(this); + } + + @Override + protected List> getInputs() { + return this.menuInfo.getInputsIndexed(this, true); + } + + @Override + protected void populateRecipeFinder(RecipeFinder recipeFinder) { + this.menuInfo.getRecipeFinderPopulator().populate(this, recipeFinder); + } + + @Override + protected void markDirty() { + this.menuInfo.markDirty(this); + } + + @Override + protected void cleanInputs() { + this.menuInfo.getInputCleanHandler().clean(this); + } + + @Override + public T getMenu() { + return container; + } + + @Override + public ServerPlayer getPlayerEntity() { + return player; + } + + @Override + public D getDisplay() { + return menuInfo.getDisplay(); + } + + @Override + public CategoryIdentifier getCategoryIdentifier() { + return category; + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/NewInputSlotCrafter.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/NewInputSlotCrafter.java new file mode 100644 index 000000000..a2d35f24d --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/NewInputSlotCrafter.java @@ -0,0 +1,134 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 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.impl.common.transfer; + +import me.shedaniel.rei.api.common.entry.InputIngredient; +import me.shedaniel.rei.api.common.transfer.RecipeFinder; +import me.shedaniel.rei.api.common.transfer.info.stack.SlotAccessor; +import net.minecraft.world.Container; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; + +import java.util.List; +import java.util.function.Predicate; + +public class NewInputSlotCrafter extends InputSlotCrafter { + protected final List inputSlots; + protected final List inventorySlots; + protected final List> inputs; + + public NewInputSlotCrafter(T container, List inputSlots, List inventorySlots, List> inputs) { + super(container); + this.inputSlots = inputSlots; + this.inventorySlots = inventorySlots; + this.inputs = inputs; + } + + @Override + protected Iterable getInputSlots() { + return this.inputSlots; + } + + @Override + protected Iterable getInventorySlots() { + return this.inventorySlots; + } + + @Override + protected List> getInputs() { + return this.inputs; + } + + @Override + protected void populateRecipeFinder(RecipeFinder recipeFinder) { + for (SlotAccessor slot : getInventorySlots()) { + recipeFinder.addNormalItem(slot.getItemStack()); + } + } + + @Override + protected void markDirty() { + player.getInventory().setChanged(); + container.sendAllDataToRemote(); + } + + @Override + protected void cleanInputs() { + for (SlotAccessor slot : getInputSlots()) { + ItemStack stackToReturn = slot.getItemStack(); + if (!stackToReturn.isEmpty()) { + for (; !(stackToReturn = slot.getItemStack()).isEmpty(); slot.takeStack(1)) { + ItemStack stackToInsert = stackToReturn.copy(); + stackToInsert.setCount(1); + + if (!getDumpHandler().test(stackToInsert)) { + throw new IllegalStateException("rei.rei.no.slot.in.inv"); + } + } + } + } + } + + private Predicate getDumpHandler() { + return (stackToDump) -> { + Iterable inventoryStacks = getInventorySlots(); + SlotAccessor occupiedSlotWithRoomForStack = getOccupiedSlotWithRoomForStack(stackToDump, inventoryStacks); + SlotAccessor emptySlot = getEmptySlot(inventoryStacks); + + SlotAccessor nextSlot = occupiedSlotWithRoomForStack == null ? emptySlot : occupiedSlotWithRoomForStack; + if (nextSlot == null) { + return false; + } + + ItemStack stack = stackToDump.copy(); + stack.setCount(nextSlot.getItemStack().getCount() + stack.getCount()); + nextSlot.setItemStack(stack); + return true; + }; + } + + static SlotAccessor getOccupiedSlotWithRoomForStack(ItemStack stack, Iterable inventoryStacks) { + for (SlotAccessor inventoryStack : inventoryStacks) { + if (canStackAddMore(inventoryStack.getItemStack(), stack)) { + return inventoryStack; + } + } + + return null; + } + + static SlotAccessor getEmptySlot(Iterable inventoryStacks) { + for (SlotAccessor inventoryStack : inventoryStacks) { + if (inventoryStack.getItemStack().isEmpty()) { + return inventoryStack; + } + } + + return null; + } + + static boolean canStackAddMore(ItemStack existingStack, ItemStack stack) { + return !existingStack.isEmpty() && ItemStack.isSameItemSameTags(existingStack, stack) && existingStack.isStackable() && existingStack.getCount() + stack.getCount() <= existingStack.getMaxStackSize(); + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/SlotAccessorRegistryImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/SlotAccessorRegistryImpl.java new file mode 100644 index 000000000..9d9654870 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/SlotAccessorRegistryImpl.java @@ -0,0 +1,105 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 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.impl.common.transfer; + +import me.shedaniel.rei.api.common.plugins.REIServerPlugin; +import me.shedaniel.rei.api.common.transfer.info.stack.SlotAccessor; +import me.shedaniel.rei.api.common.transfer.info.stack.SlotAccessorRegistry; +import me.shedaniel.rei.impl.common.InternalLogger; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; + +public class SlotAccessorRegistryImpl implements SlotAccessorRegistry { + private final Map map = new HashMap<>(); + + @Override + public void startReload() { + this.map.clear(); + } + + @Override + public void endReload() { + InternalLogger.getInstance().debug("Registered %d slot accessor serializers", map.size()); + } + + @Override + public void acceptPlugin(REIServerPlugin plugin) { + plugin.registerSlotAccessors(this); + } + + @Override + public void register(ResourceLocation id, Predicate accessorPredicate, Serializer serializer) { + this.map.put(id, new Serializer() { + @Override + public SlotAccessor read(AbstractContainerMenu menu, Player player, CompoundTag tag) { + return serializer.read(menu, player, tag); + } + + @Override + @Nullable + public CompoundTag save(AbstractContainerMenu menu, Player player, SlotAccessor accessor) { + if (!accessorPredicate.test(accessor)) { + return null; + } + return serializer.save(menu, player, accessor); + } + }); + InternalLogger.getInstance().debug("Added slot accessor serializer: %s [%s]", serializer, id); + } + + @Override + @Nullable + public Serializer get(ResourceLocation id) { + return this.map.get(id); + } + + @Override + public CompoundTag save(AbstractContainerMenu menu, Player player, SlotAccessor accessor) { + for (Map.Entry entry : map.entrySet()) { + CompoundTag tag = entry.getValue().save(menu, player, accessor); + if (tag != null) { + tag.putString("id", entry.getKey().toString()); + return tag; + } + } + return null; + } + + @Override + public SlotAccessor read(AbstractContainerMenu menu, Player player, CompoundTag tag) { + String id = tag.getString("id"); + Serializer serializer = map.get(new ResourceLocation(id)); + if (serializer == null) { + throw new NullPointerException("No serializer found for " + id); + } + return serializer.read(menu, player, tag); + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultCategoryHandler.java b/runtime/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultCategoryHandler.java index 6924b9a28..426e19f57 100644 --- a/runtime/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultCategoryHandler.java +++ b/runtime/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultCategoryHandler.java @@ -55,22 +55,29 @@ import net.minecraft.world.item.ItemStack; import java.util.ArrayList; import java.util.List; +import java.util.Optional; @Environment(EnvType.CLIENT) public class DefaultCategoryHandler implements TransferHandler { + @Override + public ApplicabilityResult checkApplicable(Context context) { + Display display = context.getDisplay(); + AbstractContainerScreen containerScreen = context.getContainerScreen(); + if (containerScreen == null) return ApplicabilityResult.createNotApplicable(); + AbstractContainerMenu menu = context.getMenu(); + MenuInfoContext menuInfoContext = ofContext(menu, display); + MenuInfo menuInfo = MenuInfoRegistry.getInstance().getClient(display, menuInfoContext, menu); + if (menuInfo == null) return ApplicabilityResult.createNotApplicable(); + return ApplicabilityResult.createApplicable(); + } + @Override public Result handle(Context context) { Display display = context.getDisplay(); AbstractContainerScreen containerScreen = context.getContainerScreen(); - if (containerScreen == null) { - return Result.createNotApplicable(); - } AbstractContainerMenu menu = context.getMenu(); MenuInfoContext menuInfoContext = ofContext(menu, display); MenuInfo menuInfo = MenuInfoRegistry.getInstance().getClient(display, menuInfoContext, menu); - if (menuInfo == null) { - return Result.createNotApplicable(); - } try { menuInfo.validate(menuInfoContext); } catch (MenuTransferException e) { diff --git a/runtime/src/main/java/me/shedaniel/rei/plugin/common/runtime/DefaultRuntimePlugin.java b/runtime/src/main/java/me/shedaniel/rei/plugin/common/runtime/DefaultRuntimePlugin.java index 1fd0126d5..ca1e44846 100644 --- a/runtime/src/main/java/me/shedaniel/rei/plugin/common/runtime/DefaultRuntimePlugin.java +++ b/runtime/src/main/java/me/shedaniel/rei/plugin/common/runtime/DefaultRuntimePlugin.java @@ -30,10 +30,18 @@ import me.shedaniel.rei.api.common.entry.type.EntryTypeRegistry; import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes; import me.shedaniel.rei.api.common.fluid.FluidSupportProvider; import me.shedaniel.rei.api.common.plugins.REIServerPlugin; +import me.shedaniel.rei.api.common.transfer.info.stack.PlayerInventorySlotAccessor; +import me.shedaniel.rei.api.common.transfer.info.stack.SlotAccessor; +import me.shedaniel.rei.api.common.transfer.info.stack.SlotAccessorRegistry; +import me.shedaniel.rei.api.common.transfer.info.stack.VanillaSlotAccessor; import me.shedaniel.rei.plugin.client.entry.FluidEntryDefinition; import me.shedaniel.rei.plugin.client.entry.ItemEntryDefinition; +import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; import java.util.Optional; import java.util.stream.Stream; @@ -55,4 +63,48 @@ public class DefaultRuntimePlugin implements REIServerPlugin { return CompoundEventResult.interruptTrue(stream.get()); }); } + + @Override + public void registerSlotAccessors(SlotAccessorRegistry registry) { + registry.register(new ResourceLocation("roughlyenoughitems", "vanilla"), + slotAccessor -> slotAccessor instanceof VanillaSlotAccessor, + new SlotAccessorRegistry.Serializer() { + @Override + public SlotAccessor read(AbstractContainerMenu menu, Player player, CompoundTag tag) { + int slot = tag.getInt("Slot"); + return new VanillaSlotAccessor(menu.slots.get(slot)); + } + + @Override + @Nullable + public CompoundTag save(AbstractContainerMenu menu, Player player, SlotAccessor accessor) { + if (!(accessor instanceof VanillaSlotAccessor)) { + throw new IllegalArgumentException("Cannot save non-vanilla slot accessor!"); + } + CompoundTag tag = new CompoundTag(); + tag.putInt("Slot", ((VanillaSlotAccessor) accessor).getSlot().index); + return tag; + } + }); + registry.register(new ResourceLocation("roughlyenoughitems", "player"), + slotAccessor -> slotAccessor instanceof PlayerInventorySlotAccessor, + new SlotAccessorRegistry.Serializer() { + @Override + public SlotAccessor read(AbstractContainerMenu menu, Player player, CompoundTag tag) { + int slot = tag.getInt("Slot"); + return new PlayerInventorySlotAccessor(player, slot); + } + + @Override + @Nullable + public CompoundTag save(AbstractContainerMenu menu, Player player, SlotAccessor accessor) { + if (!(accessor instanceof PlayerInventorySlotAccessor)) { + throw new IllegalArgumentException("Cannot save non-player slot accessor!"); + } + CompoundTag tag = new CompoundTag(); + tag.putInt("Slot", ((PlayerInventorySlotAccessor) accessor).getIndex()); + return tag; + } + }); + } } -- cgit