diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterOverlay.java deleted file mode 100644 index f95edccb1b..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterOverlay.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2018 Abex - * 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.regenmeter; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.Stroke; -import java.awt.geom.Arc2D; -import javax.inject.Inject; -import javax.inject.Singleton; -import net.runelite.api.Client; -import net.runelite.api.VarPlayer; -import net.runelite.api.widgets.Widget; -import net.runelite.api.widgets.WidgetInfo; -import net.runelite.client.ui.overlay.Overlay; -import net.runelite.client.ui.overlay.OverlayLayer; -import net.runelite.client.ui.overlay.OverlayPosition; - -@Singleton -public class RegenMeterOverlay extends Overlay -{ - private static final Color HITPOINTS_COLOR = brighter(0x9B0703); - private static final Color SPECIAL_COLOR = brighter(0x1E95B0); - private static final Color OVERLAY_COLOR = new Color(255, 255, 255, 60); - private static final double DIAMETER = 26D; - private static final int OFFSET = 27; - private static final Stroke STROKE = new BasicStroke(2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); - - private final Client client; - private final RegenMeterPlugin plugin; - - private Rectangle getBounds(WidgetInfo widgetInfo) - { - Widget widget = client.getWidget(widgetInfo); - if (widget == null || widget.isHidden()) - { - return null; - } - return widget.getBounds(); - } - - private static Color brighter(int color) - { - float[] hsv = new float[3]; - Color.RGBtoHSB(color >>> 16, (color >> 8) & 0xFF, color & 0xFF, hsv); - return Color.getHSBColor(hsv[0], 1.f, 1.f); - } - - @Inject - public RegenMeterOverlay(final Client client, final RegenMeterPlugin plugin) - { - setPosition(OverlayPosition.DYNAMIC); - setLayer(OverlayLayer.ABOVE_WIDGETS); - this.client = client; - this.plugin = plugin; - } - - @Override - public Dimension render(Graphics2D g) - { - g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); - - if (plugin.isShowHitpoints()) - { - renderRegen(g, WidgetInfo.MINIMAP_HEALTH_ORB, plugin.getHitpointsPercentage(), HITPOINTS_COLOR); - } - - if (plugin.isShowSpecial()) - { - if (client.getVar(VarPlayer.SPECIAL_ATTACK_ENABLED) == 1) - { - final Rectangle bounds = getBounds(WidgetInfo.MINIMAP_SPEC_ORB); - if (bounds != null) - { - g.setColor(RegenMeterOverlay.OVERLAY_COLOR); - g.fillOval( - bounds.x + OFFSET, - bounds.y + (int) (bounds.height / 2D - (DIAMETER) / 2D), - (int) DIAMETER, (int) DIAMETER); - } - } - - renderRegen(g, WidgetInfo.MINIMAP_SPEC_ORB, plugin.getSpecialPercentage(), SPECIAL_COLOR); - } - - return null; - } - - private void renderRegen(Graphics2D g, WidgetInfo widgetInfo, double percent, Color color) - { - final Rectangle bounds = getBounds(widgetInfo); - if (bounds != null) - { - Arc2D.Double arc = new Arc2D.Double(bounds.x + OFFSET, bounds.y + (bounds.height / 2 - DIAMETER / 2), DIAMETER, DIAMETER, 90.d, -360.d * percent, Arc2D.OPEN); - g.setStroke(STROKE); - g.setColor(color); - g.draw(arc); - } - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterPlugin.java deleted file mode 100644 index cc5b57e8ed..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterPlugin.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (c) 2019, Sean Dewar - * Copyright (c) 2018, Abex - * Copyright (c) 2018, Zimaya - * Copyright (c) 2017, 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.regenmeter; - -import com.google.inject.Provides; -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 net.runelite.api.GameState; -import net.runelite.api.Prayer; -import net.runelite.api.Skill; -import net.runelite.api.VarPlayer; -import net.runelite.api.events.ConfigChanged; -import net.runelite.api.events.GameStateChanged; -import net.runelite.api.events.GameTick; -import net.runelite.api.events.VarbitChanged; -import net.runelite.client.Notifier; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.overlay.OverlayManager; - -@PluginDescriptor( - name = "Regeneration Meter", - description = "Track and show the hitpoints and special attack regeneration timers", - tags = {"combat", "health", "hitpoints", "special", "attack", "overlay", "notifications"} -) -@Singleton -public class RegenMeterPlugin extends Plugin -{ - private static final int SPEC_REGEN_TICKS = 50; - private static final int NORMAL_HP_REGEN_TICKS = 100; - - @Inject - private Client client; - - @Inject - private OverlayManager overlayManager; - - @Inject - private Notifier notifier; - - @Inject - private RegenMeterOverlay overlay; - - @Inject - private RegenMeterConfig config; - - @Getter(AccessLevel.PACKAGE) - private double hitpointsPercentage; - - @Getter(AccessLevel.PACKAGE) - private double specialPercentage; - - private int ticksSinceSpecRegen; - private int ticksSinceHPRegen; - private boolean wasRapidHeal; - - @Getter(AccessLevel.PACKAGE) - private boolean showHitpoints; - @Getter(AccessLevel.PACKAGE) - private boolean showSpecial; - private boolean showWhenNoChange; - private int getNotifyBeforeHpRegenSeconds; - - @Provides - RegenMeterConfig provideConfig(ConfigManager configManager) - { - return configManager.getConfig(RegenMeterConfig.class); - } - - @Override - protected void startUp() throws Exception - { - updateConfig(); - overlayManager.add(overlay); - } - - @Override - protected void shutDown() throws Exception - { - overlayManager.remove(overlay); - } - - @Subscribe - private void onGameStateChanged(GameStateChanged ev) - { - if (ev.getGameState() == GameState.HOPPING || ev.getGameState() == GameState.LOGIN_SCREEN) - { - ticksSinceHPRegen = -2; // For some reason this makes this accurate - ticksSinceSpecRegen = 0; - } - } - - @Subscribe - private void onVarbitChanged(VarbitChanged ev) - { - boolean isRapidHeal = client.isPrayerActive(Prayer.RAPID_HEAL); - if (wasRapidHeal != isRapidHeal) - { - ticksSinceHPRegen = 0; - } - wasRapidHeal = isRapidHeal; - } - - @Subscribe - public void onGameTick(GameTick event) - { - if (client.getVar(VarPlayer.SPECIAL_ATTACK_PERCENT) == 1000) - { - // The recharge doesn't tick when at 100% - ticksSinceSpecRegen = 0; - } - else - { - ticksSinceSpecRegen = (ticksSinceSpecRegen + 1) % SPEC_REGEN_TICKS; - } - specialPercentage = ticksSinceSpecRegen / (double) SPEC_REGEN_TICKS; - - - int ticksPerHPRegen = NORMAL_HP_REGEN_TICKS; - if (client.isPrayerActive(Prayer.RAPID_HEAL)) - { - ticksPerHPRegen /= 2; - } - - ticksSinceHPRegen = (ticksSinceHPRegen + 1) % ticksPerHPRegen; - hitpointsPercentage = ticksSinceHPRegen / (double) ticksPerHPRegen; - - int currentHP = client.getBoostedSkillLevel(Skill.HITPOINTS); - int maxHP = client.getRealSkillLevel(Skill.HITPOINTS); - if (currentHP == maxHP && !this.showWhenNoChange) - { - hitpointsPercentage = 0; - } - else if (currentHP > maxHP) - { - // Show it going down - hitpointsPercentage = 1 - hitpointsPercentage; - } - - if (this.getNotifyBeforeHpRegenSeconds > 0 && currentHP < maxHP && shouldNotifyHpRegenThisTick(ticksPerHPRegen)) - { - notifier.notify("[" + client.getLocalPlayer().getName() + "] regenerates their next hitpoint soon!"); - } - } - - private boolean shouldNotifyHpRegenThisTick(int ticksPerHPRegen) - { - // if the configured duration lies between two ticks, choose the earlier tick - final int ticksBeforeHPRegen = ticksPerHPRegen - ticksSinceHPRegen; - final int notifyTick = (int) Math.ceil(this.getNotifyBeforeHpRegenSeconds * 1000d / Constants.GAME_TICK_LENGTH); - return ticksBeforeHPRegen == notifyTick; - } - - @Subscribe - public void onConfigChanged(ConfigChanged event) - { - if (event.getGroup().equals("regenmeter")) - { - updateConfig(); - } - } - - private void updateConfig() - { - this.showHitpoints = config.showHitpoints(); - this.showSpecial = config.showSpecial(); - this.showWhenNoChange = config.showWhenNoChange(); - this.getNotifyBeforeHpRegenSeconds = config.getNotifyBeforeHpRegenSeconds(); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/runenergy/RunEnergyConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/runenergy/RunEnergyConfig.java deleted file mode 100644 index 3c1418411a..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/runenergy/RunEnergyConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2018, Sean Dewar - * 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.runenergy; - -import net.runelite.client.config.Config; -import net.runelite.client.config.ConfigGroup; -import net.runelite.client.config.ConfigItem; - -@ConfigGroup("runenergy") -public interface RunEnergyConfig extends Config -{ - @ConfigItem( - keyName = "replaceOrbText", - name = "Replace orb text with run time left", - description = "Show the remaining run time (in seconds) next in the energy orb." - ) - default boolean replaceOrbText() - { - return false; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/runenergy/RunEnergyOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/runenergy/RunEnergyOverlay.java deleted file mode 100644 index 258fc98211..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/runenergy/RunEnergyOverlay.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2018, Sean Dewar - * 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.runenergy; - -import java.awt.Dimension; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import javax.inject.Inject; -import javax.inject.Singleton; -import net.runelite.api.Client; -import net.runelite.api.Point; -import net.runelite.api.widgets.Widget; -import net.runelite.api.widgets.WidgetInfo; -import net.runelite.client.ui.overlay.Overlay; -import net.runelite.client.ui.overlay.OverlayLayer; -import net.runelite.client.ui.overlay.OverlayPosition; -import net.runelite.client.ui.overlay.tooltip.Tooltip; -import net.runelite.client.ui.overlay.tooltip.TooltipManager; -import org.apache.commons.lang3.StringUtils; - -@Singleton -class RunEnergyOverlay extends Overlay -{ - private final RunEnergyPlugin plugin; - private final Client client; - private final RunEnergyConfig config; - private final TooltipManager tooltipManager; - - @Inject - private RunEnergyOverlay(final RunEnergyPlugin plugin, final Client client, final RunEnergyConfig config, final TooltipManager tooltipManager) - { - this.plugin = plugin; - this.client = client; - this.config = config; - this.tooltipManager = tooltipManager; - setPosition(OverlayPosition.DYNAMIC); - setLayer(OverlayLayer.ABOVE_WIDGETS); - } - - @Override - public Dimension render(Graphics2D graphics) - { - final Widget runOrb = client.getWidget(WidgetInfo.MINIMAP_TOGGLE_RUN_ORB); - - if (runOrb == null || runOrb.isHidden()) - { - return null; - } - - final Rectangle bounds = runOrb.getBounds(); - - if (bounds.getX() <= 0) - { - return null; - } - - final Point mousePosition = client.getMouseCanvasPosition(); - - if (bounds.contains(mousePosition.getX(), mousePosition.getY())) - { - StringBuilder sb = new StringBuilder(); - sb.append("Weight: ").append(client.getWeight()).append(" kg
"); - - if (config.replaceOrbText()) - { - sb.append("Run Energy: ").append(client.getEnergy()).append("%"); - } - else - { - sb.append("Run Time Remaining: ").append(plugin.getEstimatedRunTimeRemaining(false)); - } - - int secondsUntil100 = plugin.getEstimatedRecoverTimeRemaining(); - if (secondsUntil100 > 0) - { - final int minutes = (int) Math.floor(secondsUntil100 / 60.0); - final int seconds = (int) Math.floor(secondsUntil100 - (minutes * 60.0)); - - sb.append("
").append("100% Energy In: ").append(minutes).append(':').append(StringUtils.leftPad(Integer.toString(seconds), 2, "0")); - } - - tooltipManager.add(new Tooltip(sb.toString())); - } - - return null; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/runenergy/RunEnergyPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/runenergy/RunEnergyPlugin.java deleted file mode 100644 index 8ef9debda1..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/runenergy/RunEnergyPlugin.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright (c) 2018, Sean Dewar - * 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.runenergy; - -import com.google.common.collect.ImmutableSet; -import com.google.inject.Provides; -import javax.inject.Inject; -import javax.inject.Singleton; -import net.runelite.api.Client; -import net.runelite.api.Constants; -import net.runelite.api.EquipmentInventorySlot; -import net.runelite.api.InventoryID; -import net.runelite.api.Item; -import net.runelite.api.ItemContainer; -import static net.runelite.api.ItemID.AGILITY_CAPE; -import static net.runelite.api.ItemID.AGILITY_CAPET; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_11861; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_13589; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_13590; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_13601; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_13602; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_13613; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_13614; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_13625; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_13626; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_13637; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_13638; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_13677; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_13678; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_21076; -import static net.runelite.api.ItemID.GRACEFUL_BOOTS_21078; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_11853; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_13581; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_13582; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_13593; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_13594; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_13605; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_13606; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_13617; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_13618; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_13629; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_13630; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_13669; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_13670; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_21064; -import static net.runelite.api.ItemID.GRACEFUL_CAPE_21066; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_11859; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_13587; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_13588; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_13599; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_13600; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_13611; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_13612; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_13623; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_13624; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_13635; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_13636; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_13675; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_13676; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_21073; -import static net.runelite.api.ItemID.GRACEFUL_GLOVES_21075; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_11851; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_13579; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_13580; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_13591; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_13592; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_13603; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_13604; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_13615; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_13616; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_13627; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_13628; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_13667; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_13668; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_21061; -import static net.runelite.api.ItemID.GRACEFUL_HOOD_21063; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_11857; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_13585; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_13586; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_13597; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_13598; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_13609; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_13610; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_13621; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_13622; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_13633; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_13634; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_13673; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_13674; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_21070; -import static net.runelite.api.ItemID.GRACEFUL_LEGS_21072; -import static net.runelite.api.ItemID.GRACEFUL_TOP_11855; -import static net.runelite.api.ItemID.GRACEFUL_TOP_13583; -import static net.runelite.api.ItemID.GRACEFUL_TOP_13584; -import static net.runelite.api.ItemID.GRACEFUL_TOP_13595; -import static net.runelite.api.ItemID.GRACEFUL_TOP_13596; -import static net.runelite.api.ItemID.GRACEFUL_TOP_13607; -import static net.runelite.api.ItemID.GRACEFUL_TOP_13608; -import static net.runelite.api.ItemID.GRACEFUL_TOP_13619; -import static net.runelite.api.ItemID.GRACEFUL_TOP_13620; -import static net.runelite.api.ItemID.GRACEFUL_TOP_13631; -import static net.runelite.api.ItemID.GRACEFUL_TOP_13632; -import static net.runelite.api.ItemID.GRACEFUL_TOP_13671; -import static net.runelite.api.ItemID.GRACEFUL_TOP_13672; -import static net.runelite.api.ItemID.GRACEFUL_TOP_21067; -import static net.runelite.api.ItemID.GRACEFUL_TOP_21069; -import static net.runelite.api.ItemID.MAX_CAPE; -import net.runelite.api.Skill; -import net.runelite.api.Varbits; -import net.runelite.api.coords.WorldPoint; -import net.runelite.api.events.ConfigChanged; -import net.runelite.api.events.GameTick; -import net.runelite.api.widgets.Widget; -import net.runelite.api.widgets.WidgetInfo; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.overlay.OverlayManager; -import org.apache.commons.lang3.StringUtils; - -@PluginDescriptor( - name = "Run Energy", - description = "Show various information related to run energy", - tags = {"overlay", "stamina"} -) -@Singleton -public class RunEnergyPlugin extends Plugin -{ - // TODO It would be nice if we have the IDs for just the equipped variants of the Graceful set items. - private static final ImmutableSet ALL_GRACEFUL_HOODS = ImmutableSet.of( - GRACEFUL_HOOD_11851, GRACEFUL_HOOD_13579, GRACEFUL_HOOD_13580, GRACEFUL_HOOD_13591, GRACEFUL_HOOD_13592, - GRACEFUL_HOOD_13603, GRACEFUL_HOOD_13604, GRACEFUL_HOOD_13615, GRACEFUL_HOOD_13616, GRACEFUL_HOOD_13627, - GRACEFUL_HOOD_13628, GRACEFUL_HOOD_13667, GRACEFUL_HOOD_13668, GRACEFUL_HOOD_21061, GRACEFUL_HOOD_21063 - ); - - private static final ImmutableSet ALL_GRACEFUL_TOPS = ImmutableSet.of( - GRACEFUL_TOP_11855, GRACEFUL_TOP_13583, GRACEFUL_TOP_13584, GRACEFUL_TOP_13595, GRACEFUL_TOP_13596, - GRACEFUL_TOP_13607, GRACEFUL_TOP_13608, GRACEFUL_TOP_13619, GRACEFUL_TOP_13620, GRACEFUL_TOP_13631, - GRACEFUL_TOP_13632, GRACEFUL_TOP_13671, GRACEFUL_TOP_13672, GRACEFUL_TOP_21067, GRACEFUL_TOP_21069 - ); - - private static final ImmutableSet ALL_GRACEFUL_LEGS = ImmutableSet.of( - GRACEFUL_LEGS_11857, GRACEFUL_LEGS_13585, GRACEFUL_LEGS_13586, GRACEFUL_LEGS_13597, GRACEFUL_LEGS_13598, - GRACEFUL_LEGS_13609, GRACEFUL_LEGS_13610, GRACEFUL_LEGS_13621, GRACEFUL_LEGS_13622, GRACEFUL_LEGS_13633, - GRACEFUL_LEGS_13634, GRACEFUL_LEGS_13673, GRACEFUL_LEGS_13674, GRACEFUL_LEGS_21070, GRACEFUL_LEGS_21072 - ); - - private static final ImmutableSet ALL_GRACEFUL_GLOVES = ImmutableSet.of( - GRACEFUL_GLOVES_11859, GRACEFUL_GLOVES_13587, GRACEFUL_GLOVES_13588, GRACEFUL_GLOVES_13599, GRACEFUL_GLOVES_13600, - GRACEFUL_GLOVES_13611, GRACEFUL_GLOVES_13612, GRACEFUL_GLOVES_13623, GRACEFUL_GLOVES_13624, GRACEFUL_GLOVES_13635, - GRACEFUL_GLOVES_13636, GRACEFUL_GLOVES_13675, GRACEFUL_GLOVES_13676, GRACEFUL_GLOVES_21073, GRACEFUL_GLOVES_21075 - ); - - private static final ImmutableSet ALL_GRACEFUL_BOOTS = ImmutableSet.of( - GRACEFUL_BOOTS_11861, GRACEFUL_BOOTS_13589, GRACEFUL_BOOTS_13590, GRACEFUL_BOOTS_13601, GRACEFUL_BOOTS_13602, - GRACEFUL_BOOTS_13613, GRACEFUL_BOOTS_13614, GRACEFUL_BOOTS_13625, GRACEFUL_BOOTS_13626, GRACEFUL_BOOTS_13637, - GRACEFUL_BOOTS_13638, GRACEFUL_BOOTS_13677, GRACEFUL_BOOTS_13678, GRACEFUL_BOOTS_21076, GRACEFUL_BOOTS_21078 - ); - - // Agility skill capes and the non-cosmetic Max capes also count for the Graceful set effect - private static final ImmutableSet ALL_GRACEFUL_CAPES = ImmutableSet.of( - GRACEFUL_CAPE_11853, GRACEFUL_CAPE_13581, GRACEFUL_CAPE_13582, GRACEFUL_CAPE_13593, GRACEFUL_CAPE_13594, - GRACEFUL_CAPE_13605, GRACEFUL_CAPE_13606, GRACEFUL_CAPE_13617, GRACEFUL_CAPE_13618, GRACEFUL_CAPE_13629, - GRACEFUL_CAPE_13630, GRACEFUL_CAPE_13669, GRACEFUL_CAPE_13670, GRACEFUL_CAPE_21064, GRACEFUL_CAPE_21066, - AGILITY_CAPE, AGILITY_CAPET, MAX_CAPE - ); - - @Inject - private Client client; - - @Inject - private OverlayManager overlayManager; - - @Inject - private RunEnergyOverlay energyOverlay; - - @Inject - private RunEnergyConfig energyConfig; - - private boolean localPlayerRunningToDestination; - private WorldPoint prevLocalPlayerLocation; - - @Provides - RunEnergyConfig getConfig(ConfigManager configManager) - { - return configManager.getConfig(RunEnergyConfig.class); - } - - @Override - protected void startUp() throws Exception - { - overlayManager.add(energyOverlay); - } - - @Override - protected void shutDown() throws Exception - { - overlayManager.remove(energyOverlay); - localPlayerRunningToDestination = false; - prevLocalPlayerLocation = null; - resetRunOrbText(); - } - - @Subscribe - public void onGameTick(GameTick event) - { - localPlayerRunningToDestination = - prevLocalPlayerLocation != null && - client.getLocalDestinationLocation() != null && - prevLocalPlayerLocation.distanceTo(client.getLocalPlayer().getWorldLocation()) > 1; - - prevLocalPlayerLocation = client.getLocalPlayer().getWorldLocation(); - - if (energyConfig.replaceOrbText()) - { - setRunOrbText(getEstimatedRunTimeRemaining(true)); - } - } - - @Subscribe - public void onConfigChanged(ConfigChanged event) - { - if (event.getGroup().equals("runenergy") && !energyConfig.replaceOrbText()) - { - resetRunOrbText(); - } - } - - private void setRunOrbText(String text) - { - Widget runOrbText = client.getWidget(WidgetInfo.MINIMAP_RUN_ORB_TEXT); - - if (runOrbText != null) - { - runOrbText.setText(text); - } - } - - private void resetRunOrbText() - { - setRunOrbText(Integer.toString(client.getEnergy())); - } - - String getEstimatedRunTimeRemaining(boolean inSeconds) - { - // Calculate the amount of energy lost every tick. - // Negative weight has the same depletion effect as 0 kg. - final int effectiveWeight = Math.max(client.getWeight(), 0); - double lossRate = (Math.min(effectiveWeight, 64) / 100.0) + 0.64; - - if (client.getVar(Varbits.RUN_SLOWED_DEPLETION_ACTIVE) != 0) - { - lossRate *= 0.3; // Stamina effect reduces energy depletion to 30% - } - - // Calculate the number of seconds left - final double secondsLeft = (client.getEnergy() * Constants.GAME_TICK_LENGTH) / (lossRate * 1000.0); - - // Return the text - if (inSeconds) - { - return (int) Math.floor(secondsLeft) + "s"; - } - else - { - final int minutes = (int) Math.floor(secondsLeft / 60.0); - final int seconds = (int) Math.floor(secondsLeft - (minutes * 60.0)); - - return minutes + ":" + StringUtils.leftPad(Integer.toString(seconds), 2, "0"); - } - } - - private boolean isLocalPlayerWearingFullGraceful() - { - final ItemContainer equipment = client.getItemContainer(InventoryID.EQUIPMENT); - - if (equipment == null) - { - return false; - } - - final Item[] items = equipment.getItems(); - - // Check that the local player is wearing enough items to be using full Graceful - // (the Graceful boots will have the highest slot index in the worn set). - if (items == null || items.length <= EquipmentInventorySlot.BOOTS.getSlotIdx()) - { - return false; - } - - return (ALL_GRACEFUL_HOODS.contains(items[EquipmentInventorySlot.HEAD.getSlotIdx()].getId()) && - ALL_GRACEFUL_TOPS.contains(items[EquipmentInventorySlot.BODY.getSlotIdx()].getId()) && - ALL_GRACEFUL_LEGS.contains(items[EquipmentInventorySlot.LEGS.getSlotIdx()].getId()) && - ALL_GRACEFUL_GLOVES.contains(items[EquipmentInventorySlot.GLOVES.getSlotIdx()].getId()) && - ALL_GRACEFUL_BOOTS.contains(items[EquipmentInventorySlot.BOOTS.getSlotIdx()].getId()) && - ALL_GRACEFUL_CAPES.contains(items[EquipmentInventorySlot.CAPE.getSlotIdx()].getId())); - } - - int getEstimatedRecoverTimeRemaining() - { - if (localPlayerRunningToDestination) - { - return -1; - } - - // Calculate the amount of energy recovered every second - double recoverRate = (48 + client.getBoostedSkillLevel(Skill.AGILITY)) / 360.0; - - if (isLocalPlayerWearingFullGraceful()) - { - recoverRate *= 1.3; // 30% recover rate increase from Graceful set effect - } - - // Calculate the number of seconds left - return (int) ((100 - client.getEnergy()) / recoverRate); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/statusorbs/StatusOrbsConfig.java similarity index 56% rename from runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterConfig.java rename to runelite-client/src/main/java/net/runelite/client/plugins/statusorbs/StatusOrbsConfig.java index 2e8b577381..c05c47645d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/statusorbs/StatusOrbsConfig.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2018 Abex + * Copyright (c) 2018, Sean Dewar * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,29 +23,47 @@ * (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.regenmeter; +package net.runelite.client.plugins.statusorbs; import net.runelite.client.config.Config; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Stub; -@ConfigGroup("regenmeter") -public interface RegenMeterConfig extends Config +@ConfigGroup("statusorbs") +public interface StatusOrbsConfig extends Config { @ConfigItem( - keyName = "showHitpoints", - name = "Show hitpoints regen", - description = "Show a ring around the hitpoints orb") - default boolean showHitpoints() + keyName = "hp", + name = "Hitpoints", + description = "", + position = 0 + ) + default Stub hp() + { + return new Stub(); + } + + @ConfigItem( + keyName = "dynamicHpHeart", + name = "Dynamic hitpoints heart", + description = "Changes the HP heart color to match players current affliction", + parent = "hp", + position = 1 + ) + default boolean dynamicHpHeart() { return true; } @ConfigItem( - keyName = "showSpecial", - name = "Show Spec. Attack regen", - description = "Show a ring around the Special Attack orb") - default boolean showSpecial() + keyName = "showHitpoints", + name = "Show hitpoints regen", + description = "Show a ring around the hitpoints orb", + parent = "hp", + position = 2 + ) + default boolean showHitpoints() { return true; } @@ -52,7 +71,10 @@ public interface RegenMeterConfig extends Config @ConfigItem( keyName = "showWhenNoChange", name = "Show hitpoints regen at full hitpoints", - description = "Always show the hitpoints regen orb, even if there will be no stat change") + description = "Always show the hitpoints regen orb, even if there will be no stat change", + parent = "hp", + position = 3 + ) default boolean showWhenNoChange() { return false; @@ -61,10 +83,70 @@ public interface RegenMeterConfig extends Config @ConfigItem( keyName = "notifyBeforeHpRegenDuration", name = "Hitpoint Regen Notification (seconds)", - description = "Notify approximately when your next hitpoint is about to regen. A value of 0 will disable notification." + description = "Notify approximately when your next hitpoint is about to regen. A value of 0 will disable notification.", + parent = "hp", + position = 4 ) default int getNotifyBeforeHpRegenSeconds() { return 0; } -} + + @ConfigItem( + keyName = "spec", + name = "Special attack", + description = "", + position = 5 + ) + default Stub spec() + { + return new Stub(); + } + + @ConfigItem( + keyName = "showSpecial", + name = "Show Spec. Attack regen", + description = "Show a ring around the Special Attack orb", + parent = "spec", + position = 6 + ) + default boolean showSpecial() + { + return true; + } + + @ConfigItem( + keyName = "run", + name = "Run energy", + description = "", + position = 7 + ) + default Stub run() + { + return new Stub(); + } + + @ConfigItem( + keyName = "showRun", + name = "Show run energy regen", + description = "Show a ring around the run regen orb", + position = 8, + parent = "run" + ) + default boolean showRun() + { + return true; + } + + @ConfigItem( + keyName = "replaceOrbText", + name = "Replace run orb text with run time left", + description = "Show the remaining run time (in seconds) next in the energy orb", + position = 9, + parent = "run" + ) + default boolean replaceOrbText() + { + return false; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/statusorbs/StatusOrbsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/statusorbs/StatusOrbsOverlay.java new file mode 100644 index 0000000000..901b2aa771 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/statusorbs/StatusOrbsOverlay.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2018 Abex + * Copyright (c) 2018, Sean Dewar + * 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.statusorbs; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Stroke; +import java.awt.geom.Arc2D; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.InventoryID; +import net.runelite.api.Point; +import net.runelite.api.Skill; +import net.runelite.api.VarPlayer; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.tooltip.Tooltip; +import net.runelite.client.ui.overlay.tooltip.TooltipManager; +import net.runelite.client.util.Graceful; +import org.apache.commons.lang3.StringUtils; + +public class StatusOrbsOverlay extends Overlay +{ + private static final Color HITPOINTS_COLOR = brighter(0x9B0703); + private static final Color SPECIAL_COLOR = brighter(0x1E95B0); + private static final Color RUN_COLOR = new Color(255, 215, 0); + private static final Color OVERLAY_COLOR = new Color(255, 255, 255, 60); + private static final double DIAMETER = 26D; + private static final int OFFSET = 27; + + private final Client client; + private final StatusOrbsPlugin plugin; + private final TooltipManager tooltipManager; + + private long last = System.nanoTime(); + private double percentHp; + private double lastHp; + private double percentSpec; + private double lastSpec; + private double percentRun; + private double lastRun; + + private static Color brighter(int color) + { + float[] hsv = new float[3]; + Color.RGBtoHSB(color >>> 16, (color >> 8) & 0xFF, color & 0xFF, hsv); + return Color.getHSBColor(hsv[0], 1.f, 1.f); + } + + @Inject + public StatusOrbsOverlay(Client client, StatusOrbsPlugin plugin, TooltipManager tooltipManager) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + this.client = client; + this.plugin = plugin; + this.tooltipManager = tooltipManager; + } + + @Override + public Dimension render(Graphics2D g) + { + g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); + + long current = System.nanoTime(); + double ms = (current - last) / (double) 1000000; + + if (plugin.isShowHitpoints()) + { + if (lastHp == plugin.getHitpointsPercentage() && plugin.getHitpointsPercentage() != 0) + { + percentHp += ms * plugin.getHpPerMs(); + } + else + { + percentHp = plugin.getHitpointsPercentage(); + lastHp = plugin.getHitpointsPercentage(); + } + renderRegen(g, WidgetInfo.MINIMAP_HEALTH_ORB, percentHp, HITPOINTS_COLOR); + } + + if (plugin.isShowSpecial()) + { + if (client.getVar(VarPlayer.SPECIAL_ATTACK_ENABLED) == 1) + { + final Widget widget = client.getWidget(WidgetInfo.MINIMAP_SPEC_ORB); + + if (widget != null && !widget.isHidden()) + { + final Rectangle bounds = widget.getBounds(); + g.setColor(OVERLAY_COLOR); + g.fillOval( + bounds.x + OFFSET, + bounds.y + (int) (bounds.height / 2 - (DIAMETER) / 2), + (int) DIAMETER, (int) DIAMETER); + } + } + + if (lastSpec == plugin.getSpecialPercentage() && plugin.getSpecialPercentage() != 0) + { + percentSpec += ms * plugin.getSpecPerMs(); + } + else + { + percentSpec = plugin.getSpecialPercentage(); + lastSpec = plugin.getSpecialPercentage(); + } + + renderRegen(g, WidgetInfo.MINIMAP_SPEC_ORB, percentSpec, SPECIAL_COLOR); + } + + if (plugin.isReplaceOrbText()) + { + final Widget runOrb = client.getWidget(WidgetInfo.MINIMAP_TOGGLE_RUN_ORB); + + if (runOrb == null || runOrb.isHidden()) + { + return null; + } + + final Rectangle bounds = runOrb.getBounds(); + + if (bounds.getX() <= 0) + { + return null; + } + + final Point mousePosition = client.getMouseCanvasPosition(); + + if (bounds.contains(mousePosition.getX(), mousePosition.getY())) + { + StringBuilder sb = new StringBuilder(); + sb.append("Weight: ").append(client.getWeight()).append(" kg
"); + + if (plugin.isReplaceOrbText()) + { + sb.append("Run Energy: ").append(client.getEnergy()).append("%"); + } + else + { + sb.append("Run Time Remaining: ").append(plugin.getEstimatedRunTimeRemaining(false)); + } + + int secondsUntil100 = plugin.getEstimatedRecoverTimeRemaining(); + if (secondsUntil100 > 0) + { + final int minutes = (int) Math.floor(secondsUntil100 / 60.0); + final int seconds = (int) Math.floor(secondsUntil100 - (minutes * 60.0)); + + sb.append("
").append("100% Energy In: ").append(minutes).append(':').append(StringUtils.leftPad(Integer.toString(seconds), 2, "0")); + } + + tooltipManager.add(new Tooltip(sb.toString())); + } + } + + if (plugin.isShowRun()) + { + if (lastRun == plugin.getRunPercentage() && plugin.getRunPercentage() != 0) + { + double recoverRate = (48 + client.getBoostedSkillLevel(Skill.AGILITY)) / 360000.0; + + if (Graceful.hasFullSet(client.getItemContainer(InventoryID.EQUIPMENT))) + { + recoverRate *= 1.3; // 30% recover rate increase from Graceful set effect + } + + percentRun += ms * recoverRate; + } + else + { + percentRun = plugin.getRunPercentage(); + lastRun = plugin.getRunPercentage(); + } + renderRegen(g, WidgetInfo.MINIMAP_RUN_ORB, percentRun, RUN_COLOR); + } + + last = current; + + return null; + } + + private void renderRegen(Graphics2D g, WidgetInfo widgetInfo, double percent, Color color) + { + Widget widget = client.getWidget(widgetInfo); + if (widget == null || widget.isHidden()) + { + return; + } + Rectangle bounds = widget.getBounds(); + + Arc2D.Double arc = new Arc2D.Double(bounds.x + OFFSET, bounds.y + (bounds.height / 2 - DIAMETER / 2), DIAMETER, DIAMETER, 90.d, -360.d * percent, Arc2D.OPEN); + final Stroke STROKE = new BasicStroke(2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); + g.setStroke(STROKE); + g.setColor(color); + g.draw(arc); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/statusorbs/StatusOrbsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/statusorbs/StatusOrbsPlugin.java new file mode 100644 index 0000000000..3e3e671f4a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/statusorbs/StatusOrbsPlugin.java @@ -0,0 +1,490 @@ +/* + * Copyright (c) 2019, Owain van Brakel + * Copyright (c) 2018, TheStonedTurtle + * Copyright (c) 2018 Abex + * Copyright (c) 2018, Zimaya + * Copyright (c) 2017, 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.statusorbs; + +import com.google.inject.Provides; +import java.awt.image.BufferedImage; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import net.runelite.api.Client; +import net.runelite.api.Constants; +import net.runelite.api.GameState; +import net.runelite.api.InventoryID; +import net.runelite.api.Prayer; +import net.runelite.api.Skill; +import net.runelite.api.SpriteID; +import net.runelite.api.VarPlayer; +import net.runelite.api.Varbits; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.VarbitChanged; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.Notifier; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.util.Graceful; +import net.runelite.client.util.ImageUtil; +import org.apache.commons.lang3.StringUtils; + +@PluginDescriptor( + name = "Status Orbs", + description = "Configure settings for the Minimap orbs", + tags = {"minimap", "orb", "regen", "energy", "special"} +) +public class StatusOrbsPlugin extends Plugin +{ + private static final BufferedImage HEART_DISEASE; + private static final BufferedImage HEART_POISON; + private static final BufferedImage HEART_VENOM; + + static + { + HEART_DISEASE = ImageUtil.resizeCanvas(ImageUtil.getResourceStreamFromClass(StatusOrbsPlugin.class, "1067-DISEASE.png"), 26, 26); + HEART_POISON = ImageUtil.resizeCanvas(ImageUtil.getResourceStreamFromClass(StatusOrbsPlugin.class, "1067-POISON.png"), 26, 26); + HEART_VENOM = ImageUtil.resizeCanvas(ImageUtil.getResourceStreamFromClass(StatusOrbsPlugin.class, "1067-VENOM.png"), 26, 26); + } + + private static final int SPEC_REGEN_TICKS = 50; + private static final int NORMAL_HP_REGEN_TICKS = 100; + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private ConfigManager configManager; + + @Inject + private StatusOrbsConfig config; + + @Inject + private StatusOrbsOverlay overlay; + + @Inject + private OverlayManager overlayManager; + + @Inject + private Notifier notifier; + + @Getter + private double hitpointsPercentage; + + @Getter + private double specialPercentage; + + @Getter + private double runPercentage; + + @Getter + private double hpPerMs; + + @Getter + private double specPerMs = (double) 1 / (SPEC_REGEN_TICKS * 600); + + // RegenMeter + private int ticksSinceSpecRegen; + private int ticksSinceHPRegen; + private boolean wasRapidHeal; + private double ticksSinceRunRegen; + + // Run Energy + private int lastEnergy = 0; + private boolean localPlayerRunningToDestination; + private WorldPoint currPoint; + private WorldPoint prevLocalPlayerLocation; + + private BufferedImage heart; + + private boolean dynamicHpHeart; + @Getter(AccessLevel.PACKAGE) + private boolean showHitpoints; + private boolean showWhenNoChange; + private int getNotifyBeforeHpRegenSeconds; + @Getter(AccessLevel.PACKAGE) + private boolean showSpecial; + @Getter(AccessLevel.PACKAGE) + private boolean showRun; + @Getter(AccessLevel.PACKAGE) + private boolean replaceOrbText; + + @Provides + StatusOrbsConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(StatusOrbsConfig.class); + } + + @Override + protected void startUp() throws Exception + { + migrateConfigs(); + updateConfig(); + overlayManager.add(overlay); + if (this.dynamicHpHeart && client.getGameState().equals(GameState.LOGGED_IN)) + { + clientThread.invoke(this::checkHealthIcon); + } + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(overlay); + localPlayerRunningToDestination = false; + prevLocalPlayerLocation = null; + resetRunOrbText(); + if (this.dynamicHpHeart) + { + clientThread.invoke(this::resetHealthIcon); + } + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals("statusorbs")) + { + updateConfig(); + switch (event.getKey()) + { + case "replaceOrbText": + if (!this.replaceOrbText) + { + resetRunOrbText(); + } + break; + case "dynamicHpHeart": + if (this.dynamicHpHeart) + { + checkHealthIcon(); + } + else + { + resetHealthIcon(); + } + break; + } + } + } + + @Subscribe + private void onVarbitChanged(VarbitChanged e) + { + if (this.dynamicHpHeart) + { + checkHealthIcon(); + } + + boolean isRapidHeal = client.isPrayerActive(Prayer.RAPID_HEAL); + if (wasRapidHeal != isRapidHeal) + { + ticksSinceHPRegen = 0; + } + wasRapidHeal = isRapidHeal; + } + + @Subscribe + private void onGameStateChanged(GameStateChanged ev) + { + if (ev.getGameState() == GameState.HOPPING || ev.getGameState() == GameState.LOGIN_SCREEN) + { + ticksSinceHPRegen = -2; // For some reason this makes this accurate + ticksSinceSpecRegen = 0; + ticksSinceRunRegen = -1; + } + } + + @Subscribe + public void onGameTick(GameTick event) + { + if (client.getVar(VarPlayer.SPECIAL_ATTACK_PERCENT) == 1000) + { + // The recharge doesn't tick when at 100% + ticksSinceSpecRegen = 0; + } + else + { + ticksSinceSpecRegen = (ticksSinceSpecRegen + 1) % SPEC_REGEN_TICKS; + } + specialPercentage = ticksSinceSpecRegen / (double) SPEC_REGEN_TICKS; + + int ticksPerHPRegen = NORMAL_HP_REGEN_TICKS; + hpPerMs = ticksPerHPRegen / (double) 6000000; + if (client.isPrayerActive(Prayer.RAPID_HEAL)) + { + ticksPerHPRegen /= 2; + hpPerMs *= 2; + } + + ticksSinceHPRegen = (ticksSinceHPRegen + 1) % ticksPerHPRegen; + hitpointsPercentage = ticksSinceHPRegen / (double) ticksPerHPRegen; + + int currentHP = client.getBoostedSkillLevel(Skill.HITPOINTS); + int maxHP = client.getRealSkillLevel(Skill.HITPOINTS); + if (currentHP == maxHP && !this.showWhenNoChange) + { + hitpointsPercentage = 0; + } + else if (currentHP > maxHP) + { + // Show it going down + hitpointsPercentage = 1 - hitpointsPercentage; + } + + // Run Energy + localPlayerRunningToDestination = + prevLocalPlayerLocation != null && + client.getLocalDestinationLocation() != null && + prevLocalPlayerLocation.distanceTo(client.getLocalPlayer().getWorldLocation()) > 1; + + if (this.getNotifyBeforeHpRegenSeconds > 0 && currentHP < maxHP && shouldNotifyHpRegenThisTick(ticksPerHPRegen)) + { + notifier.notify("[" + client.getLocalPlayer().getName() + "] regenerates their next hitpoint soon!"); + } + + localPlayerRunningToDestination = + prevLocalPlayerLocation != null && + client.getLocalDestinationLocation() != null && + prevLocalPlayerLocation.distanceTo(client.getLocalPlayer().getWorldLocation()) > 1; + + prevLocalPlayerLocation = client.getLocalPlayer().getWorldLocation(); + + if (this.replaceOrbText) + { + setRunOrbText(getEstimatedRunTimeRemaining(true)); + } + + int currEnergy = client.getEnergy(); + currPoint = client.getLocalPlayer().getWorldLocation(); + if (currEnergy == 100 || (prevLocalPlayerLocation != null && currPoint.distanceTo(prevLocalPlayerLocation) > 1) || currEnergy < lastEnergy) + { + ticksSinceRunRegen = 0; + } + else if (currEnergy > lastEnergy) + { + if (runPercentage < 1) + { + ticksSinceRunRegen = (1 - runPercentage) / runRegenPerTick(); + ticksSinceRunRegen = ticksSinceRunRegen > 1 ? 1 : ticksSinceRunRegen; + } + else + { + ticksSinceRunRegen = (runPercentage - 1) / runRegenPerTick(); + } + } + else + { + ticksSinceRunRegen += 1; + } + runPercentage = ticksSinceRunRegen * runRegenPerTick(); + prevLocalPlayerLocation = currPoint; + lastEnergy = currEnergy; + } + + private boolean shouldNotifyHpRegenThisTick(int ticksPerHPRegen) + { + // if the configured duration lies between two ticks, choose the earlier tick + final int ticksBeforeHPRegen = ticksPerHPRegen - ticksSinceHPRegen; + final int notifyTick = (int) Math.ceil(this.getNotifyBeforeHpRegenSeconds * 1000d / Constants.GAME_TICK_LENGTH); + return ticksBeforeHPRegen == notifyTick; + } + + private void setRunOrbText(String text) + { + Widget runOrbText = client.getWidget(WidgetInfo.MINIMAP_RUN_ORB_TEXT); + + if (runOrbText != null) + { + runOrbText.setText(text); + } + } + + private void resetRunOrbText() + { + setRunOrbText(Integer.toString(client.getEnergy())); + } + + String getEstimatedRunTimeRemaining(boolean inSeconds) + { + // Calculate the amount of energy lost every 2 ticks (0.6 seconds). + // Negative weight has the same depletion effect as 0 kg. + final int effectiveWeight = Math.max(client.getWeight(), 0); + double lossRate = (Math.min(effectiveWeight, 64) / 100.0) + 0.64; + + if (client.getVar(Varbits.RUN_SLOWED_DEPLETION_ACTIVE) != 0) + { + lossRate *= 0.3; // Stamina effect reduces energy depletion to 30% + } + + // Calculate the number of seconds left + final double secondsLeft = (client.getEnergy() * 0.6) / lossRate; + + // Return the text + if (inSeconds) + { + return (int) Math.floor(secondsLeft) + "s"; + } + else + { + final int minutes = (int) Math.floor(secondsLeft / 60.0); + final int seconds = (int) Math.floor(secondsLeft - (minutes * 60.0)); + + return minutes + ":" + StringUtils.leftPad(Integer.toString(seconds), 2, "0"); + } + } + + int getEstimatedRecoverTimeRemaining() + { + if (localPlayerRunningToDestination) + { + return -1; + } + + // Calculate the amount of energy recovered every second + double recoverRate = (48 + client.getBoostedSkillLevel(Skill.AGILITY)) / 360.0; + + if (Graceful.hasFullSet(client.getItemContainer(InventoryID.EQUIPMENT))) + { + recoverRate *= 1.3; // 30% recover rate increase from Graceful set effect + } + + // Calculate the number of seconds left + final double secondsLeft = (100 - client.getEnergy()) / recoverRate; + return (int) secondsLeft; + } + + /** + * Check player afflictions to determine health icon + */ + private void checkHealthIcon() + { + BufferedImage newHeart; + + int poison = client.getVar(VarPlayer.IS_POISONED); + if (poison >= 1000000) + { + newHeart = HEART_VENOM; + } + else if (poison > 0) + { + newHeart = HEART_POISON; + } + else if (client.getVar(VarPlayer.DISEASE_VALUE) > 0) + { + newHeart = HEART_DISEASE; + } + else + { + heart = null; + resetHealthIcon(); + return; + } + + // Only update sprites when the heart icon actually changes + if (newHeart != heart) + { + heart = newHeart; + client.getWidgetSpriteCache().reset(); + client.getSpriteOverrides().put(SpriteID.MINIMAP_ORB_HITPOINTS_ICON, ImageUtil.getImageSprite(heart, client)); + } + } + + private double runRegenPerTick() + { + double recoverRate = (client.getBoostedSkillLevel(Skill.AGILITY) / 6d + 8) / 100; + + if (Graceful.hasFullSet(client.getItemContainer(InventoryID.EQUIPMENT))) + { + return recoverRate * 1.3; + } + return recoverRate; + } + + /** + * Ensure the HP Heart is the default Sprite + */ + private void resetHealthIcon() + { + client.getWidgetSpriteCache().reset(); + client.getSpriteOverrides().remove(SpriteID.MINIMAP_ORB_HITPOINTS_ICON); + } + + /** + * Migrates configs from runenergy and regenmeter to this plugin and deletes the old config values. + * This method should be removed after a reasonable amount of time. + */ + @Deprecated + private void migrateConfigs() + { + migrateConfig("regenmeter", "showHitpoints"); + migrateConfig("regenmeter", "showSpecial"); + migrateConfig("regenmeter", "showWhenNoChange"); + migrateConfig("regenmeter", "notifyBeforeHpRegenDuration"); + + migrateConfig("runenergy", "replaceOrbText"); + } + + /** + * Wrapper for migrating individual config options + * This method should be removed after a reasonable amount of time. + * + * @param group old group name + * @param key key name to migrate + */ + @Deprecated + private void migrateConfig(String group, String key) + { + String value = configManager.getConfiguration(group, key); + if (value != null) + { + configManager.setConfiguration("statusorbs", key, value); + configManager.unsetConfiguration(group, key); + } + } + + private void updateConfig() + { + this.dynamicHpHeart = config.dynamicHpHeart(); + this.showHitpoints = config.showHitpoints(); + this.showWhenNoChange = config.showWhenNoChange(); + this.getNotifyBeforeHpRegenSeconds = config.getNotifyBeforeHpRegenSeconds(); + this.showSpecial = config.showSpecial(); + this.showRun = config.showRun(); + this.replaceOrbText = config.replaceOrbText(); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/util/Graceful.java b/runelite-client/src/main/java/net/runelite/client/util/Graceful.java new file mode 100644 index 0000000000..4cd05ec128 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/util/Graceful.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018 raiyni + * 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.util; + +import com.google.common.collect.ImmutableSet; +import net.runelite.api.EquipmentInventorySlot; +import net.runelite.api.Item; +import net.runelite.api.ItemContainer; +import static net.runelite.api.ItemID.*; + +public enum Graceful +{ + // TODO: It would be nice if we have the IDs for just the equipped variants of the Graceful set items. + HOOD( + GRACEFUL_HOOD_11851, GRACEFUL_HOOD_13579, GRACEFUL_HOOD_13580, GRACEFUL_HOOD_13591, GRACEFUL_HOOD_13592, + GRACEFUL_HOOD_13603, GRACEFUL_HOOD_13604, GRACEFUL_HOOD_13615, GRACEFUL_HOOD_13616, GRACEFUL_HOOD_13627, + GRACEFUL_HOOD_13628, GRACEFUL_HOOD_13667, GRACEFUL_HOOD_13668, GRACEFUL_HOOD_21061, GRACEFUL_HOOD_21063 + ), + + TOP( + GRACEFUL_TOP_11855, GRACEFUL_TOP_13583, GRACEFUL_TOP_13584, GRACEFUL_TOP_13595, GRACEFUL_TOP_13596, + GRACEFUL_TOP_13607, GRACEFUL_TOP_13608, GRACEFUL_TOP_13619, GRACEFUL_TOP_13620, GRACEFUL_TOP_13631, + GRACEFUL_TOP_13632, GRACEFUL_TOP_13671, GRACEFUL_TOP_13672, GRACEFUL_TOP_21067, GRACEFUL_TOP_21069 + ), + + LEGS( + GRACEFUL_LEGS_11857, GRACEFUL_LEGS_13585, GRACEFUL_LEGS_13586, GRACEFUL_LEGS_13597, GRACEFUL_LEGS_13598, + GRACEFUL_LEGS_13609, GRACEFUL_LEGS_13610, GRACEFUL_LEGS_13621, GRACEFUL_LEGS_13622, GRACEFUL_LEGS_13633, + GRACEFUL_LEGS_13634, GRACEFUL_LEGS_13673, GRACEFUL_LEGS_13674, GRACEFUL_LEGS_21070, GRACEFUL_LEGS_21072 + ), + + GLOVES( + GRACEFUL_GLOVES_11859, GRACEFUL_GLOVES_13587, GRACEFUL_GLOVES_13588, GRACEFUL_GLOVES_13599, GRACEFUL_GLOVES_13600, + GRACEFUL_GLOVES_13611, GRACEFUL_GLOVES_13612, GRACEFUL_GLOVES_13623, GRACEFUL_GLOVES_13624, GRACEFUL_GLOVES_13635, + GRACEFUL_GLOVES_13636, GRACEFUL_GLOVES_13675, GRACEFUL_GLOVES_13676, GRACEFUL_GLOVES_21073, GRACEFUL_GLOVES_21075 + ), + + BOOTS( + GRACEFUL_BOOTS_11861, GRACEFUL_BOOTS_13589, GRACEFUL_BOOTS_13590, GRACEFUL_BOOTS_13601, GRACEFUL_BOOTS_13602, + GRACEFUL_BOOTS_13613, GRACEFUL_BOOTS_13614, GRACEFUL_BOOTS_13625, GRACEFUL_BOOTS_13626, GRACEFUL_BOOTS_13637, + GRACEFUL_BOOTS_13638, GRACEFUL_BOOTS_13677, GRACEFUL_BOOTS_13678, GRACEFUL_BOOTS_21076, GRACEFUL_BOOTS_21078 + ), + + // Agility skill capes and the non-cosmetic Max capes also count for the Graceful set effect + CAPE( + GRACEFUL_CAPE_11853, GRACEFUL_CAPE_13581, GRACEFUL_CAPE_13582, GRACEFUL_CAPE_13593, GRACEFUL_CAPE_13594, + GRACEFUL_CAPE_13605, GRACEFUL_CAPE_13606, GRACEFUL_CAPE_13617, GRACEFUL_CAPE_13618, GRACEFUL_CAPE_13629, + GRACEFUL_CAPE_13630, GRACEFUL_CAPE_13669, GRACEFUL_CAPE_13670, GRACEFUL_CAPE_21064, GRACEFUL_CAPE_21066, + AGILITY_CAPE, AGILITY_CAPET, MAX_CAPE + ); + + private final ImmutableSet ids; + + Graceful(Integer... ids) + { + this.ids = ImmutableSet.copyOf(ids); + } + + public static boolean hasFullSet(final ItemContainer equipment) + { + if (equipment == null) + { + return false; + } + + final Item[] items = equipment.getItems(); + + if (equipment == null || items.length <= EquipmentInventorySlot.BOOTS.getSlotIdx()) + { + return false; + } + + return HOOD.ids.contains(items[EquipmentInventorySlot.HEAD.getSlotIdx()].getId()) + && TOP.ids.contains(items[EquipmentInventorySlot.BODY.getSlotIdx()].getId()) + && LEGS.ids.contains(items[EquipmentInventorySlot.LEGS.getSlotIdx()].getId()) + && GLOVES.ids.contains(items[EquipmentInventorySlot.GLOVES.getSlotIdx()].getId()) + && BOOTS.ids.contains(items[EquipmentInventorySlot.BOOTS.getSlotIdx()].getId()) + && CAPE.ids.contains(items[EquipmentInventorySlot.CAPE.getSlotIdx()].getId()); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/skillcalculator/banked.png b/runelite-client/src/main/resources/net/runelite/client/plugins/skillcalculator/banked.png new file mode 100644 index 0000000000..5256088e5f Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/skillcalculator/banked.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/statusorbs/1067-DISEASE.png b/runelite-client/src/main/resources/net/runelite/client/plugins/statusorbs/1067-DISEASE.png new file mode 100644 index 0000000000..f2ff5ac911 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/statusorbs/1067-DISEASE.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/statusorbs/1067-POISON.png b/runelite-client/src/main/resources/net/runelite/client/plugins/statusorbs/1067-POISON.png new file mode 100644 index 0000000000..a4b05c1ca1 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/statusorbs/1067-POISON.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/statusorbs/1067-VENOM.png b/runelite-client/src/main/resources/net/runelite/client/plugins/statusorbs/1067-VENOM.png new file mode 100644 index 0000000000..df6816bb74 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/statusorbs/1067-VENOM.png differ