From fe0387946550f89a403b09f4e8cf6e43ee8f2e8f Mon Sep 17 00:00:00 2001 From: Maxim Date: Mon, 10 Jul 2023 10:13:04 +0200 Subject: Generic processing logic (#2096) * Added enumeration for check recipe result * Rework processing logic to work with MTEs too * Switched first few multiblocks to new checkRecipe method * Applied generic logic to EBF * Added support for long power base and applied generic processing logic to more machines * Address some feedback * Added more setter to further configure the processing logic * Change internal checkRecipe to work with checkRecipeResult, to allow the injection of custom failure messages * Suppress warning, change access * Merge recipeMap and mapSupplier * Move calls to setMetaTEController and setRecipeMap to base classes * Make processingLogic final * Make results non-null * Rename `ProcessingLogic#checkRecipe` -> `#validateRecipe` Otherwise it's confusing with `GT_MetaTileEntity_MultiBlockBase#checkRecipe` * oops * Added recipe locking to generic processing logic * Rename: getWorldObject -> getIHasWorldObjectAndCoords * Annotate missing overrides * Renamed recipeLockableController -> recipeLockableMachine * Migrated Cleanroom * Migrated pyrolyse oven * Migrated driller * Migrated charcoal pit * Migrated DT * Rename: controller -> machine * Make recipemaps override base `findRecipe` and mark others final * Remove unused maps * Add FindRecipeResult * Remove IHasWorldObjectAndCoords parameter from findRecipe This removes argument for printer recipemap to pass for GT_ModHandler.getAllRecipeOutput, but I don't think there's any mod that adds world-specific coloring recipe. * Added method to set processing logic power so machines can override it on demand * Restructure CheckRecipeResult, show voltage required and move package * Reword: insufficient voltage -> power * Change text color: dark gray -> gray * Added findRecipeResult for insufficient heat * Migrated PCB factory * Fix result not being reset on shut down * Show coil tier for heat * clean * Reverted migration of driller base * Migrated TPM * Moved getting of parallel supplier, to accomodate TPM * Migrated power gen multiblocks * Migrated Assembling Line * Migrated fusion * Migrated multi smelter * Migrated boiler * Migrated DTPF * Migrated ore factory * Migrated heat exchanger * Make checkRecipe() final, javadoc, minor cleanup * Fix overclock behavior with multiple hatches, javadoc, minor cleanup * Minor fix for javadoc * Fixed creation of OC calculator not factoring in batch mode correctly * Make GT_ParallelHelper#setRecipe nonnull * Rework SimpleCheckRecipeResult to not require registration * Migrate charcoal pit and cleanroom * Fix result not being reset when turning off machine * Add API for BW to make recipemap sensitive to special slot on recipe search * Migrated PA * Migrated driller base * Make ProcessingLogic#duration int * Minor cleanup * missing recipe locking for long multi * Fix NPE * Show crash message and turn off machine * minor javadoc fix * Fixed power setting for extended power base * Integrate SingleRecipeCheck into ProcessingLogic, fix duration overflow, fix duration for batch mode, migrate PA for GT_MetaTileEntity_ExtendedPowerMultiBlockBase * Fixed ME stocking busses * Minor PA fixes * Cleanup item collecting logic & apply to normal multi as well * Oversight * Derp * Multiple voltage instead of amperage with recipe map amperage, since usually amperage is intended for parallel, not for recipe checking * Fix missing EU modifiers on PCB factory OC calculator * Removed left over OC method * Fixed duration calculation of PCB not applying roughness multiplier * Fixed OC parameters * Make createOverclockCalculator nonnull & more nonnull annotations * Fixed input processing voltage * Round down voltage for other machines too * Revert Nano Forge, return correct long voltage * Use region / endregion --------- Co-authored-by: miozune --- .../java/gregtech/api/enums/HeatingCoilLevel.java | 31 +- .../interfaces/metatileentity/IMetaTileEntity.java | 3 +- .../modularui/ControllerWithOptionalFeatures.java | 19 +- .../tileentity/IHasWorldObjectAndCoords.java | 5 +- .../api/interfaces/tileentity/IRecipeLockable.java | 34 + .../java/gregtech/api/logic/ProcessingLogic.java | 308 ++++++- .../api/metatileentity/BaseMetaTileEntity.java | 12 + .../api/metatileentity/MetaTileEntity.java | 6 + .../GT_MetaTileEntity_BasicMachine.java | 16 +- ...MetaTileEntity_ExtendedPowerMultiBlockBase.java | 69 ++ .../GT_MetaTileEntity_MultiBlockBase.java | 236 +++++- .../machine/MultiTileBasicMachine.java | 7 +- .../multiblock/base/Controller.java | 16 +- .../api/recipe/check/CheckRecipeResult.java | 42 + .../recipe/check/CheckRecipeResultRegistry.java | 111 +++ .../api/recipe/check/FindRecipeResult.java | 107 +++ .../api/recipe/check/ResultInsufficientHeat.java | 57 ++ .../check/ResultInsufficientMachineTier.java | 54 ++ .../api/recipe/check/ResultInsufficientPower.java | 56 ++ .../api/recipe/check/SimpleCheckRecipeResult.java | 78 ++ .../api/recipe/check/SingleRecipeCheck.java | 404 ++++++++++ .../gregtech/api/util/GT_OverclockCalculator.java | 13 +- .../java/gregtech/api/util/GT_ParallelHelper.java | 137 +++- .../api/util/GT_ProcessingArray_Manager.java | 9 + src/main/java/gregtech/api/util/GT_Recipe.java | 893 +++++++++------------ .../gregtech/api/util/GT_Single_Recipe_Check.java | 421 ---------- .../GT_Single_Recipe_Check_Processing_Array.java | 244 ------ src/main/java/gregtech/api/util/GT_Utility.java | 17 + .../gregtech/api/util/VoidProtectionHelper.java | 2 + .../modularui/widget/CheckRecipeResultSyncer.java | 26 + .../multi/GT_MetaTileEntity_AssemblyLine.java | 22 +- .../multi/GT_MetaTileEntity_Charcoal_Pit.java | 13 +- .../multi/GT_MetaTileEntity_Cleanroom.java | 9 +- .../multi/GT_MetaTileEntity_DieselEngine.java | 18 +- .../multi/GT_MetaTileEntity_DistillationTower.java | 70 +- .../multi/GT_MetaTileEntity_DrillerBase.java | 37 +- .../GT_MetaTileEntity_ElectricBlastFurnace.java | 147 +--- .../multi/GT_MetaTileEntity_FusionComputer.java | 15 +- .../multi/GT_MetaTileEntity_HeatExchanger.java | 13 +- .../GT_MetaTileEntity_ImplosionCompressor.java | 44 +- .../GT_MetaTileEntity_IntegratedOreFactory.java | 13 +- .../multi/GT_MetaTileEntity_LargeBoiler.java | 19 +- .../GT_MetaTileEntity_LargeChemicalReactor.java | 69 +- .../multi/GT_MetaTileEntity_LargeTurbine.java | 52 +- .../GT_MetaTileEntity_LargeTurbine_Plasma.java | 50 +- .../multi/GT_MetaTileEntity_MultiFurnace.java | 16 +- .../multi/GT_MetaTileEntity_NanoForge.java | 85 +- .../multi/GT_MetaTileEntity_OilCracker.java | 51 +- .../multi/GT_MetaTileEntity_PCBFactory.java | 251 +++--- .../multi/GT_MetaTileEntity_PlasmaForge.java | 24 +- .../multi/GT_MetaTileEntity_ProcessingArray.java | 491 ++++------- .../multi/GT_MetaTileEntity_PyrolyseOven.java | 69 +- .../GT_MetaTileEntity_TranscendentPlasmaMixer.java | 95 +-- .../multi/GT_MetaTileEntity_VacuumFreezer.java | 33 +- .../machines/multiblock/AdvChemicalReactor.java | 9 +- .../multiblock/logic/CokeOvenProcessingLogic.java | 15 +- .../storage/GT_MetaTileEntity_DigitalTankBase.java | 1 + .../nei/HeatingCoilSpecialValueFormatter.java | 10 +- 58 files changed, 2719 insertions(+), 2455 deletions(-) create mode 100644 src/main/java/gregtech/api/interfaces/tileentity/IRecipeLockable.java create mode 100644 src/main/java/gregtech/api/recipe/check/CheckRecipeResult.java create mode 100644 src/main/java/gregtech/api/recipe/check/CheckRecipeResultRegistry.java create mode 100644 src/main/java/gregtech/api/recipe/check/FindRecipeResult.java create mode 100644 src/main/java/gregtech/api/recipe/check/ResultInsufficientHeat.java create mode 100644 src/main/java/gregtech/api/recipe/check/ResultInsufficientMachineTier.java create mode 100644 src/main/java/gregtech/api/recipe/check/ResultInsufficientPower.java create mode 100644 src/main/java/gregtech/api/recipe/check/SimpleCheckRecipeResult.java create mode 100644 src/main/java/gregtech/api/recipe/check/SingleRecipeCheck.java delete mode 100644 src/main/java/gregtech/api/util/GT_Single_Recipe_Check.java delete mode 100644 src/main/java/gregtech/api/util/GT_Single_Recipe_Check_Processing_Array.java create mode 100644 src/main/java/gregtech/common/gui/modularui/widget/CheckRecipeResultSyncer.java (limited to 'src/main/java/gregtech') diff --git a/src/main/java/gregtech/api/enums/HeatingCoilLevel.java b/src/main/java/gregtech/api/enums/HeatingCoilLevel.java index f80f8139c6..f281695d5a 100644 --- a/src/main/java/gregtech/api/enums/HeatingCoilLevel.java +++ b/src/main/java/gregtech/api/enums/HeatingCoilLevel.java @@ -1,5 +1,7 @@ package gregtech.api.enums; +import javax.annotation.Nonnull; + import net.minecraft.util.StatCollector; public enum HeatingCoilLevel { @@ -32,7 +34,7 @@ public enum HeatingCoilLevel { } /** - * @return the coil tier, used for discount in the Pyrolyse Oven for example. + * @return the coil tier, used for discount in the Pyrolyse Oven for example. LV == 0 */ public byte getTier() { return (byte) (this.ordinal() - 2); @@ -52,16 +54,43 @@ public enum HeatingCoilLevel { return 1 << Math.max(0, this.ordinal() - 5); } + /** + * @return Translated name of this coil + */ public String getName() { return StatCollector.translateToLocal("GT5U.coil." + this); } + @Nonnull public static HeatingCoilLevel getFromTier(byte tier) { if (tier < 0 || tier > getMaxTier()) return HeatingCoilLevel.None; return VALUES[tier + 2]; } + /** + * @param applyColor Whether to apply tiered color + * @return Translated coil name. Heat exceeding MAX is represented as "Eternal+". + */ + @Nonnull + public static String getDisplayNameFromHeat(int heat, boolean applyColor) { + for (HeatingCoilLevel heatLevel : VALUES) { + if (heatLevel == HeatingCoilLevel.None || heatLevel == HeatingCoilLevel.ULV) continue; + if (heatLevel.getHeat() >= heat) { + String name = heatLevel.getName(); + if (applyColor) { + name = GT_Values.TIER_COLORS[heatLevel.getTier() + 1] + name; + } + return name; + } + } + String name = HeatingCoilLevel.MAX.getName() + "+"; + if (applyColor) { + name = GT_Values.TIER_COLORS[HeatingCoilLevel.MAX.getTier() + 1] + name; + } + return name; + } + public static int size() { return VALUES.length; } diff --git a/src/main/java/gregtech/api/interfaces/metatileentity/IMetaTileEntity.java b/src/main/java/gregtech/api/interfaces/metatileentity/IMetaTileEntity.java index 9a60092121..7b29f185c6 100644 --- a/src/main/java/gregtech/api/interfaces/metatileentity/IMetaTileEntity.java +++ b/src/main/java/gregtech/api/interfaces/metatileentity/IMetaTileEntity.java @@ -298,8 +298,7 @@ public interface IMetaTileEntity extends ISidedInventory, IFluidTank, IFluidHand void receiveClientEvent(byte aEventID, byte aValue); /** - * Called to actually play the Sound. Do not insert Client/Server checks. That is already done for you. Do not - * use @playSoundEffect, Minecraft doesn't like that at all. Use @playSound instead. + * Called to actually play the sound on client side. Client/Server check is already done. */ void doSound(byte aIndex, double aX, double aY, double aZ); diff --git a/src/main/java/gregtech/api/interfaces/modularui/ControllerWithOptionalFeatures.java b/src/main/java/gregtech/api/interfaces/modularui/ControllerWithOptionalFeatures.java index 9f862e254d..3d4ed80f67 100644 --- a/src/main/java/gregtech/api/interfaces/modularui/ControllerWithOptionalFeatures.java +++ b/src/main/java/gregtech/api/interfaces/modularui/ControllerWithOptionalFeatures.java @@ -20,6 +20,7 @@ import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget; import gregtech.api.enums.SoundResource; import gregtech.api.enums.VoidingMode; import gregtech.api.gui.modularui.GT_UITextures; +import gregtech.api.interfaces.tileentity.IRecipeLockable; import gregtech.api.interfaces.tileentity.IVoidable; /** @@ -33,7 +34,7 @@ import gregtech.api.interfaces.tileentity.IVoidable; *
  • Recipe locking
  • * */ -public interface ControllerWithOptionalFeatures extends IVoidable { +public interface ControllerWithOptionalFeatures extends IVoidable, IRecipeLockable { boolean isAllowedToWork(); @@ -233,22 +234,6 @@ public interface ControllerWithOptionalFeatures extends IVoidable { return (ButtonWidget) button; } - /** - * Override this if you are a multi-block that has added support for single recipe locking. - */ - boolean supportsSingleRecipeLocking(); - - /** - * @return true if recipe locking is enabled, else false. This is getter is used for displaying the icon in the GUI - */ - boolean isRecipeLockingEnabled(); - - void setRecipeLocking(boolean enabled); - - default boolean getDefaultRecipeLockingMode() { - return false; - } - Pos2d getRecipeLockingButtonPos(); default ButtonWidget createLockToSingleRecipeButton(IWidgetBuilder builder) { diff --git a/src/main/java/gregtech/api/interfaces/tileentity/IHasWorldObjectAndCoords.java b/src/main/java/gregtech/api/interfaces/tileentity/IHasWorldObjectAndCoords.java index 64d206aff7..6d81d5c401 100644 --- a/src/main/java/gregtech/api/interfaces/tileentity/IHasWorldObjectAndCoords.java +++ b/src/main/java/gregtech/api/interfaces/tileentity/IHasWorldObjectAndCoords.java @@ -136,8 +136,9 @@ public interface IHasWorldObjectAndCoords { boolean isDead(); /** - * Sends a Block Event to the Client TileEntity, the byte Parameters are only for validation as Minecraft doesn't - * properly write Packet Data. + * Sends a Block Event to the Client TileEntity. + * + * @param aValue value to sync */ void sendBlockEvent(byte aID, byte aValue); diff --git a/src/main/java/gregtech/api/interfaces/tileentity/IRecipeLockable.java b/src/main/java/gregtech/api/interfaces/tileentity/IRecipeLockable.java new file mode 100644 index 0000000000..f793221a50 --- /dev/null +++ b/src/main/java/gregtech/api/interfaces/tileentity/IRecipeLockable.java @@ -0,0 +1,34 @@ +package gregtech.api.interfaces.tileentity; + +import gregtech.api.recipe.check.SingleRecipeCheck; +import gregtech.api.util.GT_Recipe; + +/** + * Machines implementing this interface can have logic to lock to a single recipe. + */ +public interface IRecipeLockable { + + /** + * @return if this machine supports single recipe locking. + */ + boolean supportsSingleRecipeLocking(); + + /** + * @return true if recipe locking is enabled, else false. This is getter is used for displaying the icon in the GUI + */ + boolean isRecipeLockingEnabled(); + + void setRecipeLocking(boolean enabled); + + default boolean getDefaultRecipeLockingMode() { + return false; + } + + default SingleRecipeCheck getSingleRecipeCheck() { + return null; + } + + default void setSingleRecipeCheck(SingleRecipeCheck recipeCheck) {} + + GT_Recipe.GT_Recipe_Map getRecipeMap(); +} diff --git a/src/main/java/gregtech/api/logic/ProcessingLogic.java b/src/main/java/gregtech/api/logic/ProcessingLogic.java index fa0d285401..db97ac151f 100644 --- a/src/main/java/gregtech/api/logic/ProcessingLogic.java +++ b/src/main/java/gregtech/api/logic/ProcessingLogic.java @@ -1,39 +1,89 @@ package gregtech.api.logic; +import java.util.List; +import java.util.function.Supplier; + +import javax.annotation.Nonnull; + import net.minecraft.item.ItemStack; import net.minecraftforge.fluids.FluidStack; +import gregtech.api.interfaces.tileentity.IRecipeLockable; +import gregtech.api.interfaces.tileentity.IVoidable; +import gregtech.api.recipe.check.CheckRecipeResult; +import gregtech.api.recipe.check.CheckRecipeResultRegistry; +import gregtech.api.recipe.check.FindRecipeResult; +import gregtech.api.recipe.check.SingleRecipeCheck; +import gregtech.api.util.GT_OverclockCalculator; +import gregtech.api.util.GT_ParallelHelper; +import gregtech.api.util.GT_Recipe; import gregtech.api.util.GT_Recipe.GT_Recipe_Map; -public abstract class ProcessingLogic { +/** + * Logic class to calculate result of recipe check from inputs, based on recipemap. + */ +@SuppressWarnings({ "unused", "UnusedReturnValue" }) +public class ProcessingLogic { - protected GT_Recipe_Map recipeMap; + protected IVoidable machine; + protected IRecipeLockable recipeLockableMachine; + protected Supplier recipeMapSupplier; + protected GT_Recipe lastRecipe; protected ItemStack[] inputItems; protected ItemStack[] outputItems; protected ItemStack[] currentOutputItems; protected FluidStack[] inputFluids; protected FluidStack[] outputFluids; protected FluidStack[] currentOutputFluids; - protected long eut; - protected long duration; + protected long calculatedEut; + protected int duration; + protected long availableVoltage; + protected long availableAmperage; + protected int overClockTimeReduction = 1; + protected int overClockPowerIncrease = 2; + protected boolean protectItems; + protected boolean protectFluids; + protected boolean isRecipeLocked; + protected int maxParallel = 1; + protected int calculatedParallels = 0; + protected Supplier maxParallelSupplier; + protected int batchSize = 1; public ProcessingLogic() {} + // region Setters + public ProcessingLogic setInputItems(ItemStack... itemInputs) { this.inputItems = itemInputs; return this; } + public ProcessingLogic setInputItems(List itemOutputs) { + this.inputItems = itemOutputs.toArray(new ItemStack[0]); + return this; + } + public ProcessingLogic setInputFluids(FluidStack... fluidInputs) { this.inputFluids = fluidInputs; return this; } + public ProcessingLogic setInputFluids(List fluidInputs) { + this.inputFluids = fluidInputs.toArray(new FluidStack[0]); + return this; + } + + /** + * Overwrites item output result of the calculation. + */ public ProcessingLogic setOutputItems(ItemStack... itemOutputs) { this.outputItems = itemOutputs; return this; } + /** + * Overwrites fluid output result of the calculation. + */ public ProcessingLogic setOutputFluids(FluidStack... fluidOutputs) { this.outputFluids = fluidOutputs; return this; @@ -49,35 +99,263 @@ public abstract class ProcessingLogic { return this; } + /** + * Enables single recipe locking mode. + */ + public ProcessingLogic setRecipeLocking(IRecipeLockable recipeLockableMachine, boolean isRecipeLocked) { + this.recipeLockableMachine = recipeLockableMachine; + this.isRecipeLocked = isRecipeLocked; + return this; + } + + /** + * Sets max amount of parallel. + */ + public ProcessingLogic setMaxParallel(int maxParallel) { + this.maxParallel = maxParallel; + return this; + } + + /** + * Sets method to get max amount of parallel. + */ + public ProcessingLogic setMaxParallelSupplier(Supplier supplier) { + this.maxParallelSupplier = supplier; + return this; + } + + /** + * Sets batch size for batch mode. + */ + public ProcessingLogic setBatchSize(int size) { + this.batchSize = size; + return this; + } + public ProcessingLogic setRecipeMap(GT_Recipe_Map recipeMap) { - this.recipeMap = recipeMap; + return setRecipeMapSupplier(() -> recipeMap); + } + + public ProcessingLogic setRecipeMapSupplier(Supplier supplier) { + this.recipeMapSupplier = supplier; + return this; + } + + /** + * Sets machine used for void protection logic. + */ + public ProcessingLogic setMachine(IVoidable machine) { + this.machine = machine; return this; } - public ProcessingLogic setDuration(long duration) { + /** + * Overwrites duration result of the calculation. + */ + public ProcessingLogic setDuration(int duration) { this.duration = duration; return this; } - public ProcessingLogic setEut(long eut) { - this.eut = eut; + /** + * Overwrites EU/t result of the calculation. + */ + public ProcessingLogic setCalculatedEut(long calculatedEut) { + this.calculatedEut = calculatedEut; + return this; + } + + /** + * Sets voltage of the machine. It doesn't need to be actual voltage (excluding amperage) of the machine; + * For example, most of the multiblock machines set maximum possible input power (including amperage) as voltage + * and 1 as amperage. That way recipemap search will be executed with overclocked voltage. + */ + public ProcessingLogic setAvailableVoltage(long voltage) { + availableVoltage = voltage; + return this; + } + + /** + * Sets amperage of the machine. This amperage doesn't involve in EU/t when searching recipemap. + * Useful for preventing tier skip but still considering amperage for parallel. + */ + public ProcessingLogic setAvailableAmperage(long amperage) { + availableAmperage = amperage; return this; } + public ProcessingLogic setVoidProtection(boolean protectItems, boolean protectFluids) { + this.protectItems = protectItems; + this.protectFluids = protectFluids; + return this; + } + + /** + * Sets custom overclock ratio. 2/4 by default. + * Parameters represent number of bit shift, so 1 -> 2x, 2 -> 4x. + */ + public ProcessingLogic setOverclock(int timeReduction, int powerIncrease) { + this.overClockTimeReduction = timeReduction; + this.overClockPowerIncrease = powerIncrease; + return this; + } + + /** + * Sets overclock ratio to 4/4. + */ + public ProcessingLogic enablePerfectOverclock() { + return this.setOverclock(2, 2); + } + /** - * Clears everything stored in the Processing Logic other than the Recipe map used + * Clears calculated results and provided machine inputs to prepare for the next machine operation. */ public ProcessingLogic clear() { this.inputItems = null; this.inputFluids = null; this.outputItems = null; this.outputFluids = null; - this.eut = 0; + this.calculatedEut = 0; this.duration = 0; + this.calculatedParallels = 0; return this; } - public abstract boolean process(); + // endregion + + // region Logic + + /** + * Executes the recipe check: Find recipe from recipemap, Calculate parallel, overclock and outputs. + */ + @Nonnull + public CheckRecipeResult process() { + if (recipeMapSupplier == null) return CheckRecipeResultRegistry.NO_RECIPE; + + GT_Recipe_Map recipeMap = recipeMapSupplier.get(); + if (recipeMap == null) return CheckRecipeResultRegistry.NO_RECIPE; + + if (maxParallelSupplier != null) { + maxParallel = maxParallelSupplier.get(); + } + + FindRecipeResult findRecipeResult; + if (isRecipeLocked && recipeLockableMachine != null && recipeLockableMachine.getSingleRecipeCheck() != null) { + // Recipe checker is already built, we'll use it + SingleRecipeCheck singleRecipeCheck = recipeLockableMachine.getSingleRecipeCheck(); + // Validate recipe here, otherwise machine will show "not enough output space" + // even if recipe cannot be found + if (singleRecipeCheck.checkRecipeInputs(false, 1, inputItems, inputFluids) == 0) { + return CheckRecipeResultRegistry.NO_RECIPE; + } + findRecipeResult = FindRecipeResult.ofSuccess( + recipeLockableMachine.getSingleRecipeCheck() + .getRecipe()); + } else { + findRecipeResult = recipeMap + .findRecipeWithResult(lastRecipe, false, false, availableVoltage, inputFluids, null, inputItems); + } + + GT_Recipe recipe; + if (findRecipeResult.isSuccessful()) { + recipe = findRecipeResult.getRecipeNonNull(); + CheckRecipeResult result = validateRecipe(recipe); + if (!result.wasSuccessful()) { + return result; + } else { + lastRecipe = recipe; + } + } else { + if (findRecipeResult.getState() == FindRecipeResult.State.INSUFFICIENT_VOLTAGE) { + return CheckRecipeResultRegistry.insufficientPower(findRecipeResult.getRecipeNonNull().mEUt); + } else { + return CheckRecipeResultRegistry.NO_RECIPE; + } + } + + GT_ParallelHelper helper = createParallelHelper(recipe); + + helper.build(); + + if (helper.getCurrentParallel() <= 0) return CheckRecipeResultRegistry.OUTPUT_FULL; + + calculatedParallels = helper.getCurrentParallel(); + + GT_OverclockCalculator calculator = createOverclockCalculator(recipe, helper); + + calculator.calculate(); + if (calculator.getConsumption() == Long.MAX_VALUE) { + return CheckRecipeResultRegistry.POWER_OVERFLOW; + } + if (calculator.getDuration() == Integer.MAX_VALUE) { + return CheckRecipeResultRegistry.DURATION_OVERFLOW; + } + + calculatedEut = calculator.getConsumption(); + + double finalDuration = calculateDuration(recipe, helper, calculator); + if (finalDuration >= Integer.MAX_VALUE) { + return CheckRecipeResultRegistry.DURATION_OVERFLOW; + } + duration = (int) finalDuration; + + outputItems = helper.getItemOutputs(); + outputFluids = helper.getFluidOutputs(); + + return CheckRecipeResultRegistry.SUCCESSFUL; + } + + /** + * Override to tweak final duration that will be set as a result of this logic class. + */ + protected double calculateDuration(@Nonnull GT_Recipe recipe, @Nonnull GT_ParallelHelper helper, + @Nonnull GT_OverclockCalculator calculator) { + return calculator.getDuration() * helper.getDurationMultiplierDouble(); + } + + /** + * Override to tweak parallel logic if needed. + */ + @Nonnull + protected GT_ParallelHelper createParallelHelper(@Nonnull GT_Recipe recipe) { + return new GT_ParallelHelper().setRecipe(recipe) + .setItemInputs(inputItems) + .setFluidInputs(inputFluids) + .setAvailableEUt(availableVoltage * availableAmperage) + .setMachine(machine, protectItems, protectFluids) + .setRecipeLocked(recipeLockableMachine, isRecipeLocked) + .setMaxParallel(maxParallel) + .enableBatchMode(batchSize) + .enableConsumption() + .enableOutputCalculation(); + } + + /** + * Override to do additional check for finding recipe if needed, mainly for special value of the recipe. + */ + @Nonnull + protected CheckRecipeResult validateRecipe(@Nonnull GT_Recipe recipe) { + return CheckRecipeResultRegistry.SUCCESSFUL; + } + + /** + * Override to tweak overclock logic if needed. + */ + @Nonnull + protected GT_OverclockCalculator createOverclockCalculator(@Nonnull GT_Recipe recipe, + @Nonnull GT_ParallelHelper helper) { + return new GT_OverclockCalculator().setRecipeEUt(recipe.mEUt) + .setParallel((int) Math.floor(helper.getCurrentParallel() / helper.getDurationMultiplierDouble())) + .setDuration(recipe.mDuration) + .setAmperage(availableAmperage) + .setEUt(availableVoltage) + .setDurationDecreasePerOC(overClockTimeReduction) + .setEUtIncreasePerOC(overClockPowerIncrease); + } + + // endregion + + // region Getters public ItemStack[] getOutputItems() { return outputItems; @@ -87,11 +365,13 @@ public abstract class ProcessingLogic { return outputFluids; } - public long getDuration() { + public int getDuration() { return duration; } - public long getEut() { - return eut; + public long getCalculatedEut() { + return calculatedEut; } + + // endregion } diff --git a/src/main/java/gregtech/api/metatileentity/BaseMetaTileEntity.java b/src/main/java/gregtech/api/metatileentity/BaseMetaTileEntity.java index a23fbd8a85..72ca9f9124 100644 --- a/src/main/java/gregtech/api/metatileentity/BaseMetaTileEntity.java +++ b/src/main/java/gregtech/api/metatileentity/BaseMetaTileEntity.java @@ -595,6 +595,12 @@ public class BaseMetaTileEntity extends CommonMetaTileEntity } catch (Throwable e) { e.printStackTrace(); e.printStackTrace(GT_Log.err); + try { + mMetaTileEntity.onTickFail(this, mTickTimer); + } catch (Throwable ex) { + ex.printStackTrace(); + ex.printStackTrace(GT_Log.err); + } } if (aSideServer && hasValidMetaTileEntity()) { @@ -1020,6 +1026,9 @@ public class BaseMetaTileEntity extends CommonMetaTileEntity @Override public void disableWorking() { mWorks = false; + if (hasValidMetaTileEntity()) { + mMetaTileEntity.onDisableWorking(); + } } @Override @@ -1060,6 +1069,9 @@ public class BaseMetaTileEntity extends CommonMetaTileEntity @Override public void setActive(boolean aActive) { mActive = aActive; + if (hasValidMetaTileEntity()) { + mMetaTileEntity.onSetActive(aActive); + } } @Override diff --git a/src/main/java/gregtech/api/metatileentity/MetaTileEntity.java b/src/main/java/gregtech/api/metatileentity/MetaTileEntity.java index 453849923d..1ac42a2ea1 100644 --- a/src/main/java/gregtech/api/metatileentity/MetaTileEntity.java +++ b/src/main/java/gregtech/api/metatileentity/MetaTileEntity.java @@ -319,6 +319,12 @@ public abstract class MetaTileEntity implements IMetaTileEntity, ICleanroomRecei } } + public void onTickFail(IGregTechTileEntity aBaseMetaTileEntity, long aTick) {} + + public void onSetActive(boolean active) {} + + public void onDisableWorking() {} + @Override public void inValidate() { /* Do nothing */ diff --git a/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_BasicMachine.java b/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_BasicMachine.java index f251b5c02f..ac037b7919 100644 --- a/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_BasicMachine.java +++ b/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_BasicMachine.java @@ -69,6 +69,7 @@ import gregtech.api.interfaces.modularui.IAddGregtechLogo; import gregtech.api.interfaces.modularui.IAddUIWidgets; import gregtech.api.interfaces.tileentity.IGregTechTileEntity; import gregtech.api.objects.GT_ItemStack; +import gregtech.api.recipe.check.FindRecipeResult; import gregtech.api.render.TextureFactory; import gregtech.api.util.GT_ClientPreference; import gregtech.api.util.GT_CoverBehaviorBase; @@ -1085,15 +1086,24 @@ public abstract class GT_MetaTileEntity_BasicMachine extends GT_MetaTileEntity_B public int checkRecipe(boolean skipOC) { GT_Recipe_Map tMap = getRecipeList(); if (tMap == null) return DID_NOT_FIND_RECIPE; - GT_Recipe tRecipe = tMap.findRecipe( - getBaseMetaTileEntity(), + FindRecipeResult result = tMap.findRecipeWithResult( mLastRecipe, false, + false, V[mTier], new FluidStack[] { getFillableStack() }, getSpecialSlot(), getAllInputs()); - if (tRecipe == null) return DID_NOT_FIND_RECIPE; + if (result.getState() == FindRecipeResult.State.EXPLODE && getBaseMetaTileEntity() != null) { + getBaseMetaTileEntity().doExplosion(V[mTier] * 4); + return DID_NOT_FIND_RECIPE; + } + if (result.getState() == FindRecipeResult.State.ON_FIRE && getBaseMetaTileEntity() != null) { + getBaseMetaTileEntity().setOnFire(); + return DID_NOT_FIND_RECIPE; + } + if (!result.isSuccessful()) return DID_NOT_FIND_RECIPE; + GT_Recipe tRecipe = result.getRecipeNonNull(); if (GT_Mod.gregtechproxy.mLowGravProcessing && (tRecipe.mSpecialValue == -100 || tRecipe.mSpecialValue == -300) && !isValidForLowGravity(tRecipe, getBaseMetaTileEntity().getWorld().provider.dimensionId)) diff --git a/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_ExtendedPowerMultiBlockBase.java b/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_ExtendedPowerMultiBlockBase.java index 47dd69ba6c..ef3f587c01 100644 --- a/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_ExtendedPowerMultiBlockBase.java +++ b/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_ExtendedPowerMultiBlockBase.java @@ -5,6 +5,8 @@ import static gregtech.api.enums.GT_Values.VN; import java.util.ArrayList; import java.util.List; +import javax.annotation.Nonnull; + import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; @@ -14,6 +16,9 @@ import net.minecraft.util.StatCollector; import net.minecraft.world.World; import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.logic.ProcessingLogic; +import gregtech.api.recipe.check.CheckRecipeResult; +import gregtech.api.recipe.check.CheckRecipeResultRegistry; import gregtech.api.util.GT_ExoticEnergyInputHelper; import gregtech.api.util.GT_Utility; @@ -126,6 +131,70 @@ public abstract class GT_MetaTileEntity_ExtendedPowerMultiBlockBase inputItems = new ArrayList<>(); + for (int i = bus.getSizeInventory() - 1; i >= 0; i--) { + ItemStack stored = bus.getStackInSlot(i); + if (stored != null) { + inputItems.add(stored); + } + } + processingLogic.setInputItems(inputItems.toArray(new ItemStack[0])); + result = processingLogic.process(); + if (result.wasSuccessful()) break; + } + } else { + processingLogic.setInputItems(getStoredInputs()); + result = processingLogic.process(); + } + + // inputs are consumed by `process()` + updateSlots(); + + if (!result.wasSuccessful()) return result; + + mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000); + mEfficiencyIncrease = 10000; + + lEUt = processingLogic.getCalculatedEut(); + mMaxProgresstime = processingLogic.getDuration(); + + if (lEUt > 0) { + lEUt = (-lEUt); + } + + mOutputItems = processingLogic.getOutputItems(); + mOutputFluids = processingLogic.getOutputFluids(); + + return result; + } + + @Override + protected void setProcessingLogicPower(ProcessingLogic logic) { + logic.setAvailableVoltage(GT_Utility.roundDownVoltage(getAverageInputVoltage())); + logic.setAvailableAmperage(getMaxInputAmps()); + } + @Override public String[] getInfoData() { int mPollutionReduction = 0; diff --git a/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_MultiBlockBase.java b/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_MultiBlockBase.java index 1c93779f01..0d383bc768 100644 --- a/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_MultiBlockBase.java +++ b/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_MultiBlockBase.java @@ -30,10 +30,12 @@ import net.minecraftforge.common.util.Constants; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.fluids.FluidStack; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.TestOnly; import org.lwjgl.input.Keyboard; import com.google.common.collect.Iterables; +import com.gtnewhorizons.modularui.api.math.Alignment; import com.gtnewhorizons.modularui.api.math.Pos2d; import com.gtnewhorizons.modularui.api.screen.ModularWindow; import com.gtnewhorizons.modularui.api.screen.UIBuildContext; @@ -60,26 +62,28 @@ import gregtech.api.interfaces.modularui.IAddUIWidgets; import gregtech.api.interfaces.modularui.IBindPlayerInventoryUI; import gregtech.api.interfaces.tileentity.IGregTechTileEntity; import gregtech.api.items.GT_MetaGenerated_Tool; +import gregtech.api.logic.ProcessingLogic; import gregtech.api.metatileentity.MetaTileEntity; import gregtech.api.objects.GT_ItemStack; +import gregtech.api.recipe.check.CheckRecipeResult; +import gregtech.api.recipe.check.CheckRecipeResultRegistry; +import gregtech.api.recipe.check.SingleRecipeCheck; import gregtech.api.util.GT_ExoticEnergyInputHelper; import gregtech.api.util.GT_Log; -import gregtech.api.util.GT_ModHandler; import gregtech.api.util.GT_ParallelHelper; import gregtech.api.util.GT_Recipe; import gregtech.api.util.GT_Recipe.GT_Recipe_Map; -import gregtech.api.util.GT_Single_Recipe_Check; import gregtech.api.util.GT_Utility; import gregtech.api.util.GT_Waila; import gregtech.api.util.OutputHatchWrapper; import gregtech.api.util.VoidProtectionHelper; import gregtech.client.GT_SoundLoop; import gregtech.common.GT_Pollution; +import gregtech.common.gui.modularui.widget.CheckRecipeResultSyncer; import gregtech.common.items.GT_MetaGenerated_Tool_01; import gregtech.common.tileentities.machines.GT_MetaTileEntity_Hatch_InputBus_ME; import gregtech.common.tileentities.machines.GT_MetaTileEntity_Hatch_OutputBus_ME; import gregtech.common.tileentities.machines.GT_MetaTileEntity_Hatch_Output_ME; -import gregtech.common.tileentities.machines.multi.GT_MetaTileEntity_DrillerBase; import gregtech.common.tileentities.machines.multi.GT_MetaTileEntity_LargeTurbine; import mcp.mobius.waila.api.IWailaConfigHandler; import mcp.mobius.waila.api.IWailaDataAccessor; @@ -105,11 +109,14 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity protected boolean inputSeparation = getDefaultInputSeparationMode(); protected VoidingMode voidingMode = getDefaultVoidingMode(); protected boolean batchMode = getDefaultBatchMode(); + private @Nonnull CheckRecipeResult checkRecipeResult = CheckRecipeResultRegistry.NONE; + private boolean isScheduledForResetCheckRecipeResult; + protected static final String INPUT_SEPARATION_NBT_KEY = "inputSeparation"; protected static final String VOID_EXCESS_NBT_KEY = "voidExcess"; protected static final String VOIDING_MODE_NBT_KEY = "voidingMode"; protected static final String BATCH_MODE_NBT_KEY = "batchMode"; - public GT_Single_Recipe_Check mSingleRecipeCheck = null; + protected SingleRecipeCheck mSingleRecipeCheck = null; public ArrayList mInputHatches = new ArrayList<>(); public ArrayList mOutputHatches = new ArrayList<>(); @@ -120,6 +127,7 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity public ArrayList mEnergyHatches = new ArrayList<>(); public ArrayList mMaintenanceHatches = new ArrayList<>(); protected List mExoticEnergyHatches = new ArrayList<>(); + protected final ProcessingLogic processingLogic; @SideOnly(Side.CLIENT) protected GT_SoundLoop activitySoundLoop; @@ -130,6 +138,7 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity public GT_MetaTileEntity_MultiBlockBase(int aID, String aName, String aNameRegional) { super(aID, aName, aNameRegional, 2); + this.processingLogic = null; GT_MetaTileEntity_MultiBlockBase.disableMaintenance = GregTech_API.sMachineFile .get(ConfigCategories.machineconfig, "MultiBlockMachines.disableMaintenance", false); this.damageFactorLow = GregTech_API.sMachineFile @@ -141,6 +150,7 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity public GT_MetaTileEntity_MultiBlockBase(String aName) { super(aName, 2); + this.processingLogic = createProcessingLogic(); GT_MetaTileEntity_MultiBlockBase.disableMaintenance = GregTech_API.sMachineFile .get(ConfigCategories.machineconfig, "MultiBlockMachines.disableMaintenance", false); this.damageFactorLow = GregTech_API.sMachineFile @@ -273,7 +283,7 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity if (supportsSingleRecipeLocking()) { mLockedToSingleRecipe = aNBT.getBoolean("mLockedToSingleRecipe"); if (mLockedToSingleRecipe && aNBT.hasKey("mSingleRecipeCheck", Constants.NBT.TAG_COMPOUND)) { - GT_Single_Recipe_Check c = loadSingleRecipeChecker(aNBT.getCompoundTag("mSingleRecipeCheck")); + SingleRecipeCheck c = loadSingleRecipeChecker(aNBT.getCompoundTag("mSingleRecipeCheck")); if (c != null) mSingleRecipeCheck = c; // the old recipe is gone. we disable the machine to prevent making garbage in case of shared inputs // maybe use a better way to inform player in the future. @@ -311,8 +321,8 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity mCrowbar = aNBT.getBoolean("mCrowbar"); } - protected GT_Single_Recipe_Check loadSingleRecipeChecker(NBTTagCompound aNBT) { - return GT_Single_Recipe_Check.tryLoad(this, aNBT); + protected SingleRecipeCheck loadSingleRecipeChecker(NBTTagCompound aNBT) { + return SingleRecipeCheck.tryLoad(getRecipeMap(), aNBT); } @Override @@ -411,6 +421,17 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity } } + @Override + public void onTickFail(IGregTechTileEntity aBaseMetaTileEntity, long aTick) { + super.onTickFail(aBaseMetaTileEntity, aTick); + if (aBaseMetaTileEntity.isServerSide()) { + aBaseMetaTileEntity.disableWorking(); + checkRecipeResult = CheckRecipeResultRegistry.CRASH; + // Don't let `onSetActive` to overwrite + isScheduledForResetCheckRecipeResult = false; + } + } + private void checkMaintenance() { if (disableMaintenance) { mWrench = true; @@ -444,14 +465,26 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity } } - protected boolean checkRecipe() { + /** + * Starts checking recipe with some operations needed to actually run the check. Overriding this without due care + * may result in dupe of items, hence it's marked as final. + *

    + * See {@link #createProcessingLogic()} or {@link #checkProcessing()} for what you want to override. + * + * @return If successfully found recipe and/or started processing + */ + protected final boolean checkRecipe() { startRecipeProcessing(); - boolean result = checkRecipe(mInventory[1]); - if (result && getProcessStartSound() != null) { - sendLoopStart(PROCESS_START_SOUND_INDEX); + CheckRecipeResult result = checkProcessing(); + if (!CheckRecipeResultRegistry.isRegistered(result.getID())) { + throw new RuntimeException(String.format("Result %s is not registered for registry", result.getID())); + } + if (result.wasSuccessful()) { + sendStartMultiBlockSoundLoop(); } + this.checkRecipeResult = result; endRecipeProcessing(); - return result; + return result.wasSuccessful(); } private boolean shouldCheckRecipeThisTick(long aTick) { @@ -544,6 +577,12 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity return mPollution < 10000; } + protected void sendStartMultiBlockSoundLoop() { + if (getProcessStartSound() != null) { + sendLoopStart(PROCESS_START_SOUND_INDEX); + } + } + @Override public void doSound(byte aIndex, double aX, double aY, double aZ) { super.doSound(aIndex, aX, aY, aZ); @@ -631,9 +670,86 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity public abstract boolean isCorrectMachinePart(ItemStack aStack); /** - * Checks the Recipe + * @deprecated Use {@link #createProcessingLogic()} or {@link #checkProcessing()} + */ + @Deprecated + public boolean checkRecipe(ItemStack aStack) { + return false; + } + + /** + * Checks recipe and setup machine if it's successful. + *

    + * For generic machine working with recipemap, use {@link #createProcessingLogic()} to make use of shared codebase. */ - public abstract boolean checkRecipe(ItemStack aStack); + @Nonnull + public CheckRecipeResult checkProcessing() { + // If no logic is found, try legacy checkRecipe + if (processingLogic == null) { + return checkRecipe(mInventory[1]) ? CheckRecipeResultRegistry.SUCCESSFUL + : CheckRecipeResultRegistry.NO_RECIPE; + } + + CheckRecipeResult result = CheckRecipeResultRegistry.NO_RECIPE; + + processingLogic.clear(); + processingLogic.setMachine(this); + processingLogic.setRecipeMapSupplier(this::getRecipeMap); + processingLogic.setVoidProtection(protectsExcessItem(), protectsExcessFluid()); + processingLogic.setBatchSize(isBatchModeEnabled() ? getMaxBatchSize() : 1); + processingLogic.setRecipeLocking(this, isRecipeLockingEnabled()); + processingLogic.setInputFluids(getStoredFluids()); + setProcessingLogicPower(processingLogic); + if (isInputSeparationEnabled()) { + for (GT_MetaTileEntity_Hatch_InputBus bus : mInputBusses) { + List inputItems = new ArrayList<>(); + for (int i = bus.getSizeInventory() - 1; i >= 0; i--) { + ItemStack stored = bus.getStackInSlot(i); + if (stored != null) { + inputItems.add(stored); + } + } + processingLogic.setInputItems(inputItems.toArray(new ItemStack[0])); + result = processingLogic.process(); + if (result.wasSuccessful()) break; + } + } else { + processingLogic.setInputItems(getStoredInputs()); + result = processingLogic.process(); + } + + // inputs are consumed by `process()` + updateSlots(); + + if (!result.wasSuccessful()) return result; + + mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000); + mEfficiencyIncrease = 10000; + + if (processingLogic.getCalculatedEut() > Integer.MAX_VALUE) { + return CheckRecipeResultRegistry.POWER_OVERFLOW; + } + mEUt = (int) processingLogic.getCalculatedEut(); + mMaxProgresstime = processingLogic.getDuration(); + + if (mEUt > 0) { + mEUt = (-mEUt); + } + + mOutputItems = processingLogic.getOutputItems(); + mOutputFluids = processingLogic.getOutputFluids(); + + return result; + } + + protected void setProcessingLogicPower(ProcessingLogic logic) { + logic.setAvailableVoltage(GT_Utility.roundDownVoltage(getMaxInputVoltage())); + logic.setAvailableAmperage(1); + } + + protected int getMaxBatchSize() { + return 128; + } /** * Checks the Machine. You have to assign the MetaTileEntities for the Hatches here. @@ -678,6 +794,7 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity mMaxProgresstime = 0; mEfficiencyIncrease = 0; getBaseMetaTileEntity().disableWorking(); + checkRecipeResult = CheckRecipeResultRegistry.NONE; } public void criticalStopMachine() { @@ -839,6 +956,9 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity return injected > 0; } + /** + * Sums up voltage of energy hatches. Amperage does not matter. + */ public long getMaxInputVoltage() { long rVoltage = 0; for (GT_MetaTileEntity_Hatch_Energy tHatch : mEnergyHatches) @@ -847,6 +967,21 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity return rVoltage; } + /** + * Sums up max input EU/t of energy hatches, amperage included. + */ + public long getMaxInputPower() { + long eut = 0; + for (GT_MetaTileEntity_Hatch_Energy tHatch : mEnergyHatches) if (isValidMetaTileEntity(tHatch)) { + IGregTechTileEntity baseTile = tHatch.getBaseMetaTileEntity(); + eut += baseTile.getInputVoltage() * baseTile.getInputAmperage(); + } + return eut; + } + + /** + * Returns voltage tier of energy hatches. If multiple tiers are found, returns 0. + */ public long getInputVoltageTier() { long rTier = 0; if (mEnergyHatches.size() > 0) { @@ -1130,10 +1265,21 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity return rList; } + @Override public GT_Recipe_Map getRecipeMap() { return null; } + /** + * Creates logic to run recipe check based on recipemap. This runs only once, on class instantiation. + *

    + * If this machine doesn't use recipemap or does some complex things, override {@link #checkProcessing()}. + */ + @ApiStatus.OverrideOnly + protected ProcessingLogic createProcessingLogic() { + return null; + } + public void updateSlots() { for (GT_MetaTileEntity_Hatch_Input tHatch : mInputHatches) if (isValidMetaTileEntity(tHatch)) tHatch.updateSlots(); @@ -1523,6 +1669,20 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity } } + @Override + public void onSetActive(boolean active) { + if (isScheduledForResetCheckRecipeResult && !active) { + checkRecipeResult = CheckRecipeResultRegistry.NONE; + isScheduledForResetCheckRecipeResult = false; + } + } + + @Override + public void onDisableWorking() { + // This prevents deleting result instantly when turning off machine + isScheduledForResetCheckRecipeResult = true; + } + protected void setMufflers(boolean state) { for (GT_MetaTileEntity_Hatch_Muffler aMuffler : mMufflerHatches) { final IGregTechTileEntity iGTTileEntity = aMuffler.getBaseMetaTileEntity(); @@ -1614,17 +1774,25 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity @Override public boolean isAllowedToWork() { - return getBaseMetaTileEntity().isAllowedToWork(); + return getBaseMetaTileEntity() != null && getBaseMetaTileEntity().isAllowedToWork(); } @Override public void disableWorking() { - getBaseMetaTileEntity().disableWorking(); + if (getBaseMetaTileEntity() != null) { + getBaseMetaTileEntity().disableWorking(); + } } @Override public void enableWorking() { - getBaseMetaTileEntity().enableWorking(); + if (getBaseMetaTileEntity() != null) { + getBaseMetaTileEntity().enableWorking(); + } + } + + public ItemStack getControllerSlot() { + return mInventory[1]; } @Override @@ -1764,7 +1932,20 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity @Override public void setRecipeLocking(boolean enabled) { - this.mLockedToSingleRecipe = enabled; + mLockedToSingleRecipe = enabled; + if (!enabled) { + setSingleRecipeCheck(null); + } + } + + @Override + public void setSingleRecipeCheck(SingleRecipeCheck recipeCheck) { + mSingleRecipeCheck = recipeCheck; + } + + @Override + public SingleRecipeCheck getSingleRecipeCheck() { + return mSingleRecipeCheck; } @Override @@ -1889,22 +2070,17 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity new TextWidget(GT_Utility.trans("142", "Running perfectly.")).setDefaultColor(COLOR_TEXT_WHITE.get()) .setEnabled( widget -> getBaseMetaTileEntity().getErrorDisplayID() == 0 && getBaseMetaTileEntity().isActive())); - screenElements.widget( - new TextWidget(GT_Utility.trans("143", "Missing Mining Pipe")).setDefaultColor(COLOR_TEXT_WHITE.get()) - .setEnabled(widget -> { - if (getBaseMetaTileEntity().getErrorDisplayID() == 0 - && this instanceof GT_MetaTileEntity_DrillerBase) { - final ItemStack tItem = inventorySlot.getMcSlot() - .getStack(); - return tItem == null - || !GT_Utility.areStacksEqual(tItem, GT_ModHandler.getIC2Item("miningPipe", 1L)); - } - return false; - })); + TextWidget.dynamicString(() -> checkRecipeResult.getDisplayString()) + .setSynced(false) + .setTextAlignment(Alignment.CenterLeft) + .setEnabled(widget -> GT_Utility.isStringValid(checkRecipeResult.getDisplayString()))) + .widget(new CheckRecipeResultSyncer(() -> checkRecipeResult, (result) -> checkRecipeResult = result)); + screenElements.widget( new TextWidget(GT_Utility.trans("144", "Missing Turbine Rotor")).setDefaultColor(COLOR_TEXT_WHITE.get()) .setEnabled(widget -> { + if (getBaseMetaTileEntity().isAllowedToWork()) return false; if (getBaseMetaTileEntity().getErrorDisplayID() == 0 && this instanceof GT_MetaTileEntity_LargeTurbine) { final ItemStack tItem = inventorySlot.getMcSlot() diff --git a/src/main/java/gregtech/api/multitileentity/machine/MultiTileBasicMachine.java b/src/main/java/gregtech/api/multitileentity/machine/MultiTileBasicMachine.java index 61adafce13..b8ac727a91 100644 --- a/src/main/java/gregtech/api/multitileentity/machine/MultiTileBasicMachine.java +++ b/src/main/java/gregtech/api/multitileentity/machine/MultiTileBasicMachine.java @@ -42,6 +42,7 @@ import gregtech.api.multitileentity.MultiTileEntityRegistry; import gregtech.api.multitileentity.base.TickableMultiTileEntity; import gregtech.api.multitileentity.interfaces.IMultiTileMachine; import gregtech.api.net.GT_Packet_MultiTileEntity; +import gregtech.api.recipe.check.CheckRecipeResult; import gregtech.api.render.TextureFactory; import gregtech.api.util.GT_Utility; import gregtech.client.GT_SoundLoop; @@ -539,17 +540,17 @@ public abstract class MultiTileBasicMachine extends TickableMultiTileEntity impl } ProcessingLogic logic = ((ProcessingLogicHost) this).getProcessingLogic(); logic.clear(); - boolean result = logic.setInputItems(getInputItems()) + CheckRecipeResult result = logic.setInputItems(getInputItems()) .setInputFluids(getInputFluids()) .setCurrentOutputItems( outputInventory.getStacks() .toArray(new ItemStack[0])) .process(); setDuration(logic.getDuration()); - setEut(logic.getEut()); + setEut(logic.getCalculatedEut()); setItemOutputs(logic.getOutputItems()); setFluidOutputs(logic.getOutputFluids()); - return result; + return result.wasSuccessful(); } /** diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java index b96a822e4b..cdefa21e71 100644 --- a/src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java +++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java @@ -95,7 +95,10 @@ import gregtech.api.multitileentity.machine.MultiTileBasicMachine; import gregtech.api.multitileentity.multiblock.casing.FunctionalCasing; import gregtech.api.multitileentity.multiblock.casing.UpgradeCasing; import gregtech.api.objects.GT_ItemStack; +import gregtech.api.recipe.check.CheckRecipeResult; +import gregtech.api.recipe.check.CheckRecipeResultRegistry; import gregtech.api.util.GT_Multiblock_Tooltip_Builder; +import gregtech.api.util.GT_Recipe; import gregtech.api.util.GT_Utility; import gregtech.api.util.GT_Waila; import gregtech.common.tileentities.casings.upgrade.Inventory; @@ -1675,7 +1678,7 @@ public abstract class Controller> extends MultiTileBasic } ProcessingLogic logic = ((ProcessingLogicHost) this).getProcessingLogic(); logic.clear(); - boolean result = false; + CheckRecipeResult result = CheckRecipeResultRegistry.NO_RECIPE; if (isSeparateInputs()) { // TODO: Add separation with fluids for (Pair inventory : getItemInputsForEachInventory()) { @@ -1684,7 +1687,7 @@ public abstract class Controller> extends MultiTileBasic result = logic.setInputItems(inventory.getLeft()) .setCurrentOutputItems(getOutputItems()) .process(); - if (result) { + if (result.wasSuccessful()) { inventoryName = inventory.getRight(); break; } @@ -1698,10 +1701,10 @@ public abstract class Controller> extends MultiTileBasic .process(); } setDuration(logic.getDuration()); - setEut(logic.getEut()); + setEut(logic.getCalculatedEut()); setItemOutputs(logic.getOutputItems()); setFluidOutputs(logic.getOutputFluids()); - return result; + return result.wasSuccessful(); } public IItemHandlerModifiable getOutputInventory() { @@ -2066,6 +2069,11 @@ public abstract class Controller> extends MultiTileBasic this.recipeLock = enabled; } + @Override + public GT_Recipe.GT_Recipe_Map getRecipeMap() { + return null; + } + @Override public Pos2d getRecipeLockingButtonPos() { return new Pos2d(0, 0); diff --git a/src/main/java/gregtech/api/recipe/check/CheckRecipeResult.java b/src/main/java/gregtech/api/recipe/check/CheckRecipeResult.java new file mode 100644 index 0000000000..ab1db6ecd2 --- /dev/null +++ b/src/main/java/gregtech/api/recipe/check/CheckRecipeResult.java @@ -0,0 +1,42 @@ +package gregtech.api.recipe.check; + +import net.minecraft.network.PacketBuffer; + +/** + * Class to indicate the result of recipe check in the machine. It doesn't need to be actual result of recipemap check, + * but can also be status of whether to start the machine. Examples can be found at {@link CheckRecipeResultRegistry}. + *

    + * Sample instance must be registered to {@link CheckRecipeResultRegistry}. + */ +public interface CheckRecipeResult { + + /** + * @return Unique registry ID + */ + String getID(); + + /** + * @return If recipe check is successful + */ + boolean wasSuccessful(); + + /** + * @return Actual text to show on client GUI + */ + String getDisplayString(); + + /** + * Create new instance to receive packet. + */ + CheckRecipeResult newInstance(); + + /** + * Encode value to sync. + */ + void encode(PacketBuffer buffer); + + /** + * Decode synced value. + */ + void decode(PacketBuffer buffer); +} diff --git a/src/main/java/gregtech/api/recipe/check/CheckRecipeResultRegistry.java b/src/main/java/gregtech/api/recipe/check/CheckRecipeResultRegistry.java new file mode 100644 index 0000000000..26f9dedd7d --- /dev/null +++ b/src/main/java/gregtech/api/recipe/check/CheckRecipeResultRegistry.java @@ -0,0 +1,111 @@ +package gregtech.api.recipe.check; + +import java.util.HashMap; +import java.util.Map; + +public final class CheckRecipeResultRegistry { + + private static final Map registry = new HashMap<>(); + + /** + * Registers CheckRecipeResult. No duplicated IDs are allowed. + * + * @param sample Sample object to register + */ + public static void register(CheckRecipeResult sample) { + if (isRegistered(sample.getID())) { + throw new IllegalStateException( + String.format( + "ID %s is already registered for %s", + sample.getID(), + registry.get(sample.getID()) + .getClass() + .getCanonicalName())); + } + registry.put(sample.getID(), sample); + } + + public static CheckRecipeResult getSampleFromRegistry(String id) { + if (!isRegistered(id)) { + throw new RuntimeException("Unknown id: " + id); + } + return registry.get(id); + } + + public static boolean isRegistered(String id) { + return registry.containsKey(id); + } + + /** + * Successfully found recipe. + */ + public static final CheckRecipeResult SUCCESSFUL = SimpleCheckRecipeResult.ofSuccess("success"); + /** + * All requirements met to generator power. + */ + public static final CheckRecipeResult GENERATING = SimpleCheckRecipeResult.ofSuccess("generating"); + /** + * Cannot find recipe. + */ + public static final CheckRecipeResult NO_RECIPE = SimpleCheckRecipeResult.ofFailure("no_recipe"); + /** + * Cannot process recipe because output is full. + */ + public static final CheckRecipeResult OUTPUT_FULL = SimpleCheckRecipeResult.ofFailure("output_full"); + /** + * Default unknown state. + */ + public static final CheckRecipeResult NONE = SimpleCheckRecipeResult.ofFailure("none"); + /** + * Code crashed. + */ + public static final CheckRecipeResult CRASH = SimpleCheckRecipeResult.ofFailure("crash"); + /** + * Cannot find valid fuel for generator. + */ + public static final CheckRecipeResult NO_FUEL_FOUND = SimpleCheckRecipeResult.ofFailure("no_fuel"); + /** + * Cannot find valid turbine. + */ + public static final CheckRecipeResult NO_TURBINE_FOUND = SimpleCheckRecipeResult.ofFailure("no_turbine"); + /** + * No data sticks found for Assembly Line. + */ + public static final CheckRecipeResult NO_DATA_STI