diff --git a/README.md b/README.md index 780a972..cdb4c92 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,44 @@ -# Easy Giant's Foundry +

+ + + + +

-Helpful overlays for the Giant's Foundry minigame +--- +The "Easy Giants' Foundry" plugin is designed to optimize your performance in the Giants' Foundry. -## Features +# Features Overview +- **Interactive Elements and NPCs** + - Highlights **Kovac, Crucible, and Mould Jig** when relevant, drawing your attention to key NPCs and equipment. + - Alerts you when the **waterfall/lava pool** can be used to correct temperature discrepancies. + - Uses **customizable status colors** to highlight relevant tools, helping you maintain the right temperature: + * **Red:** Wrong temperature + * **Green:** Right temperature + * **Orange:** One action or temperature change remaining + * **Cyan:** Click tool again for bonus progress +- **Alloy Quality and Crucible Content** + - Overlays **current crucible contents** and the **quality of the alloy** being forged. +- **Best Mould Guidance** + - Highlights the **best moulds to use** for your current task, guiding your selection process. +- **Progress and Actions Tracking** + - Displays the **number of lava/waterfall actions** needed to complete the current stage. + - Shows the **number of actions required** to complete the next stage. + - Indicates the **number of actions** before gaining or losing too much heat. + - Tracks **heat and progress** as percentages. -- Shows heat and progress as percentages -- Shows number of actions required to move to the next stage -- Shows number of actions before gaining/losing too much heat -- Shows best moulds to use -- Highlights relevant tool with customizable status colors - * Red = Wrong temperature - * Green = Right temperature - * Orange = one action or temperature change remaining - * Cyan = Click tool again for bonus progress -- Highlights Kovac, Crucible, and Mould Jig when relevant -- Highlights waterfall/lava pool when temperature is wrong +# Pictures +Best Mould | Crucible Alloy Quality +:-------------------------:|:-------------------------: +| + +Heating/Cooling Prediction | Low/High Heat Warning +:-------------------------:|:-------------------------: +| + +Bonus Click Notification | Information Panel +:-------------------------:|:-------------------------: +| ## Contributors @@ -30,4 +54,11 @@ Helpful overlays for the Giant's Foundry minigame - [Vanillj](https://github.com/Vanillj "Vanillj's github") * Added config * Added notifications for heat/stage changes - * Added config for actions/heat left for notifications \ No newline at end of file + * Added config for actions/heat left for notifications +- [Louis Hong](https://github.com/TheLouisHong "Louis Hongs' github") + * Added crucible content and alloy quality calculation and overlay + * Added tools action/heat status overlay + * Added lava/waterfall action prediction and overlay + * Added border highlighting + + diff --git a/banner_black.png b/banner_black.png new file mode 100644 index 0000000..53d3766 Binary files /dev/null and b/banner_black.png differ diff --git a/banner_white.png b/banner_white.png new file mode 100644 index 0000000..cf19be2 Binary files /dev/null and b/banner_white.png differ diff --git a/readme_gifs/best-mould.webp b/readme_gifs/best-mould.webp new file mode 100644 index 0000000..89d7730 Binary files /dev/null and b/readme_gifs/best-mould.webp differ diff --git a/readme_gifs/convert_gif2webp.bat b/readme_gifs/convert_gif2webp.bat new file mode 100644 index 0000000..a7663bf --- /dev/null +++ b/readme_gifs/convert_gif2webp.bat @@ -0,0 +1,5 @@ +# convert all gif files in the current directory to webp using ffmpeg +# requires ffmpeg and webp + +for %%i in (*.gif) do D:\ffmpeg -i "%%i" -c:v libwebp -loop 0 -pix_fmt yuva420p "%%~ni.webp" + diff --git a/readme_gifs/crucible-value.png b/readme_gifs/crucible-value.png new file mode 100644 index 0000000..d6cfc5a Binary files /dev/null and b/readme_gifs/crucible-value.png differ diff --git a/readme_gifs/info-panel.webp b/readme_gifs/info-panel.webp new file mode 100644 index 0000000..22b9384 Binary files /dev/null and b/readme_gifs/info-panel.webp differ diff --git a/readme_gifs/lava-waterfall-estimate.webp b/readme_gifs/lava-waterfall-estimate.webp new file mode 100644 index 0000000..6d82e87 Binary files /dev/null and b/readme_gifs/lava-waterfall-estimate.webp differ diff --git a/readme_gifs/tool-damage-warning.webp b/readme_gifs/tool-damage-warning.webp new file mode 100644 index 0000000..50076a8 Binary files /dev/null and b/readme_gifs/tool-damage-warning.webp differ diff --git a/readme_gifs/tools-bonus-notification.webp b/readme_gifs/tools-bonus-notification.webp new file mode 100644 index 0000000..da16a5e Binary files /dev/null and b/readme_gifs/tools-bonus-notification.webp differ diff --git a/src/main/java/com/toofifty/easygiantsfoundry/BonusWidget.java b/src/main/java/com/toofifty/easygiantsfoundry/BonusWidget.java index 5bf7f19..306f2ed 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/BonusWidget.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/BonusWidget.java @@ -3,15 +3,17 @@ package com.toofifty.easygiantsfoundry; import net.runelite.api.Client; import net.runelite.api.widgets.Widget; -public class BonusWidget { - private static final int BONUS_WIDGET = 49414148; - private static final int BONUS_COLOR = 0xfcd703; +public class BonusWidget +{ + private static final int BONUS_WIDGET = 49414148; + private static final int BONUS_COLOR = 0xfcd703; - static boolean isActive(Client client) { - Widget bonusWidget = client.getWidget(BONUS_WIDGET); - return bonusWidget != null - && bonusWidget.getChildren() != null - && bonusWidget.getChildren().length != 0 - && bonusWidget.getChild(0).getTextColor() == BONUS_COLOR; - } + static boolean isActive(Client client) + { + Widget bonusWidget = client.getWidget(BONUS_WIDGET); + return bonusWidget != null + && bonusWidget.getChildren() != null + && bonusWidget.getChildren().length != 0 + && bonusWidget.getChild(0).getTextColor() == BONUS_COLOR; + } } diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryClientIDs.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryClientIDs.java new file mode 100644 index 0000000..9420e33 --- /dev/null +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryClientIDs.java @@ -0,0 +1,41 @@ +package com.toofifty.easygiantsfoundry; + +public class EasyGiantsFoundryClientIDs +{ + // heat and progress are from 0-1000 + protected static final int VARBIT_HEAT = 13948; + protected static final int VARBIT_PROGRESS = 13949; + + protected static final int VARBIT_BRONZE_COUNT = 13931; + protected static final int VARBIT_IRON_COUNT = 13932; + protected static final int VARBIT_STEEL_COUNT = 13933; + protected static final int VARBIT_MITHRIL_COUNT = 13934; + protected static final int VARBIT_ADAMANT_COUNT = 13935; + protected static final int VARBIT_RUNE_COUNT = 13936; + + protected static final int VARBIT_FORTE_SELECTED = 13910; + protected static final int VARBIT_BLADE_SELECTED = 13911; + protected static final int VARBIT_TIP_SELECTED = 13912; + + // 0 - load bars + // 1 - set mould + // 2 - collect preform + // 3 - + protected static final int VARBIT_GAME_STAGE = 13914; + + 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; + protected static final int WIDGET_HIGH_HEAT_PARENT = 49414165; + + protected static final int WIDGET_PROGRESS_PARENT = 49414219; + // children with type 3 are stage boxes + // every 11th child is a sprite + + protected static final int SPRITE_ID_TRIP_HAMMER = 4442; + protected static final int SPRITE_ID_GRINDSTONE = 4443; + protected static final int SPRITE_ID_POLISHING_WHEEL = 4444; + + protected static final int ANIMATION_HEATING = 827; + protected static final int ANIMATION_COOLING = 832; +} diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java index f053746..1571d77 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryConfig.java @@ -1,245 +1,404 @@ 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; import net.runelite.client.config.ConfigSection; +import net.runelite.client.config.Range; import net.runelite.client.ui.ColorScheme; @ConfigGroup(EasyGiantsFoundryConfig.GROUP) -public interface EasyGiantsFoundryConfig extends Config { - String GROUP = "easygiantsfoundry"; - String SOUND_ID = "soundID"; - String POINTS_KEY = "easygiantsfoundrypoints"; +public interface EasyGiantsFoundryConfig extends Config +{ - @ConfigSection( - name = "Notifications", - description = "Notifications", - position = 0 - ) - String notificationList = "notificationList"; + String GROUP = "easygiantsfoundry"; + String SOUND_ID = "soundID"; + String POINTS_KEY = "easygiantsfoundrypoints"; - @ConfigItem( - keyName = "giantsFoundryStageNotification", - name = "Notify stage changes", - description = "Notifies just before completing a stage", - position = 0, - section = notificationList - ) - default boolean showGiantsFoundryStageNotifications() { - return true; - } + @ConfigSection( + name = "Notifications", + description = "Notifications", + position = 0 + ) + String notificationList = "notificationList"; - @ConfigItem( - keyName = "giantsFoundryHeatNotification", - name = "Notify heat changes", - description = "Notifies just before overheating/cooling when using tools", - position = 1, - section = notificationList - ) - default boolean showGiantsFoundryHeatNotifications() { - return true; - } + @ConfigItem( + keyName = "giantsFoundryStageNotification", + name = "Notify stage changes", + description = "Notifies just before completing a stage", + position = 0, + section = notificationList + ) + default boolean showGiantsFoundryStageNotifications() + { + return true; + } - @ConfigItem( - keyName = "giantsFoundryStageThreshold", - name = "Stage threshold notification", - description = "The number of actions left required for the notification.", - position = 2, - section = notificationList - ) - default int StageNotificationsThreshold() { - return 1; - } + @ConfigItem( + keyName = "giantsFoundryHeatNotification", + name = "Notify heat changes", + description = "Notifies just before overheating/cooling when using tools", + position = 1, + section = notificationList + ) + default boolean showGiantsFoundryHeatNotifications() + { + return true; + } - @ConfigItem( - keyName = "giantsFoundryHeatThreshold", - name = "Heat threshold notification", - description = "The heat level left required for the notification.", - position = 3, - section = notificationList - ) - default int HeatNotificationsThreshold() { - return 1; - } + @ConfigItem( + keyName = "giantsFoundryStageThreshold", + name = "Stage threshold notification", + description = "The number of actions left required for the notification.", + position = 2, + section = notificationList + ) + default int StageNotificationsThreshold() + { + return 1; + } - @ConfigItem( - keyName = "bonusNotification", - name = "Notify bonus", - description = "Notifies when bonus appears", - position = 4, - section = notificationList - ) - default boolean bonusNotification() { - return false; - } + @ConfigItem( + keyName = "giantsFoundryHeatThreshold", + name = "Heat threshold notification", + description = "The heat level left required for the notification.", + position = 3, + section = notificationList + ) + default int HeatNotificationsThreshold() + { + return 1; + } - @ConfigItem( - keyName = "bonusSound", - name = "Bonus sound", - description = "Plays a sound when bonus appears", - position = 5, - section = notificationList - ) - default boolean bonusSoundNotify() { - return true; - } + @ConfigItem( + keyName = "bonusNotification", + name = "Notify bonus", + description = "Notifies when bonus appears", + position = 4, + section = notificationList + ) + default boolean bonusNotification() + { + return false; + } - @ConfigItem( - keyName = SOUND_ID, - name = "Bonus sound ID", - description = "Sound Effect ID to play when bonus appears", - position = 6, - section = notificationList - ) - default int soundId() { - return 4212; - } + @ConfigItem( + keyName = "bonusSound", + name = "Bonus sound", + description = "Plays a sound when bonus appears", + position = 5, + section = notificationList + ) + default boolean bonusSoundNotify() + { + return true; + } + + @ConfigItem( + keyName = SOUND_ID, + name = "Bonus sound ID", + description = "Sound Effect ID to play when bonus appears", + position = 6, + section = notificationList + ) + default int soundId() + { + return 4212; + } - @ConfigSection( - name = "Highlights", - description = "3D npc/object highlights", - position = 1 - ) - String highlightList = "highlightList"; + @ConfigSection( + name = "Highlights", + description = "3D npc/object highlights", + position = 1 + ) + String highlightList = "highlightList"; - @ConfigItem( - keyName = "toolsHighlight", - name = "Highlight Tools", - description = "Highlights current tool with symbolic colors", - position = 0, - section = highlightList - ) - default boolean highlightTools() { - return true; - } + @ConfigItem( + name = "Highlight Style", + description = "The style of the highlight", + position = 0, + section = highlightList, + keyName = "overlayOption") + default HighlightStyle highlightStyle() + { + return HighlightStyle.HIGHLIGHT_CLICKBOX; + } - @ConfigItem( - keyName = "waterLavaHighlight", - name = "Highlight Waterfall/Lava Pool", - description = "Highlight Lava Pool / Waterfall when heat change required", - position = 1, - section = highlightList - ) - default boolean highlightWaterAndLava() { - return true; - } + @Range( + min = 1, + max = 4 + ) + @ConfigItem( + keyName = "borderThickness", + name = "Border Thickness", + description = "The thickness of the border", + position = 1, + section = highlightList + ) + default int borderThickness() + { + return 1; + } - @ConfigItem( - keyName = "mouldHighlight", - name = "Highlight Mould", - description = "Highlight Mould when it should be clicked", - position = 2, - section = highlightList - ) - default boolean highlightMould() { - return true; - } + @Range( + min = 0, + max = 4 + ) + @ConfigItem( + keyName = "borderFeather", + name = "Border Feather", + description = "The feather of the border", + position = 2, + section = highlightList + ) + default int borderFeather() + { + return 0; + } - @ConfigItem( - keyName = "crucibleHighlight", - name = "Highlight Crucible", - description = "Highlight Crucible when it should be filled/poured", - position = 3, - section = highlightList - ) - default boolean highlightCrucible() { - return true; - } + // alpha + @Range( + min = 0, + max = 255 + ) + @ConfigItem( + keyName = "highlightAlpha", + name = "Highlight Alpha", + description = "The alpha of the highlight", + position = 3, + section = highlightList + ) + default int borderAlpha() + { + return 255; + } - @ConfigItem( - keyName = "kovacHighlight", - name = "Highlight Kovac for hand in", - description = "Highlight Kovac when sword can be handed in", - position = 4, - section = highlightList - ) - default boolean highlightKovac() { - return true; - } + @ConfigItem( + keyName = "toolsHighlight", + name = "Highlight Tools", + description = "Highlights current tool with symbolic colors", + position = 4, + section = highlightList + ) + default boolean highlightTools() + { + return true; + } + + @ConfigItem( + keyName = "waterLavaHighlight", + name = "Highlight Waterfall/Lava Pool", + description = "Highlight Lava Pool / Waterfall when heat change required", + position = 5, + section = highlightList + ) + default boolean highlightWaterAndLava() + { + return true; + } + + @ConfigItem( + keyName = "mouldHighlight", + name = "Highlight Mould", + description = "Highlight Mould when it should be clicked", + position = 6, + section = highlightList + ) + default boolean highlightMould() + { + return true; + } + + @ConfigItem( + keyName = "crucibleHighlight", + name = "Highlight Crucible", + description = "Highlight Crucible when it should be filled/poured", + position = 7, + section = highlightList + ) + default boolean highlightCrucible() + { + return true; + } + + @ConfigItem( + keyName = "kovacHighlight", + name = "Highlight Kovac for hand in", + description = "Highlight Kovac when sword can be handed in", + position = 8, + section = highlightList + ) + default boolean highlightKovac() + { + return true; + } + + @ConfigItem( + keyName = "crucibleContent", + name = "Show Crucible content and quality", + description = "Show the content and quality of the crucible", + position = 9, + section = highlightList + ) + default boolean showCrucibleContent() + { + return true; + } + + @ConfigSection( + name = "Info Panel", + description = "Settings for the Info Panel overlay", + position = 2 + ) + String infoPanelList = "infoPanelList"; + + @ConfigItem( + keyName = "infoTitle", + name = "Title", + description = "Toggle for \"Easy Giant's Foundry\" text", + position = 0, + section = infoPanelList + ) + default boolean drawTitle() + { + return true; + } + + @ConfigItem( + keyName = "heatInfo", + name = "Heat", + description = "Toggle for Heat text", + position = 1, + section = infoPanelList + ) + default boolean drawHeatInfo() + { + return true; + } + + @ConfigItem( + keyName = "stageInfo", + name = "Stage", + description = "Toggle for Stage text", + position = 2, + section = infoPanelList + ) + default boolean drawStageInfo() + { + return true; + } + + @ConfigItem( + keyName = "actionLeft", + name = "Actions Left", + description = "Toggle for actions left text", + position = 3, + section = infoPanelList + ) + default boolean drawActionsLeft() + { + return true; + } + + @ConfigItem( + keyName = "heatLeft", + name = "Heat Left", + description = "Toggle for heat left text", + position = 4, + section = infoPanelList + ) + default boolean drawHeatLeft() + { + return true; + } + + @ConfigItem( + keyName = "shopPoints", + name = "Reputation", + description = "Toggle for reputation text", + position = 5, + section = infoPanelList + ) + default boolean drawShopPoints() + { + return false; + } - @ConfigSection( - name = "Info Panel", - description = "Settings for the Info Panel overlay", - position = 2 - ) - String infoPanelList = "infoPanelList"; + @ConfigSection( + name = "Info Overlay", + description = "Overlay Text Info On Objects", + position = 3 + ) + String infoOverlay = "infoOverlay"; - @ConfigItem( - keyName = "infoTitle", - name = "Title", - description = "Toggle for \"Easy Giant's Foundry\" text", - position = 0, - section = infoPanelList - ) - default boolean drawTitle() { - return true; - } + @ConfigItem( + keyName = "actionLeftOverlay", + name = "Actions Left Overlay", + description = "Toggle for actions left overlay", + position = 0, + section = infoOverlay + ) + default boolean drawActionLeftOverlay() + { + return true; + } - @ConfigItem( - keyName = "heatInfo", - name = "Heat", - description = "Toggle for Heat text", - position = 1, - section = infoPanelList - ) - default boolean drawHeatInfo() { - return true; - } + @ConfigItem( + keyName = "heatLeftOverlay", + name = "Heat Left Overlay", + description = "Toggle for heat left overlay", + position = 1, + section = infoOverlay + ) + default boolean drawHeatLeftOverlay() + { + return true; + } - @ConfigItem( - keyName = "stageInfo", - name = "Stage", - description = "Toggle for Stage text", - position = 2, - section = infoPanelList - ) - default boolean drawStageInfo() { - return true; - } + @ConfigItem( + keyName = "crucibleInfoOverlay", + name = "Crucible Info Overlay", + description = "Toggle for crucible info overlay", + position = 2, + section = infoOverlay + ) + default boolean drawCrucibleInfoOverlay() + { + return true; + } - @ConfigItem( - keyName = "actionsLeft", - name = "Actions Left", - description = "Toggle for Actions left text", - position = 3, - section = infoPanelList - ) - default boolean drawActionsLeft() { - return true; - } - - @ConfigItem( - keyName = "heatLeft", - name = "Heat Left", - description = "Toggle for Heat left text", - position = 4, - section = infoPanelList - ) - default boolean drawHeatLeft() { - return true; - } - - @ConfigItem( - keyName = "shopPoints", - name = "Reputation", - description = "Toggle for reputation text", - position = 5, - section = infoPanelList - ) - default boolean drawShopPoints() - { - return false; - } + @ConfigItem( + keyName = "mouldInfoOverlay", + name = "Mould Info Overlay", + description = "Toggle for mould info overlay", + position = 3, + section = infoOverlay + ) + default boolean drawMouldInfoOverlay() + { + return true; + } + @ConfigItem( + keyName = "LavaWaterInfoOverlay", + name = "Lava/Waterfall Info Overlay", + description = "Toggle for lava/waterfall info overlay", + position = 4, + section = infoOverlay + ) + default boolean drawLavaWaterInfoOverlay() + { + return true; + } @ConfigSection( name = "Colour", description = "Colours", - position = 3 + position = 4 ) String colourList = "colourList"; @@ -327,4 +486,23 @@ public interface EasyGiantsFoundryConfig extends Config { { return Color.CYAN; } + + @ConfigSection( + name = "Advanced", + description = "Advanced Settings", + position = 5 + ) + String generalSettings = "generalSettings"; + + @ConfigItem( + keyName = "heatingCoolingMarginOfError", + name = "Heating/Cooling Margin of Error", + description = "The margin of error for lava/waterfall calculations to compensate for decay and overshooting.", + position = 0, + section = generalSettings + ) + default int heatingCoolingBuffer() + { + return 20; + } } diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryHelper.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryHelper.java index 8041aaf..ab4d879 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryHelper.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryHelper.java @@ -1,83 +1,29 @@ package com.toofifty.easygiantsfoundry; -import com.toofifty.easygiantsfoundry.enums.Heat; -import com.toofifty.easygiantsfoundry.enums.Stage; -import javax.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.ui.ColorScheme; + import javax.inject.Singleton; +import java.awt.Color; +@Slf4j @Singleton -public class EasyGiantsFoundryHelper +public final class EasyGiantsFoundryHelper { - // heat lowers every 2 ticks - // seems to be between 7-11 per - private static final int HEAT_LAVA_HEAT = 8; - private static final int COOL_WATERFALL_HEAT = -8; - // 27-37 - private static final int DUNK_LAVA_HEAT = 32; - private static final int QUENCH_WATERFALL_HEAT = -32; - - @Inject - private EasyGiantsFoundryState state; - - /** - * Get the amount of progress each stage needs - */ - public double getProgressPerStage() + public static Color getHeatColor(int actions, int heat) { - return 1000d / state.getStages().size(); - } - - public int getActionsLeftInStage() - { - int progress = state.getProgressAmount(); - double progressPerStage = getProgressPerStage(); - double progressTillNext = progressPerStage - progress % progressPerStage; - - Stage current = state.getCurrentStage(); - return (int) Math.ceil(progressTillNext / current.getProgressPerAction()); - } - - public int[] getCurrentHeatRange() - { - switch (state.getCurrentStage()) + if (heat >= actions) { - case POLISHING_WHEEL: - return state.getLowHeatRange(); - case GRINDSTONE: - return state.getMedHeatRange(); - case TRIP_HAMMER: - return state.getHighHeatRange(); - default: - return new int[]{0, 0}; - } - } - - /** - * Get the amount of current stage actions that can be - * performed before the heat drops too high or too low to - * continue - */ - public int getActionsForHeatLevel() - { - Heat heatStage = state.getCurrentHeat(); - Stage stage = state.getCurrentStage(); - if (heatStage != stage.getHeat()) - { - // not the right heat to start with - return 0; + return ColorScheme.PROGRESS_COMPLETE_COLOR; } - int[] range = getCurrentHeatRange(); - int actions = 0; - int heat = state.getHeatAmount(); - while (heat > range[0] && heat < range[1]) + if (heat > 0) { - actions++; - heat += stage.getHeatChange(); + return ColorScheme.PROGRESS_INPROGRESS_COLOR; } - return actions; + return ColorScheme.PROGRESS_ERROR_COLOR; } } diff --git a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java index dbfc44a..2422214 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryPlugin.java @@ -1,10 +1,14 @@ package com.toofifty.easygiantsfoundry; import com.google.inject.Provides; +import static com.toofifty.easygiantsfoundry.EasyGiantsFoundryClientIDs.VARBIT_HEAT; import com.toofifty.easygiantsfoundry.enums.Stage; + import javax.inject.Inject; + import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.GameObject; import net.runelite.api.GameState; @@ -15,6 +19,7 @@ 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; @@ -143,6 +148,7 @@ public class EasyGiantsFoundryPlugin extends Plugin } } + @Subscribe public void onGameStateChanged(GameStateChanged event) { @@ -172,14 +178,14 @@ public class EasyGiantsFoundryPlugin extends Plugin } if (config.showGiantsFoundryStageNotifications() && - helper.getActionsLeftInStage() == config.StageNotificationsThreshold() && + state.getActionsLeftInStage() == config.StageNotificationsThreshold() && (oldStage == null || oldStage != state.getCurrentStage())) { notifier.notify("About to finish the current stage!"); oldStage = state.getCurrentStage(); } else if (config.showGiantsFoundryHeatNotifications() && - helper.getActionsForHeatLevel() == config.HeatNotificationsThreshold()) + state.getActionsForHeatLevel() == config.HeatNotificationsThreshold()) { notifier.notify("About to run out of heat!"); } @@ -245,6 +251,48 @@ public class EasyGiantsFoundryPlugin extends Plugin } } + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked event) + { + if (!state.isEnabled()) return; + + if (event.getMenuTarget().contains("Crucible ")) + { + if (event.getMenuOption().equals("Pour")) + { + // add persistent game message of the alloy value so user can reference later. + client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "The quality of the alloy poured is " + state.getCrucibleQuality(), 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.heatingCoolingState.stop(); + state.heatingCoolingState.setup(7, 0, "heats"); + } + else if (event.getMenuOption().startsWith("Dunk-preform")) + { + state.heatingCoolingState.stop(); + state.heatingCoolingState.setup(27, 2, "dunks"); + } + else if (event.getMenuOption().startsWith("Cool-preform")) + { + state.heatingCoolingState.stop(); + state.heatingCoolingState.setup(-7, 0, "cools"); + } + else if (event.getMenuOption().startsWith("Quench-preform")) + { + state.heatingCoolingState.stop(); + state.heatingCoolingState.setup(-27, -2, "quenches"); + } + else // canceled heating/cooling, stop the heating state-machine + { + state.heatingCoolingState.stop(); + } + } + @Subscribe public void onScriptPostFired(ScriptPostFired event) { @@ -257,6 +305,9 @@ public class EasyGiantsFoundryPlugin extends Plugin } } + // previous heat varbit value, used to filter out passive heat decay. + private int previousHeat = 0; + @Subscribe public void onVarbitChanged(VarbitChanged event) { @@ -264,6 +315,24 @@ public class EasyGiantsFoundryPlugin extends Plugin { reputation = client.getVarpValue(REPUTATION_VARBIT); } + + // 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) + { + // ignore passive heat decay, one heat per two ticks + if (event.getValue() - previousHeat != -1) + { + // if the state-machine is idle, start it + if (state.heatingCoolingState.isIdle()) + { + state.heatingCoolingState.start(state, config, state.getHeatAmount()); + } + + state.heatingCoolingState.onTick(); + } + 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 47e2b63..54b4cd6 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryState.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/EasyGiantsFoundryState.java @@ -1,7 +1,13 @@ package com.toofifty.easygiantsfoundry; +import static com.toofifty.easygiantsfoundry.MathUtil.max1; +import static com.toofifty.easygiantsfoundry.EasyGiantsFoundryClientIDs.*; + import com.toofifty.easygiantsfoundry.enums.Heat; import com.toofifty.easygiantsfoundry.enums.Stage; +import static com.toofifty.easygiantsfoundry.enums.Stage.GRINDSTONE; +import static com.toofifty.easygiantsfoundry.enums.Stage.POLISHING_WHEEL; +import static com.toofifty.easygiantsfoundry.enums.Stage.TRIP_HAMMER; import lombok.Getter; import lombok.Setter; import net.runelite.api.Client; @@ -15,33 +21,6 @@ import java.util.List; @Singleton public class EasyGiantsFoundryState { - // heat and progress are from 0-1000 - private static final int VARBIT_HEAT = 13948; - private static final int VARBIT_PROGRESS = 13949; - - private static final int VARBIT_ORE_COUNT = 13934; - private static final int VARBIT_FORTE_SELECTED = 13910; - private static final int VARBIT_BLADE_SELECTED = 13911; - private static final int VARBIT_TIP_SELECTED = 13912; - - // 0 - load bars - // 1 - set mould - // 2 - collect preform - // 3 - - static final int VARBIT_GAME_STAGE = 13914; - - private static final int WIDGET_HEAT_PARENT = 49414153; - private static final int WIDGET_LOW_HEAT_PARENT = 49414163; - private static final int WIDGET_MED_HEAT_PARENT = 49414164; - private static final int WIDGET_HIGH_HEAT_PARENT = 49414165; - - static final int WIDGET_PROGRESS_PARENT = 49414219; - // children with type 3 are stage boxes - // every 11th child is a sprite - - private static final int SPRITE_ID_TRIP_HAMMER = 4442; - private static final int SPRITE_ID_GRINDSTONE = 4443; - private static final int SPRITE_ID_POLISHING_WHEEL = 4444; @Inject private Client client; @@ -59,6 +38,11 @@ public class EasyGiantsFoundryState heatRangeRatio = 0; } + public int getBarCount() + { + return client.getVarbitValue(VARBIT_STEEL_COUNT); + } + public int getHeatAmount() { return client.getVarbitValue(VARBIT_HEAT); @@ -80,7 +64,7 @@ public class EasyGiantsFoundryState return 0; } - heatRangeRatio = medHeat.getWidth() /(double) heatWidget.getWidth(); + heatRangeRatio = medHeat.getWidth() / (double) heatWidget.getWidth(); } return heatRangeRatio; @@ -125,13 +109,13 @@ public class EasyGiantsFoundryState switch (child.getSpriteId()) { case SPRITE_ID_TRIP_HAMMER: - stages.add(Stage.TRIP_HAMMER); + stages.add(TRIP_HAMMER); break; case SPRITE_ID_GRINDSTONE: - stages.add(Stage.GRINDSTONE); + stages.add(GRINDSTONE); break; case SPRITE_ID_POLISHING_WHEEL: - stages.add(Stage.POLISHING_WHEEL); + stages.add(POLISHING_WHEEL); break; } } @@ -204,4 +188,149 @@ public class EasyGiantsFoundryState else return 0; } + + +// boolean valid = false; +// +// int bronze = 0; +// int iron = 0; +// int steel = 0; +// int mithril = 0; +// int adamant = 0; +// int rune = 0; +// // Currently 28, will prob always be 28, but I want to future-proof this +// int capacity = 28; +// +// public boolean parseCrucibleText(String text) +// { +// if (!text.startsWith("The crucible currently contains")) +// { +// return false; +// } +// String[] parts = text.split("
"); +// capacity = Integer.parseInt(parts[0].split(" / ")[1].split(" ")[0]); +// +// String[] counts = (parts[1] + parts[2]).split(", "); +// bronze = Integer.parseInt(counts[0].split(" x ")[0]); +// iron = Integer.parseInt(counts[1].split(" x ")[0]); +// steel = Integer.parseInt(counts[2].split(" x ")[0]); +// mithril = Integer.parseInt(counts[3].split(" x ")[0]); +// adamant = Integer.parseInt(counts[4].split(" x ")[0]); +// rune = Integer.parseInt(counts[5].split(" x ")[0]); +// +// valid = true; +// return true; +// } + +// public String toString() +// { +// return String.format("[Capacity %d/%d. Total Value %d] Bronze: %d, Iron: %d, Steel: %d, Mithril: %d, Adamant: %d, Rune: %d.", +// bronze, iron, steel, mithril, adamant, rune, count(), capacity, value()); +// } + + + public int getCrucibleCount() + { + int bronze = client.getVarbitValue(VARBIT_BRONZE_COUNT); + int iron = client.getVarbitValue(VARBIT_IRON_COUNT); + int steel = client.getVarbitValue(VARBIT_STEEL_COUNT); + int mithril = client.getVarbitValue(VARBIT_MITHRIL_COUNT); + int adamant = client.getVarbitValue(VARBIT_ADAMANT_COUNT); + int rune = client.getVarbitValue(VARBIT_RUNE_COUNT); + + return bronze + iron + steel + mithril + adamant + rune; + } + + public double getCrucibleQuality() + { + if (getCrucibleCount() == 0) return 0; + + int bronze = client.getVarbitValue(VARBIT_BRONZE_COUNT); + int iron = client.getVarbitValue(VARBIT_IRON_COUNT); + int steel = client.getVarbitValue(VARBIT_STEEL_COUNT); + int mithril = client.getVarbitValue(VARBIT_MITHRIL_COUNT); + int adamant = client.getVarbitValue(VARBIT_ADAMANT_COUNT); + int rune = client.getVarbitValue(VARBIT_RUNE_COUNT); + + final int BRONZE_VALUE = 1; + final int IRON_VALUE = 2; + final int STEEL_VALUE = 3; + final int MITHRIL_VALUE = 4; + final int ADAMANT_VALUE = 5; + final int RUNE_VALUE = 6; + + final double vB = (10 * BRONZE_VALUE * bronze) / 28.0; + final double vI = (10 * IRON_VALUE * iron) / 28.0; + final double vS = (10 * STEEL_VALUE * steel) / 28.0; + final double vM = (10 * MITHRIL_VALUE * mithril) / 28.0; + final double vA = (10 * ADAMANT_VALUE * adamant) / 28.0; + final double vR = (10 * RUNE_VALUE * rune) / 28.0; + + return + (10 * (vB + vI + vS + vM + vA + vR) + + (max1(vB) * max1(vI) * max1(vS) * max1(vM) * max1(vA) * max1(vR))) / 10.0; + } + + /** + * Get the amount of progress each stage needs + */ + public double getProgressPerStage() + { + return 1000d / getStages().size(); + } + + public int getActionsLeftInStage() + { + int progress = getProgressAmount(); + double progressPerStage = getProgressPerStage(); + double progressTillNext = progressPerStage - progress % progressPerStage; + + Stage current = getCurrentStage(); + return (int) Math.ceil(progressTillNext / current.getProgressPerAction()); + } + + public int[] getCurrentHeatRange() + { + switch (getCurrentStage()) + { + case POLISHING_WHEEL: + return getLowHeatRange(); + case GRINDSTONE: + return getMedHeatRange(); + case TRIP_HAMMER: + return getHighHeatRange(); + default: + return new int[]{0, 0}; + } + } + + /** + * Get the amount of current stage actions that can be + * performed before the heat drops too high or too low to + * continue + */ + public int getActionsForHeatLevel() + { + Heat heatStage = getCurrentHeat(); + Stage stage = getCurrentStage(); + if (heatStage != stage.getHeat()) + { + // not the right heat to start with + return 0; + } + + int[] range = getCurrentHeatRange(); + int actions = 0; + int heat = getHeatAmount(); + while (heat > range[0] && heat < range[1]) + { + actions++; + heat += stage.getHeatChange(); + } + + return actions; + } + + public HeatActionStateMachine heatingCoolingState = new HeatActionStateMachine(); + } diff --git a/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay2D.java b/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay2D.java index 660271f..a5e5f5b 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay2D.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay2D.java @@ -1,14 +1,15 @@ package com.toofifty.easygiantsfoundry; +import static com.toofifty.easygiantsfoundry.EasyGiantsFoundryHelper.getHeatColor; import com.toofifty.easygiantsfoundry.enums.Heat; import com.toofifty.easygiantsfoundry.enums.Stage; -import java.awt.Color; + import java.awt.Dimension; import java.awt.Graphics2D; import javax.inject.Inject; import javax.inject.Singleton; + import net.runelite.api.Client; -import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPosition; import net.runelite.client.ui.overlay.components.LineComponent; @@ -21,35 +22,22 @@ public class FoundryOverlay2D extends OverlayPanel private final Client client; private final EasyGiantsFoundryPlugin plugin; private final EasyGiantsFoundryState state; - private final EasyGiantsFoundryHelper helper; private final EasyGiantsFoundryConfig config; @Inject - private FoundryOverlay2D(Client client, EasyGiantsFoundryPlugin plugin, EasyGiantsFoundryState state, EasyGiantsFoundryHelper helper, EasyGiantsFoundryConfig config) + private FoundryOverlay2D( + Client client, + EasyGiantsFoundryPlugin plugin, + EasyGiantsFoundryState state, + EasyGiantsFoundryConfig config) { this.client = client; this.plugin = plugin; this.state = state; - this.helper = helper; this.config = config; this.setPosition(OverlayPosition.BOTTOM_LEFT); } - private Color getHeatColor(int actions, int heat) - { - if (heat >= actions) - { - return ColorScheme.PROGRESS_COMPLETE_COLOR; - } - - if (heat > 0) - { - return ColorScheme.PROGRESS_INPROGRESS_COLOR; - } - - return ColorScheme.PROGRESS_ERROR_COLOR; - } - @Override public Dimension render(Graphics2D graphics) { @@ -64,7 +52,8 @@ public class FoundryOverlay2D extends OverlayPanel panelComponent.getChildren().add(TitleComponent.builder().text("Easy Giant's Foundry").build()); } - if (swordPickedUp) { + if (swordPickedUp) + { Heat heat = state.getCurrentHeat(); Stage stage = state.getCurrentStage(); @@ -81,8 +70,8 @@ public class FoundryOverlay2D extends OverlayPanel ); } - int actionsLeft = helper.getActionsLeftInStage(); - int heatLeft = helper.getActionsForHeatLevel(); + int actionsLeft = state.getActionsLeftInStage(); + int heatLeft = state.getActionsForHeatLevel(); if (config.drawActionsLeft()) { @@ -102,7 +91,7 @@ public class FoundryOverlay2D extends OverlayPanel if (config.drawShopPoints()) { panelComponent.getChildren().add( - LineComponent.builder().left("Reputation").right(plugin.getReputation() + "").build() + LineComponent.builder().left("Reputation").right(plugin.getReputation() + "").build() ); } diff --git a/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java b/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java index 7ee4dab..2067cbd 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/FoundryOverlay3D.java @@ -1,229 +1,419 @@ 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.EasyGiantsFoundryHelper.getHeatColor; +import static com.toofifty.easygiantsfoundry.MouldHelper.SWORD_TYPE_1_VARBIT; +import static com.toofifty.easygiantsfoundry.MouldHelper.SWORD_TYPE_2_VARBIT; +import com.toofifty.easygiantsfoundry.enums.CommissionType; import com.toofifty.easygiantsfoundry.enums.Heat; import com.toofifty.easygiantsfoundry.enums.Stage; + import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Shape; import javax.inject.Inject; + import net.runelite.api.Client; import net.runelite.api.GameObject; import net.runelite.api.NPC; +import net.runelite.api.Perspective; import net.runelite.api.Point; +import net.runelite.api.coords.LocalPoint; import net.runelite.api.widgets.Widget; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.outline.ModelOutlineRenderer; +import org.apache.commons.lang3.StringUtils; -public class FoundryOverlay3D extends Overlay { +public class FoundryOverlay3D extends Overlay +{ - private static final int HAND_IN_WIDGET = 49414221; + private static final int HAND_IN_WIDGET = 49414221; + private final ModelOutlineRenderer modelOutlineRenderer; - GameObject tripHammer; - GameObject grindstone; - GameObject polishingWheel; - GameObject lavaPool; - GameObject waterfall; - GameObject mouldJig; - GameObject crucible; - NPC kovac; + GameObject tripHammer; + GameObject grindstone; + GameObject polishingWheel; + GameObject lavaPool; + GameObject waterfall; + GameObject mouldJig; + GameObject crucible; + NPC kovac; - private final Client client; - private final EasyGiantsFoundryState state; - private final EasyGiantsFoundryHelper helper; - private final EasyGiantsFoundryConfig config; + private final Client client; + private final EasyGiantsFoundryState state; + private final EasyGiantsFoundryConfig config; - @Inject - private FoundryOverlay3D(Client client, EasyGiantsFoundryState state, EasyGiantsFoundryHelper helper, - EasyGiantsFoundryConfig config) - { - setPosition(OverlayPosition.DYNAMIC); - this.client = client; - this.state = state; - this.helper = helper; - this.config = config; - } + @Inject + private FoundryOverlay3D( + Client client, + EasyGiantsFoundryState state, + EasyGiantsFoundryConfig config, + ModelOutlineRenderer modelOutlineRenderer) + { + setPosition(OverlayPosition.DYNAMIC); + this.client = client; + this.state = state; + this.config = config; + this.modelOutlineRenderer = modelOutlineRenderer; + } - private Color getObjectColor(Stage stage, Heat heat) - { - if (stage.getHeat() != heat) - { - return config.toolBad(); - } + private Color getObjectColor(Stage stage, Heat heat) + { + if (stage.getHeat() != heat) + { + return config.toolBad(); + } - if (BonusWidget.isActive(client)) - { - return config.toolBonus(); - } + if (BonusWidget.isActive(client)) + { + return config.toolBonus(); + } - int actionsLeft = helper.getActionsLeftInStage(); - int heatLeft = helper.getActionsForHeatLevel(); - if (actionsLeft <= 1 || heatLeft <= 1) - { - return config.toolCaution(); - } + int actionsLeft = state.getActionsLeftInStage(); + int heatLeft = state.getActionsForHeatLevel(); + if (actionsLeft <= 1 || heatLeft <= 1) + { + return config.toolCaution(); + } - return config.toolGood(); - } + return config.toolGood(); + } - private GameObject getStageObject(Stage stage) - { - switch (stage) - { - case TRIP_HAMMER: - return tripHammer; - case GRINDSTONE: - return grindstone; - case POLISHING_WHEEL: - return polishingWheel; - } - return null; - } + private GameObject getStageObject(Stage stage) + { + switch (stage) + { + case TRIP_HAMMER: + return tripHammer; + case GRINDSTONE: + return grindstone; + case POLISHING_WHEEL: + return polishingWheel; + } + return null; + } - @Override - public Dimension render(Graphics2D graphics) - { - if (!state.isEnabled()) - { - return null; - } + @Override + public Dimension render(Graphics2D graphics) + { + if (!state.isEnabled()) + { + return null; + } - if (config.highlightKovac()) - { - drawKovacIfHandIn(graphics); - } + if (config.highlightKovac()) + { + drawKovacIfHandIn(graphics); + } - if (state.getCurrentStage() == null) - { - if (config.highlightMould()) - { - drawMouldIfNotSet(graphics); - } - if (config.highlightCrucible()) - { - drawCrucibleIfMouldSet(graphics); - } - return null; - } + if (state.getCurrentStage() == null) + { + if (config.highlightMould()) + { + drawMouldIfNotSet(graphics); + } + if (config.highlightCrucible()) + { + drawCrucibleIfMouldSet(graphics); + } + return null; + } - Stage stage = state.getCurrentStage(); - GameObject stageObject = getStageObject(stage); - if (stageObject == null) - { - return null; - } + Stage stage = state.getCurrentStage(); + GameObject stageObject = getStageObject(stage); + if (stageObject == null) + { + return null; + } - Heat heat = state.getCurrentHeat(); - Color color = getObjectColor(stage, heat); - Shape objectClickbox = stageObject.getClickbox(); - if (objectClickbox != null && config.highlightTools()) - { - Point mousePosition = client.getMouseCanvasPosition(); - if (objectClickbox.contains(mousePosition.getX(), mousePosition.getY())) - { - graphics.setColor(color.darker()); - } - else - { - graphics.setColor(color); - } - graphics.draw(objectClickbox); - graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20)); - graphics.fill(objectClickbox); - } + drawHeatingActionOverlay(graphics, stageObject); - if (stage.getHeat() != heat && config.highlightWaterAndLava()) - { - drawHeatChangers(graphics); - } + Heat heat = state.getCurrentHeat(); + Color color = getObjectColor(stage, heat); + // TODO Config + if (config.highlightStyle() == HighlightStyle.HIGHLIGHT_CLICKBOX) + { + drawObjectClickbox(graphics, stageObject, color); + } + else + { + drawObjectOutline(graphics, stageObject, color); + } - return null; - } + if ((stage.getHeat() != heat || !state.heatingCoolingState.isIdle()) && config.highlightWaterAndLava()) + { + drawHeatChangers(graphics); + } - private void drawHeatChangers(Graphics2D graphics) - { - int change = state.getHeatChangeNeeded(); - Shape shape = null; - if (change < 0) - { - shape = waterfall.getClickbox(); - } else if (change > 0) - { - shape = lavaPool.getClickbox(); - } - if (shape != null) - { - Point mousePosition = client.getMouseCanvasPosition(); - Color color = config.lavaWaterfallColour(); - if (shape.contains(mousePosition.getX(), mousePosition.getY())) - { - graphics.setColor(color.darker()); - } - else - { - graphics.setColor(color); - } - graphics.draw(shape); - graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20)); - graphics.fill(shape); - } - } + if (state.heatingCoolingState.isCooling()) + { + drawHeatingActionOverlay(graphics, waterfall, false); + } + if (state.heatingCoolingState.isHeating()) + { + drawHeatingActionOverlay(graphics, lavaPool, true); + } - private void drawCrucibleIfMouldSet(Graphics2D graphics) - { - if (client.getVarbitValue(MouldHelper.SWORD_TYPE_1_VARBIT) == 0) - { - return; - } - if (client.getVarbitValue(EasyGiantsFoundryState.VARBIT_GAME_STAGE) != 1) - { - return; - } - Shape shape = crucible.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 drawMouldIfNotSet(Graphics2D graphics) - { - if (client.getWidget(EasyGiantsFoundryState.WIDGET_PROGRESS_PARENT) != null - || client.getVarbitValue(MouldHelper.SWORD_TYPE_1_VARBIT) == 0 - || (client.getVarbitValue(EasyGiantsFoundryState.VARBIT_GAME_STAGE) != 0 - && client.getVarbitValue(EasyGiantsFoundryState.VARBIT_GAME_STAGE) != 2)) - { - return; - } - Shape shape = mouldJig.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); - } - } + return null; + } - private void drawKovacIfHandIn(Graphics2D graphics) - { - Widget handInWidget = client.getWidget(HAND_IN_WIDGET); - if (handInWidget != null && !handInWidget.isHidden()) - { - Shape shape = kovac.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 drawObjectClickbox(Graphics2D graphics, GameObject stageObject, Color color) + { + Shape objectClickbox = stageObject.getClickbox(); + if (objectClickbox != null && config.highlightTools()) + { + Point mousePosition = client.getMouseCanvasPosition(); + if (objectClickbox.contains(mousePosition.getX(), mousePosition.getY())) + { + graphics.setColor(color.darker()); + } + else + { + graphics.setColor(color); + } + graphics.draw(objectClickbox); + graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20)); + graphics.fill(objectClickbox); + } + } + + private void drawObjectOutline(Graphics2D graphics, GameObject stageObject, Color color) + { + Color _color = new Color(color.getRed(), color.getGreen(), color.getBlue(), config.borderAlpha()); + modelOutlineRenderer.drawOutline(stageObject, config.borderThickness(), _color, config.borderFeather()); + } + + private void drawHeatingActionOverlay( + Graphics2D graphics, + GameObject stageObject, + boolean isLava /* and not cooling */) + { + if (!config.drawLavaWaterInfoOverlay()) + { + return; + } + + if (state.heatingCoolingState.isIdle()) + { + return; + } + + String text; + if (isLava) + { + // %d heats or %d dunks + text = String.format("%d %s", + state.heatingCoolingState.getRemainingDuration(), + state.heatingCoolingState.getActionName() + ); + } + else + { + // %d cools + text = String.format("%d %s", + state.heatingCoolingState.getRemainingDuration(), + state.heatingCoolingState.getActionName() + ); + } + + 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 drawHeatChangers(Graphics2D graphics) + { + int change = state.getHeatChangeNeeded(); + Shape shape = null; + + if (change < 0 || state.heatingCoolingState.isCooling()) + { + shape = waterfall.getClickbox(); + } + else if (change > 0 || state.heatingCoolingState.isHeating()) + { + shape = lavaPool.getClickbox(); + } + if (shape != null) + { + Point mousePosition = client.getMouseCanvasPosition(); + Color color = config.lavaWaterfallColour(); + if (shape.contains(mousePosition.getX(), mousePosition.getY())) + { + graphics.setColor(color.darker()); + } + else + { + graphics.setColor(color); + } + graphics.draw(shape); + graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20)); + graphics.fill(shape); + } + } + + static final int CRUCIBLE_CAPACITY = 28; + + private void drawCrucibleContent(Graphics2D graphics) + { + if (!config.drawCrucibleInfoOverlay()) + { + return; + } + String text = String.format("%d/%d quality: %.1f", state.getCrucibleCount(), CRUCIBLE_CAPACITY, state.getCrucibleQuality()); + + LocalPoint crucibleLoc = crucible.getLocalLocation(); + crucibleLoc = new LocalPoint(crucibleLoc.getX() - 100, crucibleLoc.getY()); + + Point pos = Perspective.getCanvasTextLocation(client, graphics, crucibleLoc, text, 200); + Color color; + if (state.getCrucibleCount() == CRUCIBLE_CAPACITY) + { + color = config.toolGood(); + } + else + { + color = config.generalHighlight(); + } + OverlayUtil.renderTextLocation(graphics, pos, text, color); + } + + + private void drawCrucibleIfMouldSet(Graphics2D graphics) + { + if (client.getVarbitValue(SWORD_TYPE_1_VARBIT) == 0) + { + return; + } + if (client.getVarbitValue(VARBIT_GAME_STAGE) != 1) + { + return; + } + + drawCrucibleContent(graphics); + + if (config.highlightStyle() == HighlightStyle.HIGHLIGHT_CLICKBOX) + { + Shape shape = crucible.getConvexHull(); + if (shape != null) + { + Color color = config.generalHighlight(); + if (state.getCrucibleCount() == CRUCIBLE_CAPACITY) + { + graphics.setColor(config.toolGood()); + } + else + { + graphics.setColor(config.generalHighlight()); + } + graphics.draw(shape); + graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20)); + graphics.fill(shape); + } + } + else if (config.highlightStyle() == HighlightStyle.HIGHLIGHT_BORDER) + { + Color color; + if (state.getCrucibleCount() == CRUCIBLE_CAPACITY) + { + color = config.toolGood(); + } + else + { + color = config.generalHighlight(); + } + drawObjectOutline(graphics, crucible, color); + } + } + + private void drawMouldIfNotSet(Graphics2D graphics) + { + if (client.getWidget(WIDGET_PROGRESS_PARENT) != null + || client.getVarbitValue(SWORD_TYPE_1_VARBIT) == 0 + || (client.getVarbitValue(VARBIT_GAME_STAGE) != 0 + && client.getVarbitValue(VARBIT_GAME_STAGE) != 2)) + { + return; + } + if (config.highlightStyle() == HighlightStyle.HIGHLIGHT_CLICKBOX) + { + Shape shape = mouldJig.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); + } + } + else if (config.highlightStyle() == HighlightStyle.HIGHLIGHT_BORDER) + { + drawObjectOutline(graphics, mouldJig, config.generalHighlight()); + } + + if (config.drawMouldInfoOverlay()) + { + CommissionType type1 = CommissionType.forVarbit(client.getVarbitValue(SWORD_TYPE_1_VARBIT)); + CommissionType type2 = CommissionType.forVarbit(client.getVarbitValue(SWORD_TYPE_2_VARBIT)); + String text = StringUtils.capitalize(type1.toString().toLowerCase()) + " " + StringUtils.capitalize(type2.toString().toLowerCase()); + LocalPoint textLocation = mouldJig.getLocalLocation(); + textLocation = new LocalPoint(textLocation.getX(), textLocation.getY()); + Point canvasLocation = Perspective.getCanvasTextLocation(client, graphics, textLocation, text, 100); + canvasLocation = new Point(canvasLocation.getX(), canvasLocation.getY() + 10); + OverlayUtil.renderTextLocation(graphics, canvasLocation, text, config.generalHighlight()); + } + } + + private void drawKovacIfHandIn(Graphics2D graphics) + { + Widget handInWidget = client.getWidget(HAND_IN_WIDGET); + if (handInWidget != null && !handInWidget.isHidden()) + { + Shape shape = kovac.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 drawHeatingActionOverlay(Graphics2D graphics, GameObject gameObject) + { + int actionsLeft = state.getActionsLeftInStage(); + int heatLeft = state.getActionsForHeatLevel(); + + // Draw heat left + if (config.drawHeatLeftOverlay()) + { + String text = "Heat left: " + heatLeft; + LocalPoint textLocation = gameObject.getLocalLocation(); + textLocation = new LocalPoint(textLocation.getX(), textLocation.getY()); + Point canvasLocation = Perspective.getCanvasTextLocation(client, graphics, textLocation, text, 250); + OverlayUtil.renderTextLocation(graphics, canvasLocation, text, getHeatColor(actionsLeft, heatLeft)); + } + if (config.drawActionLeftOverlay()) + // Draw actions left + { + String text = "Actions left: " + actionsLeft; + LocalPoint textLocation = gameObject.getLocalLocation(); + textLocation = new LocalPoint(textLocation.getX(), textLocation.getY()); + Point canvasLocation = Perspective.getCanvasTextLocation(client, graphics, textLocation, text, 250); + 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 new file mode 100644 index 0000000..6c534e6 --- /dev/null +++ b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionSolver.java @@ -0,0 +1,217 @@ +package com.toofifty.easygiantsfoundry; + +//import java.util.ArrayList; +//import java.util.List; + +/** + * 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). + *

+ * dx0 - players current heat at tick + * dx1 - dx0_current - dx0_last_tick, aka the first derivative + * dx2 - dx1_current - dx1_last_tick, aka the second derivative + *

+ * 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, + * 30, + * 33, + * 37, + * 41, + * 45, + * 49, + * 53, + * 57, + * 62, + * 67, + * 72, + * 77, + * 83, + * 89, + * 95, + * 91, + * }; + */ + +/* The following code-snippet can be copy-pasted into runelite developer-tools "Shell" to extract dx1 + +import java.awt.Toolkit; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Clipboard; + +import java.util.concurrent.atomic.AtomicInteger; + +int HEAT_ID = 13948; + +AtomicInteger tickCounter = new AtomicInteger(-1); +AtomicInteger prevHeat = new AtomicInteger(client.getVarbitValue(HEAT_ID)); + + +Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + +String output = ""; + +subscribe(VarbitChanged.class, ev -> +{ + if (ev.getVarbitId() == HEAT_ID) + { + int deltaHeat = ev.getValue() - prevHeat.getAndSet(ev.getValue()); + + if (deltaHeat == -1) return; // ignore passive drain + + String str = "[" + tickCounter.incrementAndGet() + + "] deltaHeat: " + deltaHeat; + log.info(str); + output = output + deltaHeat + "\n"; + StringSelection selection = new StringSelection(output); + clipboard.setContents(selection, selection); + } +}); +*/ + +public class HeatActionSolver +{ + + /** + * @param goal the desired heat destination + * @param init_dx1 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) + { + int dx0 = 0; + int dx1 = init_dx1; + int count_index = 0; + for (int dx2 = 1; dx0 <= goal; dx2++) + { // Start from 1 up to the count inclusive + int repetitions; + if (dx2 == 1) + { + repetitions = 2; // The first number appears twice + } + else if (dx2 % 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 && dx0 <= goal; j++) + { + dx0 += dx1; + dx1 += dx2 + dx2_offset; // Sum the current number 'repetitions' times + count_index += 1; + } + } + return count_index; + } + + + /** + * 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) + { + 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; + } + } + + + /** + * 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) + { + int _dx1 = constant; + for (int i = 0; i < index; ++i) + { + _dx1 += getDx2AtIndex(i); + } + + return _dx1; + } + +// 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 new file mode 100644 index 0000000..edb2cbf --- /dev/null +++ b/src/main/java/com/toofifty/easygiantsfoundry/HeatActionStateMachine.java @@ -0,0 +1,273 @@ +package com.toofifty.easygiantsfoundry; + +import com.toofifty.easygiantsfoundry.enums.Stage; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +/** + * A state-machine that keeps track of heating/cooling actions. + */ +@Slf4j +@Data +public class HeatActionStateMachine +{ + /** + * Tick counter for heating, -1 means not currently heating. + */ + int HeatingTicks = -1; + + /** + * Tick counter for cooling, -1 means not currently cooling. + */ + int CoolingTicks = -1; + + /** + * The velocity of the heating/cooling action. + */ + int Velocity; + + /** + * The acceleration bonus of the heating/cooling action. + */ + int AccelerationBonus; + + /** + * The starting heat amount of the heating/cooling action. + */ + int StartingHeat; + + /** + * The estimated tick duration of the heating/cooling action. + */ + int EstimatedDuration; + + /** + * The goal heat amount of the heating/cooling action. + */ + int GoalHeat = 0; + + /** + * 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; + + private EasyGiantsFoundryState State; + private EasyGiantsFoundryConfig Config; + + /** + * Start the state-machine with the given parameters. + *

+ * These parameters have to be set-up manually before start(). + * Velocity, AccelerationBonus, ActionName + * + * @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) + */ + public void start(EasyGiantsFoundryState state, EasyGiantsFoundryConfig config, int startingHeat) + { + // use Velocity to determine if heating or cooling + if (Velocity > 0) + { + HeatingTicks = 0; + CoolingTicks = -1; + } + else + { + CoolingTicks = 0; + HeatingTicks = -1; + } + StartingHeat = startingHeat - Velocity; + State = state; + Config = config; + + calculateEstimates(); + } + + /** + * Get the estimated remaining duration of the heating/cooling action. + * + * @return the estimated remaining duration in ticks + */ + public int getRemainingDuration() + { + if (isHeating()) + { + return Math.max(0, EstimatedDuration - HeatingTicks); + } + else if (isCooling()) + { + return Math.max(0, EstimatedDuration - CoolingTicks); + } + else + { + return 0; + } + } + + /** + * 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() + { + int[] range = State.getCurrentHeatRange(); + Stage stage = State.getCurrentStage(); + int actionsLeft = State.getActionsLeftInStage(); + int actionsLeft_DeltaHeat = actionsLeft * stage.getHeatChange(); + if (isHeating()) + { + if (stage.isHeating()) + { + GoalHeat = Math.max(range[0] + Config.heatingCoolingBuffer(), range[1] - actionsLeft_DeltaHeat); + if (StartingHeat < GoalHeat) + { + EstimatedDuration = HeatActionSolver.findDx0Index( + GoalHeat - StartingHeat, + Velocity, AccelerationBonus); + + GoalHeat += EstimatedDuration / 2; // compensate for decay during heating + + EstimatedDuration = HeatActionSolver.findDx0Index( + GoalHeat - StartingHeat, + Velocity, AccelerationBonus); + } + else // overheating + { + EstimatedDuration = 0; + } + } + else // is cooling + { + GoalHeat = Math.min(range[1] - Config.heatingCoolingBuffer(), range[0] - actionsLeft_DeltaHeat); + if (StartingHeat < GoalHeat) + { + EstimatedDuration = HeatActionSolver.findDx0Index( + GoalHeat - StartingHeat, + Velocity, AccelerationBonus + ); + } + else // cold enough + { + EstimatedDuration = 0; + } + } + } + else if (isCooling()) + { + if (stage.isCooling()) + { + GoalHeat = Math.max(range[1] - Config.heatingCoolingBuffer(), range[0] + actionsLeft_DeltaHeat); + if (StartingHeat > GoalHeat) // too hot + { + EstimatedDuration = HeatActionSolver.findDx0Index( + StartingHeat - GoalHeat, + Math.abs(Velocity), Math.abs(AccelerationBonus) + ); + } + else // hot enough + { + EstimatedDuration = 0; + } + } + else // Heating Stage + { + GoalHeat = Math.max(range[0] + Config.heatingCoolingBuffer(), range[1] - actionsLeft_DeltaHeat); + if (StartingHeat > GoalHeat) + { + EstimatedDuration = HeatActionSolver.findDx0Index( + (StartingHeat - GoalHeat), + Math.abs(Velocity), Math.abs(AccelerationBonus) + ); + } + else + { + EstimatedDuration = 0; + } + } + } + } + + /** + * 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(int velocity, int accelerationBonus, String actionName) + { + Velocity = velocity; + AccelerationBonus = accelerationBonus; + ActionName = actionName; + } + + /** + * Stop the state-machine. + */ + public void stop() + { + HeatingTicks = -1; + CoolingTicks = -1; + ActionName = null; + } + + /** + * Check if the state is currently heating. + * + * @return true if heating, false otherwise + */ + public boolean isHeating() + { + return HeatingTicks >= 0; + } + + /** + * Check if the state is currently cooling. + * + * @return true if cooling, false otherwise + */ + public boolean isCooling() + { + return CoolingTicks >= 0; + } + + /** + * Check if the heating/cooling state is currently idle. Neither heating nor cooling. + * + * @return + */ + public boolean isIdle() + { + return !(isHeating() || isCooling()); + } + + /** + * Tick the state-machine. This has to be called onVarbitChanged in order to sync with the game. + */ + public void onTick() + { + if (isHeating()) + { + HeatingTicks++; + if (HeatingTicks >= EstimatedDuration) + { + stop(); + } + } + if (isCooling()) + { + CoolingTicks++; + if (CoolingTicks >= EstimatedDuration) + { + stop(); + } + } +// log.info("\nReal Heat: " + State.getHeatAmount() +// + "\nGoal Heat - StartingHeat: " + (GoalHeat - StartingHeat) +// + "\nDuration: " + EstimatedDuration); + } + +} diff --git a/src/main/java/com/toofifty/easygiantsfoundry/HighlightStyle.java b/src/main/java/com/toofifty/easygiantsfoundry/HighlightStyle.java new file mode 100644 index 0000000..e1eaedd --- /dev/null +++ b/src/main/java/com/toofifty/easygiantsfoundry/HighlightStyle.java @@ -0,0 +1,7 @@ +package com.toofifty.easygiantsfoundry; + +public enum HighlightStyle +{ + HIGHLIGHT_BORDER, + HIGHLIGHT_CLICKBOX +} diff --git a/src/main/java/com/toofifty/easygiantsfoundry/MathUtil.java b/src/main/java/com/toofifty/easygiantsfoundry/MathUtil.java new file mode 100644 index 0000000..e5dbb4c --- /dev/null +++ b/src/main/java/com/toofifty/easygiantsfoundry/MathUtil.java @@ -0,0 +1,9 @@ +package com.toofifty.easygiantsfoundry; + +public class MathUtil +{ + public static double max1(double a) + { + return a > 0 ? a : 1; + } +} diff --git a/src/main/java/com/toofifty/easygiantsfoundry/MouldHelper.java b/src/main/java/com/toofifty/easygiantsfoundry/MouldHelper.java index 0c0a561..a6b8b9e 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/MouldHelper.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/MouldHelper.java @@ -2,9 +2,11 @@ package com.toofifty.easygiantsfoundry; import com.toofifty.easygiantsfoundry.enums.CommissionType; import com.toofifty.easygiantsfoundry.enums.Mould; + import java.util.LinkedHashMap; import java.util.Map; import javax.inject.Inject; + import net.runelite.api.Client; import net.runelite.api.ScriptID; import net.runelite.api.widgets.Widget; @@ -12,83 +14,89 @@ import net.runelite.client.callback.ClientThread; public class MouldHelper { - static final int MOULD_LIST_PARENT = 47054857; - static final int DRAW_MOULD_LIST_SCRIPT = 6093; - static final int REDRAW_MOULD_LIST_SCRIPT = 6095; - static final int RESET_MOULD_SCRIPT = 6108; - public static final int SELECT_MOULD_SCRIPT = 6098; - static final int SWORD_TYPE_1_VARBIT = 13907; // 4=Broad - static final int SWORD_TYPE_2_VARBIT = 13908; // 3=Flat - private static final int DISABLED_TEXT_COLOR = 0x9f9f9f; - private static final int GREEN = 0xdc10d; + static final int MOULD_LIST_PARENT = 47054857; + static final int DRAW_MOULD_LIST_SCRIPT = 6093; + static final int REDRAW_MOULD_LIST_SCRIPT = 6095; + static final int RESET_MOULD_SCRIPT = 6108; + public static final int SELECT_MOULD_SCRIPT = 6098; + static final int SWORD_TYPE_1_VARBIT = 13907; // 4=Broad + static final int SWORD_TYPE_2_VARBIT = 13908; // 3=Flat + private static final int DISABLED_TEXT_COLOR = 0x9f9f9f; - @Inject - private Client client; + @Inject + private Client client; - @Inject - private ClientThread clientThread; + @Inject + private ClientThread clientThread; @Inject private EasyGiantsFoundryConfig config; - public void selectBest(int scriptId) - { - Widget parent = client.getWidget(MOULD_LIST_PARENT); - if (parent == null || parent.getChildren() == null) - { - return; - } + public void selectBest(int scriptId) + { + Widget parent = client.getWidget(MOULD_LIST_PARENT); + if (parent == null || parent.getChildren() == null) + { + return; + } - Map mouldToChild = getOptions(parent.getChildren()); + Map mouldToChild = getOptions(parent.getChildren()); - int bestScore = -1; - Widget bestWidget = null; - CommissionType type1 = CommissionType.forVarbit(client.getVarbitValue(SWORD_TYPE_1_VARBIT)); - CommissionType type2 = CommissionType.forVarbit(client.getVarbitValue(SWORD_TYPE_2_VARBIT)); - for (Map.Entry entry : mouldToChild.entrySet()) { - Mould mould = entry.getKey(); - int score = mould.getScore(type1, type2); - if (score > bestScore) { - bestScore = score; - bestWidget = entry.getValue(); - } - } - if (bestWidget != null) { - bestWidget.setTextColor(config.mouldTextColour().getRGB()); - } + int bestScore = -1; + Widget bestWidget = null; + CommissionType type1 = CommissionType.forVarbit(client.getVarbitValue(SWORD_TYPE_1_VARBIT)); + CommissionType type2 = CommissionType.forVarbit(client.getVarbitValue(SWORD_TYPE_2_VARBIT)); + for (Map.Entry entry : mouldToChild.entrySet()) + { + Mould mould = entry.getKey(); + int score = mould.getScore(type1, type2); + if (score > bestScore) + { + bestScore = score; + bestWidget = entry.getValue(); + } + } + if (bestWidget != null) + { + bestWidget.setTextColor(config.mouldTextColour().getRGB()); + } - if (scriptId == DRAW_MOULD_LIST_SCRIPT || scriptId == REDRAW_MOULD_LIST_SCRIPT) - { - Widget scrollBar = client.getWidget(718, 11); - Widget scrollList = client.getWidget(718, 9); - if (scrollBar != null && scrollList != null) - { - int height = scrollList.getHeight(); - int scrollMax = scrollList.getScrollHeight(); - Widget finalBestWidget = bestWidget; - clientThread.invokeLater(() -> { - if (finalBestWidget != null) { - client.runScript( - ScriptID.UPDATE_SCROLLBAR, - scrollBar.getId(), - scrollList.getId(), - Math.min(finalBestWidget.getOriginalY() - 2, scrollMax - height)); - } - }); - } - } - } + if (scriptId == DRAW_MOULD_LIST_SCRIPT || scriptId == REDRAW_MOULD_LIST_SCRIPT) + { + Widget scrollBar = client.getWidget(718, 11); + Widget scrollList = client.getWidget(718, 9); + if (scrollBar != null && scrollList != null) + { + int height = scrollList.getHeight(); + int scrollMax = scrollList.getScrollHeight(); + Widget finalBestWidget = bestWidget; + clientThread.invokeLater(() -> + { + if (finalBestWidget != null) + { + client.runScript( + ScriptID.UPDATE_SCROLLBAR, + scrollBar.getId(), + scrollList.getId(), + Math.min(finalBestWidget.getOriginalY() - 2, scrollMax - height)); + } + }); + } + } + } - private Map getOptions(Widget[] children) { - Map mouldToChild = new LinkedHashMap<>(); - for (int i = 2; i < children.length; i += 17) - { - Widget child = children[i]; - Mould mould = Mould.forName(child.getText()); - if (mould != null && child.getTextColor() != DISABLED_TEXT_COLOR) { - mouldToChild.put(mould, child); - } - } - return mouldToChild; - } + private Map getOptions(Widget[] children) + { + Map mouldToChild = new LinkedHashMap<>(); + for (int i = 2; i < children.length; i += 17) + { + Widget child = children[i]; + Mould mould = Mould.forName(child.getText()); + if (mould != null && child.getTextColor() != DISABLED_TEXT_COLOR) + { + mouldToChild.put(mould, child); + } + } + return mouldToChild; + } } diff --git a/src/main/java/com/toofifty/easygiantsfoundry/enums/CommissionType.java b/src/main/java/com/toofifty/easygiantsfoundry/enums/CommissionType.java index 5fec63f..613c9cf 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/enums/CommissionType.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/enums/CommissionType.java @@ -1,21 +1,24 @@ package com.toofifty.easygiantsfoundry.enums; -public enum CommissionType { - NONE, - NARROW, // 1 - LIGHT, // 2 - FLAT, // 3 - BROAD, // 4 - HEAVY, // 5 - SPIKED, // 6 - ; +public enum CommissionType +{ + NONE, + NARROW, // 1 + LIGHT, // 2 + FLAT, // 3 + BROAD, // 4 + HEAVY, // 5 + SPIKED, // 6 + ; - public static final CommissionType[] values = CommissionType.values(); + public static final CommissionType[] values = CommissionType.values(); - public static CommissionType forVarbit(int varbitValue) { - if (varbitValue < 0 || varbitValue >= values.length) { - return NONE; - } - return CommissionType.values[varbitValue]; - } + public static CommissionType forVarbit(int varbitValue) + { + if (varbitValue < 0 || varbitValue >= values.length) + { + return NONE; + } + return CommissionType.values[varbitValue]; + } } diff --git a/src/main/java/com/toofifty/easygiantsfoundry/enums/Heat.java b/src/main/java/com/toofifty/easygiantsfoundry/enums/Heat.java index d196e49..6e4a026 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/enums/Heat.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/enums/Heat.java @@ -1,6 +1,7 @@ package com.toofifty.easygiantsfoundry.enums; import java.awt.Color; + import lombok.AllArgsConstructor; import lombok.Getter; import net.runelite.client.ui.ColorScheme; diff --git a/src/main/java/com/toofifty/easygiantsfoundry/enums/Mould.java b/src/main/java/com/toofifty/easygiantsfoundry/enums/Mould.java index ddcfbbe..e707d4b 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/enums/Mould.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/enums/Mould.java @@ -9,61 +9,66 @@ import static com.toofifty.easygiantsfoundry.enums.CommissionType.*; import static com.toofifty.easygiantsfoundry.enums.MouldType.*; @AllArgsConstructor -public enum Mould { - CHOPPER_FORTE("Chopper Forte", FORTE, ImmutableMap.of(BROAD, 4, LIGHT, 4, FLAT, 4)), - GALDIUS_RICASSO("Galdius Ricasso", FORTE, ImmutableMap.of(BROAD, 4, HEAVY, 4, FLAT, 4)), - DISARMING_FORTE("Disarming Forte", FORTE, ImmutableMap.of(NARROW, 4, LIGHT, 4, SPIKED, 4)), - MEDUSA_RICASSO("Medusa Ricasso", FORTE, ImmutableMap.of(BROAD, 8, HEAVY, 6, FLAT, 8)), - SERPENT_RICASSO("Serpent Ricasso", FORTE, ImmutableMap.of(NARROW, 6, LIGHT, 8, FLAT, 8)), - SERRATED_FORTE("Serrated Forte", FORTE, ImmutableMap.of(NARROW, 8, HEAVY, 8, SPIKED, 6)), - STILETTO_FORTE("Stiletto Forte", FORTE, ImmutableMap.of(NARROW, 8, LIGHT, 10, FLAT, 8)), - DEFENDER_BASE("Defender Base", FORTE, ImmutableMap.of(BROAD, 8, HEAVY, 10, FLAT, 8)), - JUGGERNAUT_FORTE("Juggernaut Forte", FORTE, ImmutableMap.of(BROAD, 4, HEAVY, 4, SPIKED, 16)), - CHOPPER_FORTE_1("Chopper Forte +1", FORTE, ImmutableMap.of(BROAD, 3, LIGHT, 4, FLAT, 18)), - SPIKER("Spiker!", FORTE, ImmutableMap.of(NARROW, 1, HEAVY, 2, SPIKED, 22)), - SAW_BLADE("Saw Blade", BLADE, ImmutableMap.of(BROAD, 4, LIGHT, 4, SPIKED, 4)), - DEFENDERS_EDGE("Defenders Edge", BLADE, ImmutableMap.of(BROAD, 4, HEAVY, 4, SPIKED, 4)), - FISH_BLADE("Fish Blade", BLADE, ImmutableMap.of(NARROW, 4, LIGHT, 4, FLAT, 4)), - MEDUSA_BLADE("Medusa Blade", BLADE, ImmutableMap.of(BROAD, 8, HEAVY, 8, FLAT, 6)), - STILETTO_BLADE("Stiletto Blade", BLADE, ImmutableMap.of(NARROW, 8, LIGHT, 6, FLAT, 8)), - GLADIUS_EDGE("Gladius Edge", BLADE, ImmutableMap.of(NARROW, 6, HEAVY, 8, FLAT, 8)), - FLAMBERGE_BLADE("Flamberge Blade", BLADE, ImmutableMap.of(NARROW, 8, LIGHT, 8, SPIKED, 10)), - SERPENT_BLADE("Serpent Blade", BLADE, ImmutableMap.of(NARROW, 10, LIGHT, 8, FLAT, 8)), - CLAYMORE_BLADE("Claymore Blade", BLADE, ImmutableMap.of(BROAD, 16, HEAVY, 4, FLAT, 4)), - FLEUR_DE_BLADE("Fleur de Blade", BLADE, ImmutableMap.of(BROAD, 4, HEAVY, 18, SPIKED, 1)), - CHOPPA("Choppa!", BLADE, ImmutableMap.of(BROAD, 1, LIGHT, 22, FLAT, 2)), - PEOPLE_POKER_POINT("People Poker Point", TIP, ImmutableMap.of(NARROW, 4, HEAVY, 4, FLAT, 4)), - CHOPPER_TIP("Chopper Tip", TIP, ImmutableMap.of(BROAD, 4, LIGHT, 4, SPIKED, 4)), - MEDUSAS_HEAD("Medusa's Head", TIP, ImmutableMap.of(BROAD, 4, HEAVY, 4, SPIKED, 4)), - SERPENTS_FANG("Serpent's Fang", TIP, ImmutableMap.of(NARROW, 8, LIGHT, 6, SPIKED, 8)), - GLADIUS_POINT("Gladius Point", TIP, ImmutableMap.of(NARROW, 8, HEAVY, 8, FLAT, 6)), - SAW_TIP("Saw Tip", TIP, ImmutableMap.of(BROAD, 6, HEAVY, 8, SPIKED, 8)), - CORRUPTED_POINT("Corrupted Point", TIP, ImmutableMap.of(NARROW, 8, LIGHT, 10, SPIKED, 8)), - DEFENDERS_TIP("Defenders Tip", TIP, ImmutableMap.of(BROAD, 10, HEAVY, 8, SPIKED, 8)), - SERRATED_TIP("Serrated Tip", TIP, ImmutableMap.of(NARROW, 4, LIGHT, 16, SPIKED, 4)), - NEEDLE_POINT("Needle Point", TIP, ImmutableMap.of(NARROW, 18, LIGHT, 3, FLAT, 4)), - THE_POINT("The Point!", TIP, ImmutableMap.of(BROAD, 2, LIGHT, 1, FLAT, 22)), - ; +public enum Mould +{ + CHOPPER_FORTE("Chopper Forte", FORTE, ImmutableMap.of(BROAD, 4, LIGHT, 4, FLAT, 4)), + GALDIUS_RICASSO("Galdius Ricasso", FORTE, ImmutableMap.of(BROAD, 4, HEAVY, 4, FLAT, 4)), + DISARMING_FORTE("Disarming Forte", FORTE, ImmutableMap.of(NARROW, 4, LIGHT, 4, SPIKED, 4)), + MEDUSA_RICASSO("Medusa Ricasso", FORTE, ImmutableMap.of(BROAD, 8, HEAVY, 6, FLAT, 8)), + SERPENT_RICASSO("Serpent Ricasso", FORTE, ImmutableMap.of(NARROW, 6, LIGHT, 8, FLAT, 8)), + SERRATED_FORTE("Serrated Forte", FORTE, ImmutableMap.of(NARROW, 8, HEAVY, 8, SPIKED, 6)), + STILETTO_FORTE("Stiletto Forte", FORTE, ImmutableMap.of(NARROW, 8, LIGHT, 10, FLAT, 8)), + DEFENDER_BASE("Defender Base", FORTE, ImmutableMap.of(BROAD, 8, HEAVY, 10, FLAT, 8)), + JUGGERNAUT_FORTE("Juggernaut Forte", FORTE, ImmutableMap.of(BROAD, 4, HEAVY, 4, SPIKED, 16)), + CHOPPER_FORTE_1("Chopper Forte +1", FORTE, ImmutableMap.of(BROAD, 3, LIGHT, 4, FLAT, 18)), + SPIKER("Spiker!", FORTE, ImmutableMap.of(NARROW, 1, HEAVY, 2, SPIKED, 22)), + SAW_BLADE("Saw Blade", BLADE, ImmutableMap.of(BROAD, 4, LIGHT, 4, SPIKED, 4)), + DEFENDERS_EDGE("Defenders Edge", BLADE, ImmutableMap.of(BROAD, 4, HEAVY, 4, SPIKED, 4)), + FISH_BLADE("Fish Blade", BLADE, ImmutableMap.of(NARROW, 4, LIGHT, 4, FLAT, 4)), + MEDUSA_BLADE("Medusa Blade", BLADE, ImmutableMap.of(BROAD, 8, HEAVY, 8, FLAT, 6)), + STILETTO_BLADE("Stiletto Blade", BLADE, ImmutableMap.of(NARROW, 8, LIGHT, 6, FLAT, 8)), + GLADIUS_EDGE("Gladius Edge", BLADE, ImmutableMap.of(NARROW, 6, HEAVY, 8, FLAT, 8)), + FLAMBERGE_BLADE("Flamberge Blade", BLADE, ImmutableMap.of(NARROW, 8, LIGHT, 8, SPIKED, 10)), + SERPENT_BLADE("Serpent Blade", BLADE, ImmutableMap.of(NARROW, 10, LIGHT, 8, FLAT, 8)), + CLAYMORE_BLADE("Claymore Blade", BLADE, ImmutableMap.of(BROAD, 16, HEAVY, 4, FLAT, 4)), + FLEUR_DE_BLADE("Fleur de Blade", BLADE, ImmutableMap.of(BROAD, 4, HEAVY, 18, SPIKED, 1)), + CHOPPA("Choppa!", BLADE, ImmutableMap.of(BROAD, 1, LIGHT, 22, FLAT, 2)), + PEOPLE_POKER_POINT("People Poker Point", TIP, ImmutableMap.of(NARROW, 4, HEAVY, 4, FLAT, 4)), + CHOPPER_TIP("Chopper Tip", TIP, ImmutableMap.of(BROAD, 4, LIGHT, 4, SPIKED, 4)), + MEDUSAS_HEAD("Medusa's Head", TIP, ImmutableMap.of(BROAD, 4, HEAVY, 4, SPIKED, 4)), + SERPENTS_FANG("Serpent's Fang", TIP, ImmutableMap.of(NARROW, 8, LIGHT, 6, SPIKED, 8)), + GLADIUS_POINT("Gladius Point", TIP, ImmutableMap.of(NARROW, 8, HEAVY, 8, FLAT, 6)), + SAW_TIP("Saw Tip", TIP, ImmutableMap.of(BROAD, 6, HEAVY, 8, SPIKED, 8)), + CORRUPTED_POINT("Corrupted Point", TIP, ImmutableMap.of(NARROW, 8, LIGHT, 10, SPIKED, 8)), + DEFENDERS_TIP("Defenders Tip", TIP, ImmutableMap.of(BROAD, 10, HEAVY, 8, SPIKED, 8)), + SERRATED_TIP("Serrated Tip", TIP, ImmutableMap.of(NARROW, 4, LIGHT, 16, SPIKED, 4)), + NEEDLE_POINT("Needle Point", TIP, ImmutableMap.of(NARROW, 18, LIGHT, 3, FLAT, 4)), + THE_POINT("The Point!", TIP, ImmutableMap.of(BROAD, 2, LIGHT, 1, FLAT, 22)), + ; - private final String name; - private final MouldType mouldType; - private final Map typeToScore; + private final String name; + private final MouldType mouldType; + private final Map typeToScore; - public static final Mould[] values = Mould.values(); + public static final Mould[] values = Mould.values(); - public static Mould forName(String text) { - for (Mould mould : values) { - if (mould.name.equalsIgnoreCase(text)) { - return mould; - } - } - return null; - } + public static Mould forName(String text) + { + for (Mould mould : values) + { + if (mould.name.equalsIgnoreCase(text)) + { + return mould; + } + } + return null; + } - public int getScore(CommissionType type1, CommissionType type2) { - int score = 0; - score += typeToScore.getOrDefault(type1, 0); - score += typeToScore.getOrDefault(type2, 0); - return score; - } + public int getScore(CommissionType type1, CommissionType type2) + { + int score = 0; + score += typeToScore.getOrDefault(type1, 0); + score += typeToScore.getOrDefault(type2, 0); + return score; + } } diff --git a/src/main/java/com/toofifty/easygiantsfoundry/enums/MouldType.java b/src/main/java/com/toofifty/easygiantsfoundry/enums/MouldType.java index eb095e2..2074385 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/enums/MouldType.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/enums/MouldType.java @@ -1,7 +1,8 @@ package com.toofifty.easygiantsfoundry.enums; -public enum MouldType { - FORTE, - BLADE, - TIP, +public enum MouldType +{ + FORTE, + BLADE, + TIP, } diff --git a/src/main/java/com/toofifty/easygiantsfoundry/enums/Stage.java b/src/main/java/com/toofifty/easygiantsfoundry/enums/Stage.java index 633fad3..35b670c 100644 --- a/src/main/java/com/toofifty/easygiantsfoundry/enums/Stage.java +++ b/src/main/java/com/toofifty/easygiantsfoundry/enums/Stage.java @@ -15,4 +15,14 @@ public enum Stage private final Heat heat; private final int progressPerAction; private final int heatChange; + + public boolean isHeating() + { + return heatChange > 0; + } + + public boolean isCooling() + { + return heatChange < 0; + } } diff --git a/src/test/java/com/toofifty/easygiantsfoundry/HeatSolverTest.java b/src/test/java/com/toofifty/easygiantsfoundry/HeatSolverTest.java new file mode 100644 index 0000000..69cac3c --- /dev/null +++ b/src/test/java/com/toofifty/easygiantsfoundry/HeatSolverTest.java @@ -0,0 +1,178 @@ +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 + 1); + } + } + + @Test + public void TestHeatSolver_Dx0_Manual() + { + for (int i = 0; i < 50; i++) + { + System.err.println("[" + (350 + i) + "]" + HeatActionSolver.findDx0Index(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.findDx0Index(1000, 7, 1)); + } + + public void TestHeatSolver_Dx0_Helper(int dx0, int constant, int answer_index) + { + System.err.print(dx0 + "->" + HeatActionSolver.findDx0Index(dx0, constant, 0) + ","); + + // test calcDx0Index + assertEquals("Asserting dx0 index for " + answer_index, + answer_index, HeatActionSolver.findDx0Index(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