From 1994de7270a05ea276643661c780b84d630a1c2a Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 3 Feb 2019 16:56:19 -0500 Subject: [PATCH] client: add poison plugin to track poison damage Co-authored-by: Hydrox6 --- .../main/java/net/runelite/api/VarPlayer.java | 8 + .../client/plugins/poison/PoisonConfig.java | 45 ++++ .../client/plugins/poison/PoisonInfobox.java | 54 ++++ .../client/plugins/poison/PoisonOverlay.java | 88 ++++++ .../client/plugins/poison/PoisonPlugin.java | 253 ++++++++++++++++++ 5 files changed, 448 insertions(+) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonInfobox.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonPlugin.java diff --git a/runelite-api/src/main/java/net/runelite/api/VarPlayer.java b/runelite-api/src/main/java/net/runelite/api/VarPlayer.java index c358d95da8..37b71aaf66 100644 --- a/runelite-api/src/main/java/net/runelite/api/VarPlayer.java +++ b/runelite-api/src/main/java/net/runelite/api/VarPlayer.java @@ -49,6 +49,14 @@ public enum VarPlayer NMZ_REWARD_POINTS(1060), + /** + * -1 : Poison immune + * Normal poison damage is ceil( this / 5.0f ) + * If this is greater than or equal to 1000000, the player is envenomed. + * Venom damage is (this - 999997) * 2 + */ + POISON(102), + /** * 0 : not started * greater than 0 : in progress diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonConfig.java new file mode 100644 index 0000000000..2595009205 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonConfig.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018 Hydrox6 + * 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.poison; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup(PoisonConfig.GROUP) +public interface PoisonConfig extends Config +{ + String GROUP = "poison"; + + @ConfigItem( + keyName = "showInfoboxes", + name = "Show Infoboxes", + description = "Configures whether to show the infoboxes" + ) + default boolean showInfoboxes() + { + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonInfobox.java b/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonInfobox.java new file mode 100644 index 0000000000..44d7746781 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonInfobox.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Hydrox6 + * 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.poison; + +import java.awt.Color; +import java.time.temporal.ChronoUnit; +import java.awt.image.BufferedImage; +import net.runelite.client.ui.overlay.infobox.Timer; + +class PoisonInfobox extends Timer +{ + private final PoisonPlugin plugin; + + PoisonInfobox(BufferedImage image, PoisonPlugin plugin) + { + super(PoisonPlugin.POISON_TICK_MILLIS, ChronoUnit.MILLIS, image, plugin); + this.plugin = plugin; + } + + @Override + public String getTooltip() + { + return plugin.createTooltip(); + } + + @Override + public Color getTextColor() + { + return Color.RED.brighter(); + } +} + diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonOverlay.java new file mode 100644 index 0000000000..758811f292 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonOverlay.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018 Hydrox6 + * 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.poison; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import javax.inject.Inject; +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; + +class PoisonOverlay extends Overlay +{ + private final PoisonPlugin plugin; + private final Client client; + private final TooltipManager tooltipManager; + + @Inject + private PoisonOverlay(final PoisonPlugin plugin, final Client client, final TooltipManager tooltipManager) + { + this.plugin = plugin; + this.client = client; + this.tooltipManager = tooltipManager; + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (plugin.getLastDamage() <= 0) + { + return null; + } + + final Widget healthOrb = client.getWidget(WidgetInfo.MINIMAP_HEALTH_ORB); + + if (healthOrb == null || healthOrb.isHidden()) + { + return null; + } + + final Rectangle bounds = healthOrb.getBounds(); + + if (bounds.getX() <= 0) + { + return null; + } + + final Point mousePosition = client.getMouseCanvasPosition(); + + if (bounds.contains(mousePosition.getX(), mousePosition.getY())) + { + tooltipManager.add(new Tooltip(plugin.createTooltip())); + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonPlugin.java new file mode 100644 index 0000000000..68b9889a2a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonPlugin.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2018 Hydrox6 + * 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.poison; + +import com.google.inject.Provides; +import java.awt.Color; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.text.MessageFormat; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import javax.inject.Inject; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.SpriteID; +import net.runelite.api.VarPlayer; +import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.VarbitChanged; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.SpriteManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; +import net.runelite.client.util.ColorUtil; + +@PluginDescriptor( + name = "Poison", + description = "Tracks current damage values for Poison and Venom", + tags = {"combat", "poison", "venom"} +) +@Slf4j +public class PoisonPlugin extends Plugin +{ + static final int POISON_TICK_MILLIS = 18200; + private static final int VENOM_THRESHOLD = 1000000; + private static final int VENOM_MAXIUMUM_DAMAGE = 20; + + @Inject + private Client client; + + @Inject + private PoisonOverlay poisonOverlay; + + @Inject + private OverlayManager overlayManager; + + @Inject + private InfoBoxManager infoBoxManager; + + @Inject + private SpriteManager spriteManager; + + @Inject + private PoisonConfig config; + + @Getter + private int lastDamage; + private boolean envenomed; + private PoisonInfobox infobox; + private Instant poisonNaturalCure; + private Instant nextPoisonTick; + private int lastValue = -1; + + @Provides + PoisonConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(PoisonConfig.class); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(poisonOverlay); + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(poisonOverlay); + + if (infobox != null) + { + infoBoxManager.removeInfoBox(infobox); + infobox = null; + } + + envenomed = false; + lastDamage = 0; + poisonNaturalCure = null; + nextPoisonTick = null; + lastValue = -1; + + } + + @Subscribe + public void onVarbitChanged(VarbitChanged event) + { + final int poisonValue = client.getVar(VarPlayer.POISON); + if (poisonValue == lastValue) + { + return; + } + + lastValue = poisonValue; + nextPoisonTick = Instant.now().plus(Duration.of(POISON_TICK_MILLIS, ChronoUnit.MILLIS)); + + final int damage = nextDamage(poisonValue); + this.lastDamage = damage; + + envenomed = poisonValue >= VENOM_THRESHOLD; + + if (poisonValue < VENOM_THRESHOLD) + { + poisonNaturalCure = Instant.now().plus(Duration.of(POISON_TICK_MILLIS * poisonValue, ChronoUnit.MILLIS)); + } + else + { + poisonNaturalCure = null; + } + + if (config.showInfoboxes()) + { + if (infobox != null) + { + infoBoxManager.removeInfoBox(infobox); + infobox = null; + } + + if (damage > 0) + { + final BufferedImage image = getSplat(envenomed ? SpriteID.HITSPLAT_DARK_GREEN_VENOM : SpriteID.HITSPLAT_GREEN_POISON, damage); + + if (image != null) + { + infobox = new PoisonInfobox(image, this); + infoBoxManager.addInfoBox(infobox); + } + } + } + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals(PoisonConfig.GROUP) && !config.showInfoboxes() && infobox != null) + { + infoBoxManager.removeInfoBox(infobox); + infobox = null; + } + } + + private static int nextDamage(int poisonValue) + { + int damage; + + if (poisonValue >= VENOM_THRESHOLD) + { + //Venom Damage starts at 6, and increments in twos; + //The VarPlayer increments in values of 1, however. + poisonValue -= VENOM_THRESHOLD - 3; + damage = poisonValue * 2; + //Venom Damage caps at 20, but the VarPlayer keeps increasing + if (damage > VENOM_MAXIUMUM_DAMAGE) + { + damage = VENOM_MAXIUMUM_DAMAGE; + } + } + else + { + damage = (int) Math.ceil(poisonValue / 5.0f); + } + + return damage; + } + + private BufferedImage getSplat(int id, int damage) + { + //Get a copy of the hitsplat to get a clean one each time + final BufferedImage rawSplat = spriteManager.getSprite(id, 0); + if (rawSplat == null) + { + return null; + } + + final BufferedImage splat = new BufferedImage( + rawSplat.getColorModel(), + rawSplat.copyData(null), + rawSplat.getColorModel().isAlphaPremultiplied(), + null); + + final Graphics g = splat.getGraphics(); + g.setFont(FontManager.getRunescapeSmallFont()); + + // Align the text in the centre of the hitsplat + final FontMetrics metrics = g.getFontMetrics(); + final String text = String.valueOf(damage); + final int x = (splat.getWidth() - metrics.stringWidth(text)) / 2; + final int y = (splat.getHeight() - metrics.getHeight()) / 2 + metrics.getAscent(); + + g.setColor(Color.BLACK); + g.drawString(String.valueOf(damage), x + 1, y + 1); + g.setColor(Color.WHITE); + g.drawString(String.valueOf(damage), x, y); + return splat; + } + + private static String getFormattedTime(Instant endTime) + { + final Duration timeLeft = Duration.between(Instant.now(), endTime); + int seconds = (int) (timeLeft.toMillis() / 1000L); + int minutes = seconds / 60; + int secs = seconds % 60; + + return String.format("%d:%02d", minutes, secs); + } + + String createTooltip() + { + String line1 = MessageFormat.format("Next {0} damage: {1}
Time until damage: {2}", + envenomed ? "venom" : "poison", ColorUtil.wrapWithColorTag(String.valueOf(lastDamage), Color.RED), getFormattedTime(nextPoisonTick)); + String line2 = envenomed ? "" : MessageFormat.format("
Time until cure: {0}", getFormattedTime(poisonNaturalCure)); + + return line1 + line2; + } +} \ No newline at end of file