From 4580ac08705112dc263d5b700e8c078fc2f5bb1b Mon Sep 17 00:00:00 2001 From: Meegoo Date: Sun, 29 Sep 2024 17:40:31 +0300 Subject: [PATCH] Lots of fixes from the comment --- .../java/codechicken/nei/BookmarkPanel.java | 187 +++++-- .../java/codechicken/nei/NEIClientConfig.java | 14 + .../crafts/BookmarkCraftingChain.java | 48 +- .../crafts/ItemStackWithMetadata.java | 11 +- .../bookmarks/crafts/graph/CraftingGraph.java | 504 ++++++++++++------ .../crafts/graph/CraftingGraphNode.java | 8 +- .../graph/FluidConversionGraphNode.java | 138 +++++ .../bookmarks/crafts/graph/ItemGraphNode.java | 19 +- .../crafts/graph/RecipeGraphNode.java | 79 ++- .../codechicken/nei/recipe/StackInfo.java | 15 + .../GTFluidStackStringifyHandler.java | 3 +- src/main/resources/assets/nei/lang/en_US.lang | 4 + 12 files changed, 743 insertions(+), 287 deletions(-) create mode 100644 src/main/java/codechicken/nei/bookmarks/crafts/graph/FluidConversionGraphNode.java diff --git a/src/main/java/codechicken/nei/BookmarkPanel.java b/src/main/java/codechicken/nei/BookmarkPanel.java index 953d776eb..3f26bdf51 100644 --- a/src/main/java/codechicken/nei/BookmarkPanel.java +++ b/src/main/java/codechicken/nei/BookmarkPanel.java @@ -36,6 +36,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.EnumChatFormatting; +import net.minecraftforge.fluids.FluidStack; import org.apache.commons.io.IOUtils; import org.lwjgl.opengl.GL11; @@ -1106,11 +1107,12 @@ protected void beforeDrawSlot(@Nullable ItemPanelSlot focus, int idx, Rectangle4 drawRect(rect.x, rect.y, rect.w, rect.h, 0x6645DA75); // inputs } else if (groupMeta.crafting.getOutputSlots().contains(idx)) { drawRect(rect.x, rect.y, rect.w, rect.h, 0x9966CCFF); // exports + } else if (groupMeta.crafting.getConflictingSlots().contains(idx)) { + drawRect(rect.x, rect.y, rect.w, rect.h, 0x99d66f6f); // conflicts } } else if (focus != null && meta.equalsRecipe(getMetadata(focus.slotIndex))) { drawRect(rect.x, rect.y, rect.w, rect.h, meta.ingredient ? 0x6645DA75 : 0x9966CCFF); // highlight - // recipe } else { super.beforeDrawSlot(focus, idx, rect); } @@ -1135,6 +1137,9 @@ protected void afterDrawSlot(@Nullable ItemPanelSlot focus, int idx, Rectangle4i if (groupMeta.crafting != null) { String text = null; + if (groupMeta.crafting.getConflictingSlots().contains(idx)) { + return; + } if (NEIClientUtils.shiftKey()) { int crafts = groupMeta.crafting.getCalculatedCraftCounts().getOrDefault(idx, 0); @@ -1174,7 +1179,12 @@ protected void drawItem(Rectangle4i rect, int idx) { ItemStack drawStack = realStack; if (groupMeta.crafting != null) { - ItemStack craftingItemStack = groupMeta.crafting.getCalculatedItems().get(idx); + ItemStack craftingItemStack; + if (NEIClientUtils.shiftKey()) { + craftingItemStack = groupMeta.crafting.getCalculatedRemainders().get(idx); + } else { + craftingItemStack = groupMeta.crafting.getCalculatedItems().get(idx); + } if (craftingItemStack != null) { drawStack = craftingItemStack; } else { @@ -1359,8 +1369,6 @@ public CraftingChainTooltipLineHandler(int groupId, BookmarkCraftingChain crafti this.groupId = groupId; this.craftingChain = craftingChain; - // TODO stuff - this.inputs = new ItemsTooltipLineHandler( translate("bookmark.crafting_chain.input"), new ArrayList<>(craftingChain.getInputStacks()), @@ -1382,8 +1390,8 @@ public CraftingChainTooltipLineHandler(int groupId, BookmarkCraftingChain crafti if (!this.inputs.isEmpty() || !this.outputs.isEmpty() || !this.remainder.isEmpty()) { this.size.height += 2 + fontRenderer.FONT_HEIGHT; this.size.width = Math.max( - this.inputs.getSize().width, - Math.max(this.outputs.getSize().width, this.remainder.getSize().width)); + Math.max(this.inputs.getSize().width, this.outputs.getSize().width), + this.remainder.getSize().width); this.size.height += this.inputs.getSize().height + this.outputs.getSize().height + this.remainder.getSize().height; @@ -1434,8 +1442,8 @@ public void draw(int x, int y) { if (!this.remainder.isEmpty()) { this.remainder.draw(x, y); + y += this.remainder.getSize().height; } - } } @@ -1600,21 +1608,8 @@ public void addRecipe(BookmarkRecipe recipe, boolean saveSize, int groupId) { } if (recipeId != null) { - final Map uniqueIngredients = new LinkedHashMap<>(); - BGrid.removeRecipe(recipeId, groupId); - - for (ItemStack stack : recipe.ingredients) { - final String GUID = StackInfo.getItemStackGUID(stack); - final NBTTagCompound nbTagU = uniqueIngredients.get(GUID); - final NBTTagCompound nbTagS = StackInfo.itemStackToNBT(stack); - - if (nbTagU == null) { - uniqueIngredients.put(GUID, nbTagS); - } else { - nbTagU.setInteger("Count", nbTagU.getInteger("Count") + nbTagS.getInteger("Count")); - } - } + final Map uniqueIngredients = getUniqueItems(recipe.ingredients); for (String GUID : uniqueIngredients.keySet()) { final NBTTagCompound nbTag = uniqueIngredients.get(GUID); @@ -1625,8 +1620,10 @@ public void addRecipe(BookmarkRecipe recipe, boolean saveSize, int groupId) { } } - for (ItemStack stack : recipe.result) { - final NBTTagCompound nbTag = StackInfo.itemStackToNBT(stack); + Map uniqueResults = getUniqueItems(recipe.result); + + for (String GUID : uniqueResults.keySet()) { + final NBTTagCompound nbTag = uniqueResults.get(GUID); final ItemStack normalized = StackInfo.loadFromNBT(nbTag, saveSize ? nbTag.getInteger("Count") : 0); final ItemStackMetadata metadata = new ItemStackMetadata(recipeId, nbTag, false, groupId); BGrid.addItem(normalized, metadata); @@ -1636,6 +1633,23 @@ public void addRecipe(BookmarkRecipe recipe, boolean saveSize, int groupId) { fixCountOfNamespaces(); } + private Map getUniqueItems(List stacks) { + final Map uniqueItems = new LinkedHashMap<>(); + + for (ItemStack stack : stacks) { + final String GUID = StackInfo.getItemStackGUID(stack); + final NBTTagCompound nbTagU = uniqueItems.get(GUID); + final NBTTagCompound nbTagS = StackInfo.itemStackToNBT(stack); + + if (nbTagU == null) { + uniqueItems.put(GUID, nbTagS); + } else { + nbTagU.setInteger("Count", nbTagU.getInteger("Count") + nbTagS.getInteger("Count")); + } + } + return uniqueItems; + } + public void addBookmarkGroup(List items, BookmarkViewMode viewMode) { List recipes = new ArrayList<>(); @@ -2625,6 +2639,59 @@ private List craftingChainTooltip(int groupId, List currenttip) public List handleItemTooltip(GuiContainer gui, ItemStack itemstack, int mousex, int mousey, List currenttip) { + final BookmarkGrid BGrid = (BookmarkGrid) grid; + final ItemPanelSlot slot = getSlotMouseOver(mousex, mousey); + if (slot != null) { + final int overRowIndex = BGrid.getHoveredRowIndex(false); + final int groupId = BGrid.getRowGroupId(overRowIndex); + final BookmarkGroup group = BGrid.groups.get(groupId); + if (group != null && group.crafting != null) { + if (group.crafting.getConflictingSlots().contains(slot.slotIndex) && NEIClientUtils.shiftKey()) { + currenttip.add( + EnumChatFormatting.RED + translate("bookmark.conflictingRecipe.tip") + + EnumChatFormatting.RESET); + return currenttip; + } + if (!NEIClientUtils.shiftKey()) { + ItemStackMetadata metadata = BGrid.getMetadata(slot.slotIndex); + if (!metadata.ingredient) { + int maxStackSize = itemstack.getMaxStackSize(); + FluidStack fluid = StackInfo.getFluid(itemstack); + if (fluid != null) { + maxStackSize = 144; + } + String details; + if (metadata.requestedAmount > 0) { + details = GuiContainerManager.countDetails( + metadata.requestedAmount, + maxStackSize, + "Requested: %s = %s * %s + %s", + "Requested: %s = %s * %s"); + } else { + details = GuiContainerManager.countDetails( + -metadata.requestedAmount, + maxStackSize, + "Requested: -%s = -%s * %s - %s", + "Requested: -%s = -%s * %s"); + } + if (details != null) { + currenttip.add(EnumChatFormatting.GRAY + details + EnumChatFormatting.RESET); + } + } + } else { + ItemStackMetadata metadata = BGrid.getMetadata(slot.slotIndex); + if (!metadata.ingredient) { + int crafts = group.crafting.getCalculatedCraftCounts().get(slot.slotIndex); + currenttip.add( + String.format( + EnumChatFormatting.GRAY + "Crafts: %d" + EnumChatFormatting.RESET, + crafts)); + } + } + } + + } + if (contains(mousex, mousey) && itemstack != null && this.recipeTooltipLineHandler != null) { currenttip.add(GuiDraw.TOOLTIP_HANDLER + GuiDraw.getTipLineId(this.recipeTooltipLineHandler)); } @@ -2697,40 +2764,38 @@ public boolean onMouseWheel(int shift, int mousex, int mousey) { if (slot != null) { final BookmarkGrid BGrid = (BookmarkGrid) grid; final ItemStackMetadata overMeta = BGrid.getMetadata(slot.slotIndex); - final HashMap items = new HashMap<>(); int shiftMultiplier = 1; if (NEIClientUtils.altKey()) { shiftMultiplier = NEIClientConfig.showItemQuantityWidget() ? NEIClientConfig.getItemQuantity() : 0; - if (shiftMultiplier == 0) { - shiftMultiplier = slot.item.getMaxStackSize(); - } + } + if (shiftMultiplier == 0) { + shiftMultiplier = slot.item.getMaxStackSize(); } BookmarkGroup group = BGrid.groups.get(overMeta.groupId); - if (group.crafting != null) { - if (NEIClientUtils.shiftKey()) { - for (ItemStackMetadata meta : BGrid.metadata) { - if (Objects.equals(meta.recipeId, overMeta.recipeId) && !meta.ingredient) { - meta.requestedAmount += shift * shiftMultiplier * meta.factor; - } + if (NEIClientUtils.shiftKey()) { + List metadata = BGrid.metadata; + for (int i = 0; i < metadata.size(); i++) { + ItemStackMetadata meta = metadata.get(i); + if (!Objects.equals(meta.recipeId, overMeta.recipeId) || meta.groupId != overMeta.groupId) { + continue; } - } else { - if (!overMeta.ingredient) { - Integer stackSize = StackInfo.getFluidCellSize(BGrid.getItem(slot.slotIndex)); - if (overMeta.fluidDisplay && stackSize != null) { - shiftMultiplier *= stackSize; - } - overMeta.requestedAmount += shift * shiftMultiplier; + if (group.crafting != null && !meta.ingredient) { + shiftRequestedAmount(meta, shift, shiftMultiplier * meta.factor); + } else if (group.crafting == null) { + shiftStackSize(BGrid, i, shift, shiftMultiplier * meta.factor); } } } else { - items.put(slot.slotIndex, shiftStackSize(BGrid, slot.slotIndex, shift, shiftMultiplier)); - - for (int slotIndex : items.keySet()) { - if (items.get(slotIndex) != null) { - BGrid.realItems.set(slotIndex, items.get(slotIndex)); - } + Integer stackSize = StackInfo.getFluidCellSize(BGrid.getItem(slot.slotIndex)); + if (overMeta.fluidDisplay && stackSize != null) { + shiftMultiplier *= stackSize; + } + if (group.crafting != null && !overMeta.ingredient) { + shiftRequestedAmount(overMeta, shift, shiftMultiplier); + } else if (group.crafting == null) { + shiftStackSize(BGrid, slot.slotIndex, shift, shiftMultiplier); } } @@ -2758,21 +2823,35 @@ public boolean onMouseWheel(int shift, int mousex, int mousey) { return false; } - private ItemStack shiftStackSize(BookmarkGrid BGrid, int slotIndex, int shift, int shiftMultiplier) { + private void shiftStackSize(BookmarkGrid BGrid, int slotIndex, int shift, int shiftMultiplier) { final NBTTagCompound nbTag = StackInfo.itemStackToNBT(BGrid.getItem(slotIndex)); final ItemStackMetadata meta = BGrid.getMetadata(slotIndex); if (meta.factor > 0) { - final int multiplier = nbTag.getInteger("Count") / meta.factor; - final long count = ((long) (multiplier + shift * shiftMultiplier) / shiftMultiplier) * shiftMultiplier - * meta.factor; + final int originalCount = nbTag.getInteger("Count"); + final long count = calculateShiftedValue(originalCount, shift, shiftMultiplier); + ItemStack newStack = StackInfo.loadFromNBT(nbTag, Math.max(count, 0)); + BGrid.realItems.set(slotIndex, newStack); + } + } - if (count <= Integer.MAX_VALUE) { - return StackInfo.loadFromNBT(nbTag, Math.max(count, 0)); - } + private int calculateShiftedValue(int originalCount, int shift, int shiftMultiplier) { + long newValue = originalCount + (long) shift * shiftMultiplier; + newValue -= Math.floorMod(newValue, (shift * shiftMultiplier)); + if (newValue <= Integer.MAX_VALUE) { + return (int) newValue; + } else { + return originalCount; + } + } + + private void shiftRequestedAmount(ItemStackMetadata meta, int shift, int shiftMultiplier) { + long newValue = calculateShiftedValue(meta.requestedAmount, shift, shiftMultiplier); + if (!NEIClientConfig.allowNegativeRequests()) { + newValue = Math.max(0, newValue); } + meta.requestedAmount = (int) newValue; - return null; } public boolean pullBookmarkItems(int groupId, boolean onlyIngredients) { diff --git a/src/main/java/codechicken/nei/NEIClientConfig.java b/src/main/java/codechicken/nei/NEIClientConfig.java index afcc93bb6..c8f3f9861 100644 --- a/src/main/java/codechicken/nei/NEIClientConfig.java +++ b/src/main/java/codechicken/nei/NEIClientConfig.java @@ -280,6 +280,12 @@ public boolean onClick(int button) { tag.getTag("inventory.bookmarks.craftingChainDir").getIntValue(1); API.addOption(new OptionCycled("inventory.bookmarks.craftingChainDir", 2, true)); + tag.getTag("inventory.bookmarks.allowNegativeRequests").getBooleanValue(false); + API.addOption(new OptionToggleButton("inventory.bookmarks.allowNegativeRequests", true)); + + tag.getTag("inventory.bookmarks.showCatalysts").getIntValue(0); + API.addOption(new OptionCycled("inventory.bookmarks.showCatalysts", 3, true)); + tag.getTag("inventory.bookmarks.ignorePotionOverlap").setComment("Ignore overlap with potion effect HUD") .getBooleanValue(false); API.addOption(new OptionToggleButton("inventory.bookmarks.ignorePotionOverlap", true)); @@ -720,6 +726,14 @@ public static boolean showRecipeMarker() { return getBooleanSetting("inventory.bookmarks.showRecipeMarker"); } + public static boolean allowNegativeRequests() { + return getBooleanSetting("inventory.bookmarks.allowNegativeRequests"); + } + + public static int showUnusedCatalysts() { + return getIntSetting("inventory.bookmarks.showCatalysts"); + } + public static boolean showItemQuantityWidget() { return getBooleanSetting("inventory.showItemQuantityWidget"); } diff --git a/src/main/java/codechicken/nei/bookmarks/crafts/BookmarkCraftingChain.java b/src/main/java/codechicken/nei/bookmarks/crafts/BookmarkCraftingChain.java index 3544bd40e..841e2c1c9 100644 --- a/src/main/java/codechicken/nei/bookmarks/crafts/BookmarkCraftingChain.java +++ b/src/main/java/codechicken/nei/bookmarks/crafts/BookmarkCraftingChain.java @@ -2,21 +2,18 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import net.minecraft.item.ItemStack; import codechicken.nei.BookmarkPanel; import codechicken.nei.bookmarks.crafts.graph.CraftingGraph; -import codechicken.nei.bookmarks.crafts.graph.CraftingGraphNode; import codechicken.nei.bookmarks.crafts.graph.ItemGraphNode; import codechicken.nei.bookmarks.crafts.graph.RecipeGraphNode; import codechicken.nei.recipe.BookmarkRecipeId; -import codechicken.nei.recipe.StackInfo; public class BookmarkCraftingChain { @@ -26,10 +23,10 @@ public void refresh(ArrayList groupItems, boolean skipCal if (groupItems.isEmpty()) { return; } - Map nodes = new HashMap<>(); + this.graph = new CraftingGraph(); - Map> groupedByRecipe = new HashMap<>(); - Map recipes = new HashMap<>(); + LinkedHashMap> groupedByRecipe = new LinkedHashMap<>(); + LinkedHashMap recipes = new LinkedHashMap<>(); List itemsWithoutRecipe = new ArrayList<>(); for (ItemStackWithMetadata groupItem : groupItems) { @@ -65,10 +62,7 @@ public void refresh(ArrayList groupItems, boolean skipCal if (recipe != null) { RecipeGraphNode node = new RecipeGraphNode(recipe, inputs, outputs); for (ItemStackWithMetadata output : outputs) { - String key = StackInfo.getItemStackGUID(output.getStack()); - if (!nodes.containsKey(key) || !(nodes.get(key) instanceof RecipeGraphNode)) { - nodes.put(key, node); - } + graph.addNode(output, node); } } } @@ -77,19 +71,13 @@ public void refresh(ArrayList groupItems, boolean skipCal } for (ItemStackWithMetadata itemWithoutRecipe : itemsWithoutRecipe) { - String key = StackInfo.getItemStackGUID(itemWithoutRecipe.getStack()); ItemGraphNode node = new ItemGraphNode(itemWithoutRecipe); - if (!nodes.containsKey(key) || !(nodes.get(key) instanceof RecipeGraphNode)) { - nodes.put(key, node); - } + graph.addNode(itemWithoutRecipe, node); } - this.graph = new CraftingGraph(nodes); - List requestedItems = groupItems.stream().filter(it -> !it.getMeta().ingredient) - .collect(Collectors.toList()); if (skipCalculation) { - this.graph.postProcess(Collections.emptyMap()); + this.graph.postProcess(); } else { - this.graph.dfs(requestedItems); + this.graph.runAll(); } } @@ -98,6 +86,11 @@ public Map getCalculatedItems() { return this.graph.getCalculatedItems(); } + public Map getCalculatedRemainders() { + if (this.graph == null) return Collections.emptyMap(); + return this.graph.getCalculatedRemainders(); + } + public Map getCalculatedCraftCounts() { if (this.graph == null) return Collections.emptyMap(); return this.graph.getCalculatedCraftCounts(); @@ -113,18 +106,23 @@ public Set getOutputSlots() { return this.graph.getCraftedOutputSlots(); } - public Set getInputStacks() { + public Set getConflictingSlots() { if (this.graph == null) return Collections.emptySet(); + return this.graph.getConflictingSlots(); + } + + public List getInputStacks() { + if (this.graph == null) return Collections.emptyList(); return this.graph.getInputStacks(); } - public Set getOutputStacks() { - if (this.graph == null) return Collections.emptySet(); + public List getOutputStacks() { + if (this.graph == null) return Collections.emptyList(); return this.graph.getOutputStacks(); } - public Set getRemainingStacks() { - if (this.graph == null) return Collections.emptySet(); + public List getRemainingStacks() { + if (this.graph == null) return Collections.emptyList(); return this.graph.getRemainingStacks(); } } diff --git a/src/main/java/codechicken/nei/bookmarks/crafts/ItemStackWithMetadata.java b/src/main/java/codechicken/nei/bookmarks/crafts/ItemStackWithMetadata.java index 61cb79c01..fce65c311 100644 --- a/src/main/java/codechicken/nei/bookmarks/crafts/ItemStackWithMetadata.java +++ b/src/main/java/codechicken/nei/bookmarks/crafts/ItemStackWithMetadata.java @@ -6,17 +6,17 @@ public class ItemStackWithMetadata { - private Integer gridIdx; + private final int gridIdx; private final ItemStack stack; private final BookmarkPanel.ItemStackMetadata meta; - public ItemStackWithMetadata(Integer idx, ItemStack stack, BookmarkPanel.ItemStackMetadata meta) { + public ItemStackWithMetadata(int idx, ItemStack stack, BookmarkPanel.ItemStackMetadata meta) { this.gridIdx = idx; this.stack = stack; this.meta = meta; } - public Integer getGridIdx() { + public int getGridIdx() { return gridIdx; } @@ -27,9 +27,4 @@ public ItemStack getStack() { public BookmarkPanel.ItemStackMetadata getMeta() { return meta; } - - public void setGridIdx(Integer gridIdx) { - this.gridIdx = gridIdx; - } - } diff --git a/src/main/java/codechicken/nei/bookmarks/crafts/graph/CraftingGraph.java b/src/main/java/codechicken/nei/bookmarks/crafts/graph/CraftingGraph.java index 99c021def..2e76ec8fb 100644 --- a/src/main/java/codechicken/nei/bookmarks/crafts/graph/CraftingGraph.java +++ b/src/main/java/codechicken/nei/bookmarks/crafts/graph/CraftingGraph.java @@ -2,13 +2,12 @@ import static codechicken.nei.recipe.StackInfo.getItemStackGUID; -import java.util.ArrayDeque; -import java.util.Collection; -import java.util.Deque; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -16,6 +15,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fluids.FluidStack; import codechicken.nei.NEIServerUtils; import codechicken.nei.bookmarks.crafts.ItemStackWithMetadata; @@ -24,231 +24,402 @@ public class CraftingGraph { - private static class QueueElement { + private final Map nodes = new HashMap<>(); + private final Map> allNodes = new HashMap<>(); - private final CraftingGraphNode node; - private final String requestedKey; - private final int requestedAmount; - private final Set history; + private final Map requestedItems = new HashMap<>(); - public QueueElement(CraftingGraphNode node, String requestedKey, int requestedAmount) { - this(node, requestedKey, requestedAmount, new HashSet<>()); - } + private final Map itemStackMapping = new HashMap<>(); - public QueueElement(CraftingGraphNode node, String requestedKey, int requestedAmount, - Set history) { - this.node = node; - this.requestedKey = requestedKey; - this.requestedAmount = requestedAmount; - this.history = history; - } + // For showing numbers on top of stacks + private final Map calculatedItems = new LinkedHashMap<>(); + private final Map calculatedRemainders = new LinkedHashMap<>(); + private final Map calculatedCraftCounts = new LinkedHashMap<>(); + + // For coloring stack backgrounds + private final Set inputSlots = new HashSet<>(); + private final Set conflictingSlots = new HashSet<>(); + private final Set outputSlots = new HashSet<>(); + + // For the tooltip + private final List inputStacks = new ArrayList<>(); + private final List outputStacks = new ArrayList<>(); + private final List remainingStacks = new ArrayList<>(); - public QueueElement withNextNode(CraftingGraphNode newNode, String requestedItemKey, int requestedItemCount) { - Set newHistory = new HashSet<>(this.history); - if (this.node instanceof RecipeGraphNode) { - newHistory.add(((RecipeGraphNode) this.node).getRecipe().getRecipeId()); + public void addNode(ItemStackWithMetadata output, CraftingGraphNode node) { + String key = StackInfo.getItemStackGUID(output.getStack()); + if (!nodes.containsKey(key) || !(nodes.get(key) instanceof RecipeGraphNode)) { + nodes.put(key, node); + if (output.getMeta().requestedAmount != 0) { + requestedItems.put(key, output.getMeta().requestedAmount); } - return new QueueElement(newNode, requestedItemKey, requestedItemCount, newHistory); + } else { + conflictingSlots.add(output.getGridIdx()); + } + if (node instanceof RecipeGraphNode || node instanceof FluidConversionGraphNode) { + if (!allNodes.containsKey(key)) { + allNodes.put(key, new ArrayList<>()); + } + allNodes.get(key).add(node); } } - private Map nodes; - - private Map itemStackDummies = new HashMap<>(); - - private Map calculatedItems = new HashMap<>(); - private Map calculatedCraftCounts = new HashMap<>(); + public void runAll() { + preProcess(); + for (Map.Entry entry : requestedItems.entrySet()) { + if (nodes.containsKey(entry.getKey())) { + CraftingGraphNode node = nodes.get(entry.getKey()); + if (entry.getValue() < 0) { + node.addToRemainders(entry.getKey(), -entry.getValue()); + } + } + } + for (Map.Entry entry : requestedItems.entrySet()) { + if (entry.getValue() > 0) { + dfs(entry.getKey(), entry.getValue(), new HashSet<>(), true); + } + } + postProcess(); + } - private Set inputSlots = new HashSet<>(); - private Set craftedOutputSlots = new HashSet<>(); + public void preProcess() { + for (CraftingGraphNode node : nodes.values()) { + if (node instanceof RecipeGraphNode recipeNode) { + for (ItemStackWithMetadata stack : recipeNode.getPinnedInputs()) { + itemStackMapping.put(getItemStackGUID(stack.getStack()), stack.getStack()); + } + for (ItemStackWithMetadata stack : recipeNode.getPinnedOutputs()) { + itemStackMapping.put(getItemStackGUID(stack.getStack()), stack.getStack()); + } + } + } - private Set inputStacks = new HashSet<>(); - private Set outputStacks = new HashSet<>(); - private Set remainingStacks = new HashSet<>(); + // Add fake fluid conversion recipes + // Identity LinkedHashSet is intentional + Map> recipesWithFluidOutputs = new HashMap<>(); + Map> recipesWithFluidInputs = new HashMap<>(); - public CraftingGraph(Map nodes) { - this.nodes = nodes; for (CraftingGraphNode node : nodes.values()) { if (node instanceof RecipeGraphNode recipeNode) { - recipeNode.getRecipe().allIngredients.stream().flatMap(Collection::stream) - .forEach(it -> itemStackDummies.put(getItemStackGUID(it), it)); - recipeNode.getRecipe().result.forEach(it -> itemStackDummies.put(getItemStackGUID(it), it)); + for (ItemStackWithMetadata pinnedOutput : recipeNode.getPinnedOutputs()) { + tryAddFluid(pinnedOutput, recipesWithFluidOutputs); + } } } - } - - public void dfs(List request) { - Deque stack = new ArrayDeque<>(); - - Map inputTotal = new HashMap<>(); - - for (ItemStackWithMetadata item : request) { - String key = getItemStackGUID(item.getStack()); - int requestedAmount = item.getMeta().requestedAmount; - if (nodes.containsKey(key)) { - CraftingGraphNode node = nodes.get(key); - if (requestedAmount > 0) { - stack.addFirst(new QueueElement(node, key, requestedAmount)); - outputStacks.add(withStackSize(item.getStack(), requestedAmount)); - } else if (requestedAmount < 0) { - node.addToRemainders(key, -requestedAmount); + for (CraftingGraphNode node : nodes.values()) { + if (node instanceof RecipeGraphNode recipeNode) { + for (ItemStackWithMetadata pinnedInput : recipeNode.getPinnedInputs()) { + tryAddFluid(pinnedInput, recipesWithFluidInputs); } } } + Set commonFluids = new HashSet<>(recipesWithFluidInputs.keySet()); + commonFluids.retainAll(recipesWithFluidOutputs.keySet()); + for (String commonFluid : commonFluids) { + // inversion of input <-> output is intentional + LinkedHashSet nodeOutputs = recipesWithFluidInputs.get(commonFluid); + LinkedHashSet nodeInputs = recipesWithFluidOutputs.get(commonFluid); + FluidConversionGraphNode node = new FluidConversionGraphNode(nodeInputs, nodeOutputs); + for (ItemStackWithMetadata output : nodeOutputs) { + String outputKey = getItemStackGUID(output.getStack()); + if (!nodes.containsKey(outputKey)) { + nodes.put(outputKey, node); + } + if (!allNodes.containsKey(outputKey)) { + allNodes.put(outputKey, new ArrayList<>()); + } + allNodes.get(outputKey).add(node); + } + } + } - while (!stack.isEmpty()) { - QueueElement queueElement = stack.pollFirst(); - CraftingGraphNode node = queueElement.node; - Set history = queueElement.history; - String requestedKey = queueElement.requestedKey; + private void tryAddFluid(ItemStackWithMetadata pinnedItem, Map> recipesWithFluids) { + FluidStack fluidStack = StackInfo.getFluid(pinnedItem.getStack()); + if (fluidStack == null) { + return; + } + String fluidKey = getFluidKey(fluidStack); + if (!recipesWithFluids.containsKey(fluidKey)) { + recipesWithFluids.put(fluidKey, new LinkedHashSet<>()); + } + recipesWithFluids.get(fluidKey).add(pinnedItem); + ItemStack fluidDisplayStack = StackInfo.getFluidDisplayStack(fluidStack); + if (fluidDisplayStack != null) { + itemStackMapping.put(getItemStackGUID(fluidDisplayStack), fluidDisplayStack); + } + } - // Handle item node. - if (node instanceof ItemGraphNode itemGraphNode) { - itemGraphNode.addCrafts(queueElement.requestedAmount); - inputTotal.compute(requestedKey, (k, v) -> (v == null ? 0 : v) + queueElement.requestedAmount); - continue; - } + public int dfs(String requestedKey, int requestedAmount, HashSet history, boolean keepOutputs) { + CraftingGraphNode node = this.nodes.get(requestedKey); + if (node == null) { + return 0; + } - RecipeGraphNode recipeNode = (RecipeGraphNode) node; - BookmarkRecipeId recipeId = recipeNode.getRecipe().getRecipeId(); + // Handle item node + if (node instanceof ItemGraphNode itemGraphNode) { + itemGraphNode.addToRemainders(requestedKey, -requestedAmount); + return requestedAmount; + } - // Handle recursive recipes - if (history.contains(recipeId)) { - inputTotal.compute(requestedKey, (k, v) -> (v == null ? 0 : v) + queueElement.requestedAmount); - continue; - } + // Handle fluid conversion + if (node instanceof FluidConversionGraphNode fluidNode) { + String keyToRequest = fluidNode.getInputKey(); + int itemAmountToRequest = fluidNode.calculateAmountToRequest(requestedKey, requestedAmount, keyToRequest); + + int returnedItemAmount = dfs(keyToRequest, itemAmountToRequest, history, false); - int requestedAmount = recipeNode.takeFromRemainders(requestedKey, queueElement.requestedAmount); + return fluidNode.processResults(requestedKey, keyToRequest, requestedAmount, returnedItemAmount); + } - // Calculate number of requested crafts - int crafts = 0; - for (ItemStack outputItemStack : recipeNode.getRecipe().result) { - if (Objects.equals(getItemStackGUID(outputItemStack), requestedKey) && requestedAmount > 0) { - crafts = NEIServerUtils.divideCeil(requestedAmount, getStackSize(outputItemStack)); - break; + if (!(node instanceof RecipeGraphNode recipeNode)) { + return 0; + } + + int availableAmount = 0; + + // Collect remainders from all nodes first + for (CraftingGraphNode passiveNode : allNodes.get(requestedKey)) { + if (availableAmount >= requestedAmount) { + break; + } + if (passiveNode instanceof FluidConversionGraphNode fluidNode) { + availableAmount += fluidNode.collectRemainders(allNodes, requestedKey, requestedAmount-availableAmount); + } else { + int remainder = passiveNode.getRemainder(requestedKey); + if (remainder > 0) { + int min = Math.min(remainder, requestedAmount - availableAmount); + passiveNode.addToRemainders(requestedKey, -min); + availableAmount += min; } } + } + int amountToRequest = requestedAmount - availableAmount; - for (ItemStack outputItemStack : recipeNode.getRecipe().result) { - String key = getItemStackGUID(outputItemStack); - if (recipeNode.getPinnedOutputKeys().containsKey(key)) { - recipeNode.addToRemainders(key, getStackSize(outputItemStack) * crafts); - } + BookmarkRecipeId recipeId = recipeNode.getRecipeId(); + // Handle recursive recipes + if (history.contains(recipeId)) { + return availableAmount; + } + + if (amountToRequest <= 0) { + if (keepOutputs) { + recipeNode.addToRemainders(requestedKey, requestedAmount); } + return requestedAmount; + } - if (crafts == 0) { - continue; + // Calculate number of requested crafts + int crafts = 0; + for (Map.Entry outputItemStack : recipeNode.getRecipeOutputs().entrySet()) { + if (Objects.equals(outputItemStack.getKey(), requestedKey)) { + crafts = NEIServerUtils.divideCeil(amountToRequest, outputItemStack.getValue()); + break; + } + } + if (!keepOutputs) { + recipeNode.addToRemainders(requestedKey, -amountToRequest); + } + for (Map.Entry outputItemStack : recipeNode.getRecipeOutputs().entrySet()) { + if (recipeNode.getPinnedOutputKeys().containsKey(outputItemStack.getKey())) { + recipeNode.addToRemainders(outputItemStack.getKey(), outputItemStack.getValue() * crafts); } + } - recipeNode.addCrafts(crafts); - final int finalCrafts = crafts; - - // Process inputs - final Map ingredientsToRequest = new LinkedHashMap<>(); - final Map chainInputs = new LinkedHashMap<>(); - - for (Map ingredientCandidates : recipeNode.getRecipeIngredients()) { - // Intersect current recipe pinned inputs with recipe inputs - Set pinnedCurrentRecipeInputs = new HashSet<>(recipeNode.getPinnedInputKeys().keySet()); - pinnedCurrentRecipeInputs.retainAll(ingredientCandidates.keySet()); - // Intersect all pinned outputs with recipe inputs - Set pinnedRecipeOutputs = new HashSet<>(nodes.keySet()); - pinnedRecipeOutputs.retainAll(ingredientCandidates.keySet()); - - if (!pinnedCurrentRecipeInputs.isEmpty() && !pinnedRecipeOutputs.isEmpty()) { - // If item is pinned, proceed to try and request craft. - String key = pinnedRecipeOutputs.iterator().next(); - ItemStack ingredient = ingredientCandidates.get(key); - ingredientsToRequest - .compute(key, (k, v) -> (v == null ? 0 : v) + getStackSize(ingredient) * finalCrafts); - } else if (!pinnedCurrentRecipeInputs.isEmpty()) { - // Otherwise add pinned item to crafting chain inputs - String key = pinnedCurrentRecipeInputs.iterator().next(); - ItemStack ingredient = ingredientCandidates.get(key); - chainInputs.compute(key, (k, v) -> (v == null ? 0 : v) + getStackSize(ingredient) * finalCrafts); - this.inputSlots.add(recipeNode.getPinnedInputKeys().get(key)); - } + recipeNode.addCrafts(crafts); + final int finalCrafts = crafts; - } + // Process inputs + final Map ingredientsToRequest = new LinkedHashMap<>(); - for (Map.Entry entry : ingredientsToRequest.entrySet()) { - CraftingGraphNode requestedNode = nodes.get(entry.getKey()); - stack.addFirst(queueElement.withNextNode(requestedNode, entry.getKey(), entry.getValue())); + for (Map ingredientCandidates : recipeNode.getRecipeIngredients()) { + // Intersect current pinned inputs with recipe inputs + Set pinnedCurrentRecipeInputs = new HashSet<>(recipeNode.getPinnedInputKeys().keySet()); + pinnedCurrentRecipeInputs.retainAll(ingredientCandidates.keySet()); + // Intersect all pinned outputs with recipe inputs + Set pinnedRecipeOutputs = new HashSet<>(this.nodes.keySet()); + pinnedRecipeOutputs.retainAll(ingredientCandidates.keySet()); + + // Nothing to do here + if (pinnedCurrentRecipeInputs.isEmpty()) { + continue; } - for (Map.Entry entry : chainInputs.entrySet()) { - inputTotal.compute(entry.getKey(), (k, v) -> (v == null ? 0 : v) + entry.getValue()); + + String keyToRequest = pinnedCurrentRecipeInputs.iterator().next(); + if (this.nodes.containsKey(keyToRequest)) { // Try to request pinned item exactly or convert fluids; + ingredientsToRequest.compute( + keyToRequest, + (k, v) -> (v == null ? 0 : v) + ingredientCandidates.get(keyToRequest) * finalCrafts); + } else if (!pinnedRecipeOutputs.isEmpty()) { // Fallback to oredict + String fallbackKey = pinnedRecipeOutputs.iterator().next(); + ingredientsToRequest.compute( + fallbackKey, + (k, v) -> (v == null ? 0 : v) + ingredientCandidates.get(fallbackKey) * finalCrafts); + } else { // Otherwise add pinned item to crafting chain inputs + String key = pinnedCurrentRecipeInputs.iterator().next(); + recipeNode.addToChainInputs(key, ingredientCandidates.get(key) * finalCrafts); } } - postProcess(inputTotal); + + for (Map.Entry entry : ingredientsToRequest.entrySet()) { + history.add(recipeId); + int provided = dfs(entry.getKey(), entry.getValue(), history, false); + int chainInputs = entry.getValue() - provided; + recipeNode.addToChainInputs(entry.getKey(), chainInputs); + history.remove(recipeId); + } + return requestedAmount; } - public void postProcess(Map inputTotal) { + public void postProcess() { this.calculatedItems.clear(); this.calculatedCraftCounts.clear(); - this.craftedOutputSlots.clear(); + this.calculatedRemainders.clear(); this.inputStacks.clear(); this.remainingStacks.clear(); - for (Map.Entry entry : inputTotal.entrySet()) { - ItemStack inputStack = this.itemStackDummies.get(entry.getKey()); - if (inputStack != null) { - this.inputStacks.add(withStackSize(inputStack, entry.getValue())); - } - } - Map remainders = new HashMap<>(); + Map inputs = new HashMap<>(); + Map outputs = new HashMap<>(); + + Map consumedEmptyContainers = new HashMap<>(); + Map producedEmptyContainers = new HashMap<>(); + IdentityHashMap distinctNodes = new IdentityHashMap<>(); for (CraftingGraphNode value : nodes.values()) { distinctNodes.put(value, null); } + for (CraftingGraphNode node : distinctNodes.keySet()) { + if (node instanceof FluidConversionGraphNode fluidNode) { + for (Map.Entry containerEntry : fluidNode.getConsumedEmptyContainers().entrySet()) { + consumedEmptyContainers.compute( + containerEntry.getKey(), + (k, v) -> (v == null ? 0 : v) + containerEntry.getValue()); + } + for (Map.Entry containerEntry : fluidNode.getProducedEmptyContainers().entrySet()) { + producedEmptyContainers.compute( + containerEntry.getKey(), + (k, v) -> (v == null ? 0 : v) + containerEntry.getValue()); + } + } + } + for (CraftingGraphNode node : distinctNodes.keySet()) { if (node instanceof RecipeGraphNode recipeNode) { for (ItemStackWithMetadata pinnedOutput : recipeNode.getPinnedOutputs()) { String key = getItemStackGUID(pinnedOutput.getStack()); - int newCount = recipeNode.getRecipe().result.stream() - .filter(it -> Objects.equals(key, getItemStackGUID(it))).findFirst() - .map(it -> getStackSize(it) * recipeNode.getCrafts()).orElse(0); + int calculatedCount = recipeNode.getRecipeOutputs().getOrDefault(key, 0) * recipeNode.getCrafts(); + // Remove empty containers from results + if (consumedEmptyContainers.containsKey(key)) { + int toRemove = Math.min(recipeNode.getRemainder(key), consumedEmptyContainers.get(key)); + consumedEmptyContainers.put(key, consumedEmptyContainers.get(key) - toRemove); + recipeNode.addToRemainders(key, -toRemove); + } - ItemStack stack = withStackSize(pinnedOutput.getStack(), newCount); - this.calculatedItems.put(pinnedOutput.getGridIdx(), stack); + this.calculatedItems + .put(pinnedOutput.getGridIdx(), withStackSize(pinnedOutput.getStack(), calculatedCount)); + this.calculatedRemainders + .put(pinnedOutput.getGridIdx(), withStackSize(pinnedOutput.getStack(), recipeNode.getRemainder(key))); this.calculatedCraftCounts.put(pinnedOutput.getGridIdx(), recipeNode.getCrafts()); - if (newCount > 0) { - this.craftedOutputSlots.add(pinnedOutput.getGridIdx()); - } } + for (ItemStackWithMetadata pinnedInput : recipeNode.getPinnedInputs()) { String key = getItemStackGUID(pinnedInput.getStack()); - int newCount = recipeNode.getRecipe().allIngredients.stream().flatMap(Collection::stream) - .filter(it -> Objects.requireNonNull(getItemStackGUID(it)).equals(key)) - .map(it -> getStackSize(it) * recipeNode.getCrafts()).mapToInt(it -> it).sum(); + int calculatedCount = recipeNode.getRecipeIngredients().stream() + .flatMap(it -> it.entrySet().stream()) + .filter(it -> Objects.requireNonNull(it.getKey()).equals(key)) + .mapToInt(it -> it.getValue() * recipeNode.getCrafts()).sum(); + // Remove empty containers from results + if (producedEmptyContainers.containsKey(key)) { + int toRemove = Math.min(recipeNode.getChainInput(key), producedEmptyContainers.get(key)); + producedEmptyContainers.put(key, producedEmptyContainers.get(key) - toRemove); + recipeNode.addToChainInputs(key, -toRemove); + } - ItemStack stack = withStackSize(pinnedInput.getStack(), newCount); - this.calculatedItems.put(pinnedInput.getGridIdx(), stack); + this.calculatedItems + .put(pinnedInput.getGridIdx(), withStackSize(pinnedInput.getStack(), calculatedCount)); + this.calculatedRemainders.put( + pinnedInput.getGridIdx(), + withStackSize(pinnedInput.getStack(), recipeNode.getChainInput(key))); } for (Map.Entry entry : recipeNode.getRemainders().entrySet()) { + int slotIdx = recipeNode.getPinnedOutputKeys().get(entry.getKey()); + this.outputSlots.add(slotIdx); + int output = Math.min(entry.getValue(), requestedItems.getOrDefault(entry.getKey(), 0)); + int remainder = Math.max(0, entry.getValue() - requestedItems.getOrDefault(entry.getKey(), 0)); + remainders.compute(entry.getKey(), (k, v) -> (v == null ? 0 : v) + remainder); + outputs.compute(entry.getKey(), (k, v) -> (v == null ? 0 : v) + output); + } + + for (Map.Entry entry : recipeNode.getChainInputs().entrySet()) { + int slotIdx = recipeNode.getPinnedInputKeys().get(entry.getKey()); + this.inputSlots.add(slotIdx); + inputs.compute(entry.getKey(), (k, v) -> (v == null ? 0 : v) + entry.getValue()); + } + } else if (node instanceof ItemGraphNode itemGraphNode) { + ItemStackWithMetadata pinnedItem = itemGraphNode.getPinnedItem(); + String key = getItemStackGUID(pinnedItem.getStack()); + int amount = itemGraphNode.getRemainder(key); + ItemStack stack = withStackSize(pinnedItem.getStack(), amount); + this.calculatedItems.put(pinnedItem.getGridIdx(), stack); + this.calculatedRemainders.put(pinnedItem.getGridIdx(), stack); + this.calculatedCraftCounts.put(pinnedItem.getGridIdx(), 0); + this.inputSlots.add(pinnedItem.getGridIdx()); + inputs.compute(key, (k, v) -> (v == null ? 0 : v) + amount); + } else if (node instanceof FluidConversionGraphNode fluidNode) { + Map.Entry entry = fluidNode.getRemainders().entrySet().iterator().next(); + if (entry.getKey() != null) { remainders.compute(entry.getKey(), (k, v) -> (v == null ? 0 : v) + entry.getValue()); } - } else { - ItemGraphNode itemGraphNode = (ItemGraphNode) node; - ItemStack stack = withStackSize( - itemGraphNode.getPinnedItem().getStack(), - itemGraphNode.getRequestedItems()); - this.calculatedItems.put(itemGraphNode.getPinnedItem().getGridIdx(), stack); + + for (Map.Entry containerEntry : fluidNode.getConsumedEmptyContainers().entrySet()) { + if (remainders.containsKey(containerEntry.getKey())) { + remainders.compute( + containerEntry.getKey(), + (k, v) -> (v == null ? 0 : v) - containerEntry.getValue()); + } + } + for (Map.Entry containerEntry : fluidNode.getProducedEmptyContainers().entrySet()) { + if (inputs.containsKey(containerEntry.getKey())) { + inputs.compute( + containerEntry.getKey(), + (k, v) -> (v == null ? 0 : v) - containerEntry.getValue()); + } + } } } - for (Map.Entry entry : remainders.entrySet()) { - ItemStack stack = itemStackDummies.get(entry.getKey()); - this.remainingStacks.add(withStackSize(stack, entry.getValue())); + + convertMapToStacks(inputs, this.inputStacks); + convertMapToStacks(remainders, this.remainingStacks); + convertMapToStacks(outputs, this.outputStacks); + } + + private void convertMapToStacks(Map map, List stacks) { + for (Map.Entry entry : map.entrySet()) { + ItemStack outputStack = this.itemStackMapping.get(entry.getKey()); + if (outputStack != null && entry.getValue() > 0) { + stacks.add(withStackSize(outputStack, entry.getValue())); + } } } + private static String getFluidKey(FluidStack fluidStack) { + NBTTagCompound nbTag = new NBTTagCompound(); + nbTag.setBoolean("fluidStack", true); + nbTag.setString("gtFluidName", fluidStack.getFluid().getName()); + return nbTag.toString(); + } + public Map getCalculatedItems() { return calculatedItems; } + public Map getCalculatedRemainders() { + return calculatedRemainders; + } + public Map getCalculatedCraftCounts() { return calculatedCraftCounts; } @@ -258,27 +429,34 @@ public Set getInputSlots() { } public Set getCraftedOutputSlots() { - return craftedOutputSlots; + return outputSlots; } - public Set getInputStacks() { + public Set getConflictingSlots() { + return conflictingSlots; + } + + public List getInputStacks() { return inputStacks; } - public Set getOutputStacks() { + public List getOutputStacks() { return outputStacks; } - public Set getRemainingStacks() { + public List getRemainingStacks() { return remainingStacks; } - private int getStackSize(ItemStack itemStack) { - return StackInfo.itemStackToNBT(itemStack).getInteger("Count"); - } - - private ItemStack withStackSize(ItemStack itemStack, int stackSize) { + private static ItemStack withStackSize(ItemStack itemStack, int stackSize) { NBTTagCompound tagCompound = StackInfo.itemStackToNBT(itemStack); return StackInfo.loadFromNBT(tagCompound, stackSize); } + + public static int getStackSize(ItemStack itemStack) { + if (itemStack == null) { + return 0; + } + return StackInfo.itemStackToNBT(itemStack).getInteger("Count"); + } } diff --git a/src/main/java/codechicken/nei/bookmarks/crafts/graph/CraftingGraphNode.java b/src/main/java/codechicken/nei/bookmarks/crafts/graph/CraftingGraphNode.java index 3fdc5e1d8..a0d597cd6 100644 --- a/src/main/java/codechicken/nei/bookmarks/crafts/graph/CraftingGraphNode.java +++ b/src/main/java/codechicken/nei/bookmarks/crafts/graph/CraftingGraphNode.java @@ -1,6 +1,12 @@ package codechicken.nei.bookmarks.crafts.graph; +import java.util.Map; + public interface CraftingGraphNode { - void addToRemainders(String itemKey, int remainder); + int addToRemainders(String itemKey, int remainder); + + int getRemainder(String itemKey); + + Map getRemainders(); } diff --git a/src/main/java/codechicken/nei/bookmarks/crafts/graph/FluidConversionGraphNode.java b/src/main/java/codechicken/nei/bookmarks/crafts/graph/FluidConversionGraphNode.java new file mode 100644 index 000000000..e7f1d258f --- /dev/null +++ b/src/main/java/codechicken/nei/bookmarks/crafts/graph/FluidConversionGraphNode.java @@ -0,0 +1,138 @@ +package codechicken.nei.bookmarks.crafts.graph; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidContainerRegistry; +import net.minecraftforge.fluids.FluidStack; + +import codechicken.nei.NEIServerUtils; +import codechicken.nei.bookmarks.crafts.ItemStackWithMetadata; +import codechicken.nei.recipe.StackInfo; + +public class FluidConversionGraphNode implements CraftingGraphNode { + private final Map conversionInputs = new HashMap<>(); + private final Map conversionOutputs = new HashMap<>(); + + private final Map consumedEmptyContainers = new HashMap<>(); + private final Map producedEmptyContainers = new HashMap<>(); + + private final Map keyToEmptyContainer = new HashMap<>(); + + private final String displayStackGUID; + private int fluidRemainder = 0; + + public FluidConversionGraphNode(Set inputFluidStacks, Set outputFluidStacks) { + for (ItemStackWithMetadata inputFluidStack : inputFluidStacks) { + ItemStack stack = inputFluidStack.getStack(); + FluidStack fluidStack = StackInfo.getFluid(stack); + String key = StackInfo.getItemStackGUID(stack); + conversionInputs.put(key, StackInfo.isFluidContainer(stack) ? fluidStack.amount : 1); + + ItemStack emptyContainer = FluidContainerRegistry.drainFluidContainer(stack); + if (emptyContainer != null) { + keyToEmptyContainer.put(key, StackInfo.getItemStackGUID(emptyContainer)); + } + } + + for (ItemStackWithMetadata outputFluidStack : outputFluidStacks) { + ItemStack stack = outputFluidStack.getStack(); + FluidStack fluidStack = StackInfo.getFluid(stack); + String key = StackInfo.getItemStackGUID(stack); + conversionOutputs.put(key, StackInfo.isFluidContainer(stack) ? fluidStack.amount : 1); + + ItemStack emptyContainer = FluidContainerRegistry.drainFluidContainer(stack); + if (emptyContainer != null) { + keyToEmptyContainer.put(key, StackInfo.getItemStackGUID(emptyContainer)); + } + } + + FluidStack fluidStack = StackInfo.getFluid(outputFluidStacks.iterator().next().getStack()); + this.displayStackGUID = StackInfo.getItemStackGUID(StackInfo.getFluidDisplayStack(fluidStack)); + } + + @Override + public int addToRemainders(String itemKey, int remainder) { + this.fluidRemainder += remainder; + return remainder; + } + + @Override + public int getRemainder(String itemKey) { + return fluidRemainder; + } + + @Override + public Map getRemainders() { + return Collections.singletonMap(displayStackGUID, fluidRemainder); + } + + public int calculateAmountToRequest(String requestedKey, int requestedAmount, String inputKey) { + int outputFluidSize = conversionOutputs.get(requestedKey); + int inputFluidSize = conversionInputs.get(inputKey); + int fluidAmountToRequest = requestedAmount * outputFluidSize - fluidRemainder; + return NEIServerUtils.divideCeil(fluidAmountToRequest, inputFluidSize); + } + + public String getInputKey() { + return conversionInputs.entrySet().stream().findFirst().get().getKey(); + } + + public int processResults(String outputKey, String inputKey, int outputItemRequested, int inputItemReturned) { + int outputFluidSize = conversionOutputs.get(outputKey); + int inputFluidSize = conversionInputs.get(inputKey); + int fluidRequested = outputItemRequested * outputFluidSize; + int fluidReturned = inputItemReturned * inputFluidSize; + this.fluidRemainder += fluidRequested - fluidReturned; + + if (keyToEmptyContainer.containsKey(outputKey)) { + String containerKey = keyToEmptyContainer.get(outputKey); + consumedEmptyContainers.compute(containerKey, (k, v) -> (v == null ? 0 : v) + outputItemRequested); + } + + if (keyToEmptyContainer.containsKey(inputKey)) { + String containerKey = keyToEmptyContainer.get(inputKey); + producedEmptyContainers.compute(containerKey, (k, v) -> (v == null ? 0 : v) + inputItemReturned); + } + return fluidReturned / outputFluidSize; + } + + public Map getConsumedEmptyContainers() { + return consumedEmptyContainers; + } + + public Map getProducedEmptyContainers() { + return producedEmptyContainers; + } + + public int collectRemainders(Map> allNodes, String requestedKey, int requestedAmount) { + int outputFluidAmount = conversionOutputs.get(requestedKey); + int fluidAmount = requestedAmount * outputFluidAmount - this.fluidRemainder; + for (Map.Entry entry : this.conversionInputs.entrySet()) { + String inputKey = entry.getKey(); + int inputFluidAmount = entry.getValue(); + for (CraftingGraphNode node : allNodes.get(inputKey)) { + int neededItemRemainder = NEIServerUtils.divideCeil(fluidAmount, inputFluidAmount); + int itemRemainder = node.getRemainder(inputKey); + if (itemRemainder > 0) { + int satisfiedAmount = Math.min(neededItemRemainder, itemRemainder); + node.addToRemainders(inputKey, -satisfiedAmount); + fluidAmount -= satisfiedAmount * inputFluidAmount; + if (fluidAmount < 0) { + this.fluidRemainder = -fluidAmount; + return requestedAmount; + } + } + } + } + + int unsatisfiedItemAmount = NEIServerUtils.divideCeil(fluidAmount, outputFluidAmount); + this.fluidRemainder = unsatisfiedItemAmount * outputFluidAmount - fluidAmount; + return requestedAmount - unsatisfiedItemAmount; + } +} diff --git a/src/main/java/codechicken/nei/bookmarks/crafts/graph/ItemGraphNode.java b/src/main/java/codechicken/nei/bookmarks/crafts/graph/ItemGraphNode.java index 79a622028..fdab6eb42 100644 --- a/src/main/java/codechicken/nei/bookmarks/crafts/graph/ItemGraphNode.java +++ b/src/main/java/codechicken/nei/bookmarks/crafts/graph/ItemGraphNode.java @@ -1,7 +1,11 @@ package codechicken.nei.bookmarks.crafts.graph; +import java.util.Collections; +import java.util.Map; + import codechicken.nei.bookmarks.crafts.ItemStackWithMetadata; +import codechicken.nei.recipe.StackInfo; public class ItemGraphNode implements CraftingGraphNode { @@ -17,16 +21,19 @@ public ItemStackWithMetadata getPinnedItem() { return pinnedItem; } - public int getRequestedItems() { + @Override + public int addToRemainders(String itemKey, int remainder) { + requestedItems -= remainder; return requestedItems; } - public void addToRemainders(String itemKey, int remainder) { - requestedItems -= remainder; + @Override + public Map getRemainders() { + return Collections.singletonMap(StackInfo.getItemStackGUID(pinnedItem.getStack()), -requestedItems); } - public int addCrafts(int crafts) { - this.requestedItems += crafts; - return this.requestedItems; + @Override + public int getRemainder(String itemKey) { + return -requestedItems; } } diff --git a/src/main/java/codechicken/nei/bookmarks/crafts/graph/RecipeGraphNode.java b/src/main/java/codechicken/nei/bookmarks/crafts/graph/RecipeGraphNode.java index df59293e2..b1410aa92 100644 --- a/src/main/java/codechicken/nei/bookmarks/crafts/graph/RecipeGraphNode.java +++ b/src/main/java/codechicken/nei/bookmarks/crafts/graph/RecipeGraphNode.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -9,6 +10,7 @@ import codechicken.nei.BookmarkPanel.BookmarkRecipe; import codechicken.nei.bookmarks.crafts.ItemStackWithMetadata; +import codechicken.nei.recipe.BookmarkRecipeId; import codechicken.nei.recipe.StackInfo; public class RecipeGraphNode implements CraftingGraphNode { @@ -19,13 +21,15 @@ public class RecipeGraphNode implements CraftingGraphNode { private final Map pinnedOutputKeys = new HashMap<>(); private final BookmarkRecipe recipe; - private final List> recipeIngredients = new ArrayList<>(); + private final List> recipeIngredients = new ArrayList<>(); + private final Map recipeOutputs = new LinkedHashMap<>(); private int crafts = 0; - private Map remainders; + private final Map remainders = new HashMap<>(); + private final Map chainInputs = new HashMap<>(); public RecipeGraphNode(BookmarkRecipe recipe, List pinnedInputs, - List pinnedOutputs) { + List pinnedOutputs) { this.pinnedInputs = pinnedInputs; this.pinnedOutputs = pinnedOutputs; this.recipe = recipe; @@ -37,13 +41,20 @@ public RecipeGraphNode(BookmarkRecipe recipe, List pinned } for (List slotIngredients : recipe.allIngredients) { - Map ingredientMap = new HashMap<>(); + Map ingredientMap = new HashMap<>(); for (ItemStack ingredient : slotIngredients) { - ingredientMap.put(StackInfo.getItemStackGUID(ingredient), ingredient); + String key = StackInfo.getItemStackGUID(ingredient); + int size = CraftingGraph.getStackSize(ingredient); + ingredientMap.compute(key, (k, v) -> v == null ? size : v + size); } recipeIngredients.add(ingredientMap); } - this.remainders = new HashMap<>(); + + for (ItemStack output : recipe.result) { + String key = StackInfo.getItemStackGUID(output); + int size = CraftingGraph.getStackSize(output); + recipeOutputs.compute(key, (k, v) -> v == null ? size : v + size); + } } public List getPinnedInputs() { @@ -62,48 +73,58 @@ public Map getPinnedOutputKeys() { return pinnedOutputKeys; } - public List> getRecipeIngredients() { + public List> getRecipeIngredients() { return recipeIngredients; } - public BookmarkRecipe getRecipe() { - return recipe; + public Map getRecipeOutputs() { + return recipeOutputs; + } + + public BookmarkRecipeId getRecipeId() { + return recipe.getRecipeId(); } public int getCrafts() { return crafts; } - public int addCrafts(int crafts) { + public void addCrafts(int crafts) { this.crafts += crafts; - return this.crafts; - } - - public int takeFromRemainders(String itemKey, int requestedAmount) { - if (remainders.containsKey(itemKey)) { - int remainder = remainders.remove(itemKey); - int requiredNew = requestedAmount - remainder; - if (requiredNew > 0) { - remainders.put(itemKey, -requiredNew); - return requiredNew; - } else if (requiredNew < 0) { - remainders.put(itemKey, -requiredNew); - } - return 0; - } else { - remainders.put(itemKey, -requestedAmount); - return requestedAmount; - } } - public void addToRemainders(String itemKey, int remainder) { + @Override + public int addToRemainders(String itemKey, int remainder) { remainders.compute(itemKey, (k, v) -> { int result = (v == null ? 0 : v) + remainder; if (result == 0) return null; return result; }); + return remainders.getOrDefault(itemKey, 0); + } + + @Override + public int getRemainder(String itemKey) { + return remainders.getOrDefault(itemKey, 0); + } + + public int getChainInput(String itemKey) { + return chainInputs.getOrDefault(itemKey, 0); + } + + public void addToChainInputs(String itemKey, int size) { + chainInputs.compute(itemKey, (k, v) -> { + int result = (v == null ? 0 : v) + size; + if (result == 0) return null; + return result; + }); + } + + public Map getChainInputs() { + return chainInputs; } + @Override public Map getRemainders() { return remainders; } diff --git a/src/main/java/codechicken/nei/recipe/StackInfo.java b/src/main/java/codechicken/nei/recipe/StackInfo.java index 9e3dafe9c..07772406c 100644 --- a/src/main/java/codechicken/nei/recipe/StackInfo.java +++ b/src/main/java/codechicken/nei/recipe/StackInfo.java @@ -38,6 +38,7 @@ protected boolean removeEldestEntry(Map.Entry eldest) { }; private static Method getContainersFromFluid = null; + private static Method getFluidDisplayStack = null; private static Class itemCell = null; static { @@ -48,6 +49,7 @@ protected boolean removeEldestEntry(Map.Entry eldest) { final Class gtUtility = ReflectionHelper .getClass(loader, "gregtech.api.util.GTUtility", "gregtech.api.util.GT_Utility"); getContainersFromFluid = gtUtility.getMethod("getContainersFromFluid", FluidStack.class); + getFluidDisplayStack = gtUtility.getMethod("getFluidDisplayStack", FluidStack.class, boolean.class); itemCell = ReflectionHelper.getClass(loader, "ic2.core.item.resources.ItemCell"); } catch (Exception e) { // do nothing @@ -253,4 +255,17 @@ public static ItemStack getItemStackWithMinimumDamage(ItemStack[] stacks) { return result.copy(); } + + public static ItemStack getFluidDisplayStack(FluidStack fluid) { + if (getFluidDisplayStack == null || fluid == null) { + return null; + } + + try { + Object itemStack = getFluidDisplayStack.invoke(null, fluid, false); + return (ItemStack) itemStack; + } catch (Exception e) { + return null; + } + } } diff --git a/src/main/java/codechicken/nei/recipe/stackinfo/GTFluidStackStringifyHandler.java b/src/main/java/codechicken/nei/recipe/stackinfo/GTFluidStackStringifyHandler.java index 491de8c33..c15fe6a8e 100644 --- a/src/main/java/codechicken/nei/recipe/stackinfo/GTFluidStackStringifyHandler.java +++ b/src/main/java/codechicken/nei/recipe/stackinfo/GTFluidStackStringifyHandler.java @@ -41,13 +41,13 @@ public NBTTagCompound convertItemStackToNBT(ItemStack stack, boolean saveStackSi if (replaceAE2FCFluidDrop || stack.getItem() != GameRegistry.findItem("ae2fc", "fluid_drop")) { final FluidStack fluidStack = getFluid(stack); - if (fluidStack != null) { final NBTTagCompound nbTag = new NBTTagCompound(); nbTag.setString("gtFluidName", fluidStack.getFluid().getName()); nbTag.setInteger("Count", saveStackSize ? fluidStack.amount : 144); return nbTag; } + return null; } return null; @@ -104,4 +104,5 @@ public FluidStack getFluid(ItemStack stack) { return null; } + } diff --git a/src/main/resources/assets/nei/lang/en_US.lang b/src/main/resources/assets/nei/lang/en_US.lang index ed685a677..38421a63c 100644 --- a/src/main/resources/assets/nei/lang/en_US.lang +++ b/src/main/resources/assets/nei/lang/en_US.lang @@ -12,6 +12,7 @@ nei.showHotkeys=Hold %s for hotkeys nei.bookmark.toggle=Bookmarks nei.bookmark.toggle.tip=Toggle visibility of the Bookmark Panel nei.bookmark.pullBookmarkedItems.tip=Pull all bookmarked items from storage to inventory +nei.bookmark.conflictingRecipe.tip=This recipe output conflicts with another recipe in this group nei.bookmark.group=Bookmarks Group nei.bookmark.group.include_group=Create/Include Group @@ -172,6 +173,9 @@ nei.options.inventory.bookmarks=Bookmarks Panel nei.options.inventory.bookmarks.enabled=Bookmarks Panel Visibility nei.options.inventory.bookmarks.enabled.true=Visible nei.options.inventory.bookmarks.enabled.false=Hidden +nei.options.inventory.bookmarks.allowNegativeRequests=Allow Negative Crafting Chain Requests +nei.options.inventory.bookmarks.allowNegativeRequests.true=Yes +nei.options.inventory.bookmarks.allowNegativeRequests.false=No nei.options.inventory.bookmarks.animation=REI Style Animation in Bookmarks nei.options.inventory.bookmarks.animation.true=Yes nei.options.inventory.bookmarks.animation.false=No