aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gtPlusPlus/core/util
diff options
context:
space:
mode:
authorKyium <43573052+Kyium@users.noreply.github.com>2023-09-13 08:24:41 +0100
committerGitHub <noreply@github.com>2023-09-13 16:24:41 +0900
commitf1d35b4dcece10106120856f5d5c30622e84616c (patch)
tree6aa3b6a6cdefd33d573a5e0ea294a125594b99b7 /src/main/java/gtPlusPlus/core/util
parent4c0679ad4f4e637161ce0bdf58bba818ba4859c5 (diff)
downloadGT5-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/gtPlusPlus/core/util')
-rw-r--r--src/main/java/gtPlusPlus/core/util/recipe/GT_RecipeUtils.java94
-rw-r--r--src/main/java/gtPlusPlus/core/util/recipe/RecipeHashStrat.java110
2 files changed, 204 insertions, 0 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;
+ }
+}