Merge pull request #34 from TheLouisHong/master

Added Crucible Overlay, Tools action/heat overlay, Lava/waterfall prediction overlay, Border highlighting
This commit is contained in:
Patrick Watts
2024-03-02 19:59:36 +04:00
committed by GitHub
29 changed files with 1934 additions and 687 deletions

View File

@@ -1,20 +1,44 @@
# Easy Giant's Foundry <p align="center">
<picture>
<source media="(prefers-color-scheme: light)" srcset="./banner_black.png">
<img width=60% src="./banner_white.png">
</picture>
</p>
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 # Pictures
- Shows number of actions required to move to the next stage Best Mould | Crucible Alloy Quality
- Shows number of actions before gaining/losing too much heat :-------------------------:|:-------------------------:
- Shows best moulds to use <img width=350px src="./readme_gifs/best-mould.webp">|<img width=350px src="./readme_gifs/crucible-value.png">
- Highlights relevant tool with customizable status colors
* Red = Wrong temperature Heating/Cooling Prediction | Low/High Heat Warning
* Green = Right temperature :-------------------------:|:-------------------------:
* Orange = one action or temperature change remaining <img width=350px src="./readme_gifs/lava-waterfall-estimate.webp">|<img width=350px src="./readme_gifs/tool-damage-warning.webp">
* Cyan = Click tool again for bonus progress
- Highlights Kovac, Crucible, and Mould Jig when relevant Bonus Click Notification | Information Panel
- Highlights waterfall/lava pool when temperature is wrong :-------------------------:|:-------------------------:
<img width=350px src="./readme_gifs/tools-bonus-notification.webp">|<img width=350px src="./readme_gifs/info-panel.webp">
## Contributors ## Contributors
@@ -30,4 +54,11 @@ Helpful overlays for the Giant's Foundry minigame
- [Vanillj](https://github.com/Vanillj "Vanillj's github") - [Vanillj](https://github.com/Vanillj "Vanillj's github")
* Added config * Added config
* Added notifications for heat/stage changes * Added notifications for heat/stage changes
* Added config for actions/heat left for notifications * 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

BIN
banner_black.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
banner_white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
readme_gifs/best-mould.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

View File

@@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

BIN
readme_gifs/info-panel.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 KiB

View File

@@ -3,15 +3,17 @@ package com.toofifty.easygiantsfoundry;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.Widget;
public class BonusWidget { public class BonusWidget
private static final int BONUS_WIDGET = 49414148; {
private static final int BONUS_COLOR = 0xfcd703; private static final int BONUS_WIDGET = 49414148;
private static final int BONUS_COLOR = 0xfcd703;
static boolean isActive(Client client) { static boolean isActive(Client client)
Widget bonusWidget = client.getWidget(BONUS_WIDGET); {
return bonusWidget != null Widget bonusWidget = client.getWidget(BONUS_WIDGET);
&& bonusWidget.getChildren() != null return bonusWidget != null
&& bonusWidget.getChildren().length != 0 && bonusWidget.getChildren() != null
&& bonusWidget.getChild(0).getTextColor() == BONUS_COLOR; && bonusWidget.getChildren().length != 0
} && bonusWidget.getChild(0).getTextColor() == BONUS_COLOR;
}
} }

View File

@@ -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;
}

View File

@@ -1,245 +1,404 @@
package com.toofifty.easygiantsfoundry; package com.toofifty.easygiantsfoundry;
import java.awt.Color; import java.awt.Color;
import net.runelite.client.config.Config; import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem; import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.ConfigSection; import net.runelite.client.config.ConfigSection;
import net.runelite.client.config.Range;
import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.ColorScheme;
@ConfigGroup(EasyGiantsFoundryConfig.GROUP) @ConfigGroup(EasyGiantsFoundryConfig.GROUP)
public interface EasyGiantsFoundryConfig extends Config { public interface EasyGiantsFoundryConfig extends Config
String GROUP = "easygiantsfoundry"; {
String SOUND_ID = "soundID";
String POINTS_KEY = "easygiantsfoundrypoints";
@ConfigSection( String GROUP = "easygiantsfoundry";
name = "Notifications", String SOUND_ID = "soundID";
description = "Notifications", String POINTS_KEY = "easygiantsfoundrypoints";
position = 0
)
String notificationList = "notificationList";
@ConfigItem( @ConfigSection(
keyName = "giantsFoundryStageNotification", name = "Notifications",
name = "Notify stage changes", description = "Notifications",
description = "Notifies just before completing a stage", position = 0
position = 0, )
section = notificationList String notificationList = "notificationList";
)
default boolean showGiantsFoundryStageNotifications() {
return true;
}
@ConfigItem( @ConfigItem(
keyName = "giantsFoundryHeatNotification", keyName = "giantsFoundryStageNotification",
name = "Notify heat changes", name = "Notify stage changes",
description = "Notifies just before overheating/cooling when using tools", description = "Notifies just before completing a stage",
position = 1, position = 0,
section = notificationList section = notificationList
) )
default boolean showGiantsFoundryHeatNotifications() { default boolean showGiantsFoundryStageNotifications()
return true; {
} return true;
}
@ConfigItem( @ConfigItem(
keyName = "giantsFoundryStageThreshold", keyName = "giantsFoundryHeatNotification",
name = "Stage threshold notification", name = "Notify heat changes",
description = "The number of actions left required for the notification.", description = "Notifies just before overheating/cooling when using tools",
position = 2, position = 1,
section = notificationList section = notificationList
) )
default int StageNotificationsThreshold() { default boolean showGiantsFoundryHeatNotifications()
return 1; {
} return true;
}
@ConfigItem( @ConfigItem(
keyName = "giantsFoundryHeatThreshold", keyName = "giantsFoundryStageThreshold",
name = "Heat threshold notification", name = "Stage threshold notification",
description = "The heat level left required for the notification.", description = "The number of actions left required for the notification.",
position = 3, position = 2,
section = notificationList section = notificationList
) )
default int HeatNotificationsThreshold() { default int StageNotificationsThreshold()
return 1; {
} return 1;
}
@ConfigItem( @ConfigItem(
keyName = "bonusNotification", keyName = "giantsFoundryHeatThreshold",
name = "Notify bonus", name = "Heat threshold notification",
description = "Notifies when bonus appears", description = "The heat level left required for the notification.",
position = 4, position = 3,
section = notificationList section = notificationList
) )
default boolean bonusNotification() { default int HeatNotificationsThreshold()
return false; {
} return 1;
}
@ConfigItem( @ConfigItem(
keyName = "bonusSound", keyName = "bonusNotification",
name = "Bonus sound", name = "Notify bonus",
description = "Plays a sound when bonus appears", description = "Notifies when bonus appears",
position = 5, position = 4,
section = notificationList section = notificationList
) )
default boolean bonusSoundNotify() { default boolean bonusNotification()
return true; {
} return false;
}
@ConfigItem( @ConfigItem(
keyName = SOUND_ID, keyName = "bonusSound",
name = "Bonus sound ID", name = "Bonus sound",
description = "Sound Effect ID to play when bonus appears", description = "Plays a sound when bonus appears",
position = 6, position = 5,
section = notificationList section = notificationList
) )
default int soundId() { default boolean bonusSoundNotify()
return 4212; {
} 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( @ConfigSection(
name = "Highlights", name = "Highlights",
description = "3D npc/object highlights", description = "3D npc/object highlights",
position = 1 position = 1
) )
String highlightList = "highlightList"; String highlightList = "highlightList";
@ConfigItem( @ConfigItem(
keyName = "toolsHighlight", name = "Highlight Style",
name = "Highlight Tools", description = "The style of the highlight",
description = "Highlights current tool with symbolic colors", position = 0,
position = 0, section = highlightList,
section = highlightList keyName = "overlayOption")
) default HighlightStyle highlightStyle()
default boolean highlightTools() { {
return true; return HighlightStyle.HIGHLIGHT_CLICKBOX;
} }
@ConfigItem( @Range(
keyName = "waterLavaHighlight", min = 1,
name = "Highlight Waterfall/Lava Pool", max = 4
description = "Highlight Lava Pool / Waterfall when heat change required", )
position = 1, @ConfigItem(
section = highlightList keyName = "borderThickness",
) name = "Border Thickness",
default boolean highlightWaterAndLava() { description = "The thickness of the border",
return true; position = 1,
} section = highlightList
)
default int borderThickness()
{
return 1;
}
@ConfigItem( @Range(
keyName = "mouldHighlight", min = 0,
name = "Highlight Mould", max = 4
description = "Highlight Mould when it should be clicked", )
position = 2, @ConfigItem(
section = highlightList keyName = "borderFeather",
) name = "Border Feather",
default boolean highlightMould() { description = "The feather of the border",
return true; position = 2,
} section = highlightList
)
default int borderFeather()
{
return 0;
}
@ConfigItem( // alpha
keyName = "crucibleHighlight", @Range(
name = "Highlight Crucible", min = 0,
description = "Highlight Crucible when it should be filled/poured", max = 255
position = 3, )
section = highlightList @ConfigItem(
) keyName = "borderAlpha",
default boolean highlightCrucible() { name = "Border Alpha",
return true; description = "The alpha of the border highlight",
} position = 3,
section = highlightList
)
default int borderAlpha()
{
return 255;
}
@ConfigItem( @ConfigItem(
keyName = "kovacHighlight", keyName = "toolsHighlight",
name = "Highlight Kovac for hand in", name = "Highlight Tools",
description = "Highlight Kovac when sword can be handed in", description = "Highlights current tool with symbolic colors",
position = 4, position = 4,
section = highlightList section = highlightList
) )
default boolean highlightKovac() { default boolean highlightTools()
return true; {
} 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( @ConfigSection(
name = "Info Panel", name = "Info Overlay",
description = "Settings for the Info Panel overlay", description = "Overlay Text Info On Objects",
position = 2 position = 3
) )
String infoPanelList = "infoPanelList"; String infoOverlay = "infoOverlay";
@ConfigItem( @ConfigItem(
keyName = "infoTitle", keyName = "actionLeftOverlay",
name = "Title", name = "Actions Left Overlay",
description = "Toggle for \"Easy Giant's Foundry\" text", description = "Toggle for actions left overlay",
position = 0, position = 0,
section = infoPanelList section = infoOverlay
) )
default boolean drawTitle() { default boolean drawActionLeftOverlay()
return true; {
} return true;
}
@ConfigItem( @ConfigItem(
keyName = "heatInfo", keyName = "heatLeftOverlay",
name = "Heat", name = "Heat Left Overlay",
description = "Toggle for Heat text", description = "Toggle for heat left overlay",
position = 1, position = 1,
section = infoPanelList section = infoOverlay
) )
default boolean drawHeatInfo() { default boolean drawHeatLeftOverlay()
return true; {
} return true;
}
@ConfigItem( @ConfigItem(
keyName = "stageInfo", keyName = "crucibleInfoOverlay",
name = "Stage", name = "Crucible Info Overlay",
description = "Toggle for Stage text", description = "Toggle for crucible info overlay",
position = 2, position = 2,
section = infoPanelList section = infoOverlay
) )
default boolean drawStageInfo() { default boolean drawCrucibleInfoOverlay()
return true; {
} return true;
}
@ConfigItem( @ConfigItem(
keyName = "actionsLeft", keyName = "mouldInfoOverlay",
name = "Actions Left", name = "Mould Info Overlay",
description = "Toggle for Actions left text", description = "Toggle for mould info overlay",
position = 3, position = 3,
section = infoPanelList section = infoOverlay
) )
default boolean drawActionsLeft() { default boolean drawMouldInfoOverlay()
return true; {
} 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 = "LavaWaterInfoOverlay",
name = "Lava/Waterfall Info Overlay",
description = "Toggle for lava/waterfall info overlay",
position = 4,
section = infoOverlay
)
default boolean drawLavaWaterInfoOverlay()
{
return true;
}
@ConfigSection( @ConfigSection(
name = "Colour", name = "Colour",
description = "Colours", description = "Colours",
position = 3 position = 4
) )
String colourList = "colourList"; String colourList = "colourList";
@@ -327,4 +486,23 @@ public interface EasyGiantsFoundryConfig extends Config {
{ {
return Color.CYAN; 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;
}
} }

View File

@@ -1,83 +1,29 @@
package com.toofifty.easygiantsfoundry; package com.toofifty.easygiantsfoundry;
import com.toofifty.easygiantsfoundry.enums.Heat; import lombok.extern.slf4j.Slf4j;
import com.toofifty.easygiantsfoundry.enums.Stage; import net.runelite.client.ui.ColorScheme;
import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.awt.Color;
@Slf4j
@Singleton @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 public static Color getHeatColor(int actions, int heat)
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()
{ {
return 1000d / state.getStages().size(); if (heat >= actions)
}
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())
{ {
case POLISHING_WHEEL: return ColorScheme.PROGRESS_COMPLETE_COLOR;
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;
} }
int[] range = getCurrentHeatRange(); if (heat > 0)
int actions = 0;
int heat = state.getHeatAmount();
while (heat > range[0] && heat < range[1])
{ {
actions++; return ColorScheme.PROGRESS_INPROGRESS_COLOR;
heat += stage.getHeatChange();
} }
return actions; return ColorScheme.PROGRESS_ERROR_COLOR;
} }
} }

View File

@@ -1,10 +1,14 @@
package com.toofifty.easygiantsfoundry; package com.toofifty.easygiantsfoundry;
import com.google.inject.Provides; import com.google.inject.Provides;
import static com.toofifty.easygiantsfoundry.EasyGiantsFoundryClientIDs.VARBIT_HEAT;
import com.toofifty.easygiantsfoundry.enums.Stage; import com.toofifty.easygiantsfoundry.enums.Stage;
import javax.inject.Inject; import javax.inject.Inject;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.GameObject; import net.runelite.api.GameObject;
import net.runelite.api.GameState; 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.GameStateChanged;
import net.runelite.api.events.GameTick; import net.runelite.api.events.GameTick;
import net.runelite.api.events.ItemContainerChanged; import net.runelite.api.events.ItemContainerChanged;
import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.events.NpcDespawned; import net.runelite.api.events.NpcDespawned;
import net.runelite.api.events.NpcSpawned; import net.runelite.api.events.NpcSpawned;
import net.runelite.api.events.ScriptPostFired; import net.runelite.api.events.ScriptPostFired;
@@ -143,6 +148,7 @@ public class EasyGiantsFoundryPlugin extends Plugin
} }
} }
@Subscribe @Subscribe
public void onGameStateChanged(GameStateChanged event) public void onGameStateChanged(GameStateChanged event)
{ {
@@ -172,14 +178,14 @@ public class EasyGiantsFoundryPlugin extends Plugin
} }
if (config.showGiantsFoundryStageNotifications() && if (config.showGiantsFoundryStageNotifications() &&
helper.getActionsLeftInStage() == config.StageNotificationsThreshold() && state.getActionsLeftInStage() == config.StageNotificationsThreshold() &&
(oldStage == null || oldStage != state.getCurrentStage())) (oldStage == null || oldStage != state.getCurrentStage()))
{ {
notifier.notify("About to finish the current stage!"); notifier.notify("About to finish the current stage!");
oldStage = state.getCurrentStage(); oldStage = state.getCurrentStage();
} }
else if (config.showGiantsFoundryHeatNotifications() && else if (config.showGiantsFoundryHeatNotifications() &&
helper.getActionsForHeatLevel() == config.HeatNotificationsThreshold()) state.getActionsForHeatLevel() == config.HeatNotificationsThreshold())
{ {
notifier.notify("About to run out of heat!"); 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 " + (int) 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 @Subscribe
public void onScriptPostFired(ScriptPostFired event) 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 @Subscribe
public void onVarbitChanged(VarbitChanged event) public void onVarbitChanged(VarbitChanged event)
{ {
@@ -264,6 +315,24 @@ public class EasyGiantsFoundryPlugin extends Plugin
{ {
reputation = client.getVarpValue(REPUTATION_VARBIT); 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 @Subscribe

View File

@@ -1,7 +1,13 @@
package com.toofifty.easygiantsfoundry; 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.Heat;
import com.toofifty.easygiantsfoundry.enums.Stage; 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.Getter;
import lombok.Setter; import lombok.Setter;
import net.runelite.api.Client; import net.runelite.api.Client;
@@ -15,33 +21,6 @@ import java.util.List;
@Singleton @Singleton
public class EasyGiantsFoundryState 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 @Inject
private Client client; private Client client;
@@ -80,7 +59,7 @@ public class EasyGiantsFoundryState
return 0; return 0;
} }
heatRangeRatio = medHeat.getWidth() /(double) heatWidget.getWidth(); heatRangeRatio = medHeat.getWidth() / (double) heatWidget.getWidth();
} }
return heatRangeRatio; return heatRangeRatio;
@@ -125,13 +104,13 @@ public class EasyGiantsFoundryState
switch (child.getSpriteId()) switch (child.getSpriteId())
{ {
case SPRITE_ID_TRIP_HAMMER: case SPRITE_ID_TRIP_HAMMER:
stages.add(Stage.TRIP_HAMMER); stages.add(TRIP_HAMMER);
break; break;
case SPRITE_ID_GRINDSTONE: case SPRITE_ID_GRINDSTONE:
stages.add(Stage.GRINDSTONE); stages.add(GRINDSTONE);
break; break;
case SPRITE_ID_POLISHING_WHEEL: case SPRITE_ID_POLISHING_WHEEL:
stages.add(Stage.POLISHING_WHEEL); stages.add(POLISHING_WHEEL);
break; break;
} }
} }
@@ -204,4 +183,109 @@ public class EasyGiantsFoundryState
else else
return 0; return 0;
} }
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();
} }

View File

@@ -1,14 +1,15 @@
package com.toofifty.easygiantsfoundry; package com.toofifty.easygiantsfoundry;
import static com.toofifty.easygiantsfoundry.EasyGiantsFoundryHelper.getHeatColor;
import com.toofifty.easygiantsfoundry.enums.Heat; import com.toofifty.easygiantsfoundry.enums.Heat;
import com.toofifty.easygiantsfoundry.enums.Stage; import com.toofifty.easygiantsfoundry.enums.Stage;
import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.OverlayPosition; import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.components.LineComponent; import net.runelite.client.ui.overlay.components.LineComponent;
@@ -21,35 +22,22 @@ public class FoundryOverlay2D extends OverlayPanel
private final Client client; private final Client client;
private final EasyGiantsFoundryPlugin plugin; private final EasyGiantsFoundryPlugin plugin;
private final EasyGiantsFoundryState state; private final EasyGiantsFoundryState state;
private final EasyGiantsFoundryHelper helper;
private final EasyGiantsFoundryConfig config; private final EasyGiantsFoundryConfig config;
@Inject @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.client = client;
this.plugin = plugin; this.plugin = plugin;
this.state = state; this.state = state;
this.helper = helper;
this.config = config; this.config = config;
this.setPosition(OverlayPosition.BOTTOM_LEFT); 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 @Override
public Dimension render(Graphics2D graphics) 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()); panelComponent.getChildren().add(TitleComponent.builder().text("Easy Giant's Foundry").build());
} }
if (swordPickedUp) { if (swordPickedUp)
{
Heat heat = state.getCurrentHeat(); Heat heat = state.getCurrentHeat();
Stage stage = state.getCurrentStage(); Stage stage = state.getCurrentStage();
@@ -81,8 +70,8 @@ public class FoundryOverlay2D extends OverlayPanel
); );
} }
int actionsLeft = helper.getActionsLeftInStage(); int actionsLeft = state.getActionsLeftInStage();
int heatLeft = helper.getActionsForHeatLevel(); int heatLeft = state.getActionsForHeatLevel();
if (config.drawActionsLeft()) if (config.drawActionsLeft())
{ {
@@ -102,7 +91,7 @@ public class FoundryOverlay2D extends OverlayPanel
if (config.drawShopPoints()) if (config.drawShopPoints())
{ {
panelComponent.getChildren().add( panelComponent.getChildren().add(
LineComponent.builder().left("Reputation").right(plugin.getReputation() + "").build() LineComponent.builder().left("Reputation").right(plugin.getReputation() + "").build()
); );
} }

View File

@@ -1,229 +1,419 @@
package com.toofifty.easygiantsfoundry; 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.Heat;
import com.toofifty.easygiantsfoundry.enums.Stage; import com.toofifty.easygiantsfoundry.enums.Stage;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Shape; import java.awt.Shape;
import javax.inject.Inject; import javax.inject.Inject;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.GameObject; import net.runelite.api.GameObject;
import net.runelite.api.NPC; import net.runelite.api.NPC;
import net.runelite.api.Perspective;
import net.runelite.api.Point; import net.runelite.api.Point;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.Widget;
import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayPosition; 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 tripHammer;
GameObject grindstone; GameObject grindstone;
GameObject polishingWheel; GameObject polishingWheel;
GameObject lavaPool; GameObject lavaPool;
GameObject waterfall; GameObject waterfall;
GameObject mouldJig; GameObject mouldJig;
GameObject crucible; GameObject crucible;
NPC kovac; NPC kovac;
private final Client client; private final Client client;
private final EasyGiantsFoundryState state; private final EasyGiantsFoundryState state;
private final EasyGiantsFoundryHelper helper; private final EasyGiantsFoundryConfig config;
private final EasyGiantsFoundryConfig config;
@Inject @Inject
private FoundryOverlay3D(Client client, EasyGiantsFoundryState state, EasyGiantsFoundryHelper helper, private FoundryOverlay3D(
EasyGiantsFoundryConfig config) Client client,
{ EasyGiantsFoundryState state,
setPosition(OverlayPosition.DYNAMIC); EasyGiantsFoundryConfig config,
this.client = client; ModelOutlineRenderer modelOutlineRenderer)
this.state = state; {
this.helper = helper; setPosition(OverlayPosition.DYNAMIC);
this.config = config; this.client = client;
} this.state = state;
this.config = config;
this.modelOutlineRenderer = modelOutlineRenderer;
}
private Color getObjectColor(Stage stage, Heat heat) private Color getObjectColor(Stage stage, Heat heat)
{ {
if (stage.getHeat() != heat) if (stage.getHeat() != heat)
{ {
return config.toolBad(); return config.toolBad();
} }
if (BonusWidget.isActive(client)) if (BonusWidget.isActive(client))
{ {
return config.toolBonus(); return config.toolBonus();
} }
int actionsLeft = helper.getActionsLeftInStage(); int actionsLeft = state.getActionsLeftInStage();
int heatLeft = helper.getActionsForHeatLevel(); int heatLeft = state.getActionsForHeatLevel();
if (actionsLeft <= 1 || heatLeft <= 1) if (actionsLeft <= 1 || heatLeft <= 1)
{ {
return config.toolCaution(); return config.toolCaution();
} }
return config.toolGood(); return config.toolGood();
} }
private GameObject getStageObject(Stage stage) private GameObject getStageObject(Stage stage)
{ {
switch (stage) switch (stage)
{ {
case TRIP_HAMMER: case TRIP_HAMMER:
return tripHammer; return tripHammer;
case GRINDSTONE: case GRINDSTONE:
return grindstone; return grindstone;
case POLISHING_WHEEL: case POLISHING_WHEEL:
return polishingWheel; return polishingWheel;
} }
return null; return null;
} }
@Override @Override
public Dimension render(Graphics2D graphics) public Dimension render(Graphics2D graphics)
{ {
if (!state.isEnabled()) if (!state.isEnabled())
{ {
return null; return null;
} }
if (config.highlightKovac()) if (config.highlightKovac())
{ {
drawKovacIfHandIn(graphics); drawKovacIfHandIn(graphics);
} }
if (state.getCurrentStage() == null) if (state.getCurrentStage() == null)
{ {
if (config.highlightMould()) if (config.highlightMould())
{ {
drawMouldIfNotSet(graphics); drawMouldIfNotSet(graphics);
} }
if (config.highlightCrucible()) if (config.highlightCrucible())
{ {
drawCrucibleIfMouldSet(graphics); drawCrucibleIfMouldSet(graphics);
} }
return null; return null;
} }
Stage stage = state.getCurrentStage(); Stage stage = state.getCurrentStage();
GameObject stageObject = getStageObject(stage); GameObject stageObject = getStageObject(stage);
if (stageObject == null) if (stageObject == null)
{ {
return null; return null;
} }
Heat heat = state.getCurrentHeat(); drawHeatingActionOverlay(graphics, stageObject);
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);
}
if (stage.getHeat() != heat && config.highlightWaterAndLava()) Heat heat = state.getCurrentHeat();
{ Color color = getObjectColor(stage, heat);
drawHeatChangers(graphics); // 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) if (state.heatingCoolingState.isCooling())
{ {
int change = state.getHeatChangeNeeded(); drawHeatingActionOverlay(graphics, waterfall, false);
Shape shape = null; }
if (change < 0) if (state.heatingCoolingState.isHeating())
{ {
shape = waterfall.getClickbox(); drawHeatingActionOverlay(graphics, lavaPool, true);
} 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);
}
}
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) return null;
{ }
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);
}
}
private void drawKovacIfHandIn(Graphics2D graphics) private void drawObjectClickbox(Graphics2D graphics, GameObject stageObject, Color color)
{ {
Widget handInWidget = client.getWidget(HAND_IN_WIDGET); Shape objectClickbox = stageObject.getClickbox();
if (handInWidget != null && !handInWidget.isHidden()) if (objectClickbox != null && config.highlightTools())
{ {
Shape shape = kovac.getConvexHull(); Point mousePosition = client.getMouseCanvasPosition();
if (shape != null) if (objectClickbox.contains(mousePosition.getX(), mousePosition.getY()))
{ {
Color color = config.generalHighlight(); graphics.setColor(color.darker());
graphics.setColor(color); }
graphics.draw(shape); else
graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20)); {
graphics.fill(shape); 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: %d", state.getCrucibleCount(), CRUCIBLE_CAPACITY, (int)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));
}
}
} }

View File

@@ -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.
* <p>
* 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).
* <p>
* 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
* <p>
* 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<Integer> generateDx2List(int count)
// {
// List<Integer> 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;
// }
}

View File

@@ -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.
* <p>
* 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);
}
}

View File

@@ -0,0 +1,7 @@
package com.toofifty.easygiantsfoundry;
public enum HighlightStyle
{
HIGHLIGHT_BORDER,
HIGHLIGHT_CLICKBOX
}

View File

@@ -0,0 +1,9 @@
package com.toofifty.easygiantsfoundry;
public class MathUtil
{
public static double max1(double a)
{
return a > 0 ? a : 1;
}
}

View File

@@ -2,9 +2,11 @@ package com.toofifty.easygiantsfoundry;
import com.toofifty.easygiantsfoundry.enums.CommissionType; import com.toofifty.easygiantsfoundry.enums.CommissionType;
import com.toofifty.easygiantsfoundry.enums.Mould; import com.toofifty.easygiantsfoundry.enums.Mould;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.ScriptID; import net.runelite.api.ScriptID;
import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.Widget;
@@ -12,83 +14,89 @@ import net.runelite.client.callback.ClientThread;
public class MouldHelper public class MouldHelper
{ {
static final int MOULD_LIST_PARENT = 47054857; static final int MOULD_LIST_PARENT = 47054857;
static final int DRAW_MOULD_LIST_SCRIPT = 6093; static final int DRAW_MOULD_LIST_SCRIPT = 6093;
static final int REDRAW_MOULD_LIST_SCRIPT = 6095; static final int REDRAW_MOULD_LIST_SCRIPT = 6095;
static final int RESET_MOULD_SCRIPT = 6108; static final int RESET_MOULD_SCRIPT = 6108;
public static final int SELECT_MOULD_SCRIPT = 6098; public static final int SELECT_MOULD_SCRIPT = 6098;
static final int SWORD_TYPE_1_VARBIT = 13907; // 4=Broad static final int SWORD_TYPE_1_VARBIT = 13907; // 4=Broad
static final int SWORD_TYPE_2_VARBIT = 13908; // 3=Flat static final int SWORD_TYPE_2_VARBIT = 13908; // 3=Flat
private static final int DISABLED_TEXT_COLOR = 0x9f9f9f; private static final int DISABLED_TEXT_COLOR = 0x9f9f9f;
private static final int GREEN = 0xdc10d;
@Inject @Inject
private Client client; private Client client;
@Inject @Inject
private ClientThread clientThread; private ClientThread clientThread;
@Inject @Inject
private EasyGiantsFoundryConfig config; private EasyGiantsFoundryConfig config;
public void selectBest(int scriptId) public void selectBest(int scriptId)
{ {
Widget parent = client.getWidget(MOULD_LIST_PARENT); Widget parent = client.getWidget(MOULD_LIST_PARENT);
if (parent == null || parent.getChildren() == null) if (parent == null || parent.getChildren() == null)
{ {
return; return;
} }
Map<Mould, Widget> mouldToChild = getOptions(parent.getChildren()); Map<Mould, Widget> mouldToChild = getOptions(parent.getChildren());
int bestScore = -1; int bestScore = -1;
Widget bestWidget = null; Widget bestWidget = null;
CommissionType type1 = CommissionType.forVarbit(client.getVarbitValue(SWORD_TYPE_1_VARBIT)); CommissionType type1 = CommissionType.forVarbit(client.getVarbitValue(SWORD_TYPE_1_VARBIT));
CommissionType type2 = CommissionType.forVarbit(client.getVarbitValue(SWORD_TYPE_2_VARBIT)); CommissionType type2 = CommissionType.forVarbit(client.getVarbitValue(SWORD_TYPE_2_VARBIT));
for (Map.Entry<Mould, Widget> entry : mouldToChild.entrySet()) { for (Map.Entry<Mould, Widget> entry : mouldToChild.entrySet())
Mould mould = entry.getKey(); {
int score = mould.getScore(type1, type2); Mould mould = entry.getKey();
if (score > bestScore) { int score = mould.getScore(type1, type2);
bestScore = score; if (score > bestScore)
bestWidget = entry.getValue(); {
} bestScore = score;
} bestWidget = entry.getValue();
if (bestWidget != null) { }
bestWidget.setTextColor(config.mouldTextColour().getRGB()); }
} if (bestWidget != null)
{
bestWidget.setTextColor(config.mouldTextColour().getRGB());
}
if (scriptId == DRAW_MOULD_LIST_SCRIPT || scriptId == REDRAW_MOULD_LIST_SCRIPT) if (scriptId == DRAW_MOULD_LIST_SCRIPT || scriptId == REDRAW_MOULD_LIST_SCRIPT)
{ {
Widget scrollBar = client.getWidget(718, 11); Widget scrollBar = client.getWidget(718, 11);
Widget scrollList = client.getWidget(718, 9); Widget scrollList = client.getWidget(718, 9);
if (scrollBar != null && scrollList != null) if (scrollBar != null && scrollList != null)
{ {
int height = scrollList.getHeight(); int height = scrollList.getHeight();
int scrollMax = scrollList.getScrollHeight(); int scrollMax = scrollList.getScrollHeight();
Widget finalBestWidget = bestWidget; Widget finalBestWidget = bestWidget;
clientThread.invokeLater(() -> { clientThread.invokeLater(() ->
if (finalBestWidget != null) { {
client.runScript( if (finalBestWidget != null)
ScriptID.UPDATE_SCROLLBAR, {
scrollBar.getId(), client.runScript(
scrollList.getId(), ScriptID.UPDATE_SCROLLBAR,
Math.min(finalBestWidget.getOriginalY() - 2, scrollMax - height)); scrollBar.getId(),
} scrollList.getId(),
}); Math.min(finalBestWidget.getOriginalY() - 2, scrollMax - height));
} }
} });
} }
}
}
private Map<Mould, Widget> getOptions(Widget[] children) { private Map<Mould, Widget> getOptions(Widget[] children)
Map<Mould, Widget> mouldToChild = new LinkedHashMap<>(); {
for (int i = 2; i < children.length; i += 17) Map<Mould, Widget> mouldToChild = new LinkedHashMap<>();
{ for (int i = 2; i < children.length; i += 17)
Widget child = children[i]; {
Mould mould = Mould.forName(child.getText()); Widget child = children[i];
if (mould != null && child.getTextColor() != DISABLED_TEXT_COLOR) { Mould mould = Mould.forName(child.getText());
mouldToChild.put(mould, child); if (mould != null && child.getTextColor() != DISABLED_TEXT_COLOR)
} {
} mouldToChild.put(mould, child);
return mouldToChild; }
} }
return mouldToChild;
}
} }

View File

@@ -1,21 +1,24 @@
package com.toofifty.easygiantsfoundry.enums; package com.toofifty.easygiantsfoundry.enums;
public enum CommissionType { public enum CommissionType
NONE, {
NARROW, // 1 NONE,
LIGHT, // 2 NARROW, // 1
FLAT, // 3 LIGHT, // 2
BROAD, // 4 FLAT, // 3
HEAVY, // 5 BROAD, // 4
SPIKED, // 6 HEAVY, // 5
; SPIKED, // 6
;
public static final CommissionType[] values = CommissionType.values(); public static final CommissionType[] values = CommissionType.values();
public static CommissionType forVarbit(int varbitValue) { public static CommissionType forVarbit(int varbitValue)
if (varbitValue < 0 || varbitValue >= values.length) { {
return NONE; if (varbitValue < 0 || varbitValue >= values.length)
} {
return CommissionType.values[varbitValue]; return NONE;
} }
return CommissionType.values[varbitValue];
}
} }

View File

@@ -1,6 +1,7 @@
package com.toofifty.easygiantsfoundry.enums; package com.toofifty.easygiantsfoundry.enums;
import java.awt.Color; import java.awt.Color;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.ColorScheme;

View File

@@ -9,61 +9,66 @@ import static com.toofifty.easygiantsfoundry.enums.CommissionType.*;
import static com.toofifty.easygiantsfoundry.enums.MouldType.*; import static com.toofifty.easygiantsfoundry.enums.MouldType.*;
@AllArgsConstructor @AllArgsConstructor
public enum Mould { 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)), CHOPPER_FORTE("Chopper Forte", FORTE, ImmutableMap.of(BROAD, 4, LIGHT, 4, FLAT, 4)),
DISARMING_FORTE("Disarming Forte", FORTE, ImmutableMap.of(NARROW, 4, LIGHT, 4, SPIKED, 4)), GALDIUS_RICASSO("Galdius Ricasso", FORTE, ImmutableMap.of(BROAD, 4, HEAVY, 4, FLAT, 4)),
MEDUSA_RICASSO("Medusa Ricasso", FORTE, ImmutableMap.of(BROAD, 8, HEAVY, 6, FLAT, 8)), DISARMING_FORTE("Disarming Forte", FORTE, ImmutableMap.of(NARROW, 4, LIGHT, 4, SPIKED, 4)),
SERPENT_RICASSO("Serpent Ricasso", FORTE, ImmutableMap.of(NARROW, 6, LIGHT, 8, FLAT, 8)), MEDUSA_RICASSO("Medusa Ricasso", FORTE, ImmutableMap.of(BROAD, 8, HEAVY, 6, FLAT, 8)),
SERRATED_FORTE("Serrated Forte", FORTE, ImmutableMap.of(NARROW, 8, HEAVY, 8, SPIKED, 6)), SERPENT_RICASSO("Serpent Ricasso", FORTE, ImmutableMap.of(NARROW, 6, LIGHT, 8, FLAT, 8)),
STILETTO_FORTE("Stiletto Forte", FORTE, ImmutableMap.of(NARROW, 8, LIGHT, 10, FLAT, 8)), SERRATED_FORTE("Serrated Forte", FORTE, ImmutableMap.of(NARROW, 8, HEAVY, 8, SPIKED, 6)),
DEFENDER_BASE("Defender Base", FORTE, ImmutableMap.of(BROAD, 8, HEAVY, 10, FLAT, 8)), STILETTO_FORTE("Stiletto Forte", FORTE, ImmutableMap.of(NARROW, 8, LIGHT, 10, FLAT, 8)),
JUGGERNAUT_FORTE("Juggernaut Forte", FORTE, ImmutableMap.of(BROAD, 4, HEAVY, 4, SPIKED, 16)), DEFENDER_BASE("Defender Base", FORTE, ImmutableMap.of(BROAD, 8, HEAVY, 10, FLAT, 8)),
CHOPPER_FORTE_1("Chopper Forte +1", FORTE, ImmutableMap.of(BROAD, 3, LIGHT, 4, FLAT, 18)), JUGGERNAUT_FORTE("Juggernaut Forte", FORTE, ImmutableMap.of(BROAD, 4, HEAVY, 4, SPIKED, 16)),
SPIKER("Spiker!", FORTE, ImmutableMap.of(NARROW, 1, HEAVY, 2, SPIKED, 22)), CHOPPER_FORTE_1("Chopper Forte +1", FORTE, ImmutableMap.of(BROAD, 3, LIGHT, 4, FLAT, 18)),
SAW_BLADE("Saw Blade", BLADE, ImmutableMap.of(BROAD, 4, LIGHT, 4, SPIKED, 4)), SPIKER("Spiker!", FORTE, ImmutableMap.of(NARROW, 1, HEAVY, 2, SPIKED, 22)),
DEFENDERS_EDGE("Defenders Edge", BLADE, ImmutableMap.of(BROAD, 4, HEAVY, 4, SPIKED, 4)), SAW_BLADE("Saw Blade", BLADE, ImmutableMap.of(BROAD, 4, LIGHT, 4, SPIKED, 4)),
FISH_BLADE("Fish Blade", BLADE, ImmutableMap.of(NARROW, 4, LIGHT, 4, FLAT, 4)), DEFENDERS_EDGE("Defenders Edge", BLADE, ImmutableMap.of(BROAD, 4, HEAVY, 4, SPIKED, 4)),
MEDUSA_BLADE("Medusa Blade", BLADE, ImmutableMap.of(BROAD, 8, HEAVY, 8, FLAT, 6)), FISH_BLADE("Fish Blade", BLADE, ImmutableMap.of(NARROW, 4, LIGHT, 4, FLAT, 4)),
STILETTO_BLADE("Stiletto Blade", BLADE, ImmutableMap.of(NARROW, 8, LIGHT, 6, FLAT, 8)), MEDUSA_BLADE("Medusa Blade", BLADE, ImmutableMap.of(BROAD, 8, HEAVY, 8, FLAT, 6)),
GLADIUS_EDGE("Gladius Edge", BLADE, ImmutableMap.of(NARROW, 6, HEAVY, 8, FLAT, 8)), STILETTO_BLADE("Stiletto Blade", BLADE, ImmutableMap.of(NARROW, 8, LIGHT, 6, FLAT, 8)),
FLAMBERGE_BLADE("Flamberge Blade", BLADE, ImmutableMap.of(NARROW, 8, LIGHT, 8, SPIKED, 10)), GLADIUS_EDGE("Gladius Edge", BLADE, ImmutableMap.of(NARROW, 6, HEAVY, 8, FLAT, 8)),
SERPENT_BLADE("Serpent Blade", BLADE, ImmutableMap.of(NARROW, 10, LIGHT, 8, FLAT, 8)), FLAMBERGE_BLADE("Flamberge Blade", BLADE, ImmutableMap.of(NARROW, 8, LIGHT, 8, SPIKED, 10)),
CLAYMORE_BLADE("Claymore Blade", BLADE, ImmutableMap.of(BROAD, 16, HEAVY, 4, FLAT, 4)), SERPENT_BLADE("Serpent Blade", BLADE, ImmutableMap.of(NARROW, 10, LIGHT, 8, FLAT, 8)),
FLEUR_DE_BLADE("Fleur de Blade", BLADE, ImmutableMap.of(BROAD, 4, HEAVY, 18, SPIKED, 1)), CLAYMORE_BLADE("Claymore Blade", BLADE, ImmutableMap.of(BROAD, 16, HEAVY, 4, FLAT, 4)),
CHOPPA("Choppa!", BLADE, ImmutableMap.of(BROAD, 1, LIGHT, 22, FLAT, 2)), FLEUR_DE_BLADE("Fleur de Blade", BLADE, ImmutableMap.of(BROAD, 4, HEAVY, 18, SPIKED, 1)),
PEOPLE_POKER_POINT("People Poker Point", TIP, ImmutableMap.of(NARROW, 4, HEAVY, 4, FLAT, 4)), CHOPPA("Choppa!", BLADE, ImmutableMap.of(BROAD, 1, LIGHT, 22, FLAT, 2)),
CHOPPER_TIP("Chopper Tip", TIP, ImmutableMap.of(BROAD, 4, LIGHT, 4, SPIKED, 4)), PEOPLE_POKER_POINT("People Poker Point", TIP, ImmutableMap.of(NARROW, 4, HEAVY, 4, FLAT, 4)),
MEDUSAS_HEAD("Medusa's Head", TIP, ImmutableMap.of(BROAD, 4, HEAVY, 4, SPIKED, 4)), CHOPPER_TIP("Chopper Tip", TIP, ImmutableMap.of(BROAD, 4, LIGHT, 4, SPIKED, 4)),
SERPENTS_FANG("Serpent's Fang", TIP, ImmutableMap.of(NARROW, 8, LIGHT, 6, SPIKED, 8)), MEDUSAS_HEAD("Medusa's Head", TIP, ImmutableMap.of(BROAD, 4, HEAVY, 4, SPIKED, 4)),
GLADIUS_POINT("Gladius Point", TIP, ImmutableMap.of(NARROW, 8, HEAVY, 8, FLAT, 6)), SERPENTS_FANG("Serpent's Fang", TIP, ImmutableMap.of(NARROW, 8, LIGHT, 6, SPIKED, 8)),
SAW_TIP("Saw Tip", TIP, ImmutableMap.of(BROAD, 6, HEAVY, 8, SPIKED, 8)), GLADIUS_POINT("Gladius Point", TIP, ImmutableMap.of(NARROW, 8, HEAVY, 8, FLAT, 6)),
CORRUPTED_POINT("Corrupted Point", TIP, ImmutableMap.of(NARROW, 8, LIGHT, 10, SPIKED, 8)), SAW_TIP("Saw Tip", TIP, ImmutableMap.of(BROAD, 6, HEAVY, 8, SPIKED, 8)),
DEFENDERS_TIP("Defenders Tip", TIP, ImmutableMap.of(BROAD, 10, HEAVY, 8, SPIKED, 8)), CORRUPTED_POINT("Corrupted Point", TIP, ImmutableMap.of(NARROW, 8, LIGHT, 10, SPIKED, 8)),
SERRATED_TIP("Serrated Tip", TIP, ImmutableMap.of(NARROW, 4, LIGHT, 16, SPIKED, 4)), DEFENDERS_TIP("Defenders Tip", TIP, ImmutableMap.of(BROAD, 10, HEAVY, 8, SPIKED, 8)),
NEEDLE_POINT("Needle Point", TIP, ImmutableMap.of(NARROW, 18, LIGHT, 3, FLAT, 4)), SERRATED_TIP("Serrated Tip", TIP, ImmutableMap.of(NARROW, 4, LIGHT, 16, SPIKED, 4)),
THE_POINT("The Point!", TIP, ImmutableMap.of(BROAD, 2, LIGHT, 1, FLAT, 22)), 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 String name;
private final MouldType mouldType; private final MouldType mouldType;
private final Map<CommissionType, Integer> typeToScore; private final Map<CommissionType, Integer> typeToScore;
public static final Mould[] values = Mould.values(); public static final Mould[] values = Mould.values();
public static Mould forName(String text) { public static Mould forName(String text)
for (Mould mould : values) { {
if (mould.name.equalsIgnoreCase(text)) { for (Mould mould : values)
return mould; {
} if (mould.name.equalsIgnoreCase(text))
} {
return null; return mould;
} }
}
return null;
}
public int getScore(CommissionType type1, CommissionType type2) { public int getScore(CommissionType type1, CommissionType type2)
int score = 0; {
score += typeToScore.getOrDefault(type1, 0); int score = 0;
score += typeToScore.getOrDefault(type2, 0); score += typeToScore.getOrDefault(type1, 0);
return score; score += typeToScore.getOrDefault(type2, 0);
} return score;
}
} }

View File

@@ -1,7 +1,8 @@
package com.toofifty.easygiantsfoundry.enums; package com.toofifty.easygiantsfoundry.enums;
public enum MouldType { public enum MouldType
FORTE, {
BLADE, FORTE,
TIP, BLADE,
TIP,
} }

View File

@@ -15,4 +15,14 @@ public enum Stage
private final Heat heat; private final Heat heat;
private final int progressPerAction; private final int progressPerAction;
private final int heatChange; private final int heatChange;
public boolean isHeating()
{
return heatChange > 0;
}
public boolean isCooling()
{
return heatChange < 0;
}
} }

View File

@@ -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<Integer> 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<Integer> 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<Integer> 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<Integer> dx2 = new ArrayList<>();
for (int i = 0; i < dx1.length - 1; i++)
{
dx2.add(dx1[i+1] - dx1[i]);
}
System.err.println(dx2);
}
}