diff options
author | Kyium <43573052+Kyium@users.noreply.github.com> | 2023-09-13 08:24:41 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-13 16:24:41 +0900 |
commit | f1d35b4dcece10106120856f5d5c30622e84616c (patch) | |
tree | 6aa3b6a6cdefd33d573a5e0ea294a125594b99b7 /src/main/java | |
parent | 4c0679ad4f4e637161ce0bdf58bba818ba4859c5 (diff) | |
download | GT5-Unofficial-f1d35b4dcece10106120856f5d5c30622e84616c.tar.gz GT5-Unofficial-f1d35b4dcece10106120856f5d5c30622e84616c.tar.bz2 GT5-Unofficial-f1d35b4dcece10106120856f5d5c30622e84616c.zip |
Fix duplicate multiblock recipes (#736)
* Added GT_Recipe util and custom hashing strategy for GT_recipe
* Added duplicate recipe removal step into multiblocks using recipes instead of cells conversion method.
* Implemented improvements to efficiency and code cleanliness.
* minor language changes.
Diffstat (limited to 'src/main/java')
3 files changed, 231 insertions, 30 deletions
diff --git a/src/main/java/gtPlusPlus/core/util/recipe/GT_RecipeUtils.java b/src/main/java/gtPlusPlus/core/util/recipe/GT_RecipeUtils.java new file mode 100644 index 0000000000..94ba432847 --- /dev/null +++ b/src/main/java/gtPlusPlus/core/util/recipe/GT_RecipeUtils.java @@ -0,0 +1,94 @@ +package gtPlusPlus.core.util.recipe; + +import static gtPlusPlus.core.slots.SlotIntegratedCircuit.isRegularProgrammableCircuit; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.item.ItemStack; + +import org.apache.commons.lang3.ArrayUtils; + +import gnu.trove.map.hash.TCustomHashMap; +import gnu.trove.set.hash.TCustomHashSet; +import gregtech.api.util.GT_Recipe; +import gtPlusPlus.api.objects.Logger; + +public class GT_RecipeUtils { + + public static List<GT_Recipe> removeDuplicates(List<GT_Recipe> inputRecipes, String recipeMapName) { + TCustomHashSet<GT_Recipe> recipesHashSet = new TCustomHashSet<>(RecipeHashStrat.RecipeHashingStrategy); + ArrayList<GT_Recipe> recipeOutput = new ArrayList<>(); + TCustomHashMap<GT_Recipe, ItemStack> circuitMap = new TCustomHashMap<>(RecipeHashStrat.RecipeHashingStrategy); + int removedRecipeCount = 0; + + for (GT_Recipe recipeInput : inputRecipes) { + ItemStack savedCircuit = null; + // create a new input ItemStack array that does not contain programmable circuits if they were in the recipe + ArrayList<ItemStack> itemInputsWithoutProgrammableCircuit = new ArrayList<>(); + // iterate over the recipe input items and add them all to a new array without any programmable circuits + for (ItemStack itemStack : recipeInput.mInputs) { + if (itemStack == null) { + continue; + } + if (isRegularProgrammableCircuit(itemStack) == -1) { + itemInputsWithoutProgrammableCircuit.add(itemStack); + } else { + savedCircuit = itemStack; + } + } + GT_Recipe newRecipe = new GT_Recipe( + false, + itemInputsWithoutProgrammableCircuit.toArray(new ItemStack[0]), + recipeInput.mOutputs, + recipeInput.mSpecialItems, + recipeInput.mChances, + recipeInput.mFluidInputs, + recipeInput.mFluidOutputs, + recipeInput.mDuration, + recipeInput.mEUt, + recipeInput.mSpecialValue); + if (!recipesHashSet.contains(newRecipe)) { + // if the recipes customHashSet does not contain the new recipe then add it + recipesHashSet.add(newRecipe); + } else { + removedRecipeCount++; + } + if (savedCircuit != null) { + // if the current recipe has a circuit and the recipe (without circuits) is already in the + // circuit map then check make sure the circuit map saves the recipe with the smallest circuit + // damage value. This is to prevent a case where recipe load order would affect which duplicate + // recipes with multiple circuit values gets removed. + if (circuitMap.containsKey(newRecipe)) { + if (circuitMap.get(newRecipe).getItemDamage() > savedCircuit.getItemDamage()) { + circuitMap.put(newRecipe, savedCircuit); + } + } else { + // If the circuit map does not have the recipe in it yet then add it + circuitMap.put(newRecipe, savedCircuit); + } + } + } + // iterate over all recipes without duplicates and add them to the output. If the recipe had a programmable + // circuit in it then add it back with its damage value coming from the circuit map. + for (GT_Recipe filteredRecipe : recipesHashSet) { + // check to see if the recipe is in the circuit map + if (circuitMap.contains(filteredRecipe)) { + // add the circuit back + // update the item input array with the new input from + // ItemInputsWithoutProgrammableCircuit + circuit map circuit + filteredRecipe.mInputs = ArrayUtils.add(filteredRecipe.mInputs, circuitMap.get(filteredRecipe)); + } + // if the recipe was not in the circuit map then just add it the output as no updates to the item input + // needs to be made + recipeOutput.add(filteredRecipe); + } + // print results to log + Logger.INFO( + "Recipe Array duplication removal process completed for '" + recipeMapName + + "': '" + + removedRecipeCount + + "' removed."); + return recipeOutput; + } +} diff --git a/src/main/java/gtPlusPlus/core/util/recipe/RecipeHashStrat.java b/src/main/java/gtPlusPlus/core/util/recipe/RecipeHashStrat.java new file mode 100644 index 0000000000..8d32b9c830 --- /dev/null +++ b/src/main/java/gtPlusPlus/core/util/recipe/RecipeHashStrat.java @@ -0,0 +1,110 @@ +package gtPlusPlus.core.util.recipe; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.Objects; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; + +import gnu.trove.strategy.HashingStrategy; +import gregtech.api.util.GT_Recipe; + +public class RecipeHashStrat { + + public static final HashingStrategy<GT_Recipe> RecipeHashingStrategy = new HashingStrategy<>() { + + @Override + public int computeHashCode(GT_Recipe recipe) { + return com.google.common.base.Objects.hashCode(recipe.mDuration, recipe.mEUt); + } + + @Override + public boolean equals(GT_Recipe recipe1, GT_Recipe recipe2) { + return areRecipesEqual(recipe1, recipe2); + } + }; + + public static boolean areRecipesEqual(GT_Recipe recipe1, GT_Recipe recipe2) { + // sort all the arrays for recipe1 + RecipeHashStrat.sortItemStackArray(recipe1.mInputs); + RecipeHashStrat.sortItemStackArray(recipe1.mOutputs); + RecipeHashStrat.sortFluidStackArray(recipe1.mFluidInputs); + RecipeHashStrat.sortFluidStackArray(recipe1.mFluidOutputs); + // sort all the arrays for recipe2 + RecipeHashStrat.sortItemStackArray(recipe2.mInputs); + RecipeHashStrat.sortItemStackArray(recipe2.mOutputs); + RecipeHashStrat.sortFluidStackArray(recipe2.mFluidInputs); + RecipeHashStrat.sortFluidStackArray(recipe2.mFluidOutputs); + + // checks if the recipe EUt, Duration, inputs and outputs for both items and fluids are equal + if (recipe1.mEUt != recipe2.mEUt) { + return false; + } + if (recipe1.mDuration != recipe2.mDuration) { + return false; + } + if (!areItemsStackArraysEqual(recipe1.mInputs, recipe2.mInputs)) { + return false; + } + if (!areItemsStackArraysEqual(recipe1.mOutputs, recipe2.mOutputs)) { + return false; + } + if (!areFluidStackArraysEqual(recipe1.mFluidInputs, recipe2.mFluidInputs)) { + return false; + } + if (!areFluidStackArraysEqual(recipe1.mFluidOutputs, recipe2.mFluidOutputs)) { + return false; + } + return true; + + } + + public static void sortItemStackArray(ItemStack[] itemStackArray) { + Arrays.sort( + itemStackArray, + Comparator.<ItemStack, Integer>comparing(itemStack -> Item.getIdFromItem(itemStack.getItem())) + .thenComparing(ItemStack::getItemDamage).thenComparing(itemStack -> itemStack.stackSize)); + } + + public static void sortFluidStackArray(FluidStack[] fluidStackArray) { + Arrays.sort( + fluidStackArray, + Comparator.comparing(FluidStack::getFluidID).thenComparing(fluidStack -> fluidStack.amount)); + } + + public static boolean areItemsStackArraysEqual(ItemStack[] array1, ItemStack[] array2) { + if (array1.length != array2.length) { + return false; + } + for (int i = 0; i < array1.length; i++) { + if (!Objects.equals(array1[i].getItem(), array2[i].getItem())) { + return false; + } + if (!Objects.equals(array1[i].getItemDamage(), array2[i].getItemDamage())) { + return false; + } + if (!Objects.equals(array1[i].stackSize, array2[i].stackSize)) { + return false; + } + } + return true; + } + + public static boolean areFluidStackArraysEqual(FluidStack[] array1, FluidStack[] array2) { + if (array1.length != array2.length) { + return false; + } + for (int i = 0; i < array1.length; i++) { + // check if the string representation of both FluidStacks are not equal + if (!Objects.equals(array1[i].getFluid(), array2[i].getFluid())) { + return false; + } + if (!Objects.equals(array1[i].amount, array2[i].amount)) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/gtPlusPlus/xmod/gregtech/loaders/RecipeGen_MultisUsingFluidInsteadOfCells.java b/src/main/java/gtPlusPlus/xmod/gregtech/loaders/RecipeGen_MultisUsingFluidInsteadOfCells.java index 25300ed515..98430a33f6 100644 --- a/src/main/java/gtPlusPlus/xmod/gregtech/loaders/RecipeGen_MultisUsingFluidInsteadOfCells.java +++ b/src/main/java/gtPlusPlus/xmod/gregtech/loaders/RecipeGen_MultisUsingFluidInsteadOfCells.java @@ -1,6 +1,6 @@ package gtPlusPlus.xmod.gregtech.loaders; -import java.util.ArrayList; +import java.util.*; import net.minecraft.item.ItemStack; import net.minecraftforge.fluids.FluidStack; @@ -13,11 +13,12 @@ import gtPlusPlus.api.objects.Logger; import gtPlusPlus.api.objects.data.AutoMap; import gtPlusPlus.core.recipe.common.CI; import gtPlusPlus.core.util.minecraft.ItemUtils; +import gtPlusPlus.core.util.recipe.GT_RecipeUtils; public class RecipeGen_MultisUsingFluidInsteadOfCells { private static ItemStack mEmptyCell; - private static AutoMap<ItemStack> mItemsToIgnore = new AutoMap<ItemStack>(); + private static final AutoMap<ItemStack> mItemsToIgnore = new AutoMap<>(); private static boolean mInit = false; private static void init() { @@ -32,9 +33,7 @@ public class RecipeGen_MultisUsingFluidInsteadOfCells { return false; } if (a.getItem() == b.getItem()) { - if (a.getItemDamage() == b.getItemDamage()) { - return true; - } + return a.getItemDamage() == b.getItemDamage(); } return false; } @@ -49,9 +48,7 @@ public class RecipeGen_MultisUsingFluidInsteadOfCells { if (mEmptyCell != null) { ItemStack aTempStack = mEmptyCell.copy(); aTempStack.stackSize = aCell.stackSize; - if (GT_Utility.areStacksEqual(aTempStack, aCell)) { - return true; - } + return GT_Utility.areStacksEqual(aTempStack, aCell); } return false; } @@ -60,11 +57,7 @@ public class RecipeGen_MultisUsingFluidInsteadOfCells { if (ingot == null) { return null; } - FluidStack aFluid = GT_Utility.getFluidForFilledItem(ingot, true); - if (aFluid != null) { - return aFluid; - } - return null; + return GT_Utility.getFluidForFilledItem(ingot, true); } public static synchronized int generateRecipesNotUsingCells(GT_Recipe_Map aInputs, GT_Recipe_Map aOutputs) { @@ -72,6 +65,7 @@ public class RecipeGen_MultisUsingFluidInsteadOfCells { int aRecipesHandled = 0; int aInvalidRecipesToConvert = 0; int aOriginalCount = aInputs.mRecipeList.size(); + ArrayList<GT_Recipe> deDuplicationInputArray = new ArrayList<>(); recipe: for (GT_Recipe x : aInputs.mRecipeList) { if (x != null) { @@ -81,13 +75,13 @@ public class RecipeGen_MultisUsingFluidInsteadOfCells { FluidStack[] aInputFluids = x.mFluidInputs.clone(); FluidStack[] aOutputFluids = x.mFluidOutputs.clone(); - AutoMap<ItemStack> aInputItemsMap = new AutoMap<ItemStack>(); - AutoMap<ItemStack> aOutputItemsMap = new AutoMap<ItemStack>(); - AutoMap<FluidStack> aInputFluidsMap = new AutoMap<FluidStack>(); - AutoMap<FluidStack> aOutputFluidsMap = new AutoMap<FluidStack>(); + AutoMap<ItemStack> aInputItemsMap = new AutoMap<>(); + AutoMap<ItemStack> aOutputItemsMap = new AutoMap<>(); + AutoMap<FluidStack> aInputFluidsMap = new AutoMap<>(); + AutoMap<FluidStack> aOutputFluidsMap = new AutoMap<>(); // Iterate Inputs, Convert valid items into fluids - inputs: for (ItemStack aInputStack : aInputItems) { + for (ItemStack aInputStack : aInputItems) { FluidStack aFoundFluid = getFluidFromItemStack(aInputStack); if (aFoundFluid == null) { for (ItemStack aBadStack : mItemsToIgnore) { @@ -104,7 +98,7 @@ public class RecipeGen_MultisUsingFluidInsteadOfCells { } } // Iterate Outputs, Convert valid items into fluids - outputs: for (ItemStack aOutputStack : aOutputItems) { + for (ItemStack aOutputStack : aOutputItems) { FluidStack aFoundFluid = getFluidFromItemStack(aOutputStack); if (aFoundFluid == null) { for (ItemStack aBadStack : mItemsToIgnore) { @@ -121,13 +115,9 @@ public class RecipeGen_MultisUsingFluidInsteadOfCells { } } // Add Input fluids second - for (FluidStack aInputFluid : aInputFluids) { - aInputFluidsMap.add(aInputFluid); - } + aInputFluidsMap.addAll(Arrays.asList(aInputFluids)); // Add Output fluids second - for (FluidStack aOutputFluid : aOutputFluids) { - aOutputFluidsMap.add(aOutputFluid); - } + aOutputFluidsMap.addAll(Arrays.asList(aOutputFluids)); // Make some new Arrays ItemStack[] aNewItemInputs = new ItemStack[aInputItemsMap.size()]; @@ -151,10 +141,8 @@ public class RecipeGen_MultisUsingFluidInsteadOfCells { if (!ItemUtils.checkForInvalidItems(aNewItemInputs, aNewItemOutputs)) { aInvalidRecipesToConvert++; - continue recipe; // Skip this recipe entirely if we find an item we don't like + continue; // Skip this recipe entirely if we find an item we don't like } - - // Add Recipe to map GT_Recipe aNewRecipe = new GTPP_Recipe( false, aNewItemInputs, @@ -167,13 +155,22 @@ public class RecipeGen_MultisUsingFluidInsteadOfCells { x.mEUt, x.mSpecialValue); aNewRecipe.owners = new ArrayList<>(x.owners); - aOutputs.add(aNewRecipe); + + // add all recipes to an intermediate array + deDuplicationInputArray.add(aNewRecipe); + aRecipesHandled++; } else { aInvalidRecipesToConvert++; } } - + // cast arraylist of input to a regular array and pass it to a duplicate recipe remover. + List<GT_Recipe> deDuplicationOutputArray = GT_RecipeUtils + .removeDuplicates(deDuplicationInputArray, aOutputs.mNEIName); + // add each recipe from the above output to the intended recipe map + for (GT_Recipe recipe : deDuplicationOutputArray) { + aOutputs.add(recipe); + } Logger.INFO("Generated Recipes for " + aOutputs.mNEIName); Logger.INFO("Original Map contains " + aOriginalCount + " recipes."); Logger.INFO("Output Map contains " + aRecipesHandled + " recipes."); |