From e8a65220e0e8a8689ab1c30ba6a198ad6edeb401 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 19 Jun 2022 00:10:32 -0400 Subject: [PATCH] boosts: add compact view --- .../client/plugins/boosts/BoostsConfig.java | 36 ++++- .../client/plugins/boosts/BoostsOverlay.java | 4 - .../client/plugins/boosts/BoostsPlugin.java | 30 +++- .../plugins/boosts/CompactBoostsOverlay.java | 152 ++++++++++++++++++ 4 files changed, 208 insertions(+), 14 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/boosts/CompactBoostsOverlay.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsConfig.java index 0711d17fb3..86b9629081 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsConfig.java @@ -59,7 +59,7 @@ public interface BoostsConfig extends Config @ConfigItem( keyName = "relativeBoost", - name = "Use Relative Boosts", + name = "Show relative boosts", description = "Configures whether or not relative boost is used", position = 2 ) @@ -70,8 +70,8 @@ public interface BoostsConfig extends Config @ConfigItem( keyName = "displayIndicators", - name = "Display as infoboxes", - description = "Configures whether or not to display the boost as infoboxes", + name = "Display infoboxes", + description = "Configures whether to display boost infoboxes", position = 3 ) default boolean displayInfoboxes() @@ -79,11 +79,33 @@ public interface BoostsConfig extends Config return true; } + @ConfigItem( + keyName = "displayPanel", + name = "Display panel", + description = "Configures whether to display the boost panel", + position = 3 + ) + default boolean displayPanel() + { + return false; + } + + @ConfigItem( + keyName = "compactDisplay", + name = "Compact display", + description = "Displays skill boosts in a more compact panel", + position = 4 + ) + default boolean compactDisplay() + { + return false; + } + @ConfigItem( keyName = "displayNextBuffChange", name = "Next buff change", description = "Configures whether or not to display when the next buffed stat change will be", - position = 4 + position = 10 ) default DisplayChangeMode displayNextBuffChange() { @@ -94,7 +116,7 @@ public interface BoostsConfig extends Config keyName = "displayNextDebuffChange", name = "Next debuff change", description = "Configures whether or not to display when the next debuffed stat change will be", - position = 5 + position = 11 ) default DisplayChangeMode displayNextDebuffChange() { @@ -105,7 +127,7 @@ public interface BoostsConfig extends Config keyName = "boostThreshold", name = "Boost threshold", description = "The threshold at which boosted levels will be displayed in a different color. A value of 0 will disable the feature.", - position = 6 + position = 12 ) default int boostThreshold() { @@ -116,7 +138,7 @@ public interface BoostsConfig extends Config keyName = "notifyOnBoost", name = "Notify on boost threshold", description = "Configures whether or not a notification will be sent for boosted stats.", - position = 7 + position = 13 ) default boolean notifyOnBoost() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsOverlay.java index fff6408f2f..16d454acf0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsOverlay.java @@ -30,10 +30,7 @@ import java.awt.Graphics2D; import java.util.Set; import javax.inject.Inject; import net.runelite.api.Client; -import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG; import net.runelite.api.Skill; -import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE; -import net.runelite.client.ui.overlay.OverlayMenuEntry; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPosition; import net.runelite.client.ui.overlay.OverlayPriority; @@ -55,7 +52,6 @@ class BoostsOverlay extends OverlayPanel this.config = config; setPosition(OverlayPosition.TOP_LEFT); setPriority(OverlayPriority.MED); - getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Boosts overlay")); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsPlugin.java index dac873dbf7..02f23a8776 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostsPlugin.java @@ -26,27 +26,32 @@ package net.runelite.client.plugins.boosts; import com.google.common.collect.ImmutableSet; import com.google.inject.Provides; +import java.awt.image.BufferedImage; import java.util.Arrays; import java.util.EnumSet; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; +import lombok.AccessLevel; import lombok.Getter; import net.runelite.api.Client; import net.runelite.api.Constants; +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG; import net.runelite.api.Prayer; import net.runelite.api.Skill; -import net.runelite.client.events.ConfigChanged; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameTick; import net.runelite.api.events.StatChanged; import net.runelite.client.Notifier; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; import net.runelite.client.game.SkillIconManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.ui.overlay.OverlayManager; +import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE; +import net.runelite.client.ui.overlay.OverlayMenuEntry; import net.runelite.client.ui.overlay.infobox.InfoBoxManager; import net.runelite.client.util.ImageUtil; @@ -85,6 +90,9 @@ public class BoostsPlugin extends Plugin @Inject private BoostsOverlay boostsOverlay; + @Inject + private CompactBoostsOverlay compactBoostsOverlay; + @Inject private BoostsConfig config; @@ -96,6 +104,9 @@ public class BoostsPlugin extends Plugin private final Set shownSkills = EnumSet.noneOf(Skill.class); + @Getter(AccessLevel.PACKAGE) + private BufferedImage buffed, debuffed; + private boolean isChangedDown = false; private boolean isChangedUp = false; private final int[] lastSkillLevels = new int[Skill.values().length - 1]; @@ -113,14 +124,23 @@ public class BoostsPlugin extends Plugin @Override protected void startUp() throws Exception { + OverlayMenuEntry menuEntry = new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Boosts overlay"); + + boostsOverlay.getMenuEntries().add(menuEntry); + compactBoostsOverlay.getMenuEntries().add(menuEntry);; + overlayManager.add(boostsOverlay); + overlayManager.add(compactBoostsOverlay); updateShownSkills(); Arrays.fill(lastSkillLevels, -1); + buffed = ImageUtil.loadImageResource(getClass(), "buffed.png"); + debuffed = ImageUtil.loadImageResource(getClass(), "debuffed.png"); + // Add infoboxes for everything at startup and then determine inside if it will be rendered - infoBoxManager.addInfoBox(new StatChangeIndicator(true, ImageUtil.loadImageResource(getClass(), "debuffed.png"), this, config)); - infoBoxManager.addInfoBox(new StatChangeIndicator(false, ImageUtil.loadImageResource(getClass(), "buffed.png"), this, config)); + infoBoxManager.addInfoBox(new StatChangeIndicator(true, buffed, this, config)); + infoBoxManager.addInfoBox(new StatChangeIndicator(false, debuffed, this, config)); for (final Skill skill : Skill.values()) { @@ -134,7 +154,10 @@ public class BoostsPlugin extends Plugin @Override protected void shutDown() throws Exception { + boostsOverlay.getMenuEntries().clear(); + compactBoostsOverlay.getMenuEntries().clear(); overlayManager.remove(boostsOverlay); + overlayManager.remove(compactBoostsOverlay); infoBoxManager.removeIf(t -> t instanceof BoostIndicator || t instanceof StatChangeIndicator); preserveBeenActive = false; lastChangeDown = -1; @@ -142,6 +165,7 @@ public class BoostsPlugin extends Plugin isChangedUp = false; isChangedDown = false; skillsToDisplay.clear(); + buffed = debuffed = null; } @Subscribe diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/CompactBoostsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/CompactBoostsOverlay.java new file mode 100644 index 0000000000..828feef03d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/CompactBoostsOverlay.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2022, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.boosts; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.util.Set; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.Skill; +import net.runelite.client.game.SkillIconManager; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; + +class CompactBoostsOverlay extends Overlay +{ + private final Client client; + private final BoostsConfig config; + private final BoostsPlugin plugin; + private final SkillIconManager skillIconManager; + + private int curY; + private int maxX; + + @Inject + private CompactBoostsOverlay(Client client, BoostsConfig config, BoostsPlugin plugin, SkillIconManager skillIconManager) + { + super(plugin); + this.client = client; + this.config = config; + this.plugin = plugin; + this.skillIconManager = skillIconManager; + setPosition(OverlayPosition.TOP_LEFT); + setPriority(OverlayPriority.MED); + } + + @Override + public Dimension render(Graphics2D graphics) + { + final Set boostedSkills = plugin.getSkillsToDisplay(); + if (boostedSkills.isEmpty() || !config.compactDisplay()) + { + return null; + } + + curY = maxX = 0; + + final FontMetrics fontMetrics = graphics.getFontMetrics(); + final int fontHeight = fontMetrics.getHeight(); + for (Skill skill : boostedSkills) + { + final int boosted = client.getBoostedSkillLevel(skill); + final int base = client.getRealSkillLevel(skill); + final int boost = boosted - base; + + if (boost == 0) + { + continue; + } + + drawBoost(graphics, fontMetrics, fontHeight, + skillIconManager.getSkillImage(skill, true), + getTextColor(boost), + getBoostText(boost, base, boosted)); + } + + int time = plugin.getChangeUpTicks(); + if (time != -1) + { + drawBoost(graphics, fontMetrics, fontHeight, + plugin.getBuffed(), + time < 10 ? Color.RED.brighter() : Color.WHITE, + String.format("%02d", plugin.getChangeTime(time))); + } + + time = plugin.getChangeDownTicks(); + if (time != -1) + { + drawBoost(graphics, fontMetrics, fontHeight, + plugin.getDebuffed(), + time < 10 ? Color.RED.brighter() : Color.WHITE, + String.format("%02d", plugin.getChangeTime(time))); + } + + return new Dimension(maxX, curY); + } + + private void drawBoost(Graphics2D graphics, FontMetrics fontMetrics, int fontHeight, BufferedImage image, Color color, String text) + { + graphics.drawImage(image, 0, curY, null); + + // add a little bit of padding to get the text off the side of the image + final int x = image.getWidth() + 6; + graphics.setColor(color); + graphics.drawString(text, x, + // this really should be y + (image.getHeight() / 2) + (fontHeight / 2), but in practice + // it is the same + curY + fontHeight); + + curY += Math.max(image.getHeight(), fontHeight) + + 1; // padding to keep images from touching + maxX = Math.max(maxX, x + fontMetrics.stringWidth(text)); + } + + private String getBoostText(int boost, int base, int boosted) + { + if (config.useRelativeBoost()) + { + return boost > 0 ? "+" + boost : "-" + boost; + } + else + { + return Integer.toString(boosted); + } + } + + private Color getTextColor(int boost) + { + if (boost < 0) + { + return new Color(238, 51, 51); + } + + return boost <= config.boostThreshold() ? Color.YELLOW : Color.GREEN; + } +}