package ee.futur.easygiantsfoundry; import ee.futur.easygiantsfoundry.enums.Heat; import ee.futur.easygiantsfoundry.enums.MetalBarType; import ee.futur.easygiantsfoundry.enums.Stage; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.util.ArrayList; import java.util.List; 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; import net.runelite.client.ui.overlay.components.TitleComponent; @Singleton public class FoundryOverlay2D extends OverlayPanel { private static final int REGION_ID = 13491; private static final int PANEL_WIDTH = 240; private final Client client; private final EasyGiantsFoundryPlugin plugin; private final EasyGiantsFoundryState state; private final MetalBarCounter metalBarCounter; private final EasyGiantsFoundryConfig config; @Inject private FoundryOverlay2D( Client client, EasyGiantsFoundryPlugin plugin, EasyGiantsFoundryState state, MetalBarCounter metalBarCounter, EasyGiantsFoundryConfig config) { this.client = client; this.plugin = plugin; this.state = state; this.metalBarCounter = metalBarCounter; this.config = config; setPosition(OverlayPosition.BOTTOM_LEFT); } @Override public Dimension render(Graphics2D graphics) { if (!config.alwaysDrawInfoPanel() && client.getLocalPlayer().getWorldLocation().getRegionID() != REGION_ID) { return null; } panelComponent.getChildren().clear(); panelComponent.setPreferredSize(new Dimension(PANEL_WIDTH, 0)); boolean hasContent = false; if (config.drawTitle()) { panelComponent.getChildren().add(TitleComponent.builder().text("Easy Giants' Foundry").build()); hasContent = true; } boolean swordPickedUp = state.isEnabled() && state.getCurrentStage() != null; if (swordPickedUp) { hasContent = appendSection(buildStatusLines(), hasContent); hasContent = appendSection(buildProgressLines(), hasContent); hasContent = appendSection(buildHeatPlannerLines(), hasContent); } hasContent = appendSection(buildReputationLines(), hasContent); hasContent = appendSection(buildMetalLines(), hasContent); if (!hasContent) { return null; } return super.render(graphics); } private boolean appendSection(List lines, boolean hasPreviousContent) { if (lines.isEmpty()) { return hasPreviousContent; } if (hasPreviousContent) { panelComponent.getChildren().add(LineComponent.builder().left(" ").right("").build()); } panelComponent.getChildren().addAll(lines); return true; } private List buildStatusLines() { List lines = new ArrayList<>(); Heat heat = state.getCurrentHeat(); Stage stage = state.getCurrentStage(); if (config.drawHeatInfo()) { lines.add( LineComponent.builder() .left("Heat") .right(heat.getName() + " (" + state.getHeatAmount() / 10 + "%)") .rightColor(heat.getColor()) .build() ); } if (config.drawStageInfo()) { lines.add( LineComponent.builder() .left("Stage") .right(stage.getName() + " (" + state.getProgressAmount() / 10 + "%)") .rightColor(stage.getHeat().getColor()) .build() ); } return lines; } private List buildProgressLines() { List lines = new ArrayList<>(); int actionsLeft = state.getActionsLeftInStage(); int heatLeft = state.getActionsForHeatLevel(); if (config.drawActionsLeft()) { lines.add(LineComponent.builder().left("Actions left").right(Integer.toString(actionsLeft)).build()); } if (config.drawHeatLeft()) { lines.add( LineComponent.builder() .left("Heat left") .right(Integer.toString(heatLeft)) .rightColor(getHeatColor(actionsLeft, heatLeft)) .build() ); } if (config.drawBonusActions()) { lines.add( LineComponent.builder() .left("Bonus actions") .right(state.getBonusActionsReceived() + "/" + state.getBonusActionsExpected()) .rightColor(getBonusActionsColor(state.getBonusActionsReceived(), state.getBonusActionsExpected())) .build() ); } return lines; } private List buildHeatPlannerLines() { List lines = new ArrayList<>(); if (!config.drawHeatDelta() && !config.drawHeatPlan() && !config.drawHeatActionTimer()) { return lines; } if (state.getCurrentStage() == null) { return lines; } int heatChangeNeeded = state.getHeatChangeNeeded(); if (config.drawHeatDelta()) { lines.add(buildHeatDeltaLine(heatChangeNeeded)); } if (config.drawHeatPlan()) { lines.addAll(buildHeatPlanLines(heatChangeNeeded)); } if (config.drawHeatActionTimer()) { LineComponent timerLine = buildHeatActionTimerLine(); if (timerLine != null) { lines.add(timerLine); } } return lines; } private LineComponent buildHeatDeltaLine(int heatChangeNeeded) { String location; if (heatChangeNeeded > 0) { location = "Lava"; } else if (heatChangeNeeded < 0) { location = "Waterfall"; } else { location = "In range"; } String deltaText = heatChangeNeeded == 0 ? "0 (in range)" : String.format("%+d (%s)", heatChangeNeeded, location); Color color = heatChangeNeeded == 0 ? ColorScheme.PROGRESS_COMPLETE_COLOR : config.lavaWaterfallColour(); return LineComponent.builder() .left("Heat Δ") .right(deltaText) .rightColor(color) .build(); } private List buildHeatPlanLines(int heatChangeNeeded) { List lines = new ArrayList<>(); if (heatChangeNeeded == 0) { lines.add( LineComponent.builder() .left("Heat plan") .right("Hold") .rightColor(ColorScheme.PROGRESS_COMPLETE_COLOR) .build() ); return lines; } boolean isHeating = heatChangeNeeded > 0; HeatActionSolver.DurationResult fast = solveHeatAction(true, isHeating); HeatActionSolver.DurationResult slow = solveHeatAction(false, isHeating); boolean added = false; if (fast != null && fast.getDuration() > 0) { lines.add(buildHeatPlanLine("Fast", isHeating, true, fast)); added = true; } if (slow != null && slow.getDuration() > 0) { lines.add(buildHeatPlanLine("Slow", isHeating, false, slow)); added = true; } if (!added) { lines.add( LineComponent.builder() .left("Heat plan") .right("Hold") .rightColor(ColorScheme.PROGRESS_COMPLETE_COLOR) .build() ); } return lines; } private LineComponent buildHeatPlanLine(String label, boolean isHeating, boolean fast, HeatActionSolver.DurationResult result) { String actionName = getActionName(isHeating, fast); String value = String.format("%s %dt → %s%s", actionName, result.getDuration(), formatPredictedHeat(result.getPredictedHeat()), formatPlanStatus(result)); return LineComponent.builder() .left(label) .right(value) .rightColor(config.lavaWaterfallColour()) .build(); } private HeatActionSolver.DurationResult solveHeatAction(boolean fast, boolean isHeating) { Stage stage = state.getCurrentStage(); if (stage == null) { return null; } return HeatActionSolver.solve( stage, state.getCurrentHeatRange(), state.getActionsLeftInStage(), state.getHeatAmount(), fast, isHeating, config.heatActionPadTicks(), state.isPlayerRunning() ); } private LineComponent buildHeatActionTimerLine() { HeatActionStateMachine machine = state.heatActionStateMachine; if (machine.isIdle() || machine.getActionname() == null) { return null; } return LineComponent.builder() .left("Action") .right(String.format( "%s %dt → %s%s", prettifyActionName(machine.getActionname()), machine.getRemainingDuration(), formatPredictedHeat(machine.getPredictedHeat()), machine.isGoalInRange() ? "" : machine.isOverShooting() ? " (overshoot)" : " (limit)")) .rightColor(config.lavaWaterfallColour()) .build(); } private String formatPredictedHeat(int predictedHeat) { return heatPercent(predictedHeat) + "%"; } private String formatPlanStatus(HeatActionSolver.DurationResult result) { if (result == null || result.isGoalInRange()) { return ""; } return result.isOvershooting() ? " (overshoot)" : " (limit)"; } private String getActionName(boolean isHeating, boolean fast) { if (isHeating) { return fast ? "Dunk" : "Heat"; } return fast ? "Quench" : "Cool"; } private String prettifyActionName(String action) { if (action == null || action.isEmpty()) { return ""; } switch (action) { case "dunks": return "Dunk"; case "heats": return "Heat"; case "cools": return "Cool"; case "quenches": return "Quench"; default: return Character.toUpperCase(action.charAt(0)) + action.substring(1); } } private int heatPercent(int heat) { return Math.max(0, Math.min(1000, heat)) / 10; } private List buildReputationLines() { List lines = new ArrayList<>(); if (!config.drawShopPoints()) { return lines; } lines.add(LineComponent.builder().left("Reputation").right(Integer.toString(plugin.getReputation())).build()); return lines; } private List buildMetalLines() { List lines = new ArrayList<>(); if (!config.drawMetals()) { return lines; } if (!metalBarCounter.isSeenBank()) { lines.add( LineComponent.builder() .left("Metals: open bank") .leftColor(Color.RED) .build() ); } addMetalCount(lines, "Bronze bars:", metalBarCounter.get(MetalBarType.BRONZE)); addMetalCount(lines, "Iron bars:", metalBarCounter.get(MetalBarType.IRON)); addMetalCount(lines, "Steel bars:", metalBarCounter.get(MetalBarType.STEEL)); addMetalCount(lines, "Mithril bars:", metalBarCounter.get(MetalBarType.MITHRIL)); addMetalCount(lines, "Adamant bars:", metalBarCounter.get(MetalBarType.ADAMANT)); addMetalCount(lines, "Runite bars:", metalBarCounter.get(MetalBarType.RUNITE)); return lines; } private void addMetalCount(List lines, String displayName, int count) { if (count > 0 || config.drawAllMetals()) { lines.add(LineComponent.builder().left(displayName).right(Integer.toString(count)).build()); } } 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; } private Color getBonusActionsColor(int received, int expected) { if (received >= expected) { return ColorScheme.PROGRESS_COMPLETE_COLOR; } return ColorScheme.PROGRESS_INPROGRESS_COLOR; } }