diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java index 0aaed08f70..d0543c996d 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java @@ -43,6 +43,21 @@ class WidgetID static final int SHOP_GROUP_ID = 300; static final int SHOP_INVENTORY_GROUP_ID = 301; static final int COMBAT_GROUP_ID = 593; + static final int DIALOG_NPC_GROUP_ID = 231; + static final int SLAYER_REWARDS_GROUP_ID = 426; + + static class SlayerRewards + { + static final int TOP_BAR = 12; + } + + static class DialogNPC + { + static final int HEAD_MODEL = 0; + static final int NAME = 1; + static final int CONTINUE = 2; + static final int TEXT = 3; + } static class PestControl { diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java index 63a525f7f8..2313307254 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java @@ -81,7 +81,14 @@ public enum WidgetInfo PRAYER_PROTECT_FROM_MAGIC(WidgetID.PRAYER_GROUP_ID, WidgetID.Prayer.PROTECT_FROM_MAGIC), PRAYER_PROTECT_FROM_MISSILES(WidgetID.PRAYER_GROUP_ID, WidgetID.Prayer.PROTECT_FROM_MISSILES), - COMBAT_LEVEL(WidgetID.COMBAT_GROUP_ID, WidgetID.Combat.LEVEL); + COMBAT_LEVEL(WidgetID.COMBAT_GROUP_ID, WidgetID.Combat.LEVEL), + + DIALOG_NPC_NAME(WidgetID.DIALOG_NPC_GROUP_ID, WidgetID.DialogNPC.NAME), + DIALOG_NPC_TEXT(WidgetID.DIALOG_NPC_GROUP_ID, WidgetID.DialogNPC.TEXT), + DIALOG_NPC_HEAD_MODEL(WidgetID.DIALOG_NPC_GROUP_ID, WidgetID.DialogNPC.HEAD_MODEL), + DIALOG_NPC_CONTINUE(WidgetID.DIALOG_NPC_GROUP_ID, WidgetID.DialogNPC.CONTINUE), + + SLAYER_REWARDS_TOPBAR(WidgetID.SLAYER_REWARDS_GROUP_ID, WidgetID.SlayerRewards.TOP_BAR); private final int groupId; diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index f23ff6ea3e..d5cc92b3f4 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -349,6 +349,11 @@ public class RuneLite return runelite; } + public static void setRunelite(RuneLite runelite) + { + RuneLite.runelite = runelite; + } + public RuneliteProperties getProperties() { return properties; diff --git a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java index f641a43f42..e6ea014bf5 100644 --- a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java +++ b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java @@ -192,7 +192,8 @@ public class Hooks logger.debug("Chat message type {}: {}", ChatMessageType.of(type), message); } - ChatMessage chatMessage = new ChatMessage(type, sender, message, clan); + ChatMessageType chatMessageType = ChatMessageType.of(type); + ChatMessage chatMessage = new ChatMessage(chatMessageType, sender, message, clan); runelite.getEventBus().post(chatMessage); } diff --git a/runelite-client/src/main/java/net/runelite/client/events/ChatMessage.java b/runelite-client/src/main/java/net/runelite/client/events/ChatMessage.java index f924ae4d15..397aac2c58 100644 --- a/runelite-client/src/main/java/net/runelite/client/events/ChatMessage.java +++ b/runelite-client/src/main/java/net/runelite/client/events/ChatMessage.java @@ -33,9 +33,9 @@ public class ChatMessage private String message; private String clan; - public ChatMessage(int type, String sender, String message, String clan) + public ChatMessage(ChatMessageType type, String sender, String message, String clan) { - this.type = ChatMessageType.of(type); + this.type = type; this.sender = sender; this.message = message; this.clan = clan; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Slayer.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Slayer.java new file mode 100644 index 0000000000..f6bf6f6332 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Slayer.java @@ -0,0 +1,339 @@ +/* + * 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 com.google.common.eventbus.Subscribe; +import java.awt.image.BufferedImage; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.ItemID; +import static net.runelite.api.Skill.SLAYER; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.RuneLite; +import net.runelite.client.events.ChatMessage; +import net.runelite.client.events.ExperienceChanged; +import net.runelite.client.events.GameStateChanged; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.task.Schedule; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@PluginDescriptor( + name = "Slayer plugin" +) +public class Slayer extends Plugin +{ + private static final Logger logger = LoggerFactory.getLogger(Slayer.class); + + //Chat messages + private static final Pattern CHAT_GEM_PROGRESS_MESSAGE = Pattern.compile("You're assigned to kill (.*); 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."; + + //NPC messages + private static final Pattern NPC_ASSIGN_MESSAGE = Pattern.compile(".*Your new task is to kill (\\d*) (.*)\\."); + private static final Pattern NPC_CURRENT_MESSAGE = Pattern.compile("You're still hunting (.*), you have (\\d*) to go\\..*"); + + //Reward UI + private static final Pattern REWARD_POINTS = Pattern.compile("Reward points: (\\d*)"); + + private final InfoBoxManager infoBoxManager = RuneLite.getRunelite().getInfoBoxManager(); + private final SlayerConfig config = RuneLite.getRunelite().getConfigManager().getConfig(SlayerConfig.class); + private final SlayerOverlay overlay = new SlayerOverlay(this); + private final Client client = RuneLite.getClient(); + + private String taskName; + private int amount; + private TaskCounter counter; + private int streak; + private int points; + private int cachedXp; + + @Override + protected void startUp() throws Exception + { + + } + + @Override + protected void shutDown() throws Exception + { + + } + + @Subscribe + public void onGameStateChange(GameStateChanged event) + { + if (!config.enabled()) + { + return; + } + + switch (event.getGameState()) + { + case HOPPING: + case LOGGING_IN: + cachedXp = 0; + taskName = ""; + amount = 0; + break; + case LOGGED_IN: + if (config.amount() != -1 && !config.taskName().isEmpty()) + { + setTask(config.taskName(), config.amount()); + } + break; + } + } + + private void save() + { + config.amount(amount); + config.taskName(taskName); + config.points(points); + config.streak(streak); + } + + @Schedule( + period = 600, + unit = ChronoUnit.MILLIS + ) + public void scheduledChecks() + { + if (!config.enabled() || client == null) + { + return; + } + + Widget NPCDialog = client.getWidget(WidgetInfo.DIALOG_NPC_TEXT); + if (NPCDialog != null) + { + String NPCText = NPCDialog.getText().replaceAll("<[^>]*>", " "); //remove color and linebreaks + Matcher mAssign = NPC_ASSIGN_MESSAGE.matcher(NPCText); //number, name + Matcher mCurrent = NPC_CURRENT_MESSAGE.matcher(NPCText); //name, number + boolean found1 = mAssign.find(); + boolean found2 = mCurrent.find(); + if (!found1 && !found2) + { + return; + } + String taskName = found1 ? mAssign.group(2) : mCurrent.group(1); + int amount = Integer.parseInt(found1 ? mAssign.group(1) : mCurrent.group(2)); + + setTask(taskName, amount); + } + + 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()) + { + points = Integer.parseInt(mPoints.group(1)); + break; + } + } + } + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + if (!config.enabled() || event.getType() != ChatMessageType.SERVER) + { + return; + } + + String chatMsg = event.getMessage().replaceAll("<[^>]*>", ""); //remove color and linebreaks + if (chatMsg.endsWith("; return to a Slayer master.")) + { + Matcher mComplete = CHAT_COMPLETE_MESSAGE.matcher(chatMsg); + + List matches = new ArrayList<>(); + while (mComplete.find()) + { + matches.add(mComplete.group(0)); + } + + 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).replaceAll(",", "")); + break; + default: + logger.warn("Unreachable default case for message ending in '; return to Slayer master'"); + } + setTask("", 0); + return; + } + + if (chatMsg.equals(CHAT_GEM_COMPLETE_MESSAGE) || chatMsg.equals(CHAT_CANCEL_MESSAGE)) + { + setTask("", 0); + return; + } + + Matcher mProgress = CHAT_GEM_PROGRESS_MESSAGE.matcher(chatMsg); + if (!mProgress.find()) + { + return; + } + String taskName = mProgress.group(1); + int amount = Integer.parseInt(mProgress.group(2)); + + setTask(taskName, amount); + } + + @Subscribe + public void onExperienceChanged(ExperienceChanged event) + { + if (!config.enabled() || event.getSkill() != SLAYER) + { + return; + } + + if (cachedXp == 0) + { + // this is the initial xp sent on login + cachedXp = client.getSkillExperience(SLAYER); + return; + } + + killedOne(); + } + + private void killedOne() + { + amount--; + save(); + if (!config.showInfobox()) + { + return; + } + counter.setText(String.valueOf(amount)); + } + + private void setTask(String name, int amt) + { + taskName = name.toLowerCase(); + amount = amt; + save(); + + infoBoxManager.removeIf(t -> t instanceof TaskCounter); + + if (taskName.isEmpty() || !config.showInfobox()) + { + return; + } + + Task task = Task.getTask(taskName); + if (task == null) + { + logger.warn("No slayer task for {} in the Task database", taskName); + } + BufferedImage taskImg = task != null ? task.getImage() : ItemManager.getImage(ItemID.ENCHANTED_GEM); + counter = new TaskCounter(taskImg, amount); + counter.setTooltip(capsString(taskName)); + + infoBoxManager.addInfoBox(counter); + } + + //Getters + public SlayerConfig getConfig() + { + return config; + } + + @Override + public SlayerOverlay getOverlay() + { + return overlay; + } + + public String getTaskName() + { + return taskName; + } + + void setTaskName(String taskName) + { + this.taskName = taskName; + } + + public int getAmount() + { + return amount; + } + + void setAmount(int amount) + { + this.amount = amount; + } + + public int getStreak() + { + return streak; + } + + void setStreak(int streak) + { + this.streak = streak; + } + + public int getPoints() + { + return points; + } + + void setPoints(int points) + { + this.points = points; + } + + //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/SlayerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerConfig.java new file mode 100644 index 0000000000..f5a89cab4d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerConfig.java @@ -0,0 +1,139 @@ +/* + * 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 net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup( + keyName = "slayer", + name = "Slayer", + description = "Configuration for the slayer plugin" +) +public interface SlayerConfig +{ + @ConfigItem( + keyName = "enabled", + name = "Enable", + description = "Configures whether slayer plugin is enabled" + ) + default boolean enabled() + { + return true; + } + + @ConfigItem( + keyName = "infobox", + name = "Task InfoBox", + description = "Display task information in an InfoBox" + ) + default boolean showInfobox() + { + return true; + } + + @ConfigItem( + keyName = "itemoverlay", + name = "Count on Items", + description = "Display task count remaining on slayer items" + ) + default boolean showItemOverlay() + { + 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 = "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); +} 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..574dc3c800 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerOverlay.java @@ -0,0 +1,157 @@ +/* + * 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.ImmutableList; +import static com.google.common.collect.ObjectArrays.concat; +import com.google.common.collect.Sets; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.util.Collection; +import java.util.Set; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.ItemID; +import net.runelite.api.Query; +import net.runelite.api.queries.EquipmentItemQuery; +import net.runelite.api.queries.InventoryItemQuery; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.api.widgets.WidgetItem; +import net.runelite.client.RuneLite; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayPosition; + +class SlayerOverlay extends Overlay +{ + private final RuneLite runelite = RuneLite.getRunelite(); + private final Client client = RuneLite.getClient(); + private final SlayerConfig config; + private final Slayer plugin; + private final Font font = FontManager.getRunescapeSmallFont().deriveFont(Font.PLAIN, 16); + + private final Set slayerJewelry = Sets.newHashSet( + 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 Set slayerEquipment = Sets.newHashSet( + 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.SLAYER_RING_ETERNAL, + ItemID.ENCHANTED_GEM, + ItemID.ETERNAL_GEM + ); + + SlayerOverlay(Slayer plugin) + { + super(OverlayPosition.DYNAMIC); + this.plugin = plugin; + this.config = plugin.getConfig(); + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (client.getGameState() != GameState.LOGGED_IN + || !config.enabled() + || client.getWidget(WidgetInfo.LOGIN_CLICK_TO_PLAY_SCREEN) != null) + { + return null; + } + + if (!config.showItemOverlay()) + { + return null; + } + + int amount = plugin.getAmount(); + if (amount <= 0) + { + return null; + } + + graphics.setFont(font); + + for (WidgetItem item : getSlayerWidgetItems()) + { + int itemId = item.getId(); + + if (!slayerEquipment.contains(itemId) && !slayerJewelry.contains(itemId)) + { + continue; + } + + renderWidgetText(graphics, itemId, item.getCanvasBounds(), amount, Color.white); + } + + return null; + } + + private Collection getSlayerWidgetItems() + { + Query inventoryQuery = new InventoryItemQuery(); + WidgetItem[] inventoryWidgetItems = runelite.runQuery(inventoryQuery); + + Query equipmentQuery = new EquipmentItemQuery().slotEquals(WidgetInfo.EQUIPMENT_HELMET, WidgetInfo.EQUIPMENT_RING); + WidgetItem[] equipmentWidgetItems = runelite.runQuery(equipmentQuery); + + WidgetItem[] items = concat(inventoryWidgetItems, equipmentWidgetItems, WidgetItem.class); + return ImmutableList.copyOf(items); + } + + private void renderWidgetText(Graphics2D graphics, int itemId, Rectangle bounds, int amount, Color color) + { + FontMetrics fm = graphics.getFontMetrics(); + + int textX = (int) bounds.getX(); + int textY = (int) bounds.getY() + (slayerJewelry.contains(itemId) ? (int) bounds.getHeight() : fm.getHeight()); + + //text shadow + graphics.setColor(Color.BLACK); + graphics.drawString(String.valueOf(amount), textX + 1, textY + 1); + + graphics.setColor(color); + graphics.drawString(String.valueOf(amount), textX, textY); + } +} 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..5418e3014e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java @@ -0,0 +1,176 @@ +/* + * 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.api.ItemID; +import net.runelite.client.game.ItemManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; + +enum Task +{ + // + ABERRANT_SPECTRES("aberrant spectres", ItemID.ABERRANT_SPECTRE), + ABYSSAL_DEMONS("abyssal demons", ItemID.ABYSSAL_DEMON), + ANKOU("ankou", ItemID.ANKOU_MASK), + AVIANSIES("aviansies", ItemID.ENSOULED_AVIANSIE_HEAD), + BANSHEES("banshees", ItemID.BANSHEE), + BASILISKS("basilisks", ItemID.BASILISK), + BATS("bats", ItemID.GIRAL_BAT_2), + BEARS("bears", ItemID.ENSOULED_BEAR_HEAD), + BIRDS("birds", ItemID.FEATHER), + BLACK_DEMONS("black demons", ItemID.BLACK_DEMON_MASK), + BLACK_DRAGONS("black dragons", ItemID.BLACK_DRAGON_MASK), + BLOODVELD("bloodveld", ItemID.BLOODVELD), + BLUE_DRAGONS("blue dragons", ItemID.BLUE_DRAGON_MASK), + BOSSES("bosses", -1), + BRINE_RATS("brine rats", ItemID.BRINE_RAT), + BRONZE_DRAGONS("bronze dragons", ItemID.BRONZE_DRAGON_MASK), + CATABLEPON("catablepon", ItemID.LEFT_SKULL_HALF), + CAVE_BUGS("cave bugs", ItemID.SWAMP_CAVE_BUG), + CAVE_CRAWLERS("cave crawlers", ItemID.CAVE_CRAWLER), + CAVE_HORRORS("cave horrors", ItemID.CAVE_HORROR), + CAVE_KRAKEN("cave kraken", ItemID.CAVE_KRAKEN), + CAVE_SLIMES("cave slimes", ItemID.SWAMP_CAVE_SLIME), + COCKATRICE("cockatrice", ItemID.COCKATRICE), + COWS("cows", ItemID.COW_MASK), + CRAWLING_HANDS("crawling hands", ItemID.CRAWLING_HAND), + CROCODILES("crocodiles", ItemID.SWAMP_LIZARD), + DAGANNOTH("dagannoth", ItemID.DAGANNOTH), + DARK_BEASTS("dark beasts", ItemID.DARK_BEAST), + DESERT_LIZARDS("desert lizards", ItemID.DESERT_LIZARD), + DOGS("dogs", ItemID.GUARD_DOG), + DUST_DEVILS("dust devils", ItemID.DUST_DEVIL), + DWARVES("dwarves", ItemID.DWARVEN_HELMET), + EARTH_WARRIORS("earth warriors", ItemID.BRONZE_FULL_HELM_T), + ELVES("elves", ItemID.ELF), + FEVER_SPIDERS("fever spiders", ItemID.FEVER_SPIDER), + FIRE_GIANTS("fire giants", -1), + FLESHCRAWLERS("fleshcrawlers", -1), + GARGOYLES("gargoyles", ItemID.GARGOYLE), + GHOSTS("ghosts", -1), + GHOULS("ghouls", -1), + GOBLINS("goblins", ItemID.ENSOULED_GOBLIN_HEAD), + GREATER_DEMONS("greater demons", ItemID.GREATER_DEMON_MASK), + GREEN_DRAGONS("green dragons", ItemID.GREEN_DRAGON_MASK), + HARPIE_BUG_SWARMS("harpie bug swarms", ItemID.SWARM), + HELLHOUNDS("hellhounds", ItemID.HELLHOUND), + HILL_GIANTS("hill giants", ItemID.ENSOULED_GIANT_HEAD), + HOBGOBLINS("hobgoblins", ItemID.HOBGOBLIN_GUARD), + ICE_GIANTS("ice giants", -1), + ICE_WARRIORS("ice warriors", ItemID.MITHRIL_FULL_HELM_T), + ICEFIENDS("icefiends", -1), + INFERNAL_MAGES("infernal mages", ItemID.INFERNAL_MAGE), + IRON_DRAGONS("iron dragons", ItemID.IRON_DRAGON_MASK), + JELLIES("jellies", ItemID.JELLY), + JUNGLE_HORRORS("jungle horrors", -1), + KALPHITE("kalphite", ItemID.KALPHITE_SOLDIER), + KILLERWATTS("killerwatts", ItemID.KILLERWATT), + KURASK("kurask", ItemID.KURASK), + LESSER_DEMONS("lesser demons", ItemID.LESSER_DEMON_MASK), + LIZARDMEN("lizardmen", ItemID.LIZARDMAN_FANG), + MINIONS_OF_SCABARAS("minions of scabaras", ItemID.GOLDEN_SCARAB), + 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), + MOSS_GIANTS("moss giants", -1), + MUTATED_ZYGOMITES("mutated zygomites", ItemID.MUTATED_ZYGOMITE), + NECHRYAEL("nechryael", ItemID.NECHRYAEL), + OGRES("ogres", ItemID.ENSOULED_OGRE_HEAD), + OTHERWORLDLY_BEING("otherworldly being", ItemID.GHOSTLY_HOOD), + PYREFIENDS("pyrefiends", ItemID.PYREFIEND), + RATS("rats", ItemID.RATS_TAIL), + RED_DRAGONS("red dragons", ItemID.BABY_RED_DRAGON), + ROCKSLUGS("rockslugs", ItemID.ROCKSLUG), + SCORPIONS("scorpions", -1), + SEA_SNAKES("sea snakes", ItemID.SNAKE_CORPSE), + SHADES("shades", ItemID.SHADE_ROBE_TOP), + SHADOW_WARRIORS("shadow warriors", -1), + SKELETAL_WYVERNS("skeletal wyverns", ItemID.SKELETAL_WYVERN), + SKELETONS("skeletons", ItemID.SKELETON_GUARD), + SMOKE_DEVILS("smoke devils", ItemID.SMOKE_DEVIL), + SPIDERS("spiders", ItemID.HUGE_SPIDER), + SPIRITUAL_CREATURES("spiritual creatures", -1), + STEEL_DRAGONS("steel dragons", ItemID.STEEL_DRAGON), + SUQAHS("suqahs", ItemID.SUQAH_TOOTH), + TERROR_DOGS("terror dogs", ItemID.TERROR_DOG), + TROLLS("trolls", ItemID.TROLL_GUARD), + TUROTH("turoth", ItemID.TUROTH), + TZHAAR("tzhaar", ItemID.ENSOULED_TZHAAR_HEAD), + VAMPIRES("vampires", ItemID.STAKE), + WALL_BEASTS("wall beasts", ItemID.SWAMP_WALLBEAST), + WATERFIENDS("waterfiends", -1), + WEREWOLVES("werewolves", ItemID.WOLFBANE), + WOLVES("wolves", ItemID.GREY_WOLF_FUR), + ZOMBIES("zombies", ItemID.ZOMBIE_HEAD); + // + + private static final Logger logger = LoggerFactory.getLogger(Task.class); + + private static final Map tasks = new HashMap<>(); + + private final String name; + + private final int itemSpriteId; + + static + { + for (Task task : values()) + { + tasks.put(task.getName(), task); + } + } + + Task(String name, int itemSpriteId) + { + this.name = name; + this.itemSpriteId = itemSpriteId; + } + + public static Task getTask(String taskName) + { + return tasks.get(taskName); + } + + public String getName() + { + return this.name; + } + + public BufferedImage getImage() + { + if (itemSpriteId == -1) + { + return ItemManager.getImage(ItemID.ENCHANTED_GEM); + } + return ItemManager.getImage(itemSpriteId); + } +} 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..ef6c54e9d4 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskCounter.java @@ -0,0 +1,37 @@ +/* + * 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.ui.overlay.infobox.Counter; + +import java.awt.image.BufferedImage; + +public class TaskCounter extends Counter +{ + public TaskCounter(BufferedImage img, int amount) + { + super(img, String.valueOf(amount)); + } +} diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/slayer/SlayerTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/slayer/SlayerTest.java new file mode 100644 index 0000000000..91fbc23bff --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/slayer/SlayerTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.slayer; + +import static net.runelite.api.ChatMessageType.SERVER; +import net.runelite.client.RuneLite; +import net.runelite.client.events.ChatMessage; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.Answers.RETURNS_DEEP_STUBS; +import org.mockito.Matchers; +import org.mockito.Mock; +import static org.mockito.Mockito.when; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class SlayerTest +{ + private static final String TASK_ONE = "You've completed one task; return to a Slayer master."; + private static final String TASK_COMPLETE_NO_POINTS = "You've completed 3 tasks; return to a Slayer master."; + private static final String TASK_POINTS = "You've completed 9 tasks and received 0 points, giving you a total of 18,000; return to a Slayer master."; + + private static final String TASK_COMPLETE = "You need something new to hunt."; + private static final String TASK_CANCELED = "Your task has been cancelled."; + + @Mock(answer = RETURNS_DEEP_STUBS) + private RuneLite runeLite; + + @Mock + private SlayerConfig slayerConfig; + + private Slayer slayerPlugin; + + @Before + public void before() + { + RuneLite.setRunelite(runeLite); + when(runeLite.getConfigManager().getConfig(Matchers.any(Class.class))).thenReturn(slayerConfig); + when(slayerConfig.enabled()).thenReturn(true); + + slayerPlugin = new Slayer(); + } + + @Test + public void testOneTask() + { + ChatMessage chatMessageEvent = new ChatMessage(SERVER, "Perterter", TASK_ONE, null); + slayerPlugin.onChatMessage(chatMessageEvent); + + assertEquals(1, slayerPlugin.getStreak()); + assertEquals("", slayerPlugin.getTaskName()); + assertEquals(0, slayerPlugin.getAmount()); + } + + @Test + public void testNoPoints() + { + ChatMessage chatMessageEvent = new ChatMessage(SERVER, "Perterter", TASK_COMPLETE_NO_POINTS, null); + slayerPlugin.onChatMessage(chatMessageEvent); + + assertEquals(3, slayerPlugin.getStreak()); + assertEquals("", slayerPlugin.getTaskName()); + assertEquals(0, slayerPlugin.getAmount()); + } + + @Test + public void testPoints() + { + ChatMessage chatMessageEvent = new ChatMessage(SERVER, "Perterter", TASK_POINTS, null); + slayerPlugin.onChatMessage(chatMessageEvent); + + assertEquals(9, slayerPlugin.getStreak()); + assertEquals("", slayerPlugin.getTaskName()); + assertEquals(0, slayerPlugin.getAmount()); + assertEquals(18_000, slayerPlugin.getPoints()); + } + + @Test + public void testComplete() + { + slayerPlugin.setTaskName("cows"); + slayerPlugin.setAmount(42); + + ChatMessage chatMessageEvent = new ChatMessage(SERVER, "Perterter", TASK_COMPLETE, null); + slayerPlugin.onChatMessage(chatMessageEvent); + + assertEquals("", slayerPlugin.getTaskName()); + assertEquals(0, slayerPlugin.getAmount()); + } + + @Test + public void testCancelled() + { + slayerPlugin.setTaskName("cows"); + slayerPlugin.setAmount(42); + + ChatMessage chatMessageEvent = new ChatMessage(SERVER, "Perterter", TASK_CANCELED, null); + slayerPlugin.onChatMessage(chatMessageEvent); + + assertEquals("", slayerPlugin.getTaskName()); + assertEquals(0, slayerPlugin.getAmount()); + } +}