diff --git a/runelite-api/src/main/java/net/runelite/api/NPC.java b/runelite-api/src/main/java/net/runelite/api/NPC.java index d57f339acb..b42fdc5a66 100644 --- a/runelite-api/src/main/java/net/runelite/api/NPC.java +++ b/runelite-api/src/main/java/net/runelite/api/NPC.java @@ -67,7 +67,7 @@ public interface NPC extends Actor * @return the transformed NPC */ @Nullable - NPCComposition getTransformedDefinition(); + NPCComposition getTransformedComposition(); void onDefinitionChanged(NPCComposition composition); } 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 new file mode 100644 index 0000000000..3abe33b392 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerConfig.java @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2017, Seth + * Copyright (c) 2018, Shaun Dreclin + * 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.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Units; + +@ConfigGroup("slayer") +public interface SlayerConfig extends Config +{ + @ConfigItem( + position = 1, + keyName = "infobox", + name = "Task InfoBox", + description = "Display task information in an InfoBox" + ) + default boolean showInfobox() + { + return true; + } + + @ConfigItem( + position = 2, + keyName = "itemoverlay", + name = "Count on Items", + description = "Display task count remaining on slayer items" + ) + default boolean showItemOverlay() + { + return true; + } + + @ConfigItem( + position = 3, + keyName = "superiornotification", + name = "Superior foe notification", + description = "Toggles notifications on superior foe encounters" + ) + default boolean showSuperiorNotification() + { + return true; + } + + @ConfigItem( + position = 4, + keyName = "statTimeout", + name = "InfoBox Expiry", + description = "Set the time until the InfoBox expires" + ) + @Units(Units.MINUTES) + default int statTimeout() + { + return 5; + } + + @ConfigItem( + position = 5, + keyName = "highlightTargets", + name = "Highlight Targets", + description = "Highlight monsters you can kill for your current slayer assignment" + ) + default boolean highlightTargets() + { + return false; + } + + @ConfigItem( + position = 6, + keyName = "targetColor", + name = "Target Color", + description = "Color of the highlighted targets" + ) + default Color getTargetColor() + { + 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; + } + + @ConfigItem( + position = 8, + keyName = "taskCommand", + name = "Task Command", + description = "Configures whether the slayer task command is enabled
!task" + ) + default boolean taskCommand() + { + return true; + } + + // Stored data + @ConfigItem( + keyName = "taskName", + name = "", + description = "", + hidden = true + ) + default String taskName() + { + return ""; + } + + @ConfigItem( + keyName = "taskName", + name = "", + description = "" + ) + void taskName(String key); + + @ConfigItem( + keyName = "amount", + name = "", + description = "", + hidden = true + ) + default int amount() + { + return -1; + } + + @ConfigItem( + keyName = "amount", + name = "", + description = "" + ) + void amount(int amt); + + @ConfigItem( + keyName = "initialAmount", + name = "", + description = "", + hidden = true + ) + default int initialAmount() + { + return -1; + } + @ConfigItem( + keyName = "initialAmount", + name = "", + description = "" + ) + void initialAmount(int initialAmount); + + @ConfigItem( + keyName = "taskLocation", + name = "", + description = "", + hidden = true + ) + default String taskLocation() + { + return ""; + } + + @ConfigItem( + keyName = "taskLocation", + name = "", + description = "" + ) + void taskLocation(String key); + + @ConfigItem( + keyName = "streak", + name = "", + description = "", + hidden = true + ) + default int streak() + { + return -1; + } + + @ConfigItem( + keyName = "streak", + name = "", + description = "" + ) + void streak(int streak); + + @ConfigItem( + keyName = "points", + name = "", + description = "", + hidden = true + ) + default int points() + { + return -1; + } + + @ConfigItem( + keyName = "points", + name = "", + description = "" + ) + void points(int points); + + @ConfigItem( + keyName = "expeditious", + name = "", + description = "", + hidden = true + ) + default int expeditious() + { + return -1; + } + + @ConfigItem( + keyName = "expeditious", + name = "", + description = "" + ) + void expeditious(int expeditious); + + @ConfigItem( + keyName = "slaughter", + name = "", + description = "", + hidden = true + ) + default int slaughter() + { + return -1; + } + + @ConfigItem( + keyName = "slaughter", + name = "", + description = "" + ) + void slaughter(int slaughter); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerOverlay.java new file mode 100644 index 0000000000..4d70d82f53 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerOverlay.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2017, Seth + * 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 com.google.common.collect.ImmutableSet; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.util.Set; +import javax.inject.Inject; +import net.runelite.api.ItemID; +import net.runelite.api.widgets.WidgetItem; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.overlay.WidgetItemOverlay; +import net.runelite.client.ui.overlay.components.TextComponent; + +class SlayerOverlay extends WidgetItemOverlay +{ + private final static Set SLAYER_JEWELRY = ImmutableSet.of( + ItemID.SLAYER_RING_1, + ItemID.SLAYER_RING_2, + ItemID.SLAYER_RING_3, + ItemID.SLAYER_RING_4, + ItemID.SLAYER_RING_5, + ItemID.SLAYER_RING_6, + ItemID.SLAYER_RING_7, + ItemID.SLAYER_RING_8 + ); + + private final static Set ALL_SLAYER_ITEMS = ImmutableSet.of( + ItemID.SLAYER_HELMET, + ItemID.SLAYER_HELMET_I, + ItemID.BLACK_SLAYER_HELMET, + ItemID.BLACK_SLAYER_HELMET_I, + ItemID.GREEN_SLAYER_HELMET, + ItemID.GREEN_SLAYER_HELMET_I, + ItemID.PURPLE_SLAYER_HELMET, + ItemID.PURPLE_SLAYER_HELMET_I, + ItemID.RED_SLAYER_HELMET, + ItemID.RED_SLAYER_HELMET_I, + ItemID.TURQUOISE_SLAYER_HELMET, + ItemID.TURQUOISE_SLAYER_HELMET_I, + ItemID.TWISTED_SLAYER_HELMET, + ItemID.TWISTED_SLAYER_HELMET_I, + ItemID.HYDRA_SLAYER_HELMET, + ItemID.HYDRA_SLAYER_HELMET_I, + ItemID.SLAYER_RING_ETERNAL, + ItemID.ENCHANTED_GEM, + ItemID.ETERNAL_GEM, + ItemID.BRACELET_OF_SLAUGHTER, + ItemID.EXPEDITIOUS_BRACELET, + ItemID.SLAYER_RING_1, + ItemID.SLAYER_RING_2, + ItemID.SLAYER_RING_3, + ItemID.SLAYER_RING_4, + ItemID.SLAYER_RING_5, + ItemID.SLAYER_RING_6, + ItemID.SLAYER_RING_7, + ItemID.SLAYER_RING_8 + ); + + private final SlayerConfig config; + private final SlayerPlugin plugin; + + @Inject + private SlayerOverlay(SlayerPlugin plugin, SlayerConfig config) + { + this.plugin = plugin; + this.config = config; + showOnInventory(); + showOnEquipment(); + } + + @Override + public void renderItemOverlay(Graphics2D graphics, int itemId, WidgetItem itemWidget) + { + if (!ALL_SLAYER_ITEMS.contains(itemId)) + { + return; + } + + if (!config.showItemOverlay()) + { + return; + } + + int amount = plugin.getAmount(); + if (amount <= 0) + { + return; + } + + int slaughterCount = plugin.getSlaughterChargeCount(); + int expeditiousCount = plugin.getExpeditiousChargeCount(); + + graphics.setFont(FontManager.getRunescapeSmallFont()); + + final Rectangle bounds = itemWidget.getCanvasBounds(); + final TextComponent textComponent = new TextComponent(); + + switch (itemId) + { + case ItemID.EXPEDITIOUS_BRACELET: + textComponent.setText(String.valueOf(expeditiousCount)); + break; + case ItemID.BRACELET_OF_SLAUGHTER: + textComponent.setText(String.valueOf(slaughterCount)); + break; + default: + textComponent.setText(String.valueOf(amount)); + break; + } + + // Draw the counter in the bottom left for equipment, and top left for jewelry + textComponent.setPosition(new Point(bounds.x - 1, bounds.y - 1 + (SLAYER_JEWELRY.contains(itemId) + ? bounds.height + : graphics.getFontMetrics().getHeight()))); + textComponent.render(graphics); + } +} 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 new file mode 100644 index 0000000000..72ad3524ee --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java @@ -0,0 +1,903 @@ +/* + * Copyright (c) 2017, Tyler + * Copyright (c) 2018, Shaun Dreclin + * 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 com.google.common.annotations.VisibleForTesting; +import com.google.inject.Provides; +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.IOException; +import static java.lang.Integer.max; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.inject.Inject; +import joptsimple.internal.Strings; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Actor; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.Hitsplat; +import net.runelite.api.ItemID; +import net.runelite.api.MessageNode; +import net.runelite.api.NPC; +import net.runelite.api.NPCComposition; +import static net.runelite.api.Skill.SLAYER; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ActorDeath; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.HitsplatApplied; +import net.runelite.api.events.NpcDespawned; +import net.runelite.api.events.NpcSpawned; +import net.runelite.api.events.StatChanged; +import net.runelite.api.vars.SlayerUnlock; +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.chat.ChatColorType; +import net.runelite.client.chat.ChatCommandManager; +import net.runelite.client.chat.ChatMessageBuilder; +import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ChatInput; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; +import net.runelite.client.util.ColorUtil; +import net.runelite.client.util.Text; +import net.runelite.http.api.chat.ChatClient; + +@PluginDescriptor( + name = "Slayer", + description = "Show additional slayer task related information", + tags = {"combat", "notifications", "overlay", "tasks"} +) +@Slf4j +public class SlayerPlugin extends Plugin +{ + //Chat messages + private static final Pattern CHAT_GEM_PROGRESS_MESSAGE = Pattern.compile("^(?:You're assigned to kill|You have received a new Slayer assignment from .*:) (?:[Tt]he )?(?.+?)(?: (?:in|on|south of) (?:the )?(?[^;]+))?(?:; only | \\()(?\\d+)(?: more to go\\.|\\))$"); + private static final String CHAT_GEM_COMPLETE_MESSAGE = "You need something new to hunt."; + private static final Pattern CHAT_COMPLETE_MESSAGE = Pattern.compile("(?:\\d+,)*\\d+"); + private static final String CHAT_CANCEL_MESSAGE = "Your task has been cancelled."; + private static final String CHAT_CANCEL_MESSAGE_JAD = "You no longer have a slayer task as you left the fight cave."; + private static final String CHAT_CANCEL_MESSAGE_ZUK = "You no longer have a slayer task as you left the Inferno."; + private static final String CHAT_SUPERIOR_MESSAGE = "A superior foe has appeared..."; + private static final String CHAT_BRACELET_SLAUGHTER = "Your bracelet of slaughter prevents your slayer"; + private static final Pattern CHAT_BRACELET_SLAUGHTER_REGEX = Pattern.compile("Your bracelet of slaughter prevents your slayer count from decreasing. It has (\\d{1,2}) charges? left\\."); + private static final String CHAT_BRACELET_EXPEDITIOUS = "Your expeditious bracelet helps you progress your"; + private static final Pattern CHAT_BRACELET_EXPEDITIOUS_REGEX = Pattern.compile("Your expeditious bracelet helps you progress your slayer (?:task )?faster. It has (\\d{1,2}) charges? left\\."); + private static final String CHAT_BRACELET_SLAUGHTER_CHARGE = "Your bracelet of slaughter has "; + private static final Pattern CHAT_BRACELET_SLAUGHTER_CHARGE_REGEX = Pattern.compile("Your bracelet of slaughter has (\\d{1,2}) charges? left\\."); + private static final String CHAT_BRACELET_EXPEDITIOUS_CHARGE = "Your expeditious bracelet has "; + private static final Pattern CHAT_BRACELET_EXPEDITIOUS_CHARGE_REGEX = Pattern.compile("Your expeditious bracelet has (\\d{1,2}) charges? left\\."); + private static final Pattern COMBAT_BRACELET_TASK_UPDATE_MESSAGE = Pattern.compile("^You still need to kill (\\d+) monsters to complete your current Slayer assignment"); + + //NPC messages + private static final Pattern NPC_ASSIGN_MESSAGE = Pattern.compile(".*(?:Your new task is to kill|You are to bring balance to)\\s*(?\\d+) (?.+?)(?: (?:in|on|south of) (?:the )?(?.+))?\\."); + private static final Pattern NPC_ASSIGN_BOSS_MESSAGE = Pattern.compile("^(?:Excellent\\. )?You're now assigned to (?:kill|bring balance to) (?:the )?(.*) (\\d+) times.*Your reward point tally is (.*)\\.$"); + private static final Pattern NPC_ASSIGN_FIRST_MESSAGE = Pattern.compile("^We'll start you off (?:hunting|bringing balance to) (.*), you'll need to kill (\\d*) of them\\.$"); + private static final Pattern NPC_CURRENT_MESSAGE = Pattern.compile("^You're (?:still(?: meant to be)?|currently assigned to) (?:hunting|bringing balance to|kill|bring balance to|slaying) (?.+?)(?: (?:in|on|south of) (?:the )?(?.+))?(?:, with|; (?:you have|only)) (?\\d+)(?: more)? to go\\..*"); + + //Reward UI + private static final Pattern REWARD_POINTS = Pattern.compile("Reward points: ((?:\\d+,)*\\d+)"); + + private static final int GROTESQUE_GUARDIANS_REGION = 6727; + + private static final int EXPEDITIOUS_CHARGE = 30; + private static final int SLAUGHTER_CHARGE = 30; + + // Chat Command + private static final String TASK_COMMAND_STRING = "!task"; + private static final Pattern TASK_STRING_VALIDATION = Pattern.compile("[^a-zA-Z0-9' -]"); + private static final int TASK_STRING_MAX_LENGTH = 50; + + @Inject + private Client client; + + @Inject + private SlayerConfig config; + + @Inject + private OverlayManager overlayManager; + + @Inject + private SlayerOverlay overlay; + + @Inject + private InfoBoxManager infoBoxManager; + + @Inject + private ItemManager itemManager; + + @Inject + private Notifier notifier; + + @Inject + private ClientThread clientThread; + + @Inject + private TargetClickboxOverlay targetClickboxOverlay; + + @Inject + private TargetWeaknessOverlay targetWeaknessOverlay; + + @Inject + private TargetMinimapOverlay targetMinimapOverlay; + + @Inject + private ChatMessageManager chatMessageManager; + + @Inject + private ChatCommandManager chatCommandManager; + + @Inject + private ScheduledExecutorService executor; + + @Inject + private ChatClient chatClient; + + @Getter(AccessLevel.PACKAGE) + private List highlightedTargets = new ArrayList<>(); + + private final Set taggedNpcs = new HashSet<>(); + private int taggedNpcsDiedPrevTick; + private int taggedNpcsDiedThisTick; + + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private int amount; + + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private int initialAmount; + + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private String taskLocation; + + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private int expeditiousChargeCount; + + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private int slaughterChargeCount; + + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) + private String taskName; + + private TaskCounter counter; + private int cachedXp = -1; + private Instant infoTimer; + private boolean loginFlag; + private final List targetNames = new ArrayList<>(); + + @Override + protected void startUp() throws Exception + { + overlayManager.add(overlay); + overlayManager.add(targetClickboxOverlay); + overlayManager.add(targetWeaknessOverlay); + overlayManager.add(targetMinimapOverlay); + + if (client.getGameState() == GameState.LOGGED_IN) + { + cachedXp = client.getSkillExperience(SLAYER); + + if (config.amount() != -1 + && !config.taskName().isEmpty()) + { + setExpeditiousChargeCount(config.expeditious()); + setSlaughterChargeCount(config.slaughter()); + clientThread.invoke(() -> setTask(config.taskName(), config.amount(), config.initialAmount(), config.taskLocation(), false)); + } + } + + chatCommandManager.registerCommandAsync(TASK_COMMAND_STRING, this::taskLookup, this::taskSubmit); + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(overlay); + overlayManager.remove(targetClickboxOverlay); + overlayManager.remove(targetWeaknessOverlay); + overlayManager.remove(targetMinimapOverlay); + removeCounter(); + highlightedTargets.clear(); + taggedNpcs.clear(); + cachedXp = -1; + + chatCommandManager.unregisterCommand(TASK_COMMAND_STRING); + } + + @Provides + SlayerConfig provideSlayerConfig(ConfigManager configManager) + { + return configManager.getConfig(SlayerConfig.class); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + switch (event.getGameState()) + { + case HOPPING: + case LOGGING_IN: + cachedXp = -1; + taskName = ""; + amount = 0; + loginFlag = true; + highlightedTargets.clear(); + taggedNpcs.clear(); + break; + case LOGGED_IN: + if (config.amount() != -1 + && !config.taskName().isEmpty() + && loginFlag) + { + setExpeditiousChargeCount(config.expeditious()); + setSlaughterChargeCount(config.slaughter()); + setTask(config.taskName(), config.amount(), config.initialAmount(), config.taskLocation(), false); + loginFlag = false; + } + break; + } + } + + private void save() + { + config.amount(amount); + config.initialAmount(initialAmount); + config.taskName(taskName); + config.taskLocation(taskLocation); + config.expeditious(expeditiousChargeCount); + config.slaughter(slaughterChargeCount); + } + + @Subscribe + public void onNpcSpawned(NpcSpawned npcSpawned) + { + NPC npc = npcSpawned.getNpc(); + if (isTarget(npc)) + { + highlightedTargets.add(npc); + } + } + + @Subscribe + public void onNpcDespawned(NpcDespawned npcDespawned) + { + NPC npc = npcDespawned.getNpc(); + taggedNpcs.remove(npc); + highlightedTargets.remove(npc); + } + + @Subscribe + public void onGameTick(GameTick tick) + { + Widget npcDialog = client.getWidget(WidgetInfo.DIALOG_NPC_TEXT); + if (npcDialog != null) + { + String npcText = Text.sanitizeMultilineText(npcDialog.getText()); //remove color and linebreaks + final Matcher mAssign = NPC_ASSIGN_MESSAGE.matcher(npcText); // amount, name, (location) + final Matcher mAssignFirst = NPC_ASSIGN_FIRST_MESSAGE.matcher(npcText); // name, number + final Matcher mAssignBoss = NPC_ASSIGN_BOSS_MESSAGE.matcher(npcText); // name, number, points + final Matcher mCurrent = NPC_CURRENT_MESSAGE.matcher(npcText); // name, (location), amount + + if (mAssign.find()) + { + String name = mAssign.group("name"); + int amount = Integer.parseInt(mAssign.group("amount")); + String location = mAssign.group("location"); + setTask(name, amount, amount, location); + } + else if (mAssignFirst.find()) + { + int amount = Integer.parseInt(mAssignFirst.group(2)); + setTask(mAssignFirst.group(1), amount, amount); + } + else if (mAssignBoss.find()) + { + int amount = Integer.parseInt(mAssignBoss.group(2)); + setTask(mAssignBoss.group(1), amount, amount); + int points = Integer.parseInt(mAssignBoss.group(3).replaceAll(",", "")); + config.points(points); + } + else if (mCurrent.find()) + { + String name = mCurrent.group("name"); + int amount = Integer.parseInt(mCurrent.group("amount")); + String location = mCurrent.group("location"); + setTask(name, amount, initialAmount, location); + } + } + + Widget braceletBreakWidget = client.getWidget(WidgetInfo.DIALOG_SPRITE_TEXT); + if (braceletBreakWidget != null) + { + String braceletText = Text.removeTags(braceletBreakWidget.getText()); //remove color and linebreaks + if (braceletText.contains("bracelet of slaughter")) + { + slaughterChargeCount = SLAUGHTER_CHARGE; + config.slaughter(slaughterChargeCount); + } + else if (braceletText.contains("expeditious bracelet")) + { + expeditiousChargeCount = EXPEDITIOUS_CHARGE; + config.expeditious(expeditiousChargeCount); + } + } + + Widget rewardsBarWidget = client.getWidget(WidgetInfo.SLAYER_REWARDS_TOPBAR); + if (rewardsBarWidget != null) + { + for (Widget w : rewardsBarWidget.getDynamicChildren()) + { + Matcher mPoints = REWARD_POINTS.matcher(w.getText()); + if (mPoints.find()) + { + final int prevPoints = config.points(); + int points = Integer.parseInt(mPoints.group(1).replaceAll(",", "")); + + if (prevPoints != points) + { + config.points(points); + removeCounter(); + addCounter(); + } + + break; + } + } + } + + if (infoTimer != null && config.statTimeout() != 0) + { + Duration timeSinceInfobox = Duration.between(infoTimer, Instant.now()); + Duration statTimeout = Duration.ofMinutes(config.statTimeout()); + + if (timeSinceInfobox.compareTo(statTimeout) >= 0) + { + removeCounter(); + } + } + + taggedNpcsDiedPrevTick = taggedNpcsDiedThisTick; + taggedNpcsDiedThisTick = 0; + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + if (event.getType() != ChatMessageType.GAMEMESSAGE && event.getType() != ChatMessageType.SPAM) + { + return; + } + + String chatMsg = Text.removeTags(event.getMessage()); //remove color and linebreaks + + if (chatMsg.startsWith(CHAT_BRACELET_SLAUGHTER)) + { + Matcher mSlaughter = CHAT_BRACELET_SLAUGHTER_REGEX.matcher(chatMsg); + + amount++; + slaughterChargeCount = mSlaughter.find() ? Integer.parseInt(mSlaughter.group(1)) : SLAUGHTER_CHARGE; + config.slaughter(slaughterChargeCount); + } + + if (chatMsg.startsWith(CHAT_BRACELET_EXPEDITIOUS)) + { + Matcher mExpeditious = CHAT_BRACELET_EXPEDITIOUS_REGEX.matcher(chatMsg); + + amount--; + expeditiousChargeCount = mExpeditious.find() ? Integer.parseInt(mExpeditious.group(1)) : EXPEDITIOUS_CHARGE; + config.expeditious(expeditiousChargeCount); + } + + if (chatMsg.startsWith(CHAT_BRACELET_EXPEDITIOUS_CHARGE)) + { + Matcher mExpeditious = CHAT_BRACELET_EXPEDITIOUS_CHARGE_REGEX.matcher(chatMsg); + + if (!mExpeditious.find()) + { + return; + } + + expeditiousChargeCount = Integer.parseInt(mExpeditious.group(1)); + config.expeditious(expeditiousChargeCount); + } + if (chatMsg.startsWith(CHAT_BRACELET_SLAUGHTER_CHARGE)) + { + Matcher mSlaughter = CHAT_BRACELET_SLAUGHTER_CHARGE_REGEX.matcher(chatMsg); + if (!mSlaughter.find()) + { + return; + } + + slaughterChargeCount = Integer.parseInt(mSlaughter.group(1)); + config.slaughter(slaughterChargeCount); + } + + if (chatMsg.startsWith("You've completed") && (chatMsg.contains("Slayer master") || chatMsg.contains("Slayer Master"))) + { + Matcher mComplete = CHAT_COMPLETE_MESSAGE.matcher(chatMsg); + + List matches = new ArrayList<>(); + while (mComplete.find()) + { + matches.add(mComplete.group(0).replaceAll(",", "")); + } + + int streak = -1, points = -1; + switch (matches.size()) + { + case 0: + streak = 1; + break; + case 1: + streak = Integer.parseInt(matches.get(0)); + break; + case 3: + streak = Integer.parseInt(matches.get(0)); + points = Integer.parseInt(matches.get(2)); + break; + default: + log.warn("Unreachable default case for message ending in '; return to Slayer master'"); + } + if (streak != -1) + { + config.streak(streak); + } + if (points != -1) + { + config.points(points); + } + + setTask("", 0, 0); + return; + } + + if (chatMsg.equals(CHAT_GEM_COMPLETE_MESSAGE) || chatMsg.equals(CHAT_CANCEL_MESSAGE) || chatMsg.equals(CHAT_CANCEL_MESSAGE_JAD) || chatMsg.equals(CHAT_CANCEL_MESSAGE_ZUK)) + { + setTask("", 0, 0); + return; + } + + if (config.showSuperiorNotification() && chatMsg.equals(CHAT_SUPERIOR_MESSAGE)) + { + notifier.notify(CHAT_SUPERIOR_MESSAGE); + return; + } + + Matcher mProgress = CHAT_GEM_PROGRESS_MESSAGE.matcher(chatMsg); + + if (mProgress.find()) + { + String name = mProgress.group("name"); + int gemAmount = Integer.parseInt(mProgress.group("amount")); + String location = mProgress.group("location"); + setTask(name, gemAmount, initialAmount, location); + return; + } + + final Matcher bracerProgress = COMBAT_BRACELET_TASK_UPDATE_MESSAGE.matcher(chatMsg); + + if (bracerProgress.find()) + { + final int taskAmount = Integer.parseInt(bracerProgress.group(1)); + setTask(taskName, taskAmount, initialAmount); + + // Avoid race condition (combat brace message goes through first before XP drop) + amount++; + } + } + + @Subscribe + public void onStatChanged(StatChanged statChanged) + { + if (statChanged.getSkill() != SLAYER) + { + return; + } + + int slayerExp = statChanged.getXp(); + + if (slayerExp <= cachedXp) + { + return; + } + + if (cachedXp == -1) + { + // this is the initial xp sent on login + cachedXp = slayerExp; + return; + } + + final int delta = slayerExp - cachedXp; + cachedXp = slayerExp; + + log.debug("Slayer xp change delta: {}, killed npcs: {}", delta, taggedNpcsDiedPrevTick); + + final Task task = Task.getTask(taskName); + if (task != null && task.getExpectedKillExp() > 0) + { + // Only decrement a kill if the xp drop matches the expected drop. This is just for Tzhaar tasks. + if (task.getExpectedKillExp() == delta) + { + killed(1); + } + } + else + { + // This is at least one kill, but if we observe multiple tagged NPCs dieing on the previous tick, count them + // instead. + killed(max(taggedNpcsDiedPrevTick, 1)); + } + } + + @Subscribe + public void onHitsplatApplied(HitsplatApplied hitsplatApplied) + { + Actor actor = hitsplatApplied.getActor(); + Hitsplat hitsplat = hitsplatApplied.getHitsplat(); + if (hitsplat.getHitsplatType() == Hitsplat.HitsplatType.DAMAGE_ME && highlightedTargets.contains(actor)) + { + // If the actor is in highlightedTargets it must be an NPC and also a task assignment + taggedNpcs.add((NPC) actor); + } + } + + @Subscribe + public void onActorDeath(ActorDeath actorDeath) + { + Actor actor = actorDeath.getActor(); + if (taggedNpcs.contains(actor)) + { + log.debug("Tagged NPC {} has died", actor.getName()); + ++taggedNpcsDiedThisTick; + } + } + + @Subscribe + private void onConfigChanged(ConfigChanged event) + { + if (!event.getGroup().equals("slayer") || !event.getKey().equals("infobox")) + { + return; + } + + if (config.showInfobox()) + { + clientThread.invoke(this::addCounter); + } + else + { + removeCounter(); + } + } + + @VisibleForTesting + void killed(int amt) + { + if (amount == 0) + { + return; + } + + amount -= amt; + if (doubleTroubleExtraKill()) + { + assert amt == 1; + amount--; + } + + config.amount(amount); // save changed value + + if (!config.showInfobox()) + { + return; + } + + // add and update counter, set timer + addCounter(); + counter.setCount(amount); + infoTimer = Instant.now(); + } + + private boolean doubleTroubleExtraKill() + { + return WorldPoint.fromLocalInstance(client, client.getLocalPlayer().getLocalLocation()).getRegionID() == GROTESQUE_GUARDIANS_REGION && + SlayerUnlock.GROTESQUE_GUARDIAN_DOUBLE_COUNT.isEnabled(client); + } + + private boolean isTarget(NPC npc) + { + if (targetNames.isEmpty()) + { + return false; + } + + String name = npc.getName(); + if (name == null) + { + return false; + } + + name = name.toLowerCase(); + + for (String target : targetNames) + { + if (name.contains(target)) + { + NPCComposition composition = npc.getTransformedComposition(); + + if (composition != null) + { + List actions = Arrays.asList(composition.getActions()); + if (actions.contains("Attack") || actions.contains("Pick")) //Pick action is for zygomite-fungi + { + return true; + } + } + } + } + return false; + } + + private void rebuildTargetNames(Task task) + { + targetNames.clear(); + + if (task != null) + { + Arrays.stream(task.getTargetNames()) + .map(String::toLowerCase) + .forEach(targetNames::add); + + targetNames.add(taskName.toLowerCase().replaceAll("s$", "")); + } + } + + private void rebuildTargetList() + { + highlightedTargets.clear(); + + for (NPC npc : client.getNpcs()) + { + if (isTarget(npc)) + { + highlightedTargets.add(npc); + } + } + } + + private void setTask(String name, int amt, int initAmt) + { + setTask(name, amt, initAmt, null); + } + + private void setTask(String name, int amt, int initAmt, String location) + { + setTask(name, amt, initAmt, location, true); + } + + private void setTask(String name, int amt, int initAmt, String location, boolean addCounter) + { + taskName = name; + amount = amt; + initialAmount = Math.max(amt, initAmt); + taskLocation = location; + save(); + removeCounter(); + + if (addCounter) + { + infoTimer = Instant.now(); + addCounter(); + } + + Task task = Task.getTask(name); + rebuildTargetNames(task); + rebuildTargetList(); + } + + private void addCounter() + { + if (!config.showInfobox() || counter != null || Strings.isNullOrEmpty(taskName)) + { + return; + } + + Task task = Task.getTask(taskName); + int itemSpriteId = ItemID.ENCHANTED_GEM; + if (task != null) + { + itemSpriteId = task.getItemSpriteId(); + } + + BufferedImage taskImg = itemManager.getImage(itemSpriteId); + String taskTooltip = ColorUtil.wrapWithColorTag("%s", new Color(255, 119, 0)) + "
"; + + if (taskLocation != null && !taskLocation.isEmpty()) + { + taskTooltip += taskLocation + "
"; + } + + taskTooltip += ColorUtil.wrapWithColorTag("Pts:", Color.YELLOW) + + " %s
" + + ColorUtil.wrapWithColorTag("Streak:", Color.YELLOW) + + " %s"; + + if (initialAmount > 0) + { + taskTooltip += "
" + + ColorUtil.wrapWithColorTag("Start:", Color.YELLOW) + + " " + initialAmount; + } + + counter = new TaskCounter(taskImg, this, amount); + counter.setTooltip(String.format(taskTooltip, capsString(taskName), config.points(), config.streak())); + + infoBoxManager.addInfoBox(counter); + } + + private void removeCounter() + { + if (counter == null) + { + return; + } + + infoBoxManager.removeInfoBox(counter); + counter = null; + } + + void taskLookup(ChatMessage chatMessage, String message) + { + if (!config.taskCommand()) + { + return; + } + + ChatMessageType type = chatMessage.getType(); + + final String player; + if (type.equals(ChatMessageType.PRIVATECHATOUT)) + { + player = client.getLocalPlayer().getName(); + } + else + { + player = Text.removeTags(chatMessage.getName()) + .replace('\u00A0', ' '); + } + + net.runelite.http.api.chat.Task task; + try + { + task = chatClient.getTask(player); + } + catch (IOException ex) + { + log.debug("unable to lookup slayer task", ex); + return; + } + + if (TASK_STRING_VALIDATION.matcher(task.getTask()).find() || task.getTask().length() > TASK_STRING_MAX_LENGTH || + TASK_STRING_VALIDATION.matcher(task.getLocation()).find() || task.getLocation().length() > TASK_STRING_MAX_LENGTH || + Task.getTask(task.getTask()) == null || !Task.LOCATIONS.contains(task.getLocation())) + { + log.debug("Validation failed for task name or location: {}", task); + return; + } + + int killed = task.getInitialAmount() - task.getAmount(); + + StringBuilder sb = new StringBuilder(); + sb.append(task.getTask()); + if (!Strings.isNullOrEmpty(task.getLocation())) + { + sb.append(" (").append(task.getLocation()).append(")"); + } + sb.append(": "); + if (killed < 0) + { + sb.append(task.getAmount()).append(" left"); + } + else + { + sb.append(killed).append('/').append(task.getInitialAmount()).append(" killed"); + } + + String response = new ChatMessageBuilder() + .append(ChatColorType.NORMAL) + .append("Slayer Task: ") + .append(ChatColorType.HIGHLIGHT) + .append(sb.toString()) + .build(); + + final MessageNode messageNode = chatMessage.getMessageNode(); + messageNode.setRuneLiteFormatMessage(response); + chatMessageManager.update(messageNode); + client.refreshChat(); + } + + private boolean taskSubmit(ChatInput chatInput, String value) + { + if (Strings.isNullOrEmpty(taskName)) + { + return false; + } + + final String playerName = client.getLocalPlayer().getName(); + + executor.execute(() -> + { + try + { + chatClient.submitTask(playerName, capsString(taskName), amount, initialAmount, taskLocation); + } + catch (Exception ex) + { + log.warn("unable to submit slayer task", ex); + } + finally + { + chatInput.resume(); + } + }); + + return true; + } + + //Utils + private String capsString(String str) + { + return str.substring(0, 1).toUpperCase() + str.substring(1); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetClickboxOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetClickboxOverlay.java new file mode 100644 index 0000000000..52b5b31865 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetClickboxOverlay.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018, James Swindle + * Copyright (c) 2018, Adam + * Copyright (c) 2018, Shaun Dreclin + * 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.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.util.List; +import javax.inject.Inject; +import net.runelite.api.NPC; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; + +public class TargetClickboxOverlay extends Overlay +{ + private final SlayerConfig config; + private final SlayerPlugin plugin; + + @Inject + TargetClickboxOverlay(SlayerConfig config, SlayerPlugin plugin) + { + this.config = config; + this.plugin = plugin; + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_SCENE); + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!config.highlightTargets()) + { + return null; + } + + List targets = plugin.getHighlightedTargets(); + for (NPC target : targets) + { + renderTargetOverlay(graphics, target, config.getTargetColor()); + } + + return null; + } + + private void renderTargetOverlay(Graphics2D graphics, NPC actor, Color color) + { + Shape objectClickbox = actor.getConvexHull(); + if (objectClickbox != null) + { + graphics.setColor(color); + graphics.setStroke(new BasicStroke(2)); + graphics.draw(objectClickbox); + graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20)); + graphics.fill(objectClickbox); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetMinimapOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetMinimapOverlay.java new file mode 100644 index 0000000000..ea12fde38a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetMinimapOverlay.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018, James Swindle + * Copyright (c) 2018, Adam + * Copyright (c) 2018, Shaun Dreclin + * 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.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.util.List; +import javax.inject.Inject; +import net.runelite.api.NPC; +import net.runelite.api.Point; +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; + +public class TargetMinimapOverlay extends Overlay +{ + private final SlayerConfig config; + private final SlayerPlugin plugin; + + @Inject + TargetMinimapOverlay(SlayerConfig config, SlayerPlugin plugin) + { + this.config = config; + this.plugin = plugin; + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!config.highlightTargets()) + { + return null; + } + + List targets = plugin.getHighlightedTargets(); + for (NPC target : targets) + { + renderTargetOverlay(graphics, target, config.getTargetColor()); + } + + return null; + } + + private void renderTargetOverlay(Graphics2D graphics, NPC actor, Color color) + { + Point minimapLocation = actor.getMinimapLocation(); + if (minimapLocation != null) + { + OverlayUtil.renderMinimapLocation(graphics, minimapLocation, color); + } + } +} 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..91f331d06b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetWeaknessOverlay.java @@ -0,0 +1,138 @@ +/* + * 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; + +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.UNDER_WIDGETS); + } + + @Override + public Dimension render(Graphics2D graphics) + { + final List targets = plugin.getHighlightedTargets(); + + if (targets.isEmpty() || !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; + } + + 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.getHealthScale(); + final int healthRatio = target.getHealthRatio(); + final Integer maxHealth = npcManager.getHealth(target.getId()); + + 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 new file mode 100644 index 0000000000..8e02556440 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2017, Tyler + * Copyright (c) 2018, Shaun Dreclin + * 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 com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import lombok.Getter; +import net.runelite.api.ItemID; + +@Getter +enum Task +{ + // + ABERRANT_SPECTRES("Aberrant spectres", ItemID.ABERRANT_SPECTRE, "Spectre"), + ABYSSAL_DEMONS("Abyssal demons", ItemID.ABYSSAL_DEMON), + ABYSSAL_SIRE("Abyssal Sire", ItemID.ABYSSAL_ORPHAN), + ADAMANT_DRAGONS("Adamant dragons", ItemID.ADAMANT_DRAGON_MASK), + ALCHEMICAL_HYDRA("Alchemical Hydra", ItemID.IKKLE_HYDRA), + ANKOU("Ankou", ItemID.ANKOU_MASK), + AVIANSIES("Aviansies", ItemID.ENSOULED_AVIANSIE_HEAD), + BANDITS("Bandits", ItemID.BANDIT, "Bandit", "Black Heather", "Donny the Lad", "Speedy Keith"), + BANSHEES("Banshees", ItemID.BANSHEE), + BARROWS_BROTHERS("Barrows Brothers", ItemID.KARILS_COIF), + BASILISKS("Basilisks", ItemID.BASILISK), + BATS("Bats", ItemID.GIRAL_BAT_2, "Death wing"), + BEARS("Bears", ItemID.ENSOULED_BEAR_HEAD), + BIRDS("Birds", ItemID.FEATHER, "Chicken", "Rooster", "Terrorbird", "Seagull", "Vulture"), + BLACK_DEMONS("Black demons", ItemID.BLACK_DEMON_MASK), + BLACK_DRAGONS("Black dragons", ItemID.BLACK_DRAGON_MASK, "Baby black dragon"), + BLACK_KNIGHTS("Black Knights", ItemID.BLACK_FULL_HELM, "Black Knight"), + BLOODVELD("Bloodveld", ItemID.BLOODVELD), + BLUE_DRAGONS("Blue dragons", ItemID.BLUE_DRAGON_MASK, "Baby blue dragon"), + BRINE_RATS("Brine rats", ItemID.BRINE_RAT), + BRONZE_DRAGONS("Bronze dragons", ItemID.BRONZE_DRAGON_MASK), + CALLISTO("Callisto", ItemID.CALLISTO_CUB), + CATABLEPON("Catablepon", ItemID.LEFT_SKULL_HALF), + CAVE_BUGS("Cave bugs", ItemID.SWAMP_CAVE_BUG), + CAVE_CRAWLERS("Cave crawlers", ItemID.CAVE_CRAWLER, "Chasm crawler"), + CAVE_HORRORS("Cave horrors", ItemID.CAVE_HORROR, "Cave abomination"), + CAVE_KRAKEN("Cave kraken", ItemID.CAVE_KRAKEN), + CAVE_SLIMES("Cave slimes", ItemID.SWAMP_CAVE_SLIME), + CERBERUS("Cerberus", ItemID.HELLPUPPY), + CHAOS_DRUIDS("Chaos druids", ItemID.ELDER_CHAOS_HOOD, "Elder Chaos druid", "Chaos druid"), + CHAOS_ELEMENTAL("Chaos Elemental", ItemID.PET_CHAOS_ELEMENTAL), + CHAOS_FANATIC("Chaos Fanatic", ItemID.ANCIENT_STAFF), + COCKATRICE("Cockatrice", ItemID.COCKATRICE, "Cockathrice"), + COWS("Cows", ItemID.COW_MASK), + CRAWLING_HANDS("Crawling hands", ItemID.CRAWLING_HAND, "Crushing hand"), + CRAZY_ARCHAEOLOGIST("Crazy Archaeologists", ItemID.FEDORA, "Crazy Archaeologist"), + CROCODILES("Crocodiles", ItemID.SWAMP_LIZARD), + DAGANNOTH("Dagannoth", ItemID.DAGANNOTH), + DAGANNOTH_KINGS("Dagannoth Kings", ItemID.PET_DAGANNOTH_PRIME), + 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), + DOGS("Dogs", ItemID.GUARD_DOG, "Jackal"), + DRAKES("Drakes", ItemID.DRAKE), + DUST_DEVILS("Dust devils", ItemID.DUST_DEVIL, "Choke devil"), + DWARVES("Dwarves", ItemID.DWARVEN_HELMET, "Dwarf", "Black Guard"), + EARTH_WARRIORS("Earth warriors", ItemID.BRONZE_FULL_HELM_T), + ELVES("Elves", ItemID.ELF, "Elf", "Iorwerth Warrior", "Iorwerth Archer"), + ENTS("Ents", ItemID.NICE_TREE, "Ent"), + FEVER_SPIDERS("Fever spiders", ItemID.FEVER_SPIDER), + FIRE_GIANTS("Fire giants", ItemID.FIRE_BATTLESTAFF), + FLESH_CRAWLERS("Fleshcrawlers", ItemID.ENSOULED_SCORPION_HEAD, "Flesh crawler"), + FOSSIL_ISLAND_WYVERNS("Fossil island wyverns", ItemID.FOSSIL_ISLAND_WYVERN, "Ancient wyvern", "Long-tailed wyvern", "Spitting wyvern", "Taloned wyvern"), + GARGOYLES("Gargoyles", ItemID.GARGOYLE, 9, ItemID.ROCK_HAMMER), + GENERAL_GRAARDOR("General Graardor", ItemID.PET_GENERAL_GRAARDOR), + GHOSTS("Ghosts", ItemID.GHOSTSPEAK_AMULET, "Death wing", "Tortured soul"), + GHOULS("Ghouls", ItemID.ZOMBIE_HEAD), + GIANT_MOLE("Giant Mole", ItemID.BABY_MOLE), + GOBLINS("Goblins", ItemID.ENSOULED_GOBLIN_HEAD), + GREATER_DEMONS("Greater demons", ItemID.GREATER_DEMON_MASK), + GREEN_DRAGONS("Green dragons", ItemID.GREEN_DRAGON_MASK, "Baby green dragon", "Elvarg"), + 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, "Cyclops"), + HOBGOBLINS("Hobgoblins", ItemID.HOBGOBLIN_GUARD), + HYDRAS("Hydras", ItemID.HYDRA), + ICEFIENDS("Icefiends", ItemID.ICE_DIAMOND), + ICE_GIANTS("Ice giants", ItemID.ICE_DIAMOND), + ICE_WARRIORS("Ice warriors", ItemID.MITHRIL_FULL_HELM_T, "Icelord"), + INFERNAL_MAGES("Infernal mages", ItemID.INFERNAL_MAGE, "Malevolent mage"), + IRON_DRAGONS("Iron dragons", ItemID.IRON_DRAGON_MASK), + JAD("TzTok-Jad", ItemID.TZREKJAD, 25250), + JELLIES("Jellies", ItemID.JELLY, "Jelly"), + JUNGLE_HORROR("Jungle horrors", ItemID.ENSOULED_HORROR_HEAD), + KALPHITE("Kalphite", ItemID.KALPHITE_SOLDIER), + KALPHITE_QUEEN("Kalphite Queen", ItemID.KALPHITE_PRINCESS), + KILLERWATTS("Killerwatts", ItemID.KILLERWATT), + KING_BLACK_DRAGON("King Black Dragon", ItemID.PRINCE_BLACK_DRAGON), + KRAKEN("Cave Kraken Boss", ItemID.PET_KRAKEN, "Kraken"), + KREEARRA("Kree'arra", ItemID.PET_KREEARRA), + KRIL_TSUTSAROTH("K'ril Tsutsaroth", ItemID.PET_KRIL_TSUTSAROTH), + KURASK("Kurask", ItemID.KURASK), + LAVA_DRAGONS("Lava Dragons", ItemID.LAVA_SCALE, "Lava dragon"), + LESSER_DEMONS("Lesser demons", ItemID.LESSER_DEMON_MASK), + LIZARDMEN("Lizardmen", ItemID.LIZARDMAN_FANG, "Lizardman"), + LIZARDS("Lizards", ItemID.DESERT_LIZARD, "Desert lizard", "Sulphur lizard", "Small lizard", "Lizard"), + MAGIC_AXES("Magic axes", ItemID.IRON_BATTLEAXE, "Magic axe"), + MAMMOTHS("Mammoths", ItemID.ATTACKER_HORN, "Mammoth"), + MINIONS_OF_SCABARAS("Minions of scabaras", ItemID.GOLDEN_SCARAB, "Scarab swarm", "Locust rider", "Scarab mage"), + MINOTAURS("Minotaurs", ItemID.ENSOULED_MINOTAUR_HEAD), + MITHRIL_DRAGONS("Mithril dragons", ItemID.MITHRIL_DRAGON_MASK), + MOGRES("Mogres", ItemID.MOGRE), + MOLANISKS("Molanisks", ItemID.MOLANISK), + MONKEYS("Monkeys", ItemID.ENSOULED_MONKEY_HEAD, "Tortured gorilla"), + MOSS_GIANTS("Moss giants", ItemID.HILL_GIANT_CLUB), + MUTATED_ZYGOMITES("Mutated zygomites", ItemID.MUTATED_ZYGOMITE, 7, ItemID.FUNGICIDE_SPRAY_0, "Zygomite", "Fungi"), + NECHRYAEL("Nechryael", ItemID.NECHRYAEL, "Nechryarch"), + OGRES("Ogres", ItemID.ENSOULED_OGRE_HEAD), + OTHERWORLDLY_BEING("Otherworldly beings", ItemID.GHOSTLY_HOOD), + PIRATES("Pirates", ItemID.PIRATE_HAT, "Pirate"), + PYREFIENDS("Pyrefiends", ItemID.PYREFIEND, "Flaming pyrelord"), + RATS("Rats", ItemID.RATS_TAIL), + RED_DRAGONS("Red dragons", ItemID.BABY_RED_DRAGON, "Baby red dragon"), + REVENANTS("Revenants", ItemID.BRACELET_OF_ETHEREUM, "Revenant imp", "Revenant goblin", "Revenant pyrefiend", "Revenant hobgoblin", "Revenant cyclops", "Revenant hellhound", "Revenant demon", "Revenant ork", "Revenant dark beast", "Revenant knight", "Revenant dragon"), + ROCKSLUGS("Rockslugs", ItemID.ROCKSLUG, 4, ItemID.BAG_OF_SALT), + ROGUES("Rogues", ItemID.ROGUE_MASK, "Rogue"), + RUNE_DRAGONS("Rune dragons", ItemID.RUNE_DRAGON_MASK), + SARACHNIS("Sarachnis", ItemID.SRARACHA), + SCORPIA("Scorpia", ItemID.SCORPIAS_OFFSPRING), + SCORPIONS("Scorpions", ItemID.ENSOULED_SCORPION_HEAD), + SEA_SNAKES("Sea snakes", ItemID.SNAKE_CORPSE), + SHADES("Shades", ItemID.SHADE_ROBE_TOP, "Loar Shadow", "Loar Shade", "Phrin Shadow", "Phrin Shade", "Riyl Shadow", "Riyl Shade", "Asyn Shadow", "Asyn Shade", "Fiyr Shadow", "Fiyr Shade"), + SHADOW_WARRIORS("Shadow warriors", ItemID.BLACK_FULL_HELM), + SKELETAL_WYVERNS("Skeletal wyverns", ItemID.SKELETAL_WYVERN), + SKELETONS("Skeletons", ItemID.SKELETON_GUARD), + SMOKE_DEVILS("Smoke devils", ItemID.SMOKE_DEVIL), + SOURHOGS("Sourhogs", ItemID.SOURHOG_FOOT), + SPIDERS("Spiders", ItemID.HUGE_SPIDER), + SPIRITUAL_CREATURES("Spiritual creatures", ItemID.DRAGON_BOOTS, "Spiritual ranger", "Spiritual mage", "Spiritual warrior"), + STEEL_DRAGONS("Steel dragons", ItemID.STEEL_DRAGON), + SULPHUR_LIZARDS("Sulphur Lizards", ItemID.SULPHUR_LIZARD), + SUQAHS("Suqahs", ItemID.SUQAH_TOOTH), + TEMPLE_SPIDERS("Temple Spiders", ItemID.RED_SPIDERS_EGGS), + TERROR_DOGS("Terror dogs", ItemID.TERROR_DOG), + THERMONUCLEAR_SMOKE_DEVIL("Thermonuclear Smoke Devil", ItemID.PET_SMOKE_DEVIL), + TROLLS("Trolls", ItemID.TROLL_GUARD, "Dad", "Arrg"), + TUROTH("Turoth", ItemID.TUROTH), + TZHAAR("Tzhaar", ItemID.ENSOULED_TZHAAR_HEAD), + UNDEAD_DRUIDS("Undead Druids", ItemID.MASK_OF_RANUL), + VAMPYRES("Vampyres", ItemID.STAKE, "Vyrewatch", "Vampire"), + VENENATIS("Venenatis", ItemID.VENENATIS_SPIDERLING), + VETION("Vet'ion", ItemID.VETION_JR), + VORKATH("Vorkath", ItemID.VORKI), + WALL_BEASTS("Wall beasts", ItemID.SWAMP_WALLBEAST), + WATERFIENDS("Waterfiends", ItemID.WATER_ORB), + WEREWOLVES("Werewolves", ItemID.WOLFBANE, "Werewolf"), + WOLVES("Wolves", ItemID.GREY_WOLF_FUR, "Wolf"), + WYRMS("Wyrms", ItemID.WYRM), + ZILYANA("Commander Zilyana", ItemID.PET_ZILYANA), + ZOMBIES("Zombies", ItemID.ZOMBIE_HEAD, "Undead"), + ZUK("TzKal-Zuk", ItemID.TZREKZUK, 101890), + ZULRAH("Zulrah", ItemID.PET_SNAKELING); + // + + private static final Map tasks; + static final List LOCATIONS = ImmutableList.of( + "", // no location is a valid location + "Abyss", + "Ancient Cavern", + "Asgarnian Ice Dungeon", + "Battlefront", + "Brimhaven Dungeon", + "Brine Rat Cavern", + "Catacombs of Kourend", + "Chasm of Fire", + "Clan Wars", + "Death Plateau", + "Evil Chicken's Lair", + "Fossil Island", + "Forthos Dungeon", + "Fremennik Slayer Dungeon", + "God Wars Dungeon", + "Iorwerth Dungeon", + "Jormungand's Prison", + "Kalphite Lair", + "Karuulm Slayer Dungeon", + "Keldagrim", + "Kraken Cove", + "Lighthouse", + "Lithkren Vault", + "Lizardman Canyon", + "Lizardman Settlement", + "Meiyerditch Laboratories", + "Molch", + "Mount Quidamortem", + "Mourner Tunnels", + "Myths' Guild Dungeon", + "Ogre Enclave", + "Slayer Tower", + "Smoke Devil Dungeon", + "Smoke Dungeon", + "Stronghold of Security", + "Stronghold Slayer Dungeon", + "task-only Kalphite Cave", + "Taverley Dungeon", + "Troll Stronghold", + "Waterbirth Island", + "Waterfall Dungeon", + "Wilderness", + "Witchaven Dungeon", + "Zanaris" + ); + + private final String name; + private final int itemSpriteId; + private final String[] targetNames; + private final int weaknessThreshold; + private final int weaknessItem; + private final int expectedKillExp; + + static + { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + + for (Task task : values()) + { + builder.put(task.getName().toLowerCase(), task); + } + + tasks = builder.build(); + } + + Task(String name, int itemSpriteId, String... targetNames) + { + Preconditions.checkArgument(itemSpriteId >= 0); + this.name = name; + this.itemSpriteId = itemSpriteId; + this.weaknessThreshold = -1; + this.weaknessItem = -1; + this.targetNames = targetNames; + this.expectedKillExp = 0; + } + + 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; + this.expectedKillExp = 0; + } + + Task(String name, int itemSpriteId, int expectedKillExp) + { + Preconditions.checkArgument(itemSpriteId >= 0); + this.name = name; + this.itemSpriteId = itemSpriteId; + this.weaknessThreshold = -1; + this.weaknessItem = -1; + this.targetNames = new String[0]; + this.expectedKillExp = expectedKillExp; + } + + @Nullable + static Task getTask(String taskName) + { + return tasks.get(taskName.toLowerCase()); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskCounter.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskCounter.java new file mode 100644 index 0000000000..0d4274c063 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskCounter.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017, Tyler + * 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 net.runelite.client.plugins.Plugin; +import net.runelite.client.ui.overlay.infobox.Counter; + +import java.awt.image.BufferedImage; + +class TaskCounter extends Counter +{ + TaskCounter(BufferedImage img, Plugin plugin, int amount) + { + super(img, plugin, amount); + } +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java index 16b4d28357..5d29390a45 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java @@ -148,7 +148,7 @@ public abstract class RSNPCMixin implements RSNPC @Inject @Override - public NPCComposition getTransformedDefinition() + public NPCComposition getTransformedComposition() { RSNPCComposition composition = getComposition(); if (composition != null && composition.getConfigs() != null)