From ec720efb1aa33984d395dc6a67515fa1a6f189b0 Mon Sep 17 00:00:00 2001 From: Exa <11907282+Exaxxion@users.noreply.github.com> Date: Fri, 31 May 2024 04:05:01 -0700 Subject: [PATCH 1/7] Don't reset recipe progess on structure break --- .../api/capability/impl/MultiblockRecipeLogic.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java index 8866172b4..4fbf9f499 100644 --- a/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java +++ b/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java @@ -28,14 +28,7 @@ public void updateWorkable() { * Used to reset cached values in the Recipe Logic on structure deform */ public void invalidate() { - previousRecipe = null; - progressTime = 0; - maxProgressTime = 0; - recipeEUt = 0; - fluidOutputs = null; - itemOutputs = null; - isOutputsFull = false; - setActive(false); // this marks dirty for us + // this space intentionally left black } public IEnergyContainer getEnergyContainer() { From 95f484dc264bafb19df920ad276c44b7dad72084 Mon Sep 17 00:00:00 2001 From: Exa <11907282+Exaxxion@users.noreply.github.com> Date: Fri, 31 May 2024 04:24:54 -0700 Subject: [PATCH 2/7] Fix Output Voiding Bug Resolves regression where multiblocks were improperly voiding all outputs if there was insufficient output space for items upon recipe completion, caused by recording the pre-truncation outputs during recipe setup. Restores prior behavior where excess outputs are voided and those that can fit in available space (as determined during recipe setup) are produced as expected (e.g. with a ULV output bus, Hot Titanium will be produced but MgCl2 will be voided). --- .../java/gregtech/api/recipes/Recipe.java | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/main/java/gregtech/api/recipes/Recipe.java b/src/main/java/gregtech/api/recipes/Recipe.java index 7a288d27d..55f915902 100644 --- a/src/main/java/gregtech/api/recipes/Recipe.java +++ b/src/main/java/gregtech/api/recipes/Recipe.java @@ -236,15 +236,43 @@ public NonNullList getOutputs() { return outputs; } - public List getResultItemOutputs(int maxOutputSlots, Random random, int tier) { + /** + * Computes real outputs of a recipe, truncated to a maximum number of output slots. + * + * @param maxOutputSlots the number of output slots to consider + * @param random the Random to use for chanced recipe outputs + * @param overclocks the number of overclocks for applying tiered bonuses + * @return the outputs of the current iteration of running the recipe + */ + public List getResultItemOutputs(int maxOutputSlots, @NotNull Random random, int overclocks) { + assert maxOutputSlots >= 0; + assert overclocks >= 0; + + // Nothing to return if there are no output slots + if(maxOutputSlots == 0) + return Collections.emptyList(); + + // Get fixed and chanced outputs ArrayList outputs = new ArrayList<>(GTUtility.copyStackList(getOutputs())); List chancedOutputsList = getChancedOutputs(); + + // If there's enough fixed outputs to reach the max, return as many as will fit. + if (outputs.size() >= maxOutputSlots) + return outputs.subList(0, maxOutputSlots); + + // if there are no chanced outputs, then we can just return the standard outputs. + if(chancedOutputsList.isEmpty()) + return outputs; + + // Truncate the chanced outputs list to fit remaining available space int maxChancedSlots = maxOutputSlots - outputs.size(); - if (chancedOutputsList.size() > maxChancedSlots) { - chancedOutputsList = chancedOutputsList.subList(0, Math.max(0, maxChancedSlots)); - } + if (chancedOutputsList.size() > maxChancedSlots) + chancedOutputsList = chancedOutputsList.subList(0, maxChancedSlots); + + // Roll each chanced output to see if it is actually produced + final RecipeMap.IChanceFunction cf = RecipeMap.getChanceFunction(); for (ChanceEntry chancedOutput : chancedOutputsList) { - int outputChance = RecipeMap.getChanceFunction().chanceFor(chancedOutput.getChance(), chancedOutput.getBoostPerTier(), tier); + int outputChance = cf.chanceFor(chancedOutput.getChance(), chancedOutput.getBoostPerTier(), overclocks); if (random.nextInt(Recipe.getMaxChancedValue()) <= outputChance) { outputs.add(chancedOutput.getItemStack().copy()); } From 6fc66956a2fed0cac3bf403b4c280ed25221cd40 Mon Sep 17 00:00:00 2001 From: Exa <11907282+Exaxxion@users.noreply.github.com> Date: Fri, 31 May 2024 04:29:00 -0700 Subject: [PATCH 3/7] Minor refactoring inspired by CEu, convert some deprecated code --- .../api/capability/impl/AbstractRecipeLogic.java | 8 +++++++- src/main/java/gregtech/api/recipes/Recipe.java | 11 +++++++++++ .../electric/MetaTileEntityElectricBlastFurnace.java | 4 ++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java index fb4ad321f..d6c72b586 100755 --- a/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java +++ b/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java @@ -186,7 +186,7 @@ protected void trySearchNewRecipe() { IMultipleTankHandler importFluids = getInputTank(); // see if the last recipe we used still works - if (this.previousRecipe != null && this.previousRecipe.matches(false, importInventory, importFluids)) + if (checkPreviousRecipe()) currentRecipe = this.previousRecipe; // If there is no active recipe, then we need to find one. else { @@ -201,11 +201,17 @@ protected void trySearchNewRecipe() { // proceed if we have a usable recipe. if (currentRecipe != null && setupAndConsumeRecipeInputs(currentRecipe)) setupRecipe(currentRecipe); + // Inputs have been inspected. metaTileEntity.getNotifiedItemInputList().clear(); metaTileEntity.getNotifiedFluidInputList().clear(); } + protected boolean checkPreviousRecipe() { + if(this.previousRecipe == null) return false; + if(this.previousRecipe.getEUt() > this.getMaxVoltage()) return false; + return this.previousRecipe.matches(false, getInputInventory(), getInputTank()); + } protected int getMinTankCapacity(IMultipleTankHandler tanks) { if(tanks.getTanks() == 0) { diff --git a/src/main/java/gregtech/api/recipes/Recipe.java b/src/main/java/gregtech/api/recipes/Recipe.java index 55f915902..2ba68de49 100644 --- a/src/main/java/gregtech/api/recipes/Recipe.java +++ b/src/main/java/gregtech/api/recipes/Recipe.java @@ -332,6 +332,17 @@ public boolean hasValidInputsForDisplay() { return hasValidInputs; } + /** + * Retrieve a property or fallback value from the property store + * @param property the desired property + * @param defaultValue fallback value + * @param type of the property + * @return the requested property's value, or the fallback value if a value isn't available. + */ + public T getProperty(RecipeProperty property, T defaultValue) { + return recipePropertyStorage.getRecipePropertyValue(property, defaultValue); + } + //region RecipeProperties /** diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java index b98b37c73..2ad5f36be 100644 --- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java +++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java @@ -11,6 +11,7 @@ import gregtech.api.multiblock.PatternMatchContext; import gregtech.api.recipes.Recipe; import gregtech.api.recipes.RecipeMaps; +import gregtech.api.recipes.recipeproperties.*; import gregtech.api.render.ICubeRenderer; import gregtech.api.render.OrientedOverlayRenderer; import gregtech.api.render.Textures; @@ -68,8 +69,7 @@ public void invalidateStructure() { @Override public boolean checkRecipe(Recipe recipe, boolean consumeIfSuccess) { - int recipeRequiredTemp = recipe.getIntegerProperty("blast_furnace_temperature"); - return this.blastFurnaceTemperature >= recipeRequiredTemp; + return this.blastFurnaceTemperature >= recipe.getProperty(BlastTemperatureProperty.getInstance(),0); } public static Predicate heatingCoilPredicate() { From 6a34935125c94cad437f1e16696621cc34e68a5e Mon Sep 17 00:00:00 2001 From: Exa <11907282+Exaxxion@users.noreply.github.com> Date: Fri, 31 May 2024 04:52:06 -0700 Subject: [PATCH 4/7] Add "Jammed" state as a contingency for reformed structures Since recipes no longer clear when a structure is broken (so long as the controller isn't broken, anyway), a contingency is needed to avoid undefined behavior when the player reforms the structure with less output space than was available when the recipe was initialized. Structures will become "Jammed" if at the time of recipe completion, there is insufficient space for all outputs as computed during recipe setup. This behavior will enforce additional constraints via RecipeMapMultiblockController::checkRecipe(Recipe, boolean). While Jammed, crafting will pause at 100% and the controller GUI will indicate the structure cannot complete the recipe as configured. When the player reforms the structure appropriately, the Jammed status will clear and the craft will complete. --- .../impl/MultiblockRecipeLogic.java | 33 +++++++++++++++++++ .../RecipeMapMultiblockController.java | 5 +-- .../resources/assets/gregtech/lang/en_us.lang | 1 + 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java index 4fbf9f499..606707574 100644 --- a/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java +++ b/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java @@ -2,6 +2,7 @@ import gregtech.api.capability.IEnergyContainer; import gregtech.api.capability.IMultipleTankHandler; +import gregtech.api.metatileentity.MetaTileEntity; import gregtech.api.metatileentity.multiblock.MultiblockAbility; import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController; import gregtech.api.recipes.Recipe; @@ -11,6 +12,8 @@ public class MultiblockRecipeLogic extends AbstractRecipeLogic { + /** If a structure was reformed with insufficient output space, the structure is jammed. */ + protected boolean isJammed = false; public MultiblockRecipeLogic(RecipeMapMultiblockController tileEntity) { super(tileEntity, tileEntity.recipeMap); @@ -99,4 +102,34 @@ protected boolean drawEnergy(int recipeEUt) { protected long getMaxVoltage() { return Math.max(getEnergyContainer().getInputVoltage(), getEnergyContainer().getOutputVoltage()); } + + public boolean isJammed() { + return this.isJammed; + } + + // Handle case where structure was reformed with insufficient output space relative to start + @Override + protected void completeRecipe() { + + RecipeMapMultiblockController controller = (RecipeMapMultiblockController) metaTileEntity; + + // The structure is jammed if there's no room for the outputs computed when ingredients were consumed + this.isJammed = + !MetaTileEntity.addItemsToItemHandler(getOutputInventory(), true, itemOutputs) || + !MetaTileEntity.addFluidsToFluidHandler(getOutputTank(), true, fluidOutputs) || + !controller.checkRecipe(previousRecipe, false); + + // Finish the recipe only if the structure is not jammed. + if(!isJammed) + super.completeRecipe(); + } + + @Override + protected void updateRecipeProgress() { + // normal update + if(!isJammed) + super.updateRecipeProgress(); + else // retry recipe completion + completeRecipe(); + } } diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java index 9149b9633..4f94f0872 100644 --- a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java +++ b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java @@ -132,9 +132,10 @@ protected void addDisplayText(List textList) { textList.add(new TextComponentTranslation("gregtech.multiblock.max_energy_per_tick", maxVoltage, voltageName)); } - if (!recipeMapWorkable.isWorkingEnabled()) { + if(recipeMapWorkable.isJammed()) { + textList.add(new TextComponentTranslation("gregtech.multiblock.jammed")); + } else if (!recipeMapWorkable.isWorkingEnabled()) { textList.add(new TextComponentTranslation("gregtech.multiblock.work_paused")); - } else if (recipeMapWorkable.isActive()) { textList.add(new TextComponentTranslation("gregtech.multiblock.running")); int currentProgress = (int) (recipeMapWorkable.getProgressPercent() * 100); diff --git a/src/main/resources/assets/gregtech/lang/en_us.lang b/src/main/resources/assets/gregtech/lang/en_us.lang index ef3ad8d4d..fc1ba6ffb 100644 --- a/src/main/resources/assets/gregtech/lang/en_us.lang +++ b/src/main/resources/assets/gregtech/lang/en_us.lang @@ -2962,6 +2962,7 @@ gregtech.fluid_pipe.throughput=Transfer: §e%,d mb/t gregtech.fluid_pipe.max_temperature=Max Temperature: §c%,dK gregtech.fluid_pipe.non_gas_proof=Can't transfer gases. +gregtech.multiblock.jammed=Unable to complete the recipe with the current configuration. gregtech.multiblock.work_paused=Work Paused. gregtech.multiblock.running=Running perfectly. gregtech.multiblock.idling=Idling. From b25cf99b9c3bae9dae478706507dde56c5532ca5 Mon Sep 17 00:00:00 2001 From: Exa <11907282+Exaxxion@users.noreply.github.com> Date: Fri, 31 May 2024 19:51:55 -0700 Subject: [PATCH 5/7] Fix missing import for NotNull annotation --- src/main/java/gregtech/api/recipes/Recipe.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/gregtech/api/recipes/Recipe.java b/src/main/java/gregtech/api/recipes/Recipe.java index 2ba68de49..004f6a079 100644 --- a/src/main/java/gregtech/api/recipes/Recipe.java +++ b/src/main/java/gregtech/api/recipes/Recipe.java @@ -10,6 +10,7 @@ import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.items.IItemHandlerModifiable; import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.NotNull; import java.util.*; import java.util.stream.Collectors; From 53d4e12c1f337f9d63c61c321051e5b0acad231f Mon Sep 17 00:00:00 2001 From: Exa <11907282+Exaxxion@users.noreply.github.com> Date: Sat, 1 Jun 2024 04:27:51 -0700 Subject: [PATCH 6/7] Add translations for Jammed text - ru_ru translation provided by NotMyWing - zh_cn text was machine translated --- src/main/resources/assets/gregtech/lang/en_us.lang | 2 +- src/main/resources/assets/gregtech/lang/ru_ru.lang | 1 + src/main/resources/assets/gregtech/lang/zh_cn.lang | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/gregtech/lang/en_us.lang b/src/main/resources/assets/gregtech/lang/en_us.lang index fc1ba6ffb..a3143cb5e 100644 --- a/src/main/resources/assets/gregtech/lang/en_us.lang +++ b/src/main/resources/assets/gregtech/lang/en_us.lang @@ -2962,7 +2962,7 @@ gregtech.fluid_pipe.throughput=Transfer: §e%,d mb/t gregtech.fluid_pipe.max_temperature=Max Temperature: §c%,dK gregtech.fluid_pipe.non_gas_proof=Can't transfer gases. -gregtech.multiblock.jammed=Unable to complete the recipe with the current configuration. +gregtech.multiblock.jammed=§cUnable to complete the recipe with the current configuration. gregtech.multiblock.work_paused=Work Paused. gregtech.multiblock.running=Running perfectly. gregtech.multiblock.idling=Idling. diff --git a/src/main/resources/assets/gregtech/lang/ru_ru.lang b/src/main/resources/assets/gregtech/lang/ru_ru.lang index e29525b23..84a4a226f 100644 --- a/src/main/resources/assets/gregtech/lang/ru_ru.lang +++ b/src/main/resources/assets/gregtech/lang/ru_ru.lang @@ -2859,6 +2859,7 @@ gregtech.fluid_pipe.throughput=Пропускная способность: §e% gregtech.fluid_pipe.max_temperature=Максимальная температура: §c%dK gregtech.fluid_pipe.non_gas_proof=Не может переносить газы. +gregtech.multiblock.jammed=§cНевозможно завершить рецепт с текущей конфигурацией (машины). gregtech.multiblock.work_paused=Работа приостановлена. gregtech.multiblock.running=Работает отлично. gregtech.multiblock.idling=Холостой ход. diff --git a/src/main/resources/assets/gregtech/lang/zh_cn.lang b/src/main/resources/assets/gregtech/lang/zh_cn.lang index daa92aa8f..b42fc21cd 100644 --- a/src/main/resources/assets/gregtech/lang/zh_cn.lang +++ b/src/main/resources/assets/gregtech/lang/zh_cn.lang @@ -2838,6 +2838,7 @@ gregtech.fluid_pipe.throughput=传输速率: §e%d 升/刻 gregtech.fluid_pipe.max_temperature=温度上限: §c%d K gregtech.fluid_pipe.non_gas_proof=不能传输气体. +gregtech.multiblock.jammed=§c当前(机器)配置无法完成配方。 gregtech.multiblock.work_paused=暂停. gregtech.multiblock.running=运行正常. gregtech.multiblock.idling=待机. From b60f9c4f2fd03ae49e42fdb62df9b0901c7772c8 Mon Sep 17 00:00:00 2001 From: Exa <11907282+Exaxxion@users.noreply.github.com> Date: Sun, 2 Jun 2024 06:56:06 -0700 Subject: [PATCH 7/7] Jamming logic refinement, prevent race condition - Jammed checking now takes effect immediately after a structure is broken and reformed, instead of when the recipe finishes crafting. - This check will also occur when when the outputs notify of changes, as the standard Jammed state check is predicated on output space. - Prevent a race condition with shared multiblock parts feature, where structures with shared outputs have sufficient space during recipe setup, but when the recipe completes other machines may have reduced available space such that the outputs can no longer fit. - MultiblockRecipeLogic will now verify there is sufficient space for the recipe's outputs at the end of the craft before attempting to output them and finish the craft. If the check fails, the machine will jam until both space becomes available and structure-specific conditions via checkRecipe are satisfied. --- .../impl/MultiblockRecipeLogic.java | 69 ++++++++++++++----- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java index 606707574..e63062c41 100644 --- a/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java +++ b/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java @@ -2,7 +2,6 @@ import gregtech.api.capability.IEnergyContainer; import gregtech.api.capability.IMultipleTankHandler; -import gregtech.api.metatileentity.MetaTileEntity; import gregtech.api.metatileentity.multiblock.MultiblockAbility; import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController; import gregtech.api.recipes.Recipe; @@ -10,11 +9,16 @@ import java.util.List; +import static gregtech.api.metatileentity.MetaTileEntity.addItemsToItemHandler; +import static gregtech.api.metatileentity.MetaTileEntity.addFluidsToFluidHandler; + public class MultiblockRecipeLogic extends AbstractRecipeLogic { - /** If a structure was reformed with insufficient output space, the structure is jammed. */ + /** Indicates that a structure fails to meet requirements for proceeding with the active recipe */ protected boolean isJammed = false; + private boolean invalidated = true; + public MultiblockRecipeLogic(RecipeMapMultiblockController tileEntity) { super(tileEntity, tileEntity.recipeMap); } @@ -31,7 +35,7 @@ public void updateWorkable() { * Used to reset cached values in the Recipe Logic on structure deform */ public void invalidate() { - // this space intentionally left black + invalidated = true; } public IEnergyContainer getEnergyContainer() { @@ -107,29 +111,56 @@ public boolean isJammed() { return this.isJammed; } - // Handle case where structure was reformed with insufficient output space relative to start - @Override - protected void completeRecipe() { + private void checkIfJammed() { + if(metaTileEntity instanceof RecipeMapMultiblockController controller) { + // determine if outputs will fit + boolean canFitItems = addItemsToItemHandler(getOutputInventory(), true, itemOutputs); + boolean canFitFluids = addFluidsToFluidHandler(getOutputTank(), true, fluidOutputs); - RecipeMapMultiblockController controller = (RecipeMapMultiblockController) metaTileEntity; + // clear output notifications since we just checked them + metaTileEntity.getNotifiedItemOutputList().clear(); + metaTileEntity.getNotifiedFluidOutputList().clear(); - // The structure is jammed if there's no room for the outputs computed when ingredients were consumed - this.isJammed = - !MetaTileEntity.addItemsToItemHandler(getOutputInventory(), true, itemOutputs) || - !MetaTileEntity.addFluidsToFluidHandler(getOutputTank(), true, fluidOutputs) || - !controller.checkRecipe(previousRecipe, false); + // Jam if we can't output all items and fluids, or we fail whatever other conditions the controller imposes + this.isJammed = !(canFitItems && canFitFluids && controller.checkRecipe(previousRecipe, false)); + } + } - // Finish the recipe only if the structure is not jammed. - if(!isJammed) - super.completeRecipe(); + private boolean hasOutputChanged() { + return hasNotifiedOutputs() && + (!metaTileEntity.getNotifiedItemOutputList().isEmpty() || + !metaTileEntity.getNotifiedFluidOutputList().isEmpty()); } @Override protected void updateRecipeProgress() { - // normal update + // Recheck jammed status after the structure has been invalidated or output inventories were modified + if(invalidated || hasOutputChanged()) + checkIfJammed(); + + // Only proceed if we're not jammed if(!isJammed) - super.updateRecipeProgress(); - else // retry recipe completion - completeRecipe(); + // if the recipe is running + if(progressTime < maxProgressTime) { + // clear invalidation flag + invalidated = false; + // do normal update check + super.updateRecipeProgress(); + } else + // the recipe is done but was probably jammed. Try to complete it. + completeRecipe(); + } + + @Override + protected void completeRecipe() { + /* + Since multiblocks can share parts, if multiple machines try to output on the same tick and can't, + the excess outputs would be silently voided. Avoid this scenario by doing a final Jammed state check. + */ + checkIfJammed(); + + // If we're not jammed, proceed with completing the recipe. Otherwise, wait for outputs to notify. + if(!this.isJammed) + super.completeRecipe(); } }