aboutsummaryrefslogtreecommitdiff
path: root/runtime-engine/menu-info/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'runtime-engine/menu-info/src/main/java')
-rw-r--r--runtime-engine/menu-info/src/main/java/me/shedaniel/rei/impl/common/transfer/InputSlotCrafter.java198
-rw-r--r--runtime-engine/menu-info/src/main/java/me/shedaniel/rei/impl/common/transfer/MenuInfoRegistryImpl.java151
-rw-r--r--runtime-engine/menu-info/src/main/java/me/shedaniel/rei/impl/common/transfer/TransferNetworkModule.java104
-rw-r--r--runtime-engine/menu-info/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultCategoryHandler.java180
-rw-r--r--runtime-engine/menu-info/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultClientTransferCategoryPlugin.java39
5 files changed, 672 insertions, 0 deletions
diff --git a/runtime-engine/menu-info/src/main/java/me/shedaniel/rei/impl/common/transfer/InputSlotCrafter.java b/runtime-engine/menu-info/src/main/java/me/shedaniel/rei/impl/common/transfer/InputSlotCrafter.java
new file mode 100644
index 000000000..e8a252389
--- /dev/null
+++ b/runtime-engine/menu-info/src/main/java/me/shedaniel/rei/impl/common/transfer/InputSlotCrafter.java
@@ -0,0 +1,198 @@
+/*
+ * 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.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.inventory.AbstractContainerMenu;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.crafting.Ingredient;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Iterator;
+import java.util.Objects;
+
+public class InputSlotCrafter<T extends AbstractContainerMenu, D extends Display> implements MenuInfoContext<T, ServerPlayer, D> {
+ protected CategoryIdentifier<D> category;
+ protected T container;
+ protected MenuInfo<T, D> menuInfo;
+ private Iterable<SlotAccessor> inputStacks;
+ private Iterable<SlotAccessor> inventoryStacks;
+ private ServerPlayer player;
+
+ private InputSlotCrafter(CategoryIdentifier<D> category, T container) {
+ this.category = category;
+ this.container = container;
+ }
+
+ public void setMenuInfo(MenuInfo<T, D> menuInfo) {
+ this.menuInfo = menuInfo;
+ }
+
+ public static <T extends AbstractContainerMenu, D extends Display> InputSlotCrafter<T, D> start(CategoryIdentifier<D> category, T menu, ServerPlayer player, CompoundTag display, boolean hasShift) {
+ InputSlotCrafter<T, D> crafter = new InputSlotCrafter<>(category, menu);
+ MenuInfo<T, D> 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) {
+ this.player = player;
+ this.inventoryStacks = this.menuInfo.getInventorySlots(this);
+ this.inputStacks = this.menuInfo.getInputSlots(this);
+
+ // Return the already placed items on the grid
+ this.cleanInputs();
+
+ RecipeFinder recipeFinder = new RecipeFinder();
+ this.menuInfo.getRecipeFinderPopulator().populate(this, recipeFinder);
+ NonNullList<Ingredient> ingredients = NonNullList.create();
+ for (InputIngredient<ItemStack> itemStacks : this.menuInfo.getInputsIndexed(this, true)) {
+ ingredients.add(CollectionUtils.toIngredient(itemStacks.get()));
+ }
+
+ if (recipeFinder.findRecipe(ingredients, null)) {
+ this.fillInputSlots(recipeFinder, ingredients, hasShift);
+ } else {
+ this.cleanInputs();
+ this.menuInfo.markDirty(this);
+ throw new NotEnoughMaterialsException();
+ }
+
+ this.menuInfo.markDirty(this);
+ }
+
+ public void alignRecipeToGrid(Iterable<SlotAccessor> inputStacks, Iterator<Integer> recipeItemIds, int craftsAmount) {
+ for (SlotAccessor inputStack : inputStacks) {
+ if (!recipeItemIds.hasNext()) {
+ return;
+ }
+
+ this.acceptAlignedInput(recipeItemIds.next(), inputStack, craftsAmount);
+ }
+ }
+
+ public void acceptAlignedInput(Integer recipeItemId, SlotAccessor inputStack, int craftsAmount) {
+ ItemStack toBeTakenStack = RecipeFinder.getStackFromId(recipeItemId);
+ if (!toBeTakenStack.isEmpty()) {
+ for (int i = 0; i < craftsAmount; ++i) {
+ this.fillInputSlot(inputStack, toBeTakenStack);
+ }
+ }
+ }
+
+ protected void fillInputSlot(SlotAccessor slot, ItemStack toBeTakenStack) {
+ SlotAccessor takenSlot = this.takeInventoryStack(toBeTakenStack);
+ if (takenSlot != null) {
+ ItemStack takenStack = takenSlot.getItemStack().copy();
+ if (!takenStack.isEmpty()) {
+ if (takenStack.getCount() > 1) {
+ takenSlot.takeStack(1);
+ } else {
+ takenSlot.setItemStack(ItemStack.EMPTY);
+ }
+
+ takenStack.setCount(1);
+ if (slot.getItemStack().isEmpty()) {
+ slot.setItemStack(takenStack);
+ } else {
+ slot.getItemStack().grow(1);
+ }
+ }
+ }
+ }
+
+ protected void fillInputSlots(RecipeFinder recipeFinder, NonNullList<Ingredient> ingredients, boolean hasShift) {
+ int recipeCrafts = recipeFinder.countRecipeCrafts(ingredients, null);
+ int amountToFill = hasShift ? recipeCrafts : 1;
+ IntList recipeItemIds = new IntArrayList();
+ if (recipeFinder.findRecipe(ingredients, recipeItemIds, amountToFill)) {
+ int finalCraftsAmount = amountToFill;
+
+ for (int itemId : recipeItemIds) {
+ finalCraftsAmount = Math.min(finalCraftsAmount, RecipeFinder.getStackFromId(itemId).getMaxStackSize());
+ }
+
+ if (recipeFinder.findRecipe(ingredients, recipeItemIds, finalCraftsAmount)) {
+ this.cleanInputs();
+ this.alignRecipeToGrid(inputStacks, recipeItemIds.iterator(), finalCraftsAmount);
+ }
+ }
+ }
+
+ protected void cleanInputs() {
+ this.menuInfo.getInputCleanHandler().clean(this);
+ }
+
+ @Nullable
+ public SlotAccessor takeInventoryStack(ItemStack itemStack) {
+ for (SlotAccessor inventoryStack : inventoryStacks) {
+ ItemStack itemStack1 = inventoryStack.getItemStack();
+ if (!itemStack1.isEmpty() && areItemsEqual(itemStack, itemStack1) && !itemStack1.isDamaged() && !itemStack1.isEnchanted() && !itemStack1.hasCustomHoverName()) {
+ return inventoryStack;
+ }
+ }
+
+ return null;
+ }
+
+ private static boolean areItemsEqual(ItemStack stack1, ItemStack stack2) {
+ return stack1.getItem() == stack2.getItem() && ItemStack.tagMatches(stack1, stack2);
+ }
+
+ @Override
+ public T getMenu() {
+ return container;
+ }
+
+ @Override
+ public ServerPlayer getPlayerEntity() {
+ return player;
+ }
+
+ @Override
+ public D getDisplay() {
+ return menuInfo.getDisplay();
+ }
+
+ @Override
+ public CategoryIdentifier<D> getCategoryIdentifier() {
+ return category;
+ }
+
+ public static class NotEnoughMaterialsException extends RuntimeException {}
+}
diff --git a/runtime-engine/menu-info/src/main/java/me/shedaniel/rei/impl/common/transfer/MenuInfoRegistryImpl.java b/runtime-engine/menu-info/src/main/java/me/shedaniel/rei/impl/common/transfer/MenuInfoRegistryImpl.java
new file mode 100644
index 000000000..875ce19c3
--- /dev/null
+++ b/runtime-engine/menu-info/src/main/java/me/shedaniel/rei/impl/common/transfer/MenuInfoRegistryImpl.java
@@ -0,0 +1,151 @@
+/*
+ * 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.impl.common.transfer;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import me.shedaniel.rei.api.common.category.CategoryIdentifier;
+import me.shedaniel.rei.api.common.display.Display;
+import me.shedaniel.rei.api.common.plugins.REIServerPlugin;
+import me.shedaniel.rei.api.common.transfer.info.MenuInfo;
+import me.shedaniel.rei.api.common.transfer.info.MenuInfoProvider;
+import me.shedaniel.rei.api.common.transfer.info.MenuInfoRegistry;
+import me.shedaniel.rei.api.common.transfer.info.MenuSerializationContext;
+import me.shedaniel.rei.impl.common.InternalLogger;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.inventory.AbstractContainerMenu;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class MenuInfoRegistryImpl implements MenuInfoRegistry {
+ private final Map<CategoryIdentifier<?>, Map<Class<? extends AbstractContainerMenu>, List<MenuInfoProvider<?, ?>>>> map = Maps.newLinkedHashMap();
+ private final Map<Predicate<CategoryIdentifier<?>>, List<MenuInfoProvider<?, ?>>> mapGeneric = Maps.newLinkedHashMap();
+
+ @Override
+ public <C extends AbstractContainerMenu, D extends Display> void register(CategoryIdentifier<D> category, Class<C> menuClass, MenuInfoProvider<C, D> menuInfo) {
+ map.computeIfAbsent(category, id -> Maps.newLinkedHashMap())
+ .computeIfAbsent(menuClass, c -> Lists.newArrayList())
+ .add(menuInfo);
+ InternalLogger.getInstance().debug("Added menu info for %s [%s]: %s", menuClass, category, menuInfo);
+ }
+
+ @Override
+ public <D extends Display> void registerGeneric(Predicate<CategoryIdentifier<?>> categoryPredicate, MenuInfoProvider<?, D> menuInfo) {
+ mapGeneric.computeIfAbsent(new Predicate<>() {
+ @Override
+ public boolean test(CategoryIdentifier<?> categoryIdentifier) {
+ return categoryPredicate.test(categoryIdentifier);
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+ }, id -> Lists.newArrayList()).add(menuInfo);
+ InternalLogger.getInstance().debug("Added generic menu info for: %s", menuInfo);
+ }
+
+ @Override
+ @Nullable
+ @Environment(EnvType.CLIENT)
+ public <C extends AbstractContainerMenu, D extends Display> MenuInfo<C, D> getClient(D display, MenuSerializationContext<C, ?, D> context, C menu) {
+ return getInternal((CategoryIdentifier<D>) display.getCategoryIdentifier(), (Class<C>) menu.getClass(), provider -> provider.provideClient(display, context, menu));
+ }
+
+ @Override
+ @Nullable
+ public <C extends AbstractContainerMenu, D extends Display> MenuInfo<C, D> get(CategoryIdentifier<D> category, C menu, MenuSerializationContext<C, ?, D> context, CompoundTag tag) {
+ return getInternal(category, (Class<C>) menu.getClass(), provider -> provider.provide(category, menu, context, tag));
+ }
+
+ private <C extends AbstractContainerMenu, D extends Display> MenuInfo<C, D> getInternal(CategoryIdentifier<D> category, Class<C> menuClass, Function<MenuInfoProvider<C, D>, Optional<MenuInfo<C, D>>> function) {
+ Map<Class<? extends AbstractContainerMenu>, List<MenuInfoProvider<?, ?>>> infoMap = map.get(category);
+ if (infoMap != null && !infoMap.isEmpty()) {
+ if (infoMap.containsKey(menuClass)) {
+ for (MenuInfoProvider<?, ?> provider : infoMap.get(menuClass)) {
+ Optional<MenuInfo<C, D>> info = function.apply((MenuInfoProvider<C, D>) provider);
+ if (info.isPresent()) {
+ return info.get();
+ }
+ }
+ }
+ for (Map.Entry<Class<? extends AbstractContainerMenu>, List<MenuInfoProvider<?, ?>>> entry : infoMap.entrySet()) {
+ if (entry.getKey().isAssignableFrom(menuClass)) {
+ for (MenuInfoProvider<?, ?> provider : entry.getValue()) {
+ Optional<MenuInfo<C, D>> info = function.apply((MenuInfoProvider<C, D>) provider);
+ if (info.isPresent()) {
+ return info.get();
+ }
+ }
+ }
+ }
+ }
+
+ for (Map.Entry<Predicate<CategoryIdentifier<?>>, List<MenuInfoProvider<?, ?>>> entry : mapGeneric.entrySet()) {
+ if (entry.getKey().test(category) && !entry.getValue().isEmpty()) {
+ List<MenuInfoProvider<?, ?>> infoList = entry.getValue();
+ if (!infoList.isEmpty()) {
+ Optional<MenuInfo<C, D>> info = function.apply((MenuInfoProvider<C, D>) infoList.get(0));
+ if (info.isPresent()) {
+ return info.get();
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public int infoSize() {
+ return map.size() + mapGeneric.size();
+ }
+
+ @Override
+ public void startReload() {
+ map.clear();
+ mapGeneric.clear();
+ }
+
+ @Override
+ public void endReload() {
+ InternalLogger.getInstance().debug("Registered %d menu infos", infoSize());
+ }
+
+ @Override
+ public void acceptPlugin(REIServerPlugin plugin) {
+ plugin.registerMenuInfo(this);
+ }
+}
diff --git a/runtime-engine/menu-info/src/main/java/me/shedaniel/rei/impl/common/transfer/TransferNetworkModule.java b/runtime-engine/menu-info/src/main/java/me/shedaniel/rei/impl/common/transfer/TransferNetworkModule.java
new file mode 100644
index 000000000..fe7cde2f7
--- /dev/null
+++ b/runtime-engine/menu-info/src/main/java/me/shedaniel/rei/impl/common/transfer/TransferNetworkModule.java
@@ -0,0 +1,104 @@
+/*
+ * 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.impl.common.transfer;
+
+import dev.architectury.networking.NetworkManager;
+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.networking.NetworkModule;
+import me.shedaniel.rei.api.common.networking.NetworkModuleKey;
+import net.minecraft.ChatFormatting;
+import net.minecraft.Util;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.chat.TranslatableComponent;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.inventory.AbstractContainerMenu;
+import net.minecraft.world.inventory.InventoryMenu;
+import net.minecraft.world.inventory.RecipeBookMenu;
+
+import java.util.Collections;
+
+public class TransferNetworkModule implements NetworkModule<NetworkModule.TransferData> {
+ public static final ResourceLocation ID = new ResourceLocation("roughlyenoughitems", "move_items");
+
+ @Override
+ public NetworkModuleKey<NetworkModule.TransferData> getKey() {
+ return NetworkModule.TRANSFER;
+ }
+
+ @Override
+ public boolean canUse(Object target) {
+ return NetworkManager.canServerReceive(TransferNetworkModule.ID);
+ }
+
+ @Override
+ public void onInitialize() {
+ NetworkManager.registerReceiver(NetworkManager.c2s(), ID, Collections.singletonList(new SplitPacketTransformer()), (packetByteBuf, context) -> {
+ ServerPlayer player = (ServerPlayer) context.getPlayer();
+ CategoryIdentifier<Display> category = CategoryIdentifier.of(packetByteBuf.readResourceLocation());
+ AbstractContainerMenu container = player.containerMenu;
+ InventoryMenu playerContainer = player.inventoryMenu;
+ try {
+ boolean shift = packetByteBuf.readBoolean();
+ try {
+ InputSlotCrafter<AbstractContainerMenu, Display> crafter = InputSlotCrafter.start(category, container, player, packetByteBuf.readAnySizeNbt(), shift);
+ } catch (InputSlotCrafter.NotEnoughMaterialsException e) {
+ if (!(container instanceof RecipeBookMenu)) {
+ return;
+ }
+ // TODO Implement Ghost Recipes
+ /*FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
+ buf.writeInt(input.size());
+ for (List<ItemStack> stacks : input) {
+ buf.writeInt(stacks.size());
+ for (ItemStack stack : stacks) {
+ buf.writeItem(stack);
+ }
+ }
+ NetworkManager.sendToPlayer(player, NOT_ENOUGH_ITEMS_PACKET, buf);*/
+ } catch (IllegalStateException e) {
+ player.sendMessage(new TranslatableComponent(e.getMessage()).withStyle(ChatFormatting.RED), Util.NIL_UUID);
+ } catch (Exception e) {
+ player.sendMessage(new TranslatableComponent("error.rei.internal.error", e.getMessage()).withStyle(ChatFormatting.RED), Util.NIL_UUID);
+ e.printStackTrace();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
+ @Override
+ public void send(Object target, TransferData data) {
+ FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
+ buf.writeResourceLocation(data.categoryIdentifier().getIdentifier());
+ buf.writeBoolean(data.stacked());
+
+ buf.writeNbt(data.displayTag());
+ NetworkManager.sendToServer(TransferNetworkModule.ID, buf);
+ }
+}
diff --git a/runtime-engine/menu-info/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultCategoryHandler.java b/runtime-engine/menu-info/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultCategoryHandler.java
new file mode 100644
index 000000000..14f685906
--- /dev/null
+++ b/runtime-engine/menu-info/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultCategoryHandler.java
@@ -0,0 +1,180 @@
+/*
+ * 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.autocrafting;
+
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntSet;
+import me.shedaniel.rei.api.client.registry.transfer.TransferHandler;
+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.networking.NetworkModule;
+import me.shedaniel.rei.api.common.networking.NetworkingHelper;
+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.MenuTransferException;
+import me.shedaniel.rei.api.common.util.CollectionUtils;
+import me.shedaniel.rei.api.common.util.EntryIngredients;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
+import net.minecraft.client.gui.screens.recipebook.RecipeUpdateListener;
+import net.minecraft.network.chat.TranslatableComponent;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.inventory.AbstractContainerMenu;
+import net.minecraft.world.item.ItemStack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Environment(EnvType.CLIENT)
+public class DefaultCategoryHandler implements TransferHandler {
+ @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<AbstractContainerMenu, Player, Display> menuInfoContext = ofContext(menu, display);
+ MenuInfo<AbstractContainerMenu, Display> menuInfo = MenuInfoRegistry.getInstance().getClient(display, menuInfoContext, menu);
+ if (menuInfo == null) {
+ return Result.createNotApplicable();
+ }
+ try {
+ menuInfo.validate(menuInfoContext);
+ } catch (MenuTransferException e) {
+ if (e.isApplicable()) {
+ return Result.createFailed(e.getError());
+ } else {
+ return Result.createNotApplicable();
+ }
+ }
+ List<InputIngredient<ItemStack>> input = menuInfo.getInputsIndexed(menuInfoContext, false);
+ List<InputIngredient<ItemStack>> missing = hasItemsIndexed(menuInfoContext, menu, menuInfo, display, input);
+ if (!missing.isEmpty()) {
+ IntList missingIndices = new IntArrayList(missing.size());
+ for (InputIngredient<ItemStack> ingredient : missing) {
+ missingIndices.add(ingredient.getIndex());
+ }
+ IntSet missingIndicesSet = new IntLinkedOpenHashSet(missingIndices);
+ List<List<ItemStack>> oldInputs = CollectionUtils.map(input, InputIngredient::get);
+ return Result.createFailed(new TranslatableComponent("error.rei.not.enough.materials"))
+ .renderer((matrices, mouseX, mouseY, delta, widgets, bounds, d) -> {
+ menuInfo.renderMissingInput(menuInfoContext, oldInputs, missingIndices, matrices, mouseX, mouseY, delta, widgets, bounds);
+ menuInfo.renderMissingInput(menuInfoContext, input, missing, missingIndicesSet, matrices, mouseX, mouseY, delta, widgets, bounds);
+ })
+ .tooltipMissing(CollectionUtils.map(missing, ingredient -> EntryIngredients.ofItemStacks(ingredient.get())));
+ }
+ if (!NetworkingHelper.getInstance().canUse(NetworkModule.TRANSFER)) {
+ return Result.createFailed(new TranslatableComponent("error.rei.not.on.server"));
+ }
+ if (!context.isActuallyCrafting()) {
+ return Result.createSuccessful();
+ }
+
+ context.getMinecraft().setScreen(containerScreen);
+ if (containerScreen instanceof RecipeUpdateListener listener) {
+ listener.getRecipeBookComponent().ghostRecipe.clear();
+ }
+ NetworkingHelper.getInstance().sendToServer(NetworkModule.TRANSFER,
+ new NetworkModule.TransferData(display.getCategoryIdentifier(),
+ context.isStackedCrafting(),
+ menuInfo.save(menuInfoContext, display)));
+ return Result.createSuccessful();
+ }
+
+ @Override
+ public double getPriority() {
+ return -10;
+ }
+
+ private static MenuInfoContext<AbstractContainerMenu, Player, Display> ofContext(AbstractContainerMenu menu, Display display) {
+ return new MenuInfoContext<AbstractContainerMenu, Player, Display>() {
+ @Override
+ public AbstractContainerMenu getMenu() {
+ return menu;
+ }
+
+ @Override
+ public Player getPlayerEntity() {
+ return Minecraft.getInstance().player;
+ }
+
+ @Override
+ public CategoryIdentifier<Display> getCategoryIdentifier() {
+ return (CategoryIdentifier<Display>) display.getCategoryIdentifier();
+ }
+
+ @Override
+ public Display getDisplay() {
+ return display;
+ }
+ };
+ }
+
+ public IntList hasItems(MenuInfoContext<AbstractContainerMenu, Player, Display> menuInfoContext, AbstractContainerMenu menu, MenuInfo<AbstractContainerMenu, Display> info, Display display, List<List<ItemStack>> inputs) {
+ List<InputIngredient<ItemStack>> missing = hasItemsIndexed(menuInfoContext, menu, info, display,
+ CollectionUtils.mapIndexed(inputs, InputIngredient::of));
+ IntList ids = new IntArrayList(missing.size());
+ for (InputIngredient<ItemStack> ingredient : missing) {
+ ids.add(ingredient.getIndex());
+ }
+ return ids;
+ }
+
+ public List<InputIngredient<ItemStack>> hasItemsIndexed(MenuInfoContext<AbstractContainerMenu, Player, Display> menuInfoContext, AbstractContainerMenu menu, MenuInfo<AbstractContainerMenu, Display> info, Display display, List<InputIngredient<ItemStack>> inputs) {
+ // Create a clone of player's inventory, and count
+ RecipeFinder recipeFinder = new RecipeFinder();
+ info.getRecipeFinderPopulator().populate(menuInfoContext, recipeFinder);
+ List<InputIngredient<ItemStack>> missing = new ArrayList<>();
+ for (InputIngredient<ItemStack> 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-engine/menu-info/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultClientTransferCategoryPlugin.java b/runtime-engine/menu-info/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultClientTransferCategoryPlugin.java
new file mode 100644
index 000000000..ac2f97861
--- /dev/null
+++ b/runtime-engine/menu-info/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultClientTransferCategoryPlugin.java
@@ -0,0 +1,39 @@
+/*
+ * 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.autocrafting;
+
+import me.shedaniel.rei.api.client.plugins.REIClientPlugin;
+import me.shedaniel.rei.api.client.registry.transfer.TransferHandlerRegistry;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import org.jetbrains.annotations.ApiStatus;
+
+@Environment(EnvType.CLIENT)
+@ApiStatus.Internal
+public class DefaultClientTransferCategoryPlugin implements REIClientPlugin {
+ @Override
+ public void registerTransferHandlers(TransferHandlerRegistry registry) {
+ registry.register(new DefaultCategoryHandler());
+ }
+}