Skip to content

Commit

Permalink
Add goal tracking infobox
Browse files Browse the repository at this point in the history
This adds a config section where you can choose an
item to track your progress towards and shows
an infobox with overall progress and component progress
  • Loading branch information
BlueSoapTurtle committed Oct 31, 2024
1 parent d8d59f9 commit fcc9bb7
Show file tree
Hide file tree
Showing 5 changed files with 320 additions and 11 deletions.
200 changes: 200 additions & 0 deletions src/main/java/work/fking/masteringmixology/GoalInfoBoxOverlay.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package work.fking.masteringmixology;

import net.runelite.api.Client;
import net.runelite.client.game.ItemManager;
import net.runelite.client.game.SpriteManager;
import net.runelite.client.ui.FontManager;
import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.components.ComponentOrientation;
import net.runelite.client.ui.overlay.components.ImageComponent;
import net.runelite.client.ui.overlay.components.LineComponent;
import net.runelite.client.ui.overlay.components.PanelComponent;
import net.runelite.client.ui.overlay.components.ProgressBarComponent;
import net.runelite.client.ui.overlay.components.SplitComponent;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.QuantityFormatter;

import javax.inject.Inject;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;

class GoalInfoBoxOverlay extends OverlayPanel {
private static final int BORDER_SIZE = 3;
private static final int VERTICAL_GAP = 2;
private static final int ICON_AND_GOAL_GAP = 5;
private static final Rectangle TOP_PANEL_BORDER = new Rectangle(2, 0, 4, 4);
private static final int COMPONENT_SPRITE_SIZE = 16;

private final Client client;
private final MasteringMixologyPlugin plugin;
private final MasteringMixologyConfig config;
private final ItemManager itemManager;
private final SpriteManager spriteManager;

private final PanelComponent topPanel = new PanelComponent();

private final EnumMap<PotionComponent, ComponentData> componentDataMap = new EnumMap<>(PotionComponent.class);
private double overallProgress = 0.0;
private RewardItem rewardItem;

private final Map<Integer, BufferedImage> rewardIconCache = new HashMap<>();
private final EnumMap<PotionComponent, BufferedImage> componentSpriteCache = new EnumMap<>(PotionComponent.class);

private boolean dataDirty = true;

@Inject
GoalInfoBoxOverlay(Client client, MasteringMixologyPlugin plugin, MasteringMixologyConfig config,
ItemManager itemManager, SpriteManager spriteManager) {
super(plugin);
this.client = client;
this.plugin = plugin;
this.config = config;
this.itemManager = itemManager;
this.spriteManager = spriteManager;

panelComponent.setBorder(new Rectangle(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE));
panelComponent.setGap(new Point(0, VERTICAL_GAP));
topPanel.setBorder(TOP_PANEL_BORDER);
topPanel.setBackgroundColor(null);
}

@Override
public Dimension render(Graphics2D graphics) {
// Recalculate the data before checking if the overlay should be displayed
if (dataDirty) {
recalculateData();
}

if (rewardItem == RewardItem.NONE || !plugin.isInLabRegion()) {
return null;
}

// Clear the panel components
topPanel.getChildren().clear();

// Set the font for the panel
graphics.setFont(FontManager.getRunescapeSmallFont());

// Build the top display with the reward item and overall progress
final LineComponent topLine = LineComponent.builder()
.left(rewardItem.itemName())
.leftFont(FontManager.getRunescapeFont())
.build();

final LineComponent bottomLine = LineComponent.builder()
.left("Progress:")
.leftFont(FontManager.getRunescapeFont())
.right((int) (overallProgress * 100) + "%")
.rightFont(FontManager.getRunescapeFont())
.rightColor(overallProgress >= 1 ? Color.GREEN : Color.WHITE)
.build();

final SplitComponent textSplit = SplitComponent.builder()
.first(topLine)
.second(bottomLine)
.orientation(ComponentOrientation.VERTICAL)
.build();

final ImageComponent rewardImageComponent = new ImageComponent(getRewardImage());
final SplitComponent topInfoSplit = SplitComponent.builder()
.first(rewardImageComponent)
.second(textSplit)
.orientation(ComponentOrientation.HORIZONTAL)
.gap(new Point(ICON_AND_GOAL_GAP, 0))
.build();

topPanel.getChildren().add(topInfoSplit);
panelComponent.getChildren().add(topPanel);

if (config.showResinProgressBars()) {
// Add the progress bars in order: MOX, AGA, LYE, PotionComponent order is not the same
createProgressBar(PotionComponent.MOX);
createProgressBar(PotionComponent.AGA);
createProgressBar(PotionComponent.LYE);
}

return super.render(graphics);
}

public void markDataAsDirty() {
this.dataDirty = true;
}

private void recalculateData() {
rewardItem = config.selectedReward();

double totalPercentage = 0.0;
totalPercentage += getAndCalculateComponentData(PotionComponent.MOX, MasteringMixologyPlugin.VARP_MOX_RESIN);
totalPercentage += getAndCalculateComponentData(PotionComponent.AGA, MasteringMixologyPlugin.VARP_AGA_RESIN);
totalPercentage += getAndCalculateComponentData(PotionComponent.LYE, MasteringMixologyPlugin.VARP_LYE_RESIN);
overallProgress = totalPercentage / PotionComponent.values().length;

dataDirty = false;
}

private double getAndCalculateComponentData(PotionComponent component, int varp) {
int currentAmount = client.getVarpValue(varp);
int goalAmount = rewardItem.componentCost(component);
double percentage = goalAmount == 0 ? 1.0 : Math.min((double) currentAmount / goalAmount, 1.0);

ComponentData data = new ComponentData(currentAmount, goalAmount, percentage);
componentDataMap.put(component, data);

return percentage;
}

private void createProgressBar(PotionComponent component) {
ComponentData data = componentDataMap.get(component);

final ImageComponent imageComponent = new ImageComponent(getComponentSprite(component));
final ProgressBarComponent progressBarComponent = new ProgressBarComponent();
progressBarComponent.setForegroundColor(Color.decode("#" + component.color()));
progressBarComponent.setBackgroundColor(new Color(61, 56, 49));
progressBarComponent.setValue(data.percentage * 100d);
progressBarComponent.setLeftLabel(QuantityFormatter.quantityToStackSize(data.currentAmount));
progressBarComponent.setRightLabel(QuantityFormatter.quantityToStackSize(data.goalAmount));

final SplitComponent progressBarSplit = SplitComponent.builder()
.first(imageComponent)
.second(progressBarComponent)
.orientation(ComponentOrientation.HORIZONTAL)
.gap(new Point(ICON_AND_GOAL_GAP, 0))
.build();

panelComponent.getChildren().add(progressBarSplit);
}

private BufferedImage getRewardImage() {
return rewardIconCache.computeIfAbsent(rewardItem.itemId(), itemManager::getImage);
}

private BufferedImage getComponentSprite(PotionComponent component) {
return componentSpriteCache.computeIfAbsent(component, comp -> {
BufferedImage sprite = spriteManager.getSprite(comp.spriteId(), 0);
if (sprite != null) {
BufferedImage resizedImage = ImageUtil.resizeImage(sprite, COMPONENT_SPRITE_SIZE, COMPONENT_SPRITE_SIZE, true);
return ImageUtil.resizeCanvas(resizedImage, COMPONENT_SPRITE_SIZE, COMPONENT_SPRITE_SIZE);
}
return null;
});
}

private static class ComponentData {
final int currentAmount;
final int goalAmount;
final double percentage;

ComponentData(int currentAmount, int goalAmount, double percentage) {
this.currentAmount = currentAmount;
this.goalAmount = goalAmount;
this.percentage = percentage;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,33 @@ default int highlightBorderWidth() {
default int highlightFeather() {
return 1;
}

@ConfigSection(
name = "Reward Tracking",
description = "Track your progress towards rewards",
position = 13
)
String REWARD_TRACKING = "RewardTracking";

@ConfigItem(
section = REWARD_TRACKING,
keyName = "selectedReward",
name = "Selected Reward",
description = "Select a reward to track resin for",
position = 1
)
default RewardItem selectedReward() {
return RewardItem.NONE;
}

@ConfigItem(
section = REWARD_TRACKING,
keyName = "showResinProgressBars",
name = "Show Resin Progress Bars",
description = "Toggle to display or hide the resin progress bars in the goal overlay",
position = 2
)
default boolean showResinProgressBars() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import net.runelite.api.FontID;
import net.runelite.api.GameState;
import net.runelite.api.InventoryID;
import net.runelite.api.Player;
import net.runelite.api.TileObject;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.events.GameStateChanged;
Expand Down Expand Up @@ -60,9 +61,9 @@ public class MasteringMixologyPlugin extends Plugin {
private static final int VARBIT_POTION_ORDER_3 = 11319;
private static final int VARBIT_POTION_MODIFIER_3 = 11320;

private static final int VARP_LYE_RESIN = 4414;
private static final int VARP_AGA_RESIN = 4415;
private static final int VARP_MOX_RESIN = 4416;
static final int VARP_LYE_RESIN = 4414;
static final int VARP_AGA_RESIN = 4415;
static final int VARP_MOX_RESIN = 4416;

private static final int VARBIT_ALEMBIC_PROGRESS = 11328;
private static final int VARBIT_AGITATOR_PROGRESS = 11329;
Expand All @@ -86,6 +87,9 @@ public class MasteringMixologyPlugin extends Plugin {
private static final int COMPONENT_POTION_ORDERS_GROUP_ID = 882;
private static final int COMPONENT_POTION_ORDERS = COMPONENT_POTION_ORDERS_GROUP_ID << 16 | 2;

private static final int LABS_REGION_ID = 5521;
private static final int LABS_REGION_PLANE = 0;

@Inject
private Client client;

Expand All @@ -107,6 +111,9 @@ public class MasteringMixologyPlugin extends Plugin {
@Inject
private InventoryPotionOverlay potionOverlay;

@Inject
private GoalInfoBoxOverlay goalInfoBoxOverlay;

private final Map<AlchemyObject, HighlightedObject> highlightedObjects = new LinkedHashMap<>();
private List<PotionOrder> potionOrders = Collections.emptyList();
private boolean inLab = false;
Expand All @@ -129,6 +136,16 @@ public boolean isInLab() {
return inLab;
}

/**
* @return true if the player is in the labs region (the area where the minigame takes place)
* the isInlab method only checks if they are inside the actual lab room where the UI is active
*/
public boolean isInLabRegion() {
Player player = client.getLocalPlayer();
return player != null && player.getWorldLocation().getRegionID() == LABS_REGION_ID
&& player.getWorldLocation().getPlane() == LABS_REGION_PLANE;
}

@Provides
MasteringMixologyConfig provideConfig(ConfigManager configManager) {
return configManager.getConfig(MasteringMixologyConfig.class);
Expand All @@ -138,16 +155,14 @@ MasteringMixologyConfig provideConfig(ConfigManager configManager) {
protected void startUp() {
overlayManager.add(overlay);
overlayManager.add(potionOverlay);

if (client.getGameState() == GameState.LOGGED_IN) {
clientThread.invokeLater(this::initialize);
}
overlayManager.add(goalInfoBoxOverlay);
}

@Override
protected void shutDown() {
overlayManager.remove(overlay);
overlayManager.remove(potionOverlay);
overlayManager.remove(goalInfoBoxOverlay);
inLab = false;
}

Expand Down Expand Up @@ -197,6 +212,10 @@ public void onConfigChanged(ConfigChanged event) {
unHighlightObject(AlchemyObject.DIGWEED_NORTH_WEST);
}

if (event.getKey().equals("selectedReward") || event.getKey().equals("fullProgressBarBehavior")) {
goalInfoBoxOverlay.markDataAsDirty();
}

if (config.highlightLevers()) {
highlightLevers();
} else {
Expand Down Expand Up @@ -235,6 +254,7 @@ public void onItemContainerChanged(ItemContainerChanged event) {
@Subscribe
public void onVarbitChanged(VarbitChanged event) {
var varbitId = event.getVarbitId();
var varpId = event.getVarpId();
var value = event.getValue();

// Whenever a potion is delivered, all the potion order related varbits are reset to 0 first then
Expand Down Expand Up @@ -348,6 +368,8 @@ public void onVarbitChanged(VarbitChanged event) {
} else if (varbitId == VARBIT_ALEMBIC_QUICKACTION) {
// alembic quick action was just successfully popped
resetDefaultHighlight(AlchemyObject.ALEMBIC);
} else if (varpId == VARP_MOX_RESIN || varpId == VARP_AGA_RESIN || varpId == VARP_LYE_RESIN) {
goalInfoBoxOverlay.markDataAsDirty();
}
}

Expand Down
14 changes: 10 additions & 4 deletions src/main/java/work/fking/masteringmixology/PotionComponent.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package work.fking.masteringmixology;

public enum PotionComponent {
AGA('A', "00e676"),
LYE('L', "e91e63"),
MOX('M', "03a9f4");
AGA('A', "00e676", 5667),
LYE('L', "e91e63", 5668),
MOX('M', "03a9f4", 5666);

private final char character;
private final String color;
private final int spriteId;

PotionComponent(char character, String color) {
PotionComponent(char character, String color, int spriteId) {
this.character = character;
this.color = color;
this.spriteId = spriteId;
}

public char character() {
Expand All @@ -20,4 +22,8 @@ public char character() {
public String color() {
return color;
}

public int spriteId() {
return spriteId;
}
}
Loading

0 comments on commit fcc9bb7

Please sign in to comment.