diff --git a/src/main/java/codechicken/nei/BookmarkCraftingChain.java b/src/main/java/codechicken/nei/BookmarkCraftingChain.java deleted file mode 100644 index 5ebd3fe13..000000000 --- a/src/main/java/codechicken/nei/BookmarkCraftingChain.java +++ /dev/null @@ -1,319 +0,0 @@ -package codechicken.nei; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - -import net.minecraft.item.ItemStack; -import net.minecraftforge.fluids.FluidStack; - -import codechicken.nei.BookmarkPanel.ItemStackMetadata; -import codechicken.nei.recipe.BookmarkRecipeId; -import codechicken.nei.recipe.StackInfo; - -public class BookmarkCraftingChain { - - public HashMap calculatedItems = new HashMap<>(); - public HashMap multiplier = new HashMap<>(); - - public HashMap inputs = new HashMap<>(); - public HashMap outputs = new HashMap<>(); - public HashMap remainder = new HashMap<>(); - public HashMap intermediate = new HashMap<>(); - - protected static class CraftingChainItem { - - public String guid; - public int stackIndex = 0; - public int recipeIndex = 0; - public int factor = 0; - public int count = 0; - public boolean ingredient = false; - public int fluidCellAmount = 1; - public ItemStack stack = null; - - public CraftingChainItem(ItemStack stack, ItemStackMetadata stackMetadata) { - FluidStack fluid = StackInfo.getFluid(stack); - this.recipeIndex = getRecipeIndex(stackMetadata.recipeId); - this.ingredient = stackMetadata.ingredient; - this.stack = stack; - - if (fluid != null) { - this.stackIndex = getStackIndex(fluid); - this.count = fluid.amount * Math.max(0, stack.stackSize); - this.fluidCellAmount = StackInfo.isFluidContainer(stack) ? fluid.amount : 1; - this.factor = this.fluidCellAmount * stackMetadata.factor; - } else { - this.stackIndex = getStackIndex(stack); - this.count = StackInfo.itemStackToNBT(stack).getInteger("Count"); - this.factor = stackMetadata.factor; - } - - this.guid = this.stackIndex + " " + this.recipeIndex; - } - - public ItemStack getItemStack(long count) { - return StackInfo.loadFromNBT( - StackInfo.itemStackToNBT(this.stack), - this.factor > 0 ? (count / this.fluidCellAmount) : 0); - } - - public static void clearStatic() { - itemCache.clear(); - fluidCache.clear(); - recipeCache.clear(); - } - - private static HashMap itemCache = new HashMap<>(); - - private static int getStackIndex(ItemStack stackA) { - - if (!itemCache.containsKey(stackA)) { - - for (ItemStack item : itemCache.keySet()) { - if (StackInfo.equalItemAndNBT(stackA, item, true)) { - itemCache.put(stackA, itemCache.get(item)); - return itemCache.get(stackA); - } - } - - itemCache.put(stackA, itemCache.size() + fluidCache.size()); - } - - return itemCache.get(stackA); - } - - private static HashMap fluidCache = new HashMap<>(); - - private static int getStackIndex(FluidStack fluidA) { - - if (!fluidCache.containsKey(fluidA)) { - for (FluidStack item : fluidCache.keySet()) { - if (fluidA.isFluidEqual(item)) { - fluidCache.put(fluidA, fluidCache.get(item)); - return fluidCache.get(fluidA); - } - } - - fluidCache.put(fluidA, itemCache.size() + fluidCache.size()); - } - - return fluidCache.get(fluidA); - } - - private static HashMap recipeCache = new HashMap<>(); - - private static int getRecipeIndex(BookmarkRecipeId recipe) { - - if (recipe == null) { - return -1; - } - - if (!recipeCache.containsKey(recipe)) { - for (BookmarkRecipeId item : recipeCache.keySet()) { - if (item.equals(recipe)) { - recipeCache.put(recipe, recipeCache.get(item)); - return recipeCache.get(recipe); - } - } - - recipeCache.put(recipe, recipeCache.size()); - } - - return recipeCache.get(recipe); - } - - } - - protected static class CraftingChainRequest { - - public List items = new ArrayList<>(); - - public HashMap multiplier = new HashMap<>(); - public HashMap counts = new HashMap<>(); - - public HashMap> inputs = new HashMap<>(); - public HashMap> outputs = new HashMap<>(); - - public HashSet initialItems = new HashSet<>(); - public HashSet startedIngrs = new HashSet<>(); - - public CraftingChainRequest(List items, List metadata) { - HashSet inputs = new HashSet<>(); - HashSet outputs = new HashSet<>(); - - for (int index = 0; index < items.size(); index++) { - CraftingChainItem item = new CraftingChainItem(items.get(index), metadata.get(index)); - item.guid = item.ingredient ? "i" + item.guid : "r" + item.guid; - this.items.add(item); - - if (this.multiplier.get(item.recipeIndex) == null) { - this.multiplier.put(item.recipeIndex, 0); - } - - this.counts.put(item.guid, this.counts.getOrDefault(item.guid, 0L) + item.count); - - if (item.ingredient) { - inputs.add(item.recipeIndex); - - if (this.inputs.get(item.stackIndex) == null) { - this.inputs.put(item.stackIndex, new HashSet<>()); - } - - this.inputs.get(item.stackIndex).add(item); - } else { - outputs.add(item.recipeIndex); - - if (this.outputs.get(item.stackIndex) == null) { - this.outputs.put(item.stackIndex, new HashSet<>()); - } - - this.outputs.get(item.stackIndex).add(item); - } - - } - - for (int recipeIndex : this.multiplier.keySet()) { - if (recipeIndex == -1 || !inputs.contains(recipeIndex) || !outputs.contains(recipeIndex)) { - this.initialItems.add(recipeIndex); - } - } - - for (CraftingChainItem item : this.items) { - if (item.ingredient == false && !this.initialItems.contains(item.recipeIndex)) { - this.startedIngrs.add(item.stackIndex); - } - } - - CraftingChainItem.clearStatic(); - } - - } - - public void refresh(List items, List metadata) { - CraftingChainRequest request = new CraftingChainRequest(items, metadata); - HashMap iShift = new HashMap<>(); - HashMap oShift = new HashMap<>(); - boolean change = true; - int iteration = 100; - - while (--iteration > 0 && change) { - change = false; - for (int stackIndex : request.startedIngrs) { - change = calculateShift(request, stackIndex) || change; - } - } - - this.inputs.clear(); - this.outputs.clear(); - this.remainder.clear(); - this.intermediate.clear(); - this.calculatedItems.clear(); - this.multiplier.clear(); - - for (CraftingChainItem item : request.items) { - if (request.initialItems.contains(item.recipeIndex)) { - long iCount = calculateCount(request, request.inputs.get(item.stackIndex)) - - iShift.getOrDefault(item.stackIndex, 0L); - this.calculatedItems.put(item.stack, item.stack); - - if (iCount == 0) { - this.inputs.put(item.stack, item.getItemStack(0)); - } else { - long count = Math.min(iCount, item.count); - this.inputs.put(item.stack, item.getItemStack(count)); - iShift.put(item.stackIndex, iShift.getOrDefault(item.stackIndex, 0L) + count); - } - - } - } - - for (CraftingChainItem item : request.items) { - if (!request.initialItems.contains(item.recipeIndex)) { - long count = item.count + item.factor * request.multiplier.get(item.recipeIndex); - this.calculatedItems.put(item.stack, item.getItemStack(count)); - - if (item.ingredient) { // input - long oCount = calculateCount(request, request.outputs.get(item.stackIndex)) - - oShift.getOrDefault(item.stackIndex, 0L); - - if (oCount < count) { - this.inputs.put(item.stack, item.getItemStack(count - oCount)); - oShift.put(item.stackIndex, oShift.getOrDefault(item.stackIndex, 0L) + oCount); - } else if (oCount >= count) { - this.intermediate.put(item.stack, item.getItemStack(0)); - oShift.put(item.stackIndex, oShift.getOrDefault(item.stackIndex, 0L) + count); - } - - } else { // output - long iCalcCount = calculateCount(request, request.inputs.get(item.stackIndex)); - long iCount = iCalcCount - iShift.getOrDefault(item.stackIndex, 0L); - this.multiplier.put(item.stack, item.factor > 0 ? (int) (count / item.factor) : 0); - - if (iCount >= count) { - this.intermediate.put(item.stack, item.getItemStack(0)); - iShift.put(item.stackIndex, iShift.getOrDefault(item.stackIndex, 0L) + count); - } else if (iCalcCount > 0) { - this.remainder.put(item.stack, item.getItemStack(count - iCount)); - iShift.put(item.stackIndex, iShift.getOrDefault(item.stackIndex, 0L) + iCount); - } else { - this.outputs.put(item.stack, item.getItemStack(count - iCount)); - iShift.put(item.stackIndex, iShift.getOrDefault(item.stackIndex, 0L) + iCount); - } - } - - } - } - - } - - private boolean calculateShift(CraftingChainRequest request, int stackIndex) { - - if (!request.outputs.containsKey(stackIndex)) { - return false; - } - - int minRecipeIndex = 0; - int minShift = Integer.MAX_VALUE; - - for (CraftingChainItem item : request.outputs.get(stackIndex)) { - if (!request.initialItems.contains(item.recipeIndex) && item.factor > 0) { - long ingrCount = calculateCount(request, request.inputs.get(item.stackIndex), item.recipeIndex); - long outputCount = calculateCount(request, request.outputs.get(item.stackIndex)); - long shift = (long) Math.ceil((ingrCount - outputCount) / (double) item.factor); - - if (shift > 0 && shift < minShift) { - minShift = (int) shift; - minRecipeIndex = item.recipeIndex; - } - } - } - - if (minShift < Integer.MAX_VALUE) { - request.multiplier.put(minRecipeIndex, request.multiplier.get(minRecipeIndex) + minShift); - return true; - } - - return false; - } - - private long calculateCount(CraftingChainRequest request, HashSet items) { - return calculateCount(request, items, -1); - } - - private long calculateCount(CraftingChainRequest request, HashSet items, int recipeIndex) { - long count = 0L; - - if (items != null) { - for (CraftingChainItem item : items) { - if (item.recipeIndex != recipeIndex || recipeIndex == -1) { - count += request.counts.get(item.guid) + item.factor * request.multiplier.get(item.recipeIndex); - } - } - } - - return count; - } - -} diff --git a/src/main/java/codechicken/nei/BookmarkPanel.java b/src/main/java/codechicken/nei/BookmarkPanel.java index 1b7874d5b..64680e82c 100644 --- a/src/main/java/codechicken/nei/BookmarkPanel.java +++ b/src/main/java/codechicken/nei/BookmarkPanel.java @@ -17,15 +17,15 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.WeakHashMap; -import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -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; @@ -53,10 +54,17 @@ import codechicken.lib.vec.Rectangle4i; import codechicken.nei.ItemPanel.ItemPanelSlot; import codechicken.nei.api.IBookmarkContainerHandler; +import codechicken.nei.bookmarks.crafts.BookmarkCraftingChain; +import codechicken.nei.bookmarks.crafts.ItemStackWithMetadata; import codechicken.nei.guihook.GuiContainerManager; import codechicken.nei.recipe.BookmarkRecipeId; import codechicken.nei.recipe.GuiCraftingRecipe; import codechicken.nei.recipe.GuiRecipe; +import codechicken.nei.recipe.GuiRecipeTab; +import codechicken.nei.recipe.HandlerInfo; +import codechicken.nei.recipe.ICraftingHandler; +import codechicken.nei.recipe.IRecipeHandler; +import codechicken.nei.recipe.SearchRecipeHandler; import codechicken.nei.recipe.StackInfo; import codechicken.nei.util.NBTJson; import codechicken.nei.util.ReadableNumberConverter; @@ -65,6 +73,7 @@ public class BookmarkPanel extends PanelWidget { protected File bookmarkFile; protected BookmarkLoadingState bookmarksState; + protected BookmarkLoadingState craftingChainState; protected SortableItem sortableItem; protected SortableGroup sortableGroup; protected GroupingItem groupingItem; @@ -195,13 +204,15 @@ public int getBottomRowIndex(BookmarkGrid BGrid) { } } - protected static class ItemStackMetadata { + public static class ItemStackMetadata { public int factor; public int groupId; public BookmarkRecipeId recipeId; - public boolean ingredient = false; - public boolean fluidDisplay = false; + public BookmarkRecipe fullRecipe; + public boolean ingredient; + public boolean fluidDisplay; + public int requestedAmount = 0; public ItemStackMetadata(BookmarkRecipeId recipeId, int factor, boolean ingredient, int groupId, boolean fluidDisplay) { @@ -217,7 +228,14 @@ public ItemStackMetadata(BookmarkRecipeId recipeId, NBTTagCompound nbTag, boolea } public ItemStackMetadata copy() { - return new ItemStackMetadata(this.recipeId, this.factor, this.ingredient, this.groupId, this.fluidDisplay); + ItemStackMetadata itemStackMetadata = new ItemStackMetadata( + this.recipeId, + this.factor, + this.ingredient, + this.groupId, + this.fluidDisplay); + itemStackMetadata.requestedAmount = this.requestedAmount; + return itemStackMetadata; } public boolean equalsRecipe(ItemStackMetadata meta) { @@ -227,6 +245,30 @@ public boolean equalsRecipe(ItemStackMetadata meta) { public boolean equalsRecipe(BookmarkRecipeId recipeId, int groupId) { return groupId == this.groupId && recipeId != null && recipeId.equals(this.recipeId); } + + public BookmarkPanel.BookmarkRecipe getFullRecipe(ItemStack stack) { + if (recipeId == null) { + return null; + } + if (fullRecipe == null) { + final ArrayList handlers = GuiCraftingRecipe + .getCraftingHandlers("recipeId", stack, recipeId); + for (ICraftingHandler handler : handlers) { + HandlerInfo localHandlerInfo = GuiRecipeTab.getHandlerInfo(handler); + if (localHandlerInfo.getHandlerName().equals(recipeId.handlerName)) { + if (!recipeId.ingredients.isEmpty()) { + int refIndex = SearchRecipeHandler.findFirst( + handler, + (recipeIndex) -> recipeId + .equalsIngredients(handler.getIngredientStacks(recipeIndex))); + fullRecipe = BookmarkPanel.constructBookmarkRecipe(handler, refIndex); + } + break; + } + } + } + return fullRecipe; + } } public enum BookmarkViewMode { @@ -244,6 +286,7 @@ public static class BookmarkRecipe { public String handlerName = ""; public List result = new ArrayList<>(); public List ingredients = new ArrayList<>(); + public List> allIngredients = new ArrayList<>(); public BookmarkRecipeId recipeId = null; public BookmarkRecipe(ItemStack... result) { @@ -251,7 +294,6 @@ public BookmarkRecipe(ItemStack... result) { } public BookmarkRecipeId getRecipeId() { - if (!handlerName.isEmpty() && !ingredients.isEmpty() && recipeId == null) { recipeId = new BookmarkRecipeId(handlerName, ingredients); } @@ -343,17 +385,33 @@ public boolean getCraftingMode(int groupId) { } public void setCraftingMode(int groupId, boolean on) { - if ((this.groups.get(groupId).crafting != null) != on) { + if ((this.groups.get(groupId).crafting == null) == on) { this.groups.get(groupId).crafting = on ? new BookmarkCraftingChain() : null; onItemsChanged(); } } public void toggleCraftingMode(int groupId) { - this.groups.get(groupId).toggleCraftingMode(); + BookmarkGroup group = this.groups.get(groupId); + if (group.crafting != null) { + copyStackSizesFromCrafting(groupId, group.crafting); + } + group.toggleCraftingMode(); onItemsChanged(); } + public void copyStackSizesFromCrafting(int groupId, BookmarkCraftingChain craftingChain) { + for (int i = 0; i < metadata.size(); i++) { + ItemStackMetadata metadata = getMetadata(i); + if (metadata.groupId == groupId) { + ItemStack calculatedStack = craftingChain.getCalculatedItems().get(i); + if (calculatedStack != null) { + realItems.set(i, calculatedStack); + } + } + } + } + @Override public int getNumPages() { @@ -732,7 +790,7 @@ private void sortGroup(int groupId) { } } - items.sort((a, b) -> sortingRank.get(a) - sortingRank.get(b)); + items.sort(Comparator.comparingInt(sortingRank::get)); for (int index : items) { sortedItems.add(this.realItems.get(index)); @@ -768,20 +826,20 @@ private void sortIngredients() { sortGroup(groupId); } - if (group.crafting != null && !inEditingState) { - ArrayList groupMetadata = new ArrayList<>(); - ArrayList groupItems = new ArrayList<>(); + if (group.crafting != null) { + ArrayList groupItems = new ArrayList<>(); for (int idx = 0; idx < this.metadata.size(); idx++) { if (this.metadata.get(idx).groupId == groupId) { - groupItems.add(this.realItems.get(idx)); - groupMetadata.add(this.metadata.get(idx)); + ItemStackWithMetadata item = new ItemStackWithMetadata( + idx, + this.realItems.get(idx), + this.metadata.get(idx)); + groupItems.add(item); } } - - group.crafting.refresh(groupItems, groupMetadata); + group.crafting.refresh(groupItems, inEditingState); } - } } @@ -838,6 +896,26 @@ protected void createGroup(GroupingItem groupingItem) { } } + if (groupIdA == DEFAULT_GROUP_ID) { + int start = Math.min(groupingItem.startItemIndexTop, groupingItem.endItemIndexTop); + int end = Math.min(groupingItem.startItemIndexBottom, groupingItem.endItemIndexBottom); + boolean skip = false; + for (int i = start; i <= bottomItemIndex; i++) { + if (this.metadata.get(i).requestedAmount != 0) { + skip = true; + break; + } + } + if (!skip) { + for (int i = start; i <= end; i++) { + ItemStackMetadata meta = this.metadata.get(i); + if (!meta.ingredient) { + meta.requestedAmount = StackInfo.itemStackToNBT(getItem(i)).getInteger("Count"); + } + } + } + } + final HashSet usedSetIds = new HashSet<>(); for (ItemStackMetadata meta : this.metadata) { usedSetIds.add(meta.groupId); @@ -938,8 +1016,8 @@ public ItemStack getItem(int idx) { final ItemStackMetadata meta = this.getMetadata(idx); final BookmarkCraftingChain crafting = this.groups.get(meta.groupId).crafting; - if (crafting != null && crafting.calculatedItems.containsKey(stack)) { - return crafting.calculatedItems.get(stack); + if (crafting != null && crafting.getCalculatedItems().containsKey(idx)) { + return crafting.getCalculatedItems().get(idx); } return stack; @@ -1047,87 +1125,87 @@ protected int getGridRenderingCacheMode() { @Override protected void beforeDrawSlot(@Nullable ItemPanelSlot focus, int idx, Rectangle4i rect) { - - if (LayoutManager.bookmarkPanel.sortableGroup != null - && this.getMetadata(idx).groupId == LayoutManager.bookmarkPanel.sortableGroup.groupId) { + SortableGroup sortableGroup = LayoutManager.bookmarkPanel.sortableGroup; + SortableItem sortableItem = LayoutManager.bookmarkPanel.sortableItem; + if (sortableGroup != null && this.getMetadata(idx).groupId == sortableGroup.groupId) { drawRect(rect.x, rect.y, rect.w, rect.h, 0x66555555); - } else if (LayoutManager.bookmarkPanel.sortableItem != null - && LayoutManager.bookmarkPanel.sortableItem.items.contains(this.realItems.get(idx))) { - drawRect(rect.x, rect.y, rect.w, rect.h, 0x66555555); - } else - if (!LayoutManager.bookmarkPanel.inEditingState()) { - - if (NEIClientUtils.shiftKey()) { - ItemStack stack = this.realItems.get(idx); - ItemStackMetadata meta = this.getMetadata(idx); - BookmarkGroup groupMeta = this.groups.get(meta.groupId); - - if (groupMeta.crafting != null && meta.groupId == this.focusedGroupId) { - - if (groupMeta.crafting.inputs.containsKey(stack)) { - drawRect(rect.x, rect.y, rect.w, rect.h, 0x6645DA75); // inputs - } else if (groupMeta.crafting.outputs.containsKey(stack)) { - drawRect(rect.x, rect.y, rect.w, rect.h, 0x9966CCFF); // exports - } else if (groupMeta.crafting.remainder.containsKey(stack)) { - drawRect(rect.x, rect.y, rect.w, rect.h, 0x9966CCFF); // exports - } - - } 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); + } else if (sortableItem != null && sortableItem.items.contains(this.realItems.get(idx))) { + drawRect(rect.x, rect.y, rect.w, rect.h, 0x66555555); + } else if (!LayoutManager.bookmarkPanel.inEditingState()) { + if (NEIClientUtils.shiftKey()) { + ItemStackMetadata meta = this.getMetadata(idx); + BookmarkGroup groupMeta = this.groups.get(meta.groupId); + BookmarkGrid BGrid = (BookmarkGrid) LayoutManager.bookmarkPanel.grid; + final int overRowIndex = BGrid.getHoveredRowIndex(false); + final int groupId = overRowIndex < 0 ? -1 : BGrid.getRowGroupId(overRowIndex); + + if (groupMeta.crafting != null && meta.groupId == groupId) { + if (groupMeta.crafting.getInputSlots().contains(idx)) { + 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 } else { super.beforeDrawSlot(focus, idx, rect); } - } + } else { + super.beforeDrawSlot(focus, idx, rect); + } + } } @Override protected void afterDrawSlot(@Nullable ItemPanelSlot focus, int idx, Rectangle4i rect) { final ItemStackMetadata meta = this.getMetadata(idx); - if (meta.ingredient == true || meta.recipeId == null - || LayoutManager.bookmarkPanel.sortableItem != null + if (meta.ingredient == true || LayoutManager.bookmarkPanel.sortableItem != null || LayoutManager.bookmarkPanel.sortableGroup != null) { return; } final ItemStack stack = this.realItems.get(idx); final BookmarkGroup groupMeta = this.groups.get(meta.groupId); - int multiplier = 0; - - if (NEIClientUtils.shiftKey()) { - final ItemStackMetadata prevMeta = idx > 0 ? this.getMetadata(idx - 1) : null; - if (prevMeta == null || prevMeta.ingredient || !meta.recipeId.equals(prevMeta.recipeId)) { - if (groupMeta.crafting != null && meta.groupId == this.focusedGroupId - && groupMeta.crafting.multiplier.containsKey(stack)) { - multiplier = this.groups.get(meta.groupId).crafting.multiplier.get(stack); - } else if (focus != null && meta.factor > 0 && meta.equalsRecipe(getMetadata(focus.slotIndex))) { - multiplier = StackInfo.itemStackToNBT(stack).getInteger("Count") / meta.factor; + + 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); + if (crafts != 0) { + text = "x" + ReadableNumberConverter.INSTANCE.toWideReadableForm(crafts); } + + } else { + int requested = meta.requestedAmount; + if (requested > 0) { + text = "+" + ReadableNumberConverter.INSTANCE.toWideReadableForm(requested); + } else if (requested < 0) { + text = ReadableNumberConverter.INSTANCE.toWideReadableForm(requested); + } + } + if (text != null) { + drawRecipeMarker(rect.x, rect.y, GuiContainerManager.getFontRenderer(stack), text, 0xffffff); + return; } } - if (multiplier > 0) { - drawRecipeMarker( - rect.x, - rect.y, - GuiContainerManager.getFontRenderer(stack), - "x" + ReadableNumberConverter.INSTANCE.toWideReadableForm(multiplier), - 16777215); - } else if (meta.recipeId != null && !meta.ingredient && NEIClientConfig.showRecipeMarker()) { + if (meta.recipeId != null && !meta.ingredient && NEIClientConfig.showRecipeMarker()) { drawRecipeMarker(rect.x, rect.y, GuiContainerManager.getFontRenderer(stack), "R", 0xA0A0A0); + return; } - } @Override protected void drawItem(Rectangle4i rect, int idx) { - if ((LayoutManager.bookmarkPanel.sortableGroup == null || this.metadata.get(idx).groupId != LayoutManager.bookmarkPanel.sortableGroup.groupId) && (LayoutManager.bookmarkPanel.sortableItem == null @@ -1137,20 +1215,18 @@ protected void drawItem(Rectangle4i rect, int idx) { final BookmarkGroup groupMeta = this.groups.get(meta.groupId); ItemStack drawStack = realStack; - if (groupMeta.crafting != null && meta.groupId == this.focusedGroupId) { - - if (groupMeta.crafting.inputs.containsKey(drawStack)) { - drawStack = groupMeta.crafting.inputs.get(drawStack); - } else if (groupMeta.crafting.outputs.containsKey(drawStack)) { - drawStack = groupMeta.crafting.outputs.get(drawStack); - } else if (groupMeta.crafting.remainder.containsKey(drawStack)) { - drawStack = groupMeta.crafting.remainder.get(drawStack); - } else if (groupMeta.crafting.intermediate.containsKey(drawStack)) { - drawStack = groupMeta.crafting.intermediate.get(drawStack); + if (groupMeta.crafting != null) { + ItemStack craftingItemStack; + if (NEIClientUtils.shiftKey()) { + craftingItemStack = groupMeta.crafting.getCalculatedRemainders().get(idx); + } else { + craftingItemStack = groupMeta.crafting.getCalculatedItems().get(idx); + } + if (craftingItemStack != null) { + drawStack = craftingItemStack; + } else { + drawStack = StackInfo.loadFromNBT(StackInfo.itemStackToNBT(drawStack), 0); } - - } else if (groupMeta.crafting != null && groupMeta.crafting.calculatedItems.containsKey(drawStack)) { - drawStack = groupMeta.crafting.calculatedItems.get(drawStack); } String stackSize = meta.fluidDisplay || drawStack.stackSize == 0 ? "" @@ -1332,27 +1408,27 @@ public CraftingChainTooltipLineHandler(int groupId, BookmarkCraftingChain crafti this.inputs = new ItemsTooltipLineHandler( translate("bookmark.crafting_chain.input"), - craftingChain.inputs.values().stream().collect(Collectors.toCollection(LinkedList::new)), + new ArrayList<>(craftingChain.getInputStacks()), true, Integer.MAX_VALUE); this.outputs = new ItemsTooltipLineHandler( translate("bookmark.crafting_chain.output"), - craftingChain.outputs.values().stream().collect(Collectors.toCollection(LinkedList::new)), + new ArrayList<>(craftingChain.getOutputStacks()), true, Integer.MAX_VALUE); this.remainder = new ItemsTooltipLineHandler( translate("bookmark.crafting_chain.remainder"), - craftingChain.remainder.values().stream().collect(Collectors.toCollection(LinkedList::new)), + new ArrayList<>(craftingChain.getRemainingStacks()), true, Integer.MAX_VALUE); 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; @@ -1403,8 +1479,8 @@ public void draw(int x, int y) { if (!this.remainder.isEmpty()) { this.remainder.draw(x, y); + y += this.remainder.getSize().height; } - } } @@ -1487,27 +1563,11 @@ public boolean inEditingState() { || this.groupingItem != null && this.groupingItem.hasEndRow(); } - public void addItem(ItemStack itemStack) { - addItem(itemStack, true); - } - - public void addItem(ItemStack itemStack, boolean saveSize) { - final BookmarkGrid BGrid = (BookmarkGrid) grid; - int idx = BGrid.indexOf(itemStack, true); - - if (idx != -1) { - BGrid.removeItem(idx); - } - - addOrRemoveItem(itemStack, null, null, false, saveSize); - } - public void addOrRemoveItem(ItemStack stackA) { - addOrRemoveItem(stackA, null, null, false, false); + addOrRemoveItem(stackA, null, false, false); } - public void addOrRemoveItem(ItemStack stackover, final String handlerName, final List ingredients, - boolean saveIngredients, boolean saveSize) { + public void addOrRemoveItem(ItemStack stackover, BookmarkRecipe recipe, boolean saveIngredients, boolean saveSize) { loadBookmarksIfNeeded(); final Point mousePos = getMousePosition(); @@ -1518,24 +1578,16 @@ public void addOrRemoveItem(ItemStack stackover, final String handlerName, final BGrid.removeRecipe(slot.slotIndex, saveIngredients); } else { BookmarkRecipeId recipeId = null; - - if (handlerName != null && !handlerName.isEmpty() && ingredients != null) { - recipeId = new BookmarkRecipeId(handlerName, ingredients); + if (recipe != null) { + recipeId = recipe.getRecipeId(); } - final int idx = BGrid.indexOf(stackover, recipeId); if (idx != -1) { BGrid.removeRecipe(idx, saveIngredients); - } else if (saveIngredients && handlerName != null && !handlerName.isEmpty() && ingredients != null) { - BookmarkRecipe recipe = new BookmarkRecipe(stackover); - recipe.handlerName = handlerName; - recipe.recipeId = recipeId; - - for (PositionedStack stack : ingredients) { - recipe.ingredients.add(stack.item); - } - + } else if (saveIngredients && recipe != null) { + recipe.result = new ArrayList<>(); + recipe.result.add(stackover); addRecipe(recipe, saveSize); } else { final NBTTagCompound nbTag = StackInfo.itemStackToNBT(stackover); @@ -1553,6 +1605,35 @@ public void addOrRemoveItem(ItemStack stackover, final String handlerName, final fixCountOfNamespaces(); } + public static BookmarkRecipe constructBookmarkRecipe(IRecipeHandler handler, int recipeIndex) { + if (recipeIndex < 0) { + return null; + } + final HandlerInfo handlerInfo = GuiRecipeTab.getHandlerInfo(handler); + final List ingredients = handler.getIngredientStacks(recipeIndex); + final BookmarkRecipeId recipeId = new BookmarkRecipeId(handlerInfo.getHandlerName(), ingredients); + + BookmarkRecipe recipe = new BookmarkRecipe(); + recipe.handlerName = recipeId.handlerName; + recipe.recipeId = recipeId; + + for (PositionedStack stack : ingredients) { + recipe.allIngredients.add(Arrays.asList(stack.items)); + recipe.ingredients.add(stack.item); + } + + PositionedStack result = handler.getResultStack(recipeIndex); + + if (result != null) { + recipe.result.add(result.item); + } else { + for (PositionedStack stack : handler.getOtherStacks(recipeIndex)) { + recipe.result.add(stack.item); + } + } + return recipe; + } + public void addRecipe(BookmarkRecipe recipe, boolean saveSize) { addRecipe(recipe, saveSize, BookmarkGrid.DEFAULT_GROUP_ID); } @@ -1566,42 +1647,59 @@ 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); final ItemStack normalized = StackInfo.loadFromNBT(nbTag, saveSize ? nbTag.getInteger("Count") : 0); final ItemStackMetadata metadata = new ItemStackMetadata(recipeId.copy(), nbTag, true, groupId); - + metadata.fullRecipe = recipe; BGrid.addItem(normalized, metadata); } } - for (ItemStack stack : recipe.result) { - final NBTTagCompound nbTag = StackInfo.itemStackToNBT(stack); + Map uniqueResults = getUniqueItems(recipe.result); + + boolean isGroupEmpty = true; + for (int rowGroupId : BGrid.gridGroupMask) { + if (rowGroupId == groupId) { + isGroupEmpty = false; + break; + } + } + + 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); - + metadata.fullRecipe = recipe; + if (isGroupEmpty && saveSize) { + metadata.requestedAmount = nbTag.getInteger("Count"); + } BGrid.addItem(normalized, metadata); } 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<>(); @@ -1804,6 +1902,7 @@ public void load() { } bookmarksState = null; + craftingChainState = null; } public void saveBookmarks() { @@ -1846,6 +1945,7 @@ public void saveBookmarks() { row.add("item", NBTJson.toJsonObject(nbTag)); row.add("factor", new JsonPrimitive(meta.factor)); row.add("ingredient", new JsonPrimitive(meta.ingredient)); + row.add("requestedAmount", new JsonPrimitive(meta.requestedAmount)); if (meta.groupId != BookmarkGrid.DEFAULT_GROUP_ID) { row.add("groupId", new JsonPrimitive(meta.groupId)); @@ -1873,8 +1973,7 @@ public void saveBookmarks() { } protected void loadBookmarksIfNeeded() { - - if (bookmarksState != null || bookmarksState == BookmarkLoadingState.LOADING) { + if (bookmarksState != null) { return; } @@ -1970,14 +2069,17 @@ protected void loadBookmarksIfNeeded() { groupId = jsonObject.has("groupId") ? jsonObject.get("groupId").getAsInt() : BookmarkGrid.DEFAULT_GROUP_ID; grid.realItems.add(itemStack); - grid.metadata.add( - new ItemStackMetadata( - recipeId, - jsonObject.has("factor") ? Math.abs(jsonObject.get("factor").getAsInt()) - : (itemStackNBT.hasKey("gtFluidName") ? 144 : 1), - jsonObject.has("ingredient") ? jsonObject.get("ingredient").getAsBoolean() : false, - grid.groups.containsKey(groupId) ? groupId : BookmarkGrid.DEFAULT_GROUP_ID, - itemStackNBT.hasKey("gtFluidName"))); + ItemStackMetadata metadata = new ItemStackMetadata( + recipeId, + jsonObject.has("factor") ? Math.abs(jsonObject.get("factor").getAsInt()) + : (itemStackNBT.hasKey("gtFluidName") ? 144 : 1), + jsonObject.has("ingredient") ? jsonObject.get("ingredient").getAsBoolean() : false, + grid.groups.containsKey(groupId) ? groupId : BookmarkGrid.DEFAULT_GROUP_ID, + itemStackNBT.hasKey("gtFluidName")); + metadata.requestedAmount = jsonObject.has("requestedAmount") + ? jsonObject.get("requestedAmount").getAsInt() + : 0; + grid.metadata.add(metadata); } else { NEIClientConfig.logger.warn( "Failed to load bookmarked ItemStack from json string, the item no longer exists:\n{}", @@ -1989,11 +2091,8 @@ protected void loadBookmarksIfNeeded() { } } - for (BookmarkGrid gr : namespaces) { - gr.onItemsChanged(); - } - this.namespaces = namespaces; + bookmarksState = BookmarkLoadingState.LOADED; if (navigation.hasKey("namespaceIndex")) { @@ -2007,6 +2106,7 @@ protected void loadBookmarksIfNeeded() { @Override public void resize(GuiContainer gui) { loadBookmarksIfNeeded(); + calculateCraftsIfNeeded(); super.resize(gui); } @@ -2227,7 +2327,7 @@ public void mouseDragged(int mousex, int mousey, int button, long heldTime) { } } else if (mouseOverSlot != null - && this.sortableItem.items.indexOf(BGrid.realItems.get(mouseOverSlot.slotIndex)) == -1) { + && !this.sortableItem.items.contains(BGrid.realItems.get(mouseOverSlot.slotIndex))) { final ItemStackMetadata meta = BGrid.getMetadata(mouseOverSlot.slotIndex); if (meta.groupId == sortMeta.groupId) { @@ -2604,6 +2704,59 @@ public boolean contains(int px, int py) { 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)); } @@ -2676,30 +2829,44 @@ 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 (NEIClientUtils.shiftKey()) { - - for (int slotIndex = grid.size() - 1; slotIndex >= 0; slotIndex--) { - if (overMeta.equalsRecipe(BGrid.getMetadata(slotIndex)) && slotIndex != slot.slotIndex) { - items.put(slotIndex, shiftStackSize(BGrid, slotIndex, shift, shiftMultiplier)); + 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; + } + if (group.crafting != null && !meta.ingredient) { + shiftRequestedAmount(meta, shift, shiftMultiplier * meta.factor); + } else if (group.crafting == null) { + int newSize = shiftStackSize(BGrid, i, shift, shiftMultiplier * meta.factor); + if (!meta.ingredient && overMeta.requestedAmount != 0) { + meta.requestedAmount = newSize; + } } } - } - - 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)); + } else { + 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) { + int newSize = shiftStackSize(BGrid, slot.slotIndex, shift, shiftMultiplier); + if (!overMeta.ingredient && overMeta.requestedAmount != 0) { + overMeta.requestedAmount = newSize; + } } } @@ -2727,21 +2894,37 @@ public boolean onMouseWheel(int shift, int mousex, int mousey) { return false; } - private ItemStack shiftStackSize(BookmarkGrid BGrid, int slotIndex, int shift, int shiftMultiplier) { + private int 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 int count = Math.max(calculateShiftedValue(originalCount, shift, shiftMultiplier), 0); + ItemStack newStack = StackInfo.loadFromNBT(nbTag, count); + BGrid.realItems.set(slotIndex, newStack); + return count; + } + return 0; + } - 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) { @@ -2765,8 +2948,8 @@ public boolean pullBookmarkItems(int groupId, boolean onlyIngredients) { final ItemStack stack = BGrid.getItem(idx); final BookmarkGroup group = BGrid.groups.get(meta.groupId); - if (!onlyIngredients || meta.ingredient && (group == null || group.crafting == null - || group.crafting.inputs.containsKey(BGrid.realItems.get(idx)))) { + if (!onlyIngredients || meta.ingredient + && (group == null || group.crafting == null || group.crafting.getInputSlots().contains(idx))) { uniqueItems.put( stack, uniqueItems.getOrDefault(stack, 0L) + StackInfo.itemStackToNBT(stack).getInteger("Count")); @@ -2791,4 +2974,16 @@ public boolean pullBookmarkItems(int groupId, boolean onlyIngredients) { containerHandler.pullBookmarkItemsFromContainer(guiContainer, items); return true; } + + public void calculateCraftsIfNeeded() { + if (craftingChainState != null || bookmarksState != BookmarkLoadingState.LOADED + || !NEIClientConfig.isLoaded()) { + return; + } + craftingChainState = BookmarkLoadingState.LOADING; + for (BookmarkPanel.BookmarkGrid gr : namespaces) { + gr.onItemsChanged(); + } + craftingChainState = BookmarkLoadingState.LOADED; + } } diff --git a/src/main/java/codechicken/nei/NEIClientConfig.java b/src/main/java/codechicken/nei/NEIClientConfig.java index 91106721c..2be08bd07 100644 --- a/src/main/java/codechicken/nei/NEIClientConfig.java +++ b/src/main/java/codechicken/nei/NEIClientConfig.java @@ -198,6 +198,9 @@ 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.ignorePotionOverlap").setComment("Ignore overlap with potion effect HUD") .getBooleanValue(false); API.addOption(new OptionToggleButton("inventory.bookmarks.ignorePotionOverlap", true)); @@ -919,6 +922,10 @@ public static boolean showRecipeMarker() { return getBooleanSetting("inventory.bookmarks.showRecipeMarker"); } + public static boolean allowNegativeRequests() { + return getBooleanSetting("inventory.bookmarks.allowNegativeRequests"); + } + public static boolean showItemQuantityWidget() { return getBooleanSetting("inventory.showItemQuantityWidget"); } diff --git a/src/main/java/codechicken/nei/NEIServerUtils.java b/src/main/java/codechicken/nei/NEIServerUtils.java index d6e076c2e..035e4a3cd 100644 --- a/src/main/java/codechicken/nei/NEIServerUtils.java +++ b/src/main/java/codechicken/nei/NEIServerUtils.java @@ -475,7 +475,7 @@ public static void writeNBT(NBTTagCompound tag, File file) throws IOException { out.close(); } - public static int divideCeil(int numerator, int denominator) { - return (int) Math.ceil((float) numerator / denominator); + public static int divideCeil(int num, int divisor) { + return num / divisor + (num % divisor == 0 ? 0 : 1); } } diff --git a/src/main/java/codechicken/nei/PanelWidget.java b/src/main/java/codechicken/nei/PanelWidget.java index 0674ae879..eb62d7879 100644 --- a/src/main/java/codechicken/nei/PanelWidget.java +++ b/src/main/java/codechicken/nei/PanelWidget.java @@ -245,7 +245,7 @@ public boolean handleClick(int mousex, int mousey, int button) { public boolean handleClickExt(int mouseX, int mouseY, int button) { if (ItemPanels.itemPanel.draggedStack != null && ItemPanels.bookmarkPanel.contains(mouseX, mouseY)) { - ItemPanels.bookmarkPanel.addOrRemoveItem(ItemPanels.itemPanel.draggedStack, null, null, false, true); + ItemPanels.bookmarkPanel.addOrRemoveItem(ItemPanels.itemPanel.draggedStack, null, false, true); ItemPanels.itemPanel.draggedStack = null; return true; } diff --git a/src/main/java/codechicken/nei/api/ShortcutInputHandler.java b/src/main/java/codechicken/nei/api/ShortcutInputHandler.java index 93df94531..eef9c23b3 100644 --- a/src/main/java/codechicken/nei/api/ShortcutInputHandler.java +++ b/src/main/java/codechicken/nei/api/ShortcutInputHandler.java @@ -6,7 +6,6 @@ import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.util.HashMap; -import java.util.List; import java.util.Map; import net.minecraft.client.gui.inventory.GuiContainer; @@ -15,12 +14,12 @@ import org.lwjgl.input.Mouse; +import codechicken.nei.BookmarkPanel; import codechicken.nei.ItemPanel.ItemPanelSlot; import codechicken.nei.ItemPanels; import codechicken.nei.LayoutManager; import codechicken.nei.NEIClientConfig; import codechicken.nei.NEIClientUtils; -import codechicken.nei.PositionedStack; import codechicken.nei.recipe.BookmarkRecipeId; import codechicken.nei.recipe.GuiCraftingRecipe; import codechicken.nei.recipe.GuiRecipe; @@ -182,19 +181,17 @@ private static boolean openOverlayRecipe(ItemStack stackover, boolean shift) { } private static boolean saveRecipeInBookmark(ItemStack stack, boolean saveIngredients, boolean saveStackSize) { - if (stack != null) { final GuiContainer gui = NEIClientUtils.getGuiContainer(); - List ingredients = null; - String handlerName = ""; - + BookmarkPanel.BookmarkRecipe recipe = null; if (gui instanceof GuiRecipe) { - ingredients = ((GuiRecipe) gui).getFocusedRecipeIngredients(); - handlerName = ((GuiRecipe) gui).getHandlerName(); - stack.stackSize = ((GuiRecipe) gui).prepareFocusedRecipeResultStackSize(stack); + GuiRecipe guiRecipe = (GuiRecipe) gui; + Integer idx = guiRecipe.getFocusedRecipeIndex(); + if (idx != null) { + recipe = BookmarkPanel.constructBookmarkRecipe(guiRecipe.getHandler(), idx); + } } - - ItemPanels.bookmarkPanel.addOrRemoveItem(stack, handlerName, ingredients, saveIngredients, saveStackSize); + ItemPanels.bookmarkPanel.addOrRemoveItem(stack, recipe, saveIngredients, saveStackSize); return true; } @@ -204,5 +201,4 @@ private static boolean saveRecipeInBookmark(ItemStack stack, boolean saveIngredi public static Map handleHotkeys(GuiContainer gui, int mousex, int mousey, ItemStack stack) { return new HashMap<>(); } - } diff --git a/src/main/java/codechicken/nei/bookmarks/crafts/BookmarkCraftingChain.java b/src/main/java/codechicken/nei/bookmarks/crafts/BookmarkCraftingChain.java new file mode 100644 index 000000000..aa9f4926d --- /dev/null +++ b/src/main/java/codechicken/nei/bookmarks/crafts/BookmarkCraftingChain.java @@ -0,0 +1,147 @@ +package codechicken.nei.bookmarks.crafts; + +import java.util.ArrayList; +import java.util.Collections; +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 net.minecraft.nbt.NBTTagCompound; + +import codechicken.nei.BookmarkPanel; +import codechicken.nei.bookmarks.crafts.graph.CraftingGraph; +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 { + + private CraftingGraph graph; + + public void refresh(ArrayList groupItems, boolean skipCalculation) { + if (groupItems.isEmpty()) { + return; + } + this.graph = new CraftingGraph(); + + LinkedHashMap> groupedByRecipe = new LinkedHashMap<>(); + LinkedHashMap recipes = new LinkedHashMap<>(); + List itemsWithoutRecipe = new ArrayList<>(); + + for (ItemStackWithMetadata groupItem : groupItems) { + BookmarkRecipeId recipeId = groupItem.getMeta().recipeId; + if (recipeId != null) { + groupedByRecipe.computeIfAbsent(recipeId, k -> new ArrayList<>()).add(groupItem); + BookmarkPanel.BookmarkRecipe fullRecipe = groupItem.getMeta().getFullRecipe(groupItem.getStack()); + if (fullRecipe != null) { + recipes.putIfAbsent(recipeId, fullRecipe); + } + } else { + itemsWithoutRecipe.add(groupItem); + } + } + + for (Map.Entry> entry : groupedByRecipe.entrySet()) { + if (entry.getKey() != null) { + List inputs = new ArrayList<>(); + List outputs = new ArrayList<>(); + for (ItemStackWithMetadata it : entry.getValue()) { + if (it.getMeta().ingredient) { + inputs.add(it); + } else { + outputs.add(it); + } + } + + if (!outputs.isEmpty()) { + if (inputs.isEmpty()) { + itemsWithoutRecipe.addAll(outputs); + } else { + BookmarkPanel.BookmarkRecipe recipe = recipes.get(entry.getKey()); + // Fallback to pinned items + if (recipe == null) { + recipe = new BookmarkPanel.BookmarkRecipe(); + recipe.ingredients = inputs.stream().map(it -> { + NBTTagCompound tagCompound = StackInfo.itemStackToNBT(it.getStack()); + return StackInfo.loadFromNBT(tagCompound, it.getMeta().factor); + }).collect(Collectors.toList()); + + recipe.allIngredients = recipe.ingredients.stream().map(Collections::singletonList) + .collect(Collectors.toList()); + + recipe.result = outputs.stream().map(it -> { + NBTTagCompound tagCompound = StackInfo.itemStackToNBT(it.getStack()); + return StackInfo.loadFromNBT(tagCompound, it.getMeta().factor); + }).collect(Collectors.toList()); + recipe.recipeId = entry.getKey(); + } + + RecipeGraphNode node = new RecipeGraphNode(recipe, inputs, outputs); + for (ItemStackWithMetadata output : outputs) { + graph.addNode(output, node); + } + } + } + } + } + + for (ItemStackWithMetadata itemWithoutRecipe : itemsWithoutRecipe) { + ItemGraphNode node = new ItemGraphNode(itemWithoutRecipe); + graph.addNode(itemWithoutRecipe, node); + } + if (skipCalculation) { + this.graph.postProcess(); + } else { + this.graph.runAll(); + } + } + + public Map getCalculatedItems() { + if (this.graph == null) return Collections.emptyMap(); + 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(); + } + + public Set getInputSlots() { + if (this.graph == null) return Collections.emptySet(); + return this.graph.getInputSlots(); + } + + public Set getOutputSlots() { + if (this.graph == null) return Collections.emptySet(); + return this.graph.getCraftedOutputSlots(); + } + + 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 List getOutputStacks() { + if (this.graph == null) return Collections.emptyList(); + return this.graph.getOutputStacks(); + } + + 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 new file mode 100644 index 000000000..fce65c311 --- /dev/null +++ b/src/main/java/codechicken/nei/bookmarks/crafts/ItemStackWithMetadata.java @@ -0,0 +1,30 @@ +package codechicken.nei.bookmarks.crafts; + +import net.minecraft.item.ItemStack; + +import codechicken.nei.BookmarkPanel; + +public class ItemStackWithMetadata { + + private final int gridIdx; + private final ItemStack stack; + private final BookmarkPanel.ItemStackMetadata meta; + + public ItemStackWithMetadata(int idx, ItemStack stack, BookmarkPanel.ItemStackMetadata meta) { + this.gridIdx = idx; + this.stack = stack; + this.meta = meta; + } + + public int getGridIdx() { + return gridIdx; + } + + public ItemStack getStack() { + return stack; + } + + public BookmarkPanel.ItemStackMetadata getMeta() { + return meta; + } +} diff --git a/src/main/java/codechicken/nei/bookmarks/crafts/graph/CraftingGraph.java b/src/main/java/codechicken/nei/bookmarks/crafts/graph/CraftingGraph.java new file mode 100644 index 000000000..c6b6e2d85 --- /dev/null +++ b/src/main/java/codechicken/nei/bookmarks/crafts/graph/CraftingGraph.java @@ -0,0 +1,482 @@ +package codechicken.nei.bookmarks.crafts.graph; + +import static codechicken.nei.recipe.StackInfo.getItemStackGUID; + +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; +import java.util.Set; + +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; +import codechicken.nei.recipe.BookmarkRecipeId; +import codechicken.nei.recipe.StackInfo; + +public class CraftingGraph { + + private final Map nodes = new HashMap<>(); + private final Map> allNodes = new HashMap<>(); + private final List fluidConversionNodes = new ArrayList<>(); + + private final Map requestedItems = new HashMap<>(); + + private final Map itemStackMapping = new HashMap<>(); + + // 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 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); + int requestedAmount = output.getMeta().requestedAmount; + if (requestedAmount != 0) { + requestedItems.put(key, requestedAmount); + } + if (node instanceof RecipeGraphNode recipeNode && requestedAmount > 0) { + recipeNode.setChainOutputs(key, requestedAmount); + } + } 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); + } + } + + 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<>()); + } + } + postProcess(); + } + + 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()); + } + } + } + + // Add fake fluid conversion recipes + // Identity LinkedHashSet is intentional + Map> recipesWithFluidOutputs = new HashMap<>(); + Map> recipesWithFluidInputs = new HashMap<>(); + + for (CraftingGraphNode node : nodes.values()) { + if (node instanceof RecipeGraphNode recipeNode) { + for (ItemStackWithMetadata pinnedOutput : recipeNode.getPinnedOutputs()) { + tryAddFluid(pinnedOutput, recipesWithFluidOutputs); + } + } + } + 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); + } + fluidConversionNodes.add(node); + } + } + + 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); + } + } + + public int dfs(String requestedKey, int requestedAmount, HashSet history) { + CraftingGraphNode node = this.nodes.get(requestedKey); + if (node == null) { + return 0; + } + + // Handle item node + if (node instanceof ItemGraphNode itemGraphNode) { + itemGraphNode.addToRemainders(requestedKey, -requestedAmount); + return requestedAmount; + } + + // Handle fluid conversion + if (node instanceof FluidConversionGraphNode fluidNode) { + String leftKey = fluidNode.getInputKey(); + int leftAmountToRequest = fluidNode.calculateAmountToRequest(requestedKey, requestedAmount, leftKey); + + int returnedLeftAmount = dfs(leftKey, leftAmountToRequest, history); + + return fluidNode.processResults(requestedKey, requestedAmount, leftKey, returnedLeftAmount); + } + + 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; + } + } + } + // Collect produced containers containers + for (FluidConversionGraphNode fluidConversionGraphNode : fluidConversionNodes) { + Map producedEmptyContainers = fluidConversionGraphNode.getProducedEmptyContainers(); + Integer availableContainers = producedEmptyContainers.get(requestedKey); + if (availableContainers != null) { + int n = Math.min(availableContainers, requestedAmount - availableAmount); + producedEmptyContainers.put(requestedKey, producedEmptyContainers.get(requestedKey) - n); + availableAmount += n; + } + } + int amountToRequest = requestedAmount - availableAmount; + + BookmarkRecipeId recipeId = recipeNode.getRecipeId(); + // Handle recursive recipes + if (history.contains(recipeId)) { + return availableAmount; + } + + if (amountToRequest <= 0) { + return requestedAmount; + } + + // 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; + } + } + + 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<>(); + + 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; + } + + 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); + } + } + + for (Map.Entry entry : ingredientsToRequest.entrySet()) { + history.add(recipeId); + int provided = dfs(entry.getKey(), entry.getValue(), history); + int chainInputs = entry.getValue() - provided; + recipeNode.addToChainInputs(entry.getKey(), chainInputs); + history.remove(recipeId); + } + return requestedAmount; + } + + public void postProcess() { + this.calculatedItems.clear(); + this.calculatedCraftCounts.clear(); + this.calculatedRemainders.clear(); + + this.inputStacks.clear(); + this.remainingStacks.clear(); + + 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 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); + } + + this.calculatedItems + .put(pinnedOutput.getGridIdx(), withStackSize(pinnedOutput.getStack(), calculatedCount)); + this.calculatedRemainders.put( + pinnedOutput.getGridIdx(), + withStackSize( + pinnedOutput.getStack(), + recipeNode.getRemainder(key) + recipeNode.getChainOutput(key))); + this.calculatedCraftCounts.put(pinnedOutput.getGridIdx(), recipeNode.getCrafts()); + } + + for (ItemStackWithMetadata pinnedInput : recipeNode.getPinnedInputs()) { + String key = getItemStackGUID(pinnedInput.getStack()); + 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); + } + + 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); + remainders.compute(entry.getKey(), (k, v) -> (v == null ? 0 : v) + entry.getValue()); + } + + for (Map.Entry entry : recipeNode.getChainOutputs().entrySet()) { + int slotIdx = recipeNode.getPinnedOutputKeys().get(entry.getKey()); + this.outputSlots.add(slotIdx); + outputs.compute(entry.getKey(), (k, v) -> (v == null ? 0 : v) + entry.getValue()); + } + + 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()); + } + + 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()); + } + } + } + } + + 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; + } + + public Set getInputSlots() { + return inputSlots; + } + + public Set getCraftedOutputSlots() { + return outputSlots; + } + + public Set getConflictingSlots() { + return conflictingSlots; + } + + public List getInputStacks() { + return inputStacks; + } + + public List getOutputStacks() { + return outputStacks; + } + + public List getRemainingStacks() { + return remainingStacks; + } + + 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 new file mode 100644 index 000000000..a0d597cd6 --- /dev/null +++ b/src/main/java/codechicken/nei/bookmarks/crafts/graph/CraftingGraphNode.java @@ -0,0 +1,12 @@ +package codechicken.nei.bookmarks.crafts.graph; + +import java.util.Map; + +public interface CraftingGraphNode { + + 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..0ff7e7609 --- /dev/null +++ b/src/main/java/codechicken/nei/bookmarks/crafts/graph/FluidConversionGraphNode.java @@ -0,0 +1,142 @@ +package codechicken.nei.bookmarks.crafts.graph; + +import java.util.Collections; +import java.util.HashMap; +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 rightKey, int rightRequestedAmount, String leftKey) { + int rightFluidSize = conversionOutputs.get(rightKey); + int leftFluidSize = conversionInputs.get(leftKey); + int fluidAmountToRequest = rightRequestedAmount * rightFluidSize - fluidRemainder; + return NEIServerUtils.divideCeil(fluidAmountToRequest, leftFluidSize); + } + + public String getInputKey() { + return conversionInputs.entrySet().stream().findFirst().get().getKey(); + } + + public int processResults(String rightKey, int rightAmount, String leftKey, int leftAmount) { + int rightFluidSize = conversionOutputs.get(rightKey); + int leftFluidSize = conversionInputs.get(leftKey); + int leftFluidReturned = leftAmount * leftFluidSize; + int rightFluidRequested = rightFluidSize * rightAmount; + int rightAmountReturned = leftFluidReturned / rightFluidSize; + + this.fluidRemainder += Math.max(0, leftFluidReturned - rightFluidRequested); + + if (keyToEmptyContainer.containsKey(rightKey)) { + String containerKey = keyToEmptyContainer.get(rightKey); + consumedEmptyContainers.compute(containerKey, (k, v) -> (v == null ? 0 : v) + rightAmountReturned); + } + + if (keyToEmptyContainer.containsKey(leftKey)) { + String containerKey = keyToEmptyContainer.get(leftKey); + producedEmptyContainers.compute(containerKey, (k, v) -> (v == null ? 0 : v) + leftAmount); + } + return rightAmountReturned; + } + + 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 new file mode 100644 index 000000000..fdab6eb42 --- /dev/null +++ b/src/main/java/codechicken/nei/bookmarks/crafts/graph/ItemGraphNode.java @@ -0,0 +1,39 @@ + +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 { + + private final ItemStackWithMetadata pinnedItem; + + private int requestedItems = 0; + + public ItemGraphNode(ItemStackWithMetadata pinnedItem) { + this.pinnedItem = pinnedItem; + } + + public ItemStackWithMetadata getPinnedItem() { + return pinnedItem; + } + + @Override + public int addToRemainders(String itemKey, int remainder) { + requestedItems -= remainder; + return requestedItems; + } + + @Override + public Map getRemainders() { + return Collections.singletonMap(StackInfo.getItemStackGUID(pinnedItem.getStack()), -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 new file mode 100644 index 000000000..21d1b7f5f --- /dev/null +++ b/src/main/java/codechicken/nei/bookmarks/crafts/graph/RecipeGraphNode.java @@ -0,0 +1,144 @@ +package codechicken.nei.bookmarks.crafts.graph; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import net.minecraft.item.ItemStack; + +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 { + + private final List pinnedInputs; + private final List pinnedOutputs; + private final Map pinnedInputKeys = new HashMap<>(); + private final Map pinnedOutputKeys = new HashMap<>(); + private final BookmarkRecipe recipe; + + private final List> recipeIngredients = new ArrayList<>(); + private final Map recipeOutputs = new LinkedHashMap<>(); + + private int crafts = 0; + private final Map remainders = new HashMap<>(); + private final Map chainInputs = new HashMap<>(); + private final Map chainOutputs = new HashMap<>(); + + public RecipeGraphNode(BookmarkRecipe recipe, List pinnedInputs, + List pinnedOutputs) { + this.pinnedInputs = pinnedInputs; + this.pinnedOutputs = pinnedOutputs; + this.recipe = recipe; + for (ItemStackWithMetadata item : pinnedInputs) { + pinnedInputKeys.put(StackInfo.getItemStackGUID(item.getStack()), item.getGridIdx()); + } + for (ItemStackWithMetadata item : pinnedOutputs) { + pinnedOutputKeys.put(StackInfo.getItemStackGUID(item.getStack()), item.getGridIdx()); + } + + for (List slotIngredients : recipe.allIngredients) { + Map ingredientMap = new HashMap<>(); + for (ItemStack ingredient : slotIngredients) { + String key = StackInfo.getItemStackGUID(ingredient); + int size = CraftingGraph.getStackSize(ingredient); + ingredientMap.compute(key, (k, v) -> v == null ? size : v + size); + } + recipeIngredients.add(ingredientMap); + } + + 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() { + return pinnedInputs; + } + + public List getPinnedOutputs() { + return pinnedOutputs; + } + + public Map getPinnedInputKeys() { + return pinnedInputKeys; + } + + public Map getPinnedOutputKeys() { + return pinnedOutputKeys; + } + + public List> getRecipeIngredients() { + return recipeIngredients; + } + + public Map getRecipeOutputs() { + return recipeOutputs; + } + + public BookmarkRecipeId getRecipeId() { + return recipe.getRecipeId(); + } + + public int getCrafts() { + return crafts; + } + + public void addCrafts(int crafts) { + this.crafts += crafts; + } + + @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 getChainOutputs() { + return chainOutputs; + } + + public int getChainOutput(String itemKey) { + return chainOutputs.getOrDefault(itemKey, 0); + } + + public void setChainOutputs(String itemKey, int size) { + chainOutputs.put(itemKey, size); + } + + public Map getChainInputs() { + return chainInputs; + } + + @Override + public Map getRemainders() { + return remainders; + } +} diff --git a/src/main/java/codechicken/nei/recipe/BookmarkRecipeId.java b/src/main/java/codechicken/nei/recipe/BookmarkRecipeId.java index bcba5c78b..b697f8f3b 100644 --- a/src/main/java/codechicken/nei/recipe/BookmarkRecipeId.java +++ b/src/main/java/codechicken/nei/recipe/BookmarkRecipeId.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; @@ -145,6 +146,11 @@ public boolean equals(Object anObject) { return false; } + @Override + public int hashCode() { + return Objects.hash(handlerName, ingredients); + } + public BookmarkRecipeId copy() { return new BookmarkRecipeId(handlerName, ingredients); } diff --git a/src/main/java/codechicken/nei/recipe/GuiCraftingRecipe.java b/src/main/java/codechicken/nei/recipe/GuiCraftingRecipe.java index 09ef87020..ffb04a592 100644 --- a/src/main/java/codechicken/nei/recipe/GuiCraftingRecipe.java +++ b/src/main/java/codechicken/nei/recipe/GuiCraftingRecipe.java @@ -130,11 +130,16 @@ private static ItemStack normalizeItemStack(ItemStack stack) { protected static BookmarkRecipeId getRecipeId(GuiScreen gui, ItemStack stackover) { if (gui instanceof GuiRecipe) { - final List ingredients = ((GuiRecipe) gui).getFocusedRecipeIngredients(); - final String handlerName = ((GuiRecipe) gui).getHandlerName(); - - if (ingredients != null && !ingredients.isEmpty()) { - return new BookmarkRecipeId(handlerName, ingredients); + GuiRecipe guiRecipe = (GuiRecipe) gui; + final Integer focusedRecipeIndex = guiRecipe.getFocusedRecipeIndex(); + if (focusedRecipeIndex != null) { + final List ingredients = guiRecipe.getHandler() + .getIngredientStacks(focusedRecipeIndex); + final String handlerName = guiRecipe.getHandlerName(); + + if (ingredients != null && !ingredients.isEmpty()) { + return new BookmarkRecipeId(handlerName, ingredients); + } } } diff --git a/src/main/java/codechicken/nei/recipe/GuiOverlayButton.java b/src/main/java/codechicken/nei/recipe/GuiOverlayButton.java index d50e05660..31cc05c73 100644 --- a/src/main/java/codechicken/nei/recipe/GuiOverlayButton.java +++ b/src/main/java/codechicken/nei/recipe/GuiOverlayButton.java @@ -15,6 +15,7 @@ import codechicken.lib.gui.GuiDraw; import codechicken.lib.vec.Rectangle4i; +import codechicken.nei.BookmarkPanel; import codechicken.nei.BookmarkPanel.BookmarkRecipe; import codechicken.nei.GuiNEIButton; import codechicken.nei.ItemPanels; @@ -305,33 +306,8 @@ public void lastKeyTyped(GuiRecipe gui, char keyChar, int keyID) { } public void saveRecipeInBookmark(boolean saveIngredients, boolean saveStackSize) { - final HandlerInfo handlerInfo = GuiRecipeTab.getHandlerInfo(this.handler); - final List ingredients = this.handler.getIngredientStacks(this.recipeIndex); - final BookmarkRecipeId recipeId = new BookmarkRecipeId(handlerInfo.getHandlerName(), ingredients); - - if (!ItemPanels.bookmarkPanel.removeBookmarkRecipeId(recipeId)) { - BookmarkRecipe recipe = new BookmarkRecipe(); - recipe.handlerName = recipeId.handlerName; - recipe.recipeId = recipeId; - - if (saveIngredients) { - for (PositionedStack stack : ingredients) { - recipe.ingredients.add(stack.item); - } - } - - PositionedStack result = this.handler.getResultStack(this.recipeIndex); - - if (result != null) { - recipe.result.add(result.item); - } else { - for (PositionedStack stack : this.handler.getOtherStacks(this.recipeIndex)) { - recipe.result.add(stack.item); - } - } - - ItemPanels.bookmarkPanel.addRecipe(recipe, saveStackSize); - } + BookmarkRecipe recipe = BookmarkPanel.constructBookmarkRecipe(this.handler, this.recipeIndex); + ItemPanels.bookmarkPanel.addRecipe(recipe, saveStackSize); } public int getResultStackSize(ItemStack stackover) { diff --git a/src/main/java/codechicken/nei/recipe/GuiRecipe.java b/src/main/java/codechicken/nei/recipe/GuiRecipe.java index 9894d9dff..2904fc1d4 100644 --- a/src/main/java/codechicken/nei/recipe/GuiRecipe.java +++ b/src/main/java/codechicken/nei/recipe/GuiRecipe.java @@ -639,46 +639,19 @@ public int openTargetRecipe(BookmarkRecipeId recipeId) { return refIndex; } - public List getFocusedRecipeIngredients() { + public Integer getFocusedRecipeIndex() { List indices = getRecipeIndices(); for (int refIndex = 0; refIndex < indices.size(); refIndex++) { final int recipeIndex = indices.get(refIndex); if (recipeInFocus(refIndex, recipeIndex)) { - return handler.original.getIngredientStacks(recipeIndex); + return recipeIndex; } } return null; } - public int prepareFocusedRecipeResultStackSize(ItemStack stackover) { - List indices = getRecipeIndices(); - - for (int refIndex = 0; refIndex < indices.size(); refIndex++) { - final int recipeIndex = indices.get(refIndex); - if (recipeInFocus(refIndex, recipeIndex)) { - final PositionedStack result = handler.original.getResultStack(recipeIndex); - int stackSize = 0; - - if (result != null && StackInfo.equalItemAndNBT(result.item, stackover, true)) { - stackSize += result.item.stackSize; - } - - final List stacks = handler.original.getOtherStacks(recipeIndex); - for (PositionedStack pStack : stacks) { - if (StackInfo.equalItemAndNBT(pStack.item, stackover, true)) { - stackSize += pStack.item.stackSize; - } - } - - return stackSize; - } - } - - return stackover.stackSize; - } - protected boolean recipeInFocus(int refIndex, int recipeIndex) { final PositionedStack result = handler.original.getResultStack(recipeIndex); diff --git a/src/main/java/codechicken/nei/recipe/RecipeCatalysts.java b/src/main/java/codechicken/nei/recipe/RecipeCatalysts.java index 903d620cb..36df07522 100644 --- a/src/main/java/codechicken/nei/recipe/RecipeCatalysts.java +++ b/src/main/java/codechicken/nei/recipe/RecipeCatalysts.java @@ -117,11 +117,13 @@ public static int getHeight() { } public static int getColumnCount(int availableHeight, int catalystsSize) { + if (availableHeight == 0) return 1; int maxItemsPerColumn = availableHeight / GuiRecipeCatalyst.ingredientSize; return NEIServerUtils.divideCeil(catalystsSize, maxItemsPerColumn); } public static int getRowCount(int availableHeight, int catalystsSize) { + if (availableHeight == 0) return 1; int columnCount = getColumnCount(availableHeight, catalystsSize); return NEIServerUtils.divideCeil(catalystsSize, columnCount); } diff --git a/src/main/java/codechicken/nei/recipe/SearchRecipeHandler.java b/src/main/java/codechicken/nei/recipe/SearchRecipeHandler.java index d5078cc2e..6e886b163 100644 --- a/src/main/java/codechicken/nei/recipe/SearchRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/SearchRecipeHandler.java @@ -11,7 +11,7 @@ import codechicken.nei.ItemList; import codechicken.nei.api.IRecipeFilter; -class SearchRecipeHandler { +public class SearchRecipeHandler { public H original; diff --git a/src/main/java/codechicken/nei/recipe/StackInfo.java b/src/main/java/codechicken/nei/recipe/StackInfo.java index 80510f868..cd66c5547 100644 --- a/src/main/java/codechicken/nei/recipe/StackInfo.java +++ b/src/main/java/codechicken/nei/recipe/StackInfo.java @@ -1,5 +1,6 @@ package codechicken.nei.recipe; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -22,6 +23,7 @@ import codechicken.nei.recipe.stackinfo.DefaultStackStringifyHandler; import codechicken.nei.recipe.stackinfo.GTFluidStackStringifyHandler; import codechicken.nei.util.ItemStackKey; +import cpw.mods.fml.relauncher.ReflectionHelper; public class StackInfo { @@ -31,9 +33,23 @@ public class StackInfo { private static final ItemStackMap guidcache = new ItemStackMap<>(); private static final LRUCache fluidcache = new LRUCache<>(200); + private static Method getContainersFromFluid = null; + private static Method getFluidDisplayStack = null; + private static Class itemCell = null; + static { stackStringifyHandlers.add(new DefaultStackStringifyHandler()); stackStringifyHandlers.add(new GTFluidStackStringifyHandler()); + try { + final ClassLoader loader = StackInfo.class.getClassLoader(); + 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 + } } public static NBTTagCompound itemStackToNBT(ItemStack stack) { @@ -117,6 +133,35 @@ public static synchronized FluidStack getFluid(ItemStack stack) { return fluid == NULL_FLUID ? null : fluid; } + public static Integer getFluidCellSize(ItemStack stack) { + FluidStack fluid = getFluid(stack); + if (fluid == null || getContainersFromFluid == null) { + return null; + } + try { + Object obj = getContainersFromFluid.invoke(null, fluid); + List containers = (List) obj; + Integer cellCapacity = null; + int fallbackCapacity = 0; + + for (ItemStack container : containers) { + if (itemCell != null && itemCell.isInstance(container.getItem())) { + cellCapacity = FluidContainerRegistry.getContainerCapacity(fluid, container); + } else { + fallbackCapacity = Math + .max(fallbackCapacity, FluidContainerRegistry.getContainerCapacity(fluid, container)); + } + } + if (cellCapacity != null) { + return cellCapacity; + } else { + return fallbackCapacity == 0 ? null : fallbackCapacity; + } + } catch (Exception e) { + return null; + } + } + public static boolean isFluidContainer(ItemStack stack) { return stack.getItem() instanceof IFluidContainerItem || FluidContainerRegistry.isContainer(stack); } @@ -219,4 +264,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 5f1166614..fa2930221 100644 --- a/src/main/resources/assets/nei/lang/en_US.lang +++ b/src/main/resources/assets/nei/lang/en_US.lang @@ -18,6 +18,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 @@ -178,6 +179,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 diff --git a/src/main/resources/assets/nei/lang/ru_RU.lang b/src/main/resources/assets/nei/lang/ru_RU.lang index ec7d64a1f..6a36f46a6 100644 --- a/src/main/resources/assets/nei/lang/ru_RU.lang +++ b/src/main/resources/assets/nei/lang/ru_RU.lang @@ -10,6 +10,7 @@ nei.showHotkeys=Удерживайте %s для отображения горя nei.bookmark.toggle=Закладки nei.bookmark.toggle.tip=Переключить видимость панели закладок nei.bookmark.pullBookmarkedItems.tip=Переместить все отмеченные предметы из хранилища в инвентарь +nei.bookmark.conflictingRecipe.tip=Этот результат данного рецепта конфликтует с другими рецептами nei.bookmark.group=Группа закладок nei.bookmark.group.include_group=Создать/Включить группу @@ -170,6 +171,9 @@ nei.options.inventory.bookmarks=Панель закладок nei.options.inventory.bookmarks.enabled=Видимость панели закладок nei.options.inventory.bookmarks.enabled.true=Видимая nei.options.inventory.bookmarks.enabled.false=Скрытая +nei.options.inventory.bookmarks.allowNegativeRequests=Разрешить отрицательные запросы в цепочке крафта +nei.options.inventory.bookmarks.allowNegativeRequests.true=Да +nei.options.inventory.bookmarks.allowNegativeRequests.false=Нет nei.options.inventory.bookmarks.animation=Анимация в стиле REI в закладках nei.options.inventory.bookmarks.animation.true=Да nei.options.inventory.bookmarks.animation.false=Нет