소스 검색

Auto Crafting should work perfectly on vanilla stuff now

Unknown 5 년 전
부모
커밋
ef589543ff

+ 1 - 0
build.gradle

@@ -68,6 +68,7 @@ dependencies {
     modImplementation "io.github.prospector.modmenu:ModMenu:${modmenu_version}"
     modImplementation "io.github.prospector.modmenu:ModMenu:${modmenu_version}"
     compile "org.lwjgl:lwjgl-jemalloc:3.2.1"
     compile "org.lwjgl:lwjgl-jemalloc:3.2.1"
     compileOnly "com.google.code.findbugs:jsr305:3.0.2"
     compileOnly "com.google.code.findbugs:jsr305:3.0.2"
+    implementation 'org.jetbrains:annotations:15.0'
 }
 }
 
 
 task sourcesJar(type: Jar, dependsOn: classes) {
 task sourcesJar(type: Jar, dependsOn: classes) {

+ 3 - 0
src/main/java/me/shedaniel/rei/RoughlyEnoughItemsNetwork.java

@@ -92,6 +92,9 @@ public class RoughlyEnoughItemsNetwork implements ModInitializer {
                         }
                         }
                     } catch (IllegalStateException e) {
                     } catch (IllegalStateException e) {
                         player.sendMessage(new TranslatableText(e.getMessage()).formatted(Formatting.RED));
                         player.sendMessage(new TranslatableText(e.getMessage()).formatted(Formatting.RED));
+                    } catch (Exception e) {
+                        player.sendMessage(new TranslatableText("error.rei.internal.error",e.getMessage()).formatted(Formatting.RED));
+                        e.printStackTrace();
                     }
                     }
                 } catch (Exception e) {
                 } catch (Exception e) {
                     e.printStackTrace();
                     e.printStackTrace();

+ 13 - 4
src/main/java/me/shedaniel/rei/api/AutoTransferHandler.java

@@ -5,6 +5,8 @@
 
 
 package me.shedaniel.rei.api;
 package me.shedaniel.rei.api;
 
 
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
 import me.shedaniel.rei.client.ScreenHelper;
 import me.shedaniel.rei.client.ScreenHelper;
 import me.shedaniel.rei.gui.ContainerScreenOverlay;
 import me.shedaniel.rei.gui.ContainerScreenOverlay;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.MinecraftClient;
@@ -31,7 +33,11 @@ public interface AutoTransferHandler {
         }
         }
         
         
         static Result createFailed(String errorKey) {
         static Result createFailed(String errorKey) {
-            return new ResultImpl(errorKey);
+            return new ResultImpl(errorKey, new IntArrayList());
+        }
+        
+        static Result createFailed(String errorKey, IntList redSlots) {
+            return new ResultImpl(errorKey, redSlots);
         }
         }
         
         
         boolean isSuccessful();
         boolean isSuccessful();
@@ -68,6 +74,7 @@ public interface AutoTransferHandler {
     public final class ResultImpl implements Result {
     public final class ResultImpl implements Result {
         private boolean successful, applicable;
         private boolean successful, applicable;
         private String errorKey;
         private String errorKey;
+        private IntList integers = new IntArrayList();
         
         
         private ResultImpl() {
         private ResultImpl() {
             this.successful = true;
             this.successful = true;
@@ -79,22 +86,24 @@ public interface AutoTransferHandler {
             this.applicable = applicable;
             this.applicable = applicable;
         }
         }
         
         
-        public ResultImpl(String errorKey) {
+        public ResultImpl(String errorKey, IntList integers) {
             this.successful = false;
             this.successful = false;
             this.applicable = true;
             this.applicable = true;
             this.errorKey = errorKey;
             this.errorKey = errorKey;
+            if (integers != null)
+                this.integers = integers;
         }
         }
         
         
         @Override
         @Override
         public boolean isSuccessful() {
         public boolean isSuccessful() {
             return successful;
             return successful;
         }
         }
-    
+        
         @Override
         @Override
         public boolean isApplicable() {
         public boolean isApplicable() {
             return applicable;
             return applicable;
         }
         }
-    
+        
         @Override
         @Override
         public String getErrorKey() {
         public String getErrorKey() {
             return errorKey;
             return errorKey;

+ 7 - 4
src/main/java/me/shedaniel/rei/plugin/DefaultAutoCraftingPlugin.java

@@ -8,7 +8,10 @@ package me.shedaniel.rei.plugin;
 import me.shedaniel.rei.RoughlyEnoughItemsCore;
 import me.shedaniel.rei.RoughlyEnoughItemsCore;
 import me.shedaniel.rei.api.RecipeHelper;
 import me.shedaniel.rei.api.RecipeHelper;
 import me.shedaniel.rei.api.plugins.REIPluginV0;
 import me.shedaniel.rei.api.plugins.REIPluginV0;
-import me.shedaniel.rei.plugin.autocrafting.*;
+import me.shedaniel.rei.plugin.autocrafting.AutoBlastingBookHandler;
+import me.shedaniel.rei.plugin.autocrafting.AutoCraftingHandler;
+import me.shedaniel.rei.plugin.autocrafting.AutoFurnaceBookHandler;
+import me.shedaniel.rei.plugin.autocrafting.AutoSmokerBookHandler;
 import net.fabricmc.loader.api.SemanticVersion;
 import net.fabricmc.loader.api.SemanticVersion;
 import net.fabricmc.loader.util.version.VersionParsingException;
 import net.fabricmc.loader.util.version.VersionParsingException;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.Identifier;
@@ -32,12 +35,12 @@ public class DefaultAutoCraftingPlugin implements REIPluginV0 {
         if (!RoughlyEnoughItemsCore.getConfigManager().getConfig().isLoadingDefaultPlugin()) {
         if (!RoughlyEnoughItemsCore.getConfigManager().getConfig().isLoadingDefaultPlugin()) {
             return;
             return;
         }
         }
-        recipeHelper.registerAutoCraftingHandler(new AutoCraftingTableBookHandler());
-        recipeHelper.registerAutoCraftingHandler(new AutoInventoryBookHandler());
+        //        recipeHelper.registerAutoCraftingHandler(new AutoCraftingTableBookHandler());
+        //        recipeHelper.registerAutoCraftingHandler(new AutoInventoryBookHandler());
         recipeHelper.registerAutoCraftingHandler(new AutoFurnaceBookHandler());
         recipeHelper.registerAutoCraftingHandler(new AutoFurnaceBookHandler());
         recipeHelper.registerAutoCraftingHandler(new AutoSmokerBookHandler());
         recipeHelper.registerAutoCraftingHandler(new AutoSmokerBookHandler());
         recipeHelper.registerAutoCraftingHandler(new AutoBlastingBookHandler());
         recipeHelper.registerAutoCraftingHandler(new AutoBlastingBookHandler());
-        recipeHelper.registerAutoCraftingHandler(new AutoCraftingTableHandler());
+        recipeHelper.registerAutoCraftingHandler(new AutoCraftingHandler());
     }
     }
     
     
 }
 }

+ 1 - 1
src/main/java/me/shedaniel/rei/plugin/autocrafting/AutoCraftingTableHandler.java → src/main/java/me/shedaniel/rei/plugin/autocrafting/AutoCraftingHandler.java

@@ -26,7 +26,7 @@ import net.minecraft.util.PacketByteBuf;
 
 
 import java.util.List;
 import java.util.List;
 
 
-public class AutoCraftingTableHandler implements AutoTransferHandler {
+public class AutoCraftingHandler implements AutoTransferHandler {
     @Override
     @Override
     public Result handle(Context context) {
     public Result handle(Context context) {
         if (!(context.getRecipe() instanceof DefaultCraftingDisplay))
         if (!(context.getRecipe() instanceof DefaultCraftingDisplay))

+ 110 - 76
src/main/java/me/shedaniel/rei/server/InputSlotCrafter.java

@@ -6,23 +6,27 @@
 package me.shedaniel.rei.server;
 package me.shedaniel.rei.server;
 
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Lists;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntListIterator;
 import net.minecraft.container.CraftingContainer;
 import net.minecraft.container.CraftingContainer;
 import net.minecraft.container.CraftingTableContainer;
 import net.minecraft.container.CraftingTableContainer;
 import net.minecraft.container.PlayerContainer;
 import net.minecraft.container.PlayerContainer;
+import net.minecraft.container.Slot;
 import net.minecraft.entity.player.PlayerInventory;
 import net.minecraft.entity.player.PlayerInventory;
 import net.minecraft.inventory.Inventory;
 import net.minecraft.inventory.Inventory;
 import net.minecraft.item.ItemStack;
 import net.minecraft.item.ItemStack;
-import net.minecraft.recipe.RecipeFinder;
+import net.minecraft.recipe.Ingredient;
 import net.minecraft.server.network.ServerPlayerEntity;
 import net.minecraft.server.network.ServerPlayerEntity;
 import net.minecraft.util.DefaultedList;
 import net.minecraft.util.DefaultedList;
 
 
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
 
 
-public class InputSlotCrafter<C extends Inventory> {
+public class InputSlotCrafter<C extends Inventory> implements RecipeGridAligner<Integer> {
     
     
-    protected final RecipeFinder recipeFinder = new RecipeFinder();
     protected CraftingContainer<C> craftingContainer;
     protected CraftingContainer<C> craftingContainer;
     protected PlayerInventory inventory;
     protected PlayerInventory inventory;
     
     
@@ -35,100 +39,130 @@ public class InputSlotCrafter<C extends Inventory> {
     }
     }
     
     
     private void fillInputSlots(ServerPlayerEntity player, Map<Integer, List<ItemStack>> map, boolean hasShift) {
     private void fillInputSlots(ServerPlayerEntity player, Map<Integer, List<ItemStack>> map, boolean hasShift) {
-        /*
-         * Steps:
-         * Return the already placed items on the grid
-         * Check if the player have the enough resource to even craft one
-         * Calculate how many items the player is going to craft
-         * Move the best suited items for the player to use
-         * Send container updates to the player
-         * Profit??
-         */
         this.inventory = player.inventory;
         this.inventory = player.inventory;
         if (this.canReturnInputs() || player.isCreative()) {
         if (this.canReturnInputs() || player.isCreative()) {
             // Return the already placed items on the grid
             // Return the already placed items on the grid
             this.returnInputs();
             this.returnInputs();
             
             
-            // Check if the player have the enough resource to even craft one
-            if (!isPossibleToCraft(map)) {
+            RecipeFinder recipeFinder = new RecipeFinder();
+            recipeFinder.clear();
+            for (ItemStack stack : player.inventory.main) {
+                recipeFinder.addNormalItem(stack);
+            }
+            this.craftingContainer.populateRecipeFinder(new net.minecraft.recipe.RecipeFinder() {
+                @Override
+                public void addNormalItem(ItemStack itemStack_1) {
+                    recipeFinder.addNormalItem(itemStack_1);
+                }
+            });
+            DefaultedList<Ingredient> ingredients = DefaultedList.create();
+            map.entrySet().stream().sorted(Comparator.comparingInt(Map.Entry::getKey)).forEach(entry -> {
+                ingredients.add(Ingredient.ofStacks(entry.getValue().toArray(new ItemStack[0])));
+            });
+            if (recipeFinder.findRecipe(ingredients, (IntList) null)) {
+                this.fillInputSlots(recipeFinder, ingredients, hasShift);
+            } else {
+                this.returnInputs();
                 craftingContainer.sendContentUpdates();
                 craftingContainer.sendContentUpdates();
                 player.inventory.markDirty();
                 player.inventory.markDirty();
                 throw new NullPointerException();
                 throw new NullPointerException();
             }
             }
             
             
-            // Calculate how many items the player is going to craft
-            int amountCrafting = hasShift ? 0 : 1;
-            if (hasShift) {
-            
-            }
-            
-            // TODO: Rewrite most parts of this
-            //            map.entrySet().stream().sorted(Comparator.comparingInt(Map.Entry::getKey)).forEach(entry -> {
-            //                int id = entry.getKey().intValue();
-            //                List<ItemStack> possibleStacks = entry.getValue();
-            //                boolean done = false;
-            //                for (ItemStack possibleStack : possibleStacks) {
-            //                    int requiredCount = possibleStack.getCount();
-            //                    int invCount = 0;
-            //                    for (ItemStack stack : inventory.main) {
-            //                        if (ItemStack.areItemsEqualIgnoreDamage(possibleStack, stack))
-            //                            invCount += stack.getCount();
-            //                    }
-            //                    if (invCount >= requiredCount) {
-            //                        for (ItemStack stack : inventory.main) {
-            //                            if (ItemStack.areItemsEqualIgnoreDamage(possibleStack, stack)) {
-            //                                Slot containerSlot = craftingContainer.getSlot(id + craftingContainer.getCraftingResultSlotIndex() + 1);
-            //                                while (containerSlot.getStack().getCount() < requiredCount && !stack.isEmpty()) {
-            //                                    if (containerSlot.getStack().isEmpty()) {
-            //                                        containerSlot.setStack(new ItemStack(stack.getItem(), 1));
-            //                                    } else {
-            //                                        containerSlot.getStack().setCount(containerSlot.getStack().getCount() + 1);
-            //                                    }
-            //                                    stack.setCount(stack.getCount() - 1);
-            //                                }
-            //                                if (containerSlot.getStack().getCount() >= requiredCount)
-            //                                    break;
-            //                            }
-            //                        }
-            //                        break;
-            //                    }
-            //                }
-            //            });
-            
             craftingContainer.sendContentUpdates();
             craftingContainer.sendContentUpdates();
             player.inventory.markDirty();
             player.inventory.markDirty();
         }
         }
     }
     }
     
     
-    private boolean isPossibleToCraft(Map<Integer, List<ItemStack>> map) {
-        // Create a clone of player's inventory, and count
-        DefaultedList<ItemStack> copyMain = DefaultedList.create();
-        for (ItemStack stack : inventory.main) {
-            copyMain.add(stack.copy());
+    @Override
+    public void acceptAlignedInput(Iterator<Integer> iterator_1, int int_1, int int_2, int int_3, int int_4) {
+        Slot slot_1 = this.craftingContainer.getSlot(int_1);
+        ItemStack itemStack_1 = net.minecraft.recipe.RecipeFinder.getStackFromId((Integer) iterator_1.next());
+        if (!itemStack_1.isEmpty()) {
+            for (int int_5 = 0; int_5 < int_2; ++int_5) {
+                this.fillInputSlot(slot_1, itemStack_1);
+            }
         }
         }
-        for (Map.Entry<Integer, List<ItemStack>> entry : map.entrySet()) {
-            List<ItemStack> possibleStacks = entry.getValue();
-            boolean done = possibleStacks.isEmpty();
-            for (ItemStack possibleStack : possibleStacks) {
-                if (!done) {
-                    int invRequiredCount = possibleStack.getCount();
-                    for (ItemStack stack : copyMain) {
-                        if (ItemStack.areItemsEqualIgnoreDamage(possibleStack, stack)) {
-                            while (invRequiredCount > 0 && !stack.isEmpty()) {
-                                invRequiredCount--;
-                                stack.decrement(1);
-                            }
-                        }
+    }
+    
+    protected void fillInputSlot(Slot slot_1, ItemStack itemStack_1) {
+        int int_1 = this.inventory.method_7371(itemStack_1);
+        if (int_1 != -1) {
+            ItemStack itemStack_2 = this.inventory.getInvStack(int_1).copy();
+            if (!itemStack_2.isEmpty()) {
+                if (itemStack_2.getCount() > 1) {
+                    this.inventory.takeInvStack(int_1, 1);
+                } else {
+                    this.inventory.removeInvStack(int_1);
+                }
+                
+                itemStack_2.setCount(1);
+                if (slot_1.getStack().isEmpty()) {
+                    slot_1.setStack(itemStack_2);
+                } else {
+                    slot_1.getStack().increment(1);
+                }
+                
+            }
+        }
+    }
+    
+    protected void fillInputSlots(RecipeFinder recipeFinder, DefaultedList<Ingredient> ingredients, boolean boolean_1) {
+        //        boolean boolean_2 = this.craftingContainer.matches(recipe_1);
+        boolean boolean_2 = true;
+        int int_1 = recipeFinder.countRecipeCrafts(ingredients, (IntList) null);
+        int int_2;
+        if (boolean_2) {
+            for (int_2 = 0; int_2 < this.craftingContainer.getCraftingHeight() * this.craftingContainer.getCraftingWidth() + 1; ++int_2) {
+                if (int_2 != this.craftingContainer.getCraftingResultSlotIndex()) {
+                    ItemStack itemStack_1 = this.craftingContainer.getSlot(int_2).getStack();
+                    if (!itemStack_1.isEmpty() && Math.min(int_1, itemStack_1.getMaxCount()) < itemStack_1.getCount() + 1) {
+                        return;
                     }
                     }
-                    if (invRequiredCount <= 0) {
-                        done = true;
+                }
+            }
+        }
+        
+        int_2 = this.getAmountToFill(boolean_1, int_1, boolean_2);
+        IntList intList_1 = new IntArrayList();
+        if (recipeFinder.findRecipe(ingredients, intList_1, int_2)) {
+            int int_4 = int_2;
+            IntListIterator var8 = intList_1.iterator();
+            
+            while (var8.hasNext()) {
+                int int_5 = (Integer) var8.next();
+                int int_6 = RecipeFinder.getStackFromId(int_5).getMaxCount();
+                if (int_6 < int_4) {
+                    int_4 = int_6;
+                }
+            }
+            
+            if (recipeFinder.findRecipe(ingredients, intList_1, int_4)) {
+                this.returnInputs();
+                this.alignRecipeToGrid(this.craftingContainer.getCraftingWidth(), this.craftingContainer.getCraftingHeight(), this.craftingContainer.getCraftingResultSlotIndex(), ingredients, intList_1.iterator(), int_4);
+            }
+        }
+        
+    }
+    
+    protected int getAmountToFill(boolean boolean_1, int int_1, boolean boolean_2) {
+        int int_2 = 1;
+        if (boolean_1) {
+            int_2 = int_1;
+        } else if (boolean_2) {
+            int_2 = 64;
+            for (int int_3 = 0; int_3 < this.craftingContainer.getCraftingWidth() * this.craftingContainer.getCraftingHeight() + 1; ++int_3) {
+                if (int_3 != this.craftingContainer.getCraftingResultSlotIndex()) {
+                    ItemStack itemStack_1 = this.craftingContainer.getSlot(int_3).getStack();
+                    if (!itemStack_1.isEmpty() && int_2 > itemStack_1.getCount()) {
+                        int_2 = itemStack_1.getCount();
                     }
                     }
                 }
                 }
             }
             }
-            if (!done)
-                return false;
+            if (int_2 < 64) {
+                ++int_2;
+            }
         }
         }
-        return true;
+        return int_2;
     }
     }
     
     
     protected void returnInputs() {
     protected void returnInputs() {

+ 319 - 0
src/main/java/me/shedaniel/rei/server/RecipeFinder.java

@@ -0,0 +1,319 @@
+package me.shedaniel.rei.server;
+
+import com.google.common.collect.Lists;
+import it.unimi.dsi.fastutil.ints.*;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.recipe.Ingredient;
+import net.minecraft.util.DefaultedList;
+import net.minecraft.util.registry.Registry;
+
+import javax.annotation.Nullable;
+import java.util.BitSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class RecipeFinder {
+    public final Int2IntMap idToAmountMap = new Int2IntOpenHashMap();
+    
+    public static int getItemId(ItemStack itemStack_1) {
+        return Registry.ITEM.getRawId(itemStack_1.getItem());
+    }
+    
+    public static ItemStack getStackFromId(int int_1) {
+        return int_1 == 0 ? ItemStack.EMPTY : new ItemStack(Item.byRawId(int_1));
+    }
+    
+    public void addNormalItem(ItemStack itemStack_1) {
+        if (!itemStack_1.isDamaged() && !itemStack_1.hasEnchantments() && !itemStack_1.hasCustomName()) {
+            this.addItem(itemStack_1);
+        }
+        
+    }
+    
+    public void addItem(ItemStack itemStack_1) {
+        this.addItem(itemStack_1, 64);
+    }
+    
+    public void addItem(ItemStack itemStack_1, int int_1) {
+        if (!itemStack_1.isEmpty()) {
+            int itemId = getItemId(itemStack_1);
+            int itemCount = Math.min(int_1, itemStack_1.getCount());
+            this.addItem(itemId, itemCount);
+        }
+        
+    }
+    
+    private boolean contains(int itemId) {
+        return this.idToAmountMap.get(itemId) > 0;
+    }
+    
+    /**
+     * Takes an amount from the finder
+     *
+     * @return the amount taken
+     */
+    private int take(int itemId, int amount) {
+        int mapAmount = this.idToAmountMap.get(itemId);
+        if (mapAmount >= amount) {
+            this.idToAmountMap.put(itemId, mapAmount - amount);
+            return itemId;
+        } else {
+            return 0;
+        }
+    }
+    
+    private void addItem(int itemId, int itemCount) {
+        this.idToAmountMap.put(itemId, this.idToAmountMap.get(itemId) + itemCount);
+    }
+    
+    public boolean findRecipe(DefaultedList<Ingredient> ingredients, @Nullable IntList intList_1) {
+        return this.findRecipe(ingredients, intList_1, 1);
+    }
+    
+    public boolean findRecipe(DefaultedList<Ingredient> ingredients, @Nullable IntList intList_1, int int_1) {
+        return (new RecipeFinder.Filter(ingredients)).find(int_1, intList_1);
+    }
+    
+    public int countRecipeCrafts(DefaultedList<Ingredient> ingredients, @Nullable IntList intList_1) {
+        return this.countRecipeCrafts(ingredients, Integer.MAX_VALUE, intList_1);
+    }
+    
+    public int countRecipeCrafts(DefaultedList<Ingredient> ingredients, int int_1, @Nullable IntList intList_1) {
+        return (new RecipeFinder.Filter(ingredients)).countCrafts(int_1, intList_1);
+    }
+    
+    public void clear() {
+        this.idToAmountMap.clear();
+    }
+    
+    class Filter {
+        private final List<Ingredient> ingredients = Lists.newArrayList();
+        private final int ingredientCount;
+        private final int[] usableIngredientItemIds;
+        private final int usableIngredientSize;
+        private final BitSet bitSet;
+        private final IntList field_7557 = new IntArrayList();
+        private final DefaultedList<Ingredient> ingredientsInput;
+        
+        public Filter(DefaultedList<Ingredient> ingredientsInput) {
+            this.ingredientsInput = ingredientsInput;
+            this.ingredients.addAll(ingredientsInput.stream().collect(Collectors.toList()));
+            this.ingredients.removeIf(Ingredient::isEmpty);
+            this.ingredientCount = this.ingredients.size();
+            this.usableIngredientItemIds = this.getUsableIngredientItemIds();
+            this.usableIngredientSize = this.usableIngredientItemIds.length;
+            this.bitSet = new BitSet(this.ingredientCount + this.usableIngredientSize + this.ingredientCount + this.ingredientCount * this.usableIngredientSize);
+            
+            for (int ingredientIndex = 0; ingredientIndex < this.ingredients.size(); ++ingredientIndex) {
+                IntList possibleStacks = ((Ingredient) this.ingredients.get(ingredientIndex)).getIds();
+                
+                // Loops over usable ingredients
+                for (int usableIngredientIndex = 0; usableIngredientIndex < this.usableIngredientSize; ++usableIngredientIndex) {
+                    if (possibleStacks.contains(this.usableIngredientItemIds[usableIngredientIndex])) {
+                        this.bitSet.set(this.method_7420(true, usableIngredientIndex, ingredientIndex));
+                    }
+                }
+            }
+            
+        }
+        
+        public boolean find(int int_1, @Nullable IntList intList_1) {
+            if (int_1 <= 0) {
+                return true;
+            } else {
+                int int_2;
+                for (int_2 = 0; this.method_7423(int_1); ++int_2) {
+                    RecipeFinder.this.take(this.usableIngredientItemIds[this.field_7557.getInt(0)], int_1);
+                    int int_3 = this.field_7557.size() - 1;
+                    this.method_7421(this.field_7557.getInt(int_3));
+                    
+                    for (int int_4 = 0; int_4 < int_3; ++int_4) {
+                        this.method_7414((int_4 & 1) == 0, this.field_7557.get(int_4), this.field_7557.get(int_4 + 1));
+                    }
+                    
+                    this.field_7557.clear();
+                    this.bitSet.clear(0, this.ingredientCount + this.usableIngredientSize);
+                }
+                
+                boolean boolean_1 = int_2 == this.ingredientCount;
+                boolean boolean_2 = boolean_1 && intList_1 != null;
+                if (boolean_2) {
+                    intList_1.clear();
+                }
+                
+                this.bitSet.clear(0, this.ingredientCount + this.usableIngredientSize + this.ingredientCount);
+                int int_5 = 0;
+                List<Ingredient> list_1 = ingredientsInput.stream().collect(Collectors.toList());
+                
+                for (int int_6 = 0; int_6 < list_1.size(); ++int_6) {
+                    if (boolean_2 && ((Ingredient) list_1.get(int_6)).isEmpty()) {
+                        intList_1.add(0);
+                    } else {
+                        for (int int_7 = 0; int_7 < this.usableIngredientSize; ++int_7) {
+                            if (this.method_7425(false, int_5, int_7)) {
+                                this.method_7414(true, int_7, int_5);
+                                RecipeFinder.this.addItem(this.usableIngredientItemIds[int_7], int_1);
+                                if (boolean_2) {
+                                    intList_1.add(this.usableIngredientItemIds[int_7]);
+                                }
+                            }
+                        }
+                        
+                        ++int_5;
+                    }
+                }
+                
+                return boolean_1;
+            }
+        }
+        
+        private int[] getUsableIngredientItemIds() {
+            IntCollection intCollection_1 = new IntAVLTreeSet();
+            Iterator var2 = this.ingredients.iterator();
+            
+            while (var2.hasNext()) {
+                Ingredient ingredient_1 = (Ingredient) var2.next();
+                intCollection_1.addAll(ingredient_1.getIds());
+            }
+            
+            IntIterator intIterator_1 = intCollection_1.iterator();
+            
+            while (intIterator_1.hasNext()) {
+                if (!RecipeFinder.this.contains(intIterator_1.nextInt())) {
+                    intIterator_1.remove();
+                }
+            }
+            
+            return intCollection_1.toIntArray();
+        }
+        
+        private boolean method_7423(int int_1) {
+            int usableIngredientSize = this.usableIngredientSize;
+            
+            for (int int_3 = 0; int_3 < usableIngredientSize; ++int_3) {
+                if (RecipeFinder.this.idToAmountMap.get(this.usableIngredientItemIds[int_3]) >= int_1) {
+                    this.method_7413(false, int_3);
+                    
+                    while (!this.field_7557.isEmpty()) {
+                        int int_4 = this.field_7557.size();
+                        boolean boolean_1 = (int_4 & 1) == 1;
+                        int int_5 = this.field_7557.getInt(int_4 - 1);
+                        if (!boolean_1 && !this.method_7416(int_5)) {
+                            break;
+                        }
+                        
+                        int int_6 = boolean_1 ? this.ingredientCount : usableIngredientSize;
+                        
+                        int int_8;
+                        for (int_8 = 0; int_8 < int_6; ++int_8) {
+                            if (!this.method_7426(boolean_1, int_8) && this.method_7418(boolean_1, int_5, int_8) && this.method_7425(boolean_1, int_5, int_8)) {
+                                this.method_7413(boolean_1, int_8);
+                                break;
+                            }
+                        }
+                        
+                        int_8 = this.field_7557.size();
+                        if (int_8 == int_4) {
+                            this.field_7557.removeInt(int_8 - 1);
+                        }
+                    }
+                    
+                    if (!this.field_7557.isEmpty()) {
+                        return true;
+                    }
+                }
+            }
+            
+            return false;
+        }
+        
+        private boolean method_7416(int int_1) {
+            return this.bitSet.get(this.method_7419(int_1));
+        }
+        
+        private void method_7421(int int_1) {
+            this.bitSet.set(this.method_7419(int_1));
+        }
+        
+        private int method_7419(int int_1) {
+            return this.ingredientCount + this.usableIngredientSize + int_1;
+        }
+        
+        private boolean method_7418(boolean boolean_1, int int_1, int int_2) {
+            return this.bitSet.get(this.method_7420(boolean_1, int_1, int_2));
+        }
+        
+        private boolean method_7425(boolean boolean_1, int int_1, int int_2) {
+            return boolean_1 != this.bitSet.get(1 + this.method_7420(boolean_1, int_1, int_2));
+        }
+        
+        private void method_7414(boolean boolean_1, int int_1, int int_2) {
+            this.bitSet.flip(1 + this.method_7420(boolean_1, int_1, int_2));
+        }
+        
+        private int method_7420(boolean boolean_1, int int_1, int int_2) {
+            int int_3 = boolean_1 ? int_1 * this.ingredientCount + int_2 : int_2 * this.ingredientCount + int_1;
+            return this.ingredientCount + this.usableIngredientSize + this.ingredientCount + 2 * int_3;
+        }
+        
+        private void method_7413(boolean boolean_1, int int_1) {
+            this.bitSet.set(this.method_7424(boolean_1, int_1));
+            this.field_7557.add(int_1);
+        }
+        
+        private boolean method_7426(boolean boolean_1, int int_1) {
+            return this.bitSet.get(this.method_7424(boolean_1, int_1));
+        }
+        
+        private int method_7424(boolean boolean_1, int int_1) {
+            return (boolean_1 ? 0 : this.ingredientCount) + int_1;
+        }
+        
+        public int countCrafts(int int_1, @Nullable IntList intList_1) {
+            int int_2 = 0;
+            int int_3 = Math.min(int_1, this.method_7415()) + 1;
+            
+            while (true) {
+                while (true) {
+                    int int_4 = (int_2 + int_3) / 2;
+                    if (this.find(int_4, (IntList) null)) {
+                        if (int_3 - int_2 <= 1) {
+                            if (int_4 > 0) {
+                                this.find(int_4, intList_1);
+                            }
+                            
+                            return int_4;
+                        }
+                        
+                        int_2 = int_4;
+                    } else {
+                        int_3 = int_4;
+                    }
+                }
+            }
+        }
+        
+        private int method_7415() {
+            int int_1 = Integer.MAX_VALUE;
+            Iterator var2 = this.ingredients.iterator();
+            
+            while (var2.hasNext()) {
+                Ingredient ingredient_1 = (Ingredient) var2.next();
+                int int_2 = 0;
+                
+                int int_3;
+                for (IntListIterator var5 = ingredient_1.getIds().iterator(); var5.hasNext(); int_2 = Math.max(int_2, RecipeFinder.this.idToAmountMap.get(int_3))) {
+                    int_3 = (Integer) var5.next();
+                }
+                
+                if (int_1 > 0) {
+                    int_1 = Math.min(int_1, int_2);
+                }
+            }
+            
+            return int_1;
+        }
+    }
+}

+ 61 - 0
src/main/java/me/shedaniel/rei/server/RecipeGridAligner.java

@@ -0,0 +1,61 @@
+package me.shedaniel.rei.server;
+
+import net.minecraft.recipe.Ingredient;
+import net.minecraft.util.DefaultedList;
+import net.minecraft.util.math.MathHelper;
+
+import java.util.Iterator;
+
+public interface RecipeGridAligner<T> {
+    default void alignRecipeToGrid(int int_1, int int_2, int int_3, DefaultedList<Ingredient> recipe_1, Iterator<T> iterator_1, int int_4) {
+        int int_5 = int_1;
+        int int_6 = int_2;
+        //      if (recipe_1 instanceof ShapedRecipe) {
+        //         ShapedRecipe shapedRecipe_1 = (ShapedRecipe)recipe_1;
+        //         int_5 = shapedRecipe_1.getWidth();
+        //         int_6 = shapedRecipe_1.getHeight();
+        //      }
+        
+        int int_7 = 0;
+        
+        for (int int_8 = 0; int_8 < int_2; ++int_8) {
+            if (int_7 == int_3) {
+                ++int_7;
+            }
+            
+            boolean boolean_1 = (float) int_6 < (float) int_2 / 2.0F;
+            int int_9 = MathHelper.floor((float) int_2 / 2.0F - (float) int_6 / 2.0F);
+            if (boolean_1 && int_9 > int_8) {
+                int_7 += int_1;
+                ++int_8;
+            }
+            
+            for (int int_10 = 0; int_10 < int_1; ++int_10) {
+                if (!iterator_1.hasNext()) {
+                    return;
+                }
+                
+                boolean_1 = (float) int_5 < (float) int_1 / 2.0F;
+                int_9 = MathHelper.floor((float) int_1 / 2.0F - (float) int_5 / 2.0F);
+                int int_11 = int_5;
+                boolean boolean_2 = int_10 < int_5;
+                if (boolean_1) {
+                    int_11 = int_9 + int_5;
+                    boolean_2 = int_9 <= int_10 && int_10 < int_9 + int_5;
+                }
+                
+                if (boolean_2) {
+                    this.acceptAlignedInput(iterator_1, int_7, int_4, int_8, int_10);
+                } else if (int_11 == int_10) {
+                    int_7 += int_1 - int_10;
+                    break;
+                }
+                
+                ++int_7;
+            }
+        }
+        
+    }
+    
+    void acceptAlignedInput(Iterator<T> var1, int var2, int var3, int var4, int var5);
+}

+ 1 - 0
src/main/resources/assets/roughlyenoughitems/lang/en_us.json

@@ -44,6 +44,7 @@
   "error.rei.transfer.too_small": "Unable to move items to a 2x2 grid.",
   "error.rei.transfer.too_small": "Unable to move items to a 2x2 grid.",
   "error.rei.not.on.server": "REI is not on the server.",
   "error.rei.not.on.server": "REI is not on the server.",
   "error.rei.not.enough.materials": "Not Enough Materials.",
   "error.rei.not.enough.materials": "Not Enough Materials.",
+  "error.rei.internal.error": "Internal Error: %s",
   "rei.rei.no.slot.in.inv": "Can't find any space for item in the inventory",
   "rei.rei.no.slot.in.inv": "Can't find any space for item in the inventory",
   "text.rei.showing_craftable": "Showing Craftable",
   "text.rei.showing_craftable": "Showing Craftable",
   "text.rei.showing_all": "Showing All",
   "text.rei.showing_all": "Showing All",