Merge pull request #3917 from deathbeam/preserve-change-indicator

Add debuff/buff change indicators
This commit is contained in:
Adam
2018-07-09 17:06:14 -04:00
committed by GitHub
8 changed files with 312 additions and 158 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

@@ -31,6 +31,13 @@ import net.runelite.client.config.ConfigItem;
@ConfigGroup("boosts")
public interface BoostsConfig extends Config
{
enum DisplayChangeMode
{
ALWAYS,
BOOSTED,
NEVER
}
@ConfigItem(
keyName = "enableSkill",
name = "Enable Skill Boosts",
@@ -65,21 +72,32 @@ 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",
keyName = "displayNextBuffChange",
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 DisplayChangeMode displayNextBuffChange()
{
return true;
return DisplayChangeMode.BOOSTED;
}
@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 DisplayChangeMode displayNextDebuffChange()
{
return DisplayChangeMode.NEVER;
}
@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,117 +27,79 @@ 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();
panelComponent.getChildren().clear();
if (!config.displayIndicators()
&& config.displayNextChange()
&& lastChange != null
&& overlayActive)
if (config.displayIndicators())
{
int nextChange = plugin.getChangeTime();
if (nextChange > 0)
{
panelComponent.getChildren().add(LineComponent.builder()
.left("Next change in")
.right(String.valueOf(nextChange))
.build());
}
return null;
}
overlayActive = false;
panelComponent.getChildren().clear();
for (Skill skill : plugin.getShownSkills())
int nextChange = plugin.getChangeDownTicks();
if (nextChange != -1)
{
int boosted = client.getBoostedSkillLevel(skill),
base = client.getRealSkillLevel(skill);
panelComponent.getChildren().add(LineComponent.builder()
.left("Next + restore in")
.right(String.valueOf(plugin.getChangeTime(nextChange)))
.build());
}
BoostIndicator indicator = indicators[skill.ordinal()];
nextChange = plugin.getChangeUpTicks();
if (boosted == base)
if (nextChange != -1)
{
panelComponent.getChildren().add(LineComponent.builder()
.left("Next - restore in")
.right(String.valueOf(plugin.getChangeTime(nextChange)))
.build());
}
if (plugin.canShowBoosts())
{
for (Skill skill : plugin.getShownSkills())
{
if (indicator != null && infoBoxManager.getInfoBoxes().contains(indicator))
final int boosted = client.getBoostedSkillLevel(skill);
final int base = client.getRealSkillLevel(skill);
if (boosted == base)
{
infoBoxManager.removeInfoBox(indicator);
}
continue;
}
overlayActive = true;
if (config.displayIndicators())
{
if (indicator == null)
{
indicator = new BoostIndicator(skill, iconManager.getSkillImage(skill), plugin, client, config);
indicators[skill.ordinal()] = indicator;
}
if (!infoBoxManager.getInfoBoxes().contains(indicator))
{
infoBoxManager.addInfoBox(indicator);
}
}
else
{
if (indicator != null && infoBoxManager.getInfoBoxes().contains(indicator))
{
infoBoxManager.removeInfoBox(indicator);
continue;
}
final int boost = boosted - base;
final Color strColor = getTextColor(boost);
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
if (config.useRelativeBoost())
{
str = String.valueOf(boost);
if (boost > 0)
@@ -145,6 +107,10 @@ class BoostsOverlay extends Overlay
str = "+" + str;
}
}
else
{
str = "<col=" + Integer.toHexString(strColor.getRGB() & 0xFFFFFF) + ">" + boosted + "<col=ffffff>/" + base;
}
panelComponent.getChildren().add(LineComponent.builder()
.left(skill.getName())

View File

@@ -24,15 +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 javax.inject.Singleton;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
@@ -40,6 +40,8 @@ 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.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.client.Notifier;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.game.SkillIconManager;
@@ -54,23 +56,20 @@ import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
tags = {"combat", "notifications", "skilling", "overlay"}
)
@Slf4j
@Singleton
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,13 +93,15 @@ 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 int lastChangeDown = -1;
private int lastChangeUp = -1;
private boolean preserveBeenActive = false;
private long lastTickMillis;
@Provides
BoostsConfig provideConfig(ConfigManager configManager)
@@ -109,12 +110,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 +138,24 @@ public class BoostsPlugin extends Plugin
{
overlayManager.remove(boostsOverlay);
infoBoxManager.removeIf(t -> t instanceof BoostIndicator || t instanceof StatChangeIndicator);
preserveBeenActive = false;
lastChangeDown = -1;
lastChangeUp = -1;
isChangedUp = false;
isChangedDown = false;
}
@Subscribe
public void onGameStateChanged(GameStateChanged event)
{
switch (event.getGameState())
{
case LOGIN_SCREEN:
case HOPPING:
// After world hop and log out timers are in undefined state so just reset
lastChangeDown = -1;
lastChangeUp = -1;
}
}
@Subscribe
@@ -132,29 +166,25 @@ public class BoostsPlugin extends Plugin
return;
}
if (event.getKey().equals("displayIndicators") || event.getKey().equals("displayNextChange"))
updateShownSkills();
if (config.displayNextBuffChange() == BoostsConfig.DisplayChangeMode.NEVER)
{
addStatChangeIndicator();
return;
lastChangeDown = -1;
}
Skill[] old = shownSkills;
updateShownSkills(config.enableSkill());
if (!Arrays.equals(old, shownSkills))
if (config.displayNextDebuffChange() == BoostsConfig.DisplayChangeMode.NEVER)
{
infoBoxManager.removeIf(t -> t instanceof BoostIndicator
&& !Arrays.asList(shownSkills).contains(((BoostIndicator) t).getSkill()));
lastChangeUp = -1;
}
}
@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 +193,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 = client.getTickCount();
}
if (cur == last + 1)
{
// Stat was restored up (from debuff)
lastChangeUp = client.getTickCount();
}
lastSkillLevels[skillIdx] = cur;
updateBoostedStats();
int boostThreshold = config.boostThreshold();
if (boostThreshold != 0)
{
int real = client.getRealSkillLevel(skill);
@@ -185,30 +222,93 @@ public class BoostsPlugin extends Plugin
}
}
private void updateShownSkills(boolean showSkillingSkills)
@Subscribe
public void onGameTick(GameTick event)
{
if (showSkillingSkills)
lastTickMillis = System.currentTimeMillis();
if (getChangeUpTicks() <= 0)
{
shownSkills = ObjectArrays.concat(COMBAT, SKILLING, Skill.class);
switch (config.displayNextDebuffChange())
{
case ALWAYS:
if (lastChangeUp != -1)
{
lastChangeUp = client.getTickCount();
}
break;
case BOOSTED:
case NEVER:
lastChangeUp = -1;
break;
}
}
else
if (getChangeDownTicks() <= 0)
{
shownSkills = COMBAT;
switch (config.displayNextBuffChange())
{
case ALWAYS:
if (lastChangeDown != -1)
{
lastChangeDown = client.getTickCount();
}
break;
case BOOSTED:
case NEVER:
lastChangeDown = -1;
break;
}
}
}
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;
}
/**
@@ -224,26 +324,54 @@ public class BoostsPlugin extends Plugin
* Preserve is only required to be on for the 4th and 5th sections of the boost timer
* to gain full effect (seconds 45-75).
*
* @return integer value in seconds until next boost change
* @return integer value in ticks until next boost change
*/
public int getChangeTime()
int getChangeDownTicks()
{
int timeSinceChange = timeSinceLastChange();
if (lastChangeDown == -1 || (config.displayNextBuffChange() == BoostsConfig.DisplayChangeMode.BOOSTED && !isChangedUp))
{
return -1;
}
int ticksSinceChange = client.getTickCount() - lastChangeDown;
boolean isPreserveActive = client.isPrayerActive(Prayer.PRESERVE);
if ((isPreserveActive && (timeSinceChange < 45 || preserveBeenActive)) || timeSinceChange > 75)
if ((isPreserveActive && (ticksSinceChange < 75 || preserveBeenActive)) || ticksSinceChange > 125)
{
preserveBeenActive = true;
return 90 - timeSinceChange;
return 150 - ticksSinceChange;
}
preserveBeenActive = false;
return (timeSinceChange > 60) ? 75 - timeSinceChange : 60 - timeSinceChange;
return (ticksSinceChange > 100) ? 125 - ticksSinceChange : 100 - ticksSinceChange;
}
private int timeSinceLastChange()
/**
* Restoration from debuff is separate timer as restoration from buff because of preserve messing up the buff timer.
* Restoration timer is always in 100 tick cycles.
*
* @return integer value in ticks until next stat restoration up
*/
int getChangeUpTicks()
{
return (int) Duration.between(lastChange, Instant.now()).getSeconds();
if (lastChangeUp == -1 || (config.displayNextDebuffChange() == BoostsConfig.DisplayChangeMode.BOOSTED && !isChangedDown))
{
return -1;
}
int ticksSinceChange = client.getTickCount() - lastChangeUp;
return 100 - ticksSinceChange;
}
}
/**
* Converts tick-based time to accurate second time
* @param time tick-based time
* @return second-based time
*/
int getChangeTime(final int time)
{
final long diff = System.currentTimeMillis() - lastTickMillis;
return time != -1 ? (int)(time * 0.6 - (diff / 1000d)) : time;
}
}

View File

@@ -24,18 +24,43 @@
*/
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", plugin.getChangeTime(up ? plugin.getChangeUpTicks() : plugin.getChangeDownTicks()));
}
@Override
public Color getTextColor()
{
return (up ? plugin.getChangeUpTicks() : plugin.getChangeDownTicks()) < 10 ? Color.RED.brighter() : Color.WHITE;
}
@Override
public boolean render()
{
final int time = up ? plugin.getChangeUpTicks() : plugin.getChangeDownTicks();
return config.displayIndicators() && time != -1;
}
}

View File

@@ -89,15 +89,20 @@ public class InfoBoxOverlay extends Overlay
: PanelComponent.Orientation.HORIZONTAL);
panelComponent.setPreferredSize(new Dimension(config.infoBoxSize(), config.infoBoxSize()));
infoBoxes.forEach(box ->
for (InfoBox box : infoBoxes)
{
if (!box.render())
{
continue;
}
final InfoBoxComponent infoBoxComponent = new InfoBoxComponent();
infoBoxComponent.setColor(box.getTextColor());
infoBoxComponent.setImage(box.getScaledImage());
infoBoxComponent.setText(box.getText());
infoBoxComponent.setTooltip(box.getTooltip());
panelComponent.getChildren().add(infoBoxComponent);
});
}
final Dimension dimension = panelComponent.render(graphics);
final Client client = clientProvider.get();