Add debuff/buff change indicators

- Add support for dynamically updating infoboxes
- Create infoboxes only once and then determine if they should be drawn
- Add indicators for both buff and debuff timers. By default only buff
timer is enabled.
- Properly reset everything on shutdown

Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
This commit is contained in:
Tomas Slusny
2018-06-21 16:28:00 +02:00
committed by Adam
parent 0223206994
commit 2ab19503df
7 changed files with 251 additions and 152 deletions

View File

@@ -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;
}
}

View File

@@ -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()
{

View File

@@ -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 = "<col=" + Integer.toHexString(strColor.getRGB() & 0xFFFFFF) + ">" + boosted + "<col=ffffff>/" + 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 = "<col=" + Integer.toHexString(strColor.getRGB() & 0xFFFFFF) + ">" + boosted + "<col=ffffff>/" + base;
}
panelComponent.getChildren().add(LineComponent.builder()
.left(skill.getName())
.right(str)
.rightColor(strColor)
.build());
}
return panelComponent.render(graphics);

View File

@@ -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<Skill> BOOSTABLE_COMBAT_SKILLS = ImmutableSet.of(
Skill.ATTACK,
Skill.STRENGTH,
Skill.DEFENCE,
Skill.RANGED,
Skill.MAGIC);
private static final Set<Skill> 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<Skill> 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;
}
}

View File

@@ -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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B