diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostIndicator.java b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostIndicator.java index 382a17b3e1..012c168534 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostIndicator.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostIndicator.java @@ -29,21 +29,22 @@ import java.awt.image.BufferedImage; import lombok.Getter; import net.runelite.api.Client; import net.runelite.api.Skill; -import net.runelite.client.plugins.Plugin; import net.runelite.client.ui.overlay.infobox.InfoBox; import net.runelite.client.ui.overlay.infobox.InfoBoxPriority; public class BoostIndicator extends InfoBox { + private final BoostsPlugin plugin; private final BoostsConfig config; private final Client client; @Getter private final Skill skill; - public BoostIndicator(Skill skill, BufferedImage image, Plugin plugin, Client client, BoostsConfig config) + BoostIndicator(Skill skill, BufferedImage image, BoostsPlugin plugin, Client client, BoostsConfig config) { super(image, plugin); + this.plugin = plugin; this.config = config; this.client = client; this.skill = skill; @@ -82,4 +83,15 @@ public class BoostIndicator extends InfoBox return new Color(238, 51, 51); } + + @Override + public boolean render() + { + if (config.displayIndicators() && plugin.canShowBoosts() && plugin.getShownSkills().contains(getSkill())) + { + return client.getBoostedSkillLevel(skill) != client.getRealSkillLevel(skill); + } + + return false; + } } 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 3e2a04edc9..058eadc765 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 @@ -66,20 +66,31 @@ public interface BoostsConfig extends Config @ConfigItem( keyName = "displayNextChange", - name = "Display next change", - description = "Configures whether or not to display when the next stat change will be", + name = "Display next buff change", + description = "Configures whether or not to display when the next buffed stat change will be", position = 4 ) - default boolean displayNextChange() + default boolean displayNextBuffChange() { return true; } + @ConfigItem( + keyName = "displayNextDebuffChange", + name = "Display next debuff change", + description = "Configures whether or not to display when the next debuffed stat change will be", + position = 5 + ) + default boolean displayNextDebuffChange() + { + return false; + } + @ConfigItem( keyName = "boostThreshold", name = "Boost Amount Threshold", description = "The amount of levels boosted to send a notification at. A value of 0 will disable notification.", - position = 5 + position = 6 ) default int boostThreshold() { 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 364d2e97b3..818ea16361 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 @@ -27,131 +27,105 @@ package net.runelite.client.plugins.boosts; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; -import java.time.Instant; import javax.inject.Inject; -import lombok.Getter; 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; import net.runelite.client.ui.overlay.components.LineComponent; import net.runelite.client.ui.overlay.components.PanelComponent; -import net.runelite.client.ui.overlay.infobox.InfoBoxManager; class BoostsOverlay extends Overlay { - @Getter - private final BoostIndicator[] indicators = new BoostIndicator[Skill.values().length - 1]; - private final Client client; private final BoostsConfig config; - private final InfoBoxManager infoBoxManager; private final PanelComponent panelComponent = new PanelComponent(); + private final BoostsPlugin plugin; @Inject - private BoostsPlugin plugin; - - @Inject - private SkillIconManager iconManager; - - private boolean overlayActive; - - @Inject - BoostsOverlay(Client client, BoostsConfig config, InfoBoxManager infoBoxManager) + private BoostsOverlay(Client client, BoostsConfig config, BoostsPlugin plugin) { - setPosition(OverlayPosition.TOP_LEFT); - setPriority(OverlayPriority.MED); + this.plugin = plugin; this.client = client; this.config = config; - this.infoBoxManager = infoBoxManager; + setPosition(OverlayPosition.TOP_LEFT); + setPriority(OverlayPriority.MED); } @Override public Dimension render(Graphics2D graphics) { - Instant lastChange = plugin.getLastChange(); + if (config.displayIndicators()) + { + return null; + } + panelComponent.getChildren().clear(); - if (!config.displayIndicators() - && config.displayNextChange() - && lastChange != null - && overlayActive) + if (config.displayNextBuffChange()) { - int nextChange = plugin.getChangeTime(); - if (nextChange > 0) + int nextChange = plugin.getChangeDownTime(); + + if (nextChange != -1) { panelComponent.getChildren().add(LineComponent.builder() - .left("Next change in") + .left("Next + restore in") .right(String.valueOf(nextChange)) .build()); } } - overlayActive = false; + if (config.displayNextDebuffChange()) + { + int nextChange = plugin.getChangeUpTime(); + + if (nextChange != -1) + { + panelComponent.getChildren().add(LineComponent.builder() + .left("Next - restore in") + .right(String.valueOf(nextChange)) + .build()); + } + } + + if (!plugin.canShowBoosts()) + { + return null; + } for (Skill skill : plugin.getShownSkills()) { - int boosted = client.getBoostedSkillLevel(skill), - base = client.getRealSkillLevel(skill); - - BoostIndicator indicator = indicators[skill.ordinal()]; + final int boosted = client.getBoostedSkillLevel(skill); + final int base = client.getRealSkillLevel(skill); if (boosted == base) { - if (indicator != null && infoBoxManager.getInfoBoxes().contains(indicator)) - { - infoBoxManager.removeInfoBox(indicator); - } - continue; } - overlayActive = true; + final int boost = boosted - base; + final Color strColor = getTextColor(boost); + String str; - if (config.displayIndicators()) + if (config.useRelativeBoost()) { - if (indicator == null) + str = String.valueOf(boost); + if (boost > 0) { - indicator = new BoostIndicator(skill, iconManager.getSkillImage(skill), plugin, client, config); - indicators[skill.ordinal()] = indicator; - } - - if (!infoBoxManager.getInfoBoxes().contains(indicator)) - { - infoBoxManager.addInfoBox(indicator); + str = "+" + str; } } else { - if (indicator != null && infoBoxManager.getInfoBoxes().contains(indicator)) - { - infoBoxManager.removeInfoBox(indicator); - } - - String str; - int boost = boosted - base; - Color strColor = getTextColor(boost); - if (!config.useRelativeBoost()) - { - str = "" + boosted + "/" + base; - } - else - { - str = String.valueOf(boost); - if (boost > 0) - { - str = "+" + str; - } - } - - panelComponent.getChildren().add(LineComponent.builder() - .left(skill.getName()) - .right(str) - .rightColor(strColor) - .build()); + str = "" + boosted + "/" + base; } + + panelComponent.getChildren().add(LineComponent.builder() + .left(skill.getName()) + .right(str) + .rightColor(strColor) + .build()); } return panelComponent.render(graphics); 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 0efb989ac0..6d5855a96a 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 @@ -24,14 +24,15 @@ */ package net.runelite.client.plugins.boosts; -import com.google.common.collect.ObjectArrays; +import com.google.common.collect.ImmutableSet; import com.google.common.eventbus.Subscribe; import com.google.inject.Provides; -import java.awt.image.BufferedImage; import java.time.Duration; import java.time.Instant; -import java.time.temporal.ChronoUnit; import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import javax.imageio.ImageIO; import javax.inject.Inject; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -40,6 +41,7 @@ import net.runelite.api.Prayer; import net.runelite.api.Skill; import net.runelite.api.events.BoostedLevelChanged; import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.GameTick; import net.runelite.client.Notifier; import net.runelite.client.config.ConfigManager; import net.runelite.client.game.SkillIconManager; @@ -56,21 +58,17 @@ import net.runelite.client.ui.overlay.infobox.InfoBoxManager; @Slf4j public class BoostsPlugin extends Plugin { - private static final Skill[] COMBAT = new Skill[] - { - Skill.ATTACK, Skill.STRENGTH, Skill.DEFENCE, Skill.RANGED, Skill.MAGIC - }; - private static final Skill[] SKILLING = new Skill[] - { + private static final Set BOOSTABLE_COMBAT_SKILLS = ImmutableSet.of( + Skill.ATTACK, + Skill.STRENGTH, + Skill.DEFENCE, + Skill.RANGED, + Skill.MAGIC); + + private static final Set BOOSTABLE_NON_COMBAT_SKILLS = ImmutableSet.of( Skill.MINING, Skill.AGILITY, Skill.SMITHING, Skill.HERBLORE, Skill.FISHING, Skill.THIEVING, Skill.COOKING, Skill.CRAFTING, Skill.FIREMAKING, Skill.FLETCHING, Skill.WOODCUTTING, Skill.RUNECRAFT, - Skill.SLAYER, Skill.FARMING, Skill.CONSTRUCTION, Skill.HUNTER - }; - - private final int[] lastSkillLevels = new int[Skill.values().length - 1]; - - @Getter - private Instant lastChange; + Skill.SLAYER, Skill.FARMING, Skill.CONSTRUCTION, Skill.HUNTER); @Inject private Notifier notifier; @@ -94,12 +92,13 @@ public class BoostsPlugin extends Plugin private SkillIconManager skillIconManager; @Getter - private Skill[] shownSkills; - - private StatChangeIndicator statChangeIndicator; - - private BufferedImage overallIcon; + private final Set shownSkills = new HashSet<>(); + private boolean isChangedDown = false; + private boolean isChangedUp = false; + private final int[] lastSkillLevels = new int[Skill.values().length - 1]; + private Instant lastChangeDown; + private Instant lastChangeUp; private boolean preserveBeenActive = false; @Provides @@ -109,12 +108,27 @@ public class BoostsPlugin extends Plugin } @Override - protected void startUp() + protected void startUp() throws Exception { overlayManager.add(boostsOverlay); - updateShownSkills(config.enableSkill()); + updateShownSkills(); + updateBoostedStats(); Arrays.fill(lastSkillLevels, -1); - overallIcon = skillIconManager.getSkillImage(Skill.OVERALL); + + // Add infoboxes for everything at startup and then determine inside if it will be rendered + synchronized (ImageIO.class) + { + infoBoxManager.addInfoBox(new StatChangeIndicator(true, ImageIO.read(getClass().getResourceAsStream("debuffed.png")), this, config)); + infoBoxManager.addInfoBox(new StatChangeIndicator(false, ImageIO.read(getClass().getResourceAsStream("buffed.png")), this, config)); + } + + for (final Skill skill : Skill.values()) + { + if (skill != Skill.OVERALL) + { + infoBoxManager.addInfoBox(new BoostIndicator(skill, skillIconManager.getSkillImage(skill), this, client, config)); + } + } } @Override @@ -122,6 +136,11 @@ public class BoostsPlugin extends Plugin { overlayManager.remove(boostsOverlay); infoBoxManager.removeIf(t -> t instanceof BoostIndicator || t instanceof StatChangeIndicator); + preserveBeenActive = false; + lastChangeDown = null; + lastChangeUp = null; + isChangedUp = false; + isChangedDown = false; } @Subscribe @@ -132,29 +151,15 @@ public class BoostsPlugin extends Plugin return; } - if (event.getKey().equals("displayIndicators") || event.getKey().equals("displayNextChange")) - { - addStatChangeIndicator(); - return; - } - - Skill[] old = shownSkills; - updateShownSkills(config.enableSkill()); - - if (!Arrays.equals(old, shownSkills)) - { - infoBoxManager.removeIf(t -> t instanceof BoostIndicator - && !Arrays.asList(shownSkills).contains(((BoostIndicator) t).getSkill())); - } + updateShownSkills(); } @Subscribe - void onBoostedLevelChange(BoostedLevelChanged boostedLevelChanged) + public void onBoostedLevelChange(BoostedLevelChanged boostedLevelChanged) { Skill skill = boostedLevelChanged.getSkill(); - // Ignore changes to hitpoints or prayer - if (skill == Skill.HITPOINTS || skill == Skill.PRAYER) + if (!BOOSTABLE_COMBAT_SKILLS.contains(skill) && !BOOSTABLE_NON_COMBAT_SKILLS.contains(skill)) { return; } @@ -163,16 +168,23 @@ public class BoostsPlugin extends Plugin int last = lastSkillLevels[skillIdx]; int cur = client.getBoostedSkillLevel(skill); - // Check if stat goes +1 or -1 - if (cur == last + 1 || cur == last - 1) + if (cur == last - 1) { - log.debug("Skill {} healed", skill); - lastChange = Instant.now(); - addStatChangeIndicator(); + // Stat was restored down (from buff) + lastChangeDown = Instant.now(); } + + if (cur == last + 1) + { + // Stat was restored up (from debuff) + lastChangeUp = Instant.now(); + } + lastSkillLevels[skillIdx] = cur; + updateBoostedStats(); int boostThreshold = config.boostThreshold(); + if (boostThreshold != 0) { int real = client.getRealSkillLevel(skill); @@ -185,30 +197,65 @@ public class BoostsPlugin extends Plugin } } - private void updateShownSkills(boolean showSkillingSkills) + @Subscribe + public void onGameTick(GameTick event) { - if (showSkillingSkills) + if (config.displayNextDebuffChange() && getChangeUpTime() < 0) { - shownSkills = ObjectArrays.concat(COMBAT, SKILLING, Skill.class); + lastChangeUp = null; } - else + + if (config.displayNextBuffChange() && getChangeDownTime() < 0) { - shownSkills = COMBAT; + lastChangeDown = null; } } - public void addStatChangeIndicator() + private void updateShownSkills() { - infoBoxManager.removeInfoBox(statChangeIndicator); - statChangeIndicator = null; - - if (lastChange != null - && config.displayIndicators() - && config.displayNextChange()) + if (config.enableSkill()) { - statChangeIndicator = new StatChangeIndicator(getChangeTime(), ChronoUnit.SECONDS, overallIcon, this); - infoBoxManager.addInfoBox(statChangeIndicator); + shownSkills.addAll(BOOSTABLE_NON_COMBAT_SKILLS); } + else + { + shownSkills.removeAll(BOOSTABLE_NON_COMBAT_SKILLS); + } + + shownSkills.addAll(BOOSTABLE_COMBAT_SKILLS); + } + + private void updateBoostedStats() + { + // Reset is boosted + isChangedDown = false; + isChangedUp = false; + + // Check if we are still boosted + for (final Skill skill : Skill.values()) + { + if (!BOOSTABLE_COMBAT_SKILLS.contains(skill) && !BOOSTABLE_NON_COMBAT_SKILLS.contains(skill)) + { + continue; + } + + final int boosted = client.getBoostedSkillLevel(skill); + final int base = client.getRealSkillLevel(skill); + + if (boosted > base) + { + isChangedUp = true; + } + else if (boosted < base) + { + isChangedDown = true; + } + } + } + + boolean canShowBoosts() + { + return isChangedDown || isChangedUp; } /** @@ -226,9 +273,14 @@ public class BoostsPlugin extends Plugin * * @return integer value in seconds until next boost change */ - public int getChangeTime() + int getChangeDownTime() { - int timeSinceChange = timeSinceLastChange(); + if (lastChangeDown == null || !isChangedUp) + { + return -1; + } + + int timeSinceChange = (int) Duration.between(lastChangeDown, Instant.now()).getSeconds(); boolean isPreserveActive = client.isPrayerActive(Prayer.PRESERVE); if ((isPreserveActive && (timeSinceChange < 45 || preserveBeenActive)) || timeSinceChange > 75) @@ -241,9 +293,20 @@ public class BoostsPlugin extends Plugin return (timeSinceChange > 60) ? 75 - timeSinceChange : 60 - timeSinceChange; } - private int timeSinceLastChange() + /** + * Restoration from debuff is separate timer as restoration from buff, and is always ticking, so just find + * diff between last change up and now and limit it to cycles of 60. + * + * @return integer value in seconds until next stat restoration up + */ + int getChangeUpTime() { - return (int) Duration.between(lastChange, Instant.now()).getSeconds(); - } + if (lastChangeUp == null || !isChangedDown) + { + return -1; + } + int timeSinceChange = (int) Duration.between(lastChangeUp, Instant.now()).getSeconds(); + return 60 - timeSinceChange; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/StatChangeIndicator.java b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/StatChangeIndicator.java index 78275e4027..ebd407d27e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/StatChangeIndicator.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/StatChangeIndicator.java @@ -24,18 +24,57 @@ */ package net.runelite.client.plugins.boosts; +import java.awt.Color; import java.awt.image.BufferedImage; -import java.time.temporal.ChronoUnit; -import net.runelite.client.plugins.Plugin; +import net.runelite.client.ui.overlay.infobox.InfoBox; import net.runelite.client.ui.overlay.infobox.InfoBoxPriority; -import net.runelite.client.ui.overlay.infobox.Timer; -public class StatChangeIndicator extends Timer +public class StatChangeIndicator extends InfoBox { - public StatChangeIndicator(long period, ChronoUnit unit, BufferedImage image, Plugin plugin) + private final boolean up; + private final BoostsPlugin plugin; + private final BoostsConfig config; + + StatChangeIndicator(boolean up, BufferedImage image, BoostsPlugin plugin, BoostsConfig config) { - super(period, unit, image, plugin); + super(image, plugin); + this.up = up; + this.plugin = plugin; + this.config = config; setPriority(InfoBoxPriority.MED); - setTooltip("Next stat change"); + setTooltip(up ? "Next debuff change" : "Next buff change"); + } + + @Override + public String getText() + { + return String.format("%02d", up ? plugin.getChangeUpTime() : plugin.getChangeDownTime()); + } + + @Override + public Color getTextColor() + { + final int time = up ? plugin.getChangeUpTime() : plugin.getChangeDownTime(); + + if (time < 6) + { + return Color.RED.brighter(); + } + + return Color.WHITE; + } + + @Override + public boolean render() + { + final int time = up ? plugin.getChangeUpTime() : plugin.getChangeDownTime(); + + if (time == -1) + { + return false; + } + + final boolean enable = up ? config.displayNextDebuffChange() : config.displayNextBuffChange(); + return config.displayIndicators() && enable; } } diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/boosts/buffed.png b/runelite-client/src/main/resources/net/runelite/client/plugins/boosts/buffed.png new file mode 100644 index 0000000000..c8ab82e845 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/boosts/buffed.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/boosts/debuffed.png b/runelite-client/src/main/resources/net/runelite/client/plugins/boosts/debuffed.png new file mode 100644 index 0000000000..fb334fd743 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/boosts/debuffed.png differ