From c8105908c05452187b75f060e030fbdbed4d3368 Mon Sep 17 00:00:00 2001 From: Sam Beresford Date: Mon, 17 Dec 2018 10:45:02 +0100 Subject: [PATCH] Add item overlay to show when monsters are weak enough to finish off (#6710) Current behaviour shows the required item to finish an NPC off above it's head when it's health is below the required threshold. Works for Rockslugs, Gargoyles, Desert Lizards and Zygomites Fixes: #784 --- .../client/plugins/slayer/SlayerConfig.java | 11 ++ .../client/plugins/slayer/SlayerPlugin.java | 8 +- .../plugins/slayer/TargetWeaknessOverlay.java | 139 ++++++++++++++++++ .../runelite/client/plugins/slayer/Task.java | 24 ++- 4 files changed, 176 insertions(+), 6 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetWeaknessOverlay.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerConfig.java index 6ade381617..28658ebf16 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerConfig.java @@ -99,6 +99,17 @@ public interface SlayerConfig extends Config return Color.RED; } + @ConfigItem( + position = 7, + keyName = "weaknessPrompt", + name = "Show Monster Weakness", + description = "Show an overlay on a monster when it is weak enough to finish off (Only Lizards, Gargoyles & Rockslugs)" + ) + default boolean weaknessPrompt() + { + return true; + } + // Stored data @ConfigItem( keyName = "taskName", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java index f14bf4b034..538a6e5302 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java @@ -138,6 +138,9 @@ public class SlayerPlugin extends Plugin @Inject private TargetClickboxOverlay targetClickboxOverlay; + @Inject + private TargetWeaknessOverlay targetWeaknessOverlay; + @Inject private TargetMinimapOverlay targetMinimapOverlay; @@ -181,6 +184,7 @@ public class SlayerPlugin extends Plugin { overlayManager.add(overlay); overlayManager.add(targetClickboxOverlay); + overlayManager.add(targetWeaknessOverlay); overlayManager.add(targetMinimapOverlay); if (client.getGameState() == GameState.LOGGED_IN @@ -200,6 +204,7 @@ public class SlayerPlugin extends Plugin { overlayManager.remove(overlay); overlayManager.remove(targetClickboxOverlay); + overlayManager.remove(targetWeaknessOverlay); overlayManager.remove(targetMinimapOverlay); removeCounter(); highlightedTargets.clear(); @@ -557,7 +562,8 @@ public class SlayerPlugin extends Plugin if (name.contains(target)) { NPCComposition composition = npc.getTransformedComposition(); - if (composition != null && Arrays.asList(composition.getActions()).contains("Attack")) + List actions = Arrays.asList(composition.getActions()); + if (composition != null && (actions.contains("Attack") || actions.contains("Pick"))) //Pick action is for zygomite-fungi { return true; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetWeaknessOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetWeaknessOverlay.java new file mode 100644 index 0000000000..af61ddfd13 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetWeaknessOverlay.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2018, Sam "Berry" Beresford + * 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.slayer; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.util.List; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.NPC; +import net.runelite.api.Perspective; +import net.runelite.api.Point; +import net.runelite.api.coords.LocalPoint; +import net.runelite.client.game.ItemManager; +import net.runelite.client.game.NPCManager; +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.OverlayUtil; +import net.runelite.client.util.Text; + +class TargetWeaknessOverlay extends Overlay +{ + private final Client client; + private final SlayerConfig config; + private final SlayerPlugin plugin; + private final ItemManager itemManager; + private final NPCManager npcManager; + + @Inject + private TargetWeaknessOverlay(Client client, SlayerConfig config, SlayerPlugin plugin, ItemManager itemManager, NPCManager npcManager) + { + this.client = client; + this.config = config; + this.plugin = plugin; + this.itemManager = itemManager; + this.npcManager = npcManager; + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_SCENE); + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!config.weaknessPrompt()) + { + return null; + } + + final Task curTask = Task.getTask(plugin.getTaskName()); + if (curTask == null || curTask.getWeaknessThreshold() < 0 || curTask.getWeaknessItem() < 0) + { + return null; + } + + final int threshold = curTask.getWeaknessThreshold(); + final BufferedImage image = itemManager.getImage(curTask.getWeaknessItem()); + + if (image == null) + { + return null; + } + + final List targets = plugin.getHighlightedTargets(); + for (NPC target : targets) + { + final int currentHealth = calculateHealth(target); + + if (currentHealth >= 0 && currentHealth <= threshold) + { + renderTargetItem(graphics, target, image); + } + } + + return null; + } + + private int calculateHealth(NPC target) + { + // Based on OpponentInfoOverlay HP calculation + if (target == null || target.getName() == null) + { + return -1; + } + + final int healthScale = target.getHealth(); + final int healthRatio = target.getHealthRatio(); + final String targetName = Text.removeTags(target.getName()); + final Integer maxHealth = npcManager.getHealth(targetName, target.getCombatLevel()); + + if (healthRatio < 0 || healthScale <= 0 || maxHealth == null) + { + return -1; + } + + return (int)((maxHealth * healthRatio / healthScale) + 0.5f); + } + + private void renderTargetItem(Graphics2D graphics, NPC actor, BufferedImage image) + { + final LocalPoint actorPosition = actor.getLocalLocation(); + final int offset = actor.getLogicalHeight() + 40; + + if (actorPosition == null || image == null) + { + return; + } + + final Point imageLoc = Perspective.getCanvasImageLocation(client, actorPosition, image, offset); + + if (imageLoc != null) + { + OverlayUtil.renderImageLocation(graphics, imageLoc, image); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java index 39239517e6..91e7bdd9e1 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java @@ -75,7 +75,7 @@ enum Task DARK_BEASTS("Dark beasts", ItemID.DARK_BEAST, "Night beast"), DARK_WARRIORS("Dark warriors", ItemID.BLACK_MED_HELM, "Dark warrior"), DERANGED_ARCHAEOLOGIST("Deranged Archaeologist", ItemID.ARCHAEOLOGISTS_DIARY), - DESERT_LIZARDS("Desert lizards", ItemID.DESERT_LIZARD, "Small lizard", "Lizard"), + DESERT_LIZARDS("Desert lizards", ItemID.DESERT_LIZARD, 4, ItemID.ICE_COOLER, "Small lizard", "Lizard"), DOGS("Dogs", ItemID.GUARD_DOG, "Jackal"), DUST_DEVILS("Dust devils", ItemID.DUST_DEVIL, "Choke devil"), DWARVES("Dwarves", ItemID.DWARVEN_HELMET, "Dwarf"), @@ -86,7 +86,7 @@ enum Task REVENANTS("Revenants", ItemID.REVENANT_ETHER, "Revenant imp", "Revenant goblin", "Revenant pyrefiend", "Revenant hobgoblin", "Revenant cyclops", "Revenant hellhound", "Revenant demon", "Revenant ork", "Revenant dark beast", "Revenant knight", "Revenant dragon"), FLESH_CRAWLERS("Flesh crawlers", ItemID.ENSOULED_SCORPION_HEAD), FOSSIL_ISLAND_WYVERNS("Fossil island wyverns", ItemID.FOSSIL_ISLAND_WYVERN, "Ancient wyvern", "Long-tailed wyvern", "Spitting wyvern", "Taloned wyvern"), - GARGOYLES("Gargoyles", ItemID.GARGOYLE), + GARGOYLES("Gargoyles", ItemID.GARGOYLE, 9, ItemID.ROCK_HAMMER), GENERAL_GRAARDOR("General Graardor", ItemID.PET_GENERAL_GRAARDOR), GHOSTS("Ghosts", ItemID.GHOSTSPEAK_AMULET, "Tortured soul"), GIANT_MOLE("Giant Mole", ItemID.BABY_MOLE), @@ -94,7 +94,7 @@ enum Task GOBLINS("Goblins", ItemID.ENSOULED_GOBLIN_HEAD), GREATER_DEMONS("Greater demons", ItemID.GREATER_DEMON_MASK), GREEN_DRAGONS("Green dragons", ItemID.GREEN_DRAGON_MASK), - GROTESQUE_GUARDIANS("Grotesque Guardians", ItemID.MIDNIGHT), + GROTESQUE_GUARDIANS("Grotesque Guardians", ItemID.MIDNIGHT, 0, ItemID.ROCK_HAMMER, "Dusk", "Dawn"), HARPIE_BUG_SWARMS("Harpie bug swarms", ItemID.SWARM), HELLHOUNDS("Hellhounds", ItemID.HELLHOUND), HILL_GIANTS("Hill giants", ItemID.ENSOULED_GIANT_HEAD), @@ -126,14 +126,14 @@ enum Task MOLANISKS("Molanisks", ItemID.MOLANISK), MONKEYS("Monkeys", ItemID.ENSOULED_MONKEY_HEAD), MOSS_GIANTS("Moss giants", ItemID.HILL_GIANT_CLUB), - MUTATED_ZYGOMITES("Mutated zygomites", ItemID.MUTATED_ZYGOMITE), + MUTATED_ZYGOMITES("Mutated zygomites", ItemID.MUTATED_ZYGOMITE, 0, ItemID.FUNGICIDE_SPRAY_0, "Zygomite", "Fungi"), NECHRYAEL("Nechryael", ItemID.NECHRYAEL, "Nechryarch"), OGRES("Ogres", ItemID.ENSOULED_OGRE_HEAD), OTHERWORLDLY_BEING("Otherworldly beings", ItemID.GHOSTLY_HOOD), PYREFIENDS("Pyrefiends", ItemID.PYREFIEND, "Flaming pyrelord"), RATS("Rats", ItemID.RATS_TAIL), RED_DRAGONS("Red dragons", ItemID.BABY_RED_DRAGON), - ROCKSLUGS("Rockslugs", ItemID.ROCKSLUG), + ROCKSLUGS("Rockslugs", ItemID.ROCKSLUG, 4, ItemID.BAG_OF_SALT), RUNE_DRAGONS("Rune dragons", ItemID.RUNITE_BAR), SCORPIA("Scorpia", ItemID.SCORPIAS_OFFSPRING), CHAOS_DRUIDS("Chaos druids", ItemID.ELDER_CHAOS_HOOD, "Elder Chaos druid", "Chaos druid"), @@ -174,6 +174,8 @@ enum Task private final String name; private final int itemSpriteId; private final String[] targetNames; + private final int weaknessThreshold; + private final int weaknessItem; static { @@ -188,6 +190,18 @@ enum Task Preconditions.checkArgument(itemSpriteId >= 0); this.name = name; this.itemSpriteId = itemSpriteId; + this.weaknessThreshold = -1; + this.weaknessItem = -1; + this.targetNames = targetNames; + } + + Task(String name, int itemSpriteId, int weaknessThreshold, int weaknessItem, String... targetNames) + { + Preconditions.checkArgument(itemSpriteId >= 0); + this.name = name; + this.itemSpriteId = itemSpriteId; + this.weaknessThreshold = weaknessThreshold; + this.weaknessItem = weaknessItem; this.targetNames = targetNames; }