From 24e6f7c57d7e7c07a9ed4f12141afc55ddeec8bc Mon Sep 17 00:00:00 2001 From: sbancuz Date: Mon, 13 Jul 2026 22:08:43 +0200 Subject: [PATCH 1/7] Allow proper representation of recipes with multiple results --- .../java/codechicken/nei/FavoriteRecipes.java | 5 ++-- .../java/codechicken/nei/PresetsList.java | 6 ++--- .../codechicken/nei/filter/RecipeFilter.java | 4 +--- .../nei/recipe/IRecipeHandler.java | 9 +++++++ .../nei/recipe/NEIRecipeWidget.java | 5 ++-- .../nei/recipe/ProfilerRecipeHandler.java | 5 ++++ .../nei/recipe/TemplateRecipeHandler.java | 24 ++++++++++++++++++- 7 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/main/java/codechicken/nei/FavoriteRecipes.java b/src/main/java/codechicken/nei/FavoriteRecipes.java index f0271d3ba..f1d709fc4 100644 --- a/src/main/java/codechicken/nei/FavoriteRecipes.java +++ b/src/main/java/codechicken/nei/FavoriteRecipes.java @@ -8,7 +8,6 @@ import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -129,8 +128,8 @@ public void execute() { } protected List getOutputs(IRecipeHandler handler, int recipeIndex) { - final PositionedStack pStackResult = handler.getResultStack(recipeIndex); - return pStackResult != null ? Collections.singletonList(pStackResult) : handler.getOtherStacks(recipeIndex); + final List pStackResults = handler.getResultStacks(recipeIndex); + return pStackResults != null ? pStackResults : handler.getOtherStacks(recipeIndex); } }; diff --git a/src/main/java/codechicken/nei/PresetsList.java b/src/main/java/codechicken/nei/PresetsList.java index c18d0895a..043be9960 100644 --- a/src/main/java/codechicken/nei/PresetsList.java +++ b/src/main/java/codechicken/nei/PresetsList.java @@ -89,9 +89,9 @@ public boolean matches(IRecipeHandler handler, int recipeIndex) { return false; } - final PositionedStack result = handler.getResultStack(recipeIndex); + final List results = handler.getResultStacks(recipeIndex); - if (result != null && matchPositionedStack(result)) { + if (!results.isEmpty() && matchPositionedStack(results, true)) { return true; } @@ -101,7 +101,7 @@ public boolean matches(IRecipeHandler handler, int recipeIndex) { return true; } - return result == null && others.isEmpty(); + return results.isEmpty() && others.isEmpty(); } private boolean matchPositionedStack(List items, boolean dir) { diff --git a/src/main/java/codechicken/nei/filter/RecipeFilter.java b/src/main/java/codechicken/nei/filter/RecipeFilter.java index 5e7af833b..9c0592756 100644 --- a/src/main/java/codechicken/nei/filter/RecipeFilter.java +++ b/src/main/java/codechicken/nei/filter/RecipeFilter.java @@ -48,9 +48,7 @@ && matchPositionedStack(handler.getIngredientStacks(recipeIndex), this.anyMatch) } if (this.context == FilterContext.ANY || this.context == FilterContext.OUTPUT) { - final PositionedStack result = handler.getResultStack(recipeIndex); - - if (result != null && matchPositionedStack(result) == this.anyMatch) { + if (matchPositionedStack(handler.getResultStacks(recipeIndex), this.anyMatch)) { return this.anyMatch; } diff --git a/src/main/java/codechicken/nei/recipe/IRecipeHandler.java b/src/main/java/codechicken/nei/recipe/IRecipeHandler.java index 0afcacd31..f4af421b2 100644 --- a/src/main/java/codechicken/nei/recipe/IRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/IRecipeHandler.java @@ -85,12 +85,21 @@ default int getRecipeHeight(int recipe) { List getOtherStacks(int recipe); /** + * Maintain this for backwards compatibility * * @param recipe The recipe index to get the result for. * @return The recipe result {@link PositionedStack} relative to the top left corner of your recipe drawing space. */ PositionedStack getResultStack(int recipe); + /** + * + * @param recipe The recipe index to get the result for. + * @return A list of the result {@link PositionedStack}s relative to the top left corner of your recipe drawing + * space. + */ + List getResultStacks(int recipe); + /** * A tick function called for updating progress bars and cycling damage items. */ diff --git a/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java b/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java index ae2d216de..10309246f 100644 --- a/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java +++ b/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java @@ -656,8 +656,9 @@ protected List getInputs() { } protected List getOutputs() { - final PositionedStack pStackResult = this.handlerRef.handler.getResultStack(this.handlerRef.recipeIndex); - return pStackResult != null ? Arrays.asList(pStackResult) + final List pStackResults = this.handlerRef.handler + .getResultStacks(this.handlerRef.recipeIndex); + return pStackResults != null ? pStackResults : this.handlerRef.handler.getOtherStacks(this.handlerRef.recipeIndex); } diff --git a/src/main/java/codechicken/nei/recipe/ProfilerRecipeHandler.java b/src/main/java/codechicken/nei/recipe/ProfilerRecipeHandler.java index 182e39b63..f2e5bcac3 100644 --- a/src/main/java/codechicken/nei/recipe/ProfilerRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/ProfilerRecipeHandler.java @@ -192,6 +192,11 @@ public PositionedStack getResultStack(int recipe) { return null; } + @Override + public List getResultStacks(int recipe) { + return new ArrayList<>(); + } + @Override public void onUpdate() {} diff --git a/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java b/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java index 8536ecebb..392925b84 100644 --- a/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java @@ -131,6 +131,22 @@ public abstract class CachedRecipe { */ public abstract PositionedStack getResult(); + /** + * The multiple items produced by this recipe, with position + * + * @return A list of positioned ingredient items. + */ + public List getResults() { + ArrayList stacks = new ArrayList<>(); + try { + PositionedStack stack = getResult(); + if (stack != null) stacks.add(stack); + } catch (ArithmeticException e) { + NEIClientConfig.logger.error("Error in getOtherStacks: " + e); + } + return stacks; + } + /** * The ingredients required to produce the result Use this if you have more than one ingredient * @@ -625,8 +641,14 @@ public List getIngredientStacks(int recipe) { } public PositionedStack getResultStack(int recipe) { + List results = getResultStacks(recipe); + if (results == null || results.isEmpty()) return null; + return results.get(0); + } + + public List getResultStacks(int recipe) { try { - return arecipes.get(recipe).getResult(); + return arecipes.get(recipe).getResults(); } catch (ArrayIndexOutOfBoundsException ignored) { return null; } From 434b4ac8799180af1034e161f06a395f6f222146 Mon Sep 17 00:00:00 2001 From: sbancuz Date: Thu, 16 Jul 2026 13:57:07 +0200 Subject: [PATCH 2/7] Review --- .../java/codechicken/nei/FavoriteRecipes.java | 2 +- .../codechicken/nei/recipe/IRecipeHandler.java | 6 +++++- .../codechicken/nei/recipe/NEIRecipeWidget.java | 4 ++-- .../nei/recipe/ProfilerRecipeHandler.java | 5 ----- src/main/java/codechicken/nei/recipe/Recipe.java | 16 ++++++++++------ .../nei/recipe/TemplateRecipeHandler.java | 3 ++- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/main/java/codechicken/nei/FavoriteRecipes.java b/src/main/java/codechicken/nei/FavoriteRecipes.java index f1d709fc4..b03aef6e0 100644 --- a/src/main/java/codechicken/nei/FavoriteRecipes.java +++ b/src/main/java/codechicken/nei/FavoriteRecipes.java @@ -129,7 +129,7 @@ public void execute() { protected List getOutputs(IRecipeHandler handler, int recipeIndex) { final List pStackResults = handler.getResultStacks(recipeIndex); - return pStackResults != null ? pStackResults : handler.getOtherStacks(recipeIndex); + return pStackResults.isEmpty() ? pStackResults : handler.getOtherStacks(recipeIndex); } }; diff --git a/src/main/java/codechicken/nei/recipe/IRecipeHandler.java b/src/main/java/codechicken/nei/recipe/IRecipeHandler.java index f4af421b2..26bf57bf4 100644 --- a/src/main/java/codechicken/nei/recipe/IRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/IRecipeHandler.java @@ -1,5 +1,6 @@ package codechicken.nei.recipe; +import java.util.Collections; import java.util.List; import net.minecraft.client.gui.inventory.GuiContainer; @@ -98,7 +99,10 @@ default int getRecipeHeight(int recipe) { * @return A list of the result {@link PositionedStack}s relative to the top left corner of your recipe drawing * space. */ - List getResultStacks(int recipe); + default List getResultStacks(int recipe) { + final PositionedStack result = getResultStack(recipe); + return result != null ? Collections.singletonList(result) : Collections.emptyList(); + } /** * A tick function called for updating progress bars and cycling damage items. diff --git a/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java b/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java index 10309246f..395f14409 100644 --- a/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java +++ b/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java @@ -658,12 +658,12 @@ protected List getInputs() { protected List getOutputs() { final List pStackResults = this.handlerRef.handler .getResultStacks(this.handlerRef.recipeIndex); - return pStackResults != null ? pStackResults + return pStackResults.isEmpty() ? pStackResults : this.handlerRef.handler.getOtherStacks(this.handlerRef.recipeIndex); } protected List getCatalysts() { - if (this.handlerRef.handler.getResultStack(this.handlerRef.recipeIndex) == null) { + if (this.handlerRef.handler.getResultStacks(this.handlerRef.recipeIndex).isEmpty()) { return Collections.emptyList(); } return this.handlerRef.handler.getOtherStacks(this.handlerRef.recipeIndex); diff --git a/src/main/java/codechicken/nei/recipe/ProfilerRecipeHandler.java b/src/main/java/codechicken/nei/recipe/ProfilerRecipeHandler.java index f2e5bcac3..182e39b63 100644 --- a/src/main/java/codechicken/nei/recipe/ProfilerRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/ProfilerRecipeHandler.java @@ -192,11 +192,6 @@ public PositionedStack getResultStack(int recipe) { return null; } - @Override - public List getResultStacks(int recipe) { - return new ArrayList<>(); - } - @Override public void onUpdate() {} diff --git a/src/main/java/codechicken/nei/recipe/Recipe.java b/src/main/java/codechicken/nei/recipe/Recipe.java index 461547007..bbd60fac1 100644 --- a/src/main/java/codechicken/nei/recipe/Recipe.java +++ b/src/main/java/codechicken/nei/recipe/Recipe.java @@ -54,19 +54,19 @@ public static RecipeId of(Object result, String handlerName, Iterable ingredi public static RecipeId of(IRecipeHandler handler, int recipeIndex) { final List ingredients = handler.getIngredientStacks(recipeIndex); final String handlerName = GuiRecipeTab.getHandlerInfo(handler).getHandlerName(); - PositionedStack pStackResult = handler.getResultStack(recipeIndex); + List pStackResults = handler.getResultStacks(recipeIndex); - if (pStackResult == null) { + if (pStackResults.isEmpty()) { for (PositionedStack otherStack : handler.getOtherStacks(recipeIndex)) { if (!FluidContainerRegistry.isContainer(otherStack.items[0]) || StackInfo.getFluid(otherStack.items[0]) != null) { - pStackResult = otherStack; + pStackResults.add(otherStack); break; } } } - return new RecipeId(extractItem(pStackResult), handlerName, extractIngredients(ingredients)); + return new RecipeId(extractItem(pStackResults), handlerName, extractIngredients(ingredients)); } public static RecipeId of(JsonObject json) { @@ -389,8 +389,12 @@ public static Recipe of(IRecipeHandler handler, int recipeIndex) { ingredients.add(RecipeIngredient.of(positionedStack)); } - if (handler.getResultStack(recipeIndex) != null) { - results.add(RecipeIngredient.of(handler.getResultStack(recipeIndex))); + final List resultStacks = handler.getResultStacks(recipeIndex); + + if (!resultStacks.isEmpty()) { + for (PositionedStack positionedStack : resultStacks) { + results.add(RecipeIngredient.of(positionedStack)); + } } else { for (PositionedStack positionedStack : handler.getOtherStacks(recipeIndex)) { results.add(RecipeIngredient.of(positionedStack)); diff --git a/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java b/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java index 392925b84..9f6791102 100644 --- a/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java @@ -4,6 +4,7 @@ import java.awt.Rectangle; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -650,7 +651,7 @@ public List getResultStacks(int recipe) { try { return arecipes.get(recipe).getResults(); } catch (ArrayIndexOutOfBoundsException ignored) { - return null; + return Collections.emptyList(); } } From 89110ccb7ee6e3137de2561d1f6b9b20d52ec8a1 Mon Sep 17 00:00:00 2001 From: sbancuz Date: Thu, 16 Jul 2026 16:47:56 +0200 Subject: [PATCH 3/7] Check if new behavior is supported (no fallbacks for now) --- .../nei/recipe/TemplateRecipeHandler.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java b/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java index 9f6791102..d2d9106a2 100644 --- a/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java @@ -54,6 +54,12 @@ public abstract class TemplateRecipeHandler implements ICraftingHandler, IUsageH protected static ReentrantLock lock = new ReentrantLock(); + /** + * To be used when creating a recipe handler to maintain backwards compatibility for NEI addons, if true the handler + * must not use `getResultStacks`, falling back to using the `other slots` as extra outputs + */ + private final boolean canUseNewSlotLayout; + public static void findFuelsOnce() { // Ensure we only find fuels once, even if threaded lock.lock(); @@ -398,7 +404,9 @@ public void onMouseDragged(GuiContainer gui, int mousex, int mousey, int button, */ public LinkedList transferRects = new LinkedList<>(); - public TemplateRecipeHandler() { + public TemplateRecipeHandler(boolean canUseNewSlotLayout) { + this.canUseNewSlotLayout = canUseNewSlotLayout; + try { loadTransferRects(); RecipeTransferRectHandler.registerRectsToGuis(getRecipeTransferRectGuis(), transferRects); @@ -408,6 +416,10 @@ public TemplateRecipeHandler() { } } + public TemplateRecipeHandler() { + this(false); + } + /** * Add all RecipeTransferRects to the transferRects list during this call. Afterward they may be added to the input * handler for the corresponding guis from getRecipeTransferRectGuis @@ -722,6 +734,14 @@ public boolean mouseScrolled(GuiRecipe gui, int scroll, int recipe) { return false; } + /** + * + * @return true if the recipe supports returning multiple results via `getResultStacks` + */ + public boolean canUseNewSlotLayout() { + return canUseNewSlotLayout; + } + private boolean transferRect(GuiRecipe gui, int recipe, boolean usage) { Point offset = gui.getRecipePosition(recipe); return transferRect(gui, transferRects, offset.x, offset.y, usage); From e6684ddebbeedaad439d70604ae4769e0a5089d8 Mon Sep 17 00:00:00 2001 From: sbancuz Date: Fri, 17 Jul 2026 11:22:49 +0200 Subject: [PATCH 4/7] Fix isEmpty conditions, and add extraInputs for not breaking old APIs --- .../java/codechicken/nei/FavoriteRecipes.java | 2 +- .../java/codechicken/nei/PresetsList.java | 6 +++ .../nei/filter/AllOthersRecipeFilter.java | 6 +++ .../nei/filter/AnyOthersRecipeFilter.java | 6 +++ .../codechicken/nei/filter/RecipeFilter.java | 4 ++ .../nei/recipe/IRecipeHandler.java | 13 ++++++- .../nei/recipe/NEIRecipeWidget.java | 27 ++++++++++++- .../java/codechicken/nei/recipe/Recipe.java | 6 ++- .../nei/recipe/TemplateRecipeHandler.java | 39 +++++++++---------- 9 files changed, 83 insertions(+), 26 deletions(-) diff --git a/src/main/java/codechicken/nei/FavoriteRecipes.java b/src/main/java/codechicken/nei/FavoriteRecipes.java index b03aef6e0..2cb6d56a7 100644 --- a/src/main/java/codechicken/nei/FavoriteRecipes.java +++ b/src/main/java/codechicken/nei/FavoriteRecipes.java @@ -129,7 +129,7 @@ public void execute() { protected List getOutputs(IRecipeHandler handler, int recipeIndex) { final List pStackResults = handler.getResultStacks(recipeIndex); - return pStackResults.isEmpty() ? pStackResults : handler.getOtherStacks(recipeIndex); + return !pStackResults.isEmpty() ? pStackResults : handler.getOtherStacks(recipeIndex); } }; diff --git a/src/main/java/codechicken/nei/PresetsList.java b/src/main/java/codechicken/nei/PresetsList.java index 043be9960..ecdca27ea 100644 --- a/src/main/java/codechicken/nei/PresetsList.java +++ b/src/main/java/codechicken/nei/PresetsList.java @@ -101,6 +101,12 @@ public boolean matches(IRecipeHandler handler, int recipeIndex) { return true; } + final List extraInputs = handler.getExtraInputStacks(recipeIndex); + + if (!extraInputs.isEmpty() && matchPositionedStack(extraInputs, true)) { + return true; + } + return results.isEmpty() && others.isEmpty(); } diff --git a/src/main/java/codechicken/nei/filter/AllOthersRecipeFilter.java b/src/main/java/codechicken/nei/filter/AllOthersRecipeFilter.java index a7ded945b..fa2a27d79 100644 --- a/src/main/java/codechicken/nei/filter/AllOthersRecipeFilter.java +++ b/src/main/java/codechicken/nei/filter/AllOthersRecipeFilter.java @@ -24,6 +24,12 @@ public boolean matches(IRecipeHandler handler, int recipeIndex) { } } + for (PositionedStack pStack : handler.getExtraInputStacks(recipeIndex)) { + if (!match(pStack)) { + return false; + } + } + return true; } diff --git a/src/main/java/codechicken/nei/filter/AnyOthersRecipeFilter.java b/src/main/java/codechicken/nei/filter/AnyOthersRecipeFilter.java index f3c4c31f0..c18ed5420 100644 --- a/src/main/java/codechicken/nei/filter/AnyOthersRecipeFilter.java +++ b/src/main/java/codechicken/nei/filter/AnyOthersRecipeFilter.java @@ -24,6 +24,12 @@ public boolean matches(IRecipeHandler handler, int recipeIndex) { } } + for (PositionedStack pStack : handler.getExtraInputStacks(recipeIndex)) { + if (match(pStack)) { + return true; + } + } + return false; } diff --git a/src/main/java/codechicken/nei/filter/RecipeFilter.java b/src/main/java/codechicken/nei/filter/RecipeFilter.java index 9c0592756..f0f8eed20 100644 --- a/src/main/java/codechicken/nei/filter/RecipeFilter.java +++ b/src/main/java/codechicken/nei/filter/RecipeFilter.java @@ -56,6 +56,10 @@ && matchPositionedStack(handler.getIngredientStacks(recipeIndex), this.anyMatch) return this.anyMatch; } + if (matchPositionedStack(handler.getExtraInputStacks(recipeIndex), this.anyMatch)) { + return this.anyMatch; + } + } return !this.anyMatch; diff --git a/src/main/java/codechicken/nei/recipe/IRecipeHandler.java b/src/main/java/codechicken/nei/recipe/IRecipeHandler.java index 26bf57bf4..05303f059 100644 --- a/src/main/java/codechicken/nei/recipe/IRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/IRecipeHandler.java @@ -83,10 +83,21 @@ default int getRecipeHeight(int recipe) { * @return A list of the other {@link PositionedStack}s in this recipe relative to the top left corner of your * recipe drawing space. For example fuel in furnaces. */ + default List getExtraInputStacks(int recipe) { + return Collections.emptyList(); + } + + /** + * Legacy API, define either {@link #getExtraInputStacks(int)} or {@link #getResultStacks(int)} + * + * @param recipe The recipe index to get items for. + * @return A list of the other {@link PositionedStack}s in this recipe relative to the top left corner of your + * recipe drawing space. For example fuel in furnaces. + */ List getOtherStacks(int recipe); /** - * Maintain this for backwards compatibility + * Legacy API, use {@link #getResultStacks(int) getResultStacks.get(0)} if you want the primary result * * @param recipe The recipe index to get the result for. * @return The recipe result {@link PositionedStack} relative to the top left corner of your recipe drawing space. diff --git a/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java b/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java index 395f14409..542e01d86 100644 --- a/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java +++ b/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java @@ -202,6 +202,15 @@ public void draw(int mouseX, int mouseY) { drawItem(pStack, mouseX, mouseY, yShift, true); } + for (PositionedStack pStack : getExtraInputs()) { + + if (!this.permutations.containsKey(pStack)) { + updatePermutationsFor(pStack); + } + + drawItem(pStack, mouseX, mouseY, yShift, true); + } + for (PositionedStack pStack : getCatalysts()) { if (!this.permutations.containsKey(pStack)) { @@ -491,7 +500,7 @@ protected boolean scrollPermutations(int scroll, int mx, int my) { final int stackIndex = indexOf(items, overStack.item); final ItemStack stack = items.get((items.size() - scroll + stackIndex) % items.size()); - Stream.concat(getInputs().stream(), getCatalysts().stream()).filter(pStack -> pStack.containsWithNBT(stack)) + Stream.concat(getInputs().stream(), Stream.concat(getExtraInputs().stream(), getCatalysts().stream())).filter(pStack -> pStack.containsWithNBT(stack)) .forEach(pStack -> pStack.setPermutationToRender(stack)); if (this.acceptsFollowingTooltipLineHandler != null) { @@ -554,6 +563,12 @@ public PositionedStack getPositionedStackMouseOver(int mx, int my) { } } + for (PositionedStack pStack : getExtraInputs()) { + if (pStack.contains(mx - this.x, my - this.y - yShift)) { + return pStack; + } + } + for (PositionedStack pStack : getCatalysts()) { if (pStack.contains(mx - this.x, my - this.y - yShift)) { return pStack; @@ -610,6 +625,10 @@ protected void updatePermutations() { updatePermutationsFor(pStack); } + for (PositionedStack pStack : getExtraInputs()) { + updatePermutationsFor(pStack); + } + for (PositionedStack pStack : getCatalysts()) { updatePermutationsFor(pStack); } @@ -655,10 +674,14 @@ protected List getInputs() { return this.handlerRef.handler.getIngredientStacks(this.handlerRef.recipeIndex); } + protected List getExtraInputs() { + return this.handlerRef.handler.getExtraInputStacks(this.handlerRef.recipeIndex); + } + protected List getOutputs() { final List pStackResults = this.handlerRef.handler .getResultStacks(this.handlerRef.recipeIndex); - return pStackResults.isEmpty() ? pStackResults + return !pStackResults.isEmpty() ? pStackResults : this.handlerRef.handler.getOtherStacks(this.handlerRef.recipeIndex); } diff --git a/src/main/java/codechicken/nei/recipe/Recipe.java b/src/main/java/codechicken/nei/recipe/Recipe.java index bbd60fac1..a3585eb9a 100644 --- a/src/main/java/codechicken/nei/recipe/Recipe.java +++ b/src/main/java/codechicken/nei/recipe/Recipe.java @@ -54,6 +54,10 @@ public static RecipeId of(Object result, String handlerName, Iterable ingredi public static RecipeId of(IRecipeHandler handler, int recipeIndex) { final List ingredients = handler.getIngredientStacks(recipeIndex); final String handlerName = GuiRecipeTab.getHandlerInfo(handler).getHandlerName(); + // Use getResultStacks even though getResult could work because if a handler, like GT for example, doesn't + // return anything from getResult, but will define all the outputs in getResultStacks we can still pick up + // the results here. If a handler still doesn't define getResultStacks, then it will naturally fall back to + // getResult anyway List pStackResults = handler.getResultStacks(recipeIndex); if (pStackResults.isEmpty()) { @@ -66,7 +70,7 @@ public static RecipeId of(IRecipeHandler handler, int recipeIndex) { } } - return new RecipeId(extractItem(pStackResults), handlerName, extractIngredients(ingredients)); + return new RecipeId(extractItem(pStackResults.get(0)), handlerName, extractIngredients(ingredients)); } public static RecipeId of(JsonObject json) { diff --git a/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java b/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java index d2d9106a2..c0fb66640 100644 --- a/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java @@ -54,12 +54,6 @@ public abstract class TemplateRecipeHandler implements ICraftingHandler, IUsageH protected static ReentrantLock lock = new ReentrantLock(); - /** - * To be used when creating a recipe handler to maintain backwards compatibility for NEI addons, if true the handler - * must not use `getResultStacks`, falling back to using the `other slots` as extra outputs - */ - private final boolean canUseNewSlotLayout; - public static void findFuelsOnce() { // Ensure we only find fuels once, even if threaded lock.lock(); @@ -154,6 +148,15 @@ public List getResults() { return stacks; } + /** + * The ingredients required to produce the result Use this if you have more than one ingredient + * + * @return A list of positioned ingredient items. + */ + public List getExtraInputs() { + return Collections.emptyList(); + } + /** * The ingredients required to produce the result Use this if you have more than one ingredient * @@ -404,9 +407,7 @@ public void onMouseDragged(GuiContainer gui, int mousex, int mousey, int button, */ public LinkedList transferRects = new LinkedList<>(); - public TemplateRecipeHandler(boolean canUseNewSlotLayout) { - this.canUseNewSlotLayout = canUseNewSlotLayout; - + public TemplateRecipeHandler() { try { loadTransferRects(); RecipeTransferRectHandler.registerRectsToGuis(getRecipeTransferRectGuis(), transferRects); @@ -416,10 +417,6 @@ public TemplateRecipeHandler(boolean canUseNewSlotLayout) { } } - public TemplateRecipeHandler() { - this(false); - } - /** * Add all RecipeTransferRects to the transferRects list during this call. Afterward they may be added to the input * handler for the corresponding guis from getRecipeTransferRectGuis @@ -667,6 +664,14 @@ public List getResultStacks(int recipe) { } } + public List getExtraInputStacks(int recipe) { + try { + return arecipes.get(recipe).getExtraInputs(); + } catch (ArrayIndexOutOfBoundsException ignored) { + return Collections.emptyList(); + } + } + public List getOtherStacks(int recipe) { return arecipes.get(recipe).getOtherStacks(); } @@ -734,14 +739,6 @@ public boolean mouseScrolled(GuiRecipe gui, int scroll, int recipe) { return false; } - /** - * - * @return true if the recipe supports returning multiple results via `getResultStacks` - */ - public boolean canUseNewSlotLayout() { - return canUseNewSlotLayout; - } - private boolean transferRect(GuiRecipe gui, int recipe, boolean usage) { Point offset = gui.getRecipePosition(recipe); return transferRect(gui, transferRects, offset.x, offset.y, usage); From 1767d4be631db4036d56b2cea6f203ee3ade4762 Mon Sep 17 00:00:00 2001 From: sbancuz Date: Fri, 17 Jul 2026 12:30:24 +0200 Subject: [PATCH 5/7] Fix isEmpty conditions, and add catalysts for not breaking old APIs --- .../java/codechicken/nei/PresetsList.java | 2 +- .../nei/filter/AllOthersRecipeFilter.java | 2 +- .../nei/filter/AnyOthersRecipeFilter.java | 2 +- .../codechicken/nei/filter/RecipeFilter.java | 2 +- .../nei/recipe/IRecipeHandler.java | 4 +-- .../nei/recipe/NEIRecipeWidget.java | 30 ++++------------ .../java/codechicken/nei/recipe/Recipe.java | 5 ++- .../nei/recipe/TemplateRecipeHandler.java | 34 +++++++++++++------ 8 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/main/java/codechicken/nei/PresetsList.java b/src/main/java/codechicken/nei/PresetsList.java index ecdca27ea..d8aef534e 100644 --- a/src/main/java/codechicken/nei/PresetsList.java +++ b/src/main/java/codechicken/nei/PresetsList.java @@ -101,7 +101,7 @@ public boolean matches(IRecipeHandler handler, int recipeIndex) { return true; } - final List extraInputs = handler.getExtraInputStacks(recipeIndex); + final List extraInputs = handler.getCatalystStacks(recipeIndex); if (!extraInputs.isEmpty() && matchPositionedStack(extraInputs, true)) { return true; diff --git a/src/main/java/codechicken/nei/filter/AllOthersRecipeFilter.java b/src/main/java/codechicken/nei/filter/AllOthersRecipeFilter.java index fa2a27d79..3b615b1e0 100644 --- a/src/main/java/codechicken/nei/filter/AllOthersRecipeFilter.java +++ b/src/main/java/codechicken/nei/filter/AllOthersRecipeFilter.java @@ -24,7 +24,7 @@ public boolean matches(IRecipeHandler handler, int recipeIndex) { } } - for (PositionedStack pStack : handler.getExtraInputStacks(recipeIndex)) { + for (PositionedStack pStack : handler.getCatalystStacks(recipeIndex)) { if (!match(pStack)) { return false; } diff --git a/src/main/java/codechicken/nei/filter/AnyOthersRecipeFilter.java b/src/main/java/codechicken/nei/filter/AnyOthersRecipeFilter.java index c18ed5420..08fd9c934 100644 --- a/src/main/java/codechicken/nei/filter/AnyOthersRecipeFilter.java +++ b/src/main/java/codechicken/nei/filter/AnyOthersRecipeFilter.java @@ -24,7 +24,7 @@ public boolean matches(IRecipeHandler handler, int recipeIndex) { } } - for (PositionedStack pStack : handler.getExtraInputStacks(recipeIndex)) { + for (PositionedStack pStack : handler.getCatalystStacks(recipeIndex)) { if (match(pStack)) { return true; } diff --git a/src/main/java/codechicken/nei/filter/RecipeFilter.java b/src/main/java/codechicken/nei/filter/RecipeFilter.java index f0f8eed20..62ec17155 100644 --- a/src/main/java/codechicken/nei/filter/RecipeFilter.java +++ b/src/main/java/codechicken/nei/filter/RecipeFilter.java @@ -56,7 +56,7 @@ && matchPositionedStack(handler.getIngredientStacks(recipeIndex), this.anyMatch) return this.anyMatch; } - if (matchPositionedStack(handler.getExtraInputStacks(recipeIndex), this.anyMatch)) { + if (matchPositionedStack(handler.getCatalystStacks(recipeIndex), this.anyMatch)) { return this.anyMatch; } diff --git a/src/main/java/codechicken/nei/recipe/IRecipeHandler.java b/src/main/java/codechicken/nei/recipe/IRecipeHandler.java index 05303f059..65ed505f1 100644 --- a/src/main/java/codechicken/nei/recipe/IRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/IRecipeHandler.java @@ -83,12 +83,12 @@ default int getRecipeHeight(int recipe) { * @return A list of the other {@link PositionedStack}s in this recipe relative to the top left corner of your * recipe drawing space. For example fuel in furnaces. */ - default List getExtraInputStacks(int recipe) { + default List getCatalystStacks(int recipe) { return Collections.emptyList(); } /** - * Legacy API, define either {@link #getExtraInputStacks(int)} or {@link #getResultStacks(int)} + * Legacy API, define either {@link #getCatalystStacks(int)} or {@link #getResultStacks(int)} * * @param recipe The recipe index to get items for. * @return A list of the other {@link PositionedStack}s in this recipe relative to the top left corner of your diff --git a/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java b/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java index 542e01d86..94a511a00 100644 --- a/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java +++ b/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java @@ -202,15 +202,6 @@ public void draw(int mouseX, int mouseY) { drawItem(pStack, mouseX, mouseY, yShift, true); } - for (PositionedStack pStack : getExtraInputs()) { - - if (!this.permutations.containsKey(pStack)) { - updatePermutationsFor(pStack); - } - - drawItem(pStack, mouseX, mouseY, yShift, true); - } - for (PositionedStack pStack : getCatalysts()) { if (!this.permutations.containsKey(pStack)) { @@ -500,7 +491,7 @@ protected boolean scrollPermutations(int scroll, int mx, int my) { final int stackIndex = indexOf(items, overStack.item); final ItemStack stack = items.get((items.size() - scroll + stackIndex) % items.size()); - Stream.concat(getInputs().stream(), Stream.concat(getExtraInputs().stream(), getCatalysts().stream())).filter(pStack -> pStack.containsWithNBT(stack)) + Stream.concat(getInputs().stream(), getCatalysts().stream()).filter(pStack -> pStack.containsWithNBT(stack)) .forEach(pStack -> pStack.setPermutationToRender(stack)); if (this.acceptsFollowingTooltipLineHandler != null) { @@ -563,12 +554,6 @@ public PositionedStack getPositionedStackMouseOver(int mx, int my) { } } - for (PositionedStack pStack : getExtraInputs()) { - if (pStack.contains(mx - this.x, my - this.y - yShift)) { - return pStack; - } - } - for (PositionedStack pStack : getCatalysts()) { if (pStack.contains(mx - this.x, my - this.y - yShift)) { return pStack; @@ -625,10 +610,6 @@ protected void updatePermutations() { updatePermutationsFor(pStack); } - for (PositionedStack pStack : getExtraInputs()) { - updatePermutationsFor(pStack); - } - for (PositionedStack pStack : getCatalysts()) { updatePermutationsFor(pStack); } @@ -674,10 +655,6 @@ protected List getInputs() { return this.handlerRef.handler.getIngredientStacks(this.handlerRef.recipeIndex); } - protected List getExtraInputs() { - return this.handlerRef.handler.getExtraInputStacks(this.handlerRef.recipeIndex); - } - protected List getOutputs() { final List pStackResults = this.handlerRef.handler .getResultStacks(this.handlerRef.recipeIndex); @@ -686,6 +663,11 @@ protected List getOutputs() { } protected List getCatalysts() { + List catalysts = this.handlerRef.handler.getCatalystStacks(this.handlerRef.recipeIndex); + if (!catalysts.isEmpty()) { + return catalysts; + } + if (this.handlerRef.handler.getResultStacks(this.handlerRef.recipeIndex).isEmpty()) { return Collections.emptyList(); } diff --git a/src/main/java/codechicken/nei/recipe/Recipe.java b/src/main/java/codechicken/nei/recipe/Recipe.java index a3585eb9a..8607fb5d9 100644 --- a/src/main/java/codechicken/nei/recipe/Recipe.java +++ b/src/main/java/codechicken/nei/recipe/Recipe.java @@ -70,7 +70,10 @@ public static RecipeId of(IRecipeHandler handler, int recipeIndex) { } } - return new RecipeId(extractItem(pStackResults.get(0)), handlerName, extractIngredients(ingredients)); + return new RecipeId( + extractItem(!pStackResults.isEmpty() ? pStackResults.get(0) : null), + handlerName, + extractIngredients(ingredients)); } public static RecipeId of(JsonObject json) { diff --git a/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java b/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java index c0fb66640..3dbded114 100644 --- a/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java @@ -149,16 +149,32 @@ public List getResults() { } /** - * The ingredients required to produce the result Use this if you have more than one ingredient + * Return extra items that are not directly involved in the ingredient->result relationship. Eg fuels. * * @return A list of positioned ingredient items. */ - public List getExtraInputs() { - return Collections.emptyList(); + public List getCatalysts() { + ArrayList stacks = new ArrayList<>(); + try { + PositionedStack stack = getCatalyst(); + if (stack != null) stacks.add(stack); + } catch (ArithmeticException e) { + NEIClientConfig.logger.error("Error in getCatalysts: " + e); + } + return stacks; + } + + /** + * Simple utility + * + * @return The another positioned stack + */ + public PositionedStack getCatalyst() { + return null; } /** - * The ingredients required to produce the result Use this if you have more than one ingredient + * The ingredients required to produce the result. Use this if you have more than one ingredient * * @return A list of positioned ingredient items. */ @@ -177,6 +193,8 @@ public PositionedStack getIngredient() { } /** + * Legacy API + * * Return extra items that are not directly involved in the ingredient->result relationship. Eg fuels. Use this * if you have more than one other stack * @@ -664,12 +682,8 @@ public List getResultStacks(int recipe) { } } - public List getExtraInputStacks(int recipe) { - try { - return arecipes.get(recipe).getExtraInputs(); - } catch (ArrayIndexOutOfBoundsException ignored) { - return Collections.emptyList(); - } + public List getCatalystStacks(int recipe) { + return arecipes.get(recipe).getCatalysts(); } public List getOtherStacks(int recipe) { From 83ee2e0d533ac7b000a5296ff15be2b131dddea2 Mon Sep 17 00:00:00 2001 From: sbancuz Date: Sun, 2 Aug 2026 21:01:08 +0200 Subject: [PATCH 6/7] FurnaceHandler --- src/main/java/codechicken/nei/recipe/FuelRecipeHandler.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/codechicken/nei/recipe/FuelRecipeHandler.java b/src/main/java/codechicken/nei/recipe/FuelRecipeHandler.java index 254ad9bf2..d48539ba9 100644 --- a/src/main/java/codechicken/nei/recipe/FuelRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/FuelRecipeHandler.java @@ -38,6 +38,11 @@ public PositionedStack getResult() { public PositionedStack getOtherStack() { return fuel.stack; } + + @Override + public PositionedStack getCatalyst() { + return fuel.stack; + } } private final ArrayList mfurnace = new ArrayList<>(); From 7f182757c3323e60cb4a84d88309c18b69b6b31b Mon Sep 17 00:00:00 2001 From: sbancuz Date: Sun, 2 Aug 2026 21:16:43 +0200 Subject: [PATCH 7/7] De-dupe draw calls --- .../nei/recipe/NEIRecipeWidget.java | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java b/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java index 94a511a00..cc1c2e8c2 100644 --- a/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java +++ b/src/main/java/codechicken/nei/recipe/NEIRecipeWidget.java @@ -6,6 +6,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; import java.util.function.Function; import java.util.stream.Stream; @@ -36,6 +37,7 @@ import codechicken.nei.recipe.Recipe.RecipeId; import codechicken.nei.recipe.debug.DebugHandlerWidget; import codechicken.nei.util.NEIMouseUtils; +import it.unimi.dsi.fastutil.longs.LongArraySet; public class NEIRecipeWidget extends Widget { @@ -54,6 +56,9 @@ public class NEIRecipeWidget extends Widget { protected boolean showAsWidget = false; protected List recipeButtons = null; + // Handles re-draws for catalysts-outputs duplication to preserve full backward compatibility for otherStacks + private final Set drawnSlots = new LongArraySet(); + public NEIRecipeWidget(RecipeHandlerRef handlerRef) { this.handlerRef = handlerRef; this.handlerInfo = GuiRecipeTab.getHandlerInfo(this.handlerRef.handler); @@ -193,7 +198,20 @@ public void draw(int mouseX, int mouseY) { GuiContainerManager.enableMatrixStackLogging(); - for (PositionedStack pStack : getInputs()) { + final List inputs = getInputs(); + final List catalysts = getCatalysts(); + final List outputs = getOutputs(); + + drawnSlots.clear(); + for (PositionedStack pStack : inputs) { + drawnSlots.add(slotKey(pStack)); + drawItem(pStack, mouseX, mouseY, yShift, false); + } + + for (PositionedStack pStack : catalysts) { + if (!drawnSlots.add(slotKey(pStack))) { + continue; + } if (!this.permutations.containsKey(pStack)) { updatePermutationsFor(pStack); @@ -202,7 +220,10 @@ public void draw(int mouseX, int mouseY) { drawItem(pStack, mouseX, mouseY, yShift, true); } - for (PositionedStack pStack : getCatalysts()) { + for (PositionedStack pStack : outputs) { + if (!drawnSlots.add(slotKey(pStack))) { + continue; + } if (!this.permutations.containsKey(pStack)) { updatePermutationsFor(pStack); @@ -211,10 +232,6 @@ public void draw(int mouseX, int mouseY) { drawItem(pStack, mouseX, mouseY, yShift, true); } - for (PositionedStack pStack : getOutputs()) { - drawItem(pStack, mouseX, mouseY, yShift, false); - } - GuiContainerManager.disableMatrixStackLogging(); this.handlerRef.handler.drawForeground(this.handlerRef.recipeIndex); @@ -246,6 +263,10 @@ public void draw(int mouseX, int mouseY) { DebugHandlerWidget.instance.drawGuiPlaceholder(this); } + private static long slotKey(PositionedStack pStack) { + return ((long) pStack.relx << 32) ^ (pStack.rely & 0xFFFFFFFFL); + } + protected void drawItem(PositionedStack pStack, int mouseX, int mouseY, int yShift, boolean input) { GuiContainerManager.drawItem(pStack.relx, pStack.rely, pStack.item);