aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gregtech/api/util/OverclockCalculator.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/gregtech/api/util/OverclockCalculator.java')
-rw-r--r--src/main/java/gregtech/api/util/OverclockCalculator.java621
1 files changed, 621 insertions, 0 deletions
diff --git a/src/main/java/gregtech/api/util/OverclockCalculator.java b/src/main/java/gregtech/api/util/OverclockCalculator.java
new file mode 100644
index 0000000000..0b27942355
--- /dev/null
+++ b/src/main/java/gregtech/api/util/OverclockCalculator.java
@@ -0,0 +1,621 @@
+package gregtech.api.util;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import javax.annotation.Nonnull;
+
+public class OverclockCalculator {
+
+ // region variables
+ // region basic properties
+ /**
+ * EUt the recipe originally runs at
+ */
+ private long recipeEUt = 0;
+ /**
+ * Voltage of the machine
+ */
+ private long machineVoltage = 0;
+ /**
+ * Amperage of the machine
+ */
+ private long machineAmperage = 1;
+ /**
+ * Duration of the recipe
+ */
+ private int duration = 0;
+ /**
+ * A supplier used for machines which have a custom way of calculating base duration, like Neutron Activator
+ */
+ private Supplier<Double> durationUnderOneTickSupplier;
+ /**
+ * The parallel the machine has when trying to overclock
+ */
+ private int parallel = 1;
+ // endregion
+ // region extra factors
+ /**
+ * Discount for EUt at the beginning of calculating overclocks, like GT++ machines
+ */
+ private double eutDiscount = 1;
+ /**
+ * Speeding/Slowing up/down the duration of a recipe at the beginning of calculating overclocks, like GT++ machines
+ */
+ private double speedBoost = 1;
+ // endregion
+ // region overclock parameters
+ /**
+ * How much the energy would be multiplied by per overclock available
+ */
+ private double eutIncreasePerOC = 4;
+ /**
+ * A supplier used for machines which have a custom way of calculating energy increase multipliers for every
+ * overclock, like Advanced Assembling Line
+ */
+ private Function<Integer, Double> eutIncreasePerOCSupplier = getDefaultEutIncreasePerOCSupplier();
+ /**
+ * How much the duration would be divided by per overclock made that isn't an overclock from HEAT
+ */
+ private double durationDecreasePerOC = 2;
+ /**
+ * A supplier used for machines which have a custom way of calculating duration decrease multipliers for every
+ * overclock
+ */
+ private Function<Integer, Double> durationDecreasePerOCSupplier = getDefaultDurationDecreasePerOCSupplier();
+ /**
+ * Whether at least one of {@link #eutIncreasePerOCSupplier} and {@link #durationDecreasePerOCSupplier} has been set
+ */
+ private boolean hasAtLeastOneSupplierBeenSet;
+ /**
+ * Whether to give EUt Discount when the duration goes below one tick
+ */
+ private boolean oneTickDiscount;
+ /**
+ * Whether the multi should use amperage to overclock normally.
+ */
+ private boolean amperageOC;
+ /**
+ * If the OC calculator should only do a given amount of overclocks. Mainly used in fusion reactors
+ */
+ private boolean limitOverclocks;
+ /**
+ * Maximum amount of overclocks to perform, when limitOverclocks = true
+ */
+ private int maxOverclocks;
+ /**
+ * How many overclocks have been performed
+ */
+ private int overclockCount;
+ /**
+ * Should we actually try to calculate overclocking
+ */
+ private boolean noOverclock;
+ /**
+ * The parallel the machine actually used.
+ */
+ private int currentParallel;
+ // endregion
+ // region heat overclock
+ /**
+ * The min heat required for the recipe
+ */
+ private int recipeHeat = 0;
+ /**
+ * The heat the machine has when starting the recipe
+ */
+ private int machineHeat = 0;
+ /**
+ * How much the duration should be divided by for each 1800K above recipe heat
+ */
+ private double durationDecreasePerHeatOC = 4;
+ /**
+ * Whether to enable overclocking with heat like the EBF every 1800 heat difference
+ */
+ private boolean heatOC;
+ /**
+ * Whether to enable heat discounts every 900 heat difference
+ */
+ private boolean heatDiscount;
+ /**
+ * The value used for discount final eut per 900 heat
+ */
+ private double heatDiscountExponent = 0.95;
+ // endregion
+ // region result
+ /**
+ * variable to check whether the overclocks have been calculated
+ */
+ private boolean calculated;
+ /**
+ * The calculated duration result.
+ */
+ private int calculatedDuration;
+ /**
+ * The calculated energy consumption result.
+ */
+ private long calculatedConsumption;
+ // endregion
+ // region constants
+ private static final int HEAT_DISCOUNT_THRESHOLD = 900;
+ private static final int HEAT_PERFECT_OVERCLOCK_THRESHOLD = 1800;
+ private static final double LOG2 = Math.log(2);
+ // endregion
+ // endregion
+
+ /**
+ * Creates calculator that doesn't do OC at all. Will use recipe duration.
+ */
+ public static OverclockCalculator ofNoOverclock(@Nonnull GTRecipe recipe) {
+ return ofNoOverclock(recipe.mEUt, recipe.mDuration);
+ }
+
+ /**
+ * Creates calculator that doesn't do OC at all, with set duration.
+ */
+ public static OverclockCalculator ofNoOverclock(long eut, int duration) {
+ return new OverclockCalculator().setRecipeEUt(eut)
+ .setDuration(duration)
+ .setEUt(eut)
+ .setNoOverclock(true);
+ }
+
+ /**
+ * An Overclock helper for calculating overclocks in many different situations
+ */
+ public OverclockCalculator() {}
+
+ // region setters
+ /**
+ * @param recipeEUt Sets the Recipe's starting voltage
+ */
+ @Nonnull
+ public OverclockCalculator setRecipeEUt(long recipeEUt) {
+ this.recipeEUt = recipeEUt;
+ return this;
+ }
+
+ /**
+ * @param machineVoltage Sets the EUt that the machine can use. This is the voltage of the machine
+ */
+ @Nonnull
+ public OverclockCalculator setEUt(long machineVoltage) {
+ this.machineVoltage = machineVoltage;
+ return this;
+ }
+
+ /**
+ * @param duration Sets the duration of the recipe
+ */
+ @Nonnull
+ public OverclockCalculator setDuration(int duration) {
+ this.duration = duration;
+ return this;
+ }
+
+ /**
+ * @param machineAmperage Sets the Amperage that the machine can support
+ */
+ @Nonnull
+ public OverclockCalculator setAmperage(long machineAmperage) {
+ this.machineAmperage = machineAmperage;
+ return this;
+ }
+
+ /**
+ * Enables Perfect OC in calculation
+ */
+ @Nonnull
+ public OverclockCalculator enablePerfectOC() {
+ this.durationDecreasePerOC = 4;
+ return this;
+ }
+
+ /**
+ * Set if we should be calculating overclocking using EBF's perfectOC
+ */
+ @Nonnull
+ public OverclockCalculator setHeatOC(boolean heatOC) {
+ this.heatOC = heatOC;
+ return this;
+ }
+
+ /**
+ * Sets if we should add a heat discount at the end of calculating an overclock, just like the EBF
+ */
+ @Nonnull
+ public OverclockCalculator setHeatDiscount(boolean heatDiscount) {
+ this.heatDiscount = heatDiscount;
+ return this;
+ }
+
+ /**
+ * Sets the starting heat of the recipe
+ */
+ @Nonnull
+ public OverclockCalculator setRecipeHeat(int recipeHeat) {
+ this.recipeHeat = recipeHeat;
+ return this;
+ }
+
+ /**
+ * Sets the heat of the coils on the machine
+ */
+ @Nonnull
+ public OverclockCalculator setMachineHeat(int machineHeat) {
+ this.machineHeat = machineHeat;
+ return this;
+ }
+
+ /**
+ * Sets an EUtDiscount. 0.9 is 10% less energy. 1.1 is 10% more energy
+ */
+ @Nonnull
+ public OverclockCalculator setEUtDiscount(float aEUtDiscount) {
+ this.eutDiscount = aEUtDiscount;
+ return this;
+ }
+
+ /**
+ * Sets a Speed Boost for the multiblock. 0.9 is 10% faster. 1.1 is 10% slower
+ */
+ @Nonnull
+ public OverclockCalculator setSpeedBoost(float aSpeedBoost) {
+ this.speedBoost = aSpeedBoost;
+ return this;
+ }
+
+ /**
+ * Sets the parallel that the multiblock uses
+ */
+ @Nonnull
+ public OverclockCalculator setParallel(int aParallel) {
+ this.parallel = aParallel;
+ return this;
+ }
+
+ /**
+ * Sets the heat discount during OC calculation if HeatOC is used. Default: 0.95 = 5% discount Used like a EU/t
+ * Discount
+ */
+ @Nonnull
+ public OverclockCalculator setHeatDiscountMultiplier(float heatDiscountExponent) {
+ this.heatDiscountExponent = heatDiscountExponent;
+ return this;
+ }
+
+ /**
+ * Sets the Overclock that should be calculated when a heat OC is applied.
+ */
+ @Nonnull
+ public OverclockCalculator setHeatPerfectOC(double heatPerfectOC) {
+ if (heatPerfectOC <= 0) throw new IllegalArgumentException("Heat OC can't be a negative number or zero");
+ this.durationDecreasePerHeatOC = heatPerfectOC;
+ return this;
+ }
+
+ /**
+ * Sets the amount that the eut would be multiplied by per overclock. Do not set as 1(ONE) if the duration decrease
+ * is also 1(ONE)!
+ */
+ @Nonnull
+ public OverclockCalculator setEUtIncreasePerOC(double eutIncreasePerOC) {
+ if (eutIncreasePerOC <= 0)
+ throw new IllegalArgumentException("EUt increase can't be a negative number or zero");
+ this.eutIncreasePerOC = eutIncreasePerOC;
+ return this;
+ }
+
+ /**
+ * Sets the amount that the duration would be divided by per overclock. Do not set as 1(ONE) if the eut increase is
+ * also 1(ONE)!
+ */
+ @Nonnull
+ public OverclockCalculator setDurationDecreasePerOC(double durationDecreasePerOC) {
+ if (durationDecreasePerOC <= 0)
+ throw new IllegalArgumentException("Duration decrease can't be a negative number or zero");
+ this.durationDecreasePerOC = durationDecreasePerOC;
+ return this;
+ }
+
+ /**
+ * Set One Tick Discount on EUt based on Duration Decrease Per Overclock. This functions the same as single blocks.
+ */
+ @Nonnull
+ public OverclockCalculator setOneTickDiscount(boolean oneTickDiscount) {
+ this.oneTickDiscount = oneTickDiscount;
+ return this;
+ }
+
+ /**
+ * Limit the amount of overclocks that can be performed, regardless of how much power is available. Mainly used for
+ * fusion reactors.
+ */
+ @Nonnull
+ public OverclockCalculator limitOverclockCount(int maxOverclocks) {
+ this.limitOverclocks = true;
+ this.maxOverclocks = maxOverclocks;
+ return this;
+ }
+
+ @Nonnull
+ public OverclockCalculator setAmperageOC(boolean amperageOC) {
+ this.amperageOC = amperageOC;
+ return this;
+ }
+
+ /**
+ * Set a supplier for calculating custom duration for when its needed under one tick
+ */
+ @Nonnull
+ public OverclockCalculator setDurationUnderOneTickSupplier(Supplier<Double> supplier) {
+ this.durationUnderOneTickSupplier = supplier;
+ return this;
+ }
+
+ /**
+ * Sets if we should do overclocking or not
+ */
+ @Nonnull
+ public OverclockCalculator setNoOverclock(boolean noOverclock) {
+ this.noOverclock = noOverclock;
+ return this;
+ }
+
+ /**
+ * Set a supplier for calculating custom EUt increase multipliers for every overclock
+ */
+ public OverclockCalculator setEutIncreasePerOCSupplier(Function<Integer, Double> eutIncreasePerOCSupplier) {
+ this.eutIncreasePerOCSupplier = eutIncreasePerOCSupplier;
+ this.hasAtLeastOneSupplierBeenSet = true;
+ return this;
+ }
+
+ /**
+ * Set a supplier for calculating custom duration decrease multipliers for every overclock
+ */
+ public OverclockCalculator setDurationDecreasePerOCSupplier(
+ Function<Integer, Double> durationDecreasePerOCSupplier) {
+ this.durationDecreasePerOCSupplier = durationDecreasePerOCSupplier;
+ this.hasAtLeastOneSupplierBeenSet = true;
+ return this;
+ }
+
+ /**
+ * Set actually performed parallel
+ */
+ public OverclockCalculator setCurrentParallel(int currentParallel) {
+ this.currentParallel = currentParallel;
+ // Sets parallel to the actually performed one if machine's parallel is underused.
+ this.parallel = Math.min(parallel, currentParallel);
+ return this;
+ }
+
+ // endregion
+ // region calculate
+ /**
+ * Call this when all values have been put it.
+ */
+ @Nonnull
+ public OverclockCalculator calculate() {
+ if (calculated) {
+ throw new IllegalStateException("Tried to calculate overclocks twice");
+ }
+ calculateOverclock();
+ calculated = true;
+ return this;
+ }
+
+ private void calculateOverclock() {
+ double durationInDouble = durationUnderOneTickSupplier != null ? durationUnderOneTickSupplier.get()
+ : duration * speedBoost;
+ calculatedConsumption = recipeEUt;
+ double heatDiscountMultiplier = calculateHeatDiscountMultiplier();
+ // Usually a safeguard when currentParallel is not set: We assume parallel is fully used.
+ currentParallel = Math.max(currentParallel, parallel);
+
+ if (noOverclock) {
+ calculatedConsumption = calculateFinalRecipeEUt(heatDiscountMultiplier);
+ calculatedDuration = (int) Math.ceil(durationInDouble);
+ return;
+ }
+
+ // First we need to overclock to reach 1 tick.
+ // Then we need to overclock under one tick to get more extra parallels.
+ // We stop overclocking if we've already reached 1 tick and got enough parallels to actually perform.
+ double requiredUnderOneTickMultiplier = durationInDouble * currentParallel / parallel;
+ if (hasAtLeastOneSupplierBeenSet) { // custom overclock
+ double currentEutIncrease = eutIncreasePerOCSupplier.apply(overclockCount + 1);
+ double currentDurationDecrease = durationDecreasePerOCSupplier.apply(overclockCount + 1);
+ double machinePower = calculateMachinePower();
+ double currentConsumption = calculateRecipePower(heatDiscountMultiplier);
+ double currentUnderOneTickMultiplier = 1;
+ // Whether we have enough power for the next overclock;
+ // whether we need more overclock to reach 1 tick and get enough extra parallel;
+ // whether we have reached the overclock limit
+ while (machinePower > currentConsumption * currentEutIncrease
+ && requiredUnderOneTickMultiplier > currentUnderOneTickMultiplier
+ && (!limitOverclocks || overclockCount < maxOverclocks)) {
+ currentConsumption *= currentEutIncrease;
+ durationInDouble /= currentDurationDecrease;
+ overclockCount++;
+ currentEutIncrease = eutIncreasePerOCSupplier.apply(overclockCount + 1);
+ currentDurationDecrease = durationDecreasePerOCSupplier.apply(overclockCount + 1);
+ }
+ calculatedConsumption = (long) Math.max(currentConsumption, 1);
+ calculatedDuration = (int) Math.max(durationInDouble, 1);
+ } else { // general overclock
+ double recipePowerTier = calculateRecipePowerTier(heatDiscountMultiplier);
+ double machinePowerTier = calculateMachinePowerTier();
+
+ int maxOverclockCount = calculateAmountOfOverclocks(machinePowerTier, recipePowerTier);
+ if (limitOverclocks) maxOverclockCount = Math.min(maxOverclocks, maxOverclockCount);
+ if (!amperageOC) {
+ // Limit overclocks by voltage tier.
+ maxOverclockCount = Math.min(maxOverclockCount, calculateRecipeToMachineVoltageDifference());
+ }
+ overclockCount = calculateAmountOfNeededOverclocks(maxOverclockCount, requiredUnderOneTickMultiplier);
+
+ // If triggered, it indicates that recipe power > machine power.
+ // Not just a safeguard. This also means that you can run a 1.2A recipe on a single hatch for a regular gt
+ // multi.
+ // This is intended, including the fact that you don't get an OC with a one tier upgrade in that case.
+ overclockCount = Math.max(overclockCount, 0);
+
+ int heatOverclockCount = Math.min(calculateMaxAmountOfHeatOverclocks(), overclockCount);
+ calculatedConsumption = (long) Math.floor(recipeEUt * Math.pow(eutIncreasePerOC, overclockCount));
+ durationInDouble /= Math.pow(durationDecreasePerHeatOC, heatOverclockCount)
+ * Math.pow(durationDecreasePerOC, overclockCount - heatOverclockCount);
+ if (oneTickDiscount) {
+ calculatedConsumption = (long) Math
+ .floor(calculatedConsumption / Math.pow(durationDecreasePerOC, maxOverclockCount - overclockCount));
+ calculatedConsumption = Math.max(calculatedConsumption, 1);
+ }
+ calculatedConsumption = calculateFinalRecipeEUt(heatDiscountMultiplier);
+ calculatedDuration = (int) Math.max(durationInDouble, 1);
+ }
+ }
+
+ private double calculateRecipePower(double heatDiscountMultiplier) {
+ return recipeEUt * parallel * eutDiscount * heatDiscountMultiplier;
+ }
+
+ private double calculateRecipePowerTier(double heatDiscountMultiplier) {
+ return calculatePowerTier(calculateRecipePower(heatDiscountMultiplier));
+ }
+
+ private double calculateMachinePower() {
+ return machineVoltage * (amperageOC ? machineAmperage : Math.min(machineAmperage, parallel));
+ }
+
+ private double calculateMachinePowerTier() {
+ return calculatePowerTier(calculateMachinePower());
+ }
+
+ private int calculateRecipeToMachineVoltageDifference() {
+ return (int) (Math.ceil(calculatePowerTier(machineVoltage)) - Math.ceil(calculatePowerTier(recipeEUt)));
+ }
+
+ private double calculatePowerTier(double voltage) {
+ return 1 + Math.max(0, (Math.log(voltage) / LOG2) - 5) / 2;
+ }
+
+ private long calculateFinalRecipeEUt(double heatDiscountMultiplier) {
+ return (long) Math.ceil(calculatedConsumption * eutDiscount * heatDiscountMultiplier * parallel);
+ }
+
+ private int calculateMaxAmountOfHeatOverclocks() {
+ return heatOC ? (machineHeat - recipeHeat) / HEAT_PERFECT_OVERCLOCK_THRESHOLD : 0;
+ }
+
+ /**
+ * Calculate maximum possible overclocks ignoring if we are going to go under 1 tick
+ */
+ private int calculateAmountOfOverclocks(double machinePowerTier, double recipePowerTier) {
+ return (int) (machinePowerTier - recipePowerTier);
+ }
+
+ private int calculateAmountOfNeededOverclocks(int maxOverclockCount, double requiredUnderOneTickMultiplier) {
+ int neededHeatOC = (int) Math.min(
+ calculateMaxAmountOfHeatOverclocks(),
+ Math.ceil(Math.log(requiredUnderOneTickMultiplier) / Math.log(durationDecreasePerHeatOC)));
+ neededHeatOC = Math.max(neededHeatOC, 0);
+ int neededNormalOC = (int) Math.ceil(
+ (Math.log(requiredUnderOneTickMultiplier) - Math.log(durationDecreasePerHeatOC) * neededHeatOC)
+ / Math.log(durationDecreasePerOC));
+ neededNormalOC = Math.max(neededNormalOC, 0);
+ return Math.min(maxOverclockCount, neededHeatOC + neededNormalOC);
+ }
+
+ private double calculateHeatDiscountMultiplier() {
+ int heatDiscounts = heatDiscount ? (machineHeat - recipeHeat) / HEAT_DISCOUNT_THRESHOLD : 0;
+ return Math.pow(heatDiscountExponent, heatDiscounts);
+ }
+
+ // endregion
+ // region result getters
+ /**
+ * @return The consumption after overclock has been calculated
+ */
+ public long getConsumption() {
+ if (!calculated) {
+ throw new IllegalStateException("Tried to get consumption before calculating");
+ }
+ return calculatedConsumption;
+ }
+
+ /**
+ * @return The duration of the recipe after overclock has been calculated
+ */
+ public int getDuration() {
+ if (!calculated) {
+ throw new IllegalStateException("Tried to get duration before calculating");
+ }
+ return calculatedDuration;
+ }
+
+ /**
+ * @return Number of performed overclocks
+ */
+ public int getPerformedOverclocks() {
+ if (!calculated) {
+ throw new IllegalStateException("Tried to get performed overclocks before calculating");
+ }
+ return overclockCount;
+ }
+
+ /**
+ * @return Whether the calculation has happened
+ */
+ public boolean getCalculationStatus() {
+ return calculated;
+ }
+
+ // endregion
+ // region misc
+ /**
+ * Returns duration as a double to show how much it is overclocking too much to determine extra parallel. This
+ * doesn't count as calculating
+ */
+ public double calculateDurationUnderOneTick() {
+ double durationInDouble = durationUnderOneTickSupplier != null ? durationUnderOneTickSupplier.get()
+ : duration * speedBoost;
+ if (noOverclock) return durationInDouble;
+ double heatDiscountMultiplier = calculateHeatDiscountMultiplier();
+ if (hasAtLeastOneSupplierBeenSet) {
+ int overclockCount = 0;
+ double currentEutIncrease = eutIncreasePerOCSupplier.apply(overclockCount + 1);
+ double currentDurationDecrease = durationDecreasePerOCSupplier.apply(overclockCount + 1);
+ double machinePower = calculateMachinePower();
+ double recipePower = calculateRecipePower(heatDiscountMultiplier);
+ while (machinePower > recipePower * currentEutIncrease
+ && (!limitOverclocks || overclockCount < maxOverclocks)) {
+ recipePower *= currentEutIncrease;
+ durationInDouble /= currentDurationDecrease;
+ overclockCount++;
+ currentEutIncrease = eutIncreasePerOCSupplier.apply(overclockCount + 1);
+ currentDurationDecrease = durationDecreasePerOCSupplier.apply(overclockCount + 1);
+ }
+ } else {
+ int maxOverclockCount = calculateAmountOfOverclocks(
+ calculateMachinePowerTier(),
+ calculateRecipePowerTier(heatDiscountMultiplier));
+ if (limitOverclocks) maxOverclockCount = Math.min(maxOverclocks, maxOverclockCount);
+ int heatOverclocks = Math.min(calculateMaxAmountOfHeatOverclocks(), maxOverclockCount);
+ durationInDouble /= Math.pow(durationDecreasePerOC, maxOverclockCount - heatOverclocks)
+ * Math.pow(durationDecreasePerHeatOC, heatOverclocks);
+ }
+ return durationInDouble;
+ }
+
+ private Function<Integer, Double> getDefaultEutIncreasePerOCSupplier() {
+ return overclockCount -> eutIncreasePerOC;
+ }
+
+ private Function<Integer, Double> getDefaultDurationDecreasePerOCSupplier() {
+ return overclockCount -> overclockCount <= calculateMaxAmountOfHeatOverclocks() ? durationDecreasePerHeatOC
+ : durationDecreasePerOC;
+ }
+
+ // endregion
+}