aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/functionalTest/java/gregtech/test/GTParallelHelperTest.java86
-rw-r--r--src/functionalTest/java/gregtech/test/mock/MockIVoidableMachine.java55
-rw-r--r--src/main/java/gregtech/api/util/GT_ParallelHelper.java100
-rw-r--r--src/main/java/gregtech/api/util/GT_Recipe.java20
4 files changed, 221 insertions, 40 deletions
diff --git a/src/functionalTest/java/gregtech/test/GTParallelHelperTest.java b/src/functionalTest/java/gregtech/test/GTParallelHelperTest.java
new file mode 100644
index 0000000000..f5002a417c
--- /dev/null
+++ b/src/functionalTest/java/gregtech/test/GTParallelHelperTest.java
@@ -0,0 +1,86 @@
+package gregtech.test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import gregtech.api.enums.Materials;
+import gregtech.api.enums.TierEU;
+import gregtech.api.util.GT_ParallelHelper;
+import gregtech.api.util.GT_Recipe;
+import gregtech.api.util.GT_Utility;
+import gregtech.test.mock.MockIVoidableMachine;
+
+public class GTParallelHelperTest {
+
+ static GT_Recipe rubberRecipe;
+ static ItemStack[] inputItems;
+ static MockIVoidableMachine machine;
+
+ @BeforeAll
+ static void setup() {
+ machine = new MockIVoidableMachine();
+ ItemStack rubberDust = Materials.RawRubber.getDust(1);
+ ItemStack sulfurDust = Materials.Sulfur.getDust(1);
+ rubberRecipe = new GT_Recipe(
+ new ItemStack[] { rubberDust.copy(), sulfurDust.copy() },
+ null,
+ null,
+ null,
+ null,
+ new FluidStack[] { Materials.Rubber.getMolten(1000) },
+ 1,
+ 1,
+ 0);
+
+ inputItems = new ItemStack[] { GT_Utility.copyAmountUnsafe(Integer.MAX_VALUE, rubberDust),
+ GT_Utility.copyAmountUnsafe(Integer.MAX_VALUE, rubberDust),
+ GT_Utility.copyAmountUnsafe(Integer.MAX_VALUE, sulfurDust),
+ GT_Utility.copyAmountUnsafe(Integer.MAX_VALUE, sulfurDust) };
+ }
+
+ @Test
+ void OutputsIntegerOverflow() {
+ GT_ParallelHelper helper = new GT_ParallelHelper().setRecipe(rubberRecipe)
+ .setMachine(machine, false, false)
+ .setItemInputs(inputItems)
+ .setMaxParallel(4_000_000)
+ .setAvailableEUt(4_000_000)
+ .setOutputCalculation(true)
+ .setConsumption(false);
+ helper.build();
+ FluidStack[] fluidStacks = helper.getFluidOutputs();
+
+ assertEquals(2, fluidStacks.length);
+ assertEquals(Integer.MAX_VALUE, fluidStacks[0].amount);
+ assertEquals(4_000_000L * 1000 - Integer.MAX_VALUE, fluidStacks[1].amount);
+ }
+
+ @Test
+ void parallelIntegerOverflow() {
+ // Without batch mode
+ GT_ParallelHelper helperWithoutBatchMode = new GT_ParallelHelper().setRecipe(rubberRecipe)
+ .setMachine(machine, false, false)
+ .setItemInputs(inputItems)
+ .setMaxParallel(Integer.MAX_VALUE)
+ .setAvailableEUt(TierEU.MAX * 16)
+ .setConsumption(false)
+ .build();
+ assertEquals(Integer.MAX_VALUE, helperWithoutBatchMode.getCurrentParallel());
+
+ // With batch mode
+ GT_ParallelHelper helperWithBatchMode = new GT_ParallelHelper().setRecipe(rubberRecipe)
+ .setMachine(machine, false, false)
+ .setItemInputs(inputItems)
+ .setMaxParallel(Integer.MAX_VALUE / 50)
+ .setAvailableEUt(TierEU.MAX * 16)
+ .enableBatchMode(128)
+ .setConsumption(false)
+ .build();
+ assertEquals(Integer.MAX_VALUE, helperWithBatchMode.getCurrentParallel());
+ }
+}
diff --git a/src/functionalTest/java/gregtech/test/mock/MockIVoidableMachine.java b/src/functionalTest/java/gregtech/test/mock/MockIVoidableMachine.java
new file mode 100644
index 0000000000..4796d1d3ee
--- /dev/null
+++ b/src/functionalTest/java/gregtech/test/mock/MockIVoidableMachine.java
@@ -0,0 +1,55 @@
+package gregtech.test.mock;
+
+import java.util.List;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+import gregtech.api.enums.VoidingMode;
+import gregtech.api.interfaces.fluid.IFluidStore;
+import gregtech.api.interfaces.tileentity.IVoidable;
+
+public class MockIVoidableMachine implements IVoidable {
+
+ protected VoidingMode voidingMode = getDefaultVoidingMode();
+
+ @Override
+ public boolean supportsVoidProtection() {
+ return true;
+ }
+
+ @Override
+ public VoidingMode getVoidingMode() {
+ return voidingMode;
+ }
+
+ @Override
+ public void setVoidingMode(VoidingMode mode) {
+ voidingMode = mode;
+ }
+
+ @Override
+ public List<ItemStack> getItemOutputSlots(ItemStack[] toOutput) {
+ return null;
+ }
+
+ @Override
+ public List<? extends IFluidStore> getFluidOutputSlots(FluidStack[] toOutput) {
+ return null;
+ }
+
+ @Override
+ public boolean canDumpItemToME() {
+ return false;
+ }
+
+ @Override
+ public boolean canDumpFluidToME() {
+ return false;
+ }
+
+ @Override
+ public VoidingMode getDefaultVoidingMode() {
+ return VoidingMode.VOID_ALL;
+ }
+}
diff --git a/src/main/java/gregtech/api/util/GT_ParallelHelper.java b/src/main/java/gregtech/api/util/GT_ParallelHelper.java
index e836122f37..6ec736b15f 100644
--- a/src/main/java/gregtech/api/util/GT_ParallelHelper.java
+++ b/src/main/java/gregtech/api/util/GT_ParallelHelper.java
@@ -1,7 +1,9 @@
package gregtech.api.util;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
+import java.util.Random;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -373,12 +375,12 @@ public class GT_ParallelHelper {
double tickTimeAfterOC = calculator.setParallel(originalMaxParallel)
.calculateDurationUnderOneTick();
if (tickTimeAfterOC < 1) {
- maxParallel = (int) (maxParallel / tickTimeAfterOC);
+ maxParallel = GT_Utility.safeInt((long) (maxParallel / tickTimeAfterOC), 0);
}
int maxParallelBeforeBatchMode = maxParallel;
if (batchMode) {
- maxParallel *= batchModifier;
+ maxParallel = GT_Utility.safeInt((long) maxParallel * batchModifier, 0);
}
final ItemStack[] truncatedItemOutputs = recipe.mOutputs != null
@@ -511,31 +513,42 @@ public class GT_ParallelHelper {
itemOutputs = customItemOutputCalculation.apply(currentParallel);
return;
}
- itemOutputs = new ItemStack[truncatedItemOutputs.length];
+ ArrayList<ItemStack> itemOutputsList = new ArrayList<>();
+ Random rand = new Random();
for (int i = 0; i < truncatedItemOutputs.length; i++) {
- if (recipe.getOutputChance(i) >= 10000) {
- ItemStack item = recipe.getOutput(i)
- .copy();
- item.stackSize *= currentParallel;
- itemOutputs[i] = item;
- continue;
- }
- int items = 0;
- int itemStackSize = recipe.getOutput(i).stackSize;
- for (int roll = 0; roll < currentParallel; roll++) {
- if (recipe.getOutputChance(i) > XSTR.XSTR_INSTANCE.nextInt(10000)) {
- items += itemStackSize;
- }
- }
- ItemStack item = recipe.getOutput(i)
+ if (recipe.getOutput(i) == null) continue;
+ long items = 0;
+ ItemStack origin = recipe.getOutput(i)
.copy();
- if (items == 0) {
- item = null;
+ final long itemStackSize = origin.stackSize;
+
+ if (recipe.getOutputChance(i) >= 10000) {
+ items = itemStackSize * currentParallel;
} else {
- item.stackSize = items;
+ double chance = (double) recipe.getOutputChance(i) / 10000;
+ double mean = currentParallel * chance;
+ double stdDev = Math.sqrt(currentParallel * chance * (1 - chance));
+ // Check if everything within 3 standard deviations of mean is within the range
+ // of possible values (0 ~ currentParallel)
+ boolean isSuitableForFittingWithNormalDistribution = mean - 3 * stdDev >= 0
+ && mean + 3 * stdDev <= currentParallel;
+ if (isSuitableForFittingWithNormalDistribution) {
+ // Use Normal Distribution to fit Binomial Distribution
+ items = (long) Math.ceil(itemStackSize * (stdDev * rand.nextGaussian() + mean));
+ items = Math.max(Math.min(items, itemStackSize * currentParallel), 0);
+ } else {
+ // Do Binomial Distribution by loop
+ for (int roll = 0; roll < currentParallel; roll++) {
+ if (recipe.getOutputChance(i) > XSTR.XSTR_INSTANCE.nextInt(10000)) {
+ items += itemStackSize;
+ }
+ }
+ }
}
- itemOutputs[i] = item;
+
+ addItemsLong(itemOutputsList, origin, items);
}
+ itemOutputs = itemOutputsList.toArray(new ItemStack[0]);
}
private void calculateFluidOutputs(FluidStack[] truncatedFluidOutputs) {
@@ -543,16 +556,43 @@ public class GT_ParallelHelper {
fluidOutputs = customFluidOutputCalculation.apply(currentParallel);
return;
}
- fluidOutputs = new FluidStack[truncatedFluidOutputs.length];
+ ArrayList<FluidStack> fluidOutputsList = new ArrayList<>();
for (int i = 0; i < truncatedFluidOutputs.length; i++) {
- if (recipe.getFluidOutput(i) == null) {
- fluidOutputs[i] = null;
- } else {
- FluidStack tFluid = recipe.getFluidOutput(i)
- .copy();
- tFluid.amount *= currentParallel;
- fluidOutputs[i] = tFluid;
+ if (recipe.getFluidOutput(i) == null) continue;
+ FluidStack origin = recipe.getFluidOutput(i)
+ .copy();
+ long fluids = (long) origin.amount * currentParallel;
+
+ addFluidsLong(fluidOutputsList, origin, fluids);
+ }
+ fluidOutputs = fluidOutputsList.toArray(new FluidStack[0]);
+ }
+
+ public static void addItemsLong(ArrayList<ItemStack> itemList, ItemStack origin, long amount) {
+ if (amount >= 0) {
+ while (amount > Integer.MAX_VALUE) {
+ ItemStack item = origin.copy();
+ item.stackSize = Integer.MAX_VALUE;
+ itemList.add(item);
+ amount -= Integer.MAX_VALUE;
+ }
+ ItemStack item = origin.copy();
+ item.stackSize = (int) amount;
+ itemList.add(item);
+ }
+ }
+
+ public static void addFluidsLong(ArrayList<FluidStack> fluidList, FluidStack origin, long amount) {
+ if (amount >= 0) {
+ while (amount > Integer.MAX_VALUE) {
+ FluidStack fluid = origin.copy();
+ fluid.amount = Integer.MAX_VALUE;
+ fluidList.add(fluid);
+ amount -= Integer.MAX_VALUE;
}
+ FluidStack fluid = origin.copy();
+ fluid.amount = (int) amount;
+ fluidList.add(fluid);
}
}
diff --git a/src/main/java/gregtech/api/util/GT_Recipe.java b/src/main/java/gregtech/api/util/GT_Recipe.java
index 57f619eb7e..159ac1bbd1 100644
--- a/src/main/java/gregtech/api/util/GT_Recipe.java
+++ b/src/main/java/gregtech/api/util/GT_Recipe.java
@@ -415,12 +415,12 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
public void consumeInput(int amountMultiplier, FluidStack[] aFluidInputs, ItemStack... aInputs) {
if (amountMultiplier <= 0) return;
- int remainingCost;
+ long remainingCost;
if (aFluidInputs != null) {
for (FluidStack recipeFluidCost : mFluidInputs) {
if (recipeFluidCost != null) {
- remainingCost = recipeFluidCost.amount * amountMultiplier;
+ remainingCost = (long) recipeFluidCost.amount * amountMultiplier;
for (FluidStack providedFluid : aFluidInputs) {
if (providedFluid != null && providedFluid.isFluidEqual(recipeFluidCost)) {
@@ -441,7 +441,7 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
for (ItemStack recipeItemCost : mInputs) {
ItemStack unifiedItemCost = GT_OreDictUnificator.get_nocopy(true, recipeItemCost);
if (unifiedItemCost != null) {
- remainingCost = recipeItemCost.stackSize * amountMultiplier;
+ remainingCost = (long) recipeItemCost.stackSize * amountMultiplier;
for (ItemStack providedItem : aInputs) {
if (isNBTSensitive && !GT_Utility.areStacksEqual(providedItem, unifiedItemCost, false)) {
@@ -484,23 +484,23 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
if (aFluidInputs != null) {
// Create map for fluid -> stored amount
- Map<Fluid, Integer> fluidMap = new HashMap<>();
- Map<Fluid, Integer> fluidCost = new HashMap<>();
+ Map<Fluid, Long> fluidMap = new HashMap<>();
+ Map<Fluid, Long> fluidCost = new HashMap<>();
for (FluidStack fluidStack : aFluidInputs) {
if (fluidStack == null) continue;
- fluidMap.merge(fluidStack.getFluid(), fluidStack.amount, Integer::sum);
+ fluidMap.merge(fluidStack.getFluid(), (long) fluidStack.amount, Long::sum);
}
for (FluidStack fluidStack : mFluidInputs) {
if (fluidStack == null) continue;
- fluidCost.merge(fluidStack.getFluid(), fluidStack.amount, Integer::sum);
+ fluidCost.merge(fluidStack.getFluid(), (long) fluidStack.amount, Long::sum);
}
// Check how many parallels can it perform for each fluid
- for (Map.Entry<Fluid, Integer> costEntry : fluidCost.entrySet()) {
+ for (Map.Entry<Fluid, Long> costEntry : fluidCost.entrySet()) {
if (costEntry.getValue() > 0) {
currentParallel = Math.min(
currentParallel,
- (double) fluidMap.getOrDefault(costEntry.getKey(), 0) / costEntry.getValue());
+ (double) fluidMap.getOrDefault(costEntry.getKey(), 0L) / costEntry.getValue());
}
if (currentParallel <= 0) {
return 0;
@@ -509,7 +509,7 @@ public class GT_Recipe implements Comparable<GT_Recipe> {
}
double remainingCost;
- int providedAmount;
+ long providedAmount;
if (aInputs != null) {
nextRecipeItemCost: for (ItemStack recipeItemCost : mInputs) {