diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java index 3bff715f40f..49c761e42d8 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java @@ -182,7 +182,9 @@ public int getMaxParallelByInput(IRecipeCapabilityHolder holder, GTRecipe recipe for (Content content : inputs) { FluidIngredient ing = of(content.content()); - int amount = ing.getAmount(); + int amount; + if (ing instanceof IRangedIngredient ranged) amount = ranged.getMaxRoll(); + else amount = ing.getAmount(); if (content.chance() == 0) { nonConsumables.addTo(ing, amount); diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/notifiable/NotifiableItemStackHandler.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/notifiable/NotifiableItemStackHandler.java index 800f40e1126..9182236e467 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/notifiable/NotifiableItemStackHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/notifiable/NotifiableItemStackHandler.java @@ -120,20 +120,9 @@ public static List handleRecipe(IO io, GTRecipe recipe, List(inputs), new HashMap<>(outputs), + new HashMap<>(), new HashMap<>(), + new HashMap<>(inputChanceLogics), new HashMap<>(outputChanceLogics), + new HashMap<>(tickInputChanceLogics), new HashMap<>(tickOutputChanceLogics), + new ArrayList<>(conditions), + new ArrayList<>(ingredientActions), data, duration, recipeCategory, groupColor); + copied.ocLevel = ocLevel; + copied.parallels = parallels; + copied.batchParallels = batchParallels; + copied.subtickParallels = subtickParallels; + return copied; + } + @Override public @NotNull RecipeSerializer getSerializer() { return GTRecipeSerializer.SERIALIZER; @@ -234,36 +246,6 @@ public ChanceLogic getChanceLogicForCapability(RecipeCapability cap, IO io, b return new EnergyStack(v, a); } - public void doPrerolls(IdentityHashMap, Object2IntMap> chanceCaches) { - var rangedContents = getFullContents(); - for (Content item : rangedContents) { - if (item.content() instanceof IRangedIngredient ranged) - ranged.rollSampledCount(); - } - } - - public void doTickPrerolls(IdentityHashMap, Object2IntMap> chanceCaches) { - var rangedContents = getFullTickContents(); - for (Content item : rangedContents) { - if (item.content() instanceof IRangedIngredient ranged) - ranged.rollSampledCount(); - } - } - - public List getFullContents() { - return Stream - .concat(inputs.values().stream(), outputs.values().stream()) - .flatMap(List::stream) - .toList(); - } - - public List getFullTickContents() { - return Stream - .concat(tickInputs.values().stream(), tickOutputs.values().stream()) - .flatMap(List::stream) - .toList(); - } - public int getTotalRuns() { return parallels * subtickParallels * batchParallels; } diff --git a/src/main/java/com/gregtechceu/gtceu/api/recipe/RecipeHelper.java b/src/main/java/com/gregtechceu/gtceu/api/recipe/RecipeHelper.java index 7a1111dc43c..fa32d52c7e6 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/recipe/RecipeHelper.java +++ b/src/main/java/com/gregtechceu/gtceu/api/recipe/RecipeHelper.java @@ -11,6 +11,7 @@ import com.gregtechceu.gtceu.api.recipe.content.Content; import com.gregtechceu.gtceu.api.recipe.ingredient.EnergyStack; import com.gregtechceu.gtceu.api.recipe.ingredient.FluidIngredient; +import com.gregtechceu.gtceu.api.recipe.ingredient.IRangedIngredient; import com.gregtechceu.gtceu.config.ConfigHolder; import com.gregtechceu.gtceu.data.recipe.builder.GTRecipeBuilder; import com.gregtechceu.gtceu.utils.GTUtil; @@ -400,6 +401,107 @@ public static int getRatioForDistillery(FluidIngredient fluidInput, FluidIngredi } public static boolean isFluidStackDivisibleForDistillery(FluidIngredient fluidStack, int divisor) { - return fluidStack.getAmount() % divisor == 0 && fluidStack.getAmount() / divisor >= 25; + int amount = (fluidStack instanceof IRangedIngredient ranged ? ranged.getMaxRoll() : fluidStack.getAmount()); + return amount % divisor == 0 && amount / divisor >= 25; + } + + /** + * Rolls the value of all Ranged Ingredients in a recipe and replaces them with appropriate Sized Ingredients. + * Called once after successful recipe search, immediately before {@link RecipeLogic#handleRecipeIO(GTRecipe, IO)}. + * If a ranged ingredient rolls 0, it is replaced by a Non-Consumed ingredient of max size. + * + * Takes the machine's current Chance Caches, but does not use them. Yet. This parameter will be used in + * the future Chanced Item Prerolls, but it has been added early to avoid changing the method signature later. + * + * @return a copy of the input recipe with all ranged ingredients replaced + */ + public static GTRecipe doPrerolls(GTRecipe recipe, + IdentityHashMap, Object2IntMap> chanceCaches) { + GTRecipe runningRecipe = recipe.copy(); + int count; + boolean zero; + for (List input : runningRecipe.inputs.values()) { + for (ListIterator iterator = input.listIterator(); iterator.hasNext();) { + Content content = iterator.next(); + if (content.content() instanceof IRangedIngredient ranged) { + count = ranged.rollSampledCount(); + zero = (count == 0); + if (zero) ranged.setSampledCount(ranged.getMaxRoll()); + + iterator.set(new Content(ranged.collapse(), (!zero ? content.chance() : 0), content.maxChance())); + ranged.reset(); + } + } + } + for (List output : runningRecipe.outputs.values()) { + for (ListIterator iterator = output.listIterator(); iterator.hasNext();) { + Content content = iterator.next(); + if (content.content() instanceof IRangedIngredient ranged) { + count = ranged.rollSampledCount(); + zero = (count == 0); + if (zero) ranged.setSampledCount(ranged.getMaxRoll()); + + iterator.set(new Content(ranged.collapse(), (!zero ? content.chance() : 0), content.maxChance())); + ranged.reset(); + } + } + } + return runningRecipe; + } + + /** + * Rolls the value of all per-tick Ranged Ingredients in a recipe and replaces them with appropriate Sized + * Ingredients. + * Called every tick while a recipe is running, immediately before + * {@link RecipeLogic#handleTickRecipeIO(GTRecipe, IO)}. + * + * If a ranged ingredient rolls 0, it is replaced by a Non-Consumed ingredient of max size. + * + * Takes the machine's current Chance Caches, but does not use them. Yet. This parameter will be used in + * the future Chanced Item Prerolls, but it has been added early to avoid changing the method signature later. + * + * @return a copy of the input recipe with all per-tick ranged ingredients replaced + */ + public static GTRecipe doTickPrerolls(GTRecipe recipe, + IdentityHashMap, Object2IntMap> chanceCaches, + GTRecipe lastDisplayedRecipe) { + if (!recipe.hasTick()) return recipe; + + GTRecipe runningRecipe = recipe.copyWithoutTicks(); + int count; + boolean zero; + for (var entry : lastDisplayedRecipe.tickInputs.entrySet()) { + RecipeCapability capability = entry.getKey(); + List handler = entry.getValue(); + for (ListIterator iterator = handler.listIterator(); iterator.hasNext();) { + Content content = iterator.next(); + if (content.content() instanceof IRangedIngredient ranged) { + count = ranged.rollSampledCount(); + zero = (count == 0); + if (zero) ranged.setSampledCount(ranged.getMaxRoll()); + + content = new Content(ranged.collapse(), (!zero ? content.chance() : 0), content.maxChance()); + ranged.reset(); + } + runningRecipe.tickInputs.computeIfAbsent(capability, c -> new ArrayList<>()).add(content); + } + } + for (var entry : lastDisplayedRecipe.tickOutputs.entrySet()) { + RecipeCapability capability = entry.getKey(); + List handler = entry.getValue(); + for (ListIterator iterator = handler.listIterator(); iterator.hasNext();) { + Content content = iterator.next(); + if (content.content() instanceof IRangedIngredient ranged) { + count = ranged.rollSampledCount(); + zero = (count == 0); + if (zero) ranged.setSampledCount(ranged.getMaxRoll()); + + content = new Content(ranged.collapse(), (!zero ? content.chance() : 0), content.maxChance()); + ranged.reset(); + } + runningRecipe.tickOutputs.computeIfAbsent(capability, c -> new ArrayList<>()).add(content); + } + } + return runningRecipe; } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/recipe/ingredient/IRangedIngredient.java b/src/main/java/com/gregtechceu/gtceu/api/recipe/ingredient/IRangedIngredient.java index 5f3575731bb..c0b936a4fd5 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/recipe/ingredient/IRangedIngredient.java +++ b/src/main/java/com/gregtechceu/gtceu/api/recipe/ingredient/IRangedIngredient.java @@ -1,5 +1,6 @@ package com.gregtechceu.gtceu.api.recipe.ingredient; +import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.GTValues; import net.minecraft.util.RandomSource; @@ -7,7 +8,7 @@ import org.jetbrains.annotations.NotNull; -public interface IRangedIngredient { +public interface IRangedIngredient { IntProvider getCountProvider(); @@ -15,6 +16,12 @@ public interface IRangedIngredient { void setSampledCount(int count); + default T collapse() { + if (!isRolled()) + GTCEu.LOGGER.warn("Ranged ingredient was collapsed without being rolled!"); + return null; + } + /** * If this ingredient has not yet had its count rolled, rolls it and returns the roll. * If it has, returns the existing roll. @@ -28,8 +35,6 @@ default int rollSampledCount() { int rollSampledCount(@NotNull RandomSource random); - int getAmount(); - /** * @return the average roll of this ranged amount */ 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 88b9e929175..3d1e1d16869 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 @@ -28,7 +28,7 @@ * and either an {@link IntProvider} or {@code int, int} range bounds (inclusive). * Functions similarly to {@link IntProviderIngredient}. */ -public class IntProviderFluidIngredient extends FluidIngredient implements IRangedIngredient { +public class IntProviderFluidIngredient extends FluidIngredient implements IRangedIngredient { public static final Codec CODEC = ExtraCodecs.JSON .xmap(IntProviderFluidIngredient::fromJson, IntProviderFluidIngredient::toJson); @@ -38,6 +38,7 @@ public class IntProviderFluidIngredient extends FluidIngredient implements IRang /** * The last result of {@link IntProviderFluidIngredient#getSampledCount()}. -1 if not rolled. */ + @Setter @Getter protected int sampledCount = -1; /** @@ -45,14 +46,11 @@ public class IntProviderFluidIngredient extends FluidIngredient implements IRang */ @Getter private final FluidIngredient inner; - @Setter - protected FluidStack[] fluidStacks = null; protected IntProviderFluidIngredient(FluidIngredient inner, IntProvider provider) { super(inner.values, provider.getMaxValue(), inner.nbt); this.inner = inner; this.countProvider = provider; - setAmount(provider.getMaxValue()); } protected IntProviderFluidIngredient(FluidIngredient inner, IntProvider provider, int sampledCount) { @@ -60,20 +58,18 @@ protected IntProviderFluidIngredient(FluidIngredient inner, IntProvider provider this.inner = inner; this.countProvider = provider; this.sampledCount = sampledCount; - setAmount(isRolled() ? sampledCount : provider.getMaxValue()); } @Override public IntProviderFluidIngredient copy() { IntProviderFluidIngredient ipfi = new IntProviderFluidIngredient(this.inner, this.countProvider); ipfi.setSampledCount(this.sampledCount); - ipfi.setAmount(this.getAmount()); return ipfi; } @Override public boolean isEmpty() { - return this.getAmount() == 0 || super.isEmpty(); + return inner.isEmpty(); } /** @@ -84,23 +80,8 @@ public boolean isEmpty() { */ @Override public FluidStack[] getStacks() { - if (changed || fluidStacks == null) { - changed = false; - if (!isRolled()) { - setAmount(rollSampledCount()); - if (getAmount() == 0) { - fluidStacks = EMPTY_STACK_ARRAY; - return EMPTY_STACK_ARRAY; - } - } - var innerStacks = inner.getStacks(); - this.fluidStacks = new FluidStack[innerStacks.length]; - for (int i = 0; i < fluidStacks.length; i++) { - fluidStacks[i] = innerStacks[i].copy(); - fluidStacks[i].setAmount(getAmount()); - } - } - return fluidStacks; + GTCEu.LOGGER.warn("Cannot get stacks of a Ranged Fluid Ingredient!"); + return EMPTY_STACK_ARRAY; } /** @@ -127,9 +108,8 @@ public FluidStack[] getStacks() { public int rollSampledCount(@NotNull RandomSource random) { if (!isRolled()) { sampledCount = countProvider.sample(random); - this.setAmount(sampledCount); } - return getAmount(); + return sampledCount; } /** @@ -144,16 +124,12 @@ public double getMidRoll() { */ public void reset() { sampledCount = -1; - super.setAmount(getMaxRoll()); - fluidStacks = null; } - /** - * Also sets the Amount of this ingredient - */ - public void setSampledCount(int count) { - this.sampledCount = count; - super.setAmount(count); + @Override + public FluidIngredient collapse() { + IRangedIngredient.super.collapse(); + return new FluidIngredient(inner.values, rollSampledCount(), inner.nbt); } /** 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 af9bafd5e11..9de22cdb596 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 @@ -33,7 +33,7 @@ * and an {@link IntProvider}. * Functions similarly to {@link IntProviderFluidIngredient}. */ -public class IntProviderIngredient extends Ingredient implements IRangedIngredient { +public class IntProviderIngredient extends Ingredient implements IRangedIngredient { public static final ResourceLocation TYPE = GTCEu.id("int_provider"); public static final ItemStack[] EMPTY_STACK_ARRAY = new ItemStack[0]; @@ -43,6 +43,7 @@ public class IntProviderIngredient extends Ingredient implements IRangedIngredie /** * The last result of {@link IntProviderIngredient#rollSampledCount(RandomSource)}. -1 if not rolled. */ + @Setter @Getter protected int sampledCount = -1; /** @@ -50,29 +51,22 @@ public class IntProviderIngredient extends Ingredient implements IRangedIngredie */ @Getter protected final Ingredient inner; - @Setter - protected ItemStack[] itemStacks = null; - @Getter - private int amount; - private boolean changed = true; protected IntProviderIngredient(Ingredient inner, IntProvider countProvider) { super(Stream.empty()); this.inner = inner; this.countProvider = countProvider; - this.amount = getMaxRoll(); } - protected IntProviderIngredient(Ingredient inner, IntProvider countProvider, int sampledCount, int amount) { + protected IntProviderIngredient(Ingredient inner, IntProvider countProvider, int sampledCount) { super(Stream.empty()); this.inner = inner; this.countProvider = countProvider; this.sampledCount = sampledCount; - this.amount = amount; } public IntProviderIngredient copy() { - return new IntProviderIngredient(this.inner, this.countProvider, this.sampledCount, this.amount); + return new IntProviderIngredient(this.inner, this.countProvider, this.sampledCount); } /** @@ -107,27 +101,8 @@ public boolean test(@Nullable ItemStack stack) { */ @Override public ItemStack @NotNull [] getItems() { - if (changed || itemStacks == null) { - changed = false; - if (!isRolled()) { - setAmount(rollSampledCount()); - if (getAmount() == 0) { - itemStacks = EMPTY_STACK_ARRAY; - return EMPTY_STACK_ARRAY; - } - } - var innerStacks = inner.getItems(); - this.itemStacks = new ItemStack[innerStacks.length]; - for (int i = 0; i < itemStacks.length; i++) { - itemStacks[i] = innerStacks[i].copyWithCount(getAmount()); - } - } - return itemStacks; - } - - public void setAmount(int amount) { - this.amount = amount; - this.changed = true; + GTCEu.LOGGER.warn("Cannot get items of a Ranged Ingredient!"); + return EMPTY_STACK_ARRAY; } /** @@ -153,17 +128,14 @@ public void setAmount(int amount) { public int rollSampledCount(@NotNull RandomSource random) { if (!isRolled()) { sampledCount = countProvider.sample(random); - this.setAmount(sampledCount); } return sampledCount; } - /** - * Also sets the Amount of this ingredient - */ - public void setSampledCount(int count) { - this.sampledCount = count; - this.setAmount(count); + @Override + public SizedIngredient collapse() { + IRangedIngredient.super.collapse(); + return SizedIngredient.create(inner, rollSampledCount()); } /** @@ -171,8 +143,6 @@ public void setSampledCount(int count) { */ public void reset() { sampledCount = -1; - setAmount(getMaxRoll()); - itemStacks = null; } @Override @@ -182,7 +152,7 @@ public void reset() { @Override public boolean isEmpty() { - return this.getAmount() == 0 || inner.isEmpty(); + return inner.isEmpty(); } @Override @@ -219,7 +189,6 @@ public static IntProviderIngredient fromJson(JsonObject json) { .getOrThrow(false, GTCEu.LOGGER::error)); json.add("ingredient", inner.toJson()); json.addProperty("sampledCount", sampledCount); - json.addProperty("amount", amount); return json; } @@ -231,8 +200,7 @@ public static IntProviderIngredient fromJson(JsonObject json) { IntProvider provider = IntProvider.CODEC.parse(NbtOps.INSTANCE, nbt.get("provider")) .getOrThrow(false, GTCEu.LOGGER::error); int sampledCount = nbt.getInt("sampledCount"); - int amount = nbt.getInt("amount"); - return new IntProviderIngredient(Ingredient.fromNetwork(buffer), provider, sampledCount, amount); + return new IntProviderIngredient(Ingredient.fromNetwork(buffer), provider, sampledCount); } @Override @@ -241,8 +209,7 @@ public static IntProviderIngredient fromJson(JsonObject json) { .getOrThrow(false, GTCEu.LOGGER::error); Ingredient inner = Ingredient.fromJson(json.get("ingredient")); int sampledCount = json.getAsJsonPrimitive("sampledCount").getAsInt(); - int amount = json.getAsJsonPrimitive("amount").getAsInt(); - return new IntProviderIngredient(inner, provider, sampledCount, amount); + return new IntProviderIngredient(inner, provider, sampledCount); } @Override @@ -251,7 +218,6 @@ public void write(FriendlyByteBuf buffer, IntProviderIngredient ingredient) { wrapper.put("provider", IntProvider.CODEC.encodeStart(NbtOps.INSTANCE, ingredient.countProvider) .getOrThrow(false, GTCEu.LOGGER::error)); wrapper.putInt("sampledCount", ingredient.sampledCount); - wrapper.putInt("amount", ingredient.amount); buffer.writeNbt(wrapper); ingredient.inner.toNetwork(buffer); } diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/AssemblyLineMachine.java b/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/AssemblyLineMachine.java index 76d98ac1e27..13438fe4db7 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/AssemblyLineMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/AssemblyLineMachine.java @@ -18,6 +18,7 @@ import com.gregtechceu.gtceu.api.recipe.GTRecipe; import com.gregtechceu.gtceu.api.recipe.RecipeHelper; import com.gregtechceu.gtceu.api.recipe.ingredient.FluidIngredient; +import com.gregtechceu.gtceu.api.recipe.ingredient.IRangedIngredient; import com.gregtechceu.gtceu.api.sync_system.annotations.SaveField; import com.gregtechceu.gtceu.config.ConfigHolder; @@ -155,7 +156,9 @@ private boolean checkFluidInputs(GTRecipe recipe, boolean isTick) { for (int i = 0; i < inputsSize; i++) { var fluidStack = fluidInventory.get(i); FluidIngredient recipeStack = FluidRecipeCapability.CAP.of(fluidInputs.get(i).content()); - if (!recipeStack.test(fluidStack) || (recipeStack.getAmount()) > fluidStack.getAmount()) { + if (!recipeStack.test(fluidStack) || + (recipeStack instanceof IRangedIngredient ranged ? ranged.getMaxRoll() : + recipeStack.getAmount()) > fluidStack.getAmount()) { return false; } } diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/DistillationTowerMachine.java b/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/DistillationTowerMachine.java index 48c120fd60e..dbba5588919 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/DistillationTowerMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/DistillationTowerMachine.java @@ -13,7 +13,7 @@ import com.gregtechceu.gtceu.api.recipe.RecipeHelper; import com.gregtechceu.gtceu.api.recipe.content.Content; import com.gregtechceu.gtceu.api.recipe.content.ContentModifier; -import com.gregtechceu.gtceu.api.recipe.ingredient.FluidIngredient; +import com.gregtechceu.gtceu.api.recipe.ingredient.IRangedIngredient; import com.gregtechceu.gtceu.api.recipe.modifier.ParallelLogic; import com.gregtechceu.gtceu.api.sync_system.annotations.SaveField; import com.gregtechceu.gtceu.api.sync_system.annotations.SyncToClient; @@ -140,7 +140,7 @@ public int limitFluidParallel(GTRecipe recipe, int multiplier, boolean tick) { .map(Content::content) .map(FluidRecipeCapability.CAP::of) .filter(i -> !i.isEmpty()) - .mapToInt(FluidIngredient::getAmount) + .mapToInt((i -> i instanceof IRangedIngredient ranged ? ranged.getMaxRoll() : i.getAmount())) .max() .orElse(0); diff --git a/src/main/java/com/gregtechceu/gtceu/integration/jade/provider/RecipeOutputProvider.java b/src/main/java/com/gregtechceu/gtceu/integration/jade/provider/RecipeOutputProvider.java index 245477e4139..9c13ba5342c 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/jade/provider/RecipeOutputProvider.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/jade/provider/RecipeOutputProvider.java @@ -4,7 +4,6 @@ import com.gregtechceu.gtceu.api.capability.recipe.FluidRecipeCapability; import com.gregtechceu.gtceu.api.capability.recipe.ItemRecipeCapability; import com.gregtechceu.gtceu.api.machine.trait.recipe.RecipeLogic; -import com.gregtechceu.gtceu.api.recipe.RecipeHelper; import com.gregtechceu.gtceu.api.recipe.content.ContentModifier; import com.gregtechceu.gtceu.api.recipe.ingredient.FluidIngredient; import com.gregtechceu.gtceu.api.recipe.ingredient.IntProviderFluidIngredient; diff --git a/src/main/java/com/gregtechceu/gtceu/integration/kjs/recipe/components/GTRecipeComponents.java b/src/main/java/com/gregtechceu/gtceu/integration/kjs/recipe/components/GTRecipeComponents.java index 474708c1fc3..ef7ba453f10 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/kjs/recipe/components/GTRecipeComponents.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/kjs/recipe/components/GTRecipeComponents.java @@ -8,6 +8,7 @@ import com.gregtechceu.gtceu.api.recipe.chance.logic.ChanceLogic; import com.gregtechceu.gtceu.api.recipe.ingredient.EnergyStack; import com.gregtechceu.gtceu.api.recipe.ingredient.FluidIngredient; +import com.gregtechceu.gtceu.api.recipe.ingredient.IRangedIngredient; import com.gregtechceu.gtceu.api.recipe.ingredient.IntProviderFluidIngredient; import com.gregtechceu.gtceu.api.registry.GTRegistries; import com.gregtechceu.gtceu.common.data.GTRecipeCapabilities; @@ -400,7 +401,8 @@ public FluidIngredientJS(Fluid fluid, int amount, @Nullable CompoundTag nbt) { @Override public long kjs$getAmount() { - return ingredient.getAmount(); + return (ingredient instanceof IRangedIngredient ranged ? ranged.getMaxRoll() : + ingredient.getAmount()); } @Override 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 index 3ebc017169d..7b129e3ce6c 100644 --- a/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderFluidIngredientTest.java +++ b/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderFluidIngredientTest.java @@ -52,6 +52,8 @@ public class IntProviderFluidIngredientTest { // 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 CR_TICK_IN = GTMaterials.Silicon.getFluid(1); + private static final FluidStack CR_TICK_OUT = GTMaterials.Cobalt.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); @@ -59,6 +61,7 @@ public class IntProviderFluidIngredientTest { 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); + 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 @@ -84,7 +87,7 @@ public static void prepare(ServerLevel level) { centHandler.beginStaging(); CRHandler.addStaging(CR_RECIPE_TYPE - .recipeBuilder(GTCEu.id("test_ranged_input_fluid_cr")) + .recipeBuilder("test_ranged_input_fluid_cr") .inputFluidsRanged(CR_IN, UniformInt.of(0, 9)) .inputItems(COBBLE) .outputFluids(REDSTONE) @@ -93,7 +96,7 @@ public static void prepare(ServerLevel level) { .buildRawRecipe()); CRHandler.addStaging(CR_RECIPE_TYPE - .recipeBuilder(GTCEu.id("test_ranged_output_fluid_cr")) + .recipeBuilder("test_ranged_output_fluid_cr") .inputFluids(CR_OUT) .outputFluidsRanged(REDSTONE, UniformInt.of(0, 9)) .EUt(GTValues.V[GTValues.HV]) @@ -101,7 +104,7 @@ public static void prepare(ServerLevel level) { .buildRawRecipe()); LCRHandler.addStaging(LCR_RECIPE_TYPE - .recipeBuilder(GTCEu.id("test_ranged_input_fluid_lcr")) + .recipeBuilder("test_ranged_input_fluid_lcr") .inputFluidsRanged(LCR_IN, UniformInt.of(0, 9)) .inputFluids(RUBBER) .outputFluids(REDSTONE) @@ -110,7 +113,7 @@ public static void prepare(ServerLevel level) { .buildRawRecipe()); LCRHandler.addStaging(LCR_RECIPE_TYPE - .recipeBuilder(GTCEu.id("test_ranged_output_fluid_lcr")) + .recipeBuilder("test_ranged_output_fluid_lcr") .inputFluids(LCR_OUT) .outputFluidsRanged(REDSTONE, UniformInt.of(0, 9)) .EUt(GTValues.V[GTValues.HV]) @@ -118,7 +121,7 @@ public static void prepare(ServerLevel level) { .buildRawRecipe()); centHandler.addStaging(CENTRIFUGE_RECIPE_TYPE - .recipeBuilder(GTCEu.id("test_ranged_input_fluid_cent")) + .recipeBuilder("test_ranged_input_fluid_cent") .inputFluidsRanged(LCENT_IN, UniformInt.of(0, 40)) .inputItems(COBBLE) .outputFluids(REDSTONE) @@ -127,13 +130,55 @@ public static void prepare(ServerLevel level) { .buildRawRecipe()); centHandler.addStaging(CENTRIFUGE_RECIPE_TYPE - .recipeBuilder(GTCEu.id("test_ranged_output_fluid_cent")) + .recipeBuilder("test_ranged_output_fluid_cent") .inputFluids(LCENT_OUT) .outputFluidsRanged(REDSTONE, UniformInt.of(0, 40)) .EUt(GTValues.V[GTValues.IV]) .duration(4) .buildRawRecipe()); + CRHandler.addStaging(CR_RECIPE_TYPE + .recipeBuilder("test_ranged_tick_input_fluid_cr") + .perTick(true) + .inputFluidsRanged(CR_TICK_IN, UniformInt.of(0, 9)) + .perTick(false) + .inputItems(COBBLE) + .outputItems(STONE) + .EUt(GTValues.V[GTValues.HV]) + .duration(7) + .buildRawRecipe()); + + CRHandler.addStaging(CR_RECIPE_TYPE + .recipeBuilder("test_ranged_tick_output_fluid_cr") + .inputItems(STONE) + .perTick(true) + .outputFluidsRanged(CR_TICK_OUT, UniformInt.of(0, 9)) + .perTick(false) + .EUt(GTValues.V[GTValues.HV]) + .duration(7) + .buildRawRecipe()); + + centHandler.addStaging(CENTRIFUGE_RECIPE_TYPE + .recipeBuilder("test_ranged_tick_input_fluid_cent") + .perTick(true) + .inputFluidsRanged(CR_TICK_IN, UniformInt.of(0, 9)) + .perTick(false) + .inputItems(COBBLE) + .outputItems(STONE) + .EUt(GTValues.V[GTValues.IV]) + .duration(4) + .buildRawRecipe()); + + centHandler.addStaging(CENTRIFUGE_RECIPE_TYPE + .recipeBuilder("test_ranged_tick_output_fluid_cent") + .inputFluids(CR_TICK_OUT) + .perTick(true) + .outputFluidsRanged(CR_TICK_OUT, UniformInt.of(0, 9)) + .perTick(false) + .EUt(GTValues.V[GTValues.IV]) + .duration(4) + .buildRawRecipe()); + CRHandler.completeStaging(); LCRHandler.completeStaging(); centHandler.completeStaging(); @@ -217,120 +262,23 @@ public static void rangedFluidIngredientTestEqualTest(GameTestHelper helper) { @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(); + + // This will print a "Cannot get stacks" warning to the log. Ignore it. + GTCEu.LOGGER.warn("This test will warn that it cannot get stacks. This is supposed to happen."); + helper.assertTrue(ingredient.getStacks().length == 0, + "A ranged fluid ingredient should not return fluids!"); + GTCEu.LOGGER.warn("If you are reading this line it means the test passed."); + + ingredient.rollSampledCount(); + var stacks = ingredient.collapse().getStacks(); helper.assertTrue(stacks.length == 1, - "IntProviderFluidIngredient should only return 1 fluid when made with 1 fluid"); + "Replaced 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.reset(); - helper.assertFalse(stacks[0].isFluidStackIdentical(ingredient.getStacks()[0]), - "IntProviderFluidIngredient.getStacks should have changed after rerolling"); - helper.succeed(); - } + "Replaced IntProviderFluidIngredient should have fluid equal to what it was made with"); - // 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); - - fluidIn.setFluidInTank(0, new FluidStack(CR_OUT, REPLICAS)); - // 1t to turn on, 2t per recipe run - // get the result of each roll independently - int[] addedRolls = new int[REPLICAS]; - - helper.runAfterDelay(2, () -> { - if (machine.getRecipeLogic().getLastRecipe().getOutputContents(FluidRecipeCapability.CAP).get(0) - .content() 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 < REPLICAS; 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(REPLICAS * 2 + 1, () -> { - FluidStack results = fluidOut.getFluidInTank(0); - helper.assertFalse((results.getAmount() == REPLICAS * 0), - "Sabotaged Singleblock CR rolled min value on every roll! " + - "This is the failure this sabotage was intended to induce."); - helper.assertFalse((results.getAmount() == REPLICAS * 9), - "Sabotaged Singleblock CR rolled max value on every roll (how??)"); - helper.assertTrue(TestUtils.isFluidWithinRange(results, REPLICAS, REPLICAS * 9), - "Sabotaged Singleblock CR didn't produce correct number of fluids, produced [" + - results.getAmount() + "] not [" + REPLICAS + "-" + (REPLICAS * 9) + "]"); - - // check if all the rolls were equal, but not min/max - int[] rolls = new int[REPLICAS]; - rolls[0] = addedRolls[0]; - boolean allEqual = false; - for (int i = 1; i < REPLICAS; 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") @@ -379,7 +327,6 @@ public static void singleblockRangedFluidOutputPreroll(GameTestHelper helper) { fluidIn.setFluidInTank(0, new FluidStack(CR_OUT, REPLICAS)); // 1t to turn on, 2t per recipe run // get the result of each preroll independently - int[] prerolls = new int[REPLICAS]; for (int i = 0; i < REPLICAS; i++) { final int finalI = i; // lambda preserve you helper.runAfterDelay(2 * i + 1, () -> { @@ -388,31 +335,12 @@ public static void singleblockRangedFluidOutputPreroll(GameTestHelper helper) { var outputPrerolls = machine.recipeLogic.getLastRecipe().outputs.get(FluidRecipeCapability.CAP); helper.assertFalse(outputPrerolls.size() == 0, "Singleblock fluid CR Preroll's recipe output contained no fluids!"); - prerolls[finalI] = ((IRangedIngredient) (outputPrerolls.get(0).content())).getAmount();; + helper.assertFalse(outputPrerolls.get(0).content() instanceof IRangedIngredient, + "Singleblock fluid CR Preroll's recipe failed to preroll and replace its " + + "ranged ingredient!"); }); } - // get the result of each roll independently - int[] addedRolls = new int[REPLICAS]; - for (int i = 0; i < REPLICAS; 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(REPLICAS * 2 + 10, () -> { - // check if all the rolls were equal, but not min/max - int[] rolls = new int[REPLICAS]; - rolls[0] = addedRolls[0]; - helper.assertFalse(prerolls[0] != rolls[0], "Singleblock fluid CR Preroll failed on run 0"); - - for (int i = 1; i < REPLICAS; i++) { - rolls[i] = addedRolls[i] - addedRolls[i - 1]; - helper.assertFalse(prerolls[i] != rolls[i], - "Singleblock fluid CR Preroll failed on run [" + i + "]"); - } - helper.succeed(); - }); + TestUtils.succeedAfterTest(helper); } // Test for singleblock machine with ranged fluid input @@ -1100,7 +1028,6 @@ public static void multiblockLCentRangedFluidOutputPreroll16ParallelBatched(Game BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper); final NotifiableFluidTank fluidIn = busHolder.inputHatch1.tank; - final NotifiableFluidTank fluidOut = busHolder.outputHatch1.tank; int batches = 16; int parallels = 16; @@ -1110,7 +1037,6 @@ public static void multiblockLCentRangedFluidOutputPreroll16ParallelBatched(Game fluidIn.setFluidInTank(0, new FluidStack(LCENT_OUT, batches * parallels)); // 1t to turn on, 64t per recipe run, 10t buffer for sanity - int[] prerolls = new int[MULTI_REPLICAS]; for (int i = 0; i < MULTI_REPLICAS; i++) { final int finalI = i; // lambda preserve you helper.runAfterDelay(75 * finalI + 20, () -> { @@ -1120,31 +1046,234 @@ public static void multiblockLCentRangedFluidOutputPreroll16ParallelBatched(Game .get(FluidRecipeCapability.CAP); helper.assertFalse(outputPrerolls.size() == 0, "Multiblock LCent fluid Preroll's recipe output contained no fluids!"); - prerolls[finalI] = ((IRangedIngredient) (outputPrerolls.get(0).content())).getAmount();; + helper.assertFalse(outputPrerolls.get(0).content() instanceof IRangedIngredient, + "Multiblock LCent fluid Preroll's recipe failed to preroll and replace its " + + "ranged ingredient!"); + // reset for a rerun + fluidIn.setFluidInTank(0, new FluidStack(LCENT_OUT, batches * parallels)); + }); + } + TestUtils.succeedAfterTest(helper); + } + + // Test for singleblock machine with per-tick ranged Fluid input + @GameTest(template = "singleblock_charged_cr", batch = "RangedFluidIngredients") + public static void singleblockRangedTickFluidInput(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); + NotifiableFluidTank fluidIn = (NotifiableFluidTank) machine + .getCapabilitiesFlat(IO.IN, FluidRecipeCapability.CAP).get(0); + NotifiableFluidTank fluidOut = (NotifiableFluidTank) machine + .getCapabilitiesFlat(IO.OUT, FluidRecipeCapability.CAP).get(0); + + fluidIn.setFluidInTank(0, new FluidStack(CR_TICK_IN, 64)); + itemIn.setStackInSlot(1, COBBLE.copyWithCount(1)); + // 1t to turn on, 7t recipe run + // get the result of each roll independently + int[] addedRolls = new int[7]; + for (int i = 0; i < 7; i++) { + final int finalI = i; // lambda preserve you + helper.runAfterDelay(i + 2, () -> { + addedRolls[finalI] = fluidIn.getFluidInTank(0).getAmount(); }); } // check the results of all rolls together - // repeat recipe MULTI_REPLICAS times - int[] addedRolls = new int[MULTI_REPLICAS]; - for (int i = 1; i <= MULTI_REPLICAS; i++) { + helper.runAfterDelay(7 + 5, () -> { + FluidStack results = fluidIn.getFluidInTank(0); + int upperLimit = 64 - (7 * 0); + int lowerLimit = 64 - (7 * 9); + helper.assertTrue(TestUtils.isItemStackEqual(itemOut.getStackInSlot(0), STONE.copyWithCount(1)), + "Singleblock per-tick fluid CR didn't complete correct number of recipes, completed [" + + itemOut.getStackInSlot(0).getCount() + "] not [" + 1 + "]"); + helper.assertFalse((results.getAmount() == lowerLimit), + "Singleblock per-tick fluid CR rolled max value on every roll"); + helper.assertFalse((results.getAmount() == upperLimit), + "Singleblock per-tick fluid CR rolled min value on every roll"); + + // check if all the rolls were equal, but not min/max + int[] rolls = new int[7]; + rolls[0] = 64 - addedRolls[0]; + boolean allEqual = false; + for (int i = 1; i < 7; i++) { + rolls[i] = addedRolls[i - 1] - addedRolls[i]; + if (rolls[i] == rolls[i - 1]) { + allEqual = true; + } else { + allEqual = false; + break; + } + } + helper.assertFalse(allEqual, + "Singleblock per-tick fluid CR rolled the same value on every input roll (rolled " + rolls[0] + + ")"); + helper.succeed(); + }); + } + + // Test for singleblock machine with per-tick ranged Fluid output + @GameTest(template = "singleblock_charged_cr", batch = "RangedFluidIngredients") + public static void singleblockRangedTickFluidOutput(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); + NotifiableFluidTank fluidIn = (NotifiableFluidTank) machine + .getCapabilitiesFlat(IO.IN, FluidRecipeCapability.CAP).get(0); + NotifiableFluidTank fluidOut = (NotifiableFluidTank) machine + .getCapabilitiesFlat(IO.OUT, FluidRecipeCapability.CAP).get(0); + + itemIn.setStackInSlot(0, STONE.copyWithCount(1)); + // 1t to turn on, 2t per recipe run + // get the result of each roll independently + int[] addedRolls = new int[7]; + for (int i = 0; i < 7; i++) { final int finalI = i; // lambda preserve you - helper.runAfterDelay(75 * finalI, () -> { - addedRolls[finalI - 1] = fluidOut.getFluidInTank(0).getAmount(); - // reset for a rerun - fluidIn.setFluidInTank(0, new FluidStack(LCENT_OUT, batches * parallels)); + helper.runAfterDelay(i + 2, () -> { + machine.getRecipeTypes(); + addedRolls[finalI] = fluidOut.getFluidInTank(0).getAmount(); }); } + // check the results of all rolls together + helper.runAfterDelay(7 + 5, () -> { + FluidStack results = fluidOut.getFluidInTank(0); + helper.assertFalse((results.getAmount() == 7 * 9), + "Singleblock per-tick CR rolled max value on every roll"); + helper.assertFalse((results.getAmount() == 7 * 0), + "Singleblock per-tick CR rolled min value on every roll"); - helper.runAfterDelay(1 + 75 * MULTI_REPLICAS, () -> { - int[] rolls = new int[MULTI_REPLICAS]; + // check if all the rolls were equal, but not min/max + int[] rolls = new int[7]; rolls[0] = addedRolls[0]; - helper.assertFalse(prerolls[0] != rolls[0], "Multiblock LCent fluid Preroll failed on run 0"); - - for (int i = 1; i < MULTI_REPLICAS; i++) { + boolean allEqual = false; + for (int i = 1; i < 7; i++) { rolls[i] = addedRolls[i] - addedRolls[i - 1]; - helper.assertFalse(prerolls[i] != rolls[i], - "Multiblock LCent fluid Preroll failed on run [" + i + "]"); + if (rolls[i] == rolls[i - 1]) { + allEqual = true; + } else { + allEqual = false; + break; + } } + helper.assertFalse(allEqual, + "Singleblock per-tick CR rolled the same value on every input roll (rolled " + rolls[0] + ")"); + helper.succeed(); + }); + } + + // test for multiblock machine with Batching and 16x Parallels with per-tick ranged Fluid input + @GameTest(template = "large_centrifuge_zpm_batch_parallel16", + batch = "RangedFluidIngredients") + public static void multiblockLCentRangedTickFluidInput16ParallelBatched(GameTestHelper helper) { + BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper); + + NotifiableItemStackHandler itemIn = busHolder.inputBus1.getInventory(); + NotifiableItemStackHandler itemOut = busHolder.outputBus1.getInventory(); + NotifiableFluidTank fluidIn = busHolder.inputHatch1.tank; + NotifiableFluidTank fluidOut = busHolder.outputHatch1.tank; + + int batches = 16; + int parallels = 16; + busHolder.controller.setBatchEnabled(true); + busHolder.parallelHatch.setCurrentParallel(parallels); + + int j; + int stacks = batches * parallels / 64; + final int amount = batches * parallels * 64 * 9; + + for (j = 0; j < stacks; j++) { + itemIn.setStackInSlot(j, COBBLE.copyWithCount((batches * parallels / stacks))); + } + fluidIn.setFluidInTank(0, new FluidStack(CR_TICK_IN, amount)); + + // 1t to turn on, 64t recipe run + // 16 parallels 16 batches + int[] rolls = new int[64]; + for (int i = 1; i <= 64; i++) { + final int finalI = i; // lambda preserve you + helper.runAfterDelay(finalI, () -> { + rolls[finalI - 1] = (int) fluidIn.getTotalContentAmount(); + }); + } + + helper.runAfterDelay(75, () -> { + // check if each roll was a multiple of run count + boolean sus = true; + for (int i = 0; i < rolls.length; i++) { + if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) { + GTCEu.LOGGER.warn("Batched Parallel LCent ranged tick fluid 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 if (sus) { + sus = false; + break; + } + } + + helper.assertFalse(sus, "Batched Parallel LCent ranged tick fluid input test rolled exactly even to" + + " Batch * Parallel count on every iteration"); + helper.succeed(); + }); + } + + // test for multiblock machine with Batching 16x Parallels with per-tick ranged Fluid output + @GameTest(template = "large_centrifuge_zpm_batch_parallel16", + batch = "RangedFluidIngredients") + public static void multiblockLCentRangedTickFluidOutput16ParallelBatched(GameTestHelper helper) { + BusHolderBatchParallel busHolder = getBussesAndFormLCENT(helper); + + NotifiableItemStackHandler itemIn = busHolder.inputBus1.getInventory(); + NotifiableItemStackHandler itemOut = busHolder.outputBus1.getInventory(); + NotifiableFluidTank fluidIn = busHolder.inputHatch1.tank; + NotifiableFluidTank fluidOut = busHolder.outputHatch1.tank; + + int batches = 16; + int parallels = 16; + busHolder.controller.setBatchEnabled(true); + busHolder.parallelHatch.setCurrentParallel(parallels); + int amount = batches * parallels; + + fluidIn.setFluidInTank(0, new FluidStack(CR_TICK_OUT, amount)); + + // 1t to turn on, 64t per recipe run, 10t buffer for sanity + // 16 parallels + // check the results of all rolls together + // repeat recipe MULTI_REPLICAS times + int[] rolls = new int[64]; + for (int i = 1; i <= 64; i++) { + final int finalI = i; // lambda preserve you + helper.runAfterDelay(finalI, () -> { + rolls[finalI - 1] = (int) fluidOut.getTotalContentAmount(); + }); + } + + helper.runAfterDelay(75, () -> { + // check if each roll was a multiple of run count + boolean sus = true; + for (int i = 0; i < rolls.length; i++) { + if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) { + GTCEu.LOGGER.warn("Batched Parallel LCent ranged tick fluid output test iteration " + i + + " 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 if (sus) { + sus = false; + break; + } + } + + helper.assertFalse(sus, "Batched Parallel LCent ranged tick 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 index 4cb04a544e4..1d7154ce2d4 100644 --- a/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderIngredientTest.java +++ b/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderIngredientTest.java @@ -48,6 +48,8 @@ public class IntProviderIngredientTest { // 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 CR_TICK_IN = new ItemStack(Items.ORANGE_STAINED_GLASS); + private static final ItemStack CR_TICK_OUT = new ItemStack(Items.BRICKS); 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); @@ -78,17 +80,17 @@ public static void prepare(ServerLevel level) { var centHandler = CENTRIFUGE_RECIPE_TYPE.getAdditionHandler(); centHandler.beginStaging(); - CRHandler.addStaging(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()); + CRHandler.addStaging( + CR_RECIPE_TYPE.recipeBuilder("test_ranged_input_item_cr") + .inputItemsRanged(CR_IN, UniformInt.of(0, 9)) + .inputItems(COBBLE) + .outputItems(STONE) + .EUt(GTValues.V[GTValues.HV]) + .duration(2) + .buildRawRecipe()); CRHandler.addStaging(CR_RECIPE_TYPE - .recipeBuilder(GTCEu.id("test_ranged_output_item_cr")) + .recipeBuilder("test_ranged_output_item_cr") .inputItems(CR_OUT) .outputItemsRanged(STONE, UniformInt.of(0, 9)) .EUt(GTValues.V[GTValues.HV]) @@ -96,7 +98,7 @@ public static void prepare(ServerLevel level) { .buildRawRecipe()); LCRHandler.addStaging(LCR_RECIPE_TYPE - .recipeBuilder(GTCEu.id("test_ranged_input_item_lcr")) + .recipeBuilder("test_ranged_input_item_lcr") .inputItemsRanged(LCR_IN, UniformInt.of(0, 9)) .inputItems(COBBLE) .outputItems(STONE) @@ -105,7 +107,7 @@ public static void prepare(ServerLevel level) { .buildRawRecipe()); LCRHandler.addStaging(LCR_RECIPE_TYPE - .recipeBuilder(GTCEu.id("test_ranged_output_item_lcr")) + .recipeBuilder("test_ranged_output_item_lcr") .inputItems(LCR_OUT) .outputItemsRanged(STONE, UniformInt.of(0, 9)) .EUt(GTValues.V[GTValues.HV]) @@ -113,7 +115,7 @@ public static void prepare(ServerLevel level) { .buildRawRecipe()); centHandler.addStaging(CENTRIFUGE_RECIPE_TYPE - .recipeBuilder(GTCEu.id("test_ranged_input_item_cent")) + .recipeBuilder("test_ranged_input_item_cent") .inputItemsRanged(LCENT_IN, UniformInt.of(0, 4)) .inputItems(COBBLE) .outputItems(STONE) @@ -122,13 +124,55 @@ public static void prepare(ServerLevel level) { .buildRawRecipe()); centHandler.addStaging(CENTRIFUGE_RECIPE_TYPE - .recipeBuilder(GTCEu.id("test_ranged_output_item_cent")) + .recipeBuilder("test_ranged_output_item_cent") .inputItems(LCENT_OUT) .outputItemsRanged(STONE, UniformInt.of(0, 4)) .EUt(GTValues.V[GTValues.IV]) .duration(4) .buildRawRecipe()); + CRHandler.addStaging(CR_RECIPE_TYPE + .recipeBuilder("test_ranged_tick_input_item_cr") + .perTick(true) + .inputItemsRanged(CR_TICK_IN, UniformInt.of(0, 4)) + .perTick(false) + .inputItems(COBBLE) + .outputItems(STONE) + .EUt(GTValues.V[GTValues.HV]) + .duration(7) + .buildRawRecipe()); + + CRHandler.addStaging(CR_RECIPE_TYPE + .recipeBuilder("test_ranged_tick_output_item_cr") + .inputItems(CR_TICK_OUT) + .perTick(true) + .outputItemsRanged(STONE, UniformInt.of(0, 4)) + .perTick(false) + .EUt(GTValues.V[GTValues.HV]) + .duration(7) + .buildRawRecipe()); + + centHandler.addStaging(CENTRIFUGE_RECIPE_TYPE + .recipeBuilder("test_ranged_tick_input_item_cent") + .perTick(true) + .inputItemsRanged(CR_TICK_IN, UniformInt.of(0, 4)) + .perTick(false) + .inputItems(COBBLE) + .outputItems(STONE) + .EUt(GTValues.V[GTValues.IV]) + .duration(4) + .buildRawRecipe()); + + centHandler.addStaging(CENTRIFUGE_RECIPE_TYPE + .recipeBuilder("test_ranged_tick_output_item_cent") + .inputItems(CR_TICK_OUT) + .perTick(true) + .outputItemsRanged(STONE, UniformInt.of(0, 4)) + .perTick(false) + .EUt(GTValues.V[GTValues.IV]) + .duration(4) + .buildRawRecipe()); + CRHandler.completeStaging(); LCRHandler.completeStaging(); centHandler.completeStaging(); @@ -208,122 +252,25 @@ public static void rangedIngredientTestEqualTest(GameTestHelper helper) { helper.succeed(); } - // test for IntProviderIngredient.getStacks() + // test for IntProviderIngredient.replace().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.reset(); - 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); - - itemIn.setStackInSlot(0, CR_OUT.copyWithCount(REPLICAS)); - // 1t to turn on, 2t per recipe run - // get the result of each roll independently - int[] addedRolls = new int[REPLICAS]; - helper.runAfterDelay(2, () -> { - if (machine.getRecipeLogic().getLastRecipe().getOutputContents(ItemRecipeCapability.CAP).get(0) - .content() 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 < REPLICAS; 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(REPLICAS * 2 + 1, () -> { - ItemStack results = itemOut.getStackInSlot(0); - helper.assertFalse((results.getCount() == REPLICAS * 0), - "Sabotaged Singleblock CR rolled min value on every roll! " + - "This is the failure this sabotage was intended to induce."); - helper.assertFalse((results.getCount() == REPLICAS * 9), - "Sabotaged Singleblock CR rolled max value on every roll (how??)"); + // This will print a "Cannot get items" warning to the log. Ignore it. + GTCEu.LOGGER.warn("This test will warn that it cannot get items. This is supposed to happen."); + helper.assertTrue(ingredient.getItems().length == 0, + "A ranged ingredient should not return items!"); + GTCEu.LOGGER.warn("If you are reading this line it means the test passed."); - helper.assertTrue(TestUtils.isItemWithinRange(results, REPLICAS, REPLICAS * 9), - "Sabotaged Singleblock CR didn't produce correct number of items, produced [" + - results.getCount() + "] not [" + REPLICAS + "-" + (REPLICAS * 9) + "]"); + ingredient.rollSampledCount(); + var stacks = ingredient.collapse().getItems(); + helper.assertTrue(stacks.length == 1, + "Replaced IntProviderIngredient should only return 1 item when made with 1 item"); + helper.assertTrue(stacks[0].is(new ItemStack(Items.BRICK, 1).getItem()), + "Replaced IntProviderIngredient should have item equal to what it was made with"); - // check if all the rolls were equal, but not min/max - int[] rolls = new int[REPLICAS]; - rolls[0] = addedRolls[0]; - boolean allEqual = false; - for (int i = 1; i < REPLICAS; 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(); - }); + helper.succeed(); } // Failure Test for singleblock machine with ranged item input @@ -372,7 +319,6 @@ public static void singleblockRangedItemOutputPreroll(GameTestHelper helper) { itemIn.setStackInSlot(0, CR_OUT.copyWithCount(REPLICAS)); // 1t to turn on, 2t per recipe run // get the result of each preroll independently - int[] prerolls = new int[REPLICAS]; for (int i = 0; i < REPLICAS; i++) { final int finalI = i; // lambda preserve you helper.runAfterDelay(2 * i + 1, () -> { @@ -381,30 +327,12 @@ public static void singleblockRangedItemOutputPreroll(GameTestHelper helper) { var outputPrerolls = machine.recipeLogic.getLastRecipe().outputs.get(ItemRecipeCapability.CAP); helper.assertFalse(outputPrerolls.size() == 0, "Singleblock item CR Preroll's recipe output contained no items!"); - prerolls[finalI] = ((IRangedIngredient) (outputPrerolls.get(0).content())).getAmount();; - }); - } - // get the result of each roll independently - int[] addedRolls = new int[REPLICAS]; - for (int i = 0; i < REPLICAS; i++) { - final int finalI = i; // lambda preserve you - helper.runAfterDelay(2 * i + 3, () -> { - addedRolls[finalI] = itemOut.getStackInSlot(0).getCount(); + helper.assertFalse(outputPrerolls.get(0).content() instanceof IRangedIngredient, + "Singleblock item CR Preroll's recipe failed to preroll and replace its " + + "ranged ingredient!"); }); } - // check the results of all rolls together - helper.runAfterDelay(REPLICAS * 2 + 10, () -> { - // check if all the rolls matched their preroll - int[] rolls = new int[REPLICAS]; - rolls[0] = addedRolls[0]; - helper.assertFalse(prerolls[0] != rolls[0], "Singleblock item CR Preroll failed on run 0"); - for (int i = 1; i < REPLICAS; i++) { - rolls[i] = addedRolls[i] - addedRolls[i - 1]; - helper.assertFalse(prerolls[i] != rolls[i], - "Singleblock CR Preroll failed on run [" + i + "]"); - } - helper.succeed(); - }); + TestUtils.succeedAfterTest(helper); } // Test for singleblock machine with ranged item input @@ -1107,7 +1035,6 @@ public static void multiblockLCentRangedItemOutputPreroll16ParallelBatched(GameT // 1t to turn on, 64t per recipe run, 10t buffer for sanity // 16 parallels - int[] prerolls = new int[MULTI_REPLICAS]; for (int i = 0; i < MULTI_REPLICAS; i++) { final int finalI = i; // lambda preserve you helper.runAfterDelay(75 * finalI + 20, () -> { @@ -1117,17 +1044,9 @@ public static void multiblockLCentRangedItemOutputPreroll16ParallelBatched(GameT .get(ItemRecipeCapability.CAP); helper.assertFalse(outputPrerolls.size() == 0, "Multiblock LCent item Preroll's recipe output contained no items!"); - prerolls[finalI] = ((IRangedIngredient) (outputPrerolls.get(0).content())).getAmount();; - }); - } - // check the results of all rolls together - // repeat recipe MULTI_REPLICAS times - int[] rolls = new int[MULTI_REPLICAS]; - for (int i = 1; i <= MULTI_REPLICAS; i++) { - final int finalI = i; // lambda preserve you - helper.runAfterDelay(75 * finalI, () -> { - int resultCount = (int) Math.round(itemOut.getTotalContentAmount()); - rolls[finalI - 1] = resultCount; + helper.assertFalse(outputPrerolls.get(0).content() instanceof IRangedIngredient, + "Multiblock LCent item Preroll's recipe failed to preroll and replace its " + + "ranged ingredient!"); // reset for a rerun for (int j = 0; j < batches; j++) { @@ -1139,15 +1058,218 @@ public static void multiblockLCentRangedItemOutputPreroll16ParallelBatched(GameT } }); } + TestUtils.succeedAfterTest(helper); + } - helper.runAfterDelay(1 + 75 * MULTI_REPLICAS, () -> { + // Test for singleblock machine with per-tick ranged item input + @GameTest(template = "singleblock_charged_cr", batch = "RangedIngredients") + public static void singleblockRangedTickItemInput(GameTestHelper helper) { + SimpleTieredMachine machine = (SimpleTieredMachine) getMetaMachine( + helper.getBlockEntity(new BlockPos(0, 1, 0))); - helper.assertFalse(prerolls[0] != rolls[0], "Multiblock LCent item Preroll failed on run 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); - for (int i = 1; i < REPLICAS; i++) { - helper.assertFalse(prerolls[i] != rolls[i], - "Multiblock LCent item Preroll failed on run [" + i + "]"); + itemIn.setStackInSlot(0, CR_TICK_IN.copyWithCount(64)); + itemIn.setStackInSlot(1, COBBLE.copyWithCount(1)); + // 1t to turn on, 7t recipe run + // get the result of each roll independently + int[] addedRolls = new int[7]; + for (int i = 0; i < 7; i++) { + final int finalI = i; // lambda preserve you + helper.runAfterDelay(i + 2, () -> { + addedRolls[finalI] = itemIn.getStackInSlot(0).getCount(); + }); + } + // check the results of all rolls together + helper.runAfterDelay(7 + 5, () -> { + ItemStack results = itemIn.getStackInSlot(0); + int upperLimit = 64 - (7 * 0); + int lowerLimit = 64 - (7 * 9); + helper.assertTrue(TestUtils.isItemStackEqual(itemOut.getStackInSlot(0), STONE.copyWithCount(1)), + "Singleblock per-tick CR didn't complete correct number of recipes, completed [" + + itemOut.getStackInSlot(0).getCount() + "] not [" + 1 + "]"); + helper.assertFalse((results.getCount() == lowerLimit), + "Singleblock per-tick CR rolled max value on every roll"); + helper.assertFalse((results.getCount() == upperLimit), + "Singleblock per-tick CR rolled min value on every roll"); + + // check if all the rolls were equal, but not min/max + int[] rolls = new int[7]; + rolls[0] = 64 - addedRolls[0]; + boolean allEqual = false; + for (int i = 1; i < 7; i++) { + rolls[i] = addedRolls[i - 1] - addedRolls[i]; + if (rolls[i] == rolls[i - 1]) { + allEqual = true; + } else { + allEqual = false; + break; + } + } + helper.assertFalse(allEqual, + "Singleblock per-tick CR rolled the same value on every input roll (rolled " + rolls[0] + ")"); + helper.succeed(); + }); + } + + // Test for singleblock machine with per-tick ranged item output + @GameTest(template = "singleblock_charged_cr", batch = "RangedIngredients") + public static void singleblockRangedTickItemOutput(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); + + itemIn.setStackInSlot(0, CR_TICK_OUT.copyWithCount(1)); + // 1t to turn on, 2t per recipe run + // get the result of each roll independently + int[] addedRolls = new int[7]; + for (int i = 0; i < 7; i++) { + final int finalI = i; // lambda preserve you + helper.runAfterDelay(i + 2, () -> { + addedRolls[finalI] = itemOut.getStackInSlot(0).getCount(); + }); + } + // check the results of all rolls together + helper.runAfterDelay(7 + 5, () -> { + helper.assertTrue(itemIn.getStackInSlot(0).isEmpty(), + "Singleblock per-tick CR didn't complete correct number of recipes, completed [" + + itemIn.getStackInSlot(0).getCount() + "] not [" + 1 + "]"); + ItemStack results = itemOut.getStackInSlot(0); + helper.assertFalse((results.getCount() == 7 * 9), + "Singleblock per-tick CR rolled max value on every roll"); + helper.assertFalse((results.getCount() == 7 * 0), + "Singleblock per-tick CR rolled min value on every roll"); + + // check if all the rolls were equal, but not min/max + int[] rolls = new int[7]; + rolls[0] = addedRolls[0]; + boolean allEqual = false; + for (int i = 1; i < 7; i++) { + rolls[i] = addedRolls[i] - addedRolls[i - 1]; + if (rolls[i] == rolls[i - 1]) { + allEqual = true; + } else { + allEqual = false; + break; + } + } + helper.assertFalse(allEqual, + "Singleblock per-tick CR rolled the same value on every input roll (rolled " + rolls[0] + ")"); + helper.succeed(); + }); + } + + // test for multiblock machine with Batching and 16x Parallels with per-tick ranged item input + @GameTest(template = "large_centrifuge_zpm_batch_parallel16", + batch = "RangedIngredients") + public static void multiblockLCentRangedTickItemInput16ParallelBatched(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 < itemIn.getSlots(); k++) { + itemIn.setStackInSlot(k, CR_TICK_IN.copyWithCount(64)); + } + + // 1t to turn on, 64t recipe run + // 16 parallels 16 batches + int[] rolls = new int[64]; + for (int i = 1; i <= 64; i++) { + final int finalI = i; // lambda preserve you + helper.runAfterDelay(finalI, () -> { + rolls[finalI - 1] = (int) itemIn.getTotalContentAmount(); + }); + } + + helper.runAfterDelay(75, () -> { + // check if each roll was a multiple of run count + boolean sus = true; + for (int i = 0; i < rolls.length; i++) { + if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) { + GTCEu.LOGGER.warn("Batched Parallel LCent ranged tick 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 if (sus) { + sus = false; + break; + } + } + + helper.assertFalse(sus, "Batched Parallel LCent ranged tick item input test rolled exactly even to" + + " Batch * Parallel count on every iteration"); + helper.succeed(); + }); + } + + // test for multiblock machine with Batching 16x Parallels with per-tick ranged item output + @GameTest(template = "large_centrifuge_zpm_batch_parallel16", + batch = "RangedIngredients") + public static void multiblockLCentRangedTickItemOutput16ParallelBatched(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, CR_TICK_OUT.copyWithCount(16)); + } + + // 1t to turn on, 64t per recipe run, 10t buffer for sanity + // 16 parallels + // check the results of all rolls together + // repeat recipe MULTI_REPLICAS times + int[] rolls = new int[64]; + for (int i = 1; i <= 64; i++) { + final int finalI = i; // lambda preserve you + helper.runAfterDelay(finalI, () -> { + rolls[finalI - 1] = (int) itemOut.getTotalContentAmount(); + }); + } + + helper.runAfterDelay(75, () -> { + // check if each roll was a multiple of run count + boolean sus = true; + for (int i = 0; i < rolls.length; i++) { + if (TestUtils.isStackSizeExactlyEvenMultiple(rolls[i], batches, parallels, 1)) { + GTCEu.LOGGER.warn("Batched Parallel LCent ranged tick item output test iteration " + i + + " 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 if (sus) { + sus = false; + break; + } } + + helper.assertFalse(sus, "Batched Parallel LCent ranged tick item output test rolled exactly even to" + + " Batch * Parallel count on every iteration"); helper.succeed(); }); }