diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java index b5006eb..7122621 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java @@ -502,6 +502,31 @@ public interface EasyGiantsFoundryConfig extends Config description = "Advanced Settings", position = 5 ) - String generalSettings = "generalSettings"; + String advancedSettings = "generalSettings"; + + @ConfigItem( + keyName = "heatActionBuffer", // renamed to reset player's settings for previous bugged implementation + name = "Lava/Waterfall Padding Ticks", + description = "Units in ticks; buffers more than optimal heat when in lava/waterfall calculations to compensate for heat decay when the player is afk or running/walking slower than optimal.", + position = 0, + section = advancedSettings + ) + default int heatActionPadTicks() + { + return 3; + } + + @ConfigItem( + keyName = "debugging", + name = "Show Debugging", + description = "Shows debugging visuals used for development", + position = 0, + section = advancedSettings, + warning = "Only used for development." + ) + default boolean debugging() + { + return false; + } } diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java index 2b7e404..2027807 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java @@ -290,27 +290,27 @@ public class EasyGiantsFoundryPlugin extends Plugin // start the HeatActionStateMachine when varbit begins to update in onVarbitChanged() if (event.getMenuOption().startsWith("Heat-preform")) { - state.heatingCoolingState.stop(); - state.heatingCoolingState.setup(7, 0, "heats"); + state.heatActionStateMachine.stop(); + state.heatActionStateMachine.setup(false, true, "heats"); } else if (event.getMenuOption().startsWith("Dunk-preform")) { - state.heatingCoolingState.stop(); - state.heatingCoolingState.setup(27, 2, "dunks"); + state.heatActionStateMachine.stop(); + state.heatActionStateMachine.setup(true, true, "dunks"); } else if (event.getMenuOption().startsWith("Cool-preform")) { - state.heatingCoolingState.stop(); - state.heatingCoolingState.setup(-7, 0, "cools"); + state.heatActionStateMachine.stop(); + state.heatActionStateMachine.setup(false, false, "cools"); } else if (event.getMenuOption().startsWith("Quench-preform")) { - state.heatingCoolingState.stop(); - state.heatingCoolingState.setup(-27, -2, "quenches"); + state.heatActionStateMachine.stop(); + state.heatActionStateMachine.setup(true, false, "quenches"); } - else // canceled heating/cooling, stop the heating state-machine + else if (!state.heatActionStateMachine.isIdle()) // canceled heating/cooling, stop the heating state-machine { - state.heatingCoolingState.stop(); + state.heatActionStateMachine.stop(); } } @@ -333,8 +333,8 @@ public class EasyGiantsFoundryPlugin extends Plugin // show mould score on Mould UI Title Widget mouldParent = client.getWidget(47054850); - Integer mouldScore = state.getMouldScore(); - if (mouldParent != null && mouldScore != null) + int mouldScore = state.getMouldScore(); + if (mouldParent != null && mouldScore >= 0) { Widget title = Objects.requireNonNull(mouldParent.getChild(1)); @@ -362,23 +362,40 @@ public class EasyGiantsFoundryPlugin extends Plugin state.setMouldScore(-1); } + // start the heating state-machine when the varbit updates // if heat varbit updated and the user clicked, start the state-machine - if (event.getVarbitId() == VARBIT_HEAT && state.heatingCoolingState.getActionName() != null) + if (event.getVarbitId() == VARBIT_HEAT) { // ignore passive heat decay, one heat per two ticks - if (event.getValue() - previousHeat != -1) + int delta = event.getValue() - previousHeat; + // sign check: num * num > 0 == same sign + if (delta != -1) { - // if the state-machine is idle, start it - if (state.heatingCoolingState.isIdle()) + if (state.heatActionStateMachine.getActionname() != null) { - state.heatingCoolingState.start(state, config, state.getHeatAmount()); + // if the state-machine is idle, start it + if (state.heatActionStateMachine.isIdle()) + { + state.heatActionStateMachine.start(state, config, previousHeat); + } + state.heatActionStateMachine.onTick(); } - state.heatingCoolingState.onTick(); + if (config.debugging()) + { + client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", + "Heat: " + event.getValue() + "" + + "Delta: " + delta + " " + + "Heating Ticks: " + state.heatActionStateMachine.heatingTicks + "" + + " Cooling Ticks: " + state.heatActionStateMachine.coolingTicks + "" + + " Remaining Ticks: " + state.heatActionStateMachine.getRemainingDuration(), ""); + } } + +// client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "Delta: " + delta + " ", ""); + previousHeat = event.getValue(); } - previousHeat = event.getValue(); } @Subscribe diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryState.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryState.java index 17bde7d..331b484 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryState.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryState.java @@ -335,5 +335,5 @@ public class EasyGiantsFoundryState return actions; } - public HeatActionStateMachine heatingCoolingState = new HeatActionStateMachine(); + public HeatActionStateMachine heatActionStateMachine = new HeatActionStateMachine(); } diff --git a/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java b/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java index 4987835..afaea7e 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java @@ -32,6 +32,7 @@ public class FoundryOverlay3D extends Overlay { private static final int HAND_IN_WIDGET = 49414221; + private static final int CRUCIBLE_CAPACITY = 28; private final ModelOutlineRenderer modelOutlineRenderer; GameObject tripHammer; @@ -132,7 +133,7 @@ public class FoundryOverlay3D extends Overlay Stage stage = state.getCurrentStage(); GameObject stageObject = getStageObject(stage); - if (stageObject == null) + if (stageObject == null || graphics == null) { return null; } @@ -151,21 +152,13 @@ public class FoundryOverlay3D extends Overlay drawObjectOutline(graphics, stageObject, color); } - if ((stage.getHeat() != heat || !state.heatingCoolingState.isIdle()) && config.highlightWaterAndLava()) + // !state.heatingCoolingState.isIdle() + // if the stage heat is already in range, but player still wants to do heat changes + if ((stage.getHeat() != heat || !state.heatActionStateMachine.isIdle()) && config.highlightWaterAndLava()) { drawHeatChangers(graphics); } - if (state.heatingCoolingState.isCooling()) - { - drawHeatChangerOverlay(graphics, waterfall); - } - if (state.heatingCoolingState.isHeating()) - { - drawHeatChangerOverlay(graphics, lavaPool); - } - - return null; } @@ -195,26 +188,87 @@ public class FoundryOverlay3D extends Overlay modelOutlineRenderer.drawOutline(stageObject, config.borderThickness(), _color, config.borderFeather()); } - private void drawHeatChangerOverlay( + private void drawHeatChangerPreviewOverlay( Graphics2D graphics, - GameObject stageObject + GameObject stageObject, + boolean isLava ) { - if (!config.drawLavaWaterInfoOverlay()) - { - return; - } - if (state.heatingCoolingState.isIdle()) - { - return; - } + int sign = isLava ? 1 : -1; + int fastVelocity = 27 * sign; + int slowVelocity = 7 * sign; + int fastAccelBonus = 2 * sign; + int slowAccelBonus = 0; + + HeatActionSolver.DurationResult fastResult = + HeatActionSolver.solve( + state.getCurrentStage(), + state.getCurrentHeatRange(), + state.getActionsLeftInStage(), + state.getHeatAmount(), + true, + isLava, + config.heatActionPadTicks() * 2 + ); + + final int fastDuration = fastResult.getDuration(); + HeatActionSolver.DurationResult slowResult = + HeatActionSolver.solve( + state.getCurrentStage(), + state.getCurrentHeatRange(), + state.getActionsLeftInStage(), + state.getHeatAmount(), + false, + isLava, + config.heatActionPadTicks() * 2 + ); + final int slowDuration = slowResult.getDuration(); + + final String fastName = isLava ? "dunks" : "quenches"; + final String slowName = isLava ? "heats" : "cools"; String text; - text = String.format("%d %s", - state.heatingCoolingState.getRemainingDuration(), - state.heatingCoolingState.getActionName() - ); + if (config.debugging()) + { + text = String.format("%d %s (predicted: %d) or %d %s (predicted: %d) (overshoot: %s goal-in-range: %s)", + fastDuration, fastName, fastResult.getPredictedHeat(), slowDuration, slowName, slowResult.getPredictedHeat(), slowResult.isOvershooting(), fastResult.isGoalInRange()); + } + else + { + text = String.format("%d %s or %d %s ", + fastDuration, fastName, slowDuration, slowName); + } + + LocalPoint stageLoc = stageObject.getLocalLocation(); + stageLoc = new LocalPoint(stageLoc.getX(), stageLoc.getY()); + + Point pos = Perspective.getCanvasTextLocation(client, graphics, stageLoc, text, 50); + Color color = config.lavaWaterfallColour(); + + OverlayUtil.renderTextLocation(graphics, pos, text, color); + } + + private void drawHeatChangerOverlay(Graphics2D graphics, GameObject stageObject) + { + + String text; + if (config.debugging()) + { + text = String.format("%d %s (overshoot: %s) [goal-in-range: %s]", + state.heatActionStateMachine.getRemainingDuration(), + state.heatActionStateMachine.getActionname(), + state.heatActionStateMachine.isOverShooting(), + state.heatActionStateMachine.isGoalInRange() + ); + } + else + { + text = String.format("%d %s", + state.heatActionStateMachine.getRemainingDuration(), + state.heatActionStateMachine.getActionname() + ); + } LocalPoint stageLoc = stageObject.getLocalLocation(); stageLoc = new LocalPoint(stageLoc.getX(), stageLoc.getY()); @@ -230,11 +284,13 @@ public class FoundryOverlay3D extends Overlay int change = state.getHeatChangeNeeded(); Shape shape = null; - if (change < 0 || state.heatingCoolingState.isCooling()) + boolean isLava = change > 0; + boolean isWaterfall = change < 0; + if (isWaterfall || state.heatActionStateMachine.isCooling()) { shape = waterfall.getClickbox(); } - else if (change > 0 || state.heatingCoolingState.isHeating()) + else if (isLava || state.heatActionStateMachine.isHeating()) { shape = lavaPool.getClickbox(); } @@ -254,9 +310,28 @@ public class FoundryOverlay3D extends Overlay graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20)); graphics.fill(shape); } + + if (config.drawLavaWaterInfoOverlay()) + { + if (state.heatActionStateMachine.isCooling()) + { + drawHeatChangerOverlay(graphics, waterfall); + } + else if (isWaterfall) + { + drawHeatChangerPreviewOverlay(graphics, waterfall, false); + } + if (state.heatActionStateMachine.isHeating()) + { + drawHeatChangerOverlay(graphics, lavaPool); + } + else if (isLava) + { + drawHeatChangerPreviewOverlay(graphics, lavaPool, true); + } + } } - static final int CRUCIBLE_CAPACITY = 28; private void drawCrucibleContent(Graphics2D graphics) { @@ -430,6 +505,8 @@ public class FoundryOverlay3D extends Overlay private void drawActionOverlay(Graphics2D graphics, GameObject gameObject) { + + int actionsLeft = state.getActionsLeftInStage(); int heatLeft = state.getActionsForHeatLevel(); @@ -440,6 +517,10 @@ public class FoundryOverlay3D extends Overlay LocalPoint textLocation = gameObject.getLocalLocation(); textLocation = new LocalPoint(textLocation.getX(), textLocation.getY()); Point canvasLocation = Perspective.getCanvasTextLocation(client, graphics, textLocation, text, 250); + if (canvasLocation == null) + { + return; + } OverlayUtil.renderTextLocation(graphics, canvasLocation, text, getHeatColor(actionsLeft, heatLeft)); } if (config.drawActionLeftOverlay()) @@ -449,6 +530,10 @@ public class FoundryOverlay3D extends Overlay LocalPoint textLocation = gameObject.getLocalLocation(); textLocation = new LocalPoint(textLocation.getX(), textLocation.getY()); Point canvasLocation = Perspective.getCanvasTextLocation(client, graphics, textLocation, text, 250); + if (canvasLocation == null) + { + return; + } canvasLocation = new Point(canvasLocation.getX(), canvasLocation.getY() + 10); OverlayUtil.renderTextLocation(graphics, canvasLocation, text, getHeatColor(actionsLeft, heatLeft)); } diff --git a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java index 55e9ee6..bf33807 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java @@ -3,13 +3,16 @@ package com.toofifty.easygiantsfoundry; //import java.util.ArrayList; //import java.util.List; +import com.toofifty.easygiantsfoundry.enums.Stage; +import lombok.Value; + /** * Solves the heating/cooling action and predicts tick duration (index) * the naming convention is focused on the algorithm rather than in-game terminology for the context. *

* the dx_n refers to successive derivatives of an ordinary-differential-equations * https://en.wikipedia.org/wiki/Ordinary_differential_equation - * also known as distance (dx0), speed (dx1), and acceleration (dx2). + * also known as position (dx0), speed (dx1), and acceleration (dx2). *

* dx0 - players current heat at tick * dx1 - dx0_current - dx0_last_tick, aka the first derivative @@ -18,7 +21,17 @@ package com.toofifty.easygiantsfoundry; * for context, here's what dx1 extracted directly from in-game dunking looks like. * the purpose of the HeatActionSolver.java is to accurately model this data. * int[] dx1 = { - * 27, + * 7, + * 8, + * 9, + * 11, + * 13, + * 15, + * 17, + * 19, + * 21, + * 24, + * 27, -- dunk/quench starts here * 30, * 33, * 37, @@ -77,156 +90,254 @@ subscribe(VarbitChanged.class, ev -> public class HeatActionSolver { - /** - *

Warning: this method prefers overshooting goal. For example, if goal is 957, - * it will return index that reaches >957.

- * - *

This may be desirable if we're aiming to heat just over range minimum; - * for example if the stage is heating (grind stone),

- * - *

but undesirable when heating to just below range maximum; - * for example if the stage is cooling (hammer.)

- * - *

- * Make sure to subtract 1 tick from duration, if so. - *

- * - * - * - * @param goal the desired heat destination - * @param dx1_init initial speed of heating/cooling. currently 7 for heat/cool, 27 for dunk/quench. - * @param dx2_offset bonus acceleration. currently, 0 for heat/cool, 2 for dunk/quench. - * @return Index here refers to tick. So an index of 10 means the goal can be reached in 10 ticks. - */ - public static int findDuration(int goal, int dx1_init, int dx2_offset) + public static final int[] DX_1 = new int[]{ + 7, + 8, + 9, + 11, + 13, + 15, + 17, + 19, + 21, + 24, + 27, // -- dunk/quench starts here + 30, + 33, + 37, + 41, + 45, + 49, + 53, + 57, + 62, + 67, + 72, + 77, + 83, + 89, + 95, + 91, // last one will always overshoot 1000 + }; + public static final int MAX_INDEX = DX_1.length; + public static final int FAST_INDEX = 10; + + @Value(staticConstructor = "of") + public static class SolveResult { + int index; + int dx0; + int dx1; + int dx2; + } + + private static SolveResult heatingSolve(int start, int goal, boolean overshoot, int max, boolean isFast) + { + return relativeSolve(goal - start, overshoot, max - start, isFast, -1); + } + + private static SolveResult coolingSolve(int start, int goal, boolean overshoot, int min, boolean isFast) + { + return relativeSolve(start - goal, overshoot, start - min, isFast, 1); + } + + private static SolveResult relativeSolve(int goal, boolean overshoot, int max, boolean isFast, int decayValue) + { + + int index = isFast ? FAST_INDEX : 0; int dx0 = 0; - int dx1 = dx1_init; - int count_index = 0; - for (int dx2 = 1; dx0 <= goal; dx2++) - { // Start from 1 up to the count inclusive - int repetitions; - if (dx2 == 1) + + boolean decay = false; + + while (true) { + + if (index >= MAX_INDEX) { - repetitions = 2; // The first number appears twice + break; } - else if (dx2 % 2 == 0) + + if (!overshoot && dx0 + DX_1[index] > goal) { - repetitions = 6; // Even numbers appear six times + break; } - else + else if (overshoot && dx0 >= goal) { - repetitions = 4; // Odd numbers (after 1) appear four times + break; } - for (int j = 0; j < repetitions && dx0 <= goal; j++) + + if (dx0 + DX_1[index] >= max) { - dx0 += dx1; - dx1 += dx2 + dx2_offset; // Sum the current number 'repetitions' times - count_index += 1; + break; } + + if (decay) + { + dx0 -= decayValue; + } + + dx0 += DX_1[index]; + ++index; + decay = !decay; } - return count_index; + + if (isFast) + { + index -= FAST_INDEX; + } + + return SolveResult.of(index, dx0, DX_1[index], -1); } - /** - * We can use the pattern to get the dx2 at a specific index numerically - * - * @param index the index/tick we want to calculate dx2 at - * @return the acceleration of heating/cooling at index/tick - */ - public static int getDx2AtIndex(int index) + @Value(staticConstructor = "of") + public static class DurationResult { - if (index <= 1) return 1; - - index -= 2; - // 0 1 2 3 4 5 6 7 8 9 - // e,e,e,e,e,e,o,o,o,o - - int block = index / 10; - int block_idx = index % 10; - int number = block * 2; - if (block_idx <= 5) - { - return number + 2; - } - else - { - return number + 3; - } + int duration; + boolean goalInRange; + boolean overshooting; + int predictedHeat; } - - /** - * We can use the pattern to get the dx1 at a specific index numerically - * - * @param index the index/tick we want to calculate the speed of heating/cooling - * @param constant the initial speed of heating/cooling. - * @return the speed of heating at index/tick - */ - public static int getDx1AtIndex(int index, int constant) + public static DurationResult solve( + Stage stage, + int[] range, + int actionLeftInStage, + int start, + boolean isFast, + boolean isActionHeating, + int padding) { - int _dx1 = constant; - for (int i = 0; i < index; ++i) + + final boolean isStageHeating = stage.isHeating(); + + // adding 1.8s/6ticks worth of padding so preform doesn't decay out of range + // average distance from lava+waterfall around 6 ticks + // preform decays 1 heat every 2 ticks + final int min = range[0] + padding; + final int max = range[1] + padding; + + final int actionsLeft_DeltaHeat = actionLeftInStage * stage.getHeatChange(); + + int estimatedDuration = 0; + + final boolean goalInRange; + boolean overshoot = false; + + SolveResult result = null; + + // case actions are all cooling, heating is mirrored version + + // goal: in-range // stage: heating + // overshoot goal + // <----------|stop|<---------------- heat + // ------|min|----------goal-------|max| + // stage ----------------> + + // goal: out-range // stage: heating + // undershoot min + // ...----------|stop|<--------------------- heat + // -goal---|min|---------------------|max| + // stage -----------------> + + // goal: in-range // stage: cooling + // undershoot goal + // <-------------------------|stop|<--------------- heat + // ------|min|----------goal-------|max| + // <---------------- stage + + // goal: out-range // stage: cooling + // overshoot max + // <--------------------|stop|<--------------- heat + // --------|min|---------------------|max|----goal + // <---------------- stage + + if (isActionHeating) { - _dx1 += getDx2AtIndex(i); + int goal = min - actionsLeft_DeltaHeat; + goalInRange = goal >= min && goal <= max; + + if (isStageHeating) + { + + if (start <= max) + { + overshoot = !goalInRange; + + if (!goalInRange) + { + goal = min; + } + + result = heatingSolve(start, goal, overshoot, max, isFast); + + estimatedDuration = result.index; + } + } + else // cooling stage + { + // actionsLeft_DeltaHeat is negative here + if (start <= max) + { + overshoot = goalInRange; + + if (!goalInRange) + { + goal = max; + } + + result = heatingSolve(start, goal, overshoot, max, isFast); + + estimatedDuration = result.index; + } + } + } + else // cooling action + { + int goal = max - actionsLeft_DeltaHeat; + goalInRange = goal >= min && goal <= max; + + if (isStageHeating) + { + if (start >= min) + { + overshoot = goalInRange; + + if (!goalInRange) + { + goal = min; + } + + result = coolingSolve(start, goal, overshoot, min, isFast); + + estimatedDuration = result.index; + } + } + else // cooling stage cooling action + { + if (start >= min) + { + overshoot = !goalInRange; + if (!goalInRange) + { + goal = max; + } + + result = coolingSolve(start, goal, overshoot, min, isFast); + + estimatedDuration = result.index; + } + } } - return _dx1; + int dx0 = result == null ? 0 : result.dx0; + if (!isActionHeating) + { + dx0 *= -1; + } + + + return DurationResult.of(estimatedDuration, goalInRange, overshoot, start + dx0); } -// Methods below are functional, but only used to for debugging & development - -// public static int getDx0AtIndex(int index, int constant) -// { -// int dx0 = 0; -// int dx1 = getDx1AtIndex(0, constant); -// for (int i = 0; i < index; i++) -// { // Start from 1 up to the count inclusive -// int dx2 = getDx2AtIndex(i); -// dx1 += dx2; // Sum the current number 'repetitions' times -// dx0 += dx1; -// } -// return dx0; -// } - - // We iteratively generate dx2 into a list -// public static List generateDx2List(int count) -// { -// List pattern = new ArrayList<>(); // This will hold our pattern -// for (int n = 1, i = 0; i < count; n++) -// { // Start from 1 up to the count inclusive -// int repetitions; -// if (n == 1) -// { -// repetitions = 2; // The first number appears twice -// } else if (n % 2 == 0) -// { -// repetitions = 6; // Even numbers appear six times -// } else -// { -// repetitions = 4; // Odd numbers (after 1) appear four times -// } -// for (int j = 0; j < repetitions && i < count; j++, i++) -// { -// pattern.add(n); // Append the current number 'repetitions' times -// } -// } -// return pattern; -// } - -// public static int findDx0IndexContinue(int goal, int constant, int init_index) -// { -// int dx0 = getDx0AtIndex(init_index, constant); -// int dx1 = getDx1AtIndex(init_index, constant); -// int count_index = init_index; -// for (; dx0 <= goal; count_index++) -// { // Start from 1 up to the count inclusive -// int dx2 = getDx2AtIndex(count_index); -// dx1 += dx2; // Sum the current number 'repetitions' times -// dx0 += dx1; -// } -// return count_index - init_index; -// } } diff --git a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java index d18b443..127ed4e 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java @@ -1,6 +1,5 @@ package com.toofifty.easygiantsfoundry; -import com.toofifty.easygiantsfoundry.enums.Stage; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -14,46 +13,45 @@ public class HeatActionStateMachine /** * Tick counter for heating, -1 means not currently heating. */ - int HeatingTicks = -1; + int heatingTicks = -1; /** * Tick counter for cooling, -1 means not currently cooling. */ - int CoolingTicks = -1; + int coolingTicks = -1; - /** - * The velocity of the heating/cooling action. - */ - int Velocity; + boolean actionFast; - /** - * The acceleration bonus of the heating/cooling action. - */ - int AccelerationBonus; + boolean actionHeating; /** * The starting heat amount of the heating/cooling action. */ - int StartingHeat; + int startingHeat; /** * The estimated tick duration of the heating/cooling action. */ - int EstimatedDuration; + int estimatedDuration; /** * The goal heat amount of the heating/cooling action. */ - int GoalHeat = 0; + int goalHeat = 0; + + // debug + boolean goalInRange; + boolean isOverShooting; + int predictedHeat; /** * The last action the player clicked on. Used for ui overlay to display. * When null, the state-machine will stop() and reset. */ - String ActionName = null; + String actionname = null; - private EasyGiantsFoundryState State; - private EasyGiantsFoundryConfig Config; + private EasyGiantsFoundryState state; + private EasyGiantsFoundryConfig config; /** * Start the state-machine with the given parameters. @@ -69,21 +67,22 @@ public class HeatActionStateMachine public void start(EasyGiantsFoundryState state, EasyGiantsFoundryConfig config, int startingHeat) { // use Velocity to determine if heating or cooling - if (Velocity > 0) + if (actionHeating) { - HeatingTicks = 0; - CoolingTicks = -1; + heatingTicks = 0; + coolingTicks = -1; } else { - CoolingTicks = 0; - HeatingTicks = -1; + heatingTicks = -1; + coolingTicks = 0; } - StartingHeat = startingHeat - Velocity; - State = state; - Config = config; - calculateEstimates(); + this.startingHeat = startingHeat; + this.state = state; + this.config = config; + + updateEstimates(); } /** @@ -95,11 +94,11 @@ public class HeatActionStateMachine { if (isHeating()) { - return Math.max(0, EstimatedDuration - HeatingTicks); + return Math.max(0, (estimatedDuration - heatingTicks)); } else if (isCooling()) { - return Math.max(0, EstimatedDuration - CoolingTicks); + return Math.max(0, (estimatedDuration - coolingTicks)); } else { @@ -111,113 +110,26 @@ public class HeatActionStateMachine * Core logic. Runs once on {@link HeatActionStateMachine#start} and assumes synchronization with the game. * Calculate the estimated duration and goal heat amount of the heating/cooling action. */ - public void calculateEstimates() + public void updateEstimates() { - // 0: left/min 1: right/max - int[] range = State.getCurrentHeatRange(); - int stageMin = range[0]; - int stageMax = range[1]; - Stage stage = State.getCurrentStage(); - int actionsLeft = State.getActionsLeftInStage(); - int actionsLeft_DeltaHeat = (actionsLeft+1) * stage.getHeatChange(); + HeatActionSolver.DurationResult result = + HeatActionSolver.solve( + getState().getCurrentStage(), + getState().getCurrentHeatRange(), + getState().getActionsLeftInStage(), + getStartingHeat(), + actionFast, + isHeating(), + config.heatActionPadTicks() * 2 + ); - if (isHeating()) - { - if (stage.isHeating()) - { - GoalHeat = Math.max(stageMin, stageMax - actionsLeft_DeltaHeat); - if (StartingHeat < GoalHeat) - { - int duration = HeatActionSolver.findDuration( - GoalHeat - StartingHeat, - Velocity, AccelerationBonus - ); + goalInRange = result.isGoalInRange(); + isOverShooting = result.isOvershooting(); - // compensate for heat decay during (1 heat every 2 ticks) - GoalHeat += duration / 2; + predictedHeat = result.getPredictedHeat(); - EstimatedDuration = HeatActionSolver.findDuration( - GoalHeat - StartingHeat, - Velocity, AccelerationBonus - ); - } - else // overheating - { - EstimatedDuration = 0; - } - } - else // is cooling - { - // actionsLeft_DeltaHeat is negative here - GoalHeat = Math.min(stageMax, stageMin - actionsLeft_DeltaHeat); - if (StartingHeat < GoalHeat) - { - int duration = HeatActionSolver.findDuration( - GoalHeat - StartingHeat, - Velocity, AccelerationBonus - ) - 1; - - GoalHeat -= duration / 2; - - EstimatedDuration = HeatActionSolver.findDuration( - GoalHeat - StartingHeat, - Velocity, AccelerationBonus - ) - 1; - } - else // cold enough - { - EstimatedDuration = 0; - } - } - } - else if (isCooling()) - { - if (stage.isHeating()) { - GoalHeat = Math.max(stageMin, stageMax - actionsLeft_DeltaHeat); - if (StartingHeat > GoalHeat) - { - int duration = HeatActionSolver.findDuration( - StartingHeat - GoalHeat, - Math.abs(Velocity), Math.abs(AccelerationBonus) - ) - 1; - - GoalHeat += duration / 2; - - EstimatedDuration = HeatActionSolver.findDuration( - (StartingHeat - GoalHeat), - Math.abs(Velocity), Math.abs(AccelerationBonus) - ) - 1; - } - else - { - EstimatedDuration = 0; - } - } - // Heating Stage - else { - GoalHeat = Math.max(stageMax, stageMin + actionsLeft_DeltaHeat); - if (StartingHeat > GoalHeat) // too hot - { - int duration = HeatActionSolver.findDuration( - StartingHeat - GoalHeat, - Math.abs(Velocity), Math.abs(AccelerationBonus) - ); - - GoalHeat -= duration / 2; - - EstimatedDuration = HeatActionSolver.findDuration( - StartingHeat - GoalHeat, - Math.abs(Velocity), Math.abs(AccelerationBonus) - ); - } - else // hot enough - { - EstimatedDuration = 0; - } - } - - } + estimatedDuration = result.getDuration(); } /** @@ -227,11 +139,11 @@ public class HeatActionStateMachine * @param accelerationBonus the acceleration bonus of the heating/cooling action. Usually 0 for slow, 2 for fast. * @param actionName the name of the action to display in the ui overlay */ - public void setup(int velocity, int accelerationBonus, String actionName) + public void setup(boolean isFast, boolean isHeating, String actionName) { - Velocity = velocity; - AccelerationBonus = accelerationBonus; - ActionName = actionName; + actionFast = isFast; + actionHeating = isHeating; + actionname = actionName; } /** @@ -239,9 +151,9 @@ public class HeatActionStateMachine */ public void stop() { - HeatingTicks = -1; - CoolingTicks = -1; - ActionName = null; + heatingTicks = -1; + coolingTicks = -1; + actionname = null; } /** @@ -251,7 +163,7 @@ public class HeatActionStateMachine */ public boolean isHeating() { - return HeatingTicks >= 0; + return heatingTicks >= 0; } /** @@ -261,7 +173,7 @@ public class HeatActionStateMachine */ public boolean isCooling() { - return CoolingTicks >= 0; + return coolingTicks >= 0; } /** @@ -279,25 +191,30 @@ public class HeatActionStateMachine */ public void onTick() { + if (isIdle()) return; + if (isHeating()) { - HeatingTicks++; - if (HeatingTicks >= EstimatedDuration) + if (heatingTicks >= estimatedDuration) { stop(); } + else + { + heatingTicks++; + } } - if (isCooling()) + else if (isCooling()) { - CoolingTicks++; - if (CoolingTicks >= EstimatedDuration) + if (coolingTicks >= estimatedDuration) { stop(); } + else + { + coolingTicks++; + } } -// log.info("\nReal Heat: " + State.getHeatAmount() -// + "\nGoal Heat - StartingHeat: " + (GoalHeat - StartingHeat) -// + "\nDuration: " + EstimatedDuration); } } diff --git a/src/test/java/com/toofifty/easygiantsfoundry/HeatSolverTest.java b/src/test/java/com/toofifty/easygiantsfoundry/HeatSolverTest.java deleted file mode 100644 index b017fad..0000000 --- a/src/test/java/com/toofifty/easygiantsfoundry/HeatSolverTest.java +++ /dev/null @@ -1,180 +0,0 @@ -package com.toofifty.easygiantsfoundry; - -import static org.junit.Assert.*; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; - -// playground to test HeatSolver -public class HeatSolverTest -{ -// @Test -// public void TestHeatSolver_dx2_Iterative() -// { -// final int[] answer = -// {1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6}; -// List produced = HeatActionSolver.generateDx2List(answer.length); -// -// System.err.println("Expected Length: " + answer.length + " Length: " + produced.size()); -// // print produced -// for (Integer integer : produced) -// { -// System.err.print(integer + ","); -// } -// System.err.println(); -// // compare -// for (int i = 0; i < answer.length; i++) -// { -// assertEquals("Asserting dx2 n=" + i, answer[i], produced.get(i).intValue()); -// } -// } - - @Test - public void TestHeatSolver_dx2_Numerical() - { - final int[] answer = - {1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6}; - // test getDx2AtIndex - for (int i = 0; i < answer.length; i++) - { - assertEquals("Asserting dx2 n=" + i, answer[i], HeatActionSolver.getDx2AtIndex(i)); - } - } - - @Test - public void TestHeatSolver_dx1() - { - final int c = 7; - final int[] answer = -// {1,1,2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6}; - {7, 8, 9, 11, 13, 15, 17, 19, 21, 24, 27, 30, 33, 37, 41, 45, 49, 53, 57, 62, 67, 72, 77, 83, 89}; - for (int i = 0; i < answer.length; i++) - { - assertEquals("Asserting dx1 n=" + i + " c=" + c + " answer=" + answer[i], answer[i], HeatActionSolver.getDx1AtIndex(i, c)); - } - } - - @Test - public void TestHeatSolver_dx2_from_groundtruth() - { - // runelite-shell script for retrieving heating/cooling delta. - // copy-paste into developer-tools -> Shell - - - // ground-truth answer from game - final int[] answer = - {7, 8, 9, 11, 13, 15, 17, 19, 21, 24, 27, 30, 33, 37, 41, 45, 49, 53, 57, 62, 67, 72, 77, 83, 89}; - for (int i = 0; i < answer.length - 1; i++) - { - System.err.print(answer[i + 1] - answer[i] + ","); - } - } - - @Test - public void TestHeatSolver_Dx0() - { - final int[] answer_dx1 = - {7, 8, 9, 11, 13, 15, 17, 19, 21, 24, 27, 30, 33, 37, 41, 45, 49, 53, 57, 62, 67, 72, 77, 83, 89}; - List answer_dx0 = new ArrayList<>(); - - - int sum = 0; - for (int i = 0; i < answer_dx1.length; i++) - { - sum += answer_dx1[i]; - answer_dx0.add(sum); - } - - System.err.println(answer_dx0); - - for (int i = 0; i < answer_dx1.length; i++) - { - TestHeatSolver_Dx0_Helper(answer_dx0.get(i), answer_dx0.get(0), i); - } - } - - @Test - public void TestHeatSolver_Dx0_Manual() - { - for (int i = 0; i < 50; i++) - { - System.err.println("[" + (350 + i) + "]" + HeatActionSolver.findDuration(350 + i, 7, 0)); - } - } - - @Test - public void TestHeatSolver_Dx0_2() - { -// 7->1,15->2,24->3,35->4,48->5,63->6,80->7,99->8,120->9,144->10,171->11,201->12,234->13,271->14,312->15,357->16,406->17,459->18,516->19,578->20,645->21,717->22,794->23,877->24,966->25 - final int[] answer_dx1 = - {7, 8, 9, 11, 13, 15, 17, 19, 21, 24, 27, 30, 33, 37, 41, 45, 49, 53, 57, 62, 67, 72, 77, 83, 89}; - List answer_dx0 = new ArrayList<>(); - - - int sum = 0; - for (int i = 0; i < answer_dx1.length; i++) - { - sum += answer_dx1[i]; - answer_dx0.add(sum); - } - - System.err.println(answer_dx0); - -// System.err.println( -// HeatSolver.findDx0IndexContinue(406, 7, 0)); -// System.err.println( -// HeatSolver.findDx0IndexContinue(406, 7, 10)); -// System.err.println( -// HeatSolver.findDx0IndexContinue(406, 7, 17)); -// -// System.err.println( -// HeatSolver.findDx0IndexContinue(1000, 7, 0)); - System.err.println( - HeatActionSolver.findDuration(957, 27, 2)); -// System.err.println( -// HeatActionSolver.findDuration(1000, 7, 1)); - } - - public void TestHeatSolver_Dx0_Helper(int dx0, int constant, int answer_index) - { - System.err.print(dx0 + "->" + HeatActionSolver.findDuration(dx0, constant, 0) + ","); - - // test calcDx0Index - assertEquals("Asserting dx0 for index " + answer_index, - answer_index, HeatActionSolver.findDuration(dx0, constant, 0)); - } - - @Test - public void Calc() - { - int[] dx1 = { - 27, - 30, - 33, - 37, - 41, - 45, - 49, - 53, - 57, - 62, - 67, - 72, - 77, - 83, - 89, - 95, - 91, - }; - - List dx2 = new ArrayList<>(); - for (int i = 0; i < dx1.length - 1; i++) - { - dx2.add(dx1[i+1] - dx1[i]); - } - - System.err.println(dx2); - } - -} \ No newline at end of file