Crucible Overlay, Tools action overlay, Lava/waterfall prediction

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
This commit is contained in:
Louis Hong
2024-02-22 20:42:22 -08:00
parent b08dab0192
commit 23752b2ef6
29 changed files with 1979 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
- 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
:-------------------------:|:-------------------------:
<img width=350px src="./readme_gifs/best-mould.webp">|<img width=350px src="./readme_gifs/crucible-value.png">
Heating/Cooling Prediction | Low/High Heat Warning
:-------------------------:|:-------------------------:
<img width=350px src="./readme_gifs/lava-waterfall-estimate.webp">|<img width=350px src="./readme_gifs/tool-damage-warning.webp">
Bonus Click Notification | Information Panel
:-------------------------:|:-------------------------:
<img width=350px src="./readme_gifs/tools-bonus-notification.webp">|<img width=350px src="./readme_gifs/info-panel.webp">
## 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
* 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.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;
}
}

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

View File

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

View File

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

View File

@@ -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("<br>");
// 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();
}

View File

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

View File

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

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.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<Mould, Widget> mouldToChild = getOptions(parent.getChildren());
Map<Mould, Widget> 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<Mould, Widget> 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<Mould, Widget> 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<Mould, Widget> getOptions(Widget[] children) {
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());
if (mould != null && child.getTextColor() != DISABLED_TEXT_COLOR) {
mouldToChild.put(mould, child);
}
}
return mouldToChild;
}
private Map<Mould, Widget> getOptions(Widget[] children)
{
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());
if (mould != null && child.getTextColor() != DISABLED_TEXT_COLOR)
{
mouldToChild.put(mould, child);
}
}
return mouldToChild;
}
}

View File

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

View File

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

View File

@@ -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<CommissionType, Integer> typeToScore;
private final String name;
private final MouldType mouldType;
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) {
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;
}
}

View File

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

View File

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

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