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