diff --git a/src/main/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderFluidIngredient.java b/src/main/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderFluidIngredient.java
index 9167785133c..fcd388d88c2 100644
--- a/src/main/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderFluidIngredient.java
+++ b/src/main/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderFluidIngredient.java
@@ -19,6 +19,7 @@
import lombok.Getter;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
/**
* Allows a {@link FluidIngredient} to be created with a ranged {@code amount}, which will be randomly rolled upon
@@ -53,6 +54,13 @@ protected IntProviderFluidIngredient(FluidIngredient inner, IntProvider provider
this.countProvider = provider;
}
+ protected IntProviderFluidIngredient(FluidIngredient inner, IntProvider provider, int sampledCount) {
+ super(inner.values, provider.getMaxValue(), null);
+ this.inner = inner;
+ this.countProvider = provider;
+ this.sampledCount = sampledCount;
+ }
+
@Override
public IntProviderFluidIngredient copy() {
IntProviderFluidIngredient ipfi = new IntProviderFluidIngredient(this.inner, this.countProvider);
@@ -150,6 +158,14 @@ public boolean isEmpty() {
return inner.isEmpty();
}
+ /**
+ * Resets the random roll on this ingredient
+ */
+ public void reroll() {
+ sampledCount = -1;
+ fluidStacks = null;
+ }
+
/**
* @param inner {@link FluidIngredient}
* @param provider usually as {@link UniformInt#of(int, int)}
@@ -162,6 +178,11 @@ public static IntProviderFluidIngredient of(FluidStack inner, int min, int max)
return IntProviderFluidIngredient.of(FluidIngredient.of(inner), UniformInt.of(min, max));
}
+ @Override
+ public boolean test(@Nullable FluidStack stack) {
+ return inner.test(stack);
+ }
+
/**
* Properties:
*
@@ -175,6 +196,7 @@ public static IntProviderFluidIngredient of(FluidStack inner, int min, int max)
json.add("count_provider", IntProvider.CODEC.encodeStart(JsonOps.INSTANCE, countProvider)
.getOrThrow(false, GTCEu.LOGGER::error));
json.add("inner", inner.toJson());
+ json.addProperty("sampledCount", sampledCount);
return json;
}
@@ -192,8 +214,9 @@ public static IntProviderFluidIngredient fromJson(JsonElement json) {
JsonObject jsonObject = GsonHelper.convertToJsonObject(json, "ingredient");
IntProvider amount = IntProvider.CODEC.parse(JsonOps.INSTANCE, jsonObject.get("count_provider"))
.getOrThrow(false, GTCEu.LOGGER::error);
+ int sampledCount = jsonObject.getAsJsonPrimitive("sampledCount").getAsInt();
FluidIngredient inner = FluidIngredient.fromJson(jsonObject.get("inner"));
- return new IntProviderFluidIngredient(inner, amount);
+ return new IntProviderFluidIngredient(inner, amount, sampledCount);
}
public CompoundTag toNBT() {
diff --git a/src/main/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderIngredient.java b/src/main/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderIngredient.java
index 60c114e7cad..518ceae445c 100644
--- a/src/main/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderIngredient.java
+++ b/src/main/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderIngredient.java
@@ -59,6 +59,13 @@ protected IntProviderIngredient(Ingredient inner, IntProvider countProvider) {
this.countProvider = countProvider;
}
+ protected IntProviderIngredient(Ingredient inner, IntProvider countProvider, int sampledCount) {
+ super(Stream.empty());
+ this.inner = inner;
+ this.countProvider = countProvider;
+ this.sampledCount = sampledCount;
+ }
+
/**
* @param inner {@link Ingredient}
* @param countProvider usually as {@link net.minecraft.util.valueproviders.UniformInt#of(int, int)}
@@ -92,7 +99,7 @@ public boolean test(@Nullable ItemStack stack) {
@Override
public ItemStack @NotNull [] getItems() {
if (itemStacks == null) {
- int cachedCount = getSampledCount(GTValues.RNG);
+ int cachedCount = getSampledCount();
if (cachedCount == 0) {
return EMPTY_STACK_ARRAY;
}
@@ -117,6 +124,19 @@ public boolean test(@Nullable ItemStack stack) {
else return inner.getItems()[0].copyWithCount(countProvider.getMaxValue());
}
+ /**
+ * If this ingredient has not yet had its {@link IntProviderIngredient#sampledCount} rolled, rolls it and
+ * returns the roll.
+ * If it has, returns the existing roll.
+ * Passthrough method, invokes {@link IntProviderIngredient#getSampledCount(RandomSource)} using the threadsafe
+ * {@link GTValues#RNG}.
+ *
+ * @return the amount rolled
+ */
+ public int getSampledCount() {
+ return getSampledCount(GTValues.RNG);
+ }
+
/**
* If this ingredient has not yet had its {@link IntProviderIngredient#sampledCount} rolled, rolls it and returns
* the roll.
@@ -139,6 +159,14 @@ public double getMidRoll() {
return ((countProvider.getMaxValue() + countProvider.getMinValue()) / 2.0);
}
+ /**
+ * Resets the random roll on this ingredient
+ */
+ public void reroll() {
+ sampledCount = -1;
+ itemStacks = null;
+ }
+
@Override
public @NotNull IntList getStackingIds() {
return inner.getStackingIds();
@@ -182,6 +210,7 @@ public static IntProviderIngredient fromJson(JsonObject json) {
json.add("count_provider", IntProvider.CODEC.encodeStart(JsonOps.INSTANCE, countProvider)
.getOrThrow(false, GTCEu.LOGGER::error));
json.add("ingredient", inner.toJson());
+ json.addProperty("sampledCount", sampledCount);
return json;
}
@@ -189,17 +218,20 @@ public static IntProviderIngredient fromJson(JsonObject json) {
@Override
public @NotNull IntProviderIngredient parse(FriendlyByteBuf buffer) {
- IntProvider amount = IntProvider.CODEC.parse(NbtOps.INSTANCE, buffer.readNbt().get("provider"))
+ var nbt = buffer.readNbt();
+ IntProvider provider = IntProvider.CODEC.parse(NbtOps.INSTANCE, nbt.get("provider"))
.getOrThrow(false, GTCEu.LOGGER::error);
- return new IntProviderIngredient(Ingredient.fromNetwork(buffer), amount);
+ int sampledCount = nbt.getInt("sampledCount");
+ return new IntProviderIngredient(Ingredient.fromNetwork(buffer), provider, sampledCount);
}
@Override
public @NotNull IntProviderIngredient parse(JsonObject json) {
- IntProvider amount = IntProvider.CODEC.parse(JsonOps.INSTANCE, json.get("count_provider"))
+ IntProvider provider = IntProvider.CODEC.parse(JsonOps.INSTANCE, json.get("count_provider"))
.getOrThrow(false, GTCEu.LOGGER::error);
Ingredient inner = Ingredient.fromJson(json.get("ingredient"));
- return new IntProviderIngredient(inner, amount);
+ int sampledCount = json.getAsJsonPrimitive("sampledCount").getAsInt();
+ return new IntProviderIngredient(inner, provider, sampledCount);
}
@Override
@@ -207,6 +239,7 @@ public void write(FriendlyByteBuf buffer, IntProviderIngredient ingredient) {
CompoundTag wrapper = new CompoundTag();
wrapper.put("provider", IntProvider.CODEC.encodeStart(NbtOps.INSTANCE, ingredient.countProvider)
.getOrThrow(false, GTCEu.LOGGER::error));
+ wrapper.putInt("sampledCount", ingredient.sampledCount);
buffer.writeNbt(wrapper);
ingredient.inner.toNetwork(buffer);
}
diff --git a/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderFluidIngredientTest.java b/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderFluidIngredientTest.java
new file mode 100644
index 00000000000..2cb3a4be6a0
--- /dev/null
+++ b/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderFluidIngredientTest.java
@@ -0,0 +1,1027 @@
+package com.gregtechceu.gtceu.api.recipe.ingredient;
+
+import com.gregtechceu.gtceu.GTCEu;
+import com.gregtechceu.gtceu.api.GTValues;
+import com.gregtechceu.gtceu.api.blockentity.MetaMachineBlockEntity;
+import com.gregtechceu.gtceu.api.capability.recipe.FluidRecipeCapability;
+import com.gregtechceu.gtceu.api.capability.recipe.IO;
+import com.gregtechceu.gtceu.api.capability.recipe.ItemRecipeCapability;
+import com.gregtechceu.gtceu.api.machine.MetaMachine;
+import com.gregtechceu.gtceu.api.machine.SimpleTieredMachine;
+import com.gregtechceu.gtceu.api.machine.multiblock.WorkableElectricMultiblockMachine;
+import com.gregtechceu.gtceu.api.machine.multiblock.WorkableMultiblockMachine;
+import com.gregtechceu.gtceu.api.machine.trait.NotifiableFluidTank;
+import com.gregtechceu.gtceu.api.machine.trait.NotifiableItemStackHandler;
+import com.gregtechceu.gtceu.api.recipe.GTRecipeType;
+import com.gregtechceu.gtceu.common.data.GTMaterials;
+import com.gregtechceu.gtceu.common.data.GTRecipeTypes;
+import com.gregtechceu.gtceu.common.machine.multiblock.part.FluidHatchPartMachine;
+import com.gregtechceu.gtceu.common.machine.multiblock.part.ItemBusPartMachine;
+import com.gregtechceu.gtceu.common.machine.multiblock.part.ParallelHatchPartMachine;
+import com.gregtechceu.gtceu.gametest.util.TestUtils;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.gametest.framework.BeforeBatch;
+import net.minecraft.gametest.framework.GameTest;
+import net.minecraft.gametest.framework.GameTestHelper;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.util.valueproviders.UniformInt;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.gametest.GameTestHolder;
+import net.minecraftforge.gametest.PrefixGameTestTemplate;
+
+import lombok.Getter;
+
+/**
+ * Test cases:
+ * Do many passes of most tests as a safeguard against bad rolls
+ * Same output more than once
+ * Out of bounds
+ * Output a multiple of batchparallels
+ * Rolls of 0
+ * Forced rolls of 0 breaking recipes
+ */
+@PrefixGameTestTemplate(false)
+@GameTestHolder(GTCEu.MOD_ID)
+public class IntProviderFluidIngredientTest {
+
+ private static GTRecipeType CR_RECIPE_TYPE;
+ private static GTRecipeType LCR_RECIPE_TYPE;
+ private static GTRecipeType CENTRIFUGE_RECIPE_TYPE;
+
+ // fluids used in recipes. Up top here for quick replacements.
+ private static final FluidStack CR_IN = GTMaterials.Hydrogen.getFluid(1);
+ private static final FluidStack CR_OUT = GTMaterials.Iron.getFluid(1);
+ private static final FluidStack LCR_IN = GTMaterials.Oxygen.getFluid(1);
+ private static final FluidStack LCR_OUT = GTMaterials.Copper.getFluid(1);
+ private static final FluidStack LCENT_IN = GTMaterials.Nitrogen.getFluid(1);
+ private static final FluidStack LCENT_OUT = GTMaterials.Gold.getFluid(1);
+ private static final FluidStack RUBBER = GTMaterials.Rubber.getFluid(1);
+ private static final FluidStack REDSTONE = GTMaterials.Redstone.getFluid(1);
+ private static final ItemStack COBBLE = new ItemStack(Items.COBBLESTONE);
+
+ /**
+ * How many times to repeat the Batch and Parallel random roll tests to avoid false positives
+ * Currently set to 7, with singleblock recipes processing up to 9 fluids, allowing for stacks of up to 63 fluids.
+ */
+ @Getter
+ private static final int REPLICAS = 7;
+
+ @BeforeBatch(batch = "RangedFluidIngredients")
+ public static void prepare(ServerLevel level) {
+ CR_RECIPE_TYPE = TestUtils.createRecipeType("ranged_fluid_ingredient_cr_tests", GTRecipeTypes.CHEMICAL_RECIPES);
+ LCR_RECIPE_TYPE = TestUtils.createRecipeType("ranged_fluid_ingredient_lcr_tests",
+ GTRecipeTypes.LARGE_CHEMICAL_RECIPES);
+ CENTRIFUGE_RECIPE_TYPE = TestUtils.createRecipeType("ranged_fluid_ingredient_centrifuge_tests",
+ GTRecipeTypes.CENTRIFUGE_RECIPES);
+
+ CR_RECIPE_TYPE.getLookup().addRecipe(CR_RECIPE_TYPE
+ .recipeBuilder(GTCEu.id("test_ranged_input_fluid_cr"))
+ .inputFluidsRanged(CR_IN, UniformInt.of(0, 9))
+ .inputItems(COBBLE)
+ .outputFluids(REDSTONE)
+ .EUt(GTValues.V[GTValues.HV])
+ .duration(2)
+ .buildRawRecipe());
+
+ CR_RECIPE_TYPE.getLookup().addRecipe(CR_RECIPE_TYPE
+ .recipeBuilder(GTCEu.id("test_ranged_output_fluid_cr"))
+ .inputFluids(CR_OUT)
+ .outputFluidsRanged(REDSTONE, UniformInt.of(0, 9))
+ .EUt(GTValues.V[GTValues.HV])
+ .duration(2)
+ .buildRawRecipe());
+
+ LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE
+ .recipeBuilder(GTCEu.id("test_ranged_input_fluid_lcr"))
+ .inputFluidsRanged(LCR_IN, UniformInt.of(0, 9))
+ .inputFluids(RUBBER)
+ .outputFluids(REDSTONE)
+ .EUt(GTValues.V[GTValues.HV])
+ .duration(2)
+ .buildRawRecipe());
+
+ LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE
+ .recipeBuilder(GTCEu.id("test_ranged_output_fluid_lcr"))
+ .inputFluids(LCR_OUT)
+ .outputFluidsRanged(REDSTONE, UniformInt.of(0, 9))
+ .EUt(GTValues.V[GTValues.HV])
+ .duration(2)
+ .buildRawRecipe());
+
+ CENTRIFUGE_RECIPE_TYPE.getLookup().addRecipe(CENTRIFUGE_RECIPE_TYPE
+ .recipeBuilder(GTCEu.id("test_ranged_input_fluid_cent"))
+ .inputFluidsRanged(LCENT_IN, UniformInt.of(0, 40))
+ .inputItems(COBBLE)
+ .outputFluids(REDSTONE)
+ .EUt(GTValues.V[GTValues.IV])
+ .duration(4)
+ .buildRawRecipe());
+
+ CENTRIFUGE_RECIPE_TYPE.getLookup().addRecipe(CENTRIFUGE_RECIPE_TYPE
+ .recipeBuilder(GTCEu.id("test_ranged_output_fluid_cent"))
+ .inputFluids(LCENT_OUT)
+ .outputFluidsRanged(REDSTONE, UniformInt.of(0, 40))
+ .EUt(GTValues.V[GTValues.IV])
+ .duration(4)
+ .buildRawRecipe());
+ }
+
+ private static MetaMachine getMetaMachine(BlockEntity entity) {
+ return ((MetaMachineBlockEntity) entity).getMetaMachine();
+ }
+
+ private record BusHolder(ItemBusPartMachine inputBus1, FluidHatchPartMachine inputHatch1,
+ ItemBusPartMachine outputBus1,
+ FluidHatchPartMachine outputHatch1, WorkableMultiblockMachine controller) {}
+
+ private record BusHolderBatchParallel(ItemBusPartMachine inputBus1, FluidHatchPartMachine inputHatch1,
+ ItemBusPartMachine outputBus1,
+ FluidHatchPartMachine outputHatch1,
+ WorkableElectricMultiblockMachine controller,
+ ParallelHatchPartMachine parallelHatch) {}
+
+ /**
+ * Retrieves the busses for this LCR template and force a multiblock structure check
+ *
+ * @param helper the GameTestHelper
+ * @return the busses, in the BusHolder record.
+ */
+ private static BusHolder getBussesAndFormLCR(GameTestHelper helper) {
+ WorkableMultiblockMachine controller = (WorkableMultiblockMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(1, 2, 0)));
+ TestUtils.formMultiblock(controller);
+ controller.setRecipeType(LCR_RECIPE_TYPE);
+ ItemBusPartMachine inputBus1 = (ItemBusPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(2, 1, 0)));
+ FluidHatchPartMachine inputHatch1 = (FluidHatchPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(2, 2, 0)));
+ ItemBusPartMachine outputBus1 = (ItemBusPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(0, 1, 0)));
+ FluidHatchPartMachine outputHatch1 = (FluidHatchPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(0, 2, 0)));
+ return new BusHolder(inputBus1, inputHatch1, outputBus1, outputHatch1, controller);
+ }
+
+ /**
+ * Retrieves the busses for this Large Centrifuge template and force a multiblock structure check
+ *
+ * @param helper the GameTestHelper
+ * @return the busses, in the BusHolder record.
+ */
+ private static BusHolderBatchParallel getBussesAndFormLCENT(GameTestHelper helper) {
+ WorkableElectricMultiblockMachine controller = (WorkableElectricMultiblockMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(2, 2, 0)));
+ TestUtils.formMultiblock(controller);
+ controller.setRecipeType(CENTRIFUGE_RECIPE_TYPE);
+ ItemBusPartMachine inputBus1 = (ItemBusPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(1, 2, 0)));
+ FluidHatchPartMachine inputHatch1 = (FluidHatchPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(0, 2, 0)));
+ ItemBusPartMachine outputBus1 = (ItemBusPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(2, 1, 0)));
+ FluidHatchPartMachine outputHatch1 = (FluidHatchPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(1, 1, 0)));
+ ParallelHatchPartMachine parallelHatch = (ParallelHatchPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(3, 3, 0)));
+ return new BusHolderBatchParallel(inputBus1, inputHatch1, outputBus1, outputHatch1, controller, parallelHatch);
+ }
+
+ // test for IntProviderFluidIngredient.test()
+ @GameTest(template = "empty", batch = "RangedFluidIngredients")
+ public static void rangedFluidIngredientTestEqualTest(GameTestHelper helper) {
+ var ingredient = IntProviderFluidIngredient.of(GTMaterials.Water.getFluid(1), 1, 5);
+ helper.assertTrue(ingredient.test(GTMaterials.Water.getFluid(3)),
+ "IntProviderFluidIngredient.test doesn't match when it should have");
+ // This should work since test only tries the fluid type.
+ helper.assertTrue(ingredient.test(GTMaterials.Water.getFluid(64)),
+ "IntProviderFluidIngredient.test doesn't match when it should have with value outside bounds");
+ helper.assertFalse(ingredient.test(GTMaterials.Lava.getFluid(3)),
+ "IntProviderFluidIngredient.test shouldn't match with different fluids");
+ helper.succeed();
+ }
+
+ // test for IntProviderFluidIngredient.getStacks()
+ @GameTest(template = "empty", batch = "RangedFluidIngredients")
+ public static void rangedFluidIngredientGetStacksTest(GameTestHelper helper) {
+ var ingredient = IntProviderFluidIngredient.of(GTMaterials.Water.getFluid(1), 1, 500000);
+ var stacks = ingredient.getStacks();
+ helper.assertTrue(stacks.length == 1,
+ "IntProviderFluidIngredient should only return 1 fluid when made with 1 fluid");
+ helper.assertTrue(stacks[0].isFluidEqual(GTMaterials.Water.getFluid(1)),
+ "IntProviderFluidIngredient should have fluid equal to what it was made with");
+ helper.assertTrue(stacks[0].isFluidStackIdentical(ingredient.getStacks()[0]),
+ "IntProviderFluidIngredient.getStacks shouldn't change between getStacks calls");
+ ingredient.reroll();
+ helper.assertFalse(stacks[0].isFluidStackIdentical(ingredient.getStacks()[0]),
+ "IntProviderFluidIngredient.getStacks should have changed after rerolling");
+ helper.succeed();
+ }
+
+ // test for IntProviderFluidIngredient.toJson()
+ @GameTest(template = "empty", batch = "RangedFluidIngredients")
+ public static void rangedIngredientJsonTest(GameTestHelper helper) {
+ var ingredient = IntProviderFluidIngredient.of(GTMaterials.Water.getFluid(1), 1, 500000);
+
+ // serialize/deserialize before rolling count
+ var jsonPreRoll = ingredient.toJson();
+ var ingredientDeserializedPreRoll = IntProviderFluidIngredient.fromJson(jsonPreRoll);
+
+ var stacks = ingredient.getStacks();
+ var stacksDeserializedPreRoll = ingredientDeserializedPreRoll.getStacks();
+
+ // serialize/deserialize after rolling count
+ var jsonPostRoll = ingredient.toJson();
+ var ingredientDeserializedPostRoll = IntProviderFluidIngredient.fromJson(jsonPostRoll);
+ var stacksDeserializedPostRoll = ingredientDeserializedPostRoll.getStacks();
+
+ helper.assertTrue(
+ stacks.length == stacksDeserializedPreRoll.length && stacks.length == stacksDeserializedPostRoll.length,
+ "IntProviderFluidIngredient should only return 1 fluid when made with 1 fluid, even after serializing");
+ helper.assertTrue(stacksDeserializedPreRoll[0].isFluidEqual(GTMaterials.Water.getFluid(1)),
+ "IntProviderFluidIngredient should have fluid equal to what it was made with after serializing");
+ helper.assertTrue(stacksDeserializedPostRoll[0].isFluidEqual(GTMaterials.Water.getFluid(1)),
+ "IntProviderFluidIngredient should have fluid equal to what it was made with after serializing");
+ helper.assertFalse(TestUtils.areFluidStacksEqual(stacksDeserializedPreRoll, ingredient.getStacks()),
+ "IntProviderFluidIngredient.getStacks should be different if it wasn't rolled before serializing");
+ helper.assertTrue(TestUtils.areFluidStacksEqual(stacksDeserializedPostRoll, ingredient.getStacks()),
+ "IntProviderFluidIngredient.getStacks shouldn't change between getStacks calls if it was rolled before serializing");
+ helper.succeed();
+ }
+
+ // Test for singleblock machine with ranged fluid input.
+ // Forcibly sabotages the first recipe run, setting its output amount to 0 to ensure that doesn't break the recipe.
+ // This is specifically a test for #3593 / #3594
+ @GameTest(template = "singleblock_charged_cr", batch = "RangedFluidIngredients")
+ public static void singleblockRangedFluidOutputSabotaged(GameTestHelper helper) {
+ SimpleTieredMachine machine = (SimpleTieredMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(0, 1, 0)));
+
+ machine.setRecipeType(CR_RECIPE_TYPE);
+ NotifiableFluidTank fluidIn = (NotifiableFluidTank) machine
+ .getCapabilitiesFlat(IO.IN, FluidRecipeCapability.CAP).get(0);
+ NotifiableFluidTank fluidOut = (NotifiableFluidTank) machine
+ .getCapabilitiesFlat(IO.OUT, FluidRecipeCapability.CAP).get(0);
+
+ int runs = 7;
+ fluidIn.setFluidInTank(0, new FluidStack(CR_OUT, runs));
+ // 1t to turn on, 2t per recipe run
+ // get the result of each roll independently
+ int[] addedRolls = new int[runs];
+
+ helper.runAfterDelay(4, () -> {
+ if (machine.getRecipeLogic().getLastRecipe().getOutputContents(FluidRecipeCapability.CAP).get(0)
+ .getContent() instanceof IntProviderFluidIngredient ingredient) {
+ ingredient.setSampledCount(0);
+
+ if (ingredient.getSampledCount() != 0) {
+ helper.fail("Singleblock Ranged Fluid Output sabotage failed! " +
+ "Output count not was altered!");
+ }
+ } else {
+ helper.fail("Singleblock Ranged Fluid Output sabotage failed! " +
+ "Recipe logic did not contain a Ranged Output!");
+ }
+ });
+ for (int i = 0; i < runs; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(2 * i + 3, () -> {
+ addedRolls[finalI] = (int) fluidOut.getTotalContentAmount();
+ });
+ }
+ // check the results of all rolls together
+ helper.runAfterDelay(runs * 2 + 1, () -> {
+ FluidStack results = fluidOut.getFluidInTank(0);
+ helper.assertTrue(TestUtils.isFluidWithinRange(results, runs, runs * 9),
+ "Sabotaged Singleblock CR didn't produce correct number of fluids, produced [" +
+ results.getAmount() + "] not [" + runs + "-" + (runs * 9) + "]");
+ helper.assertFalse((results.getAmount() == runs * 9),
+ "Sabotaged Singleblock CR rolled max value on every roll (how??)");
+ helper.assertFalse((results.getAmount() == runs * 0),
+ "Sabotaged Singleblock CR rolled min value on every roll! " +
+ "This is the failure this sabotage was intended to induce.");
+
+ // check if all the rolls were equal, but not min/max
+ int[] rolls = new int[runs];
+ rolls[0] = addedRolls[0];
+ boolean allEqual = false;
+ for (int i = 1; i < runs; i++) {
+ rolls[i] = addedRolls[i] - addedRolls[i - 1];
+ if (rolls[i] == rolls[i - 1]) {
+ allEqual = true;
+ } else {
+ allEqual = false;
+ break;
+ }
+ }
+ helper.assertFalse(allEqual,
+ "Sabotaged Singleblock CR rolled the same value on every input roll (rolled " + rolls[0] + ")");
+ helper.succeed();
+ });
+ }
+
+ // Failure Test for singleblock machine with ranged fluid input
+ // Provides too little input fluid, should not run recipes.
+ @GameTest(template = "singleblock_charged_cr", batch = "RangedFluidIngredients")
+ public static void singleblockRangedFluidInputFailure(GameTestHelper helper) {
+ SimpleTieredMachine machine = (SimpleTieredMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(0, 1, 0)));
+
+ machine.setRecipeType(CR_RECIPE_TYPE);
+ NotifiableItemStackHandler itemIn = (NotifiableItemStackHandler) machine
+ .getCapabilitiesFlat(IO.IN, ItemRecipeCapability.CAP).get(0);
+ NotifiableFluidTank fluidIn = (NotifiableFluidTank) machine
+ .getCapabilitiesFlat(IO.IN, FluidRecipeCapability.CAP).get(0);
+ NotifiableFluidTank fluidOut = (NotifiableFluidTank) machine
+ .getCapabilitiesFlat(IO.OUT, FluidRecipeCapability.CAP).get(0);
+
+ int runs = 10;
+ fluidIn.setFluidInTank(0, new FluidStack(CR_IN, 8));
+ itemIn.setStackInSlot(1, COBBLE.copyWithCount(runs));
+ // 1t to turn on, 2t per non-recipe run
+ helper.runAfterDelay(runs * 2 + 1, () -> {
+ FluidStack results = fluidIn.getFluidInTank(0);
+
+ helper.assertTrue(fluidOut.isEmpty(),
+ "Singleblock CR should not have run, ran [" +
+ fluidOut.getFluidInTank(0).getAmount() + "] times");
+ helper.assertTrue(TestUtils.isFluidStackEqual(results, new FluidStack(CR_IN, 8)),
+ "Singleblock CR should not have consumed items, consumed [" +
+ (8 - results.getAmount()) + "]");
+
+ helper.succeed();
+ });
+ }
+
+ // Test for singleblock machine with ranged fluid input
+ @GameTest(template = "singleblock_charged_cr", batch = "RangedFluidIngredients")
+ public static void singleblockRangedFluidInput(GameTestHelper helper) {
+ SimpleTieredMachine machine = (SimpleTieredMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(0, 1, 0)));
+
+ machine.setRecipeType(CR_RECIPE_TYPE);
+ NotifiableItemStackHandler itemIn = (NotifiableItemStackHandler) machine
+ .getCapabilitiesFlat(IO.IN, ItemRecipeCapability.CAP).get(0);
+ NotifiableFluidTank fluidIn = (NotifiableFluidTank) machine
+ .getCapabilitiesFlat(IO.IN, FluidRecipeCapability.CAP).get(0);
+ NotifiableFluidTank fluidOut = (NotifiableFluidTank) machine
+ .getCapabilitiesFlat(IO.OUT, FluidRecipeCapability.CAP).get(0);
+
+ int runs = 7;
+ fluidIn.setFluidInTank(0, new FluidStack(CR_IN, 64));
+ itemIn.setStackInSlot(0, COBBLE.copyWithCount(runs));
+ // 1t to turn on, 2t per recipe run
+ // get the result of each roll independently
+ int[] addedRolls = new int[runs];
+ for (int i = 0; i < runs; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(2 * i + 1, () -> {
+ addedRolls[finalI] = fluidIn.getFluidInTank(0).getAmount();
+ });
+ }
+ // check the results of all rolls together
+ helper.runAfterDelay(runs * 2 + 2, () -> {
+ FluidStack results = fluidIn.getFluidInTank(0);
+ int upperLimit = 64 - (runs * 0);
+ int lowerLimit = 64 - (runs * 9);
+ helper.assertTrue(TestUtils.isFluidStackEqual(fluidOut.getFluidInTank(0), new FluidStack(REDSTONE, runs)),
+ "Singleblock CR didn't complete correct number of recipes, completed [" +
+ fluidOut.getFluidInTank(0).getAmount() + "] not [" + runs + "]");
+ helper.assertTrue(TestUtils.isFluidWithinRange(results, lowerLimit, upperLimit),
+ "Singleblock CR didn't consume correct number of fluids, consumed [" +
+ (64 - results.getAmount()) + "] not [" + lowerLimit + "-" + upperLimit + "]");
+ helper.assertFalse((results.getAmount() == lowerLimit),
+ "Singleblock CR rolled max value on every roll");
+ helper.assertFalse((results.getAmount() == upperLimit),
+ "Singleblock CR rolled min value on every roll");
+
+ // check if all the rolls were equal, but not min/max
+ int[] rolls = new int[runs];
+ rolls[0] = 64 - addedRolls[0];
+ boolean allEqual = false;
+ for (int i = 1; i < runs; i++) {
+ rolls[i] = addedRolls[i - 1] - addedRolls[i];
+ if (rolls[i] == rolls[i - 1]) {
+ allEqual = true;
+ } else {
+ allEqual = false;
+ break;
+ }
+ }
+ helper.assertFalse(allEqual,
+ "Singleblock CR rolled the same value on every input roll (rolled " + rolls[0] + ")");
+ helper.succeed();
+ });
+ }
+
+ // Test for singleblock machine with ranged fluid input
+ @GameTest(template = "singleblock_charged_cr", batch = "RangedFluidIngredients")
+ public static void singleblockRangedFluidOutput(GameTestHelper helper) {
+ SimpleTieredMachine machine = (SimpleTieredMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(0, 1, 0)));
+
+ machine.setRecipeType(CR_RECIPE_TYPE);
+ NotifiableFluidTank fluidIn = (NotifiableFluidTank) machine
+ .getCapabilitiesFlat(IO.IN, FluidRecipeCapability.CAP).get(0);
+ NotifiableFluidTank fluidOut = (NotifiableFluidTank) machine
+ .getCapabilitiesFlat(IO.OUT, FluidRecipeCapability.CAP).get(0);
+
+ int runs = 7;
+ fluidIn.setFluidInTank(0, new FluidStack(CR_OUT, runs));
+ // 1t to turn on, 2t per recipe run
+ // get the result of each roll independently
+ int[] addedRolls = new int[runs];
+ for (int i = 0; i < runs; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(2 * i + 3, () -> {
+ addedRolls[finalI] = fluidOut.getFluidInTank(0).getAmount();
+ });
+ }
+ // check the results of all rolls together
+ helper.runAfterDelay(runs * 2 + 1, () -> {
+ helper.assertTrue(fluidIn.getFluidInTank(0).isEmpty(),
+ "Singleblock CR didn't complete correct number of recipes, completed [" +
+ fluidIn.getFluidInTank(0).getAmount() + "] not [" + runs + "]");
+ FluidStack results = fluidOut.getFluidInTank(0);
+ helper.assertTrue(TestUtils.isFluidWithinRange(results, runs, runs * 9),
+ "Singleblock CR didn't produce correct number of fluids, produced [" +
+ results.getAmount() + "] not [" + runs + "-" + (runs * 9) + "]");
+ helper.assertFalse((results.getAmount() == runs * 9),
+ "Singleblock CR rolled max value on every roll");
+ helper.assertFalse((results.getAmount() == runs * 0),
+ "Singleblock CR rolled min value on every roll");
+
+ // check if all the rolls were equal, but not min/max
+ int[] rolls = new int[runs];
+ rolls[0] = addedRolls[0];
+ boolean allEqual = false;
+ for (int i = 1; i < runs; i++) {
+ rolls[i] = addedRolls[i] - addedRolls[i - 1];
+ if (rolls[i] == rolls[i - 1]) {
+ allEqual = true;
+ } else {
+ allEqual = false;
+ break;
+ }
+ }
+ helper.assertFalse(allEqual,
+ "Singleblock CR rolled the same value on every input roll (rolled " + rolls[0] + ")");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with ranged fluid input
+ @GameTest(template = "lcr_ranged_ingredients",
+ batch = "RangedFluidIngredients")
+ public static void multiblockLCRRangedFluidInput(GameTestHelper helper) {
+ BusHolder busHolder = getBussesAndFormLCR(helper);
+
+ NotifiableFluidTank fluidIn = busHolder.inputHatch1.tank;
+ NotifiableFluidTank fluidOut = busHolder.outputHatch1.tank;
+
+ int runs = 7;
+ fluidIn.setFluidInTank(0, new FluidStack(LCR_IN, 64));
+ fluidIn.setFluidInTank(1, new FluidStack(RUBBER, runs));
+ // 1t to turn on, 2t per recipe run
+ // get the result of each roll independently
+ int[] addedRolls = new int[runs];
+ for (int i = 0; i < runs; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(2 * i + 1, () -> {
+ addedRolls[finalI] = fluidIn.getFluidInTank(0).getAmount();
+ });
+ }
+ // check the results of all rolls together
+ helper.runAfterDelay(runs * 2 + 2, () -> {
+ FluidStack results = fluidIn.getFluidInTank(0);
+ int upperLimit = 64 - (runs * 0);
+ int lowerLimit = 64 - (runs * 9);
+ helper.assertTrue(TestUtils.isFluidStackEqual(fluidOut.getFluidInTank(0), new FluidStack(REDSTONE, runs)),
+ "LCR didn't complete correct number of recipes, completed [" +
+ fluidOut.getFluidInTank(0).getAmount() + "] not [" + runs + "]");
+ helper.assertTrue(TestUtils.isFluidWithinRange(results, lowerLimit, upperLimit),
+ "LCR didn't consume correct number of fluids, consumed [" +
+ (64 - results.getAmount()) + "] not [" + lowerLimit + "-" + upperLimit + "]");
+ helper.assertFalse((results.getAmount() == lowerLimit),
+ "LCR rolled max value on every roll");
+ helper.assertFalse((results.getAmount() == upperLimit),
+ "LCR rolled min value on every roll");
+
+ // check if all the rolls were equal, but not min/max
+ int[] rolls = new int[runs];
+ rolls[0] = 64 - addedRolls[0];
+ boolean allEqual = false;
+ for (int i = 1; i < runs; i++) {
+ rolls[i] = addedRolls[i - 1] - addedRolls[i];
+ if (rolls[i] == rolls[i - 1]) {
+ allEqual = true;
+ } else {
+ allEqual = false;
+ break;
+ }
+ }
+ helper.assertFalse(allEqual,
+ "LCR rolled the same value on every input roll (rolled " + rolls[0] + ")");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with ranged fluid input
+ @GameTest(template = "lcr_ranged_ingredients",
+ batch = "RangedFluidIngredients")
+ public static void multiblockLCRRangedFluidOutput(GameTestHelper helper) {
+ BusHolder busHolder = getBussesAndFormLCR(helper);
+
+ final NotifiableFluidTank fluidIn = busHolder.inputHatch1.tank;
+ final NotifiableFluidTank fluidOut = busHolder.outputHatch1.tank;
+
+ int runs = 7;
+ fluidIn.setFluidInTank(0, new FluidStack(LCR_OUT, runs));
+ // 1t to turn on, 2t per recipe run
+ // get the result of each roll independently
+ int[] addedRolls = new int[runs];
+ for (int i = 0; i < runs; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(2 * i + 3, () -> {
+ addedRolls[finalI] = fluidOut.getFluidInTank(0).getAmount();
+ });
+ }
+ // check the results of all rolls together
+ helper.runAfterDelay(runs * 2 + 1, () -> {
+ helper.assertTrue(fluidIn.getFluidInTank(0).isEmpty(),
+ "LCR didn't complete correct number of recipes, completed [" +
+ fluidIn.getFluidInTank(0).getAmount() + "] not [" + runs + "]");
+ FluidStack results = fluidOut.getFluidInTank(0);
+ helper.assertTrue(TestUtils.isFluidWithinRange(results, runs, runs * 9),
+ "LCR didn't produce correct number of fluids, produced [" +
+ results.getAmount() + "] not [" + runs + "-" + (runs * 9) + "]");
+ helper.assertFalse((results.getAmount() == runs * 9),
+ "LCR rolled max value on every roll");
+ helper.assertFalse((results.getAmount() == runs * 0),
+ "LCR rolled min value on every roll");
+
+ // check if all the rolls were equal, but not min/max
+ int[] rolls = new int[runs];
+ rolls[0] = addedRolls[0];
+ boolean allEqual = false;
+ for (int i = 1; i < runs; i++) {
+ rolls[i] = addedRolls[i] - addedRolls[i - 1];
+ if (rolls[i] == rolls[i - 1]) {
+ allEqual = true;
+ } else {
+ allEqual = false;
+ break;
+ }
+ }
+ helper.assertFalse(allEqual,
+ "LCR rolled the same value on every input roll (rolled " + rolls[0] + ")");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with 16x Parallels with ranged fluid input
+ @GameTest(template = "large_centrifuge_zpm_batch_parallel16",
+ batch = "RangedFluidIngredients",
+ timeoutTicks = 200)
+ public static void multiblockLCentRangedFluidInput16Parallel(GameTestHelper helper) {
+ BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper);
+
+ NotifiableItemStackHandler itemIn = busHolder.inputBus1.getInventory();
+ final NotifiableFluidTank fluidIn = busHolder.inputHatch1.tank;
+ final NotifiableFluidTank fluidOut = busHolder.outputHatch1.tank;
+
+ int batches = 1; // unused on this test
+ int parallels = 16;
+ final int amount = 40 * batches * parallels;
+ busHolder.controller.setBatchEnabled(false);
+ busHolder.parallelHatch.setCurrentParallel(parallels);
+
+ itemIn.setStackInSlot(0, COBBLE.copyWithCount(batches * parallels));
+ fluidIn.setFluidInTank(0, new FluidStack(LCENT_IN, amount));
+
+ // 1t to turn on, 4t per recipe run
+ // 16 parallels
+ // check the results of all rolls together
+ // repeat recipe REPLICAS times
+ int[] rolls = new int[REPLICAS];
+ for (int i = 1; i <= REPLICAS; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(17 * finalI, () -> {
+ FluidStack results = fluidIn.getFluidInTank(0);
+ int upperLimit = amount - (batches * parallels * 0);
+ int lowerLimit = amount - (batches * parallels * 40);
+ int completed = batches * parallels * finalI;
+ helper.assertTrue(
+ TestUtils.isFluidStackEqual(new FluidStack(fluidOut.getFluidInTank(0),
+ ((int) Math.round(fluidOut.getTotalContentAmount()))),
+ new FluidStack(REDSTONE, completed)),
+ "Parallel LCent didn't complete correct number of recipes, completed [" +
+ ((int) Math.round(fluidOut.getTotalContentAmount())) + "] not [" +
+ completed + "]");
+ helper.assertTrue(TestUtils.isFluidWithinRange(results, lowerLimit, upperLimit),
+ "Parallel LCent didn't consume correct number of fluids, consumed " +
+ (amount - results.getAmount()) + "] not [" + lowerLimit + "-" + upperLimit + "]");
+
+ rolls[finalI - 1] = amount - results.getAmount();
+
+ // reset for a rerun
+ itemIn.setStackInSlot(0, COBBLE.copyWithCount(batches * parallels));
+ fluidIn.setFluidInTank(0, new FluidStack(LCENT_IN, 40 * batches * parallels));
+ });
+ }
+
+ helper.runAfterDelay(1 + 17 * REPLICAS, () -> {
+ // check if each roll was a multiple of run count
+ boolean sus = false;
+ for (int i = 0; i < REPLICAS; i++) {
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Parallel LCent ranged fluid input test iteration " + i + " consumed [" +
+ rolls[i] + "] fluids, a multiple of its Batch * Parallel count (" + (parallels) +
+ "). If this message only appears once, this is likely a false positive.");
+ } else {
+ sus = false;
+ break;
+ }
+ }
+
+ helper.assertFalse(sus, "Parallel LCent ranged fluid input test rolled exactly even to " +
+ "Batch * Parallel count on every iteration");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with 16x Parallels with ranged fluid output
+ @GameTest(template = "large_centrifuge_zpm_batch_parallel16",
+ batch = "RangedFluidIngredients",
+ timeoutTicks = 200)
+ public static void multiblockLCentRangedFluidOutput16Parallel(GameTestHelper helper) {
+ BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper);
+
+ final NotifiableFluidTank fluidIn = busHolder.inputHatch1.tank;
+ final NotifiableFluidTank fluidOut = busHolder.outputHatch1.tank;
+
+ int batches = 1; // unused on this test
+ int parallels = 16;
+ fluidIn.setFluidInTank(0, new FluidStack(LCENT_OUT, 16));
+
+ busHolder.controller.setBatchEnabled(false);
+ busHolder.parallelHatch.setCurrentParallel(parallels);
+
+ // 1t to turn on, 1t per recipe run
+ // 16 parallels
+ // check the results of all rolls together
+ // repeat recipe REPLICAS times
+ int[] addedRolls = new int[REPLICAS];
+ for (int i = 1; i <= REPLICAS; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(17 * finalI, () -> {
+ int runs = finalI * batches * parallels;
+ helper.assertTrue(fluidIn.getFluidInTank(0).isEmpty(),
+ "Parallel LCent didn't complete correct number of recipes, completed [" +
+ fluidIn.getFluidInTank(0).getAmount() + "] not [" + runs + "]");
+ int resultCount = (int) Math.round(fluidOut.getTotalContentAmount());
+ int lowerLimit = runs * 0;
+ int upperLimit = runs * 40;
+ helper.assertTrue(TestUtils.isCountWithinRange(resultCount, lowerLimit, upperLimit),
+ "Parallel LCent didn't produce correct number of fluids, produced [" +
+ resultCount + "] not [" + lowerLimit + "-" + upperLimit + "]");
+
+ addedRolls[finalI - 1] = resultCount;
+
+ // reset for a rerun
+ fluidIn.setFluidInTank(0, new FluidStack(LCENT_OUT, 16));
+ });
+ }
+
+ helper.runAfterDelay(1 + 17 * REPLICAS, () -> {
+ // check if each roll was a multiple of run count
+ boolean sus = false;
+ int[] rolls = new int[REPLICAS];
+
+ rolls[0] = addedRolls[0];
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[0], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Parallel LCent ranged fluid output test iteration " + 1 + " produced [" +
+ rolls[0] + "] fluids, a multiple of its Batch * Parallel count (" + (batches * parallels) +
+ "). If this message only appears once, this is likely a false positive.");
+ }
+ for (int i = 1; i < REPLICAS; i++) {
+ rolls[i] = addedRolls[i] - addedRolls[i - 1];
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Parallel LCent ranged fluid output test iteration " + (i + 1) + " produced [" +
+ rolls[i] + "] fluids, a multiple of its Batch * Parallel count (" + (batches * parallels) +
+ "). If this message only appears once, this is likely a false positive.");
+ } else {
+ sus = false;
+ break;
+ }
+ }
+
+ helper.assertFalse(sus, "Parallel LCent ranged fluid output test rolled exactly even to " +
+ "Batch * Parallel count on every iteration");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with 16x Parallels with ranged fluid input
+ @GameTest(template = "large_centrifuge_zpm_batch_parallel16",
+ batch = "RangedFluidIngredients",
+ timeoutTicks = 200)
+ public static void multiblockLCentRangedFluidInputBatched(GameTestHelper helper) {
+ BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper);
+
+ NotifiableItemStackHandler itemIn = busHolder.inputBus1.getInventory();
+ final NotifiableFluidTank fluidIn = busHolder.inputHatch1.tank;
+ final NotifiableFluidTank fluidOut = busHolder.outputHatch1.tank;
+
+ int batches = 16;
+ int parallels = 1;
+ final int amount = 40 * batches * parallels;
+ busHolder.controller.setBatchEnabled(true);
+ busHolder.parallelHatch.setCurrentParallel(parallels);
+
+ itemIn.setStackInSlot(0, COBBLE.copyWithCount(batches * parallels));
+ fluidIn.setFluidInTank(0, new FluidStack(LCENT_IN, amount));
+
+ // 1t to turn on, 1t per recipe run
+ // 16 batches
+ // check the results of all rolls together
+ // repeat recipe REPLICAS times
+ int[] rolls = new int[REPLICAS];
+ for (int i = 1; i <= REPLICAS; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(17 * finalI, () -> {
+ FluidStack results = fluidIn.getFluidInTank(0);
+ int upperLimit = amount - (batches * parallels * 0);
+ int lowerLimit = amount - (batches * parallels * 40);
+ int completed = batches * parallels * finalI;
+ helper.assertTrue(
+ TestUtils.isFluidStackEqual(new FluidStack(fluidOut.getFluidInTank(0),
+ ((int) Math.round(fluidOut.getTotalContentAmount()))),
+ new FluidStack(REDSTONE, completed)),
+ "Batched LCent didn't complete correct number of recipes, completed [" +
+ ((int) Math.round(fluidOut.getTotalContentAmount())) + "] not [" +
+ completed + "]");
+ helper.assertTrue(TestUtils.isFluidWithinRange(results, lowerLimit, upperLimit),
+ "Batched LCent didn't consume correct number of fluids, consumed " +
+ (amount - results.getAmount()) + "] not [" + lowerLimit + "-" + upperLimit + "]");
+
+ rolls[finalI - 1] = amount - results.getAmount();
+
+ // reset for a rerun
+ itemIn.setStackInSlot(0, COBBLE.copyWithCount(batches * parallels));
+ fluidIn.setFluidInTank(0, new FluidStack(LCENT_IN, amount));
+ });
+ }
+
+ helper.runAfterDelay(1 + 17 * REPLICAS, () -> {
+ // check if each roll was a multiple of run count
+ boolean sus = false;
+ for (int i = 0; i < REPLICAS; i++) {
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Batched LCent ranged fluid input test iteration " + i + " consumed [" +
+ rolls[i] + "] fluids, a multiple of its Batch * Parallel count (" + (batches * parallels) +
+ "). If this message only appears once, this is likely a false positive.");
+ } else {
+ sus = false;
+ break;
+ }
+ }
+
+ helper.assertFalse(sus, "Batched LCent ranged fluid input test rolled exactly even to " +
+ "Batch * Parallel count on every iteration");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with 16x Parallels with ranged fluid output
+ @GameTest(template = "large_centrifuge_zpm_batch_parallel16",
+ batch = "RangedFluidIngredients",
+ timeoutTicks = 200)
+ public static void multiblockLCentRangedFluidOutputBatched(GameTestHelper helper) {
+ BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper);
+
+ final NotifiableFluidTank fluidIn = busHolder.inputHatch1.tank;
+ final NotifiableFluidTank fluidOut = busHolder.outputHatch1.tank;
+
+ int batches = 16;
+ int parallels = 1; // unused on this test
+ fluidIn.setFluidInTank(0, new FluidStack(LCENT_OUT, 16));
+
+ busHolder.controller.setBatchEnabled(true);
+ busHolder.parallelHatch.setCurrentParallel(parallels);
+
+ // 1t to turn on, 1t per recipe run
+ // 16 parallels
+ // check the results of all rolls together
+ // repeat recipe REPLICAS times
+ int[] addedRolls = new int[REPLICAS];
+ for (int i = 1; i <= REPLICAS; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(17 * finalI, () -> {
+ int runs = finalI * batches * parallels;
+ helper.assertTrue(fluidIn.getFluidInTank(0).isEmpty(),
+ "Batched LCent didn't complete correct number of recipes, completed [" +
+ fluidIn.getFluidInTank(0).getAmount() + "] not [" + runs + "]");
+ int resultCount = (int) Math.round(fluidOut.getTotalContentAmount());
+ int lowerLimit = runs * 0;
+ int upperLimit = runs * 40;
+ helper.assertTrue(TestUtils.isCountWithinRange(resultCount, lowerLimit, upperLimit),
+ "Batched LCent didn't produce correct number of fluids, produced [" +
+ resultCount + "] not [" + lowerLimit + "-" + upperLimit + "]");
+
+ addedRolls[finalI - 1] = resultCount;
+
+ // reset for a rerun
+ fluidIn.setFluidInTank(0, new FluidStack(LCENT_OUT, 16));
+ });
+ }
+
+ helper.runAfterDelay(1 + 17 * REPLICAS, () -> {
+ // check if each roll was a multiple of run count
+ boolean sus = false;
+ int[] rolls = new int[REPLICAS];
+
+ rolls[0] = addedRolls[0];
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[0], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Batched LCent ranged fluid output test iteration " + 1 + " produced [" +
+ rolls[0] + "] fluids, a multiple of its Batch * Parallel count (" + batches +
+ "). If this message only appears once, this is likely a false positive.");
+ }
+ for (int i = 1; i < REPLICAS; i++) {
+ rolls[i] = addedRolls[i] - addedRolls[i - 1];
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Batched LCent ranged fluid output test iteration " + (i + 1) + " produced [" +
+ rolls[i] + "] fluids, a multiple of its Batch * Parallel count (" + batches +
+ "). If this message only appears once, this is likely a false positive.");
+ } else {
+ sus = false;
+ break;
+ }
+ }
+
+ helper.assertFalse(sus, "Batched LCent ranged fluid output test rolled exactly even to " +
+ "Batch * Parallel count on every iteration");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with 16x Parallels with ranged fluid input
+ @GameTest(template = "large_centrifuge_zpm_batch_parallel16",
+ batch = "RangedFluidIngredients",
+ timeoutTicks = 500)
+ public static void multiblockLCentRangedFluidInput16ParallelBatched(GameTestHelper helper) {
+ BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper);
+
+ NotifiableItemStackHandler itemIn = busHolder.inputBus1.getInventory();
+ final NotifiableFluidTank fluidIn = busHolder.inputHatch1.tank;
+ final NotifiableFluidTank fluidOut = busHolder.outputHatch1.tank;
+
+ int batches = 16;
+ int parallels = 16;
+ final int amount = batches * parallels * 40;
+ busHolder.controller.setBatchEnabled(true);
+ busHolder.parallelHatch.setCurrentParallel(parallels);
+
+ int stacks = batches * parallels / 64;
+
+ for (int j = 0; j < stacks; j++) {
+ itemIn.setStackInSlot(j, COBBLE.copyWithCount((batches * parallels / stacks)));
+ }
+ fluidIn.setFluidInTank(0, new FluidStack(LCENT_IN, amount));
+
+ // 1t to turn on, 64t per recipe run
+ // check the results of all rolls together
+ // repeat recipe REPLICAS times
+ int[] rolls = new int[REPLICAS];
+ for (int i = 1; i <= REPLICAS; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(65 * finalI, () -> {
+ FluidStack results = fluidIn.getFluidInTank(0);
+ int completed = batches * parallels * finalI;
+ helper.assertTrue(
+ TestUtils.isFluidStackEqual(
+ new FluidStack(fluidOut.getFluidInTank(0), fluidOut.getFluidInTank(0).getAmount()),
+ new FluidStack(REDSTONE, completed)),
+ "Batched Parallel LCent didn't complete correct number of recipes, completed [" +
+ (fluidOut.getFluidInTank(0).getAmount()) + "] not [" + completed + "]");
+ int upperLimit = amount - (batches * parallels * 0);
+ int lowerLimit = amount - (batches * parallels * 40);
+ helper.assertTrue(TestUtils.isFluidWithinRange(results, lowerLimit, upperLimit),
+ "Batched Parallel LCent didn't consume correct number of fluids, consumed " +
+ (amount - results.getAmount()) + "] not [" + lowerLimit + "-" + upperLimit + "]");
+
+ rolls[finalI - 1] = 64 - results.getAmount();
+
+ // reset for a rerun
+ for (int l = 0; l < stacks; l++) {
+ itemIn.setStackInSlot(l,
+ COBBLE.copyWithCount((batches * parallels / stacks)));
+ }
+ fluidIn.setFluidInTank(0, new FluidStack(LCENT_IN, 40 * batches * parallels));
+ });
+ }
+
+ helper.runAfterDelay(1 + 65 * REPLICAS, () -> {
+ // check if each roll was a multiple of run count
+ boolean sus = false;
+ for (int i = 0; i < REPLICAS; i++) {
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Batched Parallel LCent ranged fluid input test iteration " + i + " consumed [" +
+ rolls[i] + "] fluids, a multiple of its Batch * Parallel count (" + (batches * parallels) +
+ "). If this message only appears once, this is likely a false positive.");
+ } else {
+ sus = false;
+ break;
+ }
+ }
+
+ helper.assertFalse(sus, "Batched Parallel LCent ranged fluid input test rolled exactly even to" +
+ " Batch * Parallel count on every iteration");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with 16x Parallels with ranged fluid output
+ @GameTest(template = "large_centrifuge_zpm_batch_parallel16",
+ batch = "RangedFluidIngredients",
+ timeoutTicks = 500)
+ public static void multiblockLCentRangedFluidOutput16ParallelBatched(GameTestHelper helper) {
+ BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper);
+
+ final NotifiableFluidTank fluidIn = busHolder.inputHatch1.tank;
+ final NotifiableFluidTank fluidOut = busHolder.outputHatch1.tank;
+
+ int batches = 16;
+ int parallels = 16;
+ busHolder.controller.setBatchEnabled(true);
+ busHolder.parallelHatch.setCurrentParallel(parallels);
+
+ fluidIn.setFluidInTank(0, new FluidStack(LCENT_OUT, batches * parallels));
+
+ // 1t to turn on, 64t per recipe run
+ // check the results of all rolls together
+ // repeat recipe REPLICAS times
+ int[] addedRolls = new int[REPLICAS];
+ for (int i = 1; i <= REPLICAS; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(65 * finalI, () -> {
+ int runs = finalI * batches * parallels;
+ helper.assertTrue(fluidIn.isEmpty(),
+ "Batched Parallel LCent didn't complete correct number of recipes, completed [" +
+ (runs - fluidIn.getFluidInTank(0).getAmount()) + "] not [" + runs + "]");
+ int resultCount = fluidOut.getFluidInTank(0).getAmount();
+ int lowerLimit = runs * 0;
+ int upperLimit = runs * 40;
+ helper.assertTrue(TestUtils.isCountWithinRange(resultCount, lowerLimit, upperLimit),
+ "Batched Parallel LCent didn't produce correct number of fluids, produced [" +
+ resultCount + "] not [" + lowerLimit + "-" + upperLimit + "]");
+
+ addedRolls[finalI - 1] = resultCount;
+
+ // reset for a rerun
+ fluidIn.setFluidInTank(0, new FluidStack(LCENT_OUT, batches * parallels));
+ });
+ }
+
+ helper.runAfterDelay(1 + 65 * REPLICAS, () -> {
+ // check if each roll was a multiple of run count
+ boolean sus = false;
+ int[] rolls = new int[REPLICAS];
+
+ rolls[0] = addedRolls[0];
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[0], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Batched Parallel LCent ranged fluid output test iteration " + 1 + " produced [" +
+ rolls[0] + "] fluids, a multiple of its Batch * Parallel count (" + (batches * parallels) +
+ "). If this message only appears once, this is likely a false positive.");
+ }
+ for (int i = 1; i < REPLICAS; i++) {
+ rolls[i] = addedRolls[i] - addedRolls[i - 1];
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Batched Parallel LCent ranged fluid output test iteration " + (i + 1) +
+ " produced [" + rolls[i] + "] fluids, a multiple of its Batch * Parallel count (" +
+ (batches * parallels) + "). If this message only appears once, this is likely" +
+ " a false positive.");
+ } else {
+ sus = false;
+ break;
+ }
+ }
+
+ helper.assertFalse(sus, "Batched Parallel LCent ranged fluid output test rolled exactly even to" +
+ " Batch * Parallel count on every iteration");
+ helper.succeed();
+ });
+ }
+}
diff --git a/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderIngredientTest.java b/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderIngredientTest.java
new file mode 100644
index 00000000000..aaa898e6655
--- /dev/null
+++ b/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderIngredientTest.java
@@ -0,0 +1,1018 @@
+package com.gregtechceu.gtceu.api.recipe.ingredient;
+
+import com.gregtechceu.gtceu.GTCEu;
+import com.gregtechceu.gtceu.api.GTValues;
+import com.gregtechceu.gtceu.api.blockentity.MetaMachineBlockEntity;
+import com.gregtechceu.gtceu.api.capability.recipe.IO;
+import com.gregtechceu.gtceu.api.capability.recipe.ItemRecipeCapability;
+import com.gregtechceu.gtceu.api.machine.MetaMachine;
+import com.gregtechceu.gtceu.api.machine.SimpleTieredMachine;
+import com.gregtechceu.gtceu.api.machine.multiblock.WorkableElectricMultiblockMachine;
+import com.gregtechceu.gtceu.api.machine.multiblock.WorkableMultiblockMachine;
+import com.gregtechceu.gtceu.api.machine.trait.NotifiableItemStackHandler;
+import com.gregtechceu.gtceu.api.recipe.GTRecipeType;
+import com.gregtechceu.gtceu.common.machine.multiblock.part.FluidHatchPartMachine;
+import com.gregtechceu.gtceu.common.machine.multiblock.part.ItemBusPartMachine;
+import com.gregtechceu.gtceu.common.machine.multiblock.part.ParallelHatchPartMachine;
+import com.gregtechceu.gtceu.gametest.util.TestUtils;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.gametest.framework.BeforeBatch;
+import net.minecraft.gametest.framework.GameTest;
+import net.minecraft.gametest.framework.GameTestHelper;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.util.valueproviders.UniformInt;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraftforge.gametest.GameTestHolder;
+import net.minecraftforge.gametest.PrefixGameTestTemplate;
+
+import lombok.Getter;
+
+/**
+ * Test cases:
+ * do many passes of most tests as a safeguard against bad rolls
+ * Same output more than once
+ * Out of bounds
+ * Output a multiple of batchparallels
+ * Rolls of 0
+ * Forced rolls of 0 breaking recipes
+ */
+@PrefixGameTestTemplate(false)
+@GameTestHolder(GTCEu.MOD_ID)
+public class IntProviderIngredientTest {
+
+ private static GTRecipeType CR_RECIPE_TYPE;
+ private static GTRecipeType LCR_RECIPE_TYPE;
+ private static GTRecipeType CENTRIFUGE_RECIPE_TYPE;
+
+ // items used in recipes. Up top here for quick replacements.
+ private static final ItemStack CR_IN = new ItemStack(Items.GREEN_STAINED_GLASS);
+ private static final ItemStack CR_OUT = new ItemStack(Items.BRICK_SLAB);
+ private static final ItemStack LCR_IN = new ItemStack(Items.BLACK_STAINED_GLASS);
+ private static final ItemStack LCR_OUT = new ItemStack(Items.BRICK_STAIRS);
+ private static final ItemStack LCENT_IN = new ItemStack(Items.LIME_STAINED_GLASS);
+ private static final ItemStack LCENT_OUT = new ItemStack(Items.BRICK_WALL);
+ private static final ItemStack COBBLE = new ItemStack(Items.COBBLESTONE);
+ private static final ItemStack STONE = new ItemStack(Items.STONE);
+
+ /**
+ * How many times to repeat the Batch and Parallel random roll tests to avoid false positives
+ * Currently set to 7, with singleblock recipes processing up to 9 items, allowing for stacks of up to 63 items.
+ */
+ @Getter
+ private static final int REPLICAS = 7;
+
+ @BeforeBatch(batch = "RangedIngredients")
+ public static void prepare(ServerLevel level) {
+ CR_RECIPE_TYPE = TestUtils.createRecipeType("ranged_ingredient_cr_tests", 2, 2, 3, 2);
+ LCR_RECIPE_TYPE = TestUtils.createRecipeType("ranged_ingredient_lcr_tests", 3, 3, 5, 4);
+ CENTRIFUGE_RECIPE_TYPE = TestUtils.createRecipeType("ranged_ingredient_centrifuge_tests", 2, 6, 1, 6);
+
+ CR_RECIPE_TYPE.getLookup().addRecipe(CR_RECIPE_TYPE
+ .recipeBuilder(GTCEu.id("test_ranged_input_item_cr"))
+ .inputItemsRanged(CR_IN, UniformInt.of(0, 9))
+ .inputItems(COBBLE)
+ .outputItems(STONE)
+ .EUt(GTValues.V[GTValues.HV])
+ .duration(2)
+ .buildRawRecipe());
+
+ CR_RECIPE_TYPE.getLookup().addRecipe(CR_RECIPE_TYPE
+ .recipeBuilder(GTCEu.id("test_ranged_output_item_cr"))
+ .inputItems(CR_OUT)
+ .outputItemsRanged(STONE, UniformInt.of(0, 9))
+ .EUt(GTValues.V[GTValues.HV])
+ .duration(2)
+ .buildRawRecipe());
+
+ LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE
+ .recipeBuilder(GTCEu.id("test_ranged_input_item_lcr"))
+ .inputItemsRanged(LCR_IN, UniformInt.of(0, 9))
+ .inputItems(COBBLE)
+ .outputItems(STONE)
+ .EUt(GTValues.V[GTValues.HV])
+ .duration(2)
+ .buildRawRecipe());
+
+ LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE
+ .recipeBuilder(GTCEu.id("test_ranged_output_item_lcr"))
+ .inputItems(LCR_OUT)
+ .outputItemsRanged(STONE, UniformInt.of(0, 9))
+ .EUt(GTValues.V[GTValues.HV])
+ .duration(2)
+ .buildRawRecipe());
+
+ CENTRIFUGE_RECIPE_TYPE.getLookup().addRecipe(CENTRIFUGE_RECIPE_TYPE
+ .recipeBuilder(GTCEu.id("test_ranged_input_item_cent"))
+ .inputItemsRanged(LCENT_IN, UniformInt.of(0, 4))
+ .inputItems(COBBLE)
+ .outputItems(STONE)
+ .EUt(GTValues.V[GTValues.IV])
+ .duration(4)
+ .buildRawRecipe());
+
+ CENTRIFUGE_RECIPE_TYPE.getLookup().addRecipe(CENTRIFUGE_RECIPE_TYPE
+ .recipeBuilder(GTCEu.id("test_ranged_output_item_cent"))
+ .inputItems(LCENT_OUT)
+ .outputItemsRanged(STONE, UniformInt.of(0, 4))
+ .EUt(GTValues.V[GTValues.IV])
+ .duration(4)
+ .buildRawRecipe());
+ }
+
+ private static MetaMachine getMetaMachine(BlockEntity entity) {
+ return ((MetaMachineBlockEntity) entity).getMetaMachine();
+ }
+
+ private record BusHolder(ItemBusPartMachine inputBus1, FluidHatchPartMachine inputHatch1,
+ ItemBusPartMachine outputBus1,
+ FluidHatchPartMachine outputHatch1, WorkableMultiblockMachine controller) {}
+
+ private record BusHolderBatchParallel(ItemBusPartMachine inputBus1, FluidHatchPartMachine inputHatch1,
+ ItemBusPartMachine outputBus1,
+ FluidHatchPartMachine outputHatch1,
+ WorkableElectricMultiblockMachine controller,
+ ParallelHatchPartMachine parallelHatch) {}
+
+ /**
+ * Retrieves the busses for this LCR template and force a multiblock structure check
+ *
+ * @param helper the GameTestHelper
+ * @return the busses, in the BusHolder record.
+ */
+ private static BusHolder getBussesAndFormLCR(GameTestHelper helper) {
+ WorkableMultiblockMachine controller = (WorkableMultiblockMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(1, 2, 0)));
+ TestUtils.formMultiblock(controller);
+ controller.setRecipeType(LCR_RECIPE_TYPE);
+ ItemBusPartMachine inputBus1 = (ItemBusPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(2, 1, 0)));
+ FluidHatchPartMachine inputHatch1 = (FluidHatchPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(2, 2, 0)));
+ ItemBusPartMachine outputBus1 = (ItemBusPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(0, 1, 0)));
+ FluidHatchPartMachine outputHatch1 = (FluidHatchPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(0, 2, 0)));
+ return new BusHolder(inputBus1, inputHatch1, outputBus1, outputHatch1, controller);
+ }
+
+ /**
+ * Retrieves the busses for this Large Centrifuge template and force a multiblock structure check
+ *
+ * @param helper the GameTestHelper
+ * @return the busses, in the BusHolder record.
+ */
+ private static BusHolderBatchParallel getBussesAndFormLCENT(GameTestHelper helper) {
+ WorkableElectricMultiblockMachine controller = (WorkableElectricMultiblockMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(2, 2, 0)));
+ TestUtils.formMultiblock(controller);
+ controller.setRecipeType(CENTRIFUGE_RECIPE_TYPE);
+ ItemBusPartMachine inputBus1 = (ItemBusPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(1, 2, 0)));
+ FluidHatchPartMachine inputHatch1 = (FluidHatchPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(0, 2, 0)));
+ ItemBusPartMachine outputBus1 = (ItemBusPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(2, 1, 0)));
+ FluidHatchPartMachine outputHatch1 = (FluidHatchPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(1, 1, 0)));
+ ParallelHatchPartMachine parallelHatch = (ParallelHatchPartMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(3, 3, 0)));
+ return new BusHolderBatchParallel(inputBus1, inputHatch1, outputBus1, outputHatch1, controller, parallelHatch);
+ }
+
+ // test for IntProviderIngredient.test()
+ @GameTest(template = "empty", batch = "RangedIngredients")
+ public static void rangedIngredientTestEqualTest(GameTestHelper helper) {
+ var ingredient = IntProviderIngredient.of(new ItemStack(Items.BRICK, 1), UniformInt.of(1, 5));
+ helper.assertTrue(ingredient.test(new ItemStack(Items.BRICK, 3)),
+ "IntProviderIngredient.test doesn't match when it should have");
+ // This should work since test only tries the item type.
+ helper.assertTrue(ingredient.test(new ItemStack(Items.BRICK, 64)),
+ "IntProviderIngredient.test doesn't match when it should have with value outside bounds");
+ helper.assertFalse(ingredient.test(new ItemStack(Items.COBBLESTONE, 3)),
+ "IntProviderIngredient.test shouldn't match with different items");
+ helper.succeed();
+ }
+
+ // test for IntProviderIngredient.getStacks()
+ @GameTest(template = "empty", batch = "RangedIngredients")
+ public static void rangedIngredientGetStacksTest(GameTestHelper helper) {
+ var ingredient = IntProviderIngredient.of(new ItemStack(Items.BRICK, 1), UniformInt.of(1, 5000));
+ var stacks = ingredient.getItems();
+ helper.assertTrue(stacks.length == 1, "IntProviderIngredient should only return 1 item when made with 1 item");
+ helper.assertTrue(stacks[0].is(new ItemStack(Items.BRICK, 1).getItem()),
+ "IntProviderIngredient should have item equal to what it was made with");
+ helper.assertTrue(TestUtils.areItemStacksEqual(stacks, ingredient.getItems()),
+ "IntProviderIngredient.getItems shouldn't change between getStacks calls");
+ ingredient.reroll();
+ helper.assertFalse(TestUtils.areItemStacksEqual(stacks, ingredient.getItems()),
+ "IntProviderIngredient.getItems should have changed after rerolling");
+ helper.succeed();
+ }
+
+ // test for IntProviderIngredient.toJson()
+ @GameTest(template = "empty", batch = "RangedIngredients")
+ public static void rangedIngredientJsonTest(GameTestHelper helper) {
+ var ingredient = IntProviderIngredient.of(new ItemStack(Items.BRICK, 1), UniformInt.of(1, 5000));
+
+ // serialize/deserialize before rolling count
+ var jsonPreRoll = ingredient.toJson();
+ var ingredientDeserializedPreRoll = IntProviderIngredient.fromJson(jsonPreRoll);
+
+ var stacks = ingredient.getItems();
+ var stacksDeserializedPreRoll = ingredientDeserializedPreRoll.getItems();
+
+ // serialize/deserialize after rolling count
+ var jsonPostRoll = ingredient.toJson();
+ var ingredientDeserializedPostRoll = IntProviderIngredient.fromJson(jsonPostRoll);
+ var stacksDeserializedPostRoll = ingredientDeserializedPostRoll.getItems();
+
+ helper.assertTrue(
+ stacks.length == stacksDeserializedPreRoll.length && stacks.length == stacksDeserializedPostRoll.length,
+ "IntProviderIngredient should only return 1 item when made with 1 item, even after serializing");
+ helper.assertTrue(stacksDeserializedPreRoll[0].is(new ItemStack(Items.BRICK, 1).getItem()),
+ "IntProviderIngredient should have item equal to what it was made with after serializing");
+ helper.assertTrue(stacksDeserializedPostRoll[0].is(new ItemStack(Items.BRICK, 1).getItem()),
+ "IntProviderIngredient should have item equal to what it was made with after serializing");
+ helper.assertFalse(TestUtils.areItemStacksEqual(stacksDeserializedPreRoll, ingredient.getItems()),
+ "IntProviderIngredient.getItems should be different if it wasn't rolled before serializing");
+ helper.assertTrue(TestUtils.areItemStacksEqual(stacksDeserializedPostRoll, ingredient.getItems()),
+ "IntProviderIngredient.getItems shouldn't change between getItems calls if it was rolled before serializing");
+ helper.succeed();
+ }
+
+ // Test for singleblock machine with ranged item input.
+ // Forcibly sabotages the first recipe run, setting its output amount to 0 to ensure that doesn't break the recipe.
+ // This is specifically a test for #3593 / #3594
+ @GameTest(template = "singleblock_charged_cr", batch = "RangedIngredients")
+ public static void singleblockRangedItemOutputSabotaged(GameTestHelper helper) {
+ SimpleTieredMachine machine = (SimpleTieredMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(0, 1, 0)));
+
+ machine.setRecipeType(CR_RECIPE_TYPE);
+ NotifiableItemStackHandler itemIn = (NotifiableItemStackHandler) machine
+ .getCapabilitiesFlat(IO.IN, ItemRecipeCapability.CAP).get(0);
+ NotifiableItemStackHandler itemOut = (NotifiableItemStackHandler) machine
+ .getCapabilitiesFlat(IO.OUT, ItemRecipeCapability.CAP).get(0);
+
+ int runs = 7;
+ itemIn.setStackInSlot(0, CR_OUT.copyWithCount(runs));
+ // 1t to turn on, 2t per recipe run
+ // get the result of each roll independently
+ int[] addedRolls = new int[runs];
+
+ helper.runAfterDelay(4, () -> {
+ if (machine.getRecipeLogic().getLastRecipe().getOutputContents(ItemRecipeCapability.CAP).get(0)
+ .getContent() instanceof IntProviderIngredient ingredient) {
+ ingredient.setSampledCount(0);
+
+ if (ingredient.getSampledCount() != 0) {
+ helper.fail("Singleblock Ranged Item Output sabotage failed! " +
+ "Output count not was altered!");
+ }
+ } else {
+ helper.fail("Singleblock Ranged Item Output sabotage failed! " +
+ "Recipe logic did not contain a Ranged Output!");
+ }
+ });
+ for (int i = 0; i < runs; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(2 * i + 3, () -> {
+ addedRolls[finalI] = itemOut.getStackInSlot(0).getCount();
+ });
+ }
+ // check the results of all rolls together
+ helper.runAfterDelay(runs * 2 + 1, () -> {
+ ItemStack results = itemOut.getStackInSlot(0);
+ helper.assertTrue(TestUtils.isItemWithinRange(results, runs, runs * 9),
+ "Sabotaged Singleblock CR didn't produce correct number of items, produced [" +
+ results.getCount() + "] not [" + runs + "-" + (runs * 9) + "]");
+ helper.assertFalse((results.getCount() == runs * 9),
+ "Sabotaged Singleblock CR rolled max value on every roll (how??)");
+ helper.assertFalse((results.getCount() == runs * 0),
+ "Sabotaged Singleblock CR rolled min value on every roll! " +
+ "This is the failure this sabotage was intended to induce.");
+
+ // check if all the rolls were equal, but not min/max
+ int[] rolls = new int[runs];
+ rolls[0] = addedRolls[0];
+ boolean allEqual = false;
+ for (int i = 1; i < runs; i++) {
+ rolls[i] = addedRolls[i] - addedRolls[i - 1];
+ if (rolls[i] == rolls[i - 1]) {
+ allEqual = true;
+ } else {
+ allEqual = false;
+ break;
+ }
+ }
+ helper.assertFalse(allEqual,
+ "Sabotaged Singleblock CR rolled the same value on every input roll (rolled " + rolls[0] + ")");
+ helper.succeed();
+ });
+ }
+
+ // Failure Test for singleblock machine with ranged item input
+ // Provides too few input items, should not run recipes.
+ @GameTest(template = "singleblock_charged_cr", batch = "RangedIngredients")
+ public static void singleblockRangedItemInputFailure(GameTestHelper helper) {
+ SimpleTieredMachine machine = (SimpleTieredMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(0, 1, 0)));
+
+ machine.setRecipeType(CR_RECIPE_TYPE);
+ NotifiableItemStackHandler itemIn = (NotifiableItemStackHandler) machine
+ .getCapabilitiesFlat(IO.IN, ItemRecipeCapability.CAP).get(0);
+ NotifiableItemStackHandler itemOut = (NotifiableItemStackHandler) machine
+ .getCapabilitiesFlat(IO.OUT, ItemRecipeCapability.CAP).get(0);
+
+ int runs = 10;
+ itemIn.setStackInSlot(0, CR_IN.copyWithCount(8));
+ itemIn.setStackInSlot(1, COBBLE.copyWithCount(runs));
+ // 1t to turn on, 2t per non- recipe run
+ helper.runAfterDelay(runs * 2 + 1, () -> {
+ ItemStack results = itemIn.getStackInSlot(0);
+
+ helper.assertTrue(itemOut.isEmpty(),
+ "Singleblock CR should not have run, ran [" +
+ itemOut.getStackInSlot(0).getCount() + "] times");
+ helper.assertTrue(TestUtils.isItemStackEqual(results, CR_IN.copyWithCount(8)),
+ "Singleblock CR should not have consumed items, consumed [" +
+ (8 - results.getCount()) + "]");
+
+ helper.succeed();
+ });
+ }
+
+ // Test for singleblock machine with ranged item input
+ @GameTest(template = "singleblock_charged_cr", batch = "RangedIngredients")
+ public static void singleblockRangedItemInput(GameTestHelper helper) {
+ SimpleTieredMachine machine = (SimpleTieredMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(0, 1, 0)));
+
+ machine.setRecipeType(CR_RECIPE_TYPE);
+ NotifiableItemStackHandler itemIn = (NotifiableItemStackHandler) machine
+ .getCapabilitiesFlat(IO.IN, ItemRecipeCapability.CAP).get(0);
+ NotifiableItemStackHandler itemOut = (NotifiableItemStackHandler) machine
+ .getCapabilitiesFlat(IO.OUT, ItemRecipeCapability.CAP).get(0);
+
+ int runs = 7;
+ itemIn.setStackInSlot(0, CR_IN.copyWithCount(64));
+ itemIn.setStackInSlot(1, COBBLE.copyWithCount(runs));
+ // 1t to turn on, 2t per recipe run
+ // get the result of each roll independently
+ int[] addedRolls = new int[runs];
+ for (int i = 0; i < runs; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(2 * i + 1, () -> {
+ addedRolls[finalI] = itemIn.getStackInSlot(0).getCount();
+ });
+ }
+ // check the results of all rolls together
+ helper.runAfterDelay(runs * 2 + 1, () -> {
+ ItemStack results = itemIn.getStackInSlot(0);
+ int upperLimit = 64 - (runs * 0);
+ int lowerLimit = 64 - (runs * 9);
+ helper.assertTrue(TestUtils.isItemStackEqual(itemOut.getStackInSlot(0), STONE.copyWithCount(runs)),
+ "Singleblock CR didn't complete correct number of recipes, completed [" +
+ itemOut.getStackInSlot(0).getCount() + "] not [" + runs + "]");
+ helper.assertTrue(TestUtils.isItemWithinRange(results, lowerLimit, upperLimit),
+ "Singleblock CR didn't consume correct number of items, consumed [" +
+ (64 - results.getCount()) + "] not [" + lowerLimit + "-" + upperLimit + "]");
+ helper.assertFalse((results.getCount() == lowerLimit),
+ "Singleblock CR rolled max value on every roll");
+ helper.assertFalse((results.getCount() == upperLimit),
+ "Singleblock CR rolled min value on every roll");
+
+ // check if all the rolls were equal, but not min/max
+ int[] rolls = new int[runs];
+ rolls[0] = 64 - addedRolls[0];
+ boolean allEqual = false;
+ for (int i = 1; i < runs; i++) {
+ rolls[i] = addedRolls[i - 1] - addedRolls[i];
+ if (rolls[i] == rolls[i - 1]) {
+ allEqual = true;
+ } else {
+ allEqual = false;
+ break;
+ }
+ }
+ helper.assertFalse(allEqual,
+ "Singleblock CR rolled the same value on every input roll (rolled " + rolls[0] + ")");
+ helper.succeed();
+ });
+ }
+
+ // Test for singleblock machine with ranged item output
+ @GameTest(template = "singleblock_charged_cr", batch = "RangedIngredients")
+ public static void singleblockRangedItemOutput(GameTestHelper helper) {
+ SimpleTieredMachine machine = (SimpleTieredMachine) getMetaMachine(
+ helper.getBlockEntity(new BlockPos(0, 1, 0)));
+
+ machine.setRecipeType(CR_RECIPE_TYPE);
+ NotifiableItemStackHandler itemIn = (NotifiableItemStackHandler) machine
+ .getCapabilitiesFlat(IO.IN, ItemRecipeCapability.CAP).get(0);
+ NotifiableItemStackHandler itemOut = (NotifiableItemStackHandler) machine
+ .getCapabilitiesFlat(IO.OUT, ItemRecipeCapability.CAP).get(0);
+
+ int runs = 7;
+ itemIn.setStackInSlot(0, CR_OUT.copyWithCount(runs));
+ // 1t to turn on, 2t per recipe run
+ // get the result of each roll independently
+ int[] addedRolls = new int[runs];
+ for (int i = 0; i < runs; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(2 * i + 3, () -> {
+ addedRolls[finalI] = itemOut.getStackInSlot(0).getCount();
+ });
+ }
+ // check the results of all rolls together
+ helper.runAfterDelay(runs * 2 + 1, () -> {
+ helper.assertTrue(itemIn.getStackInSlot(0).isEmpty(),
+ "Singleblock CR didn't complete correct number of recipes, completed [" +
+ itemIn.getStackInSlot(0).getCount() + "] not [" + runs + "]");
+ ItemStack results = itemOut.getStackInSlot(0);
+ helper.assertTrue(TestUtils.isItemWithinRange(results, runs, runs * 9),
+ "Singleblock CR didn't produce correct number of items, produced [" +
+ results.getCount() + "] not [" + runs + "-" + (runs * 9) + "]");
+ helper.assertFalse((results.getCount() == runs * 9),
+ "Singleblock CR rolled max value on every roll");
+ helper.assertFalse((results.getCount() == runs * 0),
+ "Singleblock CR rolled min value on every roll");
+
+ // check if all the rolls were equal, but not min/max
+ int[] rolls = new int[runs];
+ rolls[0] = addedRolls[0];
+ boolean allEqual = false;
+ for (int i = 1; i < runs; i++) {
+ rolls[i] = addedRolls[i] - addedRolls[i - 1];
+ if (rolls[i] == rolls[i - 1]) {
+ allEqual = true;
+ } else {
+ allEqual = false;
+ break;
+ }
+ }
+ helper.assertFalse(allEqual,
+ "Singleblock CR rolled the same value on every input roll (rolled " + rolls[0] + ")");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with ranged item input
+ @GameTest(template = "lcr_ranged_ingredients", batch = "RangedIngredients")
+ public static void multiblockLCRRangedItemInput(GameTestHelper helper) {
+ BusHolder busHolder = getBussesAndFormLCR(helper);
+
+ NotifiableItemStackHandler itemIn = busHolder.inputBus1.getInventory();
+ NotifiableItemStackHandler itemOut = busHolder.outputBus1.getInventory();
+
+ int runs = 7;
+ itemIn.setStackInSlot(0, LCR_IN.copyWithCount(64));
+ itemIn.setStackInSlot(1, COBBLE.copyWithCount(runs));
+ // 1t to turn on, 2t per recipe run
+ // get the result of each roll independently
+ int[] addedRolls = new int[runs];
+ for (int i = 0; i < runs; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(2 * i + 1, () -> {
+ addedRolls[finalI] = itemIn.getStackInSlot(0).getCount();
+ });
+ }
+ // check the results of all rolls together
+ helper.runAfterDelay(runs * 2 + 1, () -> {
+ ItemStack results = itemIn.getStackInSlot(0);
+ int upperLimit = 64 - (runs * 0);
+ int lowerLimit = 64 - (runs * 9);
+ helper.assertTrue(TestUtils.isItemStackEqual(itemOut.getStackInSlot(0), STONE.copyWithCount(runs)),
+ "LCR didn't complete correct number of recipes, completed [" +
+ itemOut.getStackInSlot(0).getCount() + "] not [" + runs + "]");
+ helper.assertTrue(TestUtils.isItemWithinRange(results, lowerLimit, upperLimit),
+ "LCR didn't consume correct number of items, consumed [" +
+ (64 - results.getCount()) + "] not [" + lowerLimit + "-" + upperLimit + "]");
+ helper.assertFalse((results.getCount() == lowerLimit),
+ "LCR rolled max value on every roll");
+ helper.assertFalse((results.getCount() == upperLimit),
+ "LCR rolled min value on every roll");
+
+ // check if all the rolls were equal, but not min/max
+ int[] rolls = new int[runs];
+ rolls[0] = 64 - addedRolls[0];
+ boolean allEqual = false;
+ for (int i = 1; i < runs; i++) {
+ rolls[i] = addedRolls[i - 1] - addedRolls[i];
+ if (rolls[i] == rolls[i - 1]) {
+ allEqual = true;
+ } else {
+ allEqual = false;
+ break;
+ }
+ }
+ helper.assertFalse(allEqual,
+ "LCR rolled the same value on every input roll (rolled " + rolls[0] + ")");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with ranged item input
+ @GameTest(template = "lcr_ranged_ingredients", batch = "RangedIngredients")
+ public static void multiblockLCRRangedItemOutput(GameTestHelper helper) {
+ BusHolder busHolder = getBussesAndFormLCR(helper);
+
+ NotifiableItemStackHandler itemIn = busHolder.inputBus1.getInventory();
+ NotifiableItemStackHandler itemOut = busHolder.outputBus1.getInventory();
+
+ int runs = 7;
+ itemIn.setStackInSlot(0, LCR_OUT.copyWithCount(runs));
+ // 1t to turn on, 2t per recipe run
+ // get the result of each roll independently
+ int[] addedRolls = new int[runs];
+ for (int i = 0; i < runs; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(2 * i + 3, () -> {
+ addedRolls[finalI] = itemOut.getStackInSlot(0).getCount();
+ });
+ }
+ // check the results of all rolls together
+ helper.runAfterDelay(runs * 2 + 1, () -> {
+ helper.assertTrue(itemIn.getStackInSlot(0).isEmpty(),
+ "LCR didn't complete correct number of recipes, completed [" +
+ itemIn.getStackInSlot(0).getCount() + "] not [" + runs + "]");
+ ItemStack results = itemOut.getStackInSlot(0);
+ helper.assertTrue(TestUtils.isItemWithinRange(results, runs, runs * 9),
+ "LCR didn't produce correct number of items, produced [" +
+ results.getCount() + "] not [" + runs + "-" + (runs * 9) + "]");
+ helper.assertFalse((results.getCount() == runs * 9),
+ "LCR rolled max value on every roll");
+ helper.assertFalse((results.getCount() == runs * 0),
+ "LCR rolled min value on every roll");
+
+ // check if all the rolls were equal, but not min/max
+ int[] rolls = new int[runs];
+ rolls[0] = addedRolls[0];
+ boolean allEqual = false;
+ for (int i = 1; i < runs; i++) {
+ rolls[i] = addedRolls[i] - addedRolls[i - 1];
+ if (rolls[i] == rolls[i - 1]) {
+ allEqual = true;
+ } else {
+ allEqual = false;
+ break;
+ }
+ }
+ helper.assertFalse(allEqual,
+ "LCR rolled the same value on every input roll (rolled " + rolls[0] + ")");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with 16x Parallels with ranged item input
+ @GameTest(template = "large_centrifuge_zpm_batch_parallel16",
+ batch = "RangedIngredients",
+ timeoutTicks = 200)
+ public static void multiblockLCentRangedItemInput16Parallel(GameTestHelper helper) {
+ BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper);
+
+ NotifiableItemStackHandler itemIn = busHolder.inputBus1.getInventory();
+ NotifiableItemStackHandler itemOut = busHolder.outputBus1.getInventory();
+
+ int batches = 1; // unused on this test
+ int parallels = 16;
+ busHolder.controller.setBatchEnabled(false);
+ busHolder.parallelHatch.setCurrentParallel(parallels);
+
+ itemIn.setStackInSlot(0, LCENT_IN.copyWithCount(64));
+ itemIn.setStackInSlot(1, COBBLE.copyWithCount(parallels));
+
+ // 1t to turn on, 4t per recipe run
+ // 16 parallels
+ // check the results of all rolls together
+ // repeat recipe REPLICAS times
+ int[] rolls = new int[REPLICAS];
+ for (int i = 1; i <= REPLICAS; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(17 * finalI, () -> {
+ ItemStack results = itemIn.getStackInSlot(0);
+ int upperLimit = 64 - (batches * parallels * 0);
+ int lowerLimit = 64 - (batches * parallels * 4);
+ int completed = batches * parallels * finalI;
+ helper.assertTrue(
+ TestUtils.isItemStackEqual(itemOut.getStackInSlot(0)
+ .copyWithCount((int) Math.round(itemOut.getTotalContentAmount())),
+ STONE.copyWithCount(completed)),
+ "Parallel LCent didn't complete correct number of recipes, completed [" +
+ ((int) Math.round(itemOut.getTotalContentAmount())) + "] not [" +
+ completed + "]");
+ helper.assertTrue(TestUtils.isItemWithinRange(results, lowerLimit, upperLimit),
+ "Parallel LCent didn't consume correct number of items, consumed " +
+ (64 - results.getCount()) + "] not [" + lowerLimit + "-" + upperLimit + "]");
+
+ rolls[finalI - 1] = 64 - results.getCount();
+
+ // reset for a rerun
+ itemIn.setStackInSlot(0, LCENT_IN.copyWithCount(64));
+ itemIn.setStackInSlot(1, COBBLE.copyWithCount(parallels));
+ });
+ }
+
+ helper.runAfterDelay(1 + 17 * REPLICAS, () -> {
+ // check if each roll was a multiple of run count
+ boolean sus = false;
+ for (int i = 0; i < REPLICAS; i++) {
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Parallel LCent ranged item input test iteration " + i + " consumed [" +
+ rolls[i] + "] items, a multiple of its Batch * Parallel count (" + (parallels) +
+ "). If this message only appears once, this is likely a false positive.");
+ } else {
+ sus = false;
+ break;
+ }
+ }
+
+ helper.assertFalse(sus, "Parallel LCent ranged item input test rolled exactly even to " +
+ "Batch * Parallel count on every iteration");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with 16x Parallels with ranged item output
+ @GameTest(template = "large_centrifuge_zpm_batch_parallel16",
+ batch = "RangedIngredients",
+ timeoutTicks = 200)
+ public static void multiblockLCentRangedItemOutput16Parallel(GameTestHelper helper) {
+ BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper);
+
+ NotifiableItemStackHandler itemIn = busHolder.inputBus1.getInventory();
+ NotifiableItemStackHandler itemOut = busHolder.outputBus1.getInventory();
+
+ int batches = 1; // unused on this test
+ int parallels = 16;
+ itemIn.setStackInSlot(0, LCENT_OUT.copyWithCount(16));
+
+ busHolder.controller.setBatchEnabled(false);
+ busHolder.parallelHatch.setCurrentParallel(parallels);
+
+ // 1t to turn on, 1t per recipe run
+ // 16 parallels
+ // check the results of all rolls together
+ // repeat recipe REPLICAS times
+ int[] addedRolls = new int[REPLICAS];
+ for (int i = 1; i <= REPLICAS; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(17 * finalI, () -> {
+ int runs = finalI * batches * parallels;
+ helper.assertTrue(itemIn.getStackInSlot(0).isEmpty(),
+ "Parallel LCent didn't complete correct number of recipes, completed [" +
+ itemIn.getStackInSlot(0).getCount() + "] not [" + runs + "]");
+ int resultCount = (int) Math.round(itemOut.getTotalContentAmount());
+ int lowerLimit = runs * 0;
+ int upperLimit = runs * 4;
+ helper.assertTrue(TestUtils.isCountWithinRange(resultCount, lowerLimit, upperLimit),
+ "Parallel LCent didn't produce correct number of items, produced [" +
+ resultCount + "] not [" + lowerLimit + "-" + upperLimit + "]");
+
+ addedRolls[finalI - 1] = resultCount;
+
+ // reset for a rerun
+ itemIn.setStackInSlot(0, LCENT_OUT.copyWithCount(16));
+ });
+ }
+
+ helper.runAfterDelay(1 + 17 * REPLICAS, () -> {
+ // check if each roll was a multiple of run count
+ boolean sus = false;
+ int[] rolls = new int[REPLICAS];
+
+ rolls[0] = addedRolls[0];
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[0], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Parallel LCent ranged item output test iteration " + 1 + " produced [" +
+ rolls[0] + "] items, a multiple of its Batch * Parallel count (" + (batches * parallels) +
+ "). If this message only appears once, this is likely a false positive.");
+ }
+ for (int i = 1; i < REPLICAS; i++) {
+ rolls[i] = addedRolls[i] - addedRolls[i - 1];
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Parallel LCent ranged item output test iteration " + (i + 1) + " produced [" +
+ rolls[i] + "] items, a multiple of its Batch * Parallel count (" + (batches * parallels) +
+ "). If this message only appears once, this is likely a false positive.");
+ } else {
+ sus = false;
+ break;
+ }
+ }
+
+ helper.assertFalse(sus, "Parallel LCent ranged item output test rolled exactly even to " +
+ "Batch * Parallel count on every iteration");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with 16x Parallels with ranged item input
+ @GameTest(template = "large_centrifuge_zpm_batch_parallel16",
+ batch = "RangedIngredients",
+ timeoutTicks = 200)
+ public static void multiblockLCentRangedItemInputBatched(GameTestHelper helper) {
+ BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper);
+
+ NotifiableItemStackHandler itemIn = busHolder.inputBus1.getInventory();
+ NotifiableItemStackHandler itemOut = busHolder.outputBus1.getInventory();
+
+ int batches = 16;
+ int parallels = 1; // unused on this test
+ busHolder.controller.setBatchEnabled(true);
+ busHolder.parallelHatch.setCurrentParallel(parallels);
+
+ itemIn.setStackInSlot(0, LCENT_IN.copyWithCount(64));
+ itemIn.setStackInSlot(1, COBBLE.copyWithCount(batches));
+
+ // 1t to turn on, 1t per recipe run
+ // 16 batches
+ // check the results of all rolls together
+ // repeat recipe REPLICAS times
+ int[] rolls = new int[REPLICAS];
+ for (int i = 1; i <= REPLICAS; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(17 * finalI, () -> {
+ ItemStack results = itemIn.getStackInSlot(0);
+ int upperLimit = 64 - (batches * parallels * 0);
+ int lowerLimit = 64 - (batches * parallels * 4);
+ int completed = batches * parallels * finalI;
+ helper.assertTrue(
+ TestUtils.isItemStackEqual(itemOut.getStackInSlot(0)
+ .copyWithCount((int) Math.round(itemOut.getTotalContentAmount())),
+ STONE.copyWithCount(completed)),
+ "Parallel LCent didn't complete correct number of recipes, completed [" +
+ ((int) Math.round(itemOut.getTotalContentAmount())) + "] not [" +
+ completed + "]");
+ helper.assertTrue(TestUtils.isItemWithinRange(results, lowerLimit, upperLimit),
+ "Parallel LCent didn't consume correct number of items, consumed " +
+ (64 - results.getCount()) + "] not [" + lowerLimit + "-" + upperLimit + "]");
+
+ rolls[finalI - 1] = 64 - results.getCount();
+
+ // reset for a rerun
+ itemIn.setStackInSlot(0, LCENT_IN.copyWithCount(64));
+ itemIn.setStackInSlot(1, COBBLE.copyWithCount(batches));
+ });
+ }
+
+ helper.runAfterDelay(1 + 17 * REPLICAS, () -> {
+ // check if each roll was a multiple of run count
+ boolean sus = false;
+ for (int i = 0; i < REPLICAS; i++) {
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Parallel LCent ranged item input test iteration " + i + " consumed [" +
+ rolls[i] + "] items, a multiple of its Batch * Parallel count (" + (batches * parallels) +
+ "). If this message only appears once, this is likely a false positive.");
+ } else {
+ sus = false;
+ break;
+ }
+ }
+
+ helper.assertFalse(sus, "Parallel LCent ranged item input test rolled exactly even to " +
+ "Batch * Parallel count on every iteration");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with 16x Parallels with ranged item output
+ @GameTest(template = "large_centrifuge_zpm_batch_parallel16",
+ batch = "RangedIngredients",
+ timeoutTicks = 200)
+ public static void multiblockLCentRangedItemOutputBatched(GameTestHelper helper) {
+ BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper);
+
+ NotifiableItemStackHandler itemIn = busHolder.inputBus1.getInventory();
+ NotifiableItemStackHandler itemOut = busHolder.outputBus1.getInventory();
+
+ int batches = 16;
+ int parallels = 1; // unused on this test
+ itemIn.setStackInSlot(0, LCENT_OUT.copyWithCount(16));
+
+ busHolder.controller.setBatchEnabled(true);
+ busHolder.parallelHatch.setCurrentParallel(parallels);
+
+ // 1t to turn on, 1t per recipe run
+ // 16 parallels
+ // check the results of all rolls together
+ // repeat recipe REPLICAS times
+ int[] addedRolls = new int[REPLICAS];
+ for (int i = 1; i <= REPLICAS; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(17 * finalI, () -> {
+ int runs = finalI * batches * parallels;
+ helper.assertTrue(itemIn.getStackInSlot(0).isEmpty(),
+ "Batched LCent didn't complete correct number of recipes, completed [" +
+ itemIn.getStackInSlot(0).getCount() + "] not [" + runs + "]");
+ int resultCount = (int) Math.round(itemOut.getTotalContentAmount());
+ int lowerLimit = runs * 0;
+ int upperLimit = runs * 4;
+ helper.assertTrue(TestUtils.isCountWithinRange(resultCount, lowerLimit, upperLimit),
+ "Batched LCent didn't produce correct number of items, produced [" +
+ resultCount + "] not [" + lowerLimit + "-" + upperLimit + "]");
+
+ addedRolls[finalI - 1] = resultCount;
+
+ // reset for a rerun
+ itemIn.setStackInSlot(0, LCENT_OUT.copyWithCount(16));
+ });
+ }
+
+ helper.runAfterDelay(1 + 17 * REPLICAS, () -> {
+ // check if each roll was a multiple of run count
+ boolean sus = false;
+ int[] rolls = new int[REPLICAS];
+
+ rolls[0] = addedRolls[0];
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[0], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Batched LCent ranged item output test iteration " + 1 + " produced [" +
+ rolls[0] + "] items, a multiple of its Batch * Parallel count (" + batches +
+ "). If this message only appears once, this is likely a false positive.");
+ }
+ for (int i = 1; i < REPLICAS; i++) {
+ rolls[i] = addedRolls[i] - addedRolls[i - 1];
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Batched LCent ranged item output test iteration " + (i + 1) + " produced [" +
+ rolls[i] + "] items, a multiple of its Batch * Parallel count (" + batches +
+ "). If this message only appears once, this is likely a false positive.");
+ } else {
+ sus = false;
+ break;
+ }
+ }
+
+ helper.assertFalse(sus, "Batched LCent ranged item output test rolled exactly even to " +
+ "Batch * Parallel count on every iteration");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with 16x Parallels with ranged item input
+ @GameTest(template = "large_centrifuge_zpm_batch_parallel16",
+ batch = "RangedIngredients",
+ timeoutTicks = 500)
+ public static void multiblockLCentRangedItemInput16ParallelBatched(GameTestHelper helper) {
+ BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper);
+
+ NotifiableItemStackHandler itemIn = busHolder.inputBus1.getInventory();
+ NotifiableItemStackHandler itemOut = busHolder.outputBus1.getInventory();
+
+ int batches = 16;
+ int parallels = 16;
+ busHolder.controller.setBatchEnabled(true);
+ busHolder.parallelHatch.setCurrentParallel(parallels);
+
+ int j;
+ int stacks = batches * parallels / 64;
+
+ for (j = 0; j < stacks; j++) {
+ itemIn.setStackInSlot(j, COBBLE.copyWithCount((batches * parallels / stacks)));
+ }
+ for (int k = j; k < stacks + batches; k++) {
+ itemIn.setStackInSlot(k, LCENT_IN.copyWithCount(64));
+ }
+
+ // 1t to turn on, 1t per recipe run
+ // 16 parallels
+ // check the results of all rolls together
+ // repeat recipe REPLICAS times
+ int[] rolls = new int[REPLICAS];
+ for (int i = 1; i <= REPLICAS; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(65 * finalI, () -> {
+ ItemStack results = itemIn.getStackInSlot(0);
+ int upperLimit = 64 - (batches * parallels * 0);
+ int lowerLimit = 64 - (batches * parallels * 4);
+ int completed = batches * parallels * finalI;
+ helper.assertTrue(TestUtils.isItemStackEqual(itemOut.getStackInSlot(0)
+ .copyWithCount((int) Math.round(itemOut.getTotalContentAmount())),
+ STONE.copyWithCount(completed)),
+ "Batched Parallel LCent didn't complete correct number of recipes, completed [" +
+ ((int) Math.round(itemOut.getTotalContentAmount())) + "] not [" +
+ completed + "]");
+ helper.assertTrue(TestUtils.isItemWithinRange(results, lowerLimit, upperLimit),
+ "Batched Parallel LCent didn't consume correct number of items, consumed " +
+ (64 - results.getCount()) + "] not [" + lowerLimit + "-" + upperLimit + "]");
+
+ rolls[finalI - 1] = 64 - results.getCount();
+
+ // reset for a rerun
+ int l;
+ for (l = 0; l < stacks; l++) {
+ itemIn.setStackInSlot(l,
+ COBBLE.copyWithCount((batches * parallels / stacks)));
+ }
+ for (int k = l; k < stacks + batches; k++) {
+ itemIn.setStackInSlot(k, LCENT_IN.copyWithCount(64));
+ }
+ });
+ }
+
+ helper.runAfterDelay(1 + 65 * REPLICAS, () -> {
+ // check if each roll was a multiple of run count
+ boolean sus = false;
+ for (int i = 0; i < REPLICAS; i++) {
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Batched Parallel LCent ranged item input test iteration " + i + " consumed [" +
+ rolls[i] + "] items, a multiple of its Batch * Parallel count (" + (batches * parallels) +
+ "). If this message only appears once, this is likely a false positive.");
+ } else {
+ sus = false;
+ break;
+ }
+ }
+
+ helper.assertFalse(sus, "Batched Parallel LCent ranged item input test rolled exactly even to" +
+ " Batch * Parallel count on every iteration");
+ helper.succeed();
+ });
+ }
+
+ // test for multiblock machine with 16x Parallels with ranged item output
+ @GameTest(template = "large_centrifuge_zpm_batch_parallel16",
+ batch = "RangedIngredients",
+ timeoutTicks = 500)
+ public static void multiblockLCentRangedItemOutput16ParallelBatched(GameTestHelper helper) {
+ BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper);
+
+ NotifiableItemStackHandler itemIn = busHolder.inputBus1.getInventory();
+ NotifiableItemStackHandler itemOut = busHolder.outputBus1.getInventory();
+
+ int batches = 16;
+ int parallels = 16;
+ busHolder.controller.setBatchEnabled(true);
+ busHolder.parallelHatch.setCurrentParallel(parallels);
+
+ for (int j = 0; j < batches; j++) {
+ itemIn.setStackInSlot(j, LCENT_OUT.copyWithCount(16));
+ }
+
+ // 1t to turn on, 1t per recipe run
+ // 16 parallels
+ // check the results of all rolls together
+ // repeat recipe REPLICAS times
+ int[] addedRolls = new int[REPLICAS];
+ for (int i = 1; i <= REPLICAS; i++) {
+ final int finalI = i; // lambda preserve you
+ helper.runAfterDelay(65 * finalI, () -> {
+ int runs = finalI * batches * parallels;
+ helper.assertTrue(itemIn.isEmpty(),
+ "Batched Parallel LCent didn't complete correct number of recipes, completed [" +
+ itemIn.getTotalContentAmount() + "] not [" + runs + "]");
+ int resultCount = (int) Math.round(itemOut.getTotalContentAmount());
+ int lowerLimit = runs * 0;
+ int upperLimit = runs * 4;
+ helper.assertTrue(TestUtils.isCountWithinRange(resultCount, lowerLimit, upperLimit),
+ "Batched Parallel LCent didn't produce correct number of items, produced [" +
+ resultCount + "] not [" + lowerLimit + "-" + upperLimit + "]");
+
+ addedRolls[finalI - 1] = resultCount;
+
+ // reset for a rerun
+ for (int j = 0; j < batches; j++) {
+ itemIn.setStackInSlot(j, LCENT_OUT.copyWithCount(16));
+ }
+ });
+ }
+
+ helper.runAfterDelay(1 + 65 * REPLICAS, () -> {
+ // check if each roll was a multiple of run count
+ boolean sus = false;
+ int[] rolls = new int[REPLICAS];
+
+ rolls[0] = addedRolls[0];
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[0], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Batched Parallel LCent ranged item output test iteration " + 1 + " produced [" +
+ rolls[0] + "] items, a multiple of its Batch * Parallel count (" + (batches * parallels) +
+ "). If this message only appears once, this is likely a false positive.");
+ }
+ for (int i = 1; i < REPLICAS; i++) {
+ rolls[i] = addedRolls[i] - addedRolls[i - 1];
+ if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) {
+ sus = true;
+ GTCEu.LOGGER.warn("Batched Parallel LCent ranged item output test iteration " + (i + 1) +
+ " produced [" + rolls[i] + "] items, a multiple of its Batch * Parallel count (" +
+ (batches * parallels) + "). If this message only appears once, this is likely" +
+ " a false positive.");
+ } else {
+ sus = false;
+ break;
+ }
+ }
+
+ helper.assertFalse(sus, "Batched Parallel LCent ranged item output test rolled exactly even to" +
+ " Batch * Parallel count on every iteration");
+ helper.succeed();
+ });
+ }
+}
diff --git a/src/test/java/com/gregtechceu/gtceu/api/recipe/lookup/GTRecipeLookupTest.java b/src/test/java/com/gregtechceu/gtceu/api/recipe/lookup/GTRecipeLookupTest.java
index 3db27adca83..6c471a3d931 100644
--- a/src/test/java/com/gregtechceu/gtceu/api/recipe/lookup/GTRecipeLookupTest.java
+++ b/src/test/java/com/gregtechceu/gtceu/api/recipe/lookup/GTRecipeLookupTest.java
@@ -6,19 +6,24 @@
import com.gregtechceu.gtceu.api.recipe.GTRecipeType;
import com.gregtechceu.gtceu.api.recipe.ingredient.SizedIngredient;
import com.gregtechceu.gtceu.api.recipe.lookup.ingredient.AbstractMapIngredient;
+import com.gregtechceu.gtceu.api.recipe.lookup.ingredient.fluid.FluidStackMapIngredient;
import com.gregtechceu.gtceu.api.recipe.lookup.ingredient.item.ItemStackMapIngredient;
+import com.gregtechceu.gtceu.common.data.GTMaterials;
import com.gregtechceu.gtceu.gametest.util.TestUtils;
import net.minecraft.gametest.framework.BeforeBatch;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.server.level.ServerLevel;
+import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
+import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.gametest.GameTestHolder;
import net.minecraftforge.gametest.PrefixGameTestTemplate;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
@@ -31,6 +36,7 @@ public class GTRecipeLookupTest {
private static final Predicate ALWAYS_FALSE = gtRecipe -> false;
private static GTRecipeType RECIPE_TYPE;
private static GTRecipe SMELT_STONE, SMELT_ACACIA_WOOD, SMELT_BIRCH_WOOD, SMELT_CHERRY_WOOD;
+ private static GTRecipe RANGED_INPUT_ITEM, RANGED_INPUT_FLUID, RANGED_INPUT_BOTH;
@BeforeBatch(batch = "GTRecipeLookup")
public static void prepare(ServerLevel level) {
@@ -53,11 +59,27 @@ public static void prepare(ServerLevel level) {
.inputItems(Items.CHERRY_WOOD, 16)
.outputItems(Items.CHARCOAL, 1)
.buildRawRecipe();
+ RANGED_INPUT_ITEM = RECIPE_TYPE.recipeBuilder("ranged_input_item")
+ .inputItemsRanged(Items.RED_WOOL, UniformInt.of(0, 4))
+ .outputItems(Items.CHARCOAL, 1)
+ .buildRawRecipe();
+ RANGED_INPUT_FLUID = RECIPE_TYPE.recipeBuilder("ranged_input_fluid")
+ .inputFluidsRanged(GTMaterials.Helium.getFluid(1), UniformInt.of(0, 4))
+ .outputItems(Items.CHARCOAL, 1)
+ .buildRawRecipe();
+ RANGED_INPUT_BOTH = RECIPE_TYPE.recipeBuilder("ranged_input_both")
+ .inputItemsRanged(Items.BLUE_WOOL, UniformInt.of(0, 4))
+ .inputFluidsRanged(GTMaterials.Iron.getFluid(1), UniformInt.of(0, 4))
+ .outputItems(Items.CHARCOAL, 1)
+ .buildRawRecipe();
for (GTRecipe recipe : List.of(SMELT_STONE,
SMELT_ACACIA_WOOD,
SMELT_BIRCH_WOOD,
- SMELT_CHERRY_WOOD)) {
+ SMELT_CHERRY_WOOD,
+ RANGED_INPUT_ITEM,
+ RANGED_INPUT_FLUID,
+ RANGED_INPUT_BOTH)) {
LOOKUP.addRecipe(recipe);
}
}
@@ -69,6 +91,17 @@ private static List> createIngredients(ItemStack...
.toList());
}
+ private static List> createIngredients(FluidStack... stacks) {
+ return List.of(
+ Arrays.stream(stacks)
+ .map(stack -> (AbstractMapIngredient) new FluidStackMapIngredient(stack))
+ .toList());
+ }
+
+ private static List> createIngredients(List>... stacks) {
+ return Arrays.stream(stacks).flatMap(Collection::stream).toList();
+ }
+
// Simple recipe test whose lookup should succeed
@GameTest(template = "empty", batch = "GTRecipeLookup")
public static void recipeLookupSimpleSuccessTest(GameTestHelper helper) {
@@ -152,4 +185,48 @@ public static void recipeLookupCustomCountCanHandleTest(GameTestHelper helper) {
helper.succeed();
}
+
+ // Simple recipe test with ranged item input, whose lookup should succeed
+ // Repeats 100 times to make sure there's no random roll interference
+ @GameTest(template = "empty", batch = "GTRecipeLookup")
+ public static void recipeLookupSimpleRangedItemSuccessTest(GameTestHelper helper) {
+ var ingredients = createIngredients(new ItemStack(Items.RED_WOOL, 4));
+ for (int i = 0; i < 100; i++) {
+ GTRecipe resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe(ingredients, LOOKUP.getLookup(),
+ ALWAYS_TRUE);
+ helper.assertTrue(RANGED_INPUT_ITEM.equals(resultRecipe),
+ "GT Recipe should be ranged_input_item, instead was " + resultRecipe + ". Failed on check " + i);
+ }
+ helper.succeed();
+ }
+
+ // Simple recipe test with ranged fluid input, whose lookup should succeed
+ // Repeats 100 times to make sure there's no random roll interference
+ @GameTest(template = "empty", batch = "GTRecipeLookup")
+ public static void recipeLookupSimpleRangedFluidSuccessTest(GameTestHelper helper) {
+ var ingredients = createIngredients(GTMaterials.Helium.getFluid(4));
+ for (int i = 0; i < 100; i++) {
+ GTRecipe resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe(ingredients, LOOKUP.getLookup(),
+ ALWAYS_TRUE);
+ helper.assertTrue(RANGED_INPUT_FLUID.equals(resultRecipe),
+ "GT Recipe should be ranged_input_fluid, instead was " + resultRecipe + ". Failed on check " + i);
+ }
+ helper.succeed();
+ }
+
+ // Simple recipe test with ranged item and fluid inputs, whose lookup should succeed
+ // Repeats 100 times to make sure there's no random roll interference
+ @GameTest(template = "empty", batch = "GTRecipeLookup")
+ public static void recipeLookupSimpleRangedItemFluidSuccessTest(GameTestHelper helper) {
+ var ingredients = createIngredients(
+ createIngredients(new ItemStack(Items.BLUE_WOOL, 4)),
+ createIngredients(GTMaterials.Iron.getFluid(4)));
+ for (int i = 0; i < 100; i++) {
+ GTRecipe resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe(ingredients, LOOKUP.getLookup(),
+ ALWAYS_TRUE);
+ helper.assertTrue(RANGED_INPUT_BOTH.equals(resultRecipe),
+ "GT Recipe should be raged_input_both, instead was " + resultRecipe + ". Failed on check " + i);
+ }
+ helper.succeed();
+ }
}
diff --git a/src/test/java/com/gregtechceu/gtceu/gametest/util/TestUtils.java b/src/test/java/com/gregtechceu/gtceu/gametest/util/TestUtils.java
index 8cffbf6bdd8..58273e6a5ff 100644
--- a/src/test/java/com/gregtechceu/gtceu/gametest/util/TestUtils.java
+++ b/src/test/java/com/gregtechceu/gtceu/gametest/util/TestUtils.java
@@ -43,7 +43,7 @@ public class TestUtils {
/**
* Compares two itemstacks' items and amounts
* DOES NOT CHECK TAGS OR NBT ETC!
- *
+ *
* @return {@code true} if items and amounts are equal
*/
public static boolean isItemStackEqual(ItemStack stack1, ItemStack stack2) {
@@ -52,7 +52,7 @@ public static boolean isItemStackEqual(ItemStack stack1, ItemStack stack2) {
/**
* Compares two itemstacks and a range.
- *
+ *
* @return {@code true} if items are equal, and if stack2's amount is within range.
*/
public static boolean isItemStackWithinRange(ItemStack stack1, ItemStack stack2, int min, int max) {
@@ -65,17 +65,32 @@ public static boolean isItemStackWithinRange(ItemStack stack1, ItemStack stack2,
* multiplied.
* This test can trigger false positives from bad luck and should be run more than once to reduce the odds of bad
* luck.
- *
+ *
* @return {@code true} if the size is an exact multiple of the total run count. TRUE INDICATES FAILURE.
*/
public static boolean isStackSizeExactlyEvenMultiple(int size, int batches, int parallels, int runs) {
return size % (batches * parallels * runs) == 0;
}
+ /**
+ * Compares two itemstack[]s' items and amounts
+ * Necessary because itemStack does not implement .equals()
+ */
+ public static boolean areItemStacksEqual(ItemStack[] stack1, ItemStack[] stack2) {
+ if (stack1.length != stack2.length)
+ return false;
+
+ for (int i = 0; i < stack1.length; i++) {
+ if (!isItemStackEqual(stack1[i], stack2[i]))
+ return false;
+ }
+ return true;
+ }
+
/**
* Compares two fluidstacks' fluids and amounts
* DOES NOT CHECK TAGS OR NBT ETC!
- *
+ *
* @return {@code true} if fluids and amounts are equal
*/
public static boolean isFluidStackEqual(FluidStack stack1, FluidStack stack2) {
@@ -84,16 +99,31 @@ public static boolean isFluidStackEqual(FluidStack stack1, FluidStack stack2) {
/**
* Compares two fluidstacks and a range.
- *
+ *
* @return {@code true} if items are equal, and if stack2's amount is within range.
*/
public static boolean isFluidStackWithinRange(FluidStack stack1, FluidStack stack2, int min, int max) {
return stack1.isFluidEqual(stack2) && isFluidWithinRange(stack2, min, max);
}
+ /**
+ * Compares two fluidstack[]s' fluids and amounts
+ * Necessary because fluidStack's implementation of .equals() does not check amounts
+ */
+ public static boolean areFluidStacksEqual(FluidStack[] stack1, FluidStack[] stack2) {
+ if (stack1.length != stack2.length)
+ return false;
+
+ for (int i = 0; i < stack1.length; i++) {
+ if (!isFluidStackEqual(stack1[i], stack2[i]))
+ return false;
+ }
+ return true;
+ }
+
/**
* Compares an ItemStack with a range
- *
+ *
* @return {@code true} if the ItemStack's count is within range
*/
public static boolean isItemWithinRange(ItemStack stack, int min, int max) {
@@ -102,13 +132,22 @@ public static boolean isItemWithinRange(ItemStack stack, int min, int max) {
/**
* Compares a FluidStack with a range
- *
+ *
* @return {@code true} if the FluidStack's amount is within range
*/
public static boolean isFluidWithinRange(FluidStack stack, int min, int max) {
return stack.getAmount() <= max && stack.getAmount() >= min;
}
+ /**
+ * compares an integer with a range
+ *
+ * @return {@code true} if the integer count is within range
+ */
+ public static boolean isCountWithinRange(int stack, int min, int max) {
+ return stack <= max && stack >= min;
+ }
+
/**
* Forces a structure check on multiblocks after being placed, to avoid having to wait ticks.
* Ideally this doesn't need to happen, but it seems not doing this makes the multiblock tests flakey
@@ -212,9 +251,10 @@ public static void assertEqual(GameTestHelper helper, ItemStack stack1, ItemStac
}
public static void assertEqual(GameTestHelper helper, FluidStack stack1, FluidStack stack2) {
- helper.assertTrue(isFluidStackEqual(stack1, stack2), "Fluid stacks not equal: \"%s %d\" != \"%s %d\"".formatted(
- stack1.getDisplayName().getString(), stack1.getAmount(),
- stack2.getDisplayName().getString(), stack2.getAmount()));
+ helper.assertTrue(stack1.isFluidStackIdentical(stack2),
+ "Fluid stacks not equal: \"%s %d\" != \"%s %d\"".formatted(
+ stack1.getDisplayName().getString(), stack1.getAmount(),
+ stack2.getDisplayName().getString(), stack2.getAmount()));
}
public static void assertLampOn(GameTestHelper helper, BlockPos pos) {
@@ -227,7 +267,7 @@ public static void assertLampOff(GameTestHelper helper, BlockPos pos) {
/**
* Shortcut function to retrieve a metamachine from a blockentity's
- *
+ *
* @param entity The MetaMachineBlockEntity
* @return the machine held, if any
*/
@@ -237,7 +277,7 @@ public static MetaMachine getMetaMachine(BlockEntity entity) {
/**
* Helper function to succeed after the test is over
- *
+ *
* @param helper GameTestHelper
*/
public static void succeedAfterTest(GameTestHelper helper) {
@@ -246,7 +286,7 @@ public static void succeedAfterTest(GameTestHelper helper) {
/**
* Helper function to succeed after the test is over
- *
+ *
* @param helper GameTestHelper
* @param timeout Ticks to wait until succeeding
*/
diff --git a/src/test/resources/data/gtceu/structures/large_centrifuge_zpm_batch_parallel16.nbt b/src/test/resources/data/gtceu/structures/large_centrifuge_zpm_batch_parallel16.nbt
new file mode 100644
index 00000000000..d8a1be28dec
Binary files /dev/null and b/src/test/resources/data/gtceu/structures/large_centrifuge_zpm_batch_parallel16.nbt differ
diff --git a/src/test/resources/data/gtceu/structures/lcr_ranged_ingredients.nbt b/src/test/resources/data/gtceu/structures/lcr_ranged_ingredients.nbt
new file mode 100644
index 00000000000..4d66dd54a86
Binary files /dev/null and b/src/test/resources/data/gtceu/structures/lcr_ranged_ingredients.nbt differ