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 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.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 iCount = calculateCount(request, request.inputs.get(item.stackIndex)) - iShift.getOrDefault(item.stackIndex, 0L); this.multiplier.put(item.stack, item.factor > 0 ? (int) (count / item.factor) : 0); if (iCount < count) { this.outputs.put(item.stack, item.getItemStack(count - iCount)); iShift.put(item.stackIndex, iShift.getOrDefault(item.stackIndex, 0L) + iCount); } else if (iCount >= count) { this.intermediate.put(item.stack, item.getItemStack(0)); iShift.put(item.stackIndex, iShift.getOrDefault(item.stackIndex, 0L) + count); } } } } } 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; } }