From 9d7304a436fa973b376aa91da291ab90728bcb59 Mon Sep 17 00:00:00 2001 From: Louis Hong Date: Wed, 30 Oct 2024 22:24:30 -0700 Subject: [PATCH 01/13] comments and renaming for easier to read code --- .../EasyGiantsFoundryState.java | 2 +- .../easygiantsfoundry/FoundryOverlay3D.java | 6 ++--- .../easygiantsfoundry/HeatActionSolver.java | 22 +++++++++++++------ .../HeatActionStateMachine.java | 18 ++++++++------- .../easygiantsfoundry/HeatSolverTest.java | 10 ++++----- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryState.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryState.java index 45c6bf6..17bde7d 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryState.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryState.java @@ -44,7 +44,7 @@ public class EasyGiantsFoundryState @Setter @Getter - private int lastKnownCrucibleScore = -1; // will be set when "Pour"ed + private int lastKnownCrucibleScore = -1; // will be set when "Pour"ed (because the crucible will be empty then) private final List stages = new ArrayList<>(); private double heatRangeRatio = 0; diff --git a/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java b/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java index d93101d..4987835 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java @@ -158,11 +158,11 @@ public class FoundryOverlay3D extends Overlay if (state.heatingCoolingState.isCooling()) { - drawHeatingCoolingOverlay(graphics, waterfall); + drawHeatChangerOverlay(graphics, waterfall); } if (state.heatingCoolingState.isHeating()) { - drawHeatingCoolingOverlay(graphics, lavaPool); + drawHeatChangerOverlay(graphics, lavaPool); } @@ -195,7 +195,7 @@ public class FoundryOverlay3D extends Overlay modelOutlineRenderer.drawOutline(stageObject, config.borderThickness(), _color, config.borderFeather()); } - private void drawHeatingCoolingOverlay( + private void drawHeatChangerOverlay( Graphics2D graphics, GameObject stageObject ) diff --git a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java index e500b9d..55e9ee6 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java @@ -78,22 +78,30 @@ 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 over range minimum, - * but undesirable when cooling below range maximum; make sure to -1 the index if so. + *

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 init_dx1 initial speed of heating/cooling. currently 7 for heat/cool, 27 for dunk/quench. + * @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 findDx0Index(int goal, int init_dx1, int dx2_offset) + public static int findDuration(int goal, int dx1_init, int dx2_offset) { int dx0 = 0; - int dx1 = init_dx1; + int dx1 = dx1_init; int count_index = 0; for (int dx2 = 1; dx0 <= goal; dx2++) { // Start from 1 up to the count inclusive diff --git a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java index 00e0da6..d18b443 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java @@ -121,6 +121,7 @@ public class HeatActionStateMachine Stage stage = State.getCurrentStage(); int actionsLeft = State.getActionsLeftInStage(); int actionsLeft_DeltaHeat = (actionsLeft+1) * stage.getHeatChange(); + if (isHeating()) { if (stage.isHeating()) @@ -128,14 +129,15 @@ public class HeatActionStateMachine GoalHeat = Math.max(stageMin, stageMax - actionsLeft_DeltaHeat); if (StartingHeat < GoalHeat) { - int duration = HeatActionSolver.findDx0Index( + int duration = HeatActionSolver.findDuration( GoalHeat - StartingHeat, Velocity, AccelerationBonus ); + // compensate for heat decay during (1 heat every 2 ticks) GoalHeat += duration / 2; - EstimatedDuration = HeatActionSolver.findDx0Index( + EstimatedDuration = HeatActionSolver.findDuration( GoalHeat - StartingHeat, Velocity, AccelerationBonus ); @@ -151,14 +153,14 @@ public class HeatActionStateMachine GoalHeat = Math.min(stageMax, stageMin - actionsLeft_DeltaHeat); if (StartingHeat < GoalHeat) { - int duration = HeatActionSolver.findDx0Index( + int duration = HeatActionSolver.findDuration( GoalHeat - StartingHeat, Velocity, AccelerationBonus ) - 1; GoalHeat -= duration / 2; - EstimatedDuration = HeatActionSolver.findDx0Index( + EstimatedDuration = HeatActionSolver.findDuration( GoalHeat - StartingHeat, Velocity, AccelerationBonus ) - 1; @@ -175,14 +177,14 @@ public class HeatActionStateMachine GoalHeat = Math.max(stageMin, stageMax - actionsLeft_DeltaHeat); if (StartingHeat > GoalHeat) { - int duration = HeatActionSolver.findDx0Index( + int duration = HeatActionSolver.findDuration( StartingHeat - GoalHeat, Math.abs(Velocity), Math.abs(AccelerationBonus) ) - 1; GoalHeat += duration / 2; - EstimatedDuration = HeatActionSolver.findDx0Index( + EstimatedDuration = HeatActionSolver.findDuration( (StartingHeat - GoalHeat), Math.abs(Velocity), Math.abs(AccelerationBonus) ) - 1; @@ -197,14 +199,14 @@ public class HeatActionStateMachine GoalHeat = Math.max(stageMax, stageMin + actionsLeft_DeltaHeat); if (StartingHeat > GoalHeat) // too hot { - int duration = HeatActionSolver.findDx0Index( + int duration = HeatActionSolver.findDuration( StartingHeat - GoalHeat, Math.abs(Velocity), Math.abs(AccelerationBonus) ); GoalHeat -= duration / 2; - EstimatedDuration = HeatActionSolver.findDx0Index( + EstimatedDuration = HeatActionSolver.findDuration( StartingHeat - GoalHeat, Math.abs(Velocity), Math.abs(AccelerationBonus) ); diff --git a/src/test/java/com/toofifty/easygiantsfoundry/HeatSolverTest.java b/src/test/java/com/toofifty/easygiantsfoundry/HeatSolverTest.java index f5fb53b..b017fad 100644 --- a/src/test/java/com/toofifty/easygiantsfoundry/HeatSolverTest.java +++ b/src/test/java/com/toofifty/easygiantsfoundry/HeatSolverTest.java @@ -99,7 +99,7 @@ public class HeatSolverTest { for (int i = 0; i < 50; i++) { - System.err.println("[" + (350 + i) + "]" + HeatActionSolver.findDx0Index(350 + i, 7, 0)); + System.err.println("[" + (350 + i) + "]" + HeatActionSolver.findDuration(350 + i, 7, 0)); } } @@ -131,18 +131,18 @@ public class HeatSolverTest // System.err.println( // HeatSolver.findDx0IndexContinue(1000, 7, 0)); System.err.println( - HeatActionSolver.findDx0Index(957, 27, 2)); + HeatActionSolver.findDuration(957, 27, 2)); // System.err.println( -// HeatActionSolver.findDx0Index(1000, 7, 1)); +// HeatActionSolver.findDuration(1000, 7, 1)); } public void TestHeatSolver_Dx0_Helper(int dx0, int constant, int answer_index) { - System.err.print(dx0 + "->" + HeatActionSolver.findDx0Index(dx0, constant, 0) + ","); + System.err.print(dx0 + "->" + HeatActionSolver.findDuration(dx0, constant, 0) + ","); // test calcDx0Index assertEquals("Asserting dx0 for index " + answer_index, - answer_index, HeatActionSolver.findDx0Index(dx0, constant, 0)); + answer_index, HeatActionSolver.findDuration(dx0, constant, 0)); } @Test From 319f77af3cb3713a1472131d5f748da9b369940a Mon Sep 17 00:00:00 2001 From: Louis Hong Date: Sat, 2 Nov 2024 17:28:55 -0700 Subject: [PATCH 02/13] heat/cool prediction: continued important algorithm bug fix for prediction. Added configurable padding ticks for afk/inefficiency. --- .../EasyGiantsFoundryConfig.java | 27 +- .../EasyGiantsFoundryPlugin.java | 55 ++- .../EasyGiantsFoundryState.java | 2 +- .../easygiantsfoundry/FoundryOverlay3D.java | 143 +++++-- .../easygiantsfoundry/HeatActionSolver.java | 371 ++++++++++++------ .../HeatActionStateMachine.java | 211 +++------- .../easygiantsfoundry/HeatSolverTest.java | 180 --------- 7 files changed, 482 insertions(+), 507 deletions(-) delete mode 100644 src/test/java/com/toofifty/easygiantsfoundry/HeatSolverTest.java 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 From dd73d961c08b35b874f6738aa1debb096f933947 Mon Sep 17 00:00:00 2001 From: Louis Hong Date: Sat, 9 Nov 2024 01:28:17 -0800 Subject: [PATCH 03/13] stored preform stage added & heat/cool prediction state machine filters only gameobject actions previously, when preform is stored in storage, plugin assumes it's time to talk to kovac for new commision. previously, any menu action will interupt and stop the heat/cooling statemachine state. --- .../EasyGiantsFoundryClientIDs.java | 2 + .../EasyGiantsFoundryConfig.java | 12 +++ .../EasyGiantsFoundryPlugin.java | 79 ++++++++++++------- .../easygiantsfoundry/FoundryOverlay3D.java | 27 ++++++- 4 files changed, 87 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryClientIDs.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryClientIDs.java index 9420e33..537ae7e 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryClientIDs.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryClientIDs.java @@ -23,6 +23,8 @@ public class EasyGiantsFoundryClientIDs // 3 - protected static final int VARBIT_GAME_STAGE = 13914; + protected static final int VARBIT_PREFORM_STORED = 13947; + protected static final int WIDGET_HEAT_PARENT = 49414153; protected static final int WIDGET_LOW_HEAT_PARENT = 49414163; protected static final int WIDGET_MED_HEAT_PARENT = 49414164; diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java index 7122621..d02654e 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java @@ -247,6 +247,18 @@ public interface EasyGiantsFoundryConfig extends Config return true; } + @ConfigItem( + keyName = "storageHighlight", + name = "Highlight Preform Storage", + description = "Highlight Storage when it contains a preform.", + position = 10, + section = highlightList + ) + default boolean highlightStorage() + { + return true; + } + @ConfigSection( name = "Info Panel", description = "Settings for the Info Panel overlay", diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java index 2027807..427a075 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java @@ -17,6 +17,7 @@ import net.runelite.api.GameState; import net.runelite.api.InventoryID; import net.runelite.api.Item; import net.runelite.api.ItemContainer; +import net.runelite.api.MenuAction; import net.runelite.api.Skill; import net.runelite.api.events.GameObjectDespawned; import net.runelite.api.events.GameObjectSpawned; @@ -58,6 +59,7 @@ public class EasyGiantsFoundryPlugin extends Plugin private static final int CRUCIBLE = 44776; private static final int MOULD_JIG = 44777; + private static final int STORAGE = 44778; private static final int KOVAC_NPC = 11472; @@ -156,10 +158,47 @@ public class EasyGiantsFoundryPlugin extends Plugin case CRUCIBLE: overlay3d.crucible = gameObject; break; + case STORAGE: + overlay3d.storage = gameObject; + break; } } + @Subscribe + public void onGameObjectDespawned(GameObjectDespawned event) + { + GameObject gameObject = event.getGameObject(); + switch (gameObject.getId()) + { + case POLISHING_WHEEL: + state.setEnabled(false); + overlay3d.polishingWheel = null; + break; + case GRINDSTONE: + overlay3d.grindstone = null; + break; + case LAVA_POOL: + overlay3d.lavaPool = null; + break; + case WATERFALL: + overlay3d.waterfall = null; + break; + case TRIP_HAMMER: + overlay3d.tripHammer = null; + break; + case MOULD_JIG: + overlay3d.mouldJig = null; + break; + case CRUCIBLE: + overlay3d.crucible = null; + break; + case STORAGE: + overlay3d.storage = null; + break; + } + } + @Subscribe public void onGameStateChanged(GameStateChanged event) { @@ -202,36 +241,6 @@ public class EasyGiantsFoundryPlugin extends Plugin } } - @Subscribe - public void onGameObjectDespawned(GameObjectDespawned event) - { - GameObject gameObject = event.getGameObject(); - switch (gameObject.getId()) - { - case POLISHING_WHEEL: - state.setEnabled(false); - overlay3d.polishingWheel = null; - break; - case GRINDSTONE: - overlay3d.grindstone = null; - break; - case LAVA_POOL: - overlay3d.lavaPool = null; - break; - case WATERFALL: - overlay3d.waterfall = null; - break; - case TRIP_HAMMER: - overlay3d.tripHammer = null; - break; - case MOULD_JIG: - overlay3d.mouldJig = null; - break; - case CRUCIBLE: - overlay3d.crucible = null; - break; - } - } @Subscribe public void onNpcSpawned(NpcSpawned event) @@ -271,6 +280,16 @@ public class EasyGiantsFoundryPlugin extends Plugin @Subscribe public void onMenuOptionClicked(MenuOptionClicked event) { + + if (!(event.getMenuAction() == MenuAction.GAME_OBJECT_FIRST_OPTION + || event.getMenuAction() == MenuAction.GAME_OBJECT_SECOND_OPTION + || event.getMenuAction() == MenuAction.GAME_OBJECT_THIRD_OPTION + || event.getMenuAction() == MenuAction.GAME_OBJECT_FOURTH_OPTION + || event.getMenuAction() == MenuAction.GAME_OBJECT_FIFTH_OPTION)) + { + return; + } + if (!state.isEnabled()) return; if (event.getMenuTarget().contains("Crucible ")) diff --git a/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java b/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java index afaea7e..263041a 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java @@ -1,7 +1,6 @@ package com.toofifty.easygiantsfoundry; -import static com.toofifty.easygiantsfoundry.EasyGiantsFoundryClientIDs.VARBIT_GAME_STAGE; -import static com.toofifty.easygiantsfoundry.EasyGiantsFoundryClientIDs.WIDGET_PROGRESS_PARENT; +import static com.toofifty.easygiantsfoundry.EasyGiantsFoundryClientIDs.*; import static com.toofifty.easygiantsfoundry.EasyGiantsFoundryHelper.getHeatColor; import static com.toofifty.easygiantsfoundry.MouldHelper.SWORD_TYPE_1_VARBIT; import static com.toofifty.easygiantsfoundry.MouldHelper.SWORD_TYPE_2_VARBIT; @@ -42,6 +41,7 @@ public class FoundryOverlay3D extends Overlay GameObject waterfall; GameObject mouldJig; GameObject crucible; + GameObject storage; NPC kovac; private final Client client; @@ -111,6 +111,15 @@ public class FoundryOverlay3D extends Overlay drawKovacIfHandIn(graphics); } + if (client.getVarbitValue(VARBIT_PREFORM_STORED) == 1) + { + if (config.highlightStorage()) + { + drawStorage(graphics); + } + return null; + } + if (state.getCurrentStage() == null) { if (config.highlightMould()) @@ -127,7 +136,6 @@ public class FoundryOverlay3D extends Overlay drawPreformScoreIfPoured(graphics); } - return null; } @@ -486,6 +494,19 @@ public class FoundryOverlay3D extends Overlay } } + private void drawStorage(Graphics2D graphics) + { + Shape shape = storage.getConvexHull(); + if (shape != null) + { + Color color = config.generalHighlight(); + graphics.setColor(color); + graphics.draw(shape); + graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20)); + graphics.fill(shape); + } + } + private void drawKovacIfHandIn(Graphics2D graphics) { Widget handInWidget = client.getWidget(HAND_IN_WIDGET); From 914113b1fb122afbefc9bc0727caab5c790914af Mon Sep 17 00:00:00 2001 From: Louis Hong Date: Sat, 9 Nov 2024 02:20:30 -0800 Subject: [PATCH 04/13] hovering lava/waterfall now shows prediction preview & added more menu actions that may inturpt state machine --- .../EasyGiantsFoundryPlugin.java | 25 ++++++++++--------- .../easygiantsfoundry/FoundryOverlay3D.java | 18 +++++++++++++ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java index 427a075..d9c6339 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java @@ -19,17 +19,7 @@ import net.runelite.api.Item; import net.runelite.api.ItemContainer; import net.runelite.api.MenuAction; import net.runelite.api.Skill; -import net.runelite.api.events.GameObjectDespawned; -import net.runelite.api.events.GameObjectSpawned; -import net.runelite.api.events.GameStateChanged; -import net.runelite.api.events.GameTick; -import net.runelite.api.events.ItemContainerChanged; -import net.runelite.api.events.MenuOptionClicked; -import net.runelite.api.events.NpcDespawned; -import net.runelite.api.events.NpcSpawned; -import net.runelite.api.events.ScriptPostFired; -import net.runelite.api.events.StatChanged; -import net.runelite.api.events.VarbitChanged; +import net.runelite.api.events.*; import net.runelite.api.widgets.Widget; import net.runelite.client.Notifier; import net.runelite.client.callback.ClientThread; @@ -277,6 +267,15 @@ public class EasyGiantsFoundryPlugin extends Plugin } } + public void onMenuEntryAdded(MenuEntryAdded event) + { + if (event.getOption().startsWith("Heat-preform") || event.getOption().startsWith("Dunk-preform")) + { + } + else if (event.getOption().startsWith("Cool-preform") || event.getOption().startsWith("Quench-preform")) { + } + } + @Subscribe public void onMenuOptionClicked(MenuOptionClicked event) { @@ -285,7 +284,9 @@ public class EasyGiantsFoundryPlugin extends Plugin || event.getMenuAction() == MenuAction.GAME_OBJECT_SECOND_OPTION || event.getMenuAction() == MenuAction.GAME_OBJECT_THIRD_OPTION || event.getMenuAction() == MenuAction.GAME_OBJECT_FOURTH_OPTION - || event.getMenuAction() == MenuAction.GAME_OBJECT_FIFTH_OPTION)) + || event.getMenuAction() == MenuAction.GAME_OBJECT_FIFTH_OPTION + || event.getMenuAction() == MenuAction.WIDGET_TARGET_ON_GAME_OBJECT + || event.getMenuAction() == MenuAction.WALK)) { return; } diff --git a/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java b/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java index 263041a..6cb3554 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java @@ -16,6 +16,7 @@ import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.GameObject; +import net.runelite.api.MenuEntry; import net.runelite.api.NPC; import net.runelite.api.Perspective; import net.runelite.api.Point; @@ -166,6 +167,23 @@ public class FoundryOverlay3D extends Overlay { drawHeatChangers(graphics); } + else + { + MenuEntry[] menuEntries = client.getMenuEntries(); + if (menuEntries.length != 0) + { + MenuEntry hoveredMenu = menuEntries[menuEntries.length - 1]; + + if (hoveredMenu.getIdentifier() == lavaPool.getId()) + { + drawHeatChangerPreviewOverlay(graphics, lavaPool, true); + } + else if (hoveredMenu.getIdentifier() == waterfall.getId()) + { + drawHeatChangerPreviewOverlay(graphics, waterfall, false); + } + } + } return null; } From 47842501b7fdace2bdcd497d6a8d753981aea2c1 Mon Sep 17 00:00:00 2001 From: Louis Hong Date: Sat, 9 Nov 2024 02:40:32 -0800 Subject: [PATCH 05/13] made config settings effective for hovering over lava/waterfall action info --- .../java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java b/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java index 6cb3554..3f60076 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java @@ -167,7 +167,8 @@ public class FoundryOverlay3D extends Overlay { drawHeatChangers(graphics); } - else + // mouse hover over preview + else if (config.drawLavaWaterInfoOverlay()) { MenuEntry[] menuEntries = client.getMenuEntries(); if (menuEntries.length != 0) From 74d8f531d6eee9bd77d43c50bd6b8875163f2d36 Mon Sep 17 00:00:00 2001 From: Louis Hong Date: Sat, 9 Nov 2024 06:56:47 -0800 Subject: [PATCH 06/13] lava/waterfall: added one additional tick to default heat padding --- .../com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java index d02654e..144c445 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java @@ -526,7 +526,7 @@ public interface EasyGiantsFoundryConfig extends Config ) default int heatActionPadTicks() { - return 3; + return 4; } @ConfigItem( From f58e5ad868fda380971bc631d5ea0f86ccac2686 Mon Sep 17 00:00:00 2001 From: Louis Hong Date: Sat, 9 Nov 2024 09:33:43 -0800 Subject: [PATCH 07/13] ui/client thread race condition: moved lava/waterfall state machine logic into the client thread so it's synced with the client thread. --- .../EasyGiantsFoundryPlugin.java | 101 +++++++++--------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java index d9c6339..3dcf978 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java @@ -279,59 +279,62 @@ public class EasyGiantsFoundryPlugin extends Plugin @Subscribe public void onMenuOptionClicked(MenuOptionClicked event) { - - if (!(event.getMenuAction() == MenuAction.GAME_OBJECT_FIRST_OPTION - || event.getMenuAction() == MenuAction.GAME_OBJECT_SECOND_OPTION - || event.getMenuAction() == MenuAction.GAME_OBJECT_THIRD_OPTION - || event.getMenuAction() == MenuAction.GAME_OBJECT_FOURTH_OPTION - || event.getMenuAction() == MenuAction.GAME_OBJECT_FIFTH_OPTION - || event.getMenuAction() == MenuAction.WIDGET_TARGET_ON_GAME_OBJECT - || event.getMenuAction() == MenuAction.WALK)) + clientThread.invokeAtTickEnd(() -> { - return; - } - - if (!state.isEnabled()) return; - - if (event.getMenuTarget().contains("Crucible ")) - { - if (event.getMenuOption().equals("Pour")) + if (!(event.getMenuAction() == MenuAction.GAME_OBJECT_FIRST_OPTION + || event.getMenuAction() == MenuAction.GAME_OBJECT_SECOND_OPTION + || event.getMenuAction() == MenuAction.GAME_OBJECT_THIRD_OPTION + || event.getMenuAction() == MenuAction.GAME_OBJECT_FOURTH_OPTION + || event.getMenuAction() == MenuAction.GAME_OBJECT_FIFTH_OPTION + || event.getMenuAction() == MenuAction.WIDGET_TARGET_ON_GAME_OBJECT + || event.getMenuAction() == MenuAction.WALK)) { - if (client.getVarbitValue(VARBIT_GAME_STAGE) == 1) - { - state.setLastKnownCrucibleScore((int) state.getCrucibleScore()); - } - // add persistent game message of the alloy value so user can reference later. - client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "The score of the preform is " + ((int) state.getCrucibleScore() + state.getMouldScore()), null); + return; } - } - // Could not find a varbit to capture, so capture the menu-option directly. - // start the HeatActionStateMachine when varbit begins to update in onVarbitChanged() - if (event.getMenuOption().startsWith("Heat-preform")) - { - state.heatActionStateMachine.stop(); - state.heatActionStateMachine.setup(false, true, "heats"); - } - else if (event.getMenuOption().startsWith("Dunk-preform")) - { - state.heatActionStateMachine.stop(); - state.heatActionStateMachine.setup(true, true, "dunks"); - } - else if (event.getMenuOption().startsWith("Cool-preform")) - { - state.heatActionStateMachine.stop(); - state.heatActionStateMachine.setup(false, false, "cools"); - } - else if (event.getMenuOption().startsWith("Quench-preform")) - { - state.heatActionStateMachine.stop(); - state.heatActionStateMachine.setup(true, false, "quenches"); - } - else if (!state.heatActionStateMachine.isIdle()) // canceled heating/cooling, stop the heating state-machine - { - state.heatActionStateMachine.stop(); - } + if (!state.isEnabled()) return; + + if (event.getMenuTarget().contains("Crucible ")) + { + if (event.getMenuOption().equals("Pour")) + { + if (client.getVarbitValue(VARBIT_GAME_STAGE) == 1) + { + state.setLastKnownCrucibleScore((int) state.getCrucibleScore()); + } + // add persistent game message of the alloy value so user can reference later. + client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "The score of the preform is " + ((int) state.getCrucibleScore() + state.getMouldScore()), null); + } + } + + // Could not find a varbit to capture, so capture the menu-option directly. + // start the HeatActionStateMachine when varbit begins to update in onVarbitChanged() + if (event.getMenuOption().startsWith("Heat-preform")) + { + state.heatActionStateMachine.stop(); + state.heatActionStateMachine.setup(false, true, "heats"); + } + else if (event.getMenuOption().startsWith("Dunk-preform")) + { + state.heatActionStateMachine.stop(); + state.heatActionStateMachine.setup(true, true, "dunks"); + } + else if (event.getMenuOption().startsWith("Cool-preform")) + { + state.heatActionStateMachine.stop(); + state.heatActionStateMachine.setup(false, false, "cools"); + } + else if (event.getMenuOption().startsWith("Quench-preform")) + { + state.heatActionStateMachine.stop(); + state.heatActionStateMachine.setup(true, false, "quenches"); + } + else if (!state.heatActionStateMachine.isIdle()) // canceled heating/cooling, stop the heating state-machine + { + state.heatActionStateMachine.stop(); + } + + }); } @Subscribe From 723ca3109398e56515b0c1267f854b7a6acf8461 Mon Sep 17 00:00:00 2001 From: Louis Hong Date: Sat, 9 Nov 2024 11:27:27 -0800 Subject: [PATCH 08/13] heat range: the stage ranges are inclusive. showing incorrect out of range when is in range. additional note on previous commit f58e5ad (don't want to force push an ammend) the race condition caused an invalid state which showing incorrect prediction when race condition occurs - when the menu click and the heat/cool ticks occurs simultaniously. --- .../easygiantsfoundry/EasyGiantsFoundryState.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryState.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryState.java index 331b484..0e1ba90 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryState.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryState.java @@ -153,19 +153,19 @@ public class EasyGiantsFoundryState int heat = getHeatAmount(); int[] low = getLowHeatRange(); - if (heat > low[0] && heat < low[1]) + if (heat >= low[0] && heat <= low[1]) { return Heat.LOW; } int[] med = getMedHeatRange(); - if (heat > med[0] && heat < med[1]) + if (heat >= med[0] && heat <= med[1]) { return Heat.MED; } int[] high = getHighHeatRange(); - if (heat > high[0] && heat < high[1]) + if (heat >= high[0] && heat <= high[1]) { return Heat.HIGH; } @@ -326,7 +326,7 @@ public class EasyGiantsFoundryState int[] range = getCurrentHeatRange(); int actions = 0; int heat = getHeatAmount(); - while (heat > range[0] && heat < range[1]) + while (heat >= range[0] && heat <= range[1]) { actions++; heat += stage.getHeatChange(); From 703dd6ff9729379950bf2dfd6d65d531a599d786 Mon Sep 17 00:00:00 2001 From: Louis Hong Date: Sat, 9 Nov 2024 11:30:18 -0800 Subject: [PATCH 09/13] mould helper: moved scrolling to tick end. mould helper bugging out when selecting best moul because race condition between game tick widget update and client thread scroll script. --- src/main/java/com/toofifty/easygiantsfoundry/MouldHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/toofifty/easygiantsfoundry/MouldHelper.java b/src/main/java/com/toofifty/easygiantsfoundry/MouldHelper.java index 5b14081..dad7e3e 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/MouldHelper.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/MouldHelper.java @@ -114,7 +114,7 @@ public class MouldHelper int height = scrollList.getHeight(); int scrollMax = scrollList.getScrollHeight(); Widget finalBestWidget = bestWidget; - clientThread.invokeLater(() -> + clientThread.invokeAtTickEnd(() -> { if (finalBestWidget != null) { From 40e3a150993901bdeb1833d96db3416891ecfd2e Mon Sep 17 00:00:00 2001 From: Louis Hong Date: Sat, 9 Nov 2024 11:30:34 -0800 Subject: [PATCH 10/13] minor: fixed outdated javadoc --- .../com/toofifty/easygiantsfoundry/HeatActionStateMachine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java index 127ed4e..7b5ba91 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java @@ -62,7 +62,7 @@ public class HeatActionStateMachine * @param state the current state of the foundry * @param config the current configuration of the plugin * @param startingHeat the starting heat amount - * @see HeatActionStateMachine#setup(int, int, String) + * @see HeatActionStateMachine#setup(boolean, boolean, String) */ public void start(EasyGiantsFoundryState state, EasyGiantsFoundryConfig config, int startingHeat) { From 0cda8011650f1c4a2dacbfdad89e6996d30165cf Mon Sep 17 00:00:00 2001 From: Louis Hong Date: Thu, 14 Nov 2024 03:41:44 -0800 Subject: [PATCH 11/13] heat solver: Fixed index going out of bounds When attempting solving impossible heat goals and ranges, algo runs out of prebaked DX1 values. 1. Fixed incorrect loop condition that caused the out of bounds. 2. Clamped the heat goal so they also never attempt to solve impossible heat goals. Stacktrace ---------- java.lang.ArrayIndexOutOfBoundsException: Index 27 out of bounds for length 27 at com.toofifty.easygiantsfoundry.HeatActionSolver.relativeSolve(HeatActionSolver.java:188) at com.toofifty.easygiantsfoundry.HeatActionSolver.heatingSolve(HeatActionSolver.java:136) at com.toofifty.easygiantsfoundry.HeatActionSolver.solve(HeatActionSolver.java:288) at com.toofifty.easygiantsfoundry.FoundryOverlay3D.drawHeatChangerPreviewOverlay(FoundryOverlay3D.java:244) --- .../toofifty/easygiantsfoundry/HeatActionSolver.java | 11 ++++++----- .../easygiantsfoundry/HeatActionStateMachine.java | 2 -- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java index bf33807..4367a5b 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java @@ -151,7 +151,7 @@ public class HeatActionSolver while (true) { - if (index >= MAX_INDEX) + if (index > MAX_INDEX) { break; } @@ -175,6 +175,7 @@ public class HeatActionSolver dx0 -= decayValue; } + dx0 += DX_1[index]; ++index; decay = !decay; @@ -210,11 +211,11 @@ public class HeatActionSolver 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 + // adding 2.4s/8ticks worth of padding so preform doesn't decay out of range + // average distance from lava+waterfall around 8 ticks // preform decays 1 heat every 2 ticks - final int min = range[0] + padding; - final int max = range[1] + padding; + final int min = Math.min(1000, range[0] + padding); + final int max = Math.min(1000, range[1] + padding); final int actionsLeft_DeltaHeat = actionLeftInStage * stage.getHeatChange(); diff --git a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java index 7b5ba91..c0c3d19 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java @@ -135,8 +135,6 @@ public class HeatActionStateMachine /** * Helper to remind the neccessary parameters to start the state-machine. * - * @param velocity the velocity of the heating/cooling action, 7 for slow, 27 for fast. - * @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(boolean isFast, boolean isHeating, String actionName) From ca6fcc2d06633c44c83872418af8c3be96d1eb38 Mon Sep 17 00:00:00 2001 From: Louis Hong Date: Thu, 14 Nov 2024 03:47:35 -0800 Subject: [PATCH 12/13] follow-up on 0cda801: also clamp against negative paddings bringing goals below 0 --- .../java/com/toofifty/easygiantsfoundry/HeatActionSolver.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java index 4367a5b..6516e15 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java @@ -214,8 +214,8 @@ public class HeatActionSolver // adding 2.4s/8ticks worth of padding so preform doesn't decay out of range // average distance from lava+waterfall around 8 ticks // preform decays 1 heat every 2 ticks - final int min = Math.min(1000, range[0] + padding); - final int max = Math.min(1000, range[1] + padding); + final int min = Math.max(0, Math.min(1000, range[0] + padding)); + final int max = Math.max(0, Math.min(1000, range[1] + padding)); final int actionsLeft_DeltaHeat = actionLeftInStage * stage.getHeatChange(); From 13360bbcc33945246f34ff6f9dadc2ba2c27038d Mon Sep 17 00:00:00 2001 From: pww918 Date: Wed, 20 Nov 2024 18:29:57 +0400 Subject: [PATCH 13/13] Max heat padding ticks on user side set to 50 --- .../toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java index 144c445..baf7ea2 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java @@ -1,7 +1,6 @@ package com.toofifty.easygiantsfoundry; import java.awt.Color; - import net.runelite.client.config.Config; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; @@ -517,6 +516,9 @@ public interface EasyGiantsFoundryConfig extends Config String advancedSettings = "generalSettings"; + @Range( + max = 50 + ) @ConfigItem( keyName = "heatActionBuffer", // renamed to reset player's settings for previous bugged implementation name = "Lava/Waterfall Padding Ticks",