From 904de4954393b1d353a2c469cf947f0aec455768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Salom=C3=A3o?= Date: Wed, 23 Jun 2021 10:16:59 -0300 Subject: [PATCH] wip fix substitution crafting, for real this time wip 2 wip. still has NBT issues fix semantic changes fix interface with free slots on interface terminal Auto stash before cherry pick of "fix interface with free slots on interface terminal" --- gradle.properties | 2 +- .../crafting/ICraftingPatternDetails.java | 8 + .../implementations/GuiInterfaceTerminal.java | 5 + .../appeng/crafting/CraftingTreeNode.java | 7 +- .../appeng/crafting/CraftingTreeProcess.java | 2 +- .../java/appeng/helpers/PatternHelper.java | 160 +++++++++++++----- .../appeng/items/misc/ItemEncodedPattern.java | 2 +- .../appeng/me/cache/CraftingGridCache.java | 2 +- .../implementations/CraftingCPUCluster.java | 159 +++++++++++------ .../util/item/FuzzyItemVariantList.java | 4 +- src/main/java/appeng/util/item/ItemList.java | 2 +- 11 files changed, 246 insertions(+), 107 deletions(-) diff --git a/gradle.properties b/gradle.properties index af9b06e35..30523eba6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ aechannel=stable aebuild=7 aegroup=appeng aebasename=appliedenergistics2 -trousers=omni-fixes-v43 +trousers=omni-fixes-v44d ######################################################### # Versions # diff --git a/src/api/java/appeng/api/networking/crafting/ICraftingPatternDetails.java b/src/api/java/appeng/api/networking/crafting/ICraftingPatternDetails.java index 5f90e2c01..11e4ee367 100644 --- a/src/api/java/appeng/api/networking/crafting/ICraftingPatternDetails.java +++ b/src/api/java/appeng/api/networking/crafting/ICraftingPatternDetails.java @@ -31,6 +31,9 @@ import net.minecraft.world.World; import appeng.api.implementations.ICraftingPatternItem; import appeng.api.storage.data.IAEItemStack; +import java.util.Collections; +import java.util.List; + /** * do not implement provided by {@link ICraftingPatternItem} @@ -84,6 +87,11 @@ public interface ICraftingPatternDetails */ boolean canSubstitute(); + default List getSubstituteInputs( int slot ) + { + return Collections.emptyList(); + } + /** * Allow using this INSTANCE of the pattern details to preform the crafting action with performance enhancements. * diff --git a/src/main/java/appeng/client/gui/implementations/GuiInterfaceTerminal.java b/src/main/java/appeng/client/gui/implementations/GuiInterfaceTerminal.java index 452e6fafb..3ff378c22 100644 --- a/src/main/java/appeng/client/gui/implementations/GuiInterfaceTerminal.java +++ b/src/main/java/appeng/client/gui/implementations/GuiInterfaceTerminal.java @@ -418,8 +418,12 @@ public class GuiInterfaceTerminal extends AEBaseGui // Search if the current inventory holds a pattern containing the search term. if( !found ) { + int slot = 0; for( final ItemStack itemStack : entry.getInventory() ) { + if (slot > 8 + numUpgradesMap.get(entry) * 9) { + break; + } if( !searchFieldInputs.isEmpty() && !searchFieldOutputs.isEmpty() ) { if (this.itemStackMatchesSearchTerm(itemStack, searchFieldInputs, 0) || this.itemStackMatchesSearchTerm(itemStack, searchFieldOutputs, 1)) { found = true; @@ -441,6 +445,7 @@ public class GuiInterfaceTerminal extends AEBaseGui // If only Interfaces with empty slots should be shown, check that here if(itemStack.isEmpty()) interfaceHasFreeSlots = true; + slot++; } } // if found, filter skipped or machine name matching the search term, add it diff --git a/src/main/java/appeng/crafting/CraftingTreeNode.java b/src/main/java/appeng/crafting/CraftingTreeNode.java index 136ed0a6e..bf285c4df 100644 --- a/src/main/java/appeng/crafting/CraftingTreeNode.java +++ b/src/main/java/appeng/crafting/CraftingTreeNode.java @@ -130,7 +130,12 @@ public class CraftingTreeNode if( this.parent.details.canSubstitute() ) { - itemList = inventoryList.findFuzzy( this.what, FuzzyMode.IGNORE_ALL ); + final List substitutes = this.parent.details.getSubstituteInputs(this.slot); + itemList = new ArrayList<>(substitutes.size()); + + for (IAEItemStack stack : substitutes) { + itemList.addAll(inventoryList.findFuzzy(stack, FuzzyMode.IGNORE_ALL)); + } } else { diff --git a/src/main/java/appeng/crafting/CraftingTreeProcess.java b/src/main/java/appeng/crafting/CraftingTreeProcess.java index 8d5403ac3..5bddbd27e 100644 --- a/src/main/java/appeng/crafting/CraftingTreeProcess.java +++ b/src/main/java/appeng/crafting/CraftingTreeProcess.java @@ -305,7 +305,7 @@ public class CraftingTreeProcess void getPlan( final IItemList plan ) { - for( IAEItemStack i : this.details.getOutputs() ) + for( IAEItemStack i : this.details.getCondensedOutputs() ) { i = i.copy(); i.setCountRequestable( i.getStackSize() * this.crafts ); diff --git a/src/main/java/appeng/helpers/PatternHelper.java b/src/main/java/appeng/helpers/PatternHelper.java index dd20d02d9..5582ad3aa 100644 --- a/src/main/java/appeng/helpers/PatternHelper.java +++ b/src/main/java/appeng/helpers/PatternHelper.java @@ -19,21 +19,18 @@ package appeng.helpers; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; +import java.util.*; +import java.util.stream.Collectors; import net.minecraft.inventory.InventoryCrafting; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.CraftingManager; import net.minecraft.item.crafting.IRecipe; +import net.minecraft.item.crafting.Ingredient; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; +import net.minecraft.util.NonNullList; import net.minecraft.world.World; import appeng.api.AEApi; @@ -46,11 +43,17 @@ import appeng.core.AELog; import appeng.core.features.AEFeature; import appeng.util.Platform; import appeng.util.item.AEItemStack; +import net.minecraftforge.common.crafting.IShapedRecipe; public class PatternHelper implements ICraftingPatternDetails, Comparable { + private static final int CRAFTING_GRID_DIMENSION = 3; + private static final int ALL_INPUT_LIMIT = CRAFTING_GRID_DIMENSION * CRAFTING_GRID_DIMENSION; + private static final int CRAFTING_OUTPUT_LIMIT = 1; + private static final int PROCESSING_OUTPUT_LIMIT = 3; + private final ItemStack patternItem; private final InventoryCrafting crafting = new InventoryCrafting( new ContainerNull(), 3, 3 ); private final InventoryCrafting testFrame = new InventoryCrafting( new ContainerNull(), 3, 3 ); @@ -60,6 +63,7 @@ public class PatternHelper implements ICraftingPatternDetails, Comparable> substituteInputs; private final boolean isCrafting; private final boolean canSubstitute; private final Set failCache = new HashSet<>(); @@ -143,9 +147,11 @@ public class PatternHelper implements ICraftingPatternDetails, Comparable(ALL_INPUT_LIMIT); final Map tmpOutputs = new HashMap<>(); @@ -257,6 +263,14 @@ public class PatternHelper implements ICraftingPatternDetails, Comparable getSubstituteInputs(int slot) { + if (this.inputs[slot] == null) { + return Collections.emptyList(); + } + + return this.substituteInputs.computeIfAbsent(slot, value -> { + ItemStack[] matchingStacks = getRecipeIngredient(slot).getMatchingStacks(); + List itemList = new ArrayList<>(matchingStacks.length + 1); + for (ItemStack matchingStack : matchingStacks) { + itemList.add(AEItemStack.fromItemStack(matchingStack)); + } + + // Ensure that the specific item put in by the user is at the beginning, + // so that it takes precedence over substitutions + itemList.add(0, this.inputs[slot]); + return itemList; + }); + } + + /** + * Gets the {@link Ingredient} from the actual used recipe for a given slot-index into {@link #getInputs()}. + *

+ * Conversion is needed for two reasons: our sparse ingredients are always organized in a 3x3 grid, while Vanilla's + * ingredient list will be condensed to the actual recipe's grid size. In addition, in our 3x3 grid, the user can + * shift the actual recipe input to the right and down. + */ + private Ingredient getRecipeIngredient(int slot) { + + if (standardRecipe instanceof IShapedRecipe ) { + IShapedRecipe shapedRecipe = (IShapedRecipe) standardRecipe; + + return getShapedRecipeIngredient(slot, shapedRecipe.getRecipeWidth()); + } else { + return getShapelessRecipeIngredient(slot); + } + } + + private Ingredient getShapedRecipeIngredient(int slot, int recipeWidth) { + // Compute the offset of the user's input vs. crafting grid origin + // Which is >0 if they have empty rows above or to the left of their input + int topOffset = 0; + if (inputs[0] == null && inputs[1] == null && inputs[2] == null) { + topOffset++; // First row is fully empty + if (inputs[3] == null && inputs[4] == null && inputs[5] == null) { + topOffset++; // Second row is fully empty + } + } + int leftOffset = 0; + if (inputs[0] == null && inputs[3] == null && inputs[6] == null) { + leftOffset++; // First column is fully empty + if (inputs[1] == null && inputs[4] == null && inputs[7] == null) { + leftOffset++; // Second column is fully empty + } + } + + // Compute the x,y of the slot, as-if the recipe was anchored to 0,0 + int slotX = slot % CRAFTING_GRID_DIMENSION - leftOffset; + int slotY = slot / CRAFTING_GRID_DIMENSION - topOffset; + + // Compute the index into the recipe's ingredient list now + int ingredientIndex = slotY * recipeWidth + slotX; + + NonNullList ingredients = standardRecipe.getIngredients(); + + if (ingredientIndex < 0 || ingredientIndex > ingredients.size()) { + return Ingredient.EMPTY; + } + + return ingredients.get(ingredientIndex); + } + + private Ingredient getShapelessRecipeIngredient(int slot) { + // We map the list of *filled* sparse inputs to the shapeless (ergo unordered) + // ingredients. While these do not actually correspond to each other, + // since both lists have the same length, the mapping is at least stable. + int ingredientIndex = 0; + for (int i = 0; i < slot; i++) { + if (inputs[i] != null) { + ingredientIndex++; + } + } + + NonNullList ingredients = standardRecipe.getIngredients(); + if (ingredientIndex < ingredients.size()) { + return ingredients.get(ingredientIndex); + } + + return Ingredient.EMPTY; + } + @Override public ItemStack getOutput( final InventoryCrafting craftingInv, final World w ) { @@ -406,24 +492,6 @@ public class PatternHelper implements ICraftingPatternDetails, Comparable for output <%s> rejected inputs [%s]. %s", - this.standardRecipe.getRegistryName(), this.standardRecipe.getRecipeOutput(), joinActualInputs, foundAlternativeRecipe ); - } - @Override public boolean equals( final Object obj ) { diff --git a/src/main/java/appeng/items/misc/ItemEncodedPattern.java b/src/main/java/appeng/items/misc/ItemEncodedPattern.java index d91d6015f..ff00dbad5 100644 --- a/src/main/java/appeng/items/misc/ItemEncodedPattern.java +++ b/src/main/java/appeng/items/misc/ItemEncodedPattern.java @@ -225,7 +225,7 @@ public class ItemEncodedPattern extends AEBaseItem implements ICraftingPatternIt final ICraftingPatternDetails details = this.getPatternForItem( item, w ); - out = details != null ? details.getOutputs()[0].createItemStack() : ItemStack.EMPTY; + out = details != null ? details.getCondensedOutputs()[0].createItemStack() : ItemStack.EMPTY; SIMPLE_CACHE.put( item, out ); return out; diff --git a/src/main/java/appeng/me/cache/CraftingGridCache.java b/src/main/java/appeng/me/cache/CraftingGridCache.java index dcf4a1b98..74ff8ffb9 100644 --- a/src/main/java/appeng/me/cache/CraftingGridCache.java +++ b/src/main/java/appeng/me/cache/CraftingGridCache.java @@ -296,7 +296,7 @@ public class CraftingGridCache implements ICraftingGrid, ICraftingProviderHelper // new craftables! for( final ICraftingPatternDetails details : this.craftingMethods.keySet() ) { - for( IAEItemStack out : details.getOutputs() ) + for( IAEItemStack out : details.getCondensedOutputs() ) { out = out.copy(); out.reset(); diff --git a/src/main/java/appeng/me/cluster/implementations/CraftingCPUCluster.java b/src/main/java/appeng/me/cluster/implementations/CraftingCPUCluster.java index 0ee2fef79..da90676a1 100644 --- a/src/main/java/appeng/me/cluster/implementations/CraftingCPUCluster.java +++ b/src/main/java/appeng/me/cluster/implementations/CraftingCPUCluster.java @@ -494,22 +494,87 @@ public final class CraftingCPUCluster implements IAECluster, ICraftingCPU return null; } - private boolean canCraft( final ICraftingPatternDetails details, final IAEItemStack[] condensedInputs ) + private boolean canCraft(final ICraftingPatternDetails details, final IAEItemStack[] condensedInputs) { - for( IAEItemStack g : condensedInputs ) + if( !details.isCraftable() ) { + // Processing patterns are relatively easy + for ( IAEItemStack input : condensedInputs ) + { + final IAEItemStack ais = this.inventory.extractItems( input.copy(), Actionable.SIMULATE, + this.machineSrc ); - if( details.isCraftable() ) + if( ais == null || ais.getStackSize() < input.getStackSize() ) + { + return false; + } + } + } + else if( details.canSubstitute() ) + { + // When substitutions are allowed, we have to keep track of which items we've reserved + IAEItemStack[] inputs = details.getInputs(); + Map consumedCount = new HashMap<>(); + for ( int i = 0; i < inputs.length; i++ ) + { + List substitutes = details.getSubstituteInputs( i ); + if( substitutes.isEmpty() ) + { + continue; + } + + boolean found = false; + for ( IAEItemStack substitute : substitutes ) + { + for ( IAEItemStack fuzz : this.inventory.getItemList().findFuzzy( substitute, FuzzyMode.IGNORE_ALL ) ) + { + int alreadyConsumed = consumedCount.getOrDefault( fuzz, 0 ); + if( fuzz.getStackSize() - alreadyConsumed <= 0 ) + { + continue; // Already fully consumed by a previous slot of this recipe + } + + fuzz = fuzz.copy(); + fuzz.setStackSize( 1 ); // We're iterating over non condensed inputs which means there's 1 of each needed + final IAEItemStack ais = this.inventory.extractItems( fuzz, Actionable.SIMULATE, + this.machineSrc ); + + if( ais != null && ais.getStackSize() > 0 ) + { + // Mark 1 of the stack as consumed + consumedCount.merge( fuzz, 1, Integer::sum ); + found = true; + break; + } + } + if( found ) + { + break; + } + } + + if( !found ) + { + return false; + } + } + + } + else + { + // When no substitutions can occur, we can simply check that all items are accounted since + // each type of item should only occur once + for ( IAEItemStack g : condensedInputs ) { boolean found = false; - for( IAEItemStack fuzz : this.inventory.getItemList().findFuzzy( g, FuzzyMode.IGNORE_ALL ) ) + for ( IAEItemStack fuzz : this.inventory.getItemList().findFuzzy( g, FuzzyMode.IGNORE_ALL ) ) { fuzz = fuzz.copy(); fuzz.setStackSize( g.getStackSize() ); final IAEItemStack ais = this.inventory.extractItems( fuzz, Actionable.SIMULATE, this.machineSrc ); - if (ais != null && ais.getStackSize() == g.getStackSize()) + if( ais != null && ais.getStackSize() >= g.getStackSize() ) { found = true; break; @@ -526,15 +591,7 @@ public final class CraftingCPUCluster implements IAECluster, ICraftingCPU return false; } } - else - { - final IAEItemStack ais = this.inventory.extractItems( g.copy(), Actionable.SIMULATE, this.machineSrc ); - if (ais == null || ais.getStackSize() < g.getStackSize()) - { - return false; - } - } } return true; @@ -689,71 +746,67 @@ public final class CraftingCPUCluster implements IAECluster, ICraftingCPU ic = new InventoryCrafting( new ContainerNull(), 3, 3 ); boolean found = false; - for( int x = 0; x < input.length; x++ ) - { - if( input[x] != null ) - { + for (int x = 0; x < input.length; x++) { + if (input[x] != null) { found = false; - if( details.isCraftable() ) - { + if (details.isCraftable()) { final Collection itemList; - if( details.canSubstitute() ) - { - itemList = this.inventory.getItemList().findFuzzy( input[x], FuzzyMode.IGNORE_ALL ); - } - else - { - itemList = new ArrayList<>( 1 ); + if (details.canSubstitute()) { + final List substitutes = details.getSubstituteInputs(x); + itemList = new ArrayList<>(substitutes.size()); - final IAEItemStack item = this.inventory.getItemList().findPrecise( input[x] ); + for (IAEItemStack stack : substitutes) { + itemList.addAll(this.inventory.getItemList().findFuzzy(stack, + FuzzyMode.IGNORE_ALL)); + } + } else { + itemList = new ArrayList<>(1); - if( item != null ) - { - itemList.add( item ); + final IAEItemStack item = this.inventory.getItemList() + .findPrecise(input[x]); + + if (item != null) { + itemList.add(item); } } - for( IAEItemStack fuzz : itemList ) - { + for (IAEItemStack fuzz : itemList) { fuzz = fuzz.copy(); - fuzz.setStackSize( input[x].getStackSize() ); + fuzz.setStackSize(input[x].getStackSize()); - if( details.isValidItemForSlot( x, fuzz.createItemStack(), this.getWorld() ) ) - { - final IAEItemStack ais = this.inventory.extractItems( fuzz, Actionable.MODULATE, this.machineSrc ); - final ItemStack is = ais == null ? ItemStack.EMPTY : ais.createItemStack(); + if (details.isValidItemForSlot(x, fuzz.createItemStack(), + this.getWorld())) { + final IAEItemStack ais = this.inventory.extractItems(fuzz, + Actionable.MODULATE, this.machineSrc); + final ItemStack is = ais == null ? ItemStack.EMPTY + : ais.createItemStack(); - if( !is.isEmpty() ) - { - this.postChange( AEItemStack.fromItemStack( is ), this.machineSrc ); - ic.setInventorySlotContents( x, is ); + if (!is.isEmpty()) { + this.postChange(AEItemStack.fromItemStack(is), this.machineSrc); + ic.setInventorySlotContents(x, is); found = true; break; } } } - } - else - { - final IAEItemStack ais = this.inventory.extractItems( input[x].copy(), Actionable.MODULATE, this.machineSrc ); + } else { + final IAEItemStack ais = this.inventory.extractItems(input[x].copy(), + Actionable.MODULATE, this.machineSrc); final ItemStack is = ais == null ? ItemStack.EMPTY : ais.createItemStack(); - if( !is.isEmpty() ) - { - this.postChange( input[x], this.machineSrc ); - ic.setInventorySlotContents( x, is ); - if( is.getCount() == input[x].getStackSize() ) - { + if (!is.isEmpty()) { + this.postChange(input[x], this.machineSrc); + ic.setInventorySlotContents(x, is); + if (is.getCount() == input[x].getStackSize()) { found = true; continue; } } } - if( !found ) - { + if (!found) { break; } } diff --git a/src/main/java/appeng/util/item/FuzzyItemVariantList.java b/src/main/java/appeng/util/item/FuzzyItemVariantList.java index 90b4143b3..4968cd6f8 100644 --- a/src/main/java/appeng/util/item/FuzzyItemVariantList.java +++ b/src/main/java/appeng/util/item/FuzzyItemVariantList.java @@ -148,7 +148,7 @@ class FuzzyItemVariantList extends ItemVariantList { */ static ItemDamageBound makeLowerBound(final ItemStack stack, final FuzzyMode fuzzy) { - Preconditions.checkState( stack.getItem().isDamageable() || (Platform.isModLoaded( "gregtech" ) && stack.getItem() instanceof MetaTool), "Item#isDamageable() has to be true" ); + Preconditions.checkState( stack.getItem().isDamageable() , "Item#isDamageable() has to be true" ); int damage; if( fuzzy == FuzzyMode.IGNORE_ALL ) @@ -176,7 +176,7 @@ class FuzzyItemVariantList extends ItemVariantList { * lower number than the lower bound. It also is exclusive. */ static ItemDamageBound makeUpperBound(final ItemStack stack, final FuzzyMode fuzzy) { - Preconditions.checkState(stack.getItem().isDamageable() || (Platform.isModLoaded( "gregtech" ) && stack.getItem() instanceof MetaTool), "Item#isDamageable() has to be true"); + Preconditions.checkState(stack.getItem().isDamageable() , "Item#isDamageable() has to be true"); int damage; if (fuzzy == FuzzyMode.IGNORE_ALL) { diff --git a/src/main/java/appeng/util/item/ItemList.java b/src/main/java/appeng/util/item/ItemList.java index 782bd06d8..069bdcc38 100644 --- a/src/main/java/appeng/util/item/ItemList.java +++ b/src/main/java/appeng/util/item/ItemList.java @@ -151,7 +151,7 @@ public final class ItemList implements IItemList { } private ItemVariantList makeRecordMap(Item item) { - if (item.isDamageable() || (Platform.isModLoaded( "gregtech" ) && item instanceof MetaTool) ) { + if (item.isDamageable() ) { return new FuzzyItemVariantList(); } else { return new NormalItemVariantList();