diff --git a/pom.xml b/pom.xml
index 5d9386effe..2122c9f537 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,7 +42,7 @@
true
true
-
+ true
179
@@ -120,6 +120,7 @@
runelite-mixins
runelite-script-assembler-plugin
runescape-api
+ runelite-plugin-archetype
http-api
http-service
protocol-api
@@ -151,6 +152,13 @@
pom
import
+
+ org.apache.maven.archetype
+ archetype-packaging
+ 3.0.1
+ pom
+ import
+
diff --git a/runelite-api/src/main/java/net/runelite/api/Actor.java b/runelite-api/src/main/java/net/runelite/api/Actor.java
index 6357a91ebc..b0bd59aec1 100644
--- a/runelite-api/src/main/java/net/runelite/api/Actor.java
+++ b/runelite-api/src/main/java/net/runelite/api/Actor.java
@@ -38,6 +38,12 @@ import net.runelite.api.coords.WorldPoint;
*/
public interface Actor extends Renderable
{
+ /**
+ * Used by the "Tick Counter Plugin
+ */
+ int getActionFrame();
+ int getActionFrameCycle();
+
/**
* Gets the combat level of the actor.
*
diff --git a/runelite-api/src/main/java/net/runelite/api/AnimationID.java b/runelite-api/src/main/java/net/runelite/api/AnimationID.java
index e3fa08fd88..02b3fd786c 100644
--- a/runelite-api/src/main/java/net/runelite/api/AnimationID.java
+++ b/runelite-api/src/main/java/net/runelite/api/AnimationID.java
@@ -154,10 +154,26 @@ public final class AnimationID
public static final int PISCARILIUS_CRANE_REPAIR = 7199;
public static final int HOME_MAKE_TABLET = 4067;
+ //block animations for players and perhaps npcs as well?
+ public static final int BLOCK_DEFENDER = 4177;
+ public static final int BLOCK_NO_SHIELD = 420;
+ public static final int BLOCK_SHIELD = 1156;
+ public static final int BLOCK_SWORD = 388;
+ public static final int BLOCK_UNARMED = 424;
+
// NPC animations
public static final int TZTOK_JAD_MAGIC_ATTACK = 2656;
public static final int TZTOK_JAD_RANGE_ATTACK = 2652;
public static final int HELLHOUND_DEFENCE = 6566;
+ public static final int VORKATH_WAKE_UP = 7950;
+ public static final int VORKATH_DEATH = 7949;
+ public static final int VORKATH_SLASH_ATTACK = 7951;
+ public static final int VORKATH_ATTACK = 7952;
+ public static final int VORKATH_FIRE_BOMB_ATTACK = 7960;
+ public static final int VORKATH_ACID_ATTACK = 7957;
+ public static final int BLACKJACK_KO = 838;
+ public static final int VETION_EARTHQUAKE = 5507;
+ public static final int ZULRAH_DEATH = 5804;
// Farming
public static final int FARMING_HARVEST_FRUIT_TREE = 2280;
@@ -194,4 +210,31 @@ public final class AnimationID
// POH Animations
public static final int INCENSE_BURNER = 3687;
-}
+
+ // Weapons
+ public static final int LOW_LEVEL_MAGIC_ATTACK = 1162;
+ public static final int HIGH_LEVEL_MAGIC_ATTACK = 1167;
+ public static final int BLOWPIPE_ATTACK = 5061;
+
+ // Hydra
+ public static final int HYDRA_POISON_1 = 8234;
+ public static final int HYDRA_RANGED_1 = 8235;
+ public static final int HYDRA_MAGIC_1 = 8236;
+ public static final int HYDRA_1_1 = 8237;
+ public static final int HYDRA_1_2 = 8238;
+ public static final int HYDRA_LIGHTNING = 8241;
+ public static final int HYDRA_RANGED_2 = 8242;
+ public static final int HYDRA_MAGIC_2 = 8243;
+ public static final int HYDRA_2_1 = 8244;
+ public static final int HYDRA_2_2 = 8245;
+ public static final int HYDRA_FIRE = 8248;
+ public static final int HYDRA_RANGED_3 = 8249;
+ public static final int HYDRA_MAGIC_3 = 8250;
+ public static final int HYDRA_3_1 = 8251;
+ public static final int HYDRA_3_2 = 8252;
+ public static final int HYDRA_MAGIC_4 = 8254;
+ public static final int HYDRA_POISON_4 = 8254;
+ public static final int HYDRA_RANGED_4 = 8255;
+ public static final int HYDRA_4_1 = 8257;
+ public static final int HYDRA_4_2 = 8258;
+}
\ No newline at end of file
diff --git a/runelite-api/src/main/java/net/runelite/api/ClanMemberRank.java b/runelite-api/src/main/java/net/runelite/api/ClanMemberRank.java
index c919e343c7..7f5a29873a 100644
--- a/runelite-api/src/main/java/net/runelite/api/ClanMemberRank.java
+++ b/runelite-api/src/main/java/net/runelite/api/ClanMemberRank.java
@@ -39,43 +39,43 @@ public enum ClanMemberRank
/**
* Not in a clan.
*/
- UNRANKED(-1),
+ UNRANKED(-1, "", -1),
/**
* Friend rank.
*/
- FRIEND(0),
+ FRIEND(0, "https://cdn.discordapp.com/attachments/556184918770843649/557023638826778635/1004.png", SpriteID.CLAN_CHAT_RANK_SMILEY_FRIEND),
/**
* Recruit rank.
*/
- RECRUIT(1),
+ RECRUIT(1, "https://cdn.discordapp.com/attachments/556184918770843649/557023639111991306/1012-0.png", SpriteID.CLAN_CHAT_RANK_SINGLE_CHEVRON_RECRUIT),
/**
* Corporal rank.
*/
- CORPORAL(2),
+ CORPORAL(2, "https://cdn.discordapp.com/attachments/556184918770843649/557023638889431052/1011-0.png", SpriteID.CLAN_CHAT_RANK_DOUBLE_CHEVRON_CORPORAL),
/**
* Sergeant rank.
*/
- SERGEANT(3),
+ SERGEANT(3, "https://cdn.discordapp.com/attachments/556184918770843649/557023641968312321/1010-0.png", SpriteID.CLAN_CHAT_RANK_TRIPLE_CHEVRON_SERGEANT),
/**
* Lieutenant rank.
*/
- LIEUTENANT(4),
+ LIEUTENANT(4, "https://cdn.discordapp.com/attachments/556184918770843649/557023638893756416/1009-0.png", SpriteID.CLAN_CHAT_RANK_BRONZE_STAR_LIEUTENANT),
/**
* Captain rank.
*/
- CAPTAIN(5),
+ CAPTAIN(5, "https://cdn.discordapp.com/attachments/556184918770843649/557023638910664734/1008-0.png", SpriteID.CLAN_CHAT_RANK_SILVER_STAR_CAPTAIN),
/**
* General rank.
*/
- GENERAL(6),
+ GENERAL(6, "https://cdn.discordapp.com/attachments/556184918770843649/557023638835036170/1007-0.png", SpriteID.CLAN_CHAT_RANK_GOLD_STAR_GENERAL),
/**
* Channel owner rank.
*/
- OWNER(7),
+ OWNER(7, "https://cdn.discordapp.com/attachments/556184918770843649/557023638822453248/1006-0.png", SpriteID.CLAN_CHAT_RANK_KEY_CHANNEL_OWNER),
/**
* JMod rank.
*/
- JMOD(127);
+ JMOD(127, "", SpriteID.CLAN_CHAT_RANK_CROWN_JAGEX_MODERATOR);
private static final Map RANKS = new HashMap<>();
@@ -87,6 +87,8 @@ public enum ClanMemberRank
}
}
+
+
/**
* Utility method that maps the rank value to its respective
* {@link ClanMemberRank} value.
@@ -99,8 +101,11 @@ public enum ClanMemberRank
return RANKS.get(rank);
}
+
/**
* The value of the clan rank.
*/
private final int value;
+ private final String discavatar;
+ private final int spriteID;
}
diff --git a/runelite-api/src/main/java/net/runelite/api/InventoryID.java b/runelite-api/src/main/java/net/runelite/api/InventoryID.java
index 000bb8aa52..8ce1f884b0 100644
--- a/runelite-api/src/main/java/net/runelite/api/InventoryID.java
+++ b/runelite-api/src/main/java/net/runelite/api/InventoryID.java
@@ -57,6 +57,10 @@ public enum InventoryID
* Chambers of Xeric chest inventory.
*/
CHAMBERS_OF_XERIC_CHEST(581),
+ /**
+ * Looting Bag inventory
+ */
+ LOOTING_BAG(516),
/**
* Theater of Blood reward chest inventory (Raids 2)
*/
diff --git a/runelite-api/src/main/java/net/runelite/api/ProjectileID.java b/runelite-api/src/main/java/net/runelite/api/ProjectileID.java
index 34d5c54f3a..def8bed3e9 100644
--- a/runelite-api/src/main/java/net/runelite/api/ProjectileID.java
+++ b/runelite-api/src/main/java/net/runelite/api/ProjectileID.java
@@ -44,7 +44,7 @@ public class ProjectileID
public static final int VASA_RANGED_AOE = 1329;
public static final int TEKTON_METEOR_AOE = 660;
- public static final int OLM_FALLING_CRYSTAL_AOE = -1; //please help
+ public static final int OLM_FALLING_CRYSTAL_AOE = 1357;
public static final int OLM_BURNING_AOE = -1;
public static final int VORKATH_BOMB_AOE = 1481;
@@ -84,4 +84,10 @@ public class ProjectileID
public static final int VORKATH_PRAYER_DISABLE = 1471;
public static final int VORKATH_VENOM = 1470;
public static final int VORKATH_ICE = 350;
+
+ public static final int HYDRA_MAGIC = 1662;
+ public static final int HYDRA_RANGED = 1663;
+ public static final int HYDRA_POISON = 1644;
+ public static final int HYDRA_LIGHTNING = 1664;
+ public static final int HYDRA_LIGHTNING_2 = 1665;
}
diff --git a/runelite-api/src/main/java/net/runelite/api/SpriteID.java b/runelite-api/src/main/java/net/runelite/api/SpriteID.java
index e707854c12..e83c268535 100644
--- a/runelite-api/src/main/java/net/runelite/api/SpriteID.java
+++ b/runelite-api/src/main/java/net/runelite/api/SpriteID.java
@@ -431,7 +431,7 @@ public final class SpriteID
public static final int SPELL_FIRE_SURGE_DISABLED = 415;
/* Unmapped: 416, 417, 418 */
public static final int UNKNOWN_STANCE_ICON_1 = 419;
- public static final int UNKNOWN_STANCE_ICON_2 = 320;
+ public static final int UNKNOWN_STANCE_ICON_2 = 420;
public static final int UNKNOWN_STANCE_ICON_3 = 421;
public static final int MINIMAP_DESTINATION_FLAG = 422;
public static final int CHATBOX_BADGE_CROWN_PLAYER_MODERATOR = 423;
@@ -1566,4 +1566,7 @@ public final class SpriteID
public static final int MOBILE_YELLOW_TOUCH_ANIMATION_2 = 1626;
public static final int TAB_MAGIC_SPELLBOOK_ARCEUUS_UNUSED = 1708;
public static final int TAB_MAGIC_SPELLBOOK_ARCEUUS = 1711;
+ public static final int BIG_ASS_GUTHIX_SPELL = 1774;
+ public static final int BIG_SUPERHEAT = 1800;
+ public static final int BIG_SPEC_TRANSFER = 1959;
}
diff --git a/runelite-api/src/main/java/net/runelite/api/VarClientInt.java b/runelite-api/src/main/java/net/runelite/api/VarClientInt.java
index 0652f3b0e0..45354e55d2 100644
--- a/runelite-api/src/main/java/net/runelite/api/VarClientInt.java
+++ b/runelite-api/src/main/java/net/runelite/api/VarClientInt.java
@@ -48,6 +48,12 @@ public enum VarClientInt
INVENTORY_TAB(171),
+ /**
+ * -1 = player inventory closed
+ * 3 = player inventory opened
+ */
+ PLAYER_INVENTORY_OPENED(171),
+
WORLD_MAP_SEARCH_FOCUSED(190);
private final int index;
diff --git a/runelite-api/src/main/java/net/runelite/api/Varbits.java b/runelite-api/src/main/java/net/runelite/api/Varbits.java
index 88ce84f272..8a42e1247f 100644
--- a/runelite-api/src/main/java/net/runelite/api/Varbits.java
+++ b/runelite-api/src/main/java/net/runelite/api/Varbits.java
@@ -212,6 +212,9 @@ public enum Varbits
* Barbarian Assault
*/
IN_GAME_BA(3923),
+ COLL_BAG_EGG1(3259),
+ COLL_BAG_EGG2(3260),
+ COLL_BAG_EGG3(3269),
/**
* 0 = Outside wilderness
@@ -293,6 +296,7 @@ public enum Varbits
*/
NMZ_ABSORPTION(3956),
NMZ_POINTS(3949),
+ NMZ_OVERLOAD(3955),
/**
* Blast Furnace
@@ -349,17 +353,17 @@ public enum Varbits
*/
MULTICOMBAT_AREA(4605),
+ /**
+ * Wilderness area
+ */
+ WILDERNESS_AREA(5963),
+
/**
* Kingdom Management
*/
KINGDOM_FAVOR(72),
KINGDOM_COFFER(74),
- /**
- * The Hand in the Sand quest status
- */
- QUEST_THE_HAND_IN_THE_SAND(1527),
-
/**
* Daily Tasks (Collection availability)
*/
@@ -470,6 +474,19 @@ public enum Varbits
BANK_TAB_EIGHT_COUNT(4178),
BANK_TAB_NINE_COUNT(4179),
+ /*
+ * Spells being auto-casted
+ * */
+ AUTO_CAST_SPELL(276),
+
+ /**
+ * Temple Trekking
+ */
+ TREK_POINTS(1955),
+ TREK_STARTED(1956),
+ TREK_EVENT(1958),
+ TREK_STATUS(6719),
+
/**
* Type of GE offer currently being created
* 0 = buy
@@ -477,6 +494,95 @@ public enum Varbits
*/
GE_OFFER_CREATION_TYPE(4397),
+ /**
+ * f2p Quest varbits, these don't hold the completion value.
+ */
+ QUEST_DEMON_SLAYER(2561),
+ QUEST_GOBLIN_DIPLOMACY(2378),
+ QUEST_MISTHALIN_MYSTERY(3468),
+ QUEST_THE_CORSAIR_CURSE(6071),
+ QUEST_X_MARKS_THE_SPOT(8063),
+
+ /**
+ * member Quest varbits, these don't hold the completion value.
+ */
+ QUEST_ANIMAL_MAGNETISM(3185),
+ QUEST_BETWEEN_A_ROCK(299),
+ QUEST_CONTACT(3274),
+ QUEST_ZOGRE_FLESH_EATERS(487),
+ QUEST_DARKNESS_OF_HALLOWVALE(2573),
+ QUEST_DEATH_TO_THE_DORGESHUUN(2258),
+ QUEST_DESERT_TREASURE(358),
+ QUEST_DEVIOUS_MINDS(1465),
+ QUEST_EAGLES_PEAK(2780),
+ QUEST_ELEMENTAL_WORKSHOP_II(2639),
+ QUEST_ENAKHRAS_LAMENT(1560),
+ QUEST_ENLIGHTENED_JOURNEY(2866),
+ QUEST_THE_EYES_OF_GLOUPHRIE(2497),
+ QUEST_FAIRYTALE_I_GROWING_PAINS(1803),
+ QUEST_FAIRYTALE_II_CURE_A_QUEEN(2326),
+ QUEST_THE_FEUD(334),
+ QUEST_FORGETTABLE_TALE(822),
+ QUEST_GARDEN_OF_TRANQUILLITY(961),
+ QUEST_GHOSTS_AHOY(217),
+ QUEST_THE_GIANT_DWARF(571),
+ QUEST_THE_GOLEM(346),
+ QUEST_THE_HAND_IN_THE_SAND(1527),
+ QUEST_HORROR_FROM_THE_DEEP(34),
+ QUEST_ICTHLARINS_LITTLE_HELPER(418),
+ QUEST_IN_AID_OF_THE_MYREQUE(1990),
+ QUEST_THE_LOST_TRIBE(532),
+ QUEST_LUNAR_DIPLOMACY(2448),
+ QUEST_MAKING_HISTORY(1383),
+ QUEST_MOUNTAIN_DAUGHTER(260),
+ QUEST_MOURNINGS_ENDS_PART_II(1103),
+ QUEST_MY_ARMS_BIG_ADVENTURE(2790),
+ QUEST_RATCATCHERS(1404),
+ QUEST_RECIPE_FOR_DISASTER(1850),
+ QUEST_RECRUITMENT_DRIVE(657),
+ QUEST_ROYAL_TROUBLE(2140),
+ QUEST_THE_SLUG_MENACE(2610),
+ QUEST_SHADOW_OF_THE_STORM(1372),
+ QUEST_A_SOULS_BANE(2011),
+ QUEST_SPIRITS_OF_THE_ELID(1444),
+ QUEST_SWAN_SONG(2098),
+ QUEST_A_TAIL_OF_TWO_CATS(1028),
+ QUEST_TEARS_OF_GUTHIX(451),
+ QUEST_WANTED(1051),
+ QUEST_COLD_WAR(3293),
+ QUEST_THE_FREMENNIK_ISLES(3311),
+ QUEST_TOWER_OF_LIFE(3337),
+ QUEST_WHAT_LIES_BELOW(3523),
+ QUEST_OLAFS_QUEST(3534),
+ QUEST_ANOTHER_SLICE_OF_HAM(3550),
+ QUEST_DREAM_MENTOR(3618),
+ QUEST_GRIM_TALES(2783),
+ QUEST_KINGS_RANSOM(3888),
+ QUEST_MONKEY_MADNESS_II(5027),
+ QUEST_CLIENT_OF_KOUREND(5619),
+ QUEST_BONE_VOYAGE(5795),
+ QUEST_THE_QUEEN_OF_THIEVES(6037),
+ QUEST_THE_DEPTHS_OF_DESPAIR(6027),
+ QUEST_DRAGON_SLAYER_II(6104),
+ QUEST_TALE_OF_THE_RIGHTEOUS(6358),
+ QUEST_A_TASTE_OF_HOPE(6396),
+ QUEST_MAKING_FRIENDS_WITH_MY_ARM(6528),
+ QUEST_THE_ASCENT_OF_ARCEUUS(7856),
+ QUEST_THE_FORSAKEN_TOWER(7796),
+
+ /**
+ * mini-quest varbits, these don't hold the completion value.
+ */
+ QUEST_ARCHITECTURAL_ALLIANCE(4982),
+ QUEST_BEAR_YOUR_SOUL(5078),
+ QUEST_CURSE_OF_THE_EMPTY_LORD(821),
+ QUEST_ENCHANTED_KEY(1391),
+ QUEST_THE_GENERALS_SHADOW(3330),
+ QUEST_SKIPPY_AND_THE_MOGRES(1344),
+ QUEST_LAIR_OF_TARN_RAZORLOR(3290),
+ QUEST_FAMILY_PEST(5347),
+ QUEST_THE_MAGE_ARENA_II(6067),
+
/**
* The active tab within the quest interface
*/
diff --git a/runelite-api/src/main/java/net/runelite/api/events/CannonballFired.java b/runelite-api/src/main/java/net/runelite/api/events/CannonballFired.java
new file mode 100644
index 0000000000..077a88096d
--- /dev/null
+++ b/runelite-api/src/main/java/net/runelite/api/events/CannonballFired.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019, Davis Cook
+ * 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.api.events;
+
+/**
+ * an event posted when a cannonball is fired
+ */
+public class CannonballFired
+{
+}
diff --git a/runelite-api/src/main/java/net/runelite/api/kit/KitType.java b/runelite-api/src/main/java/net/runelite/api/kit/KitType.java
index fdbf92ebfd..e52167c563 100644
--- a/runelite-api/src/main/java/net/runelite/api/kit/KitType.java
+++ b/runelite-api/src/main/java/net/runelite/api/kit/KitType.java
@@ -36,6 +36,7 @@ import net.runelite.api.PlayerComposition;
*/
public enum KitType
{
+ HELM(0),
CAPE(1),
AMULET(2),
WEAPON(3),
diff --git a/runelite-api/src/main/java/net/runelite/api/queries/BankItemQuery.java b/runelite-api/src/main/java/net/runelite/api/queries/BankItemQuery.java
new file mode 100644
index 0000000000..b0131dce32
--- /dev/null
+++ b/runelite-api/src/main/java/net/runelite/api/queries/BankItemQuery.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2017, Devin French
+ * 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.api.queries;
+
+import net.runelite.api.Client;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.api.widgets.WidgetItem;
+
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Objects;
+
+public class BankItemQuery extends WidgetItemQuery
+{
+ private static final int ITEM_EMPTY = 6512;
+
+ @Override
+ public WidgetItem[] result(Client client)
+ {
+ Collection widgetItems = getBankItems(client);
+ if (widgetItems != null)
+ {
+ return widgetItems.stream()
+ .filter(Objects::nonNull)
+ .filter(predicate)
+ .toArray(WidgetItem[]::new);
+ }
+ return new WidgetItem[0];
+ }
+
+ private Collection getBankItems(Client client)
+ {
+ Collection widgetItems = new ArrayList<>();
+ Widget bank = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER);
+ if (bank != null && !bank.isHidden())
+ {
+ Widget[] children = bank.getDynamicChildren();
+ for (int i = 0; i < children.length; i++)
+ {
+ Widget child = children[i];
+ if (child.getItemId() == ITEM_EMPTY || child.isSelfHidden())
+ {
+ continue;
+ }
+ // set bounds to same size as default inventory
+ Rectangle bounds = child.getBounds();
+ bounds.setBounds(bounds.x - 1, bounds.y - 1, 32, 32);
+ // Index is set to 0 because the widget's index does not correlate to the order in the bank
+ widgetItems.add(new WidgetItem(child.getItemId(), child.getItemQuantity(), 0, bounds));
+ }
+ }
+ return widgetItems;
+ }
+}
diff --git a/runelite-api/src/main/java/net/runelite/api/queries/EquipmentItemQuery.java b/runelite-api/src/main/java/net/runelite/api/queries/EquipmentItemQuery.java
new file mode 100644
index 0000000000..210fc1e8d0
--- /dev/null
+++ b/runelite-api/src/main/java/net/runelite/api/queries/EquipmentItemQuery.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2017, Devin French
+ * 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.api.queries;
+
+import net.runelite.api.Client;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.api.widgets.WidgetItem;
+
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+
+public class EquipmentItemQuery extends WidgetItemQuery
+{
+ private static final WidgetInfo[] ALL_EQUIPMENT_WIDGET_INFOS =
+ {
+ WidgetInfo.EQUIPMENT_HELMET,
+ WidgetInfo.EQUIPMENT_CAPE,
+ WidgetInfo.EQUIPMENT_AMULET,
+ WidgetInfo.EQUIPMENT_WEAPON,
+ WidgetInfo.EQUIPMENT_BODY,
+ WidgetInfo.EQUIPMENT_SHIELD,
+ WidgetInfo.EQUIPMENT_LEGS,
+ WidgetInfo.EQUIPMENT_GLOVES,
+ WidgetInfo.EQUIPMENT_BOOTS,
+ WidgetInfo.EQUIPMENT_RING,
+ WidgetInfo.EQUIPMENT_AMMO,
+ };
+
+ private final Collection slots = new ArrayList<>();
+
+ public EquipmentItemQuery slotEquals(WidgetInfo... slotWidgetInfo)
+ {
+ slots.addAll(Arrays.asList(slotWidgetInfo));
+ return this;
+ }
+
+ @Override
+ public WidgetItem[] result(Client client)
+ {
+ Collection widgetItems = getEquippedItems(client);
+ if (widgetItems != null)
+ {
+ return widgetItems.stream()
+ .filter(Objects::nonNull)
+ .filter(predicate)
+ .toArray(WidgetItem[]::new);
+ }
+ return new WidgetItem[0];
+ }
+
+ private Collection getEquippedItems(Client client)
+ {
+ Collection widgetItems = new ArrayList<>();
+ Widget equipment = client.getWidget(WidgetInfo.EQUIPMENT);
+ if (equipment != null && !equipment.isHidden())
+ {
+ if (slots.isEmpty())
+ {
+ slots.addAll(Arrays.asList(ALL_EQUIPMENT_WIDGET_INFOS));
+ }
+ for (WidgetInfo slot : slots)
+ {
+ Widget parentWidget = client.getWidget(slot);
+ Widget itemWidget = parentWidget.getChild(1);
+ // Check if background icon is hidden. if hidden, item is equipped.
+ boolean equipped = parentWidget.getChild(2).isSelfHidden();
+ // set bounds to same size as default inventory
+ Rectangle bounds = itemWidget.getBounds();
+ bounds.setBounds(bounds.x - 1, bounds.y - 1, 32, 32);
+ // Index is set to 0 because there is no set in stone order of equipment slots
+ widgetItems.add(new WidgetItem(equipped ? itemWidget.getItemId() : -1, itemWidget.getItemQuantity(), 0, bounds));
+ }
+ }
+ return widgetItems;
+ }
+}
diff --git a/runelite-api/src/main/java/net/runelite/api/queries/InventoryWidgetItemQuery.java b/runelite-api/src/main/java/net/runelite/api/queries/InventoryWidgetItemQuery.java
new file mode 100644
index 0000000000..1872eeed65
--- /dev/null
+++ b/runelite-api/src/main/java/net/runelite/api/queries/InventoryWidgetItemQuery.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2017, Devin French
+ * 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.api.queries;
+
+import net.runelite.api.Client;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.api.widgets.WidgetItem;
+
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Objects;
+
+public class InventoryWidgetItemQuery extends WidgetItemQuery
+{
+ private static final WidgetInfo[] INVENTORY_WIDGET_INFOS =
+ {
+ WidgetInfo.DEPOSIT_BOX_INVENTORY_ITEMS_CONTAINER,
+ WidgetInfo.BANK_INVENTORY_ITEMS_CONTAINER,
+ WidgetInfo.SHOP_INVENTORY_ITEMS_CONTAINER,
+ WidgetInfo.GRAND_EXCHANGE_INVENTORY_ITEMS_CONTAINER,
+ WidgetInfo.GUIDE_PRICES_INVENTORY_ITEMS_CONTAINER,
+ WidgetInfo.EQUIPMENT_INVENTORY_ITEMS_CONTAINER,
+ WidgetInfo.INVENTORY
+ };
+
+ @Override
+ public WidgetItem[] result(Client client)
+ {
+ Collection widgetItems = getInventoryItems(client);
+ if (widgetItems != null)
+ {
+ return widgetItems.stream()
+ .filter(Objects::nonNull)
+ .filter(predicate)
+ .toArray(WidgetItem[]::new);
+ }
+ return new WidgetItem[0];
+ }
+
+ private Collection getInventoryItems(Client client)
+ {
+ Collection widgetItems = new ArrayList<>();
+ for (WidgetInfo widgetInfo : INVENTORY_WIDGET_INFOS)
+ {
+ Widget inventory = client.getWidget(widgetInfo);
+ if (inventory == null || inventory.isHidden())
+ {
+ continue;
+ }
+ if (widgetInfo == WidgetInfo.INVENTORY)
+ {
+ widgetItems.addAll(inventory.getWidgetItems());
+ break;
+ }
+ else
+ {
+ Widget[] children = inventory.getDynamicChildren();
+ for (int i = 0; i < children.length; i++)
+ {
+ Widget child = children[i];
+ // set bounds to same size as default inventory
+ Rectangle bounds = child.getBounds();
+ bounds.setBounds(bounds.x - 1, bounds.y - 1, 32, 32);
+ widgetItems.add(new WidgetItem(child.getItemId(), child.getItemQuantity(), i, bounds));
+ }
+ break;
+ }
+ }
+ return widgetItems;
+ }
+}
diff --git a/runelite-api/src/main/java/net/runelite/api/queries/ShopItemQuery.java b/runelite-api/src/main/java/net/runelite/api/queries/ShopItemQuery.java
new file mode 100644
index 0000000000..cd037f0a28
--- /dev/null
+++ b/runelite-api/src/main/java/net/runelite/api/queries/ShopItemQuery.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2017, Devin French
+ * 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.api.queries;
+
+import net.runelite.api.Client;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.api.widgets.WidgetItem;
+
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Objects;
+
+public class ShopItemQuery extends WidgetItemQuery
+{
+ @Override
+ public WidgetItem[] result(Client client)
+ {
+ Collection widgetItems = getShopItems(client);
+ if (widgetItems != null)
+ {
+ return widgetItems.stream()
+ .filter(Objects::nonNull)
+ .filter(predicate)
+ .toArray(WidgetItem[]::new);
+ }
+ return new WidgetItem[0];
+ }
+
+ private Collection getShopItems(Client client)
+ {
+ Collection widgetItems = new ArrayList<>();
+ Widget shop = client.getWidget(WidgetInfo.SHOP_ITEMS_CONTAINER);
+ if (shop != null && !shop.isHidden())
+ {
+ Widget[] children = shop.getDynamicChildren();
+ for (int i = 1; i < children.length; i++)
+ {
+ Widget child = children[i];
+ // set bounds to same size as default inventory
+ Rectangle bounds = child.getBounds();
+ bounds.setBounds(bounds.x - 1, bounds.y - 1, 32, 32);
+ widgetItems.add(new WidgetItem(child.getItemId(), child.getItemQuantity(), i - 1, bounds));
+ }
+ }
+ return widgetItems;
+ }
+}
diff --git a/runelite-api/src/main/java/net/runelite/api/queries/WidgetItemQuery.java b/runelite-api/src/main/java/net/runelite/api/queries/WidgetItemQuery.java
new file mode 100644
index 0000000000..9a71a9bf3c
--- /dev/null
+++ b/runelite-api/src/main/java/net/runelite/api/queries/WidgetItemQuery.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2017, Devin French
+ * 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.api.queries;
+
+import net.runelite.api.Client;
+import net.runelite.api.Query;
+import net.runelite.api.widgets.WidgetItem;
+
+public abstract class WidgetItemQuery extends Query
+{
+
+ public WidgetItemQuery idEquals(int... ids)
+ {
+ predicate = and(item ->
+ {
+ for (int id : ids)
+ {
+ if (item.getId() == id)
+ {
+ return true;
+ }
+ }
+ return false;
+ });
+ return this;
+ }
+
+ public WidgetItemQuery indexEquals(int... indexes)
+ {
+ predicate = and(item ->
+ {
+ for (int index : indexes)
+ {
+ if (item.getIndex() == index)
+ {
+ return true;
+ }
+ }
+ return false;
+ });
+ return this;
+ }
+
+ public WidgetItemQuery quantityEquals(int quantity)
+ {
+ predicate = and(item -> item.getQuantity() == quantity);
+ return this;
+ }
+
+ @Override
+ public abstract WidgetItem[] result(Client client);
+}
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 d84bae473e..e115364f10 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
@@ -129,6 +129,7 @@ public class WidgetID
public static final int SKILLS_GROUP_ID = 320;
public static final int QUESTTAB_GROUP_ID = 629;
public static final int MUSIC_GROUP_ID = 239;
+ public static final int MUSICTAB_GROUP_ID = 239;
public static final int BARROWS_PUZZLE_GROUP_ID = 25;
static class WorldMap
@@ -555,7 +556,7 @@ public class WidgetID
static final int CURRENT_WAVE_WIDGET = 4;
static final int CURRENT_WAVE = 5;
static final int CALL_WIDGET = 6;
- static final int CALL_TEXT = 7;
+ static final int HEARD_CALL = 7;
static final int TO_CALL_WIDGET = 8;
static final int TO_CALL = 9;
static final int ROLE_SPRITE = 10;
@@ -563,6 +564,7 @@ public class WidgetID
static final int REWARD_TEXT = 57;
}
+
static class LevelUp
{
static final int SKILL = 1;
@@ -701,16 +703,154 @@ public class WidgetID
static class StandardSpellBook
{
static final int LUMBRIDGE_HOME_TELEPORT = 4;
+ static final int WIND_STRIKE = 5;
+ static final int CONFUSE = 6;
+ static final int ENCHANT_CROSSBOW_BOLT = 7;
+ static final int WATER_STRIKE = 8;
+ static final int LVL_1_ENCHANT = 9;
+ static final int EARTH_STRIKE = 10;
+ static final int WEAKEN = 11;
+ static final int FIRE_STRIKE = 12;
+ static final int BONES_TO_BANANAS = 13;
+ static final int WIND_BOLT = 14;
+ static final int CURSE = 15;
+ static final int BIND = 16;
+ static final int LOW_LEVEL_ALCHEMY = 17;
+ static final int WATER_BOLT = 18;
+ static final int VARROCK_TELEPORT = 19;
+ static final int LVL_2_ENCHANT = 20;
+ static final int EARTH_BOLT = 21;
+ static final int LUMBRIDGE_TELEPORT = 22;
+ static final int TELEKINETIC_GRAB = 23;
+ static final int FIRE_BOLT = 24;
+ static final int FALADOR_TELEPORT = 25;
+ static final int CRUMBLE_UNDEAD = 26;
+ static final int TELEPORT_TO_HOUSE = 27;
+ static final int WIND_BLAST = 28;
+ static final int SUPERHEAT_ITEM = 29;
+ static final int CAMELOT_TELEPORT = 30;
+ static final int WATER_BLAST = 31;
+ static final int LVL_3_ENCHANT = 32;
+ static final int IBAN_BLAST = 33;
+ static final int SNARE = 34;
+ static final int MAGIC_DART = 35;
+ static final int ARDOUGNE_TELEPORT = 36;
+ static final int EARTH_BLAST = 37;
+ static final int HIGH_LEVEL_ALCHEMY = 38;
+ static final int CHARGE_WATER_ORB = 39;
+ static final int LVL_4_ENCHANT = 40;
+ static final int WATCHTOWER_TELEPORT = 41;
+ static final int FIRE_BLAST = 42;
+ static final int CHARGE_EARTH_ORB = 43;
+ static final int BONES_TO_PEACHES = 44;
+ static final int SARADOMIN_STRIKE = 45;
+ static final int CLAWS_OF_GUTHIX = 46;
+ static final int FLAMES_OF_ZAMORAK = 47;
+ static final int TROLLHEIM_TELEPORT = 48;
+ static final int WIND_WAVE = 49;
+ static final int HARGE_FIRE_ORB = 50;
+ static final int TELEPORT_TO_APE_ATOLL = 51;
+ static final int WATER_WAVE = 52;
+ static final int CHARGE_AIR_ORB = 53;
+ static final int VULNERABILITY = 54;
+ static final int LVL_5_ENCHANT = 55;
+ static final int TELEPORT_TO_KOUREND = 56;
+ static final int EARTH_WAVE = 57;
+ static final int ENFEEBLE = 58;
+ static final int TELEOTHER_LUMBRIDGE = 59;
+ static final int FIRE_WAVE = 60;
+ static final int ENTANGLE = 61;
+ static final int STUN = 62;
+ static final int CHARGE = 63;
+ static final int WIND_SURGE = 64;
+ static final int TELEOTHER_FALADOR = 65;
+ static final int WATER_SURGE = 66;
+ static final int TELE_BLOCK = 67;
+ static final int BOUNTY_TARGET_TELEPORT = 68;
+ static final int LVL_6_ENCHANT = 69;
+ static final int TELEOTHER_CAMELOT = 70;
+ static final int EARTH_SURGE = 71;
+ static final int LVL_7_ENCHANT = 72;
+ static final int FIRE_SURGE = 73;
}
static class AncientSpellBook
{
+ static final int BOUNTY_TARGET_TELEPORT = 68;
+ static final int ICE_RUSH = 74;
+ static final int ICE_BLITZ = 75;
+ static final int ICE_BURST = 76;
+ static final int ICE_BARRAGE = 77;
+ static final int BLOOD_RUSH = 78;
+ static final int BLOOD_BLITZ = 79;
+ static final int BLOOD_BURST = 80;
+ static final int BLOOD_BARRAGE = 81;
+ static final int SMOKE_RUSH = 82;
+ static final int SMOKE_BLITZ = 83;
+ static final int SMOKE_BURST = 84;
+ static final int SMOKE_BARRAGE = 85;
+ static final int SHADOW_RUSH = 86;
+ static final int SHADOW_BLITZ = 87;
+ static final int SHADOW_BURST = 88;
+ static final int SHADOW_BARRAGE = 89;
+ static final int PADDEWWA_TELEPORT = 90;
+ static final int SENNTISTEN_TELEPORT = 91;
+ static final int KHARYRLL_TELEPORT = 92;
+ static final int LASSAR_TELEPORT = 93;
+ static final int DAREEYAK_TELEPORT = 94;
+ static final int CARRALLANGER_TELEPORT = 95;
+ static final int ANNAKARL_TELEPORT = 96;
+ static final int GHORROCK_TELEPORT = 97;
static final int EDGEVILLE_HOME_TELEPORT = 98;
}
static class LunarSpellBook
{
+ static final int BOUNTY_TARGET_TELEPORT = 68;
static final int LUNAR_HOME_TELEPORT = 99;
+ static final int BAKE_PIE = 100;
+ static final int CURE_PLANT = 101;
+ static final int MONSTER_EXAMINE = 102;
+ static final int NPC_CONTACT = 103;
+ static final int CURE_OTHER = 104;
+ static final int HUMIDIFY = 105;
+ static final int MOONCLAN_TELEPORT = 106;
+ static final int TELE_GROUP_MOONCLAN = 107;
+ static final int CURE_ME = 108;
+ static final int HUNTER_KIT = 109;
+ static final int WATERBIRTH_TELEPORT = 110;
+ static final int TELE_GROUP_WATERBIRTH = 111;
+ static final int CURE_GROUP = 112;
+ static final int STAT_SPY = 113;
+ static final int BARBARIAN_TELEPORT = 114;
+ static final int TELE_GROUP_BARBARIAN = 115;
+ static final int SUPERGLASS_MAKE = 116;
+ static final int TAN_LEATHER = 117;
+ static final int KHAZARD_TELEPORT = 118;
+ static final int TELE_GROUP_KHAZARD = 119;
+ static final int DREAM = 120;
+ static final int STRING_JEWELLERY = 121;
+ static final int STAT_RESTORE_POT_SHARE = 122;
+ static final int MAGIC_IMBUE = 123;
+ static final int FERTILE_SOIL = 124;
+ static final int BOOST_POTION_SHARE = 125;
+ static final int FISHING_GUILD_TELEPORT = 126;
+ static final int TELE_GROUP_FISHING_GUILD = 127;
+ static final int PLANK_MAKE = 128;
+ static final int CATHERBY_TELEPORT = 129;
+ static final int TELE_GROUP_CATHERBY = 130;
+ static final int RECHARGE_DRAGONSTONE = 131;
+ static final int ICE_PLATEAU_TELEPORT = 132;
+ static final int TELE_GROUP_ICE_PLATEAU = 133;
+ static final int ENERGY_TRANSFER = 134;
+ static final int HEAL_OTHER = 135;
+ static final int VENGEANCE_OTHER = 136;
+ static final int VENGEANCE = 137;
+ static final int HEAL_GROUP = 138;
+ static final int SPELLBOOK_SWAP = 139;
+ static final int GEOMANCY = 140;
+ static final int SPIN_FLAX = 141;
+ static final int OURANIA_TELEPORT = 142;
}
static class ArceuusSpellBook
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 5f12519d5d..88c1677891 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
@@ -351,6 +351,7 @@ public enum WidgetInfo
BA_COLL_WAVE_TEXT(WidgetID.BA_COLLECTOR_GROUP_ID, WidgetID.BarbarianAssault.CURRENT_WAVE),
BA_COLL_CALL_TEXT(WidgetID.BA_COLLECTOR_GROUP_ID, WidgetID.BarbarianAssault.TO_CALL),
BA_COLL_LISTEN_TEXT(WidgetID.BA_COLLECTOR_GROUP_ID, WidgetID.BarbarianAssault.CORRECT_STYLE),
+ BA_COLL_HEARD_TEXT(WidgetID.BA_COLLECTOR_GROUP_ID, WidgetID.BarbarianAssault.HEARD_CALL),
BA_COLL_ROLE_TEXT(WidgetID.BA_COLLECTOR_GROUP_ID, WidgetID.BarbarianAssault.ROLE),
BA_COLL_ROLE_SPRITE(WidgetID.BA_COLLECTOR_GROUP_ID, WidgetID.BarbarianAssault.ROLE_SPRITE),
@@ -450,10 +451,56 @@ public enum WidgetInfo
MINIGAME_TELEPORT_BUTTON(WidgetID.MINIGAME_TAB_ID, WidgetID.Minigames.TELEPORT_BUTTON),
+ /* STANDARD SPELL BOOK WIDGETS*/
SPELL_LUMBRIDGE_HOME_TELEPORT(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.StandardSpellBook.LUMBRIDGE_HOME_TELEPORT),
- SPELL_EDGEVILLE_HOME_TELEPORT(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.EDGEVILLE_HOME_TELEPORT),
+ SPELL_BIND(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.StandardSpellBook.BIND),
+ SPELL_SNARE(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.StandardSpellBook.SNARE),
+ SPELL_ENTANGLE(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.StandardSpellBook.ENTANGLE),
+ SPELL_TELE_BLOCK(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.StandardSpellBook.TELE_BLOCK),
+ SPELL_FIRE_SURGE(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.StandardSpellBook.FIRE_SURGE),
+ SPELL_BOUNTY_TARGET_TELEPORT2(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.StandardSpellBook.BOUNTY_TARGET_TELEPORT),
+ /* END OF STANDARD SPELL BOOK WIDGETS*/
+
+ /* LUNAR SPELL BOOK WIDGETS*/
SPELL_LUNAR_HOME_TELEPORT(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.LunarSpellBook.LUNAR_HOME_TELEPORT),
+ SPELL_VENGEANCE_OTHER(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.LunarSpellBook.VENGEANCE_OTHER),
+ SPELL_VENGEANCE(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.LunarSpellBook.VENGEANCE),
+ SPELL_BOUNTY_TARGET_TELEPORT3(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.LunarSpellBook.BOUNTY_TARGET_TELEPORT),
+ /* LUNA SPELL BOOK WIDGETS*/
+
+ /* ARCEUUS SPELL BOOK WIDGETS*/
SPELL_ARCEUUS_HOME_TELEPORT(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.ArceuusSpellBook.ARCEUUS_HOME_TELEPORT),
+ /* END OF ARCEUUS SPELL BOOK WIDGETS*/
+
+ /* ANCIENT SPELL BOOK WIDGETS*/
+ SPELL_ICE_RUSH(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.ICE_RUSH),
+ SPELL_ICE_BLITZ(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.ICE_BLITZ),
+ SPELL_ICE_BURST(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.ICE_BURST),
+ SPELL_ICE_BARRAGE(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.ICE_BARRAGE),
+ SPELL_BLOOD_RUSH(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.BLOOD_RUSH),
+ SPELL_BLOOD_BLITZ(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.BLOOD_BLITZ),
+ SPELL_BLOOD_BURST(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.BLOOD_BURST),
+ SPELL_BLOOD_BARRAGE(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.BLOOD_BARRAGE),
+ SPELL_SMOKE_RUSH(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.SMOKE_RUSH),
+ SPELL_SMOKE_BLITZ(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.SMOKE_BLITZ),
+ SPELL_SMOKE_BURST(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.SMOKE_BURST),
+ SPELL_SMOKE_BARRAGE(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.SMOKE_BARRAGE),
+ SPELL_SHADOW_RUSH(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.SHADOW_RUSH),
+ SPELL_SHADOW_BLITZ(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.SHADOW_BLITZ),
+ SPELL_SHADOW_BURST(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.SHADOW_BURST),
+ SPELL_SHADOW_BARRAGE(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.SHADOW_BARRAGE),
+ SPELL_PADDEWWA_TELEPORT(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.PADDEWWA_TELEPORT),
+ SPELL_SENNTISTEN_TELEPORT(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.SENNTISTEN_TELEPORT),
+ SPELL_KHARYRLL_TELEPORT(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.KHARYRLL_TELEPORT),
+ SPELL_LASSAR_TELEPORT(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.LASSAR_TELEPORT),
+ SPELL_DAREEYAK_TELEPORT(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.DAREEYAK_TELEPORT),
+ SPELL_CARRALLANGER_TELEPORT(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.CARRALLANGER_TELEPORT),
+ SPELL_ANNAKARL_TELEPORT(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.ANNAKARL_TELEPORT),
+ SPELL_GHORROCK_TELEPORT(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.GHORROCK_TELEPORT),
+ SPELL_EDGEVILLE_HOME_TELEPORT(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.EDGEVILLE_HOME_TELEPORT),
+ SPELL_BOUNTY_TARGET_TELEPORT(WidgetID.SPELLBOOK_GROUP_ID, WidgetID.AncientSpellBook.BOUNTY_TARGET_TELEPORT),
+
+ /* END OF ANCIENT SPELL BOOK WIDGETS*/
PVP_SKULL_CONTAINER(WidgetID.PVP_GROUP_ID, WidgetID.Pvp.SKULL_CONTAINER),
PVP_WORLD_SAFE_ZONE(WidgetID.PVP_GROUP_ID, WidgetID.Pvp.SAFE_ZONE),
@@ -473,12 +520,25 @@ public enum WidgetInfo
QUESTLIST_BOX(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.BOX),
QUESTLIST_CONTAINER(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.CONTAINER),
- QUESTLIST_SCROLLBAR(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.SCROLLBAR),
+ QUESTLIST_SCROLLBAR(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.SCROLLBAR),
QUESTLIST_FREE_CONTAINER(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.FREE_CONTAINER),
QUESTLIST_MEMBERS_CONTAINER(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.MEMBERS_CONTAINER),
QUESTLIST_MINIQUEST_CONTAINER(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.MINIQUEST_CONTAINER),
+ QUESTTAB_QUEST_TAB(WidgetID.QUESTTAB_GROUP_ID, WidgetID.QuestTab.QUEST_TAB),
- QUESTTAB_QUEST_TAB(WidgetID.QUESTTAB_GROUP_ID, WidgetID.QuestTab.QUEST_TAB);
+ MUSICTAB_INTERFACE(WidgetID.MUSICTAB_GROUP_ID, 1),
+ MUSICTAB_SONG_BOX(WidgetID.MUSICTAB_GROUP_ID, 2),
+ MUSICTAB_ALL_SONGS(WidgetID.MUSICTAB_GROUP_ID, 3),
+ MUSICTAB_SCROLLBAR(WidgetID.MUSICTAB_GROUP_ID, 4),
+ MUSICTAB_PLAYING(WidgetID.MUSICTAB_GROUP_ID, 5),
+ MUSICTAB_CURRENT_SONG_NAME(WidgetID.MUSICTAB_GROUP_ID, 6),
+ MUSICTAB_AUTO_BUTTON_LISTENER(WidgetID.MUSICTAB_GROUP_ID, 7),
+ MUSICTAB_AUTO_BUTTON(WidgetID.MUSICTAB_GROUP_ID, 8),
+ MUSICTAB_MANUAL_BUTTON_LISTENER(WidgetID.MUSICTAB_GROUP_ID, 9),
+ MUSICTAB_MANUAL_BUTTON(WidgetID.MUSICTAB_GROUP_ID, 10),
+ MUSICTAB_LOOP_BUTTON_LISTENER(WidgetID.MUSICTAB_GROUP_ID, 11),
+ MUSICTAB_LOOP_BUTTON(WidgetID.MUSICTAB_GROUP_ID, 12),
+ MUSICTAB_UNLOCKED_SONGS(WidgetID.MUSICTAB_GROUP_ID, 13);
private final int groupId;
private final int childId;
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 ba3169020d..74594a8ed4 100644
--- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java
+++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java
@@ -77,6 +77,7 @@ public class RuneLite
{
public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite");
public static final File PROFILES_DIR = new File(RUNELITE_DIR, "profiles");
+ public static final File PLUGIN_DIR = new File(RUNELITE_DIR, "plugins");
public static final File SCREENSHOT_DIR = new File(RUNELITE_DIR, "screenshots");
@Getter
@@ -243,6 +244,9 @@ public class RuneLite
// Load the session, including saved configuration
sessionManager.loadSession();
+ // Begin watching for new plugins
+ pluginManager.watch();
+
// Tell the plugin manager if client is outdated or not
pluginManager.setOutdated(isOutdated);
diff --git a/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java b/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java
index 546f7e77bc..da48d2d116 100644
--- a/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java
@@ -63,6 +63,17 @@ public interface RuneLiteConfig extends Config
return false;
}
+ @ConfigItem(
+ keyName = "enablePlugins",
+ name = "Enable loading of external plugins",
+ description = "Enable loading of external plugins",
+ position = 10
+ )
+ default boolean enablePlugins()
+ {
+ return false;
+ }
+
@ConfigItem(
keyName = "containInScreen",
name = "Contain in screen",
@@ -272,4 +283,4 @@ public interface RuneLiteConfig extends Config
{
return 35;
}
-}
\ No newline at end of file
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java
index be5efa9081..192f587665 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java
@@ -28,10 +28,15 @@ import com.google.inject.Binder;
import com.google.inject.Injector;
import com.google.inject.Module;
+import java.io.File;
+
public abstract class Plugin implements Module
{
protected Injector injector;
+ public File file;
+ public PluginClassLoader loader;
+
@Override
public void configure(Binder binder)
{
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginClassLoader.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginClassLoader.java
new file mode 100644
index 0000000000..3f6f44ec81
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginClassLoader.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2016-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;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+/**
+ * A classloader for external plugins
+ *
+ * @author Adam
+ */
+public class PluginClassLoader extends URLClassLoader
+{
+ private final ClassLoader parent;
+
+ public PluginClassLoader(File plugin, ClassLoader parent) throws MalformedURLException
+ {
+ super(
+ new URL[]
+ {
+ plugin.toURI().toURL()
+ },
+ null // null or else class path scanning includes everything from the main class loader
+ );
+
+ this.parent = parent;
+ }
+
+ @Override
+ public Class> loadClass(String name) throws ClassNotFoundException
+ {
+ try
+ {
+ return super.loadClass(name);
+ }
+ catch (ClassNotFoundException ex)
+ {
+ // fall back to main class loader
+ return parent.loadClass(name);
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java
index 73d6bf32e4..62e8a22aa8 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java
@@ -92,6 +92,9 @@ public class PluginManager
private final String runeliteGroupName = RuneLiteConfig.class
.getAnnotation(ConfigGroup.class).value();
+ @Inject
+ PluginWatcher pluginWatcher;
+
@Setter
boolean isOutdated;
@@ -113,6 +116,11 @@ public class PluginManager
this.sceneTileManager = sceneTileManager;
}
+ public void watch()
+ {
+ pluginWatcher.start();
+ }
+
@Subscribe
public void onSessionOpen(SessionOpen event)
{
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginWatcher.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginWatcher.java
new file mode 100644
index 0000000000..031d408c03
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginWatcher.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2016-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;
+
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URLClassLoader;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchEvent.Kind;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.List;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.client.RuneLite;
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.config.RuneLiteConfig;
+
+@Singleton
+@Slf4j
+public class PluginWatcher extends Thread
+{
+ private static final File BASE = RuneLite.PLUGIN_DIR;
+
+ private final RuneLiteConfig runeliteConfig;
+ private final PluginManager pluginManager;
+ private final WatchService watchService;
+ private final WatchKey watchKey;
+
+ @Inject
+ private ConfigManager configManager;
+
+ @Inject
+ public PluginWatcher(RuneLiteConfig runeliteConfig, PluginManager pluginManager) throws IOException
+ {
+ this.runeliteConfig = runeliteConfig;
+ this.pluginManager = pluginManager;
+
+ setName("Plugin Watcher");
+ setDaemon(true);
+
+ watchService = FileSystems.getDefault().newWatchService();
+ BASE.mkdirs();
+ Path dir = BASE.toPath();
+ watchKey = dir.register(watchService, ENTRY_MODIFY, ENTRY_DELETE);
+ }
+
+ public void cancel()
+ {
+ watchKey.cancel();
+ }
+
+ @Override
+ public void run()
+ {
+ if (runeliteConfig.enablePlugins())
+ {
+ scan();
+ }
+
+ for (;;)
+ {
+ try
+ {
+ WatchKey key = watchService.take();
+ Thread.sleep(50);
+
+ if (!runeliteConfig.enablePlugins())
+ {
+ key.reset();
+ continue;
+ }
+
+ for (WatchEvent> event : key.pollEvents())
+ {
+ Kind> kind = event.kind();
+ Path path = (Path) event.context();
+ File file = new File(BASE, path.toFile().getName());
+
+ log.debug("Event {} file {}", kind, file);
+
+ if (kind == ENTRY_MODIFY)
+ {
+ Plugin existing = findPluginForFile(file);
+ if (existing != null)
+ {
+ log.info("Reloading plugin {}", file);
+ unload(existing);
+ }
+ else
+ {
+ log.info("Loading plugin {}", file);
+ }
+
+ load(file);
+ }
+ else if (kind == ENTRY_DELETE)
+ {
+ Plugin existing = findPluginForFile(file);
+ if (existing != null)
+ {
+ log.info("Unloading plugin {}", file);
+
+ unload(existing);
+ }
+ }
+ }
+ key.reset();
+
+ }
+ catch (InterruptedException ex)
+ {
+ log.warn("error polling for plugins", ex);
+ }
+ }
+ }
+
+ private void scan()
+ {
+ for (File file : BASE.listFiles())
+ {
+ if (!file.getName().endsWith(".jar"))
+ {
+ continue;
+ }
+
+ log.info("Loading plugin from {}", file);
+ load(file);
+ }
+ }
+
+ private Plugin findPluginForFile(File file)
+ {
+ for (Plugin plugin : pluginManager.getPlugins())
+ {
+ if (plugin.file != null && plugin.file.equals(file))
+ {
+ return plugin;
+ }
+ }
+ return null;
+ }
+
+ private void load(File pluginFile)
+ {
+ PluginClassLoader loader;
+ try
+ {
+ loader = new PluginClassLoader(pluginFile, getClass().getClassLoader());
+ }
+ catch (MalformedURLException ex)
+ {
+ log.warn("Error loading plugin", ex);
+ return;
+ }
+
+ List loadedPlugins;
+ try
+ {
+ loadedPlugins = pluginManager.scanAndInstantiate(loader, null);
+ }
+ catch (IOException ex)
+ {
+ close(loader);
+ log.warn("Error loading plugin", ex);
+ return;
+ }
+
+ if (loadedPlugins.isEmpty())
+ {
+ close(loader);
+ log.warn("No plugin found in plugin {}", pluginFile);
+ return;
+ }
+
+ if (loadedPlugins.size() != 1)
+ {
+ close(loader);
+ log.warn("You can not have more than one plugin per jar");
+ return;
+ }
+
+ Plugin plugin = loadedPlugins.get(0);
+ plugin.file = pluginFile;
+ plugin.loader = loader;
+
+ // Initialize default configuration
+ Injector injector = plugin.getInjector();
+ for (Key> key : injector.getAllBindings().keySet())
+ {
+ Class> type = key.getTypeLiteral().getRawType();
+ if (Config.class.isAssignableFrom(type))
+ {
+ Config config = (Config) injector.getInstance(key);
+ configManager.setDefaultConfiguration(config, false);
+ }
+ }
+
+ try
+ {
+ pluginManager.startPlugin(plugin);
+ }
+ catch (PluginInstantiationException ex)
+ {
+ close(loader);
+ log.warn("unable to start plugin", ex);
+ return;
+ }
+
+ // Plugin is now running
+ pluginManager.add(plugin);
+ }
+
+ private void unload(Plugin plugin)
+ {
+ try
+ {
+ pluginManager.stopPlugin(plugin);
+ }
+ catch (PluginInstantiationException ex)
+ {
+ log.warn("unable to stop plugin", ex);
+ }
+
+ pluginManager.remove(plugin); // remove it regardless
+
+ close(plugin.loader);
+ }
+
+ private void close(URLClassLoader classLoader)
+ {
+ try
+ {
+ classLoader.close();
+ }
+ catch (IOException ex1)
+ {
+ log.warn(null, ex1);
+ }
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragConfig.java
index 1bed02603e..5124905378 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragConfig.java
@@ -24,9 +24,14 @@
*/
package net.runelite.client.plugins.antidrag;
+import java.awt.Color;
+import java.awt.event.KeyEvent;
+import net.runelite.client.config.Alpha;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
+import net.runelite.client.config.Keybind;
+import net.runelite.client.config.ModifierlessKeybind;
@ConfigGroup("antiDrag")
public interface AntiDragConfig extends Config
@@ -41,4 +46,49 @@ public interface AntiDragConfig extends Config
{
return 600 / 20; // one game tick
}
+
+ @ConfigItem(
+ keyName = "keybind",
+ name = "keybind",
+ description = "The keybind you want to use for antidrag",
+ position = 2
+ )
+ default Keybind key()
+ {
+ return new ModifierlessKeybind(KeyEvent.VK_SHIFT, 0);
+ }
+
+ @ConfigItem(
+ keyName = "reqfocus",
+ name = "Reset on focus loss",
+ description = "Disable antidrag when losing focus (like alt tabbing)",
+ position = 3
+ )
+ default boolean reqfocus()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "overlay",
+ name = "Enable overlay",
+ description = "Do you really need a description?",
+ position = 4
+ )
+ default boolean overlay()
+ {
+ return true;
+ }
+
+ @Alpha
+ @ConfigItem(
+ keyName = "color",
+ name = "Overlay color",
+ description = "Change the overlay color, duh",
+ position = 5
+ )
+ default Color color()
+ {
+ return new Color(255, 0, 0, 30);
+ }
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragOverlay.java
new file mode 100644
index 0000000000..c19fc3d6c1
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragOverlay.java
@@ -0,0 +1,47 @@
+package net.runelite.client.plugins.antidrag;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Rectangle;
+import net.runelite.api.Client;
+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.OverlayPriority;
+
+@Singleton
+public class AntiDragOverlay extends Overlay
+{
+ private static final int RADIUS = 20;
+
+ private Client client;
+ private AntiDragConfig config;
+
+ @Inject
+ private AntiDragOverlay(Client client, AntiDragConfig config)
+ {
+ this.config = config;
+ this.client = client;
+ setPosition(OverlayPosition.TOOLTIP);
+ setPriority(OverlayPriority.HIGHEST);
+ setLayer(OverlayLayer.ALWAYS_ON_TOP);
+ }
+
+ @Override
+ public Dimension render(Graphics2D g)
+ {
+ final Color color = config.color();
+ g.setColor(color);
+
+ final net.runelite.api.Point mouseCanvasPosition = client.getMouseCanvasPosition();
+ final Point mousePosition = new Point(mouseCanvasPosition.getX() - RADIUS, mouseCanvasPosition.getY() - RADIUS);
+ final Rectangle bounds = new Rectangle(mousePosition.x, mousePosition.y, 2 * RADIUS, 2 * RADIUS);
+ g.fillOval(bounds.x, bounds.y, bounds.height, bounds.width);
+
+ return bounds.getSize();
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragPlugin.java
index 26f926be4a..de431ff051 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragPlugin.java
@@ -25,25 +25,26 @@
package net.runelite.client.plugins.antidrag;
import com.google.inject.Provides;
-import java.awt.event.KeyEvent;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.events.FocusChanged;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
-import net.runelite.client.input.KeyListener;
import net.runelite.client.input.KeyManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+import net.runelite.client.util.HotkeyListener;
@PluginDescriptor(
name = "Shift Anti Drag",
description = "Prevent dragging an item for a specified delay",
tags = {"antidrag", "delay", "inventory", "items"}
)
-public class AntiDragPlugin extends Plugin implements KeyListener
+public class AntiDragPlugin extends Plugin
{
private static final int DEFAULT_DELAY = 5;
+ private boolean toggleDrag;
@Inject
private Client client;
@@ -51,6 +52,12 @@ public class AntiDragPlugin extends Plugin implements KeyListener
@Inject
private AntiDragConfig config;
+ @Inject
+ private AntiDragOverlay overlay;
+
+ @Inject
+ private OverlayManager overlayManager;
+
@Inject
private KeyManager keyManager;
@@ -63,46 +70,50 @@ public class AntiDragPlugin extends Plugin implements KeyListener
@Override
protected void startUp() throws Exception
{
- keyManager.registerKeyListener(this);
+ keyManager.registerKeyListener(hotkeyListener);
+ toggleDrag = false;
+
}
@Override
protected void shutDown() throws Exception
{
client.setInventoryDragDelay(DEFAULT_DELAY);
- keyManager.unregisterKeyListener(this);
+ keyManager.unregisterKeyListener(hotkeyListener);
+ toggleDrag = false;
+ overlayManager.remove(overlay);
}
- @Override
- public void keyTyped(KeyEvent e)
+ private final HotkeyListener hotkeyListener = new HotkeyListener(() -> config.key())
{
-
- }
-
- @Override
- public void keyPressed(KeyEvent e)
- {
- if (e.getKeyCode() == KeyEvent.VK_SHIFT)
+ @Override
+ public void hotkeyPressed()
{
- client.setInventoryDragDelay(config.dragDelay());
- }
- }
+ toggleDrag = !toggleDrag;
+ if (toggleDrag)
+ {
+ if (config.overlay())
+ {
+ overlayManager.add(overlay);
+ }
- @Override
- public void keyReleased(KeyEvent e)
- {
- if (e.getKeyCode() == KeyEvent.VK_SHIFT)
- {
- client.setInventoryDragDelay(DEFAULT_DELAY);
+ client.setInventoryDragDelay(config.dragDelay());
+ }
+ else
+ {
+ overlayManager.remove(overlay);
+ client.setInventoryDragDelay(DEFAULT_DELAY);
+ }
}
- }
+ };
@Subscribe
public void onFocusChanged(FocusChanged focusChanged)
{
- if (!focusChanged.isFocused())
+ if (!focusChanged.isFocused() && config.reqfocus())
{
client.setInventoryDragDelay(DEFAULT_DELAY);
+ overlayManager.remove(overlay);
}
}
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeProjectile.java b/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeProjectile.java
new file mode 100644
index 0000000000..8bd41f2610
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeProjectile.java
@@ -0,0 +1,58 @@
+/*
+ * 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.aoewarnings;
+
+import net.runelite.api.coords.LocalPoint;
+
+import java.time.Instant;
+
+public class AoeProjectile
+{
+ private final Instant startTime;
+ private final LocalPoint targetPoint;
+ private final AoeProjectileInfo aoeProjectileInfo;
+
+ public AoeProjectile(Instant startTime, LocalPoint targetPoint, AoeProjectileInfo aoeProjectileInfo)
+ {
+ this.startTime = startTime;
+ this.targetPoint = targetPoint;
+ this.aoeProjectileInfo = aoeProjectileInfo;
+ }
+
+ public Instant getStartTime()
+ {
+ return startTime;
+ }
+
+ public LocalPoint getTargetPoint()
+ {
+ return targetPoint;
+ }
+
+ public AoeProjectileInfo getAoeProjectileInfo()
+ {
+ return aoeProjectileInfo;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeProjectileInfo.java b/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeProjectileInfo.java
new file mode 100644
index 0000000000..4d4f59a650
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeProjectileInfo.java
@@ -0,0 +1,128 @@
+package net.runelite.client.plugins.aoewarnings;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import net.runelite.api.ProjectileID;
+
+public enum AoeProjectileInfo
+{
+ LIZARDMAN_SHAMAN_AOE(ProjectileID.LIZARDMAN_SHAMAN_AOE, 3000, 3),
+ CRAZY_ARCHAEOLOGIST_AOE(ProjectileID.CRAZY_ARCHAEOLOGIST_AOE, 3000, 3),
+ ICE_DEMON_RANGED_AOE(ProjectileID.ICE_DEMON_RANGED_AOE, 3000, 3),
+ /**
+ * When you don't have pray range on ice demon does an ice barrage
+ */
+ ICE_DEMON_ICE_BARRAGE_AOE(ProjectileID.ICE_DEMON_ICE_BARRAGE_AOE, 3000, 3),
+ /**
+ * The AOE when vasa first starts
+ */
+ VASA_AWAKEN_AOE(ProjectileID.VASA_AWAKEN_AOE, 4500, 3),
+ VASA_RANGED_AOE(ProjectileID.VASA_RANGED_AOE, 3000, 3),
+ TEKTON_METEOR_AOE(ProjectileID.TEKTON_METEOR_AOE, 4000, 3),
+
+ /**
+ * The AOEs of Vorkath
+ */
+ VORKATH_BOMB(ProjectileID.VORKATH_BOMB_AOE, 2400, 3),
+ VORKATH_POISON_POOL(ProjectileID.VORKATH_POISON_POOL_AOE, 1800, 1),
+ VORKATH_SPAWN(ProjectileID.VORKATH_SPAWN_AOE, 3000, 1), //extra tick because hard to see otherwise
+ VORKATH_TICK_FIRE(ProjectileID.VORKATH_TICK_FIRE_AOE, 600, 1),
+
+ /**
+ * the AOEs of Galvek
+ */
+ GALVEK_MINE(ProjectileID.GALVEK_MINE, 3600, 3),
+ GALVEK_BOMB(ProjectileID.GALVEK_BOMB, 2400, 3),
+
+ DAWN_FREEZE(ProjectileID.DAWN_FREEZE, 3000, 3),
+ DUSK_CEILING(ProjectileID.DUSK_CEILING, 3000, 3),
+
+ /**
+ * the AOE of Vet'ion
+ */
+ VETION_LIGHTNING(ProjectileID.VETION_LIGHTNING, 3000, 1),
+
+ /**
+ * the AOE of Chaos Fanatic
+ */
+ CHAOS_FANATIC(ProjectileID.CHAOS_FANATIC_AOE, 3000, 1),
+
+ /**
+ * the AOE of the Corporeal Beast
+ */
+
+ CORPOREAL_BEAST(ProjectileID.CORPOREAL_BEAST_AOE, 3000, 1),
+ CORPOREAL_BEAST_DARK_CORE(ProjectileID.CORPOREAL_BEAST_DARK_CORE_AOE, 3000, 3),
+
+ /**
+ * the AOEs of The Great Olm
+ * missing ids and length, please help
+ */
+ OLM_FALLING_CRYSTAL(1357, 3000, 3),
+ OLM_BURNING(1349, 2400, 1),
+ OLM_FALLING_CRYSTAL_TRAIL(1352, 2400, 1),
+ OLM_ACID_TRAIL(1354, 2400, 1),
+ OLM_FIRE_LINE(1347, 2400, 1),
+
+ /**
+ * the AOE of the Wintertodt snow that falls
+ */
+ WINTERTODT_SNOW_FALL(1310, 4000, 3);
+
+
+ /**
+ * The id of the projectile to trigger this AoE warning
+ */
+ private final int id;
+
+ /**
+ * How long the indicator should last for this AoE warning This might
+ * need to be a bit longer than the projectile actually takes to land as
+ * there is a fade effect on the warning
+ */
+ private final Duration lifeTime;
+
+ /**
+ * The size of the splash radius of the AoE warning Ex. Lizardman shaman
+ * AoE is a 3x3, so aoeSize = 3
+ */
+ private final int aoeSize;
+
+ private static final Map map = new HashMap<>();
+
+ static
+ {
+ for (AoeProjectileInfo aoe : values())
+ {
+ map.put(aoe.id, aoe);
+ }
+ }
+
+ AoeProjectileInfo(int id, int lifeTimeMillis, int aoeSize)
+ {
+ this.id = id;
+ this.lifeTime = Duration.ofMillis(lifeTimeMillis);
+ this.aoeSize = aoeSize;
+ }
+
+ public Duration getLifeTime()
+ {
+ return lifeTime;
+ }
+
+ public int getId()
+ {
+ return id;
+ }
+
+ public int getAoeSize()
+ {
+ return aoeSize;
+ }
+
+ public static AoeProjectileInfo getById(int id)
+ {
+ return map.get(id);
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeWarningConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeWarningConfig.java
new file mode 100644
index 0000000000..af1b361ec0
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeWarningConfig.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2018, 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.aoewarnings;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("aoe")
+public interface AoeWarningConfig extends Config
+{
+ @ConfigItem(
+ keyName = "enabled",
+ name = "AoE Warnings Enabled",
+ description = "Configures whether or not AoE Projectile Warnings plugin is displayed"
+ )
+ default boolean enabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "lizardmanaoe",
+ name = "Lizardman Shamans",
+ description = "Configures whether or not AoE Projectile Warnings for Lizardman Shamans is displayed"
+ )
+ default boolean isShamansEnabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "archaeologistaoe",
+ name = "Crazy Archaeologist",
+ description = "Configures whether or not AoE Projectile Warnings for Archaeologist is displayed"
+ )
+ default boolean isArchaeologistEnabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "icedemon",
+ name = "Ice Demon",
+ description = "Configures whether or not AoE Projectile Warnings for Ice Demon is displayed"
+ )
+ default boolean isIceDemonEnabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "vasa",
+ name = "Vasa",
+ description = "Configures whether or not AoE Projectile Warnings for Vasa is displayed"
+ )
+ default boolean isVasaEnabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "tekton",
+ name = "Tekton",
+ description = "Configures whether or not AoE Projectile Warnings for Tekton is displayed"
+ )
+ default boolean isTektonEnabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "vorkath",
+ name = "Vorkath",
+ description = "Configures whether or not AoE Projectile Warnings for Vorkath are displayed"
+ )
+ default boolean isVorkathEnabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "galvek",
+ name = "Galvek",
+ description = "Configures whether or not AoE Projectile Warnings for Galvek are displayed"
+ )
+ default boolean isGalvekEnabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "gargboss",
+ name = "Gargoyle Boss",
+ description = "Configs whether or not AoE Projectile Warnings for Dawn/Dusk are displayed"
+ )
+ default boolean isGargBossEnabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "vetion",
+ name = "Vet'ion",
+ description = "Configures whether or not AoE Projectile Warnings for Vet'ion are displayed"
+ )
+ default boolean isVetionEnabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "chaosfanatic",
+ name = "Chaos Fanatic",
+ description = "Configures whether or not AoE Projectile Warnings for Chaos Fanatic are displayed"
+ )
+ default boolean isChaosFanaticEnabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "olm",
+ name = "Great Olm",
+ description = "Configures whether or not AoE Projectile Warnings for The Great Olm are displayed"
+ )
+ default boolean isOlmEnabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "bombDisplay",
+ name = "Display crystal phase bomb tracker",
+ description = "Display a timer and colour-coded AoE for Olm's crystal-phase bombs."
+ )
+ default boolean bombDisplay()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "corp",
+ name = "Corporeal Beast",
+ description = "Configures whether or not AoE Projectile Warnings for the Corporeal Beast are displayed"
+ )
+ default boolean isCorpEnabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "wintertodt",
+ name = "Wintertodt Snow Fall",
+ description = "Configures whether or not AOE Projectile Warnings for the Wintertodt snow fall are displayed"
+ )
+ default boolean isWintertodtEnabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "outline",
+ name = "Display Outline",
+ description = "Configures whether or not AoE Projectile Warnings have an outline"
+ )
+ default boolean isOutlineEnabled()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "lightning",
+ name = "Show Lightning Trails",
+ description = "Show Lightning Trails"
+ )
+ default boolean LightningTrail()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "fade",
+ name = "Fade Warnings",
+ description = "Configures whether or not AoE Projectile Warnings fade over time"
+ )
+ default boolean isFadeEnabled()
+ {
+ return true;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeWarningOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeWarningOverlay.java
new file mode 100644
index 0000000000..7e6dd2409a
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeWarningOverlay.java
@@ -0,0 +1,166 @@
+/*
+ * 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.aoewarnings;
+
+import net.runelite.api.Client;
+import net.runelite.api.Perspective;
+import net.runelite.api.Projectile;
+import net.runelite.api.coords.LocalPoint;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayLayer;
+import net.runelite.client.ui.overlay.OverlayPosition;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import java.awt.*;
+import java.time.Instant;
+import java.util.Iterator;
+import java.util.Map;
+
+public class AoeWarningOverlay extends Overlay
+{
+ private static final int FILL_START_ALPHA = 25;
+ private static final int OUTLINE_START_ALPHA = 255;
+
+ private final Client client;
+ private final AoeWarningPlugin plugin;
+ private final AoeWarningConfig config;
+
+ @Inject
+ public AoeWarningOverlay(@Nullable Client client, AoeWarningPlugin plugin, AoeWarningConfig config)
+ {
+ setPosition(OverlayPosition.DYNAMIC);
+ setLayer(OverlayLayer.UNDER_WIDGETS);
+ this.client = client;
+ this.plugin = plugin;
+ this.config = config;
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if (!config.enabled())
+ {
+ return null;
+ }
+ for (WorldPoint point : plugin.getLightningTrail())
+ {
+ drawTile(graphics, point, new Color(0,150,200), 2, 150, 50);
+ }
+ for (WorldPoint point : plugin.getAcidTrail())
+ {
+ drawTile(graphics, point, new Color(69, 241, 44), 2, 150, 50);
+ }
+ for (WorldPoint point : plugin.getCrystalSpike())
+ {
+ drawTile(graphics, point, new Color(255, 0, 84), 2, 150, 50);
+ }
+
+ Instant now = Instant.now();
+ Map projectiles = plugin.getProjectiles();
+ for (Iterator it = projectiles.values().iterator(); it.hasNext();)
+ {
+ AoeProjectile aoeProjectile = it.next();
+
+ if (now.isAfter(aoeProjectile.getStartTime().plus(aoeProjectile.getAoeProjectileInfo().getLifeTime())))
+ {
+ it.remove();
+ continue;
+ }
+
+ Polygon tilePoly = Perspective.getCanvasTileAreaPoly(client, aoeProjectile.getTargetPoint(), aoeProjectile.getAoeProjectileInfo().getAoeSize());
+ if (tilePoly == null)
+ {
+ continue;
+ }
+
+ // how far through the projectiles lifetime between 0-1.
+ double progress = (System.currentTimeMillis() - aoeProjectile.getStartTime().toEpochMilli()) / (double) aoeProjectile.getAoeProjectileInfo().getLifeTime().toMillis();
+
+ int fillAlpha, outlineAlpha;
+ if (config.isFadeEnabled())
+ {
+ fillAlpha = (int) ((1 - progress) * FILL_START_ALPHA);//alpha drop off over lifetime
+ outlineAlpha = (int) ((1 - progress) * OUTLINE_START_ALPHA);
+ }
+ else
+ {
+ fillAlpha = FILL_START_ALPHA;
+ outlineAlpha = OUTLINE_START_ALPHA;
+ }
+
+ if (fillAlpha < 0)
+ {
+ fillAlpha = 0;
+ }
+ if (outlineAlpha < 0)
+ {
+ outlineAlpha = 0;
+ }
+
+ if (fillAlpha > 255)
+ {
+ fillAlpha = 255;
+ }
+ if (outlineAlpha > 255)
+ {
+ outlineAlpha = 255;//Make sure we don't pass in an invalid alpha
+ }
+
+ if (config.isOutlineEnabled())
+ {
+ graphics.setColor(new Color(0, 150, 200, outlineAlpha));
+ graphics.drawPolygon(tilePoly);
+ }
+
+ graphics.setColor(new Color(0, 150, 200, fillAlpha));
+ graphics.fillPolygon(tilePoly);
+ }
+ return null;
+ }
+
+ private void drawTile(Graphics2D graphics, WorldPoint point, Color color, int strokeWidth, int outlineAlpha, int fillAlpha) {
+ WorldPoint playerLocation = client.getLocalPlayer().getWorldLocation();
+ if (point.distanceTo(playerLocation) >= 32) {
+ return;
+ }
+ LocalPoint lp = LocalPoint.fromWorld(client, point);
+ if (lp == null) {
+ return;
+ }
+
+ Polygon poly = Perspective.getCanvasTilePoly(client, lp);
+ if (poly == null) {
+ return;
+ }
+ //OverlayUtil.renderPolygon(graphics, poly, color);
+ graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), outlineAlpha));
+ graphics.setStroke(new BasicStroke(strokeWidth));
+ graphics.draw(poly);
+ graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), fillAlpha));
+ graphics.fill(poly);
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeWarningPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeWarningPlugin.java
new file mode 100644
index 0000000000..c3219d53b7
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/AoeWarningPlugin.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2018, 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.aoewarnings;
+
+import com.google.inject.Provides;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.GameObject;
+import net.runelite.api.GameState;
+import net.runelite.api.GraphicID;
+import net.runelite.api.GraphicsObject;
+import net.runelite.api.ObjectID;
+import net.runelite.api.Projectile;
+import net.runelite.api.Client;
+import net.runelite.api.Tile;
+import net.runelite.api.coords.LocalPoint;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.api.events.GameObjectDespawned;
+import net.runelite.api.events.GameObjectSpawned;
+import net.runelite.api.events.GameStateChanged;
+import net.runelite.api.events.GameTick;
+import net.runelite.api.events.GraphicsObjectCreated;
+import net.runelite.api.events.ProjectileMoved;
+import net.runelite.client.Notifier;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+
+import javax.inject.Inject;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+@PluginDescriptor(
+ name = "!AoE Warnings",
+ description = "Shows the final destination for AoE Attack projectiles",
+ tags = {"bosses", "combat", "pve", "overlay"}
+)
+
+@Slf4j
+public class AoeWarningPlugin extends Plugin
+{
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private AoeWarningOverlay coreOverlay;
+
+ @Inject
+ public AoeWarningConfig config;
+
+ @Inject
+ private BombOverlay bombOverlay;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private Notifier notifier;
+
+ @Getter
+ private final Map bombs = new HashMap<>();
+ @Getter(AccessLevel.PACKAGE)
+ private List LightningTrail = new ArrayList<>();
+ @Getter(AccessLevel.PACKAGE)
+ private List AcidTrail = new ArrayList<>();
+ @Getter(AccessLevel.PACKAGE)
+ private List CrystalSpike = new ArrayList<>();
+
+
+ @Provides
+ AoeWarningConfig getConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(AoeWarningConfig.class);
+ }
+
+
+ private final Map projectiles = new HashMap<>();
+
+ public Map getProjectiles()
+ {
+ return projectiles;
+ }
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ overlayManager.add(coreOverlay);
+ overlayManager.add(bombOverlay);
+ LightningTrail.clear();
+ AcidTrail.clear();
+ CrystalSpike.clear();
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ overlayManager.remove(coreOverlay);
+ overlayManager.remove(bombOverlay);
+ }
+
+ @Subscribe
+ public void onProjectileMoved(ProjectileMoved event)
+ {
+ Projectile projectile = event.getProjectile();
+
+ int projectileId = projectile.getId();
+ AoeProjectileInfo aoeProjectileInfo = AoeProjectileInfo.getById(projectileId);
+ if (aoeProjectileInfo != null && isConfigEnabledForProjectileId(projectileId))
+ {
+ LocalPoint targetPoint = event.getPosition();
+ AoeProjectile aoeProjectile = new AoeProjectile(Instant.now(), targetPoint, aoeProjectileInfo);
+ projectiles.put(projectile, aoeProjectile);
+ }
+ }
+
+ @Subscribe
+ public void onGameObjectSpawned(GameObjectSpawned event)
+ {
+ final GameObject gameObject = event.getGameObject();
+ final WorldPoint bombLocation = gameObject.getWorldLocation();
+
+ switch (gameObject.getId())
+ {
+ case ObjectID.CRYSTAL_BOMB:
+ bombs.put(bombLocation, new CrystalBomb(gameObject, client.getTickCount()));
+ break;
+ case ObjectID.ACID_POOL:
+ AcidTrail.add(bombLocation);
+ break;
+ case ObjectID.SMALL_CRYSTALS:
+ //todo
+ CrystalSpike.add(bombLocation);
+ break;
+ }
+ }
+
+ @Subscribe
+ public void onGameObjectDespawned(GameObjectDespawned event)
+ {
+ GameObject gameObject = event.getGameObject();
+ WorldPoint bombLocation = gameObject.getWorldLocation();
+ switch (gameObject.getId())
+ {
+ case ObjectID.CRYSTAL_BOMB:
+ //might as well check the ObjectID to save some time.
+ purgeBombs(bombs);
+ break;
+ case ObjectID.ACID_POOL:
+ AcidTrail.remove(bombLocation);
+ break;
+ case ObjectID.SMALL_CRYSTALS:
+ //todo
+ CrystalSpike.remove(bombLocation);
+ break;
+ }
+ }
+
+ @Subscribe
+ public void onGameStateChanged(GameStateChanged delta)
+ {
+ if (client.getGameState() == GameState.LOGGED_IN)
+ {
+ purgeBombs(bombs);
+ }
+ }
+
+ @Subscribe
+ public void onGameTick(GameTick event)
+ {
+ if (config.LightningTrail())
+ {
+ LightningTrail.clear();
+ for (GraphicsObject o : client.getGraphicsObjects())
+ {
+ if (o.getId() == 1356)
+ {
+ LightningTrail.add(WorldPoint.fromLocal(client, o.getLocation()));
+ }
+ }
+ }
+
+ Iterator> it = bombs.entrySet().iterator();
+
+ while (it.hasNext())
+ {
+ Map.Entry entry = it.next();
+ CrystalBomb bomb = entry.getValue();
+ bomb.bombClockUpdate();
+ //bombClockUpdate smooths the shown timer; not using this results in 1.2 --> .6 vs. 1.2 --> 1.1, etc.
+ }
+ }
+
+ private void purgeBombs(Map bombs)
+ {
+ Iterator> it = bombs.entrySet().iterator();
+ Tile[][][] tiles = client.getScene().getTiles();
+
+ while (it.hasNext())
+ {
+ Map.Entry entry = it.next();
+ WorldPoint world = entry.getKey();
+ LocalPoint local = LocalPoint.fromWorld(client, world);
+ Tile tile = tiles[world.getPlane()][local.getSceneX()][local.getSceneY()];
+ GameObject[] objects = tile.getGameObjects();
+ boolean containsObjects = false;
+
+ for (GameObject object : objects)
+ {
+ if (object != null)
+ {
+ containsObjects = true;
+ }
+ }
+
+ if (!containsObjects)
+ {
+ it.remove();
+ }
+
+ }
+ }
+
+ private boolean isConfigEnabledForProjectileId(int projectileId)
+ {
+ AoeProjectileInfo projectileInfo = AoeProjectileInfo.getById(projectileId);
+ if (projectileInfo == null)
+ {
+ return false;
+ }
+
+ switch (projectileInfo)
+ {
+ case LIZARDMAN_SHAMAN_AOE:
+ return config.isShamansEnabled();
+ case CRAZY_ARCHAEOLOGIST_AOE:
+ return config.isArchaeologistEnabled();
+ case ICE_DEMON_RANGED_AOE:
+ case ICE_DEMON_ICE_BARRAGE_AOE:
+ return config.isIceDemonEnabled();
+ case VASA_AWAKEN_AOE:
+ case VASA_RANGED_AOE:
+ return config.isVasaEnabled();
+ case TEKTON_METEOR_AOE:
+ return config.isTektonEnabled();
+ case VORKATH_BOMB:
+ case VORKATH_POISON_POOL:
+ case VORKATH_SPAWN:
+ case VORKATH_TICK_FIRE:
+ return config.isVorkathEnabled();
+ case VETION_LIGHTNING:
+ return config.isVetionEnabled();
+ case CHAOS_FANATIC:
+ return config.isChaosFanaticEnabled();
+ case GALVEK_BOMB:
+ case GALVEK_MINE:
+ return config.isGalvekEnabled();
+ case DAWN_FREEZE:
+ case DUSK_CEILING:
+ return config.isGargBossEnabled();
+ case OLM_FALLING_CRYSTAL:
+ case OLM_BURNING:
+ case OLM_FALLING_CRYSTAL_TRAIL:
+ case OLM_ACID_TRAIL:
+ case OLM_FIRE_LINE:
+ return config.isOlmEnabled();
+ case CORPOREAL_BEAST:
+ case CORPOREAL_BEAST_DARK_CORE:
+ return config.isCorpEnabled();
+ case WINTERTODT_SNOW_FALL:
+ return config.isWintertodtEnabled();
+ }
+
+ return false;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/BombOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/BombOverlay.java
new file mode 100644
index 0000000000..0ee6162a76
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/BombOverlay.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2018, PallasDieKatze (Pallas Cat)
+ * 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.aoewarnings;
+
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.Perspective;
+import net.runelite.api.Player;
+import net.runelite.api.Point;
+import net.runelite.api.coords.LocalPoint;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.client.plugins.aoewarnings.CrystalBomb;
+import net.runelite.client.ui.overlay.*;
+import javax.inject.Inject;
+import java.awt.Graphics2D;
+import java.awt.Dimension;
+import java.awt.Color;
+import java.awt.Polygon;
+import java.awt.BasicStroke;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.time.Instant;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+@Slf4j
+public class BombOverlay extends Overlay
+{
+
+ private static final String SAFE = "#00cc00";
+ //safe
+ private static final String CAUTION = "#ffff00";
+ //1 tile in range (minor damage)
+ private static final String WARNING = "#ff9933";
+ //2 tiles in range (moderate damage)
+ private static final String DANGER = "#ff6600";
+ //3 tiles in range/adjacent to bomb (major damage)
+ private static final String LETHAL = "#cc0000";
+ //On the bomb, using it as a makeshift space launch vehicle. (massive damage)
+
+ private static final int BOMB_AOE = 7;
+ private static final int BOMB_DETONATE_TIME = 8;
+ //This is in ticks. It should be 10, but it varies from 8 to 11.
+ private static final double ESTIMATED_TICK_LENGTH = .6;
+ //Thank you Woox & co. for this assumption. .6 seconds/tick.
+
+
+ //Utilized from the npc highlight code for formatting text being displayed on the client canvas.
+ private static final NumberFormat TIME_LEFT_FORMATTER =
+ DecimalFormat.getInstance(Locale.US);
+
+ static
+ {
+ ((DecimalFormat) TIME_LEFT_FORMATTER).applyPattern("#0.0");
+ }
+
+ private final Client client;
+ private final AoeWarningConfig config;
+ private final AoeWarningPlugin plugin;
+
+ @Inject
+ public BombOverlay(Client client, AoeWarningPlugin plugin, AoeWarningConfig config)
+ {
+ this.client = client;
+ this.plugin = plugin;
+ this.config = config;
+ setPosition(OverlayPosition.DYNAMIC);
+ setLayer(OverlayLayer.ABOVE_SCENE);
+ setPriority(OverlayPriority.MED);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if (config.bombDisplay())
+ {
+ drawBombs(graphics);
+ }
+ return null;
+ }
+
+ private void drawBombs(Graphics2D graphics)
+ //I can condense drawDangerZone into this. Ambivalent though.
+ {
+ Iterator> it = plugin.getBombs().entrySet().iterator();
+ while (it.hasNext())
+ {
+ Map.Entry entry = it.next();
+ CrystalBomb bomb = entry.getValue();
+ drawDangerZone(graphics, bomb);
+ }
+ }
+
+ private void drawDangerZone(Graphics2D graphics, CrystalBomb bomb)
+ {
+ final Player localPlayer = client.getLocalPlayer();
+ LocalPoint localLoc = LocalPoint.fromWorld(client, bomb.getWorldLocation());
+ double distance_x = Math.abs(bomb.getWorldLocation().getX() - localPlayer.getWorldLocation().getX());
+ double distance_y = Math.abs(bomb.getWorldLocation().getY() - localPlayer.getWorldLocation().getY());
+ Color color_code = Color.decode(SAFE);
+ //defaults to this unless conditionals met below.
+
+ if (distance_x < 1 && distance_y < 1)
+ {
+ color_code = Color.decode(LETHAL);
+ }
+ else if (distance_x < 2 && distance_y < 2)
+ {
+ color_code = Color.decode(DANGER);
+ }
+ else if (distance_x < 3 && distance_y < 3)
+ {
+ color_code = Color.decode(WARNING);
+ }
+ else if (distance_x < 4 && distance_y < 4)
+ {
+ color_code = Color.decode(CAUTION);
+ }
+ LocalPoint CenterPoint = new LocalPoint(localLoc.getX() + 0, localLoc.getY() + 0);
+ Polygon poly = Perspective.getCanvasTileAreaPoly(client, CenterPoint, BOMB_AOE);
+
+ if (poly != null)
+ {
+ //manually generating the polygon so as to assign a custom alpha value. Request adtl' arg for alpha maybe?
+ graphics.setColor(color_code);
+ graphics.setStroke(new BasicStroke(1));
+ graphics.drawPolygon(poly);
+ graphics.setColor(new Color(0, 0, 0, 10));
+ graphics.fillPolygon(poly);
+ }
+
+ Instant now = Instant.now();
+ double timeLeft = ((BOMB_DETONATE_TIME - (client.getTickCount() -
+ bomb.getTickStarted())) * ESTIMATED_TICK_LENGTH) -
+ (now.toEpochMilli() - bomb.getLastClockUpdate().toEpochMilli()) / 1000.0;
+ //divided by 1000.00 because of milliseconds :)
+
+ timeLeft = Math.max(0.0, timeLeft);
+ String bombTimerString = TIME_LEFT_FORMATTER.format(timeLeft);
+ int textWidth = graphics.getFontMetrics().stringWidth(bombTimerString);
+ int textHeight = graphics.getFontMetrics().getAscent();
+ Point canvasPoint = Perspective.localToCanvas(client, localLoc.getX(),
+ localLoc.getY(), bomb.getWorldLocation().getPlane());
+
+ if (canvasPoint != null)
+ {
+ Point canvasCenterPoint = new Point(
+ canvasPoint.getX() - textWidth / 2,
+ canvasPoint.getY() + textHeight / 2);
+ OverlayUtil.renderTextLocation(graphics, canvasCenterPoint, bombTimerString, color_code);
+ }
+
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/CrystalBomb.java b/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/CrystalBomb.java
new file mode 100644
index 0000000000..d138aed20c
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/aoewarnings/CrystalBomb.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018, PallasDieKatze (Pallas Cat)
+ * 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.aoewarnings;
+
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.GameObject;
+import net.runelite.api.coords.WorldPoint;
+import java.time.Instant;
+
+@Slf4j
+public class CrystalBomb
+{
+ @Getter
+ private Instant plantedOn;
+
+ @Getter
+ private Instant lastClockUpdate;
+
+ @Getter
+ private int objectId;
+
+ @Getter
+ private int tickStarted;
+ //
+
+ @Getter
+ private WorldPoint worldLocation;
+
+ public CrystalBomb(GameObject gameObject, int startTick)
+ {
+ this.plantedOn = Instant.now();
+ this.objectId = gameObject.getId();
+ this.worldLocation = gameObject.getWorldLocation();
+ this.tickStarted = startTick;
+ }
+
+ public void bombClockUpdate()
+ {
+ lastClockUpdate = Instant.now();
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultConfig.java
index 52d6a18af5..1a0b8ef4ab 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultConfig.java
@@ -37,17 +37,53 @@ public interface BarbarianAssaultConfig extends Config
name = "Show call change timer",
description = "Show time to next call change"
)
- default boolean showTimer()
- {
- return true;
- }
+ default boolean showTimer() { return true; }
@ConfigItem(
keyName = "waveTimes",
name = "Show wave and game duration",
description = "Displays wave and game duration"
+ )
+ default boolean waveTimes() { return true; }
+ @ConfigItem(
+ keyName = "showEggCountMessage",
+ name = "Show count of eggs collected as collector.",
+ description = "Display egg count as collector after each wave",
+ position = 0
+ )
+ default boolean showEggCount() { return false; }
+
+ @ConfigItem(
+ keyName = "showEggCountOverlay",
+ name = "Overlay of eggs counted",
+ description = "Display current egg count as collector",
+ position = 1
+ )
+ default boolean showEggCountOverlay() { return false; }
+
+ @ConfigItem(
+ keyName = "showHpCountMessage",
+ name = "Show count of Hp healed as healer.",
+ description = "Display healed count as healer after each wave",
+ position = 2
+ )
+ default boolean showHpCount() { return false; }
+
+ @ConfigItem(
+ keyName = "showHpCountOverlay",
+ name = "Overlay of Hp counted",
+ description = "Display current healed count as healer",
+ position = 3
+ )
+ default boolean showHpCountOverlay() { return false; }
+
+
+ @ConfigItem(
+ keyName = "highlightCollectorEggs",
+ name = "Highlight collector eggs",
+ description = "Highlight called egg colors"
)
- default boolean waveTimes()
+ default boolean highlightCollectorEggs()
{
return true;
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultOverlay.java
index 0fdf9edc2e..5bc5959622 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultOverlay.java
@@ -24,24 +24,37 @@
*/
package net.runelite.client.plugins.barbarianassault;
+import java.awt.BasicStroke;
+import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
+import java.awt.Polygon;
import java.awt.Rectangle;
+import java.awt.Stroke;
+import java.util.Map;
import javax.inject.Inject;
import lombok.Getter;
import lombok.Setter;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG;
+import net.runelite.api.Perspective;
+import net.runelite.api.Player;
+import net.runelite.api.Point;
+import net.runelite.api.coords.LocalPoint;
+import net.runelite.api.coords.WorldPoint;
import net.runelite.api.widgets.Widget;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE;
import net.runelite.client.ui.overlay.OverlayMenuEntry;
import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.OverlayUtil;
class BarbarianAssaultOverlay extends Overlay
{
+ private static final int MAX_EGG_DISTANCE = 2500;
+
private final Client client;
private final BarbarianAssaultPlugin plugin;
private final BarbarianAssaultConfig config;
@@ -82,12 +95,95 @@ class BarbarianAssaultOverlay extends Overlay
if (config.showTimer() && roleText != null && roleSprite != null)
{
- roleText.setText(String.format("00:%02d", currentRound.getTimeToChange()));
+ if (config.showEggCountOverlay() && role.equals(Role.COLLECTOR))
+ {
+ roleText.setText(String.format("(%d) 00:%02d", plugin.getCollectedEggCount(), currentRound.getTimeToChange()));
+ }
+ else if (config.showHpCountOverlay() && role.equals(Role.HEALER))
+ {
+ roleText.setText(String.format("(%d) 00:%02d", plugin.getHpHealed(), currentRound.getTimeToChange()));
+ }
+ else
+ {
+ roleText.setText(String.format("00:%02d", currentRound.getTimeToChange()));
+ }
Rectangle spriteBounds = roleSprite.getBounds();
roleSprite.setHidden(true);
graphics.drawImage(plugin.getClockImage(), spriteBounds.x, spriteBounds.y, null);
}
+ if (role == Role.COLLECTOR && config.highlightCollectorEggs())
+ {
+ String heardCall = plugin.getCollectorHeardCall();
+ Color highlightColor;
+ Map calledEggMap;
+
+ Map yellowEggMap = plugin.getYellowEggs();
+
+ switch (heardCall)
+ {
+ case "Red eggs":
+ calledEggMap = plugin.getRedEggs();
+ highlightColor = Color.RED;
+ break;
+ case "Green eggs":
+ calledEggMap = plugin.getGreenEggs();
+ highlightColor = Color.GREEN;
+ break;
+ case "Blue eggs":
+ calledEggMap = plugin.getBlueEggs();
+ highlightColor = Color.BLUE;
+ break;
+ default:
+ calledEggMap = null;
+ highlightColor = null;
+ }
+
+ if (calledEggMap != null)
+ {
+ for (WorldPoint worldPoint : calledEggMap.keySet())
+ {
+ int quantity = calledEggMap.get(worldPoint);
+ renderEggLocation(graphics, worldPoint, quantity, highlightColor);
+ }
+ }
+
+ // Always show yellow eggs
+ for (WorldPoint worldPoint : yellowEggMap.keySet())
+ {
+ int quantity = yellowEggMap.get(worldPoint);
+ renderEggLocation(graphics, worldPoint, quantity, highlightColor);
+ }
+ }
+
return null;
}
+
+ private void renderEggLocation(Graphics2D graphics, WorldPoint location, int quantity, Color color)
+ {
+ LocalPoint groundPoint = LocalPoint.fromWorld(client, location);
+ Player player = client.getLocalPlayer();
+
+ if (groundPoint == null || player == null)
+ {
+ return;
+ }
+
+ if (player.getLocalLocation().distanceTo(groundPoint) > MAX_EGG_DISTANCE)
+ {
+ return;
+ }
+
+ Polygon poly = Perspective.getCanvasTilePoly(client, groundPoint);
+ final Stroke originalStroke = graphics.getStroke();
+
+ graphics.setColor(color);
+ graphics.setStroke(new BasicStroke(2));
+ graphics.drawPolygon(poly);
+ graphics.setStroke(originalStroke);
+
+ String quantityText = "x" + quantity;
+ Point textPoint = Perspective.getCanvasTextLocation(client, graphics, groundPoint, quantityText, 0);
+ OverlayUtil.renderTextLocation(graphics, textPoint, quantityText, Color.WHITE);
+ }
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultPlugin.java
index 985ef626f5..89ef526630 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultPlugin.java
@@ -28,13 +28,23 @@ package net.runelite.client.plugins.barbarianassault;
import com.google.inject.Provides;
import java.awt.Font;
import java.awt.Image;
+import java.util.HashMap;
import javax.inject.Inject;
+import lombok.Getter;
+import lombok.AccessLevel;
+import lombok.Getter;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.ItemID;
+import net.runelite.api.Player;
+import net.runelite.api.Tile;
import net.runelite.api.Varbits;
+import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameTick;
+import net.runelite.api.events.ItemDespawned;
+import net.runelite.api.events.ItemDespawned;
+import net.runelite.api.events.ItemSpawned;
import net.runelite.api.events.VarbitChanged;
import net.runelite.api.events.WidgetLoaded;
import net.runelite.api.kit.KitType;
@@ -58,18 +68,38 @@ import net.runelite.client.util.ImageUtil;
description = "Show a timer to the next call change and game/wave duration in chat.",
tags = {"minigame", "overlay", "timer"}
)
-public class BarbarianAssaultPlugin extends Plugin
-{
+public class BarbarianAssaultPlugin extends Plugin {
private static final int BA_WAVE_NUM_INDEX = 2;
private static final String START_WAVE = "1";
private static final String ENDGAME_REWARD_NEEDLE_TEXT = "
5";
+ @Getter
+ private int collectedEggCount = 0;
+ @Getter
+ private int HpHealed = 0;
+ @Getter
+ private int totalCollectedEggCount = 0;
+ @Getter
+ private int totalHpHealed = 0;
+
private Font font;
private Image clockImage;
private int inGameBit = 0;
private String currentWave = START_WAVE;
private GameTimer gameTime;
+ @Getter(AccessLevel.PACKAGE)
+ private HashMap redEggs;
+
+ @Getter(AccessLevel.PACKAGE)
+ private HashMap greenEggs;
+
+ @Getter(AccessLevel.PACKAGE)
+ private HashMap blueEggs;
+
+ @Getter(AccessLevel.PACKAGE)
+ private HashMap yellowEggs;
+
@Inject
private Client client;
@@ -86,74 +116,90 @@ public class BarbarianAssaultPlugin extends Plugin
private BarbarianAssaultOverlay overlay;
@Provides
- BarbarianAssaultConfig provideConfig(ConfigManager configManager)
- {
+ BarbarianAssaultConfig provideConfig(ConfigManager configManager) {
return configManager.getConfig(BarbarianAssaultConfig.class);
}
@Override
- protected void startUp() throws Exception
- {
+ protected void startUp() throws Exception {
overlayManager.add(overlay);
font = FontManager.getRunescapeFont()
- .deriveFont(Font.BOLD, 24);
+ .deriveFont(Font.BOLD, 24);
clockImage = ImageUtil.getResourceStreamFromClass(getClass(), "clock.png");
+
+ redEggs = new HashMap<>();
+ greenEggs = new HashMap<>();
+ blueEggs = new HashMap<>();
+ yellowEggs = new HashMap<>();
}
@Override
- protected void shutDown() throws Exception
- {
+ protected void shutDown() throws Exception {
overlayManager.remove(overlay);
gameTime = null;
currentWave = START_WAVE;
inGameBit = 0;
+ collectedEggCount = 0;
+ HpHealed = 0;
}
@Subscribe
- public void onWidgetLoaded(WidgetLoaded event)
- {
- if (event.getGroupId() == WidgetID.BA_REWARD_GROUP_ID)
- {
+ public void onWidgetLoaded(WidgetLoaded event) {
+ if (event.getGroupId() == WidgetID.BA_REWARD_GROUP_ID) {
Widget rewardWidget = client.getWidget(WidgetInfo.BA_REWARD_TEXT);
-
- if (config.waveTimes() && rewardWidget != null && rewardWidget.getText().contains(ENDGAME_REWARD_NEEDLE_TEXT) && gameTime != null)
- {
- announceTime("Game finished, duration: ", gameTime.getTime(false));
+ String amt,type,totalMsg,total;
+ amt=type=totalMsg=total="";
+ if (config.waveTimes() && rewardWidget != null && rewardWidget.getText().contains(ENDGAME_REWARD_NEEDLE_TEXT) && gameTime != null) {
+ if (config.showHpCount() && HpHealed > 0) {
+ totalMsg = "; Total Healed: ";
+ total = ""+totalHpHealed;
+ }
+ else if (config.showEggCount() && collectedEggCount > 0) {
+ totalMsg = "; Total Collected: ";
+ total = ""+totalCollectedEggCount;
+ }
+ announceTime("Game finished, duration: ", gameTime.getTime(false),type, amt, totalMsg, total);
}
}
}
@Subscribe
- public void onChatMessage(ChatMessage event)
- {
+ public void onChatMessage(ChatMessage event) {
if (event.getType() == ChatMessageType.GAMEMESSAGE
- && event.getMessage().startsWith("---- Wave:"))
- {
+ && event.getMessage().startsWith("---- Wave:")) {
String[] message = event.getMessage().split(" ");
currentWave = message[BA_WAVE_NUM_INDEX];
+ collectedEggCount = 0;
+ HpHealed = 0;
- if (currentWave.equals(START_WAVE))
- {
+ if (currentWave.equals(START_WAVE)) {
gameTime = new GameTimer();
- }
- else if (gameTime != null)
- {
+ totalHpHealed = 0;
+ totalCollectedEggCount = 0;
+ } else if (gameTime != null) {
gameTime.setWaveStartTime();
}
+ } else if (event.getType() == ChatMessageType.GAMEMESSAGE
+ && event.getMessage().contains("egg explode")) {
+ collectedEggCount -= 2;
+ } else if (event.getType() == ChatMessageType.GAMEMESSAGE
+ && event.getMessage().contains("healed")) {
+ String message = event.getMessage();
+ String[] tokens = message.split(" ");
+ if (Integer.parseInt(tokens[2]) > 0) {
+ int Hp = Integer.parseInt(tokens[2]);
+ HpHealed += Hp;
+ }
}
}
@Subscribe
- public void onGameTick(GameTick event)
- {
- if (client.getVar(Varbits.IN_GAME_BA) == 0 || client.getLocalPlayer() == null || overlay.getCurrentRound() != null)
- {
+ public void onGameTick(GameTick event) {
+ if (client.getVar(Varbits.IN_GAME_BA) == 0 || client.getLocalPlayer() == null || overlay.getCurrentRound() != null) {
return;
}
-
- switch (client.getLocalPlayer().getPlayerComposition().getEquipmentId(KitType.CAPE))
- {
+ switch (client.getLocalPlayer().getPlayerComposition().getEquipmentId(KitType.CAPE)) {
case ItemID.ATTACKER_ICON:
overlay.setCurrentRound(new Round(Role.ATTACKER));
break;
@@ -170,39 +216,146 @@ public class BarbarianAssaultPlugin extends Plugin
}
@Subscribe
- public void onVarbitChanged(VarbitChanged event)
- {
+ public void onVarbitChanged(VarbitChanged event) {
int inGame = client.getVar(Varbits.IN_GAME_BA);
-
- if (inGameBit != inGame)
- {
- if (inGameBit == 1)
- {
+ String amt,type,totalMsg,total;
+ amt=type=totalMsg=total="";
+ if (inGameBit != inGame) {
+ if (inGameBit == 1) {
overlay.setCurrentRound(null);
- if (config.waveTimes() && gameTime != null)
- {
- announceTime("Wave " + currentWave + " duration: ", gameTime.getTime(true));
+ if (config.waveTimes() && gameTime != null) {
+ totalCollectedEggCount += collectedEggCount;
+ totalHpHealed += HpHealed;
+ if (config.showHpCount() && HpHealed > 0) {
+ amt = "" + HpHealed;
+ type = "; Healed: ";
+ totalMsg = "; Total Healed: ";
+ total = ""+totalHpHealed;
+ }
+ else if (config.showEggCount() && collectedEggCount > 0) {
+ amt = "" + collectedEggCount;
+ type = "; Collected: ";
+ totalMsg = "; Total Collected: ";
+ total = ""+totalCollectedEggCount;
+ }
+ if (currentWave.equals("10"))
+ {
+ totalMsg=total="";
+ }
+ announceTime("Wave " + currentWave + " duration: ", gameTime.getTime(true), type, amt, totalMsg, total);
}
+
}
}
inGameBit = inGame;
}
- private void announceTime(String preText, String time)
+ @Subscribe
+ public void onItemSpawned(ItemSpawned itemSpawned)
+ {
+ int itemId = itemSpawned.getItem().getId();
+ WorldPoint worldPoint = itemSpawned.getTile().getWorldLocation();
+ HashMap eggMap = getEggMap(itemId);
+
+ if (eggMap != null)
+ {
+ Integer existingQuantity = eggMap.putIfAbsent(worldPoint, 1);
+ if (existingQuantity != null)
+ {
+ eggMap.put(worldPoint, existingQuantity + 1);
+ }
+ }
+ }
+
+ @Subscribe
+ public void onItemDespawned(ItemDespawned event)
{
+ if (client.getVar(Varbits.IN_GAME_BA) == 0 || !isEgg(event.getItem().getId()))
+ {
+ return;
+ }
+ if (isUnderPlayer(event.getTile()))
+ {
+ collectedEggCount++;
+ }
+ }
+
+ String getCollectorHeardCall()
+ {
+ Widget widget = client.getWidget(WidgetInfo.BA_COLL_HEARD_TEXT);
+ String call = null;
+
+ if (widget != null)
+ {
+ call = widget.getText();
+ }
+
+ return call;
+ }
+
+
+ private HashMap getEggMap(int itemID)
+ {
+ switch (itemID)
+ {
+ case ItemID.RED_EGG:
+ return redEggs;
+ case ItemID.GREEN_EGG:
+ return greenEggs;
+ case ItemID.BLUE_EGG:
+ return blueEggs;
+ case ItemID.YELLOW_EGG:
+ return yellowEggs;
+ default:
+ return null;
+ }
+ }
+
+
+ private void announceTime(String preText, String time, String type, String amt, String totalMsg, String total) {
+
+
final String chatMessage = new ChatMessageBuilder()
- .append(ChatColorType.NORMAL)
- .append(preText)
- .append(ChatColorType.HIGHLIGHT)
- .append(time)
- .build();
+ .append(ChatColorType.NORMAL)
+ .append(preText)
+ .append(ChatColorType.HIGHLIGHT)
+ .append(time)
+ .append(ChatColorType.NORMAL)
+ .append(type)
+ .append(ChatColorType.HIGHLIGHT)
+ .append(amt)
+ .append(ChatColorType.NORMAL)
+ .append(totalMsg)
+ .append(ChatColorType.HIGHLIGHT)
+ .append(total)
+ .build();
chatMessageManager.queue(QueuedMessage.builder()
- .type(ChatMessageType.CONSOLE)
- .runeLiteFormattedMessage(chatMessage)
- .build());
+ .type(ChatMessageType.CONSOLE)
+ .runeLiteFormattedMessage(chatMessage)
+ .build());
+ }
+
+ private boolean isEgg(int itemID)
+ {
+ if (itemID == ItemID.RED_EGG || itemID == ItemID.GREEN_EGG
+ || itemID == ItemID.BLUE_EGG || itemID == ItemID.YELLOW_EGG)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isUnderPlayer(Tile tile) {
+ Player local = client.getLocalPlayer();
+ if (local == null)
+ {
+ return false;
+ }
+
+ return (tile.getWorldLocation().equals(local.getWorldLocation()));
}
public Font getFont()
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/batools/BAToolsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/batools/BAToolsConfig.java
new file mode 100644
index 0000000000..64cf45dc8f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/batools/BAToolsConfig.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2018, Cameron
+ * Copyright (c) 2018, Jacob M
+ * 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.batools;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("BATools")
+public interface BAToolsConfig extends Config
+{
+ @ConfigItem(
+ keyName = "defTimer",
+ name = "Defender Tick Timer",
+ description = "Shows the current cycle tick of runners."
+ )
+ default boolean defTimer()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "calls",
+ name = "Remove Incorrect Calls",
+ description = "Remove incorrect calls."
+ )
+ default boolean calls()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "swapLadder",
+ name = "Swap ladder option",
+ description = "Swap Climb-down with Quick-start in the wave lobbies"
+ )
+ default boolean swapLadder()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "healerCodes",
+ name = "Healer Codes",
+ description = "Overlay to show healer codes"
+ )
+ default boolean healerCodes()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "healerMenuOption",
+ name = "Healer menu options",
+ description = "asd"
+ )
+ default boolean healerMenuOption()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "antiDrag",
+ name = "Anti Drag",
+ description = "asd"
+ )
+ default boolean antiDrag()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "antiDragDelay",
+ name = "Anti Drag Delay",
+ description = "asd"
+ )
+ default int antiDragDelay()
+ {
+ return 5;
+ }
+
+ @ConfigItem(
+ keyName = "eggBoi",
+ name = "Collector helper",
+ description = "asd"
+ )
+ default boolean eggBoi()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "osHelp",
+ name = "Shift OS",
+ description = "asd"
+ )
+ default boolean osHelp()
+ {
+ return false;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/batools/BAToolsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/batools/BAToolsOverlay.java
new file mode 100644
index 0000000000..43a7bf4e7f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/batools/BAToolsOverlay.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2018, Woox
+ * 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.batools;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import net.runelite.api.NPCComposition;
+import javax.inject.Inject;
+import net.runelite.api.Client;
+import net.runelite.client.ui.overlay.OverlayUtil;
+import net.runelite.client.ui.overlay.Overlay;
+import java.time.Duration;
+import java.time.Instant;
+import net.runelite.client.ui.overlay.OverlayLayer;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import lombok.extern.slf4j.Slf4j;
+@Slf4j
+
+public class BAToolsOverlay extends Overlay
+{
+ private static final Color RED = new Color(221, 44, 0);
+ private static final Color GREEN = new Color(0, 200, 83);
+ private static final Color ORANGE = new Color(255, 109, 0);
+ private static final Color YELLOW = new Color(255, 214, 0);
+ private static final Color CYAN = new Color(0, 184, 212);
+ private static final Color BLUE = new Color(41, 98, 255);
+ private static final Color DEEP_PURPLE = new Color(98, 0, 234);
+ private static final Color PURPLE = new Color(170, 0, 255);
+ private static final Color GRAY = new Color(158, 158, 158);
+
+ private final BAToolsConfig config;
+ private Client client;
+ private BAToolsPlugin plugin;
+
+ @Inject
+ public BAToolsOverlay(Client client, BAToolsPlugin plugin, BAToolsConfig config)
+ {
+ setPosition(OverlayPosition.DYNAMIC);
+ setLayer(OverlayLayer.ABOVE_SCENE);
+ this.config = config;
+ this.client = client;
+ this.plugin = plugin;
+ }
+
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if(!config.healerCodes())
+ {
+ return null;
+ }
+
+ for (Healer healer : plugin.getHealers().values())
+ {
+ NPCComposition composition = healer.getNpc().getComposition();
+ Color color = composition.getCombatLevel() > 1 ? YELLOW : ORANGE;
+ if (composition.getConfigs() != null)
+ {
+ NPCComposition transformedComposition = composition.transform();
+ if (transformedComposition == null)
+ {
+ color = GRAY;
+ }
+ else
+ {
+ composition = transformedComposition;
+ }
+ }
+ int timeLeft = healer.getLastFoodTime() - (int)Duration.between(plugin.getWave_start(), Instant.now()).getSeconds();
+ timeLeft = timeLeft < 1 ? 0 : timeLeft;
+
+ if(healer.getFoodRemaining() > 1)
+ {
+ color = GREEN;
+ }
+ else if(healer.getFoodRemaining() == 1)
+ {
+ if(timeLeft > 0)
+ {
+ color = RED;
+ }
+ else
+ {
+ color = GREEN;
+ }
+ }
+ else
+ {
+ continue;
+ }
+
+ String text = String.format("%d %d",
+ healer.getFoodRemaining(),
+ timeLeft);
+
+
+
+ OverlayUtil.renderActorOverlay(graphics, healer.getNpc(), text, color);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/batools/BAToolsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/batools/BAToolsPlugin.java
new file mode 100644
index 0000000000..7b9e195848
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/batools/BAToolsPlugin.java
@@ -0,0 +1,643 @@
+/*
+ * Copyright (c) 2018, Cameron
+ * Copyright (c) 2018, Jacob M
+ * 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.batools;
+
+import net.runelite.client.eventbus.EventBus;
+import net.runelite.client.eventbus.Subscribe;
+import com.google.inject.Provides;
+import java.awt.event.KeyEvent;
+import java.awt.image.BufferedImage;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.inject.Inject;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Actor;
+import net.runelite.api.ChatMessageType;
+import net.runelite.api.Client;
+import static net.runelite.api.Constants.CHUNK_SIZE;
+import net.runelite.api.ItemID;
+import net.runelite.api.MenuEntry;
+import net.runelite.api.NPC;
+import net.runelite.api.NpcID;
+import net.runelite.api.Varbits;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.api.events.ChatMessage;
+import net.runelite.api.events.ConfigChanged;
+import net.runelite.api.events.GameTick;
+import net.runelite.api.events.HitsplatApplied;
+import net.runelite.api.events.InteractingChanged;
+import net.runelite.api.events.MenuEntryAdded;
+import net.runelite.api.events.MenuOptionClicked;
+import net.runelite.api.events.NpcDespawned;
+import net.runelite.api.events.NpcSpawned;
+import net.runelite.api.events.VarbitChanged;
+import net.runelite.api.events.WidgetLoaded;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetID;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.client.chat.ChatMessageManager;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.input.KeyListener;
+import net.runelite.client.input.KeyManager;
+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.Text;
+
+@Slf4j
+@PluginDescriptor(
+ name = "BA Tools",
+ description = "Custom tools for Barbarian Assault",
+ tags = {"minigame", "overlay", "timer"}
+)
+public class BAToolsPlugin extends Plugin implements KeyListener
+{
+ int inGameBit = 0;
+ int tickNum;
+ int pastCall = 0;
+ private int currentWave = 1;
+ private static final int BA_WAVE_NUM_INDEX = 2;
+ private final List entries = new ArrayList<>();
+ private HashMap foodPressed = new HashMap<>();
+ private CycleCounter counter;
+ private Actor lastInteracted;
+
+ private boolean shiftDown;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private ConfigManager configManager;
+
+ @Inject
+ private ChatMessageManager chatMessageManager;
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private BAToolsConfig config;
+
+ @Inject
+ private ItemManager itemManager;
+
+ @Inject
+ private InfoBoxManager infoBoxManager;
+
+ @Inject
+ private BAToolsOverlay overlay;
+
+ @Getter
+ private Map healers;
+
+ @Getter
+ private Instant wave_start;
+
+ @Inject
+ private KeyManager keyManager;
+
+
+ @Provides
+ BAToolsConfig provideConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(BAToolsConfig.class);
+ }
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ overlayManager.add(overlay);
+ healers = new HashMap<>();
+ wave_start = Instant.now();
+ lastInteracted = null;
+ foodPressed.clear();
+ client.setInventoryDragDelay(config.antiDragDelay());
+ keyManager.registerKeyListener(this);
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ removeCounter();
+ healers.clear();
+ inGameBit = 0;
+ lastInteracted = null;
+ overlayManager.remove(overlay);
+ client.setInventoryDragDelay(5);
+ keyManager.unregisterKeyListener(this);
+ shiftDown = false;
+ }
+
+ @Subscribe
+ public void onWidgetLoaded(WidgetLoaded event)
+ {
+ switch (event.getGroupId())
+ {
+ case WidgetID.BA_REWARD_GROUP_ID:
+ {
+ Widget rewardWidget = client.getWidget(WidgetInfo.BA_REWARD_TEXT);
+
+ if (rewardWidget != null && rewardWidget.getText().contains("
5"))
+ {
+ tickNum = 0;
+ }
+ }
+ }
+ }
+
+ @Subscribe
+ public void onGameTick(GameTick event)
+ {
+ if (config.antiDrag())
+ {
+ client.setInventoryDragDelay(config.antiDragDelay());
+ }
+
+ Widget callWidget = getWidget();
+
+ if (callWidget != null)
+ {
+ if (callWidget.getTextColor() != pastCall && callWidget.getTextColor() == 16316664)
+ {
+ tickNum = 0;
+ }
+ pastCall = callWidget.getTextColor();
+ }
+ if (inGameBit == 1)
+ {
+ if (tickNum > 9)
+ {
+ tickNum = 0;
+ }
+ if (counter == null)
+ {
+ addCounter();
+ }
+ //counter.setText(String.valueOf(tickNum));
+ counter.setCount(tickNum);
+ if (config.defTimer())
+ {
+ log.info("" + tickNum++);
+ }
+ }
+ }
+
+ private Widget getWidget()
+ {
+ if (client.getWidget(WidgetInfo.BA_DEF_CALL_TEXT) != null)
+ {
+ return client.getWidget(WidgetInfo.BA_DEF_CALL_TEXT);
+ }
+ else if (client.getWidget(WidgetInfo.BA_ATK_CALL_TEXT) != null)
+ {
+ return client.getWidget(WidgetInfo.BA_ATK_CALL_TEXT);
+ }
+ else if (client.getWidget(WidgetInfo.BA_COLL_CALL_TEXT) != null)
+ {
+ return client.getWidget(WidgetInfo.BA_COLL_CALL_TEXT);
+ }
+ else if (client.getWidget(WidgetInfo.BA_HEAL_CALL_TEXT) != null)
+ {
+ return client.getWidget(WidgetInfo.BA_HEAL_CALL_TEXT);
+ }
+ return null;
+ }
+
+ @Subscribe
+ public void onVarbitChanged(VarbitChanged event)
+ {
+ int inGame = client.getVar(Varbits.IN_GAME_BA);
+
+ if (inGameBit != inGame)
+ {
+ if (inGameBit == 1)
+ {
+ pastCall = 0;
+ removeCounter();
+ foodPressed.clear();
+ }
+ else
+ {
+ addCounter();
+ }
+ }
+
+ inGameBit = inGame;
+ }
+
+ @Subscribe
+ public void onChatMessage(ChatMessage event)
+ {
+ if (event.getType() == ChatMessageType.CONSOLE
+ && event.getMessage().startsWith("---- Wave:"))
+ {
+ String[] message = event.getMessage().split(" ");
+ currentWave = Integer.parseInt(message[BA_WAVE_NUM_INDEX]);
+ wave_start = Instant.now();
+ healers.clear();
+ }
+ }
+
+ @Subscribe
+ public void onNpcSpawned(NpcSpawned event)
+ {
+ NPC npc = event.getNpc();
+
+ if (isNpcHealer(npc.getId()))
+ {
+ if (checkNewSpawn(npc) || Duration.between(wave_start, Instant.now()).getSeconds() < 16)
+ {
+ int spawnNumber = healers.size();
+ healers.put(npc, new Healer(npc, spawnNumber, currentWave));
+ log.info("spawn number: " + spawnNumber + " on wave " + currentWave);
+ }
+ }
+ }
+
+ @Subscribe
+ public void onHitsplatApplied(HitsplatApplied hitsplatApplied)
+ {
+ Actor actor = hitsplatApplied.getActor();
+
+ if (healers.isEmpty() && !(actor instanceof NPC) && lastInteracted == null)
+ {
+ return;
+ }
+
+ for (Healer healer : healers.values())
+ {
+ if (healer.getNpc() == actor && actor == lastInteracted)
+ {
+ healer.setFoodRemaining(healer.getFoodRemaining() - 1);
+ }
+ }
+ }
+
+ @Subscribe
+ public void onNpcDespawned(NpcDespawned event)
+ {
+ if (healers.remove(event.getNpc()) != null && healers.isEmpty())
+ {
+ healers.clear();
+ }
+ }
+
+ @Subscribe
+ public void onInteractingChanged(InteractingChanged event)
+ {
+ Actor opponent = event.getTarget();
+
+ if (opponent != null && opponent instanceof NPC && isNpcHealer(((NPC) opponent).getId()) && event.getSource() != client.getLocalPlayer())
+ {
+ lastInteracted = opponent;
+ }
+ }
+
+ public static boolean isNpcHealer(int npcId)
+ {
+ return npcId == NpcID.PENANCE_HEALER ||
+ npcId == NpcID.PENANCE_HEALER_5766 ||
+ npcId == NpcID.PENANCE_HEALER_5767 ||
+ npcId == NpcID.PENANCE_HEALER_5768 ||
+ npcId == NpcID.PENANCE_HEALER_5769 ||
+ npcId == NpcID.PENANCE_HEALER_5770 ||
+ npcId == NpcID.PENANCE_HEALER_5771 ||
+ npcId == NpcID.PENANCE_HEALER_5772 ||
+ npcId == NpcID.PENANCE_HEALER_5773 ||
+ npcId == NpcID.PENANCE_HEALER_5774;
+ }
+
+ @Subscribe
+ public void onMenuEntryAdded(MenuEntryAdded event)
+ {
+ if (config.calls() && getWidget() != null && event.getTarget().endsWith("horn") && !event.getTarget().contains("Unicorn"))
+ {
+ MenuEntry[] menuEntries = client.getMenuEntries();
+ Widget callWidget = getWidget();
+ String call = Calls.getOption(callWidget.getText());
+ MenuEntry correctCall = null;
+
+ entries.clear();
+ for (MenuEntry entry : menuEntries)
+ {
+ String option = entry.getOption();
+ if (option.equals(call))
+ {
+ correctCall = entry;
+ }
+ else if (!option.startsWith("Tell-"))
+ {
+ entries.add(entry);
+ }
+ }
+
+ if (correctCall != null) //&& callWidget.getTextColor()==16316664)
+ {
+ entries.add(correctCall);
+ client.setMenuEntries(entries.toArray(new MenuEntry[entries.size()]));
+ }
+ }
+ else if (config.calls() && event.getTarget().endsWith("horn"))
+ {
+ entries.clear();
+ client.setMenuEntries(entries.toArray(new MenuEntry[entries.size()]));
+ }
+
+ String option = Text.removeTags(event.getOption()).toLowerCase();
+ String target = Text.removeTags(event.getTarget()).toLowerCase();
+
+ if (config.swapLadder() && option.equals("climb-down") && target.equals("ladder"))
+ {
+ swap("quick-start", option, target, true);
+ }
+
+ if (inGameBit == 1 && config.healerMenuOption() && event.getTarget().contains("Penance Healer"))
+ {
+
+ MenuEntry[] menuEntries = client.getMenuEntries();
+ MenuEntry lastEntry = menuEntries[menuEntries.length - 1];
+ String targett = lastEntry.getTarget();
+
+ if (foodPressed.containsKey(lastEntry.getIdentifier()))
+ {
+ lastEntry.setTarget(lastEntry.getTarget().split("\\(")[0] + "(" + Duration.between(foodPressed.get(lastEntry.getIdentifier()), Instant.now()).getSeconds() + ")");
+ if (Duration.between(foodPressed.get(lastEntry.getIdentifier()), Instant.now()).getSeconds() > 20)
+ {
+ lastEntry.setTarget(lastEntry.getTarget().replace("", ""));
+ }
+ }
+ else
+ {
+ lastEntry.setTarget(targett.replace("", ""));
+
+ }
+
+ client.setMenuEntries(menuEntries);
+ }
+
+ if (client.getWidget(WidgetInfo.BA_COLL_LISTEN_TEXT) != null && inGameBit == 1 && config.eggBoi() && event.getTarget().endsWith("egg") && shiftDown)
+ {
+ String[] currentCall = client.getWidget(WidgetInfo.BA_COLL_LISTEN_TEXT).getText().split(" ");
+ log.info("1 " + currentCall[0]);
+ MenuEntry[] menuEntries = client.getMenuEntries();
+ MenuEntry correctEgg = null;
+ entries.clear();
+
+ for (MenuEntry entry : menuEntries)
+ {
+ if (entry.getTarget().contains(currentCall[0]) && entry.getOption().equals("Take"))
+ {
+ correctEgg = entry;
+ }
+ }
+ if (correctEgg != null)
+ {
+ entries.add(correctEgg);
+ client.setMenuEntries(entries.toArray(new MenuEntry[entries.size()]));
+ }
+ }
+
+ if (client.getWidget(WidgetInfo.BA_HEAL_LISTEN_TEXT) != null && inGameBit == 1 && config.osHelp() && event.getTarget().equals("Healer item machine") && shiftDown)
+ {
+ String[] currentCall = client.getWidget(WidgetInfo.BA_HEAL_LISTEN_TEXT).getText().split(" ");
+
+ if (!currentCall[0].contains("Pois."))
+ {
+ return;
+ }
+
+ MenuEntry[] menuEntries = client.getMenuEntries();
+ MenuEntry correctEgg = null;
+ entries.clear();
+
+ for (MenuEntry entry : menuEntries)
+ {
+ if (entry.getOption().equals("Take-" + currentCall[1]))
+ {
+ correctEgg = entry;
+ }
+ }
+ if (correctEgg != null)
+ {
+ entries.add(correctEgg);
+ client.setMenuEntries(entries.toArray(new MenuEntry[entries.size()]));
+ }
+ }
+ }
+
+ @Subscribe
+ public void onMenuOptionClicked(MenuOptionClicked event)
+ {
+ if (!config.healerMenuOption() || !event.getMenuTarget().contains("Penance Healer") || client.getWidget(WidgetInfo.BA_HEAL_CALL_TEXT) == null)
+ {
+ return;
+ }
+
+ String currentCall = client.getWidget(WidgetInfo.BA_HEAL_CALL_TEXT).getText();
+ String target = event.getMenuTarget();
+
+ if ((currentCall.equals("Pois. Worms") && (target.contains("Poisoned worms") && target.contains("->") && target.contains("Penance Healer")))
+ || (currentCall.equals("Pois. Meat") && (target.contains("Poisoned meat") && target.contains("->") && target.contains("Penance Healer")))
+ || (currentCall.equals("Pois. Tofu") && (target.contains("Poisoned tofu") && target.contains("->") && target.contains("Penance Healer"))))
+ {
+ foodPressed.put(event.getId(), Instant.now());
+ }
+
+ if (target.contains("->") && target.contains("Penance Healer"))
+ {
+ foodPressed.put(event.getId(), Instant.now());
+ }
+ }
+
+ @Subscribe
+ public void onConfigChanged(ConfigChanged event)
+ {
+ if (config.antiDrag())
+ {
+ client.setInventoryDragDelay(config.antiDragDelay());
+ }
+ }
+
+
+ private void addCounter()
+ {
+ if (!config.defTimer() || counter != null)
+ {
+ return;
+ }
+
+ int itemSpriteId = ItemID.FIGHTER_TORSO;
+
+ BufferedImage taskImg = itemManager.getImage(itemSpriteId);
+ counter = new CycleCounter(taskImg, this, tickNum);
+
+ infoBoxManager.addInfoBox(counter);
+ }
+
+ private void removeCounter()
+ {
+ if (counter == null)
+ {
+ return;
+ }
+
+ infoBoxManager.removeInfoBox(counter);
+ counter = null;
+ }
+
+ private void swap(String optionA, String optionB, String target, boolean strict)
+ {
+ MenuEntry[] entries = client.getMenuEntries();
+
+ int idxA = searchIndex(entries, optionA, target, strict);
+ int idxB = searchIndex(entries, optionB, target, strict);
+
+ if (idxA >= 0 && idxB >= 0)
+ {
+ MenuEntry entry = entries[idxA];
+ entries[idxA] = entries[idxB];
+ entries[idxB] = entry;
+
+ client.setMenuEntries(entries);
+ }
+ }
+
+ private int searchIndex(MenuEntry[] entries, String option, String target, boolean strict)
+ {
+ for (int i = entries.length - 1; i >= 0; i--)
+ {
+ MenuEntry entry = entries[i];
+ String entryOption = Text.removeTags(entry.getOption()).toLowerCase();
+ String entryTarget = Text.removeTags(entry.getTarget()).toLowerCase();
+
+ if (strict)
+ {
+ if (entryOption.equals(option) && entryTarget.equals(target))
+ {
+ return i;
+ }
+ }
+ else
+ {
+ if (entryOption.contains(option.toLowerCase()) && entryTarget.equals(target))
+ {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ private static WorldPoint rotate(WorldPoint point, int rotation)
+ {
+ int chunkX = point.getX() & ~(CHUNK_SIZE - 1);
+ int chunkY = point.getY() & ~(CHUNK_SIZE - 1);
+ int x = point.getX() & (CHUNK_SIZE - 1);
+ int y = point.getY() & (CHUNK_SIZE - 1);
+ switch (rotation)
+ {
+ case 1:
+ return new WorldPoint(chunkX + y, chunkY + (CHUNK_SIZE - 1 - x), point.getPlane());
+ case 2:
+ return new WorldPoint(chunkX + (CHUNK_SIZE - 1 - x), chunkY + (CHUNK_SIZE - 1 - y), point.getPlane());
+ case 3:
+ return new WorldPoint(chunkX + (CHUNK_SIZE - 1 - y), chunkY + x, point.getPlane());
+ }
+ return point;
+ }
+
+ private boolean checkNewSpawn(NPC npc)
+ {
+ int regionId = 7509;
+ int regionX = 42;
+ int regionY = 46;
+ int z = 0;
+
+ // world point of the tile marker
+ WorldPoint worldPoint = new WorldPoint(
+ ((regionId >>> 8) << 6) + regionX,
+ ((regionId & 0xff) << 6) + regionY,
+ z
+ );
+
+ int[][][] instanceTemplateChunks = client.getInstanceTemplateChunks();
+ for (int x = 0; x < instanceTemplateChunks[z].length; ++x)
+ {
+ for (int y = 0; y < instanceTemplateChunks[z][x].length; ++y)
+ {
+ int chunkData = instanceTemplateChunks[z][x][y];
+ int rotation = chunkData >> 1 & 0x3;
+ int templateChunkY = (chunkData >> 3 & 0x7FF) * CHUNK_SIZE;
+ int templateChunkX = (chunkData >> 14 & 0x3FF) * CHUNK_SIZE;
+ if (worldPoint.getX() >= templateChunkX && worldPoint.getX() < templateChunkX + CHUNK_SIZE
+ && worldPoint.getY() >= templateChunkY && worldPoint.getY() < templateChunkY + CHUNK_SIZE)
+ {
+ WorldPoint p = new WorldPoint(client.getBaseX() + x * CHUNK_SIZE + (worldPoint.getX() & (CHUNK_SIZE - 1)),
+ client.getBaseY() + y * CHUNK_SIZE + (worldPoint.getY() & (CHUNK_SIZE - 1)),
+ worldPoint.getPlane());
+ p = rotate(p, rotation);
+ if (p.distanceTo(npc.getWorldLocation()) < 5)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void keyTyped(KeyEvent e)
+ {
+ }
+
+ @Override
+ public void keyPressed(KeyEvent e)
+ {
+ if (e.getKeyCode() == KeyEvent.VK_SHIFT)
+ {
+ shiftDown = true;
+ }
+ }
+
+ @Override
+ public void keyReleased(KeyEvent e)
+ {
+ if (e.getKeyCode() == KeyEvent.VK_SHIFT)
+ {
+ shiftDown = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/batools/Calls.java b/runelite-client/src/main/java/net/runelite/client/plugins/batools/Calls.java
new file mode 100644
index 0000000000..0c273f5be5
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/batools/Calls.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018, Cameron
+ * 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.batools;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum Calls
+{
+ //Attacker Calls
+ RED_EGG("Red egg", "Tell-red"),
+ GREEN_EGG("Green egg", "Tell-green"),
+ BLUE_EGG("Blue egg", "Tell-blue"),
+ //Collector Calls
+ CONTROLLED("Controlled/Bullet/Wind", "Tell-controlled"),
+ ACCURATE("Accurate/Field/Water", "Tell-accurate"),
+ AGGRESSIVE("Aggressive/Blunt/Earth", "Tell-aggressive"),
+ DEFENSIVE("Defensive/Barbed/Fire", "Tell-defensive"),
+ //Healer Calls
+ TOFU("Tofu", "Tell-tofu"),
+ CRACKERS("Crackers", "Tell-crackers"),
+ WORMS("Worms", "Tell-worms"),
+ //Defender Calls
+ POIS_WORMS("Pois. Worms", "Tell-worms"),
+ POIS_TOFU("Pois. Tofu", "Tell-tofu"),
+ POIS_MEAT("Pois. Meat", "Tell-meat");
+
+ private final String call;
+ private final String option;
+
+ private static final Map CALL_MENU = new HashMap<>();
+
+ static
+ {
+ for (Calls s : values())
+ {
+ CALL_MENU.put(s.getCall(), s.getOption());
+ }
+ }
+
+ Calls(String call, String option)
+ {
+ this.call = call;
+ this.option = option;
+ }
+
+ public String getCall()
+ {
+ return call;
+ }
+
+ public String getOption()
+ {
+ return option;
+ }
+
+ public static String getOption(String call)
+ {
+ return CALL_MENU.get(call);
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/batools/CycleCounter.java b/runelite-client/src/main/java/net/runelite/client/plugins/batools/CycleCounter.java
new file mode 100644
index 0000000000..e8f6e3e639
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/batools/CycleCounter.java
@@ -0,0 +1,14 @@
+package net.runelite.client.plugins.batools;
+
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.ui.overlay.infobox.Counter;
+
+import java.awt.image.BufferedImage;
+
+public class CycleCounter extends Counter
+{
+ public CycleCounter(BufferedImage img, Plugin plugin, int tick)
+ {
+ super(img, plugin, tick);
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/batools/Healer.java b/runelite-client/src/main/java/net/runelite/client/plugins/batools/Healer.java
new file mode 100644
index 0000000000..9fb07e90e8
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/batools/Healer.java
@@ -0,0 +1,84 @@
+package net.runelite.client.plugins.batools;
+
+
+import lombok.Getter;
+import lombok.Setter;
+
+import net.runelite.api.NPC;
+import net.runelite.api.Actor;
+
+
+public class Healer
+{
+
+ @Getter
+ private NPC npc;
+
+ @Getter
+ @Setter
+ private int wave;
+
+ @Getter
+ @Setter
+ private int spawnNumber;
+
+ @Getter
+ @Setter
+ private int foodRemaining;
+
+ @Getter
+ @Setter
+ private int lastFoodTime;
+
+ @Getter
+ @Setter
+ private int firstCallFood;
+
+ @Getter
+ @Setter
+ private int secondCallFood;
+
+
+
+ public Healer(NPC npc, int spawnNumber, int wave)
+ {
+ this.npc = npc;
+ this.wave = wave;
+ this.spawnNumber = spawnNumber;
+ this.firstCallFood = getCode(wave).getFirstCallFood()[spawnNumber];
+ this.secondCallFood = getCode(wave).getSecondCallFood()[spawnNumber];
+ this.foodRemaining = firstCallFood + secondCallFood;
+ this.lastFoodTime = getCode(wave).getSpacing()[spawnNumber];
+ }
+
+ private HealerCode getCode(int wave)
+ {
+ switch(wave)
+ {
+ case 1:
+ return HealerCode.WAVEONE;
+ case 2:
+ return HealerCode.WAVETWO;
+ case 3:
+ return HealerCode.WAVETHREE;
+ case 4:
+ return HealerCode.WAVEFOUR;
+ case 5:
+ return HealerCode.WAVEFIVE;
+ case 6:
+ return HealerCode.WAVESIX;
+ case 7:
+ return HealerCode.WAVESEVEN;
+ case 8:
+ return HealerCode.WAVEEIGHT;
+ case 9:
+ return HealerCode.WAVENINE;
+ case 10:
+ return HealerCode.WAVETEN;
+ default: return null;
+ }
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/batools/HealerCode.java b/runelite-client/src/main/java/net/runelite/client/plugins/batools/HealerCode.java
new file mode 100644
index 0000000000..ee7f492585
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/batools/HealerCode.java
@@ -0,0 +1,34 @@
+package net.runelite.client.plugins.batools;
+
+import lombok.Getter;
+
+
+enum HealerCode
+{
+
+ WAVEONE(new int[] {1,1}, new int[] {0,0}, new int[] {0,0}),
+ WAVETWO(new int[] {1,1,2}, new int[] {0,0,0}, new int[] {0,0,21}),
+ WAVETHREE(new int[] {1,6,2}, new int[] {0,0,0}, new int[] {0,0,0}),
+ WAVEFOUR(new int[] {2,5,2,0}, new int[] {0,0,7,10}, new int[] {0,0,0,0}),
+ WAVEFIVE(new int[] {2,5,2,3,0}, new int[] {0,0,0,0,7}, new int[] {0,0,21,30,0}),
+ WAVESIX(new int[] {3,5,3,1,0,0}, new int[] {0,0,0,2,9,10}, new int[] {18,0,0,0,0,0}),
+ WAVESEVEN(new int[] {5,2,1,1,0,0,0}, new int[] {0,0,0,0,6,8,10}, new int[] {27,33,0,0,51,0,0}),
+ WAVEEIGHT(new int[] {2,8,1,1,0,0,0}, new int[] {1,0,1,1,3,1,10}, new int[] {36,0,33,39,45,48,0}),
+ WAVENINE(new int[] {2,8,1,1,0,0,0,0}, new int[] {1,1,1,1,1,1,1,10}, new int[] {0,21,0,0,0,0,0,0,0}),
+ WAVETEN(new int[] {5,2,1,1,0,0,0}, new int[] {0,1,1,1,3,3,10}, new int[] {27,33,0,0,51,0,0});
+
+
+ @Getter
+ private final int[] firstCallFood;
+ @Getter
+ private final int[] secondCallFood;
+ @Getter
+ private final int[] spacing;
+
+ HealerCode(int[] firstCallFood, int[] secondCallFood, int[] spacing)
+ {
+ this.firstCallFood = firstCallFood;
+ this.secondCallFood = secondCallFood;
+ this.spacing = spacing;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonConfig.java
index 29a1496f85..15f50725e6 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonConfig.java
@@ -83,4 +83,24 @@ public interface CannonConfig extends Config
{
return true;
}
+
+ @ConfigItem(
+ keyName = "ammoAmount",
+ name = "Ammo left",
+ description = "Configure to set the amount of ammo left to receive ammo left notification"
+ )
+ default int ammoAmount()
+ {
+ return 5;
+ }
+
+ @ConfigItem(
+ keyName = "notifyAmmoLeft",
+ name = "Ammo left notification",
+ description = "Sends a notification when cannon ammo is under the specified amount"
+ )
+ default boolean notifyAmmoLeft()
+ {
+ return true;
+ }
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonPlugin.java
index de44d67c59..9e7b460fa5 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonPlugin.java
@@ -46,12 +46,7 @@ import net.runelite.api.Projectile;
import static net.runelite.api.ProjectileID.CANNONBALL;
import static net.runelite.api.ProjectileID.GRANITE_CANNONBALL;
import net.runelite.api.coords.WorldPoint;
-import net.runelite.api.events.ChatMessage;
-import net.runelite.api.events.ConfigChanged;
-import net.runelite.api.events.GameObjectSpawned;
-import net.runelite.api.events.GameTick;
-import net.runelite.api.events.ItemContainerChanged;
-import net.runelite.api.events.ProjectileMoved;
+import net.runelite.api.events.*;
import net.runelite.client.Notifier;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
@@ -275,6 +270,7 @@ public class CannonPlugin extends Plugin
if (!skipProjectileCheckThisTick)
{
cballsLeft--;
+ client.getCallbacks().post(new CannonballFired());
}
}
}
@@ -379,6 +375,13 @@ public class CannonPlugin extends Plugin
{
return Color.orange;
}
+ else if (cballsLeft <= config.ammoAmount())
+ {
+ if (config.notifyAmmoLeft())
+ {
+ notifier.notify("Your cannon has " + config.ammoAmount() + " balls left!");
+ }
+ }
return Color.red;
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatConfig.java
index 5fdd7996be..f60f143f06 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatConfig.java
@@ -126,4 +126,37 @@ public interface ClanChatConfig extends Config
{
return false;
}
+
+ @ConfigItem(
+ keyName = "discord",
+ name = "Discord",
+ description = "Send clan chats to a discord webhook
See https://support.discordapp.com/hc/en-us/articles/228383668",
+ position = 8
+ )
+ default boolean discord()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "discordPath",
+ name = "Webhook path",
+ description = "Your webhook id and webhook token
(the part after \"/webhooks/\")",
+ position = 9
+ )
+ default String discordPath()
+ {
+ return "";
+ }
+
+ @ConfigItem(
+ keyName = "discordAccount",
+ name = "Discord/RS account",
+ description = "The login username (not rsn!) of the runescape account you want to use this on",
+ position = 10
+ )
+ default String discordAccount()
+ {
+ return "";
+ }
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatPlugin.java
index 506c9a58c0..e9cf3d698b 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatPlugin.java
@@ -39,6 +39,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
import net.runelite.api.ChatLineBuffer;
import net.runelite.api.ChatMessageType;
import net.runelite.api.ClanMember;
@@ -72,18 +73,22 @@ import net.runelite.client.game.ClanManager;
import net.runelite.client.game.SpriteManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.plugins.clanchat.discord.DiscordClient;
+import net.runelite.client.plugins.clanchat.discord.DiscordMessage;
import static net.runelite.client.ui.JagexColors.CHAT_CLAN_NAME_OPAQUE_BACKGROUND;
import static net.runelite.client.ui.JagexColors.CHAT_CLAN_NAME_TRANSPARENT_BACKGROUND;
import static net.runelite.client.ui.JagexColors.CHAT_CLAN_TEXT_OPAQUE_BACKGROUND;
import static net.runelite.client.ui.JagexColors.CHAT_CLAN_TEXT_TRANSPARENT_BACKGROUND;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
import net.runelite.client.util.Text;
+import static net.runelite.client.util.Text.removeTags;
@PluginDescriptor(
name = "Clan Chat",
description = "Add rank icons to users talking in clan chat",
tags = {"icons", "rank", "recent"}
)
+@Slf4j
public class ClanChatPlugin extends Plugin
{
private static final int MAX_CHATS = 10;
@@ -113,6 +118,9 @@ public class ClanChatPlugin extends Plugin
private List chats = new ArrayList<>();
private List clanMembers = new ArrayList<>();
private ClanChatIndicator clanMemberCounter;
+
+ private DiscordClient discordClient;
+
/**
* queue of temporary messages added to the client
*/
@@ -138,6 +146,7 @@ public class ClanChatPlugin extends Plugin
clanMembers.clear();
removeClanCounter();
resetClanChats();
+ stopDiscordClient();
}
@Subscribe
@@ -158,6 +167,15 @@ public class ClanChatPlugin extends Plugin
{
removeClanCounter();
}
+
+ if (config.discord())
+ {
+ startDiscordClient();
+ }
+ else
+ {
+ stopDiscordClient();
+ }
}
}
@@ -285,6 +303,38 @@ public class ClanChatPlugin extends Plugin
addClanActivityMessages();
}
+ private void submitMessage(String message, String user, String url)
+ {
+ if (discordClient == null)
+ {
+ return;
+ }
+
+ DiscordMessage discordMessage = new DiscordMessage();
+
+ message = removeTags(message);
+ if (!user.contains("
") && !user.contains("
"))
+ {
+ discordMessage.setContent(message);
+ }
+ else if (user.contains("img=10"))
+ {
+ discordMessage.setContent("<:hcim:557056153834487819> " + message);
+ }
+ else
+ {
+ discordMessage.setContent("<:iron:557056153729630209> " + message);
+ }
+ discordMessage.setUsername(removeTags(user));
+ discordMessage.setAvatar_url(url);
+
+ discordClient.submit(discordMessage, config.discordPath());
+ }
+ private void submitMessage(String message)
+ {
+ submitMessage(message, "", "");
+ }
+
private void timeoutClanMessages()
{
if (clanJoinMessages.isEmpty())
@@ -346,7 +396,7 @@ public class ClanChatPlugin extends Plugin
private void addActivityMessage(ClanMember member, ClanActivityType activityType)
{
- final String activityMessage = activityType == ClanActivityType.JOINED ? " has joined." : " has left.";
+ final String activityMessage = activityType == ClanActivityType.JOINED ? " has joined. " : " has left. ";
final ClanMemberRank rank = member.getRank();
Color textColor = CHAT_CLAN_TEXT_OPAQUE_BACKGROUND;
Color channelColor = CHAT_CLAN_NAME_OPAQUE_BACKGROUND;
@@ -358,7 +408,7 @@ public class ClanChatPlugin extends Plugin
channelColor = CHAT_CLAN_NAME_TRANSPARENT_BACKGROUND;
}
- if (config.clanChatIcons() && rank != null && rank != ClanMemberRank.UNRANKED)
+ if (config.clanChatIcons() && rank != ClanMemberRank.UNRANKED)
{
rankIcon = clanManager.getIconNumber(rank);
}
@@ -378,6 +428,10 @@ public class ClanChatPlugin extends Plugin
final String messageString = message.build();
client.addChatMessage(ChatMessageType.FRIENDSCHATNOTIFICATION, "", messageString, "");
+ if (discordClient != null)
+ {
+ submitMessage(member.getUsername() + activityMessage + client.getClanChatCount() + " people online.");
+ }
final ChatLineBuffer chatLineBuffer = client.getChatLineMap().get(ChatMessageType.FRIENDSCHATNOTIFICATION.getType());
final MessageNode[] lines = chatLineBuffer.getLines();
@@ -399,12 +453,7 @@ public class ClanChatPlugin extends Plugin
@Subscribe
public void onChatMessage(ChatMessage chatMessage)
{
- if (client.getGameState() != GameState.LOADING && client.getGameState() != GameState.LOGGED_IN)
- {
- return;
- }
-
- if (client.getClanChatCount() <= 0)
+ if (client.getGameState() != GameState.LOADING && client.getGameState() != GameState.LOGGED_IN || client.getClanChatCount() <= 0)
{
return;
}
@@ -426,6 +475,12 @@ public class ClanChatPlugin extends Plugin
}
break;
case FRIENDSCHAT:
+ if (discordClient != null)
+ {
+ String url = clanManager.getRank(chatMessage.getName()).getDiscavatar();
+ submitMessage(chatMessage.getMessage(), chatMessage.getName(), url);
+ }
+
if (!config.clanChatIcons())
{
return;
@@ -447,8 +502,12 @@ public class ClanChatPlugin extends Plugin
{
clanMembers.clear();
removeClanCounter();
-
clanJoinMessages.clear();
+
+ if (gameState == GameState.LOGIN_SCREEN)
+ {
+ stopDiscordClient();
+ }
}
}
@@ -477,6 +536,11 @@ public class ClanChatPlugin extends Plugin
if (event.isJoined())
{
clanJoinedTick = client.getTickCount();
+
+ if (config.discord())
+ {
+ startDiscordClient();
+ }
}
else
{
@@ -595,4 +659,22 @@ public class ClanChatPlugin extends Plugin
clanMemberCounter = new ClanChatIndicator(image, this);
infoBoxManager.addInfoBox(clanMemberCounter);
}
+
+ private void startDiscordClient()
+ {
+ if (discordClient == null && config.discordAccount().equals(client.getUsername()))
+ {
+ discordClient = new DiscordClient();
+ submitMessage("Started\n\n" + client.getLocalPlayer().getName() + " has joined.");
+ }
+ }
+
+ private void stopDiscordClient()
+ {
+ if (discordClient != null)
+ {
+ submitMessage(client.getLocalPlayer().getName() + " has left.\n\nStopped");
+ discordClient = null;
+ }
+ }
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/discord/DiscordClient.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/discord/DiscordClient.java
new file mode 100644
index 0000000000..ec523710e1
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/discord/DiscordClient.java
@@ -0,0 +1,63 @@
+package net.runelite.client.plugins.clanchat.discord;
+
+import com.google.gson.Gson;
+import java.io.IOException;
+import static java.lang.Integer.parseInt;
+import java.time.Instant;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.http.api.RuneLiteAPI;
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.HttpUrl;
+import okhttp3.MediaType;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+@Slf4j
+public class DiscordClient
+{
+ private static final MediaType JSON = MediaType.parse("application/json");
+ private static final Gson GSON = RuneLiteAPI.GSON;
+ private int rateLimit = 1;
+ private Instant rateReset = Instant.EPOCH;
+
+ public void submit(DiscordMessage message, String path)
+ {
+ if (rateLimit < 1 && Instant.now().isBefore(rateReset))
+ {
+ return;
+ }
+
+ final HttpUrl url = new HttpUrl.Builder()
+ .scheme("https")
+ .host("discordapp.com")
+ .addPathSegments("api/webhooks")
+ .addPathSegments(path)
+ .build();
+
+ Request request = new Request.Builder()
+ .post(RequestBody.create(JSON, GSON.toJson(message)))
+ .url(url)
+ .build();
+
+ RuneLiteAPI.CLIENT.newCall(request).enqueue(new Callback()
+ {
+ @Override
+ public void onFailure(Call call, IOException e)
+ {
+ log.debug("discord message failed", e);
+ }
+
+ @Override
+ public void onResponse(Call call, Response response)
+ {
+ rateLimit = parseInt(response.header("X-RateLimit-Remaining"));
+ rateReset = Instant.ofEpochSecond((parseInt(response.header("X-RateLimit-Reset"))));
+ log.debug("Submitted discord message, limit: {}, reset: {}", rateLimit, rateReset);
+ response.close();
+ }
+ });
+ }
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/discord/DiscordMessage.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/discord/DiscordMessage.java
new file mode 100644
index 0000000000..f4f037824c
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/discord/DiscordMessage.java
@@ -0,0 +1,12 @@
+package net.runelite.client.plugins.clanchat.discord;
+
+import lombok.Data;
+
+@Data
+public class DiscordMessage
+{
+ private String username;
+ private String content;
+ private String avatar_url;
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModeConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModeConfig.java
new file mode 100644
index 0000000000..3f2d8da467
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModeConfig.java
@@ -0,0 +1,168 @@
+package net.runelite.client.plugins.clanmanmode;
+
+import java.awt.Color;
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("clanmanmode")
+public interface ClanManModeConfig extends Config
+{
+ @ConfigItem(
+ position = 0,
+ keyName = "highlightattackable",
+ name = "Highlight attackable targets",
+ description = "Highlights targets attackable by all clan members"
+ )
+ default boolean highlightAttackable()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 1,
+ keyName = "attackablecolor",
+ name = "Attackable target c olor",
+ description = "Color of targets all clan members can target"
+ )
+ default Color getAttackableColor()
+ {
+ return new Color(0, 184, 212);
+ }
+
+ @ConfigItem(
+ position = 2,
+ keyName = "highlightattacked",
+ name = "Highlight clan targets",
+ description = "Highlights people being attacked by your clan"
+ )
+ default boolean highlightAttacked()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 3,
+ keyName = "attackedcolor",
+ name = "Clan target color",
+ description = "Color of players being attacked by clan"
+ )
+ default Color getClanAttackableColor()
+ {
+ return new Color(0, 184, 212);
+ }
+
+ @ConfigItem(
+ position = 4,
+ keyName = "drawPlayerTiles",
+ name = "Draw tiles under players",
+ description = "Configures whether or not tiles under highlighted players should be drawn"
+ )
+ default boolean drawTiles()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 5,
+ keyName = "drawOverheadPlayerNames",
+ name = "Draw names above players",
+ description = "Configures whether or not player names should be drawn above players"
+ )
+ default boolean drawOverheadPlayerNames()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ position = 6,
+ keyName = "drawMinimapNames",
+ name = "Draw names on minimap",
+ description = "Configures whether or not minimap names for players with rendered names should be drawn"
+ )
+ default boolean drawMinimapNames()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 7,
+ keyName = "showtargets",
+ name = "Highlight My Attackers",
+ description = "Shows players interacting with you"
+ )
+ default boolean showAttackers()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 8,
+ keyName = "attackcolor",
+ name = "Attacker Color",
+ description = "Color of attackers"
+ )
+ default Color getAttackerColor()
+ {
+ return new Color(255, 0, 0);
+ }
+
+ @ConfigItem(
+ position = 9,
+ keyName = "showbold",
+ name = "Bold names of clan targets",
+ description = "Turns names of clan targets bold"
+ )
+ default boolean ShowBold() { return false; }
+
+ @ConfigItem(
+ position = 10,
+ keyName = "hideafter",
+ name = "Hide attackable targets after login",
+ description = "Automatically disables attackable player highlighting after login"
+ )
+ default boolean hideAttackable() { return false; }
+
+ @ConfigItem(
+ position = 11,
+ keyName = "hidetime",
+ name = "Ticks to hide",
+ description = "How many ticks after you are logged in that attackbles are hidden (1 tick = 0.6 seconds)"
+ )
+ default int hideTime() { return 5; }
+
+ @ConfigItem(
+ position = 12,
+ keyName = "mycblvl",
+ name = "Calc targets on my own combat level",
+ description = "Calculates potential targets based off your own combat lvl instead of clans"
+ )
+ default boolean CalcSelfCB() { return false; }
+
+ @ConfigItem(
+ position = 13,
+ keyName = "hideatkopt",
+ name = "Hide attack option for clan members",
+ description = "Disables attack option for clan members"
+ )
+ default boolean hideAtkOpt() { return false; }
+
+ @ConfigItem(
+ position = 14,
+ keyName = "showclanmembers",
+ name = "Persistent Clan Members",
+ description = "Will highlight clan members even when not in clan chat"
+ )
+ default boolean PersistentClan() { return false; }
+
+ @ConfigItem(
+ position = 15,
+ keyName = "clancolor",
+ name = "Clan Member Color",
+ description = "Color of clan members"
+ )
+ default Color getClanMemberColor()
+ {
+ return new Color(255, 0, 0);
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModeMinimapOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModeMinimapOverlay.java
new file mode 100644
index 0000000000..be94ee06f3
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModeMinimapOverlay.java
@@ -0,0 +1,52 @@
+package net.runelite.client.plugins.clanmanmode;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import net.runelite.api.Player;
+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.OverlayPriority;
+import net.runelite.client.ui.overlay.OverlayUtil;
+
+@Singleton
+public class ClanManModeMinimapOverlay extends Overlay
+{
+ private final ClanManModeService ClanManModeService;
+ private final ClanManModeConfig config;
+
+ @Inject
+ private ClanManModeMinimapOverlay(ClanManModeConfig config, ClanManModeService ClanManModeService)
+ {
+ this.config = config;
+ this.ClanManModeService = ClanManModeService;
+ setLayer(OverlayLayer.ABOVE_WIDGETS);
+ setPosition(OverlayPosition.DYNAMIC);
+ setPriority(OverlayPriority.HIGH);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ ClanManModeService.forEachPlayer((player, color) -> renderPlayerOverlay(graphics, player, color));
+ return null;
+ }
+
+ private void renderPlayerOverlay(Graphics2D graphics, Player actor, Color color)
+ {
+ final String name = actor.getName().replace('\u00A0', ' ');
+
+ if (config.drawMinimapNames())
+ {
+ final net.runelite.api.Point minimapLocation = actor.getMinimapLocation();
+
+ if (minimapLocation != null)
+ {
+ OverlayUtil.renderTextLocation(graphics, minimapLocation, name, color);
+ }
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModeOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModeOverlay.java
new file mode 100644
index 0000000000..ff058f675c
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModeOverlay.java
@@ -0,0 +1,63 @@
+package net.runelite.client.plugins.clanmanmode;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import net.runelite.api.ClanMemberRank;
+import net.runelite.api.Player;
+import net.runelite.api.Point;
+import net.runelite.client.game.ClanManager;
+import net.runelite.client.ui.FontManager;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.OverlayPriority;
+import net.runelite.client.ui.overlay.OverlayUtil;
+
+@Singleton
+public class ClanManModeOverlay extends Overlay
+{
+ private final ClanManModeService ClanManModeService;
+ private final ClanManModeConfig config;
+ private final ClanManager clanManager;
+
+ @Inject
+ private ClanManModeOverlay(ClanManModeConfig config, ClanManModeService ClanManModeService,
+ ClanManager clanManager)
+ {
+ this.config = config;
+ this.ClanManModeService = ClanManModeService;
+ this.clanManager = clanManager;
+ setPosition(OverlayPosition.DYNAMIC);
+ setPriority(OverlayPriority.MED);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ ClanManModeService.forEachPlayer((player, color) -> renderPlayerOverlay(graphics, player, color));
+ return null;
+ }
+
+ private void renderPlayerOverlay(Graphics2D graphics, Player actor, Color color)
+ {
+ if (!config.drawOverheadPlayerNames())
+ {
+ return;
+ }
+
+ String name = actor.getName().replace('\u00A0', ' ');
+ int offset = actor.getLogicalHeight() + 40;
+ Point textLocation = actor.getCanvasTextLocation(graphics, name, offset);
+
+ if (textLocation != null)
+ {
+ if (config.getClanAttackableColor().equals(color) && config.ShowBold()) {
+ graphics.setFont(FontManager.getRunescapeBoldFont());
+ }
+ OverlayUtil.renderTextLocation(graphics, textLocation, name, color);
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModePlugin.java
new file mode 100644
index 0000000000..d71d054674
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModePlugin.java
@@ -0,0 +1,137 @@
+package net.runelite.client.plugins.clanmanmode;
+
+import net.runelite.client.eventbus.Subscribe;
+import com.google.inject.Provides;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.inject.Inject;
+import net.runelite.api.*;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.api.events.GameStateChanged;
+import net.runelite.api.events.GameTick;
+import net.runelite.api.events.MenuEntryAdded;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.game.ClanManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+import net.runelite.client.util.Text;
+import org.apache.commons.lang3.ArrayUtils;
+
+@PluginDescriptor(
+ name = "!Clan Man Mode",
+ description = "Assists in clan PVP scenarios",
+ tags = {"highlight", "minimap", "overlay", "players"}
+)
+public class ClanManModePlugin extends Plugin
+{
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private ClanManModeConfig config;
+
+ @Inject
+ private ClanManModeOverlay ClanManModeOverlay;
+
+ @Inject
+ private ClanManModeTileOverlay ClanManModeTileOverlay;
+
+ @Inject
+ private ClanManModeMinimapOverlay ClanManModeMinimapOverlay;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private ClanManager clanManager;
+
+ @Provides
+ ClanManModeConfig provideConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(ClanManModeConfig.class);
+ }
+
+ int wildernessLevel;
+ int clanmin;
+ int clanmax;
+ int inwildy;
+ int ticks;
+ Map clan = new HashMap<>();
+
+ @Override
+ protected void startUp() throws Exception {
+ overlayManager.add(ClanManModeOverlay);
+ overlayManager.add(ClanManModeTileOverlay);
+ overlayManager.add(ClanManModeMinimapOverlay);
+ }
+
+ @Override
+ protected void shutDown() throws Exception {
+ overlayManager.remove(ClanManModeOverlay);
+ overlayManager.remove(ClanManModeTileOverlay);
+ overlayManager.remove(ClanManModeMinimapOverlay);
+ clan.clear();
+ ticks = 0;
+ wildernessLevel = 0;
+ clanmin = 0;
+ clanmax = 0;
+ inwildy = 0;
+ }
+
+ @Subscribe
+ public void onGameStateChanged(GameStateChanged gameStateChanged) {
+ if (gameStateChanged.getGameState() == GameState.LOGIN_SCREEN || gameStateChanged.getGameState() == GameState.HOPPING) {
+ ticks = 0;
+ }
+ }
+
+ @Subscribe
+ public void onGameTick(GameTick event) {
+ ticks++;
+ final Player localPlayer = client.getLocalPlayer();
+ if (!clan.containsKey(localPlayer.getName())) {
+ clan.put(localPlayer.getName(), localPlayer.getCombatLevel());
+ }
+ WorldPoint a = localPlayer.getWorldLocation();
+ int underLevel = ((a.getY() - 9920) / 8) + 1;
+ int upperLevel = ((a.getY() - 3520) / 8) + 1;
+ wildernessLevel = a.getY() > 6400 ? underLevel : upperLevel;
+ inwildy = client.getVar(Varbits.IN_WILDERNESS);
+ if (clan.size() > 0) {
+ clanmin = Collections.min(clan.values());
+ clanmax = Collections.max(clan.values());
+ }
+ }
+
+ @Subscribe
+ public void onMenuEntryAdded(MenuEntryAdded event) {
+ if (!config.hideAtkOpt()) {
+ return;
+ }
+ if (client.getGameState() != GameState.LOGGED_IN) {
+ return;
+ }
+
+ final String option = Text.removeTags(event.getOption()).toLowerCase();
+
+ if (option.equals("attack")) {
+ final Pattern ppattern = Pattern.compile("(.+?) interactors = new HashMap<>();
+
+ public void forEachPlayer(final BiConsumer consumer)
+ {
+ int minatk = plugin.clanmax - plugin.wildernessLevel;
+ int maxatk = plugin.clanmin + plugin.wildernessLevel;
+ final Player localPlayer = client.getLocalPlayer();
+ final String localName = localPlayer.getName();
+ int selfmin = localPlayer.getCombatLevel() - plugin.wildernessLevel;
+ int selfmax = localPlayer.getCombatLevel() + plugin.wildernessLevel;
+ for (Player player : client.getPlayers())
+ {
+ if (player == null || player.getName() == null) {
+ continue;
+ }
+
+ if (player == localPlayer) {
+ continue;
+ }
+
+ boolean isClanMember = player.isClanMember();
+ Actor interacting = player.getInteracting();
+ Player interactor = null;
+ if (interacting != null && !(interacting instanceof NPC)) {
+ interactor = ((Player) interacting);
+ }
+
+ if (config.showAttackers()) {
+ if (interactor != null) {
+ if (interactor.getName().equals(localName)) {
+ consumer.accept(player, config.getAttackerColor());
+ }
+ }
+ }
+
+ if (plugin.inwildy == 1) {
+ if (isClanMember) {
+ if (!plugin.clan.containsKey(player.getName())) {
+ plugin.clan.put(player.getName(), player.getCombatLevel());
+ }
+ if (config.highlightAttacked()) {
+ if (interactor != null) {
+ if (!interactors.containsKey(interactor.getName())) {
+ WorldPoint a = interactor.getWorldLocation();
+ int underLevel = ((a.getY() - 9920) / 8) + 1;
+ int upperLevel = ((a.getY() - 3520) / 8) + 1;
+ int wildernessLevel = a.getY() > 6400 ? underLevel : upperLevel;
+ int wildydiff = plugin.wildernessLevel - wildernessLevel;
+ if (wildydiff < 0) {
+ wildydiff = 0;
+ }
+ if (config.CalcSelfCB()) {
+ if (interacting.getCombatLevel() <= selfmax && interacting.getCombatLevel() - wildydiff >= selfmin && !interactor.isClanMember()) {
+ interactors.put(interactor.getName(), player.getName());
+ consumer.accept(interactor, config.getClanAttackableColor());
+ }
+ } else {
+ if (interacting.getCombatLevel() <= maxatk && interacting.getCombatLevel() - wildydiff >= minatk && !interactor.isClanMember()) {
+ interactors.put(interactor.getName(), player.getName());
+ consumer.accept(interactor, config.getClanAttackableColor());
+ }
+ }
+ }
+ }
+ }
+ } else {
+ if (config.PersistentClan()) {
+ if (plugin.clan.containsKey(player.getName())) {
+ consumer.accept(player, config.getClanMemberColor());
+ }
+ }
+ if (config.highlightAttacked()) {
+ if (interactors.containsKey(player.getName())) {
+ String attackername = interactors.get(player.getName());
+ Boolean found = false;
+ for (Player attacker : client.getPlayers()) {
+ if (attacker == null || attacker.getName() == null) {
+ continue;
+ }
+ if (attacker.getName().equals(attackername)) {
+ found = true;
+ Actor ainteract = attacker.getInteracting();
+ if (ainteract != null) {
+ if (ainteract.getName().equals(player.getName())) {
+ consumer.accept(player, config.getClanAttackableColor());
+ } else {
+ interactors.remove(player.getName());
+ }
+ } else {
+ interactors.remove(player.getName());
+ }
+ break;
+ }
+ }
+ if (!found) {
+ interactors.remove(player.getName());
+ }
+ continue;
+ }
+ }
+ if (config.highlightAttackable()) {
+ if ((config.hideAttackable() && plugin.ticks >= config.hideTime()) || plugin.clan.containsKey(player.getName())) {
+ continue;
+ }
+ WorldPoint a = player.getWorldLocation();
+ int underLevel = ((a.getY() - 9920) / 8) + 1;
+ int upperLevel = ((a.getY() - 3520) / 8) + 1;
+ int wildernessLevel = a.getY() > 6400 ? underLevel : upperLevel;
+ int wildydiff = plugin.wildernessLevel - wildernessLevel;
+ if (wildydiff < 0) {
+ wildydiff = 0;
+ }
+ if (config.CalcSelfCB()) {
+ if (player.getCombatLevel() <= selfmax && player.getCombatLevel() - wildydiff >= selfmin) {
+ consumer.accept(player, config.getAttackableColor());
+ }
+ } else {
+ if (player.getCombatLevel() <= maxatk && player.getCombatLevel() - wildydiff >= minatk) {
+ consumer.accept(player, config.getAttackableColor());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModeTileOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModeTileOverlay.java
new file mode 100644
index 0000000000..5aea2e108f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanmanmode/ClanManModeTileOverlay.java
@@ -0,0 +1,48 @@
+package net.runelite.client.plugins.clanmanmode;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Polygon;
+import javax.inject.Inject;
+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.OverlayPriority;
+import net.runelite.client.ui.overlay.OverlayUtil;
+
+public class ClanManModeTileOverlay extends Overlay
+{
+ private final ClanManModeService ClanManModeService;
+ private final ClanManModeConfig config;
+
+ @Inject
+ private ClanManModeTileOverlay(ClanManModeConfig config, ClanManModeService ClanManModeService)
+ {
+ this.config = config;
+ this.ClanManModeService = ClanManModeService;
+ setLayer(OverlayLayer.ABOVE_SCENE);
+ setPosition(OverlayPosition.DYNAMIC);
+ setPriority(OverlayPriority.MED);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if (!config.drawTiles())
+ {
+ return null;
+ }
+
+ ClanManModeService.forEachPlayer((player, color) ->
+ {
+ final Polygon poly = player.getCanvasTilePoly();
+
+ if (poly != null)
+ {
+ OverlayUtil.renderPolygon(graphics, poly, color);
+ }
+ });
+
+ return null;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cox/CoxConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/cox/CoxConfig.java
new file mode 100644
index 0000000000..8922ebf867
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cox/CoxConfig.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2019, Jacky
+ * 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.cox;
+
+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.Range;
+
+@ConfigGroup("hydra")
+public interface CoxConfig extends Config
+{
+ @ConfigItem(
+ position = 0,
+ keyName = "showHydraTile",
+ name = "Hydra's Size Box ",
+ description = "Displays hydra's size box for luring over vents"
+ )
+ default boolean showHydraTile()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 1,
+ keyName = "tileColour",
+ name = "Color of hydra tile size",
+ description = "Configures the color of hydra's size"
+ )
+ default Color hydraTileColour()
+ {
+ return Color.ORANGE;
+ }
+
+ @Range(
+ min = -1000,
+ max = 1000
+ )
+ @ConfigItem(
+ position = 2,
+ keyName = "prayerHeight",
+ name = "Sets hydra's prayer height",
+ description = "The height of the prayer indicator over Hydra"
+ )
+ default int prayerHeight()
+ {
+ return 450;
+ }
+
+ @ConfigItem(
+ position = 3,
+ keyName = "showHydraPrayer",
+ name = "Hydra's Attack Style Indicator",
+ description = "Displays hydra's size box for luring over vents"
+ )
+ default boolean showPrayer()
+ {
+ return false;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cox/CoxOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/cox/CoxOverlay.java
new file mode 100644
index 0000000000..1a227b7ec5
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cox/CoxOverlay.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2019, Jacky
+ * 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.cox;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import javax.inject.Inject;
+import net.runelite.api.Client;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.components.LineComponent;
+import net.runelite.client.ui.overlay.components.PanelComponent;
+
+public class CoxOverlay extends Overlay
+{
+ private final Client client;
+ private final CoxPlugin plugin;
+
+ private final PanelComponent panelComponent = new PanelComponent();
+
+ @Inject
+ public CoxOverlay(Client client, CoxPlugin plugin)
+ {
+ this.client = client;
+ this.plugin = plugin;
+ setPosition(OverlayPosition.TOP_LEFT);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if (!client.isInInstancedRegion()) return null;
+
+ panelComponent.getChildren().clear();
+ {
+ if (plugin.getOlm() != null)
+ {
+ if (plugin.getAttackStyle() == 0)
+ {
+ panelComponent.getChildren().add(LineComponent.builder()
+ .left("Pray: ")
+ .right("Mage")
+ .build());
+ }
+
+ if (plugin.getAttackStyle() == 1)
+ {
+ panelComponent.getChildren().add(LineComponent.builder()
+ .left("Pray: ")
+ .right("Ranged")
+ .build());
+ }
+ }
+ }
+ return panelComponent.render(graphics);
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cox/CoxOverlayAbove.java b/runelite-client/src/main/java/net/runelite/client/plugins/cox/CoxOverlayAbove.java
new file mode 100644
index 0000000000..3942055ec1
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cox/CoxOverlayAbove.java
@@ -0,0 +1,124 @@
+package net.runelite.client.plugins.cox;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Polygon;
+import java.util.List;
+import javax.inject.Inject;
+import net.runelite.api.Client;
+import net.runelite.api.Constants;
+import net.runelite.api.GameObject;
+import net.runelite.api.GraphicsObject;
+import net.runelite.api.Perspective;
+import net.runelite.api.Player;
+import net.runelite.api.Scene;
+import net.runelite.api.Tile;
+import net.runelite.api.coords.LocalPoint;
+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 CoxOverlayAbove extends Overlay
+{
+ private final Client client;
+ private final CoxPlugin plugin;
+ private CoxConfig config;
+
+ @Inject
+ public CoxOverlayAbove(Client client, CoxPlugin plugin, CoxConfig config)
+ {
+ setPosition(OverlayPosition.DYNAMIC);
+ setLayer(OverlayLayer.ABOVE_SCENE);
+ this.client = client;
+ this.config = config;
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if (!client.isInInstancedRegion() && plugin.getOlm() == null) return null;
+
+ renderGroundObject(graphics);
+
+ renderTileObjects(graphics);
+
+ return null;
+ }
+
+ // renders special attack ground objects
+ private void renderGroundObject(Graphics2D graphics)
+ {
+ List graphicsObjects = client.getGraphicsObjects();
+
+ for (GraphicsObject graphicsObject : graphicsObjects)
+ {
+ if (graphicsObject.getId() == 1447 || graphicsObject.getId() == 1356)
+ {
+ LocalPoint lp = graphicsObject.getLocation();
+ Polygon poly = Perspective.getCanvasTilePoly(client, lp);
+
+ if (poly != null)
+ {
+ OverlayUtil.renderPolygon(graphics, poly, Color.RED);
+ }
+ }
+ }
+ }
+
+ private void renderGameObjects(Graphics2D graphics, Tile tile, Player player)
+ {
+ GameObject[] gameObjects = tile.getGameObjects();
+ if (gameObjects != null)
+ {
+ for (GameObject gameObject : gameObjects)
+ {
+ if (gameObject != null && gameObject.getId() == 30033)
+ {
+ if (player.getLocalLocation().distanceTo(gameObject.getLocalLocation()) <= 5)
+ {
+ // Draw a polygon around the convex hull
+ // of the model vertices
+ Polygon p = gameObject.getConvexHull();
+ if (p != null)
+ {
+ graphics.drawPolygon(p);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void renderTileObjects(Graphics2D graphics)
+ {
+ Scene scene = client.getScene();
+ Tile[][][] tiles = scene.getTiles();
+
+ int z = client.getPlane();
+
+ for (int x = 0; x < Constants.SCENE_SIZE; ++x)
+ {
+ for (int y = 0; y < Constants.SCENE_SIZE; ++y)
+ {
+ Tile tile = tiles[z][x][y];
+
+ if (tile == null)
+ {
+ continue;
+ }
+
+ Player player = client.getLocalPlayer();
+ if (player == null)
+ {
+ continue;
+ }
+
+ renderGameObjects(graphics, tile, player);
+ }
+ }
+ }
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cox/CoxPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/cox/CoxPlugin.java
new file mode 100644
index 0000000000..afc3d095a7
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cox/CoxPlugin.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2019, Jacky
+ * 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.cox;
+
+import com.google.inject.Provides;
+import java.util.List;
+import javax.inject.Inject;
+import lombok.Getter;
+import net.runelite.api.Client;
+import net.runelite.api.NPC;
+import net.runelite.api.Projectile;
+import net.runelite.api.events.GameTick;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+
+@PluginDescriptor(
+ name = "Cox",
+ description = "COX Helper",
+ tags = {"combat", "overlay", "pve", "pvm"}
+)
+public class CoxPlugin extends Plugin
+{
+ @Getter
+ private NPC olm = null;
+
+ @Getter
+ private int attackStyle = 0; // 0 - mage // 1 - range
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private CoxOverlay coxOverlay;
+
+ @Inject CoxOverlayAbove coxOverlayAbove;
+
+ @Inject
+ private CoxConfig config;
+
+
+ @Provides
+ CoxConfig provideConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(CoxConfig.class);
+ }
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ overlayManager.add(coxOverlay);
+ overlayManager.add(coxOverlayAbove);
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ overlayManager.remove(coxOverlay);
+ overlayManager.remove(coxOverlayAbove);
+ }
+
+ @Subscribe
+ public void onGameTick(GameTick event)
+ {
+ if (!client.isInInstancedRegion())
+ {
+ olm = null;
+ return;
+ }
+
+ if (client.getPlane() != 0) return;
+
+ List npcs = client.getNpcs();
+
+ for (NPC npc : npcs)
+ {
+ if (npc.getId() == 7554)
+ {
+ olm = npc;
+ }
+ }
+
+ if (olm == null) return;
+
+ List projectiles = client.getProjectiles();
+
+ for (Projectile projectile : projectiles)
+ {
+ if (projectile.getId() == 1339)
+ {
+ // mage
+ attackStyle = 0;
+ }
+
+ if (projectile.getId() == 1340)
+ {
+ // range
+ attackStyle = 1;
+ }
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/bank/EasyBankConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/bank/EasyBankConfig.java
new file mode 100644
index 0000000000..ef9382fe66
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/bank/EasyBankConfig.java
@@ -0,0 +1,133 @@
+package net.runelite.client.plugins.easy.bank;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+
+@ConfigGroup("easybank")
+public interface EasyBankConfig extends Config {
+
+ @ConfigItem(
+ keyName = "withdrawOne",
+ name = "Withdraw/Deposit One",
+ description = "",
+ position = 0
+ )
+
+ default boolean getWithdrawOne() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "withdrawOneItems",
+ name = "Items",
+ description = "",
+ position = 1
+ )
+
+ default String getWithdrawOneItems() {
+ return "";
+ }
+
+ @ConfigItem(
+ keyName = "withdrawFive",
+ name = "Withdraw/Deposit Five",
+ description = "",
+ position = 2
+ )
+
+ default boolean getWithdrawFive() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "withdrawFiveItems",
+ name = "Items",
+ description = "",
+ position = 3
+ )
+
+ default String getWithdrawFiveItems() {
+ return "";
+ }
+
+ @ConfigItem(
+ keyName = "withdrawTen",
+ name = "Withdraw/Deposit Ten",
+ description = "",
+ position = 4
+ )
+
+ default boolean getWithdrawTen() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "withdrawTenItems",
+ name = "Items",
+ description = "",
+ position = 5
+ )
+
+ default String getWithdrawTenItems() {
+ return "";
+ }
+
+ @ConfigItem(
+ keyName = "withdrawX",
+ name = "Withdraw/Deposit X",
+ description = "",
+ position = 6
+ )
+
+ default boolean getWithdrawX() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "withdrawXAmount",
+ name = "Amount",
+ description = "",
+ position = 7
+ )
+
+ default String getWithdrawXAmount() {
+ return "";
+ }
+
+ @ConfigItem(
+ keyName = "withdrawXItems",
+ name = "Items",
+ description = "",
+ position = 8
+ )
+
+ default String getWithdrawXItems() {
+ return "";
+ }
+
+ @ConfigItem(
+ keyName = "withdrawAll",
+ name = "Withdraw/Deposit All",
+ description = "",
+ position = 9
+ )
+
+ default boolean getWithdrawAll() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "withdrawAllItems",
+ name = "Items",
+ description = "",
+ position = 10
+ )
+
+ default String getWithdrawAllItems() {
+ return "";
+ }
+
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/bank/EasyBankPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/bank/EasyBankPlugin.java
new file mode 100644
index 0000000000..4ef7e40d83
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/bank/EasyBankPlugin.java
@@ -0,0 +1,133 @@
+package net.runelite.client.plugins.easy.bank;
+
+import com.google.inject.Provides;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.GameState;
+import net.runelite.api.MenuEntry;
+import net.runelite.api.events.MenuEntryAdded;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.plugins.easy.util.Swapper;
+import net.runelite.client.util.Text;
+
+import javax.inject.Inject;
+
+
+@PluginDescriptor(
+ name = "EasyBank",
+ description = "EasyBank.",
+ tags = {"EasyBank", "easy"}
+)
+
+@Slf4j
+public class EasyBankPlugin extends Plugin {
+
+ private Swapper swapper = new Swapper();
+ private MenuEntry[] entries;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private EasyBankConfig config;
+
+ @Provides
+ EasyBankConfig provideConfig(ConfigManager configManager) {
+ return configManager.getConfig(EasyBankConfig.class);
+ }
+
+ @Override
+ public void startUp() {
+ log.debug("EasyBank Started.");
+ }
+
+ @Override
+ public void shutDown() {
+ log.debug("EasyBank Stopped.");
+ }
+
+ @Subscribe
+ public void onMenuEntryAdded(MenuEntryAdded event) {
+
+ if (client.getGameState() != GameState.LOGGED_IN) {
+ return;
+ }
+
+ Widget loginScreenOne = client.getWidget(WidgetInfo.LOGIN_CLICK_TO_PLAY_SCREEN);
+ Widget loginScreenTwo = client.getWidget(WidgetInfo.LOGIN_CLICK_TO_PLAY_SCREEN_MESSAGE_OF_THE_DAY);
+
+ if (loginScreenOne != null || loginScreenTwo != null) {
+ return;
+ }
+
+ final String option = Text.removeTags(event.getOption()).toLowerCase();
+ final String target = Text.removeTags(event.getTarget()).toLowerCase();
+
+ Widget widgetBankTitleBar = client.getWidget(WidgetInfo.BANK_TITLE_BAR);
+
+ swapper.setEntries(client.getMenuEntries());
+
+ if (!(widgetBankTitleBar == null) && !widgetBankTitleBar.isHidden()) {
+
+ if (config.getWithdrawOne()) {
+ for (String item : config.getWithdrawOneItems().split(",")) {
+ item = item.trim();
+ if (target.equalsIgnoreCase(item)) {
+ swapper.markForSwap("Withdraw-1", option, target);
+ swapper.markForSwap("Deposit-1", option, target);
+ }
+ }
+ }
+
+ if (config.getWithdrawFive()) {
+ for (String item : config.getWithdrawFiveItems().split(",")) {
+ item = item.trim();
+ if (target.equalsIgnoreCase(item)) {
+ swapper.markForSwap("Withdraw-5", option, target);
+ swapper.markForSwap("Deposit-5", option, target);
+ }
+ }
+ }
+
+ if (config.getWithdrawTen()) {
+ for (String item : config.getWithdrawTenItems().split(",")) {
+ item = item.trim();
+ if (target.equalsIgnoreCase(item)) {
+ swapper.markForSwap("Withdraw-10", option, target);
+ swapper.markForSwap("Deposit-10", option, target);
+ }
+ }
+ }
+
+ if (config.getWithdrawX()) {
+ for (String item : config.getWithdrawXItems().split(",")) {
+ item = item.trim();
+ if (target.equalsIgnoreCase(item)) {
+ swapper.markForSwap("Withdraw-" + config.getWithdrawXAmount(), option, target);
+ swapper.markForSwap("Deposit-" + config.getWithdrawXAmount(), option, target);
+ }
+ }
+ }
+
+ if (config.getWithdrawAll()) {
+ for (String item : config.getWithdrawAllItems().split(",")) {
+ item = item.trim();
+ if (target.equalsIgnoreCase(item)) {
+ swapper.markForSwap("Withdraw-All", option, target);
+ swapper.markForSwap("Deposit-All", option, target);
+ }
+ }
+ }
+
+ }
+
+ swapper.startSwap();
+ client.setMenuEntries(swapper.getEntries());
+ }
+
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/pvp/EasyPvpConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/pvp/EasyPvpConfig.java
new file mode 100644
index 0000000000..536fe4e343
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/pvp/EasyPvpConfig.java
@@ -0,0 +1,56 @@
+package net.runelite.client.plugins.easy.pvp;
+
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("easypvp")
+public interface EasyPvpConfig extends Config {
+
+ @ConfigItem(
+ keyName = "showWildernessRange",
+ name = "Show Wilderness Range",
+ description = "",
+ position = 0
+ )
+
+ default boolean getShowWildernessRange() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "ShowAttackablePlayers",
+ name = "Show Attackable Players",
+ description = "",
+ position = 1
+ )
+
+ default boolean getShowAttackablePlayers() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "warnProtectItemOff",
+ name = "Warn Protect Item Off",
+ description = "",
+ position = 2
+ )
+
+ default boolean getWarnProtectItemOff() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "showFreezeTimers",
+ name = "Show Freeze Timers",
+ description = "",
+ position = 3
+ )
+
+ default boolean getShowFreezeTimers() {
+ return true;
+ }
+
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/pvp/EasyPvpOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/pvp/EasyPvpOverlay.java
new file mode 100644
index 0000000000..81553cd787
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/pvp/EasyPvpOverlay.java
@@ -0,0 +1,102 @@
+package net.runelite.client.plugins.easy.pvp;
+
+import com.google.inject.Inject;
+import net.runelite.api.Client;
+import net.runelite.api.Player;
+import net.runelite.api.Point;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.OverlayPriority;
+import net.runelite.client.ui.overlay.OverlayUtil;
+
+import java.awt.*;
+
+public class EasyPvpOverlay extends Overlay {
+
+ private final Client client;
+ private final EasyPvpPlugin plugin;
+ private final EasyPvpConfig config;
+
+ @Inject
+ private EasyPvpOverlay(Client client, EasyPvpPlugin plugin, EasyPvpConfig config) {
+ this.client = client;
+ this.plugin = plugin;
+ this.config = config;
+
+ setPosition(OverlayPosition.DYNAMIC);
+ setPriority(OverlayPriority.MED);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics) {
+ if (config.getShowAttackablePlayers()) {
+ for (Player player : plugin.getTargets()) {
+ OverlayUtil.renderPolygon(graphics, player.getConvexHull(), Color.RED);
+
+ Point minimapLocation = player.getMinimapLocation();
+ if (minimapLocation != null) {
+ OverlayUtil.renderMinimapLocation(graphics, minimapLocation, Color.RED.darker());
+ OverlayUtil.renderTextLocation(graphics, minimapLocation, player.getName(), Color.RED);
+ }
+ }
+ }
+
+ //
+
+
+ return null;
+ }
+}
+// @Override
+// public Dimension render(Graphics2D graphics)
+// {
+// if (config.getShowWildernessRange())
+// {
+//// plugin.getDeadNpcsToDisplay().forEach((id, npc) -> renderNpcRespawn(npc, graphics));
+// }
+//
+// if (config.getShowAttackablePlayers())
+//
+// for (Player player : plugin.getAttackablePlayers()) {
+// if (player != null) {
+// renderNpcOverlay(graphics, player, player.getName(), Color.RED);
+// } else {
+// plugin.getAttackablePlayers().remove(player);
+// }
+// }
+//
+// return null;
+// }
+
+// private void renderNpcOverlay(Graphics2D graphics, Player actor, String name, Color color)
+// {
+// LocalPoint lp = actor.getLocalLocation();
+//// Polygon tilePoly = Perspective.getCanvasTileAreaPoly(client, lp, 1);
+//
+// renderTile(graphics, lp, color);
+//// renderMinimap();
+//
+//// if (config.drawNames())
+//// {
+//// Point textLocation = actor.getCanvasTextLocation(graphics, name, actor.getLogicalHeight() + 40);
+////
+//// if (textLocation != null)
+//// {
+//// OverlayUtil.renderTextLocation(graphics, textLocation, name, color);
+//// }
+//// }
+// }
+
+// private void renderTile(final Graphics2D graphics, final LocalPoint dest, final Color color)
+// {
+// if (dest == null)
+// {
+// return;
+// }
+// final Polygon poly = Perspective.getCanvasTilePoly(client, dest);
+// if (poly == null)
+// {
+// return;
+// }
+// OverlayUtil.renderPolygon(graphics, poly, color);
+// }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/pvp/EasyPvpPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/pvp/EasyPvpPlugin.java
new file mode 100644
index 0000000000..bca9c2b5a5
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/pvp/EasyPvpPlugin.java
@@ -0,0 +1,197 @@
+package net.runelite.client.plugins.easy.pvp;
+
+import com.google.inject.Inject;
+import com.google.inject.Provides;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.Player;
+import net.runelite.api.Varbits;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.api.events.GameTick;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@PluginDescriptor(
+ name = "EasyPvP",
+ description = "EasyPvP.",
+ tags = {"EasyPVP", "easy"}
+)
+
+@Slf4j
+public class EasyPvpPlugin extends Plugin {
+
+ private int inWildy;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private EasyPvpConfig config;
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private EasyPvpOverlay overlay;
+
+ @Getter(AccessLevel.PACKAGE)
+ private Set targets = new HashSet<>();
+
+ private String target;
+ private boolean prayMage;
+ private WorldPoint location;
+ private String spell;
+ private int currentExperience;
+ private int gainedExperience;
+
+ @Provides
+ EasyPvpConfig provideConfig(ConfigManager configManager) {
+ return configManager.getConfig(EasyPvpConfig.class);
+ }
+
+ @Override
+ public void startUp() {
+ prayMage = false;
+ spell = "";
+ overlayManager.add(overlay);
+ log.debug("EasyPvP Started.");
+ }
+
+ @Override
+ public void shutDown() {
+ overlayManager.remove(overlay);
+ log.debug("EasyPvP Stopped.");
+ }
+
+ @Subscribe
+ public void onGameTick(GameTick event) {
+
+ inWildy = client.getVar(Varbits.IN_WILDERNESS);
+
+ if (inWildy == 0) {
+ targets.clear();
+ return;
+ }
+
+ Widget wildyText = client.getWidget(WidgetInfo.PVP_WILDERNESS_LEVEL);
+
+ if (wildyText == null) {
+ return;
+ }
+
+ int wildyLevel = Integer.parseInt(wildyText.getText().split(":")[1].trim());
+
+
+ if (config.getShowAttackablePlayers()) {
+ for (Player player : client.getPlayers()) {
+ if (Math.abs(player.getCombatLevel() - client.getLocalPlayer().getCombatLevel()) <= wildyLevel) { // && !player.equals(client.getLocalPlayer())
+ targets.add(player);
+ } else targets.remove(player);
+ }
+ }
+ }
+
+// @Subscribe
+// public void onGameStateChanged(GameStateChanged event) {
+// if (event.getGameState() == GameState.LOGGED_IN) {
+// currentExperience = client.getSkillExperience(Skill.MAGIC);
+// }
+// }
+//
+// @Subscribe
+// public void onMenuOptionClicked(MenuOptionClicked event) {
+// if (event.getMenuTarget().contains("->")) {
+// target = Text.removeTags(event.getMenuTarget()).split(" -> ")[1];
+// spell = Text.removeTags(event.getMenuTarget()).split(" -> ")[0];
+// log.debug("{} - {}", spell, target);
+// prayMage = false;
+// }
+// }
+//
+// @Subscribe
+// public void onExperienceChanged(ExperienceChanged event) {
+// if (event.getSkill() == Skill.MAGIC) {
+// gainedExperience = client.getSkillExperience(Skill.MAGIC) - currentExperience;
+// currentExperience = client.getSkillExperience(Skill.MAGIC);
+// long frozenTime = calculateFreezeTime(gainedExperience, spell, prayMage);
+// if (frozenTime > 0) {
+// for (Player player : client.getPlayers()) {
+// if (player.getName().equals(target)) {
+// location = player.getWorldLocation();
+// if (player.getOverheadIcon() != null && player.getOverheadIcon().equals(HeadIcon.MAGIC)) {
+// prayMage = true;
+// break;
+// }
+// targets.add(new Markable(player, location, System.currentTimeMillis(), frozenTime));
+// log.debug("Marked Target {}", player.getName());
+// }
+// }
+// }
+// }
+// }
+//
+// public long calculateFreezeTime(int experience, String spell, boolean protectionPrayer) {
+// long freezeTime = 0;
+// switch (spell) {
+// case "Bind":
+// if (experience > 30) {
+// if (protectionPrayer) {
+// freezeTime = 2500;
+// } else {
+// freezeTime = 5000;
+// }
+// }
+// break;
+// case "Snare":
+// if (experience > 60) {
+// if (protectionPrayer) {
+// freezeTime = 5000;
+// } else {
+// freezeTime = 10000;
+// }
+// }
+// break;
+// case "Entangle":
+// if (experience > 89) {
+// if (protectionPrayer) {
+// freezeTime = 7500;
+// } else {
+// freezeTime = 15000;
+// }
+// }
+// break;
+// case "Ice Rush":
+// if (experience > 34) {
+// freezeTime = 5000;
+// }
+// break;
+// case "Ice Burst":
+// if (experience > 40) {
+// freezeTime = 10000;
+// }
+// break;
+// case "Ice Blitz":
+// if (experience > 46) {
+// freezeTime = 15000;
+// }
+// break;
+// case "Ice Barrage":
+// if (experience > 52) {
+// freezeTime = 20000;
+// }
+// break;
+// }
+// return freezeTime;
+// }
+
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/scape/EasyScapeConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/scape/EasyScapeConfig.java
new file mode 100644
index 0000000000..6e1bf5da09
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/scape/EasyScapeConfig.java
@@ -0,0 +1,43 @@
+package net.runelite.client.plugins.easy.scape;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("easyscape")
+public interface EasyScapeConfig extends Config {
+
+ @ConfigItem(
+ keyName = "removeExamine",
+ name = "Remove Examine",
+ description = "Removes Examine from the list of options.",
+ position = 0
+ )
+
+ default boolean getRemoveExamine() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "removeObjects",
+ name = "Remove Objects",
+ description = "Removes interaction with the listed objects.",
+ position = 1
+ )
+
+ default boolean getRemoveObjects() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "removedObjects",
+ name = "Objects",
+ description = "Objects listed here will have all interaction be removed.",
+ position = 2
+ )
+
+ default String getRemovedObjects() {
+ return "";
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/scape/EasyScapePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/scape/EasyScapePlugin.java
new file mode 100644
index 0000000000..9db0b3e0c5
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/scape/EasyScapePlugin.java
@@ -0,0 +1,111 @@
+package net.runelite.client.plugins.easy.scape;
+
+import com.google.inject.Provides;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.GameState;
+import net.runelite.api.MenuEntry;
+import net.runelite.api.events.MenuEntryAdded;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.util.Text;
+import org.apache.commons.lang3.ArrayUtils;
+
+import javax.inject.Inject;
+
+@PluginDescriptor(
+ name = "EasyScape",
+ description = "EasyScape.",
+ tags = {"EasyScape"},
+ enabledByDefault = false
+)
+
+@Slf4j
+public class EasyScapePlugin extends Plugin {
+
+ private MenuEntry[] entries;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private EasyScapeConfig config;
+
+ @Provides
+ EasyScapeConfig provideConfig(ConfigManager configManager) {
+ return configManager.getConfig(EasyScapeConfig.class);
+ }
+
+ @Override
+ public void startUp() {
+ log.debug("EasyScape Started.");
+ }
+
+ @Override
+ public void shutDown() {
+ log.debug("EasyScape Stopped.");
+ }
+
+ @Subscribe
+ public void onMenuEntryAdded(MenuEntryAdded event) {
+
+ if (client.getGameState() != GameState.LOGGED_IN) {
+ return;
+ }
+
+ Widget loginScreenOne = client.getWidget(WidgetInfo.LOGIN_CLICK_TO_PLAY_SCREEN);
+ Widget loginScreenTwo = client.getWidget(WidgetInfo.LOGIN_CLICK_TO_PLAY_SCREEN_MESSAGE_OF_THE_DAY);
+
+ if (loginScreenOne != null || loginScreenTwo != null) {
+ return;
+ }
+
+ final String target = Text.removeTags(event.getTarget()).toLowerCase();
+
+ entries = client.getMenuEntries();
+
+ if (config.getRemoveExamine()) {
+ for (int i = entries.length - 1; i >= 0; i--) {
+ if (entries[i].getOption().equals("Examine")) {
+ entries = ArrayUtils.remove(entries, i);
+ i--;
+ }
+ }
+ client.setMenuEntries(entries);
+ }
+
+ if (config.getRemoveObjects() && !config.getRemovedObjects().equals("")) {
+ for (String removed : config.getRemovedObjects().split(",")) {
+ removed = removed.trim();
+ if (target.contains("(") && target.split(" \\(")[0].equalsIgnoreCase(removed)) {
+ delete(event.getIdentifier());
+ } else if (target.contains("->")) {
+ String trimmed = target.split("->")[1].trim();
+ if (trimmed.length() >= removed.length() && trimmed.substring(0, removed.length()).equalsIgnoreCase(removed)) {
+ delete(event.getIdentifier());
+ break;
+ }
+ } else if (target.length() >= removed.length() && target.substring(0, removed.length()).equalsIgnoreCase(removed)) {
+ delete(event.getIdentifier());
+ break;
+ }
+ }
+ }
+
+ }
+
+ private void delete(int target) {
+ for (int i = entries.length - 1; i >= 0; i--) {
+ if (entries[i].getIdentifier() == target) {
+ entries = ArrayUtils.remove(entries, i);
+ i--;
+ }
+ }
+ client.setMenuEntries(entries);
+ }
+
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/shop/EasyShopConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/shop/EasyShopConfig.java
new file mode 100644
index 0000000000..ec86c4c85c
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/shop/EasyShopConfig.java
@@ -0,0 +1,186 @@
+package net.runelite.client.plugins.easy.shop;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("easyshop")
+public interface EasyShopConfig extends Config {
+
+ @ConfigItem(
+ keyName = "swapBuyOne",
+ name = "Swappable Buy One",
+ description = "",
+ position = 0
+ )
+
+ default boolean getSwapBuyOne() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "buyOneItems",
+ name = "Items",
+ description = "",
+ position = 1
+ )
+
+ default String getBuyOneItems() {
+ return "";
+ }
+
+ @ConfigItem(
+ keyName = "swapBuyFive",
+ name = "Swappable Buy Five",
+ description = "",
+ position = 2
+ )
+
+ default boolean getSwapBuyFive() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "buyFiveItems",
+ name = "Items",
+ description = "",
+ position = 3
+ )
+
+ default String getBuyFiveItems() {
+ return "";
+ }
+
+ @ConfigItem(
+ keyName = "swapBuyTen",
+ name = "Swappable Buy Ten",
+ description = "",
+ position = 4
+ )
+
+ default boolean getSwapBuyTen() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "buyTenItems",
+ name = "Items",
+ description = "",
+ position = 5
+ )
+
+ default String getBuyTenItems() {
+ return "";
+ }
+
+ @ConfigItem(
+ keyName = "swapBuyFifty",
+ name = "Swappable Buy Fifty",
+ description = "",
+ position = 6
+ )
+
+ default boolean getSwapBuyFifty() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "buyFiftyItems",
+ name = "Items",
+ description = "",
+ position = 7
+ )
+
+ default String getBuyFiftyItems() {
+ return "";
+ }
+
+ @ConfigItem(
+ keyName = "swapSellOne",
+ name = "Swappable Sell One",
+ description = "",
+ position = 8
+ )
+
+ default boolean getSwapSellOne() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "sellOneItems",
+ name = "Items",
+ description = "",
+ position = 9
+ )
+
+ default String getSellOneItems() {
+ return "";
+ }
+
+ @ConfigItem(
+ keyName = "swapSellFive",
+ name = "Swappable Sell Five",
+ description = "",
+ position = 10
+ )
+
+ default boolean getSwapSellFive() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "sellFiveItems",
+ name = "Items",
+ description = "",
+ position = 11
+ )
+
+ default String getSellFiveItems() {
+ return "";
+ }
+
+ @ConfigItem(
+ keyName = "swapSellTen",
+ name = "Swappable Sell Ten",
+ description = "",
+ position = 12
+ )
+
+ default boolean getSwapSellTen() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "sellTenItems",
+ name = "Items",
+ description = "",
+ position = 13
+ )
+
+ default String getSellTenItems() {
+ return "";
+ }
+
+ @ConfigItem(
+ keyName = "swapSellFifty",
+ name = "Swappable Sell Fifty",
+ description = "",
+ position = 14
+ )
+
+ default boolean getSwapSellFifty() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "sellFiftyItems",
+ name = "Items",
+ description = "",
+ position = 15
+ )
+
+ default String getSellFiftyItems() {
+ return "";
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/shop/EasyShopPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/shop/EasyShopPlugin.java
new file mode 100644
index 0000000000..e13a97ff5d
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/shop/EasyShopPlugin.java
@@ -0,0 +1,140 @@
+package net.runelite.client.plugins.easy.shop;
+
+
+import com.google.inject.Provides;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.GameState;
+import net.runelite.api.events.MenuEntryAdded;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.plugins.easy.util.Swapper;
+import net.runelite.client.util.Text;
+
+import javax.inject.Inject;
+
+@PluginDescriptor(
+ name = "EasyShop",
+ description = "EasyShop.",
+ tags = {"EasyShop", "easy"}
+)
+
+@Slf4j
+public class EasyShopPlugin extends Plugin {
+
+ private Swapper swapper = new Swapper();
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private EasyShopConfig config;
+
+ @Provides
+ EasyShopConfig provideConfig(ConfigManager configManager) {
+ return configManager.getConfig(EasyShopConfig.class);
+ }
+
+ @Override
+ public void startUp() {
+ log.debug("EasyShop Started.");
+ }
+
+ @Override
+ public void shutDown() {
+ log.debug("EasyShop Stopped.");
+ }
+
+ @Subscribe
+ public void onMenuEntryAdded(MenuEntryAdded event) {
+
+ if (client.getGameState() != GameState.LOGGED_IN) {
+ return;
+ }
+
+ Widget loginScreenOne = client.getWidget(WidgetInfo.LOGIN_CLICK_TO_PLAY_SCREEN);
+ Widget loginScreenTwo = client.getWidget(WidgetInfo.LOGIN_CLICK_TO_PLAY_SCREEN_MESSAGE_OF_THE_DAY);
+
+ if (loginScreenOne != null || loginScreenTwo != null) {
+ return;
+ }
+
+ final String option = Text.removeTags(event.getOption()).toLowerCase();
+ final String target = Text.removeTags(event.getTarget()).toLowerCase();
+
+ swapper.setEntries(client.getMenuEntries());
+
+ if (config.getSwapBuyOne() && !config.getBuyOneItems().equals("")) {
+ for (String item : config.getBuyOneItems().split(",")) {
+ if (target.equalsIgnoreCase(item.trim())) {
+ swapper.markForSwap("Buy 1", option, target);
+ }
+ }
+ }
+
+ if (config.getSwapBuyFive() && !config.getBuyFiveItems().equals("")) {
+ for (String item : config.getBuyFiveItems().split(",")) {
+ if (target.equalsIgnoreCase(item.trim())) {
+ swapper.markForSwap("Buy 5", option, target);
+ }
+ }
+ }
+
+ if (config.getSwapBuyTen() && !config.getBuyTenItems().equals("")) {
+ for (String item : config.getBuyTenItems().split(",")) {
+ if (target.equalsIgnoreCase(item.trim())) {
+ swapper.markForSwap("Buy 10", option, target);
+ }
+ }
+ }
+
+ if (config.getSwapBuyFifty() && !config.getBuyFiftyItems().equals("")) {
+ for (String item : config.getBuyFiftyItems().split(",")) {
+ if (target.equalsIgnoreCase(item.trim())) {
+ swapper.markForSwap("Buy 50", option, target);
+ }
+ }
+ }
+
+ if (config.getSwapSellOne() && !config.getSellOneItems().equals("")) {
+ for (String item : config.getSellOneItems().split(",")) {
+ if (target.equalsIgnoreCase(item.trim())) {
+ swapper.markForSwap("Sell 1", option, target);
+ }
+ }
+ }
+
+ if (config.getSwapSellFive() && !config.getSellFiveItems().equals("")) {
+ for (String item : config.getSellFiveItems().split(",")) {
+ if (target.equalsIgnoreCase(item.trim())) {
+ swapper.markForSwap("Sell 5", option, target);
+ }
+ }
+ }
+
+ if (config.getSwapSellTen() && !config.getSellTenItems().equals("")) {
+ for (String item : config.getSellTenItems().split(",")) {
+ if (target.equalsIgnoreCase(item.trim())) {
+ swapper.markForSwap("Sell 10", option, target);
+ }
+ }
+ }
+
+ if (config.getSwapSellFifty() && !config.getSellFiftyItems().equals("")) {
+ for (String item : config.getSellFiftyItems().split(",")) {
+ if (target.equalsIgnoreCase(item.trim())) {
+ swapper.markForSwap("Sell 50", option, target);
+ }
+ }
+ }
+
+ swapper.startSwap();
+ client.setMenuEntries(swapper.getEntries());
+ }
+
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/swap/EasySwapConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/swap/EasySwapConfig.java
new file mode 100644
index 0000000000..3e8cc6a41a
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/swap/EasySwapConfig.java
@@ -0,0 +1,201 @@
+package net.runelite.client.plugins.easy.swap;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+import net.runelite.client.plugins.easy.util.DuelingRingMode;
+import net.runelite.client.plugins.easy.util.EssenceMode;
+import net.runelite.client.plugins.easy.util.GamesNecklaceMode;
+import net.runelite.client.plugins.easy.util.GloryMode;
+
+@ConfigGroup("easyswap")
+public interface EasySwapConfig extends Config {
+
+ @ConfigItem(
+ keyName = "easyConstruction",
+ name = "Easy Construction",
+ description = "Makes \"Remove\" the default option for listed items in build mode.",
+ position = 0
+ )
+
+ default boolean getEasyConstruction() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "constructionItems",
+ name = "Construction Items",
+ description = "Items listed here will have the default option set to \"Removed\" in build mode.",
+ position = 1
+ )
+
+ default String getConstructionItems() {
+ return "";
+ }
+
+
+ @ConfigItem(
+ keyName = "swapSmithing",
+ name = "Swap Smithing",
+ description = "Enables swapping of smith-1 and smith-all options.",
+ position = 2
+ )
+
+ default boolean getSwapSmithing() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapTanning",
+ name = "Swap Tanning",
+ description = "Enables swapping of tan-1 and tan-all options.",
+ position = 3
+ )
+
+ default boolean getSwapTanning() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapCrafting",
+ name = "Swap Crafting",
+ description = "Enables swapping of Make-1 and Make-all options.",
+ position = 4
+ )
+
+ default boolean getSwapCrafting() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapArdougneCape",
+ name = "Swap Ardougne Cape",
+ description = "Enables swapping of teleport and wear.",
+ position = 5
+ )
+
+ default boolean getSwapArdougneCape() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapSawmill",
+ name = "Swap Sawmill Operator",
+ description = "Makes Buy-plank the default option on the sawmill operator.",
+ position = 6
+ )
+
+ default boolean getSwapSawmill() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapSawmillPlanks",
+ name = "Swap Buy Planks",
+ description = "Makes Buy All the default option in buy planks.",
+ position = 7
+ )
+
+ default boolean getSwapSawmillPlanks() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapPuroPuro",
+ name = "Swap Puro Puro Wheat",
+ description = "",
+ position = 8
+ )
+
+ default boolean getSwapPuro() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapEssencePounch",
+ name = "Swap Essence Pouch",
+ description = "",
+ position = 9
+ )
+
+ default boolean getSwapEssencePouch() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "essenceMode",
+ name = "Mode",
+ description = "",
+ position = 10
+ )
+
+ default EssenceMode getEssenceMode() {
+ return EssenceMode.RUNECRAFTING;
+ }
+
+ @ConfigItem(
+ keyName = "swapGamesNecklace",
+ name = "Swap Games Necklace",
+ description = "",
+ position = 11
+ )
+ default boolean getGamesNecklace() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "gamesNecklaceMode",
+ name = "Mode",
+ description = "",
+ position = 12
+ )
+
+ default GamesNecklaceMode getGamesNecklaceMode() {
+ return GamesNecklaceMode.BURTHORPE;
+ }
+
+ @ConfigItem(
+ keyName = "swapDuelingRing",
+ name = "Swap Dueling Ring",
+ description = "",
+ position = 13
+ )
+
+ default boolean getDuelingRing() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "duelingRingMode",
+ name = "Mode",
+ description = "",
+ position = 14
+ )
+
+ default DuelingRingMode getDuelingRingMode() {
+ return DuelingRingMode.DUEL_ARENA;
+ }
+
+ @ConfigItem(
+ keyName = "swapGlory",
+ name = "Swap Glory",
+ description = "",
+ position = 15
+ )
+
+ default boolean getGlory() {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "gloryMode",
+ name = "Mode",
+ description = "",
+ position = 16
+ )
+
+ default GloryMode getGloryMode() {
+ return GloryMode.EDGEVILLE;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/swap/EasySwapPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/swap/EasySwapPlugin.java
new file mode 100644
index 0000000000..ae89f2b3d8
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/swap/EasySwapPlugin.java
@@ -0,0 +1,236 @@
+package net.runelite.client.plugins.easy.swap;
+
+import com.google.inject.Provides;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.GameObject;
+import net.runelite.api.GameState;
+import net.runelite.api.Player;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.api.events.MenuEntryAdded;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.plugins.easy.util.Swapper;
+import net.runelite.client.util.Text;
+import net.runelite.api.events.GameObjectSpawned;
+import net.runelite.api.events.GameStateChanged;
+
+import javax.inject.Inject;
+
+import static net.runelite.api.MenuAction.WALK;
+import static net.runelite.api.ObjectID.PORTAL_4525;
+
+@PluginDescriptor(
+ name = "EasySwap",
+ description = "EasySwap.",
+ tags = {"EasySwap", "easy"}
+)
+
+@Slf4j
+public class EasySwapPlugin extends Plugin {
+
+ private static final int PURO_PURO_REGION_ID = 10307;
+
+ private Swapper swapper = new Swapper();
+ private boolean inHouse = false;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private EasySwapConfig config;
+
+ @Provides
+ EasySwapConfig provideConfig(ConfigManager configManager) {
+ return configManager.getConfig(EasySwapConfig.class);
+ }
+
+ @Override
+ public void startUp() {
+ log.debug("EasySwap Started.");
+ }
+
+ @Override
+ public void shutDown() {
+ log.debug("EasySwap Stopped.");
+ }
+
+ @Subscribe
+ public void onMenuEntryAdded(MenuEntryAdded event) {
+
+ if (client.getGameState() != GameState.LOGGED_IN) {
+ return;
+ }
+
+ Widget loginScreenOne = client.getWidget(WidgetInfo.LOGIN_CLICK_TO_PLAY_SCREEN);
+ Widget loginScreenTwo = client.getWidget(WidgetInfo.LOGIN_CLICK_TO_PLAY_SCREEN_MESSAGE_OF_THE_DAY);
+
+ if (loginScreenOne != null || loginScreenTwo != null) {
+ return;
+ }
+
+ final String option = Text.removeTags(event.getOption()).toLowerCase();
+ final String target = Text.removeTags(event.getTarget()).toLowerCase();
+
+ swapper.setEntries(client.getMenuEntries());
+
+ if (config.getSwapPuro() && isPuroPuro()) {
+ if (event.getType() == WALK.getId()) {
+ swapper.deprioritizeWalk();
+ } else if (option.equalsIgnoreCase("examine")) {
+ swapper.markForSwap("push-through", option, target);
+ } else if (option.equalsIgnoreCase("use")) {
+ swapper.markForSwap("escape", option, target);
+ }
+ }
+
+ if (config.getEasyConstruction() && !config.getConstructionItems().equals("") && inHouse) {
+ if (event.getType() == WALK.getId()) {
+ swapper.deprioritizeWalk();
+ }
+
+ swapper.markForSwap("Build", option, target);
+
+ for (int i = swapper.getEntries().length - 1; i >= 0; i--) {
+ for (String item : config.getConstructionItems().split(",")) {
+ if (item.trim().equalsIgnoreCase(Text.removeTags(swapper.getEntries()[i].getTarget()))) {
+ if (!swapper.getEntries()[i].getOption().equalsIgnoreCase("remove")) {
+ swapper.removeIndex(i);
+ i--;
+ }
+ }
+ }
+ }
+ }
+
+ if (config.getSwapSmithing()) {
+ if (option.equalsIgnoreCase("Smith 1")) {
+ swapper.markForSwap("Smith All", option, target);
+ } else if (option.equalsIgnoreCase("Smith 1 Set")) {
+ swapper.markForSwap("Smith All Sets", option, target);
+ }
+ }
+
+ if (config.getSwapTanning() && option.equalsIgnoreCase("Tan 1")) {
+ swapper.markForSwap("Tan All", option, target);
+ }
+
+ if (config.getSwapCrafting()) {
+ switch (option) {
+ case "Make-1":
+ swapper.markForSwap("Make-All", option, target);
+ break;
+ case "Craft 1":
+ swapper.markForSwap("Craft All", option, target);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (config.getSwapSawmill() && target.equalsIgnoreCase("Sawmill operator")) {
+ swapper.markForSwap("Buy-plank", option, target);
+ }
+
+ if (config.getSwapSawmillPlanks() && option.equalsIgnoreCase("Buy 1")) {
+ swapper.markForSwap("Buy All", option, target);
+ }
+
+ if (option.equalsIgnoreCase("Clear-All") && target.equalsIgnoreCase("bank Filler")) {
+ swapper.markForSwap("Clear", option, target);
+ }
+
+ if (target.toLowerCase().contains("ardougne cloak") && config.getSwapArdougneCape()) {
+ swapper.markForSwap("Kandarin Monastery", option, target);
+ swapper.markForSwap("Monastery Teleport", option, target);
+ }
+
+ if (config.getSwapEssencePouch()) {
+ if (isEssencePouch(target)) {
+ Widget widgetBankTitleBar = client.getWidget(WidgetInfo.BANK_TITLE_BAR);
+ switch (config.getEssenceMode()) {
+ case RUNECRAFTING:
+ if (widgetBankTitleBar == null || widgetBankTitleBar.isHidden()) {
+ swapper.markForSwap("Empty", option, target);
+ } else {
+ swapper.markForSwap("Fill", option, target);
+ }
+ break;
+ case ESSENCE_MINING:
+ if (widgetBankTitleBar == null || widgetBankTitleBar.isHidden()) {
+ swapper.markForSwap("Fill", option, target);
+ } else {
+ swapper.markForSwap("Empty", option, target);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (config.getGamesNecklace()) {
+ if (target.toLowerCase().contains("games necklace")) {
+ swapper.markForSwap(config.getGamesNecklaceMode().toString(), option, target);
+ }
+ }
+
+ if (config.getDuelingRing()) {
+ if (target.toLowerCase().contains("ring of dueling")) {
+ swapper.markForSwap(config.getDuelingRingMode().toString(), option, target);
+ }
+ }
+
+ if (config.getGlory()) {
+ if (target.toLowerCase().contains("amulet of glory")) {
+ swapper.markForSwap(config.getGloryMode().toString(), option, target);
+ }
+ }
+
+ swapper.startSwap();
+ client.setMenuEntries(swapper.getEntries());
+ }
+
+ private boolean isEssencePouch(String target) {
+ return (target.equalsIgnoreCase("Small Pouch") || target.equalsIgnoreCase("Medium Pouch") || target.equalsIgnoreCase("Large Pouch") || target.equalsIgnoreCase("Giant Pouch"));
+ }
+
+ @Subscribe
+ public void onGameObjectSpawned(GameObjectSpawned event)
+ {
+ final GameObject gameObject = event.getGameObject();
+ if (PORTAL_4525 == gameObject.getId())
+ {
+ this.inHouse = true;
+ }
+ }
+
+ @Subscribe
+ public void onGameStateChanged(GameStateChanged event)
+ {
+ if (event.getGameState() == GameState.LOADING)
+ {
+ this.inHouse = false;
+ }
+ }
+
+ private boolean isHouse() {
+ return this.inHouse;
+ }
+
+ private boolean isPuroPuro() {
+ Player player = client.getLocalPlayer();
+
+ if (player == null) {
+ return false;
+ } else {
+ WorldPoint location = player.getWorldLocation();
+ return location.getRegionID() == PURO_PURO_REGION_ID;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/DuelingRingMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/DuelingRingMode.java
new file mode 100644
index 0000000000..9aa0c47ad3
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/DuelingRingMode.java
@@ -0,0 +1,18 @@
+package net.runelite.client.plugins.easy.util;
+
+public enum DuelingRingMode {
+ DUEL_ARENA("Duel Arena"),
+ CASTLE_WARS("Castle Wars"),
+ CLAN_WARS("Clan Wars");
+
+ private final String name;
+
+ DuelingRingMode(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/EssenceMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/EssenceMode.java
new file mode 100644
index 0000000000..bdf5f75bd0
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/EssenceMode.java
@@ -0,0 +1,17 @@
+package net.runelite.client.plugins.easy.util;
+
+public enum EssenceMode {
+ RUNECRAFTING("Runecrafting"),
+ ESSENCE_MINING("Essence Mining");
+
+ private final String name;
+
+ EssenceMode(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/GamesNecklaceMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/GamesNecklaceMode.java
new file mode 100644
index 0000000000..e176207f63
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/GamesNecklaceMode.java
@@ -0,0 +1,20 @@
+package net.runelite.client.plugins.easy.util;
+
+public enum GamesNecklaceMode {
+ BURTHORPE("Burthorpe"),
+ BARBARIAN_OUTPOST("Barbarian Outpost"),
+ CORPOREAL_BEAST("Corporeal Beast"),
+ TEARS_OF_GUTHIX("Tears of Guthix"),
+ WINTERTODT("Wintertodt Camp");
+
+ private final String name;
+
+ GamesNecklaceMode(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/GloryMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/GloryMode.java
new file mode 100644
index 0000000000..8f5a93c56f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/GloryMode.java
@@ -0,0 +1,19 @@
+package net.runelite.client.plugins.easy.util;
+
+public enum GloryMode {
+ EDGEVILLE("Edgeville"),
+ KARAMJA("Karamja"),
+ DRAYNOR_VILLAGE("Draynor Village"),
+ AL_KHARID("Al Kharid");
+
+ private final String name;
+
+ GloryMode(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/Markable.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/Markable.java
new file mode 100644
index 0000000000..696d57f542
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/Markable.java
@@ -0,0 +1,28 @@
+package net.runelite.client.plugins.easy.util;
+
+import lombok.Getter;
+import net.runelite.api.Player;
+import net.runelite.api.coords.WorldPoint;
+
+public class Markable {
+
+ @Getter
+ private Player player;
+
+ @Getter
+ private WorldPoint location;
+
+ @Getter
+ private long frozenTime;
+
+ @Getter
+ private long frozenUntil;
+
+ public Markable(Player player, WorldPoint location, long frozenTime, long frozenLength) {
+ this.player = player;
+ this.location = location;
+ this.frozenTime = frozenTime;
+ this.frozenUntil = frozenTime + frozenLength + 3000;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/Swappable.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/Swappable.java
new file mode 100644
index 0000000000..3fc3667a86
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/Swappable.java
@@ -0,0 +1,38 @@
+package net.runelite.client.plugins.easy.util;
+
+import lombok.Getter;
+import lombok.Setter;
+import net.runelite.client.util.Text;
+
+public class Swappable {
+
+ @Getter
+ private String target;
+
+ @Getter
+ private String optionOne;
+
+ @Getter
+ private String optionTwo;
+
+ @Getter
+ @Setter
+ private int indexOne;
+
+ @Getter
+ @Setter
+ private int indexTwo;
+
+ public Swappable(String target, String optionOne, String optionTwo) {
+ this.target = Text.removeTags(target.toLowerCase());
+ this.optionOne = Text.removeTags(optionOne);
+ this.optionTwo = Text.removeTags(optionTwo);
+ this.indexOne = -1;
+ this.indexTwo = -1;
+ }
+
+ public boolean isReady() {
+ return this.indexOne != -1 && this.indexTwo != -1;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/Swapper.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/Swapper.java
new file mode 100644
index 0000000000..e0c05e8396
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/util/Swapper.java
@@ -0,0 +1,67 @@
+package net.runelite.client.plugins.easy.util;
+
+import lombok.Getter;
+import lombok.Setter;
+import net.runelite.api.MenuAction;
+import net.runelite.api.MenuEntry;
+import net.runelite.client.util.Text;
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static net.runelite.api.MenuAction.MENU_ACTION_DEPRIORITIZE_OFFSET;
+
+public class Swapper {
+
+ private Set swapping;
+
+ @Getter
+ @Setter
+ private MenuEntry[] entries;
+
+ public Swapper() {
+ this.swapping = new HashSet<>();
+ }
+
+ public void deprioritizeWalk() {
+ MenuEntry menuEntry = entries[entries.length - 1];
+ menuEntry.setType(MenuAction.WALK.getId() + MENU_ACTION_DEPRIORITIZE_OFFSET);
+ }
+
+ public void removeIndex(int index) {
+ entries = ArrayUtils.remove(entries, index);
+ }
+
+ public void markForSwap(String optionA, String optionB, String target) {
+ swapping.add(new Swappable(target, optionA, optionB));
+ }
+
+ public void startSwap() {
+ int index = 0;
+ for (MenuEntry entry : entries) {
+ String target = Text.removeTags(entry.getTarget()).toLowerCase();
+ String option = Text.removeTags(entry.getOption()).toLowerCase();
+ for (Swappable swap : swapping) {
+ if (swap.getTarget().equalsIgnoreCase(target)) {
+ if (option.equalsIgnoreCase(swap.getOptionOne())) {
+ swap.setIndexOne(index);
+ } else if (option.equalsIgnoreCase(swap.getOptionTwo())) {
+ swap.setIndexTwo(index);
+ }
+ }
+ }
+ index++;
+ }
+
+ for (Swappable swap : swapping) {
+ if (swap.isReady()) {
+ MenuEntry entry = entries[swap.getIndexOne()];
+ entries[swap.getIndexOne()] = entries[swap.getIndexTwo()];
+ entries[swap.getIndexTwo()] = entry;
+ }
+ }
+ swapping.clear();
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/zulrah/EasyZulrahConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/zulrah/EasyZulrahConfig.java
new file mode 100644
index 0000000000..491c02aaee
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/zulrah/EasyZulrahConfig.java
@@ -0,0 +1,4 @@
+package net.runelite.client.plugins.easy.zulrah;
+
+public class EasyZulrahConfig {
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/zulrah/EasyZulrahPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/zulrah/EasyZulrahPlugin.java
new file mode 100644
index 0000000000..4cd1d455b0
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/easyscape/zulrah/EasyZulrahPlugin.java
@@ -0,0 +1,4 @@
+package net.runelite.client.plugins.easy.zulrah;
+
+public class EasyZulrahPlugin {
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/EquipmentInspectorConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/EquipmentInspectorConfig.java
new file mode 100644
index 0000000000..98392d4539
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/EquipmentInspectorConfig.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2017, Aria
+ * 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.equipmentinspector;
+
+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.plugins.grounditems.config.ItemHighlightMode;
+import net.runelite.client.plugins.grounditems.config.MenuHighlightMode;
+import net.runelite.client.plugins.grounditems.config.PriceDisplayMode;
+
+@ConfigGroup("grounditems")
+public interface EquipmentInspectorConfig extends Config
+{
+ @ConfigItem(
+ keyName = "ShowValue",
+ name = "Show the total value of the items",
+ description = "shows the total value of the items",
+ position = 1
+ )
+ default boolean ShowValue()
+ {
+ return true;
+ }
+ @ConfigItem(
+ keyName = "protecteditems",
+ name = "# of protected items",
+ description = "Limit 4",
+ position = 2
+ )
+ default int protecteditems()
+ { return 1; }
+ @ConfigItem(
+ keyName = "ExactValue",
+ name = "Show exact value",
+ description = "shows the excact gp value",
+ position = 3
+ )
+ default boolean ExactValue()
+ { return false; }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/EquipmentInspectorPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/EquipmentInspectorPanel.java
new file mode 100644
index 0000000000..e630c28b4f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/EquipmentInspectorPanel.java
@@ -0,0 +1,98 @@
+package net.runelite.client.plugins.equipmentinspector;
+
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.ItemComposition;
+import net.runelite.api.kit.KitType;
+import net.runelite.client.game.AsyncBufferedImage;
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.ui.ColorScheme;
+import net.runelite.client.ui.PluginPanel;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.swing.*;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Singleton
+public class EquipmentInspectorPanel extends PluginPanel
+{
+ private final static String NO_PLAYER_SELECTED = "No player selected";
+
+ private GridBagConstraints c;
+ private JPanel equipmentPanels;
+ private JPanel header;
+ private JLabel nameLabel;
+
+ @Inject
+ private ItemManager itemManager;
+
+ public EquipmentInspectorPanel()
+ {
+ GroupLayout layout = new GroupLayout(this);
+ setLayout(layout);
+ setBorder(new EmptyBorder(10, 10, 10, 10));
+ setBackground(ColorScheme.DARK_GRAY_COLOR);
+
+ equipmentPanels = new JPanel(new GridBagLayout());
+ c = new GridBagConstraints();
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.gridx = 0;
+ c.gridy = 0;
+
+ header = new JPanel();
+ header.setLayout(new BorderLayout());
+ header.setBorder(new CompoundBorder(
+ BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(58, 58, 58)),
+ BorderFactory.createEmptyBorder(0, 0, 10, 0)));
+
+ nameLabel = new JLabel(NO_PLAYER_SELECTED);
+ nameLabel.setForeground(Color.WHITE);
+
+ header.add(nameLabel, BorderLayout.CENTER);
+
+ layout.setHorizontalGroup(layout.createParallelGroup()
+ .addComponent(equipmentPanels)
+ .addComponent(header)
+ );
+ layout.setVerticalGroup(layout.createSequentialGroup()
+ .addComponent(header)
+ .addGap(10)
+ .addComponent(equipmentPanels)
+ );
+
+ update(new HashMap<>(), "");
+ }
+
+ public void update(Map playerEquipment, String playerName)
+ {
+ if (playerName.isEmpty() || playerName == null)
+ {
+ nameLabel.setText(NO_PLAYER_SELECTED);
+ }
+ else
+ {
+ nameLabel.setText("Player: " + playerName);
+ }
+
+ SwingUtilities.invokeLater(() ->
+ {
+ equipmentPanels.removeAll();
+ playerEquipment.forEach((kitType, itemComposition) ->
+ {
+ AsyncBufferedImage itemImage = itemManager.getImage(itemComposition.getId());
+ equipmentPanels.add(new ItemPanel(itemComposition, kitType, itemImage), c);
+ c.gridy++;
+
+ });
+ header.revalidate();
+ header.repaint();
+ }
+ );
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/EquipmentInspectorPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/EquipmentInspectorPlugin.java
new file mode 100644
index 0000000000..8af1567609
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/EquipmentInspectorPlugin.java
@@ -0,0 +1,240 @@
+package net.runelite.client.plugins.equipmentinspector;
+
+
+import com.google.inject.Provides;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.*;
+import net.runelite.api.events.PlayerMenuOptionClicked;
+import net.runelite.api.kit.KitType;
+import net.runelite.client.chat.ChatColorType;
+import net.runelite.client.chat.ChatMessageBuilder;
+import net.runelite.client.chat.ChatMessageManager;
+import net.runelite.client.chat.QueuedMessage;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.menus.MenuManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.ClientToolbar;
+import net.runelite.client.ui.NavigationButton;
+import net.runelite.client.util.Text;
+import net.runelite.http.api.item.ItemPrice;
+
+import javax.annotation.Nullable;
+import javax.imageio.ImageIO;
+import javax.inject.Inject;
+import javax.swing.*;
+import java.awt.image.BufferedImage;
+import java.lang.reflect.InvocationTargetException;
+import java.text.NumberFormat;
+import java.util.*;
+import java.util.concurrent.ScheduledExecutorService;
+
+@PluginDescriptor(
+ name = "!Equipment Inspector",
+ enabledByDefault = false
+)
+
+@Slf4j
+
+public class EquipmentInspectorPlugin extends Plugin {
+
+ private static final String INSPECT_EQUIPMENT = "Gear";
+ private static final String KICK_OPTION = "Kick";
+
+ @Inject
+ @Nullable
+ private Client client;
+
+ @Inject
+ private ItemManager itemManager;
+
+ @Inject
+ private EquipmentInspectorConfig config;
+
+ @Inject
+ private ChatMessageManager chatMessageManager;
+ @Inject
+ private MenuManager menuManager;
+
+ @Inject
+ private ScheduledExecutorService executor;
+
+ @Inject
+ private ClientToolbar pluginToolbar;
+
+ @Provides
+ EquipmentInspectorConfig provideConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(EquipmentInspectorConfig.class);
+ }
+
+ private NavigationButton navButton;
+ private EquipmentInspectorPanel equipmentInspectorPanel;
+ private int TotalPrice = 0;
+ private int Prot1 = 0;
+ private int Prot2 = 0;
+ private int Prot3 = 0;
+ private int Prot4 = 0;
+
+
+ @Override
+ protected void startUp() throws Exception
+ {
+
+ equipmentInspectorPanel = injector.getInstance(EquipmentInspectorPanel.class);
+ if(client != null) {
+ menuManager.addPlayerMenuItem(INSPECT_EQUIPMENT);
+ }
+
+ BufferedImage icon;
+ synchronized (ImageIO.class)
+ {
+ icon = ImageIO.read(getClass().getResourceAsStream("normal.png"));
+ }
+
+ navButton = NavigationButton.builder()
+ .tooltip("Equipment Inspector")
+ .icon(icon)
+ .priority(5)
+ .panel(equipmentInspectorPanel)
+ .build();
+
+
+ pluginToolbar.addNavigation(navButton);
+
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+
+ menuManager.removePlayerMenuItem(INSPECT_EQUIPMENT);
+ }
+
+ @Subscribe
+ public void onPlayerMenuOptionClicked(PlayerMenuOptionClicked event)
+ {
+ if (event.getMenuOption().equals(INSPECT_EQUIPMENT))
+ {
+
+
+ executor.execute(() ->
+ {
+ try
+ {
+ SwingUtilities.invokeAndWait(() ->
+ {
+ if (!navButton.isSelected())
+ {
+ navButton.getOnSelect().run();
+ }
+ });
+ }
+ catch (InterruptedException | InvocationTargetException e)
+ {
+
+ throw new RuntimeException(e);
+
+ }
+ String playerName = Text.removeTags(event.getMenuTarget());
+ // The player menu uses a non-breaking space in the player name, we need to replace this to compare
+ // against the playerName in the player cache.
+ String finalPlayerName = playerName.replace('\u00A0', ' ');
+ System.out.println(finalPlayerName);
+ List players = client.getPlayers();
+ Optional targetPlayer = players.stream()
+ .filter(Objects::nonNull)
+ .filter(p -> p.getName().equals(finalPlayerName)).findFirst();
+
+ if (targetPlayer.isPresent())
+ {
+ TotalPrice = 0;
+ Prot1 = 0;
+ Prot2 = 0;
+ Prot3 = 0;
+ Prot4 = 0;
+ Player p = targetPlayer.get();
+ Map playerEquipment = new HashMap<>();
+
+ for (KitType kitType : KitType.values())
+ {
+ int itemId = p.getPlayerComposition().getEquipmentId(kitType);
+ if (itemId != -1)
+ {
+ ItemComposition itemComposition = client.getItemDefinition(itemId);
+ playerEquipment.put(kitType, itemComposition);
+ int ItemPrice = itemManager.getItemPrice(itemId);
+ TotalPrice += ItemPrice;
+ if (ItemPrice > Prot1 ) {
+ Prot4 = Prot3;
+ Prot3 = Prot2;
+ Prot2 = Prot1;
+
+ Prot1 = ItemPrice;
+ } else if (ItemPrice > Prot2){
+ Prot4 = Prot3;
+ Prot3 = Prot2;
+ Prot2 = ItemPrice;
+ } else if (ItemPrice > Prot3){
+ Prot4 = Prot3;
+ Prot3 = ItemPrice;
+ } else if (ItemPrice > Prot4){
+ Prot4 = ItemPrice;
+ }
+ }
+ }
+ int IgnoredItems = config.protecteditems();
+ if (IgnoredItems != 0 && IgnoredItems != 1 && IgnoredItems != 2 && IgnoredItems != 3) {
+ IgnoredItems = 4;
+
+ }
+ if (config.ShowValue()) {
+ switch (IgnoredItems) {
+ case 1:
+ TotalPrice = TotalPrice - Prot1;
+ break;
+ case 2:
+ TotalPrice = TotalPrice - Prot1;
+ TotalPrice = TotalPrice - Prot2;
+
+ break;
+ case 3:
+ TotalPrice = TotalPrice - Prot1;
+ TotalPrice = TotalPrice - Prot2;
+ TotalPrice = TotalPrice - Prot3;
+ break;
+ case 4:
+ TotalPrice = TotalPrice - Prot1;
+ TotalPrice = TotalPrice - Prot2;
+ TotalPrice = TotalPrice - Prot3;
+ TotalPrice = TotalPrice - Prot4;
+ break;
+ }
+ String StringPrice = "";
+ if (!config.ExactValue()) {
+ TotalPrice = TotalPrice / 1000;
+ StringPrice = NumberFormat.getIntegerInstance().format(TotalPrice);
+ StringPrice = StringPrice + 'K';
+ }
+ if (config.ExactValue()) {
+ StringPrice = NumberFormat.getIntegerInstance().format(TotalPrice);
+ }
+ chatMessageManager.queue(QueuedMessage.builder()
+ .type(ChatMessageType.FRIENDSCHATNOTIFICATION)
+ .runeLiteFormattedMessage(new ChatMessageBuilder()
+ .append(ChatColorType.HIGHLIGHT)
+ .append("Risked Value: ")
+ .append(ChatColorType.NORMAL)
+ .append(StringPrice)
+ .build())
+ .build());
+ }
+ equipmentInspectorPanel.update(playerEquipment, playerName);
+
+ }
+ });
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/ItemPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/ItemPanel.java
new file mode 100644
index 0000000000..873bf058b6
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/ItemPanel.java
@@ -0,0 +1,52 @@
+package net.runelite.client.plugins.equipmentinspector;
+
+import net.runelite.api.ItemComposition;
+import net.runelite.api.kit.KitType;
+import net.runelite.client.game.AsyncBufferedImage;
+import net.runelite.client.ui.ColorScheme;
+import net.runelite.client.ui.FontManager;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+
+class ItemPanel extends JPanel
+{
+
+ ItemPanel(ItemComposition item, KitType kitType, AsyncBufferedImage icon)
+ {
+ setBorder(new EmptyBorder(3, 3, 3, 3));
+ setBackground(ColorScheme.DARK_GRAY_COLOR);
+
+ GroupLayout layout = new GroupLayout(this);
+ this.setLayout(layout);
+
+ JLabel name = new JLabel(item.getName());
+
+ JLabel location = new JLabel(StringUtils.capitalize(kitType.toString().toLowerCase()));
+ location.setFont(FontManager.getRunescapeSmallFont());
+
+ JLabel imageLabel = new JLabel();
+ icon.addTo(imageLabel);
+
+ layout.setVerticalGroup(layout.createParallelGroup()
+ .addComponent(imageLabel)
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(name)
+ .addComponent(location)
+ )
+ );
+
+ layout.setHorizontalGroup(layout.createSequentialGroup()
+ .addComponent(imageLabel)
+ .addGap(8)
+ .addGroup(layout.createParallelGroup()
+ .addComponent(name)
+ .addComponent(location)
+ )
+ );
+
+ // AWT's Z order is weird. This put image at the back of the stack
+ setComponentZOrder(imageLabel, getComponentCount() - 1);
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/normal.png b/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/normal.png
new file mode 100644
index 0000000000..613f95e46d
Binary files /dev/null and b/runelite-client/src/main/java/net/runelite/client/plugins/equipmentinspector/normal.png differ
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fightcavejadhelper/FightCaveJadHelperOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavejadhelper/FightCaveJadHelperOverlay.java
new file mode 100644
index 0000000000..3698b395c1
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavejadhelper/FightCaveJadHelperOverlay.java
@@ -0,0 +1,63 @@
+package net.runelite.client.plugins.fightcavejadhelper;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import javax.inject.Inject;
+import net.runelite.api.Client;
+import net.runelite.api.SpriteID;
+import net.runelite.client.game.SpriteManager;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.OverlayPriority;
+import net.runelite.client.ui.overlay.components.ComponentConstants;
+import net.runelite.client.ui.overlay.components.ImageComponent;
+import net.runelite.client.ui.overlay.components.PanelComponent;
+
+public class FightCaveJadHelperOverlay extends Overlay
+{
+ private static final Color NOT_ACTIVATED_BACKGROUND_COLOR = new Color(150, 0, 0, 150);
+
+ private final Client client;
+ private final FightCaveJadHelperPlugin plugin;
+ private final SpriteManager spriteManager;
+ private final PanelComponent imagePanelComponent = new PanelComponent();
+
+ @Inject
+ private FightCaveJadHelperOverlay(Client client, FightCaveJadHelperPlugin plugin, SpriteManager spriteManager)
+ {
+ setPosition(OverlayPosition.BOTTOM_RIGHT);
+ setPriority(OverlayPriority.HIGH);
+ this.client = client;
+ this.plugin = plugin;
+ this.spriteManager = spriteManager;
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ final JadAttack attack = plugin.getAttack();
+
+ if (attack == null)
+ {
+ return null;
+ }
+
+ final BufferedImage prayerImage = getPrayerImage(attack);
+
+ imagePanelComponent.getChildren().clear();
+ imagePanelComponent.getChildren().add(new ImageComponent(prayerImage));
+ imagePanelComponent.setBackgroundColor(client.isPrayerActive(attack.getPrayer())
+ ? ComponentConstants.STANDARD_BACKGROUND_COLOR
+ : NOT_ACTIVATED_BACKGROUND_COLOR);
+
+ return imagePanelComponent.render(graphics);
+ }
+
+ private BufferedImage getPrayerImage(JadAttack attack)
+ {
+ final int prayerSpriteID = attack == JadAttack.MAGIC ? SpriteID.PRAYER_PROTECT_FROM_MAGIC : SpriteID.PRAYER_PROTECT_FROM_MISSILES;
+ return spriteManager.getSprite(prayerSpriteID, 0);
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fightcavejadhelper/FightCaveJadHelperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavejadhelper/FightCaveJadHelperPlugin.java
new file mode 100644
index 0000000000..4eeb311101
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavejadhelper/FightCaveJadHelperPlugin.java
@@ -0,0 +1,89 @@
+package net.runelite.client.plugins.fightcavejadhelper;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import lombok.AccessLevel;
+import lombok.Getter;
+import net.runelite.api.NPC;
+import net.runelite.api.NpcID;
+import net.runelite.api.events.AnimationChanged;
+import net.runelite.api.events.NpcDespawned;
+import net.runelite.api.events.NpcSpawned;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+
+@PluginDescriptor(
+ name = "!Fight Cave - Jad",
+ description = "Show what to pray against Jad",
+ tags = {"bosses", "combat", "minigame", "overlay", "prayer", "pve", "pvm"},
+ enabledByDefault = false
+)
+public class FightCaveJadHelperPlugin extends Plugin
+{
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private FightCaveJadHelperOverlay overlay;
+
+ @Getter(AccessLevel.PACKAGE)
+ @Nullable
+ private JadAttack attack;
+
+ private NPC jad;
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ overlayManager.add(overlay);
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ overlayManager.remove(overlay);
+ jad = null;
+ attack = null;
+ }
+
+ @Subscribe
+ public void onNpcSpawned(final NpcSpawned event)
+ {
+ final int id = event.getNpc().getId();
+
+ if (id == NpcID.TZTOKJAD || id == NpcID.TZTOKJAD_6506)
+ {
+ jad = event.getNpc();
+ }
+ }
+
+ @Subscribe
+ public void onNpcDespawned(final NpcDespawned event)
+ {
+ if (jad == event.getNpc())
+ {
+ jad = null;
+ attack = null;
+ }
+ }
+
+ @Subscribe
+ public void onAnimationChanged(final AnimationChanged event)
+ {
+ if (event.getActor() != jad)
+ {
+ return;
+ }
+
+ if (jad.getAnimation() == JadAttack.MAGIC.getAnimation())
+ {
+ attack = JadAttack.MAGIC;
+ }
+ else if (jad.getAnimation() == JadAttack.RANGE.getAnimation())
+ {
+ attack = JadAttack.RANGE;
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fightcavejadhelper/JadAttack.java b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavejadhelper/JadAttack.java
new file mode 100644
index 0000000000..9d9ec47a4b
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavejadhelper/JadAttack.java
@@ -0,0 +1,29 @@
+package net.runelite.client.plugins.fightcavejadhelper;
+
+import net.runelite.api.AnimationID;
+import net.runelite.api.Prayer;
+
+public enum JadAttack
+{
+ MAGIC(AnimationID.TZTOK_JAD_MAGIC_ATTACK, Prayer.PROTECT_FROM_MAGIC),
+ RANGE(AnimationID.TZTOK_JAD_RANGE_ATTACK, Prayer.PROTECT_FROM_MISSILES);
+
+ private final int animation;
+ private final Prayer prayer;
+
+ JadAttack(int animation, Prayer prayer)
+ {
+ this.animation = animation;
+ this.prayer = prayer;
+ }
+
+ public int getAnimation()
+ {
+ return animation;
+ }
+
+ public Prayer getPrayer()
+ {
+ return prayer;
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/FightCaveWaveHelperConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/FightCaveWaveHelperConfig.java
new file mode 100644
index 0000000000..989c4965e8
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/FightCaveWaveHelperConfig.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018, Jordan Atwood
+ * 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.fightcavewavehelper;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("Fight Cave - Wave Helper")
+public interface FightCaveWaveHelperConfig extends Config
+{
+ @ConfigItem(
+ keyName = "waveDisplay",
+ name = "Wave display",
+ description = "Shows monsters that will spawn on the selected wave(s)."
+ )
+ default WaveDisplayMode waveDisplay()
+ {
+ return WaveDisplayMode.BOTH;
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/FightCaveWaveHelperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/FightCaveWaveHelperPlugin.java
new file mode 100644
index 0000000000..d06451a9d0
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/FightCaveWaveHelperPlugin.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2018, Jordan Atwood
+ * 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.fightcavewavehelper;
+
+import com.google.inject.Provides;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.inject.Inject;
+import lombok.Getter;
+import net.runelite.api.ChatMessageType;
+import net.runelite.api.Client;
+import net.runelite.api.GameState;
+import net.runelite.api.events.ChatMessage;
+import net.runelite.api.events.GameStateChanged;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+import org.apache.commons.lang3.ArrayUtils;
+
+@PluginDescriptor(
+ name = "!Fight Cave - Waves",
+ description = "Displays current and upcoming wave monsters in the Fight Caves",
+ tags = {"bosses", "combat", "minigame", "overlay", "pve", "pvm", "jad", "fire", "cape", "wave"},
+ enabledByDefault = false
+)
+public class FightCaveWaveHelperPlugin extends Plugin
+{
+ private static final Pattern WAVE_PATTERN = Pattern.compile(".*Wave: (\\d+).*");
+ private static final int FIGHT_CAVE_REGION = 9551;
+ private static final int MAX_MONSTERS_OF_TYPE_PER_WAVE = 2;
+
+ static final int MAX_WAVE = 63;
+
+ @Getter
+ static final List> WAVES = new ArrayList<>();
+
+ @Getter
+ private int currentWave = -1;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private WaveOverlay waveOverlay;
+
+ static
+ {
+ final WaveMonster[] waveMonsters = WaveMonster.values();
+
+ // Add wave 1, future waves are derived from its contents
+ final EnumMap waveOne = new EnumMap<>(WaveMonster.class);
+ waveOne.put(waveMonsters[0], 1);
+ WAVES.add(waveOne);
+
+ for (int wave = 1; wave < MAX_WAVE; wave++)
+ {
+ final EnumMap prevWave = WAVES.get(wave - 1).clone();
+ int maxMonsterOrdinal = -1;
+
+ for (int i = 0; i < waveMonsters.length; i++)
+ {
+ final int ordinalMonsterQuantity = prevWave.getOrDefault(waveMonsters[i], 0);
+
+ if (ordinalMonsterQuantity == MAX_MONSTERS_OF_TYPE_PER_WAVE)
+ {
+ maxMonsterOrdinal = i;
+ break;
+ }
+ }
+
+ if (maxMonsterOrdinal >= 0)
+ {
+ prevWave.remove(waveMonsters[maxMonsterOrdinal]);
+ }
+
+ final int addedMonsterOrdinal = maxMonsterOrdinal >= 0 ? maxMonsterOrdinal + 1 : 0;
+ final WaveMonster addedMonster = waveMonsters[addedMonsterOrdinal];
+ final int addedMonsterQuantity = prevWave.getOrDefault(addedMonster, 0);
+
+ prevWave.put(addedMonster, addedMonsterQuantity + 1);
+
+ WAVES.add(prevWave);
+ }
+ }
+
+ @Provides
+ FightCaveWaveHelperConfig provideConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(FightCaveWaveHelperConfig.class);
+ }
+
+ @Override
+ public void startUp()
+ {
+ overlayManager.add(waveOverlay);
+ }
+
+ @Override
+ public void shutDown()
+ {
+ overlayManager.remove(waveOverlay);
+ currentWave = -1;
+ }
+
+ @Subscribe
+ public void onGameStateChanged(GameStateChanged event)
+ {
+ if (event.getGameState() != GameState.LOGGED_IN)
+ {
+ return;
+ }
+
+ if (!inFightCave())
+ {
+ currentWave = -1;
+ }
+ }
+
+ @Subscribe
+ public void onChatMessage(ChatMessage event)
+ {
+ final Matcher waveMatcher = WAVE_PATTERN.matcher(event.getMessage());
+
+ if (event.getType() != ChatMessageType.GAMEMESSAGE
+ || !inFightCave()
+ || !waveMatcher.matches())
+ {
+ return;
+ }
+
+ currentWave = Integer.parseInt(waveMatcher.group(1));
+ }
+
+ boolean inFightCave()
+ {
+ return ArrayUtils.contains(client.getMapRegions(), FIGHT_CAVE_REGION);
+ }
+
+ static String formatMonsterQuantity(final WaveMonster monster, final int quantity)
+ {
+ return String.format("%dx %s", quantity, monster);
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/WaveDisplayMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/WaveDisplayMode.java
new file mode 100644
index 0000000000..79a9d8174e
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/WaveDisplayMode.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018, Jordan Atwood
+ * 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.fightcavewavehelper;
+
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public enum WaveDisplayMode
+{
+ CURRENT("Current wave"),
+ NEXT("Next wave"),
+ BOTH("Both");
+
+ private final String name;
+
+ @Override
+ public String toString()
+ {
+ return name;
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/WaveMonster.java b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/WaveMonster.java
new file mode 100644
index 0000000000..df2fa9b7af
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/WaveMonster.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018, Jordan Atwood
+ * 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.fightcavewavehelper;
+
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+enum WaveMonster
+{
+ TZ_KIH("Tz-Kih", 22),
+ TZ_KEK("Tz-Kek", 45),
+ TOK_XIL("Tok-Xil", 90),
+ YT_MEJKOT("Yt-MejKot", 180),
+ KET_ZEK("Ket-Zek", 360),
+ TZKOK_JAD("TzTok-Jad", 702);
+
+ private final String name;
+ private final int level;
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s - Level %s", name, level);
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/WaveOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/WaveOverlay.java
new file mode 100644
index 0000000000..b5e6878b02
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/fightcavewavehelper/WaveOverlay.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2018, Jordan Atwood
+ * 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.fightcavewavehelper;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import javax.inject.Inject;
+import net.runelite.client.ui.ColorScheme;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.components.LineComponent;
+import net.runelite.client.ui.overlay.components.PanelComponent;
+import net.runelite.client.ui.overlay.components.TitleComponent;
+
+class WaveOverlay extends Overlay
+{
+ private static final Color HEADER_COLOR = ColorScheme.BRAND_ORANGE;
+
+ private final FightCaveWaveHelperConfig config;
+ private final FightCaveWaveHelperPlugin plugin;
+
+ private final PanelComponent panelComponent = new PanelComponent();
+
+ @Inject
+ private WaveOverlay(FightCaveWaveHelperConfig config, FightCaveWaveHelperPlugin plugin)
+ {
+ setPosition(OverlayPosition.TOP_RIGHT);
+ this.config = config;
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if (!plugin.inFightCave()
+ || plugin.getCurrentWave() < 0)
+ {
+ return null;
+ }
+
+ panelComponent.getChildren().clear();
+
+ final int currentWave = plugin.getCurrentWave();
+ final int waveIndex = currentWave - 1;
+
+ if (config.waveDisplay() == WaveDisplayMode.CURRENT
+ || config.waveDisplay() == WaveDisplayMode.BOTH)
+ {
+ final Map waveContents = FightCaveWaveHelperPlugin.getWAVES().get(waveIndex);
+
+ addWaveInfo("Wave " + plugin.getCurrentWave(), waveContents);
+ }
+
+ if ((config.waveDisplay() == WaveDisplayMode.NEXT
+ || config.waveDisplay() == WaveDisplayMode.BOTH)
+ && currentWave != FightCaveWaveHelperPlugin.MAX_WAVE)
+ {
+ final Map waveContents = FightCaveWaveHelperPlugin.getWAVES().get(waveIndex + 1);
+
+ addWaveInfo("Next wave", waveContents);
+ }
+
+ return panelComponent.render(graphics);
+ }
+
+ private void addWaveInfo(final String headerText, final Map waveContents)
+ {
+ panelComponent.getChildren().add(TitleComponent.builder()
+ .text(headerText)
+ .color(HEADER_COLOR)
+ .build());
+
+ for (LineComponent line : buildWaveLines(waveContents))
+ {
+ panelComponent.getChildren().add(line);
+ }
+ }
+
+ private static Collection buildWaveLines(final Map wave)
+ {
+ final List> monsters = new ArrayList<>(wave.entrySet());
+ monsters.sort(Map.Entry.comparingByKey());
+ final List outputLines = new ArrayList<>();
+
+ for (Map.Entry monsterEntry : monsters)
+ {
+ final WaveMonster monster = monsterEntry.getKey();
+ final int quantity = monsterEntry.getValue();
+ final LineComponent line = LineComponent.builder()
+ .left(FightCaveWaveHelperPlugin.formatMonsterQuantity(monster, quantity))
+ .build();
+
+ outputLines.add(line);
+ }
+
+ return outputLines;
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/Barrage.java b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/Barrage.java
new file mode 100644
index 0000000000..6b80dd4ce3
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/Barrage.java
@@ -0,0 +1,43 @@
+package net.runelite.client.plugins.freezetimers;
+
+import net.runelite.api.Actor;
+import net.runelite.client.plugins.freezetimers.Spell;
+import net.runelite.client.util.Text;
+
+public class Barrage
+extends Spell {
+ public static final long DURATION = 20000L;
+ private long remainingTime;
+ private boolean isFinished;
+
+ public Barrage(Actor affectedTarget, Actor caster) {
+ super(affectedTarget, caster);
+ }
+
+ public long getRemainingTime() {
+ long elapsedTime = System.currentTimeMillis() - this.startTime;
+ if (Barrage.getDURATION() > elapsedTime) {
+ return Barrage.getDURATION() - elapsedTime;
+ }
+ this.isFinished = true;
+ return 0L;
+ }
+
+ public boolean equals(Object o) {
+ if (o instanceof Barrage) {
+ Barrage barrage = (Barrage)o;
+ return Text.standardize(this.getAffectedTarget().getName()).equals(Text.standardize(((Barrage)o).getAffectedTarget().getName())) && this.getStartTime() == ((Barrage)o).getStartTime();
+ }
+ return false;
+ }
+
+ public static long getDURATION() {
+ return 20000L;
+ }
+
+ @Override
+ public boolean isFinished() {
+ return this.isFinished;
+ }
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersConfig.java
new file mode 100644
index 0000000000..512905759d
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersConfig.java
@@ -0,0 +1,51 @@
+package net.runelite.client.plugins.freezetimers;
+
+import java.awt.Color;
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup(value="freezetimers")
+public interface FreezeTimersConfig
+extends Config {
+ @ConfigItem(position=0, keyName="freezeenable", name="Enable PvP freeze timers", description="Configures whether or not to show freeze timers.")
+ default public boolean EnableFreezeTimers() {
+ return false;
+ }
+
+ @ConfigItem(position=1, keyName="tilehighlight", name="Frozen opponent tile highlighting", description="Configures whether or not to highlight tiles frozen opponents are standing on.")
+ default public boolean drawTiles() {
+ return false;
+ }
+
+ @ConfigItem(position=2, keyName="timercolor", name="Freeze Timer Color", description="Color of freeze timer")
+ default public Color FreezeTimerColor() {
+ return new Color(0, 184, 212);
+ }
+
+ @ConfigItem(position=3, keyName="spellIcon", name="Show spell icon", description="Shows the spell icon for the freeze spell affecting the target")
+ default public boolean spellIcon() {
+ return true;
+ }
+
+ @ConfigItem(position=4, keyName="refreezeTimer", name="Refreeze Timer", description="Show a timer that counts up until the target can be refrozen")
+ default public boolean refreezeTimer() {
+ return true;
+ }
+
+ @ConfigItem(position=5, keyName="refreezeTimerColor", name="Refreeze color", description="The color for the timer that counts until the target can be refrozen")
+ default public Color RefreezeTimerColor() {
+ return Color.red;
+ }
+
+ @ConfigItem(position = 6, keyName = "tbtimer", name = "Tele Block Timer", description = "Enables tele block timer")
+ default boolean TBTimer() {
+ return true;
+ }
+
+ @ConfigItem(position = 7, keyName = "timerpos", name = "Freeze Timer Position", description = "Position of freeze timer")
+ default int FreezeTimerPos() {
+ return 80;
+ }
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersOverlay.java
new file mode 100644
index 0000000000..1d7dbd162e
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersOverlay.java
@@ -0,0 +1,157 @@
+package net.runelite.client.plugins.freezetimers;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.Stroke;
+import java.awt.image.BufferedImage;
+import java.util.function.BiConsumer;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import net.runelite.api.Client;
+import net.runelite.api.HeadIcon;
+import net.runelite.api.Player;
+import net.runelite.api.Point;
+import net.runelite.client.game.SpriteManager;
+import net.runelite.client.plugins.freezetimers.FreezeTimersConfig;
+import net.runelite.client.plugins.freezetimers.FreezeTimersPlugin;
+import net.runelite.client.plugins.freezetimers.FreezeTimersService;
+import net.runelite.client.ui.FontManager;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.OverlayPriority;
+import net.runelite.client.ui.overlay.OverlayUtil;
+
+@Singleton
+public class FreezeTimersOverlay
+extends Overlay {
+ private final FreezeTimersService FreezeTimersService;
+ private final FreezeTimersConfig config;
+ private final FreezeTimersPlugin plugin;
+ private final SpriteManager spriteManager;
+ private final Client client;
+
+ @Inject
+ private FreezeTimersOverlay(FreezeTimersConfig config, FreezeTimersService FreezeTimersService2, FreezeTimersPlugin plugin, Client client, SpriteManager spriteManager) {
+ this.config = config;
+ this.FreezeTimersService = FreezeTimersService2;
+ this.plugin = plugin;
+ this.client = client;
+ this.spriteManager = spriteManager;
+ this.setPosition(OverlayPosition.DYNAMIC);
+ this.setPriority(OverlayPriority.MED);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics) {
+ if (!this.config.EnableFreezeTimers()) {
+ return null;
+ }
+ this.FreezeTimersService.forEachPlayer((player, color) -> this.renderPlayerOverlay(graphics, (Player)player, (Color)color));
+ return null;
+ }
+
+ private void renderPlayerOverlay(Graphics2D graphics, Player actor, Color color) {
+ BufferedImage clanchatImage;
+ int timer = 0;
+ String name = actor.getName();
+ int freezetype = this.plugin.freezetype(name);
+ boolean frozenoverlay = false;
+ int offset = 5;
+ long dtime = this.plugin.opponentfreezetime(name);
+ long tbed = plugin.istbed(name);
+ Point textLocation = null;
+ HeadIcon headIcon = actor.getOverheadIcon();
+ int freezetime = 0;
+ if (freezetype == 1 || freezetype == 4) {
+ freezetime = 5000;
+ } else if (freezetype == 2 || freezetype == 5) {
+ freezetime = 10000;
+ } else if (freezetype == 3 || freezetype == 6) {
+ freezetime = 15000;
+ } else if (freezetype == 7) {
+ freezetime = 20000;
+ } else if (freezetype == 8) {
+ freezetime = 2500;
+ } else if (freezetype == 9) {
+ freezetime = 5000;
+ } else if (freezetype == 10) {
+ freezetime = 7500;
+ }
+ long currenttime = System.currentTimeMillis();
+ long timediff = currenttime - dtime;
+ timer = (freezetime - (int)timediff) / 1000;
+ if (timediff < (long)freezetime) {
+ textLocation = actor.getCanvasTextLocation(graphics, String.valueOf(timer), actor.getLogicalHeight() + config.FreezeTimerPos());
+ textLocation = new Point(textLocation.getX(), textLocation.getY() - config.FreezeTimerPos());
+ } else if (timediff < (long)(freezetime + 3000)) {
+ timer = Math.abs(timer);
+ ++timer;
+ if (this.config.refreezeTimer()) {
+ textLocation = actor.getCanvasTextLocation(graphics, String.valueOf(timer), actor.getLogicalHeight() + config.FreezeTimerPos());
+ textLocation = new Point(textLocation.getX(), textLocation.getY() - config.FreezeTimerPos());
+ graphics.setFont(FontManager.getRunescapeBoldFont());
+ if (headIcon != null) {
+ textLocation = new Point(textLocation.getX(), textLocation.getY() - config.FreezeTimerPos());
+ }
+ frozenoverlay = true;
+ OverlayUtil.renderTextLocation(graphics, textLocation, String.valueOf(timer), this.config.RefreezeTimerColor());
+ return;
+ }
+ } else {
+ this.plugin.deleteopponent(name);
+ }
+ if (textLocation != null && (clanchatImage = this.plugin.GetFreezeIcon(freezetype - 1)) != null) {
+ int width = clanchatImage.getWidth();
+ int textHeight = graphics.getFontMetrics().getHeight() - graphics.getFontMetrics().getMaxDescent();
+ Point imageLocation = new Point(textLocation.getX(), textLocation.getY() - (config.FreezeTimerPos() / 2));
+ graphics.setFont(FontManager.getRunescapeFont());
+ graphics.setStroke(new BasicStroke(3.0f));
+ if (this.config.spellIcon()) {
+ frozenoverlay = true;
+ graphics.drawOval(imageLocation.getX(), imageLocation.getY(), clanchatImage.getWidth(), clanchatImage.getHeight());
+ OverlayUtil.renderImageLocation(graphics, imageLocation, clanchatImage);
+ OverlayUtil.renderTextLocation(graphics, textLocation, String.valueOf(timer), color);
+ } else {
+ graphics.setColor(Color.cyan);
+ graphics.drawOval(textLocation.getX() - 3, textLocation.getY() - 15, clanchatImage.getWidth(), graphics.getFontMetrics().getHeight());
+ graphics.setColor(Color.blue);
+ graphics.fillOval(textLocation.getX() - 3, textLocation.getY() - 15, clanchatImage.getWidth(), graphics.getFontMetrics().getHeight());
+ OverlayUtil.renderTextLocation(graphics, textLocation, String.valueOf(timer), Color.WHITE);
+ }
+ }
+
+ if (config.TBTimer()) {
+ if (tbed > 0) {
+ int type = plugin.tbtype(name);
+ int tbexpiry;
+ if (type > 0) {
+ if (type == 1) {
+ tbexpiry = 300000;
+ } else if (type == 2) {
+ tbexpiry = 150000;
+ } else {
+ return;
+ }
+ long tbtime = currenttime - tbed;
+ int tbtimer = (tbexpiry - (int) tbtime) / 1000;
+ if (tbtime < tbexpiry) {
+ textLocation = actor.getCanvasTextLocation(graphics, Integer.toString(tbtimer), actor.getLogicalHeight() + config.FreezeTimerPos());
+ if (frozenoverlay) {
+ textLocation = new Point(textLocation.getX() + 40, textLocation.getY() - config.FreezeTimerPos());
+ } else {
+ textLocation = new Point(textLocation.getX(), textLocation.getY() - config.FreezeTimerPos());
+ }
+ } else {
+ plugin.deletetb(name);
+ }
+ }
+
+ }
+ }
+ }
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersPlugin.java
new file mode 100644
index 0000000000..878270bfd4
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersPlugin.java
@@ -0,0 +1,402 @@
+package net.runelite.client.plugins.freezetimers;
+
+
+
+
+
+import com.google.inject.Provides;
+import java.awt.*;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.image.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.ImageObserver;
+import java.awt.image.IndexColorModel;
+import java.awt.image.WritableRaster;
+import java.util.*;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.inject.Inject;
+import net.runelite.api.*;
+import net.runelite.api.Actor;
+import net.runelite.api.Client;
+import net.runelite.api.events.*;
+import net.runelite.api.GameState;
+import net.runelite.api.HeadIcon;
+import net.runelite.api.IndexedSprite;
+import net.runelite.api.Player;
+import net.runelite.api.Skill;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.api.events.AnimationChanged;
+import net.runelite.api.events.ExperienceChanged;
+import net.runelite.api.events.GameStateChanged;
+import net.runelite.api.events.GameTick;
+import net.runelite.api.events.MenuOptionClicked;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.game.SpriteManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.plugins.freezetimers.Barrage;
+import net.runelite.client.plugins.freezetimers.FreezeTimersConfig;
+import net.runelite.client.plugins.freezetimers.FreezeTimersOverlay;
+import net.runelite.client.plugins.freezetimers.FreezeTimersTileOverlay;
+import net.runelite.client.plugins.freezetimers.Spell;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayManager;
+import net.runelite.client.util.ImageUtil;
+import org.slf4j.Logger;
+
+@PluginDescriptor(
+ name = "!Freeze Timers",
+ description = "PVP Freeze Timers",
+ tags = {"PvP", "Freeze", "Timers"}
+)
+
+public class FreezeTimersPlugin
+extends Plugin {
+ @Inject
+ private OverlayManager overlayManager;
+ @Inject
+ private FreezeTimersConfig config;
+ @Inject
+ private FreezeTimersOverlay FreezeTimersOverlay;
+ @Inject
+ private FreezeTimersTileOverlay FreezeTimersTileOverlay;
+ @Inject
+ private Client client;
+ @Inject
+ private SpriteManager spriteManager;
+
+ private static final int[] FREEZE_ICONS = {
+ SpriteID.SPELL_BIND,
+ SpriteID.SPELL_SNARE,
+ SpriteID.SPELL_ENTANGLE,
+ SpriteID.SPELL_ICE_RUSH,
+ SpriteID.SPELL_ICE_BURST,
+ SpriteID.SPELL_ICE_BLITZ,
+ SpriteID.SPELL_ICE_BARRAGE,
+ SpriteID.SPELL_BIND,
+ SpriteID.SPELL_SNARE,
+ SpriteID.SPELL_ENTANGLE,
+ SpriteID.SPELL_TELE_BLOCK
+ };
+ private static final Dimension FREEZE_ICON_DIMENSION = new Dimension(25, 25);
+ private static final Color FREEZE_ICON_OUTLINE_COLOR = new Color(33, 33, 33);
+ private final BufferedImage[] FreezeIcons = new BufferedImage[FREEZE_ICONS.length];
+ private final int SPLASH_ID = 85;
+ Map tbedthings = new HashMap<>();
+ Map tbtypes = new HashMap<>();
+ Map testMap = new HashMap();
+ Map frozenthings = new HashMap();
+ Map frozenthingpoints = new HashMap();
+ Map freezetype = new HashMap();
+ Map magexp = new HashMap();
+ int lastxp;
+ int ticks;
+ int currticks;
+ String currtarget;
+ String spell;
+
+ @Provides
+ FreezeTimersConfig provideConfig(ConfigManager configManager) {
+ return configManager.getConfig(FreezeTimersConfig.class);
+ }
+
+ @Subscribe
+ public void onGameStateChanged(GameStateChanged gameStateChanged) {
+ if (gameStateChanged.getGameState() == GameState.LOGGED_IN) {
+ this.loadFreezeIcons();
+ }
+ }
+
+ @Override
+ protected void startUp() throws Exception {
+ this.overlayManager.add(this.FreezeTimersOverlay);
+ this.overlayManager.add(this.FreezeTimersTileOverlay);
+ }
+
+ @Override
+ protected void shutDown() throws Exception {
+ this.overlayManager.remove(this.FreezeTimersOverlay);
+ this.overlayManager.remove(this.FreezeTimersTileOverlay);
+ this.frozenthings.clear();
+ this.frozenthingpoints.clear();
+ this.tbedthings.clear();
+ this.tbtypes.clear();
+ }
+
+ @Subscribe
+ public void onMenuOptionClicked(MenuOptionClicked event) {
+ if (event.getMenuTarget().contains("->")) {
+ Pattern spattern = Pattern.compile(">(.+?)");
+ Pattern ppattern = Pattern.compile("> (.+?) 0 && this.currtarget != null) {
+ if (this.frozenthings.containsKey(this.currtarget)) {
+ this.currtarget = null;
+ return;
+ }
+ WorldPoint targetPosition = null;
+ for (Player player : this.client.getPlayers()) {
+ String playerName;
+ if (player == null || !(playerName = player.getName()).equals(this.currtarget)) continue;
+ if (player.getOverheadIcon() != null && player.getOverheadIcon().equals((Object)HeadIcon.MAGIC)) {
+ praymage = true;
+ }
+ targetPosition = player.getWorldLocation();
+ break;
+ }
+ if (targetPosition != null) {
+ if (this.spell.equals("Bind") && xp > 30) {
+ this.frozenthings.put(this.currtarget, System.currentTimeMillis());
+ this.frozenthingpoints.put(this.currtarget, targetPosition);
+ if (praymage) {
+ this.freezetype.put(this.currtarget, 8);
+ } else {
+ this.freezetype.put(this.currtarget, 1);
+ }
+ } else if (this.spell.equals("Snare") && xp > 60) {
+ this.frozenthings.put(this.currtarget, System.currentTimeMillis());
+ this.frozenthingpoints.put(this.currtarget, targetPosition);
+ if (praymage) {
+ this.freezetype.put(this.currtarget, 9);
+ } else {
+ this.freezetype.put(this.currtarget, 2);
+ }
+ } else if (this.spell.equals("Entangle") && xp >= 89) {
+ this.frozenthings.put(this.currtarget, System.currentTimeMillis());
+ this.frozenthingpoints.put(this.currtarget, targetPosition);
+ if (praymage) {
+ this.freezetype.put(this.currtarget, 10);
+ } else {
+ this.freezetype.put(this.currtarget, 3);
+ }
+ } else if (this.spell.equals("Ice Rush") && xp > 34) {
+ this.frozenthings.put(this.currtarget, System.currentTimeMillis());
+ this.frozenthingpoints.put(this.currtarget, targetPosition);
+ this.freezetype.put(this.currtarget, 4);
+ } else if (this.spell.equals("Ice Burst") && xp > 40) {
+ this.frozenthings.put(this.currtarget, System.currentTimeMillis());
+ this.frozenthingpoints.put(this.currtarget, targetPosition);
+ this.freezetype.put(this.currtarget, 5);
+ } else if (this.spell.equals("Ice Blitz") && xp > 46) {
+ this.frozenthings.put(this.currtarget, System.currentTimeMillis());
+ this.frozenthingpoints.put(this.currtarget, targetPosition);
+ this.freezetype.put(this.currtarget, 6);
+ } else if (this.spell.equals("Ice Barrage") && xp > 52) {
+ Barrage barrage = new Barrage(this.client.getLocalPlayer().getInteracting(), this.client.getLocalPlayer());
+ this.testMap.put(this.currtarget, barrage);
+ this.frozenthings.put(this.currtarget, System.currentTimeMillis());
+ this.frozenthingpoints.put(this.currtarget, targetPosition);
+ this.freezetype.put(this.currtarget, 7);
+ } else if (spell.equals("Tele Block") && xp == 95) {
+ if (config.TBTimer()) {
+ if (praymage) {
+ this.tbtypes.put(this.currtarget, 2);
+ } else {
+ this.tbtypes.put(this.currtarget, 1);
+ }
+ this.tbedthings.put(this.currtarget, System.currentTimeMillis());
+ }
+ }
+ }
+ }
+ if (this.currtarget != null && this.ticks > this.currticks + 1) {
+ Player local = this.client.getLocalPlayer();
+ Actor interacting = local.getInteracting();
+ if (interacting != null) {
+ if (!interacting.getName().equals(this.currtarget)) {
+ this.currtarget = null;
+ }
+ } else {
+ this.currtarget = null;
+ }
+ }
+ ++this.ticks;
+ }
+
+ public long opponentfreezetime(String name) {
+ if (this.frozenthings.containsKey(name)) {
+ return this.frozenthings.get(name);
+ }
+ return 0L;
+ }
+
+ public WorldPoint playerpos(String name) {
+ if (this.frozenthingpoints.containsKey(name)) {
+ return this.frozenthingpoints.get(name);
+ }
+ return null;
+ }
+
+ public void updatePosition(String name, WorldPoint point) {
+ if (this.frozenthingpoints.containsKey(name)) {
+ this.frozenthingpoints.remove(name);
+ this.frozenthingpoints.put(name, point);
+ }
+ }
+
+ public int freezetype(String name) {
+ if (this.freezetype.containsKey(name)) {
+ return this.freezetype.get(name);
+ }
+ return 0;
+ }
+ public long istbed(String name) {
+ if (this.tbedthings.containsKey(name)) {
+ return this.tbedthings.get(name);
+ }
+ return 0;
+ }
+ public int tbtype(String name) {
+ if (this.tbtypes.containsKey(name)) {
+ return this.tbtypes.get(name);
+ }
+ return 0;
+ }
+ public void deleteopponent(String name) {
+ if (this.frozenthings.containsKey(name)) {
+ this.frozenthings.remove(name);
+ }
+ if (this.frozenthingpoints.containsKey(name)) {
+ this.frozenthingpoints.remove(name);
+ }
+ if (this.freezetype.containsKey(name)) {
+ this.freezetype.remove(name);
+ }
+ }
+ public void deletetb(String name) {
+ if (this.tbedthings.containsKey(name)) {
+ this.tbedthings.remove(name);
+ }
+ if (this.tbtypes.containsKey(name)) {
+ this.tbtypes.remove(name);
+ }
+ }
+ private void loadFreezeIcons() {
+ IndexedSprite[] freezeIcons = new IndexedSprite[]{};
+ IndexedSprite[] newfreezeIcons = Arrays.copyOf(freezeIcons, FREEZE_ICONS.length);
+ int curPosition = 0;
+ int i = 0;
+ while (i < FREEZE_ICONS.length) {
+ int resource = FREEZE_ICONS[i];
+ this.FreezeIcons[i] = FreezeTimersPlugin.rgbaToIndexedBufferedImage(FreezeTimersPlugin.FreezeIconFromSprite(this.spriteManager.getSprite(resource, 0)));
+ newfreezeIcons[curPosition] = FreezeTimersPlugin.createIndexedSprite(this.client, this.FreezeIcons[i]);
+ ++i;
+ ++curPosition;
+ }
+ }
+
+ private static IndexedSprite createIndexedSprite(Client client, BufferedImage bufferedImage) {
+ IndexColorModel indexedCM = (IndexColorModel)bufferedImage.getColorModel();
+ int width = bufferedImage.getWidth();
+ int height = bufferedImage.getHeight();
+ byte[] pixels = ((DataBufferByte)bufferedImage.getRaster().getDataBuffer()).getData();
+ int[] palette = new int[indexedCM.getMapSize()];
+ indexedCM.getRGBs(palette);
+ IndexedSprite newIndexedSprite = client.createIndexedSprite();
+ newIndexedSprite.setPixels(pixels);
+ newIndexedSprite.setPalette(palette);
+ newIndexedSprite.setWidth(width);
+ newIndexedSprite.setHeight(height);
+ newIndexedSprite.setOriginalWidth(width);
+ newIndexedSprite.setOriginalHeight(height);
+ newIndexedSprite.setOffsetX(0);
+ newIndexedSprite.setOffsetY(0);
+ return newIndexedSprite;
+ }
+
+ private static BufferedImage rgbaToIndexedBufferedImage(BufferedImage sourceBufferedImage) {
+ BufferedImage indexedImage = new BufferedImage(sourceBufferedImage.getWidth(), sourceBufferedImage.getHeight(), 13);
+ ColorModel cm = indexedImage.getColorModel();
+ IndexColorModel icm = (IndexColorModel)cm;
+ int size = icm.getMapSize();
+ byte[] reds = new byte[size];
+ byte[] greens = new byte[size];
+ byte[] blues = new byte[size];
+ icm.getReds(reds);
+ icm.getGreens(greens);
+ icm.getBlues(blues);
+ WritableRaster raster = indexedImage.getRaster();
+ int pixel = raster.getSample(0, 0, 0);
+ IndexColorModel resultIcm = new IndexColorModel(8, size, reds, greens, blues, pixel);
+ BufferedImage resultIndexedImage = new BufferedImage(resultIcm, raster, sourceBufferedImage.isAlphaPremultiplied(), null);
+ resultIndexedImage.getGraphics().drawImage(sourceBufferedImage, 0, 0, null);
+ return resultIndexedImage;
+ }
+
+ private static BufferedImage FreezeIconFromSprite(BufferedImage freezeSprite) {
+ BufferedImage freezeCanvas = ImageUtil.resizeCanvas(freezeSprite, FreezeTimersPlugin.FREEZE_ICON_DIMENSION.width, FreezeTimersPlugin.FREEZE_ICON_DIMENSION.height);
+ return ImageUtil.outlineImage(freezeCanvas, FREEZE_ICON_OUTLINE_COLOR);
+ }
+
+ BufferedImage GetFreezeIcon(int id) {
+ return this.FreezeIcons[id];
+ }
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersService.java b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersService.java
new file mode 100644
index 0000000000..257aae69b8
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersService.java
@@ -0,0 +1,81 @@
+package net.runelite.client.plugins.freezetimers;
+
+import java.awt.Color;
+import java.util.List;
+import java.util.function.BiConsumer;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import net.runelite.api.Client;
+import net.runelite.api.Player;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.client.plugins.freezetimers.FreezeTimersConfig;
+import net.runelite.client.plugins.freezetimers.FreezeTimersPlugin;
+
+@Singleton
+public class FreezeTimersService {
+ private final Client client;
+ private final FreezeTimersConfig config;
+ private final FreezeTimersPlugin plugin;
+
+ @Inject
+ private FreezeTimersService(Client client, FreezeTimersConfig config, FreezeTimersPlugin plugin) {
+ this.config = config;
+ this.plugin = plugin;
+ this.client = client;
+ }
+
+ public void forEachPlayer(BiConsumer consumer) {
+ for (Player player : this.client.getPlayers()) {
+ if (player == null || player.getName() == null) continue;
+ String name = player.getName();
+ int freezetype = this.plugin.freezetype(name);
+ long tbed = plugin.istbed(name);
+ long dtime = this.plugin.opponentfreezetime(name);
+ int freezetime = 0;
+ if (freezetype == 1 || freezetype == 4) {
+ freezetime = 5000;
+ } else if (freezetype == 2 || freezetype == 5) {
+ freezetime = 10000;
+ } else if (freezetype == 3 || freezetype == 6) {
+ freezetime = 15000;
+ } else if (freezetype == 7) {
+ freezetime = 20000;
+ } else if (freezetype == 8) {
+ freezetime = 2500;
+ } else if (freezetype == 9) {
+ freezetime = 5000;
+ } else if (freezetype == 10) {
+ freezetime = 7500;
+ }
+ if (dtime <= 0L) continue;
+ long currenttime = System.currentTimeMillis();
+ long timediff = currenttime - dtime;
+ if (timediff < (long)freezetime) {
+ WorldPoint lastWorldPoint;
+ WorldPoint currentWorldPoint = player.getWorldLocation();
+ if (currentWorldPoint.equals(lastWorldPoint = this.plugin.playerpos(name))) {
+ consumer.accept(player, this.config.FreezeTimerColor());
+ continue;
+ }
+ if (timediff < 605L) {
+ this.plugin.updatePosition(name, currentWorldPoint);
+ consumer.accept(player, this.config.FreezeTimerColor());
+ continue;
+ }
+ this.plugin.deleteopponent(name);
+ continue;
+ }
+ if (timediff < (long)(freezetime + 3000)) {
+ consumer.accept(player, Color.YELLOW);
+ continue;
+ } else {
+ this.plugin.deleteopponent(name);
+ }
+ if (tbed > 0) {
+ consumer.accept(player, config.FreezeTimerColor());
+ return;
+ }
+ }
+ }
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersTileOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersTileOverlay.java
new file mode 100644
index 0000000000..a945470c85
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/FreezeTimersTileOverlay.java
@@ -0,0 +1,46 @@
+package net.runelite.client.plugins.freezetimers;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Polygon;
+import java.util.function.BiConsumer;
+import javax.inject.Inject;
+import net.runelite.api.Player;
+import net.runelite.client.plugins.freezetimers.FreezeTimersConfig;
+import net.runelite.client.plugins.freezetimers.FreezeTimersService;
+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.OverlayPriority;
+import net.runelite.client.ui.overlay.OverlayUtil;
+
+public class FreezeTimersTileOverlay
+extends Overlay {
+ private final FreezeTimersService FreezeTimersService;
+ private final FreezeTimersConfig config;
+
+ @Inject
+ private FreezeTimersTileOverlay(FreezeTimersConfig config, FreezeTimersService FreezeTimersService2) {
+ this.config = config;
+ this.FreezeTimersService = FreezeTimersService2;
+ this.setLayer(OverlayLayer.ABOVE_SCENE);
+ this.setPosition(OverlayPosition.DYNAMIC);
+ this.setPriority(OverlayPriority.MED);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics) {
+ if (!this.config.drawTiles()) {
+ return null;
+ }
+ this.FreezeTimersService.forEachPlayer((player, color) -> {
+ Polygon poly = player.getCanvasTilePoly();
+ if (poly != null) {
+ OverlayUtil.renderPolygon(graphics, poly, color);
+ }
+ });
+ return null;
+ }
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/PlayerSpellEffect.java b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/PlayerSpellEffect.java
new file mode 100644
index 0000000000..8bc136fbb5
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/PlayerSpellEffect.java
@@ -0,0 +1,35 @@
+package net.runelite.client.plugins.freezetimers;
+
+public enum PlayerSpellEffect {
+ BARRAGE("Ice Barrage", 20000, false),
+ BLITZ("Ice Blitz", 15000, false);
+
+ private final String SPELL_NAME;
+ private long startTime;
+ private int duration;
+ private boolean halvable;
+
+ private PlayerSpellEffect(String name, int duration, boolean halvable) {
+ this.SPELL_NAME = name;
+ this.duration = duration;
+ this.halvable = halvable;
+ this.startTime = System.currentTimeMillis();
+ }
+
+ public String getSPELL_NAME() {
+ return this.SPELL_NAME;
+ }
+
+ public long getStartTime() {
+ return this.startTime;
+ }
+
+ public int getDuration() {
+ return this.duration;
+ }
+
+ public boolean isHalvable() {
+ return this.halvable;
+ }
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/Spell.java b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/Spell.java
new file mode 100644
index 0000000000..d9033a7c0d
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/freezetimers/Spell.java
@@ -0,0 +1,34 @@
+package net.runelite.client.plugins.freezetimers;
+
+import net.runelite.api.Actor;
+
+public abstract class Spell {
+ private final Actor affectedTarget;
+ private final Actor caster;
+ public final long startTime;
+ private long remainingTime;
+ private boolean isFinished;
+
+ protected Spell(Actor affectedTarget, Actor caster) {
+ this.affectedTarget = affectedTarget;
+ this.caster = caster;
+ this.startTime = System.currentTimeMillis();
+ }
+
+ public Actor getAffectedTarget() {
+ return this.affectedTarget;
+ }
+
+ public Actor getCaster() {
+ return this.caster;
+ }
+
+ public long getStartTime() {
+ return this.startTime;
+ }
+
+ public boolean isFinished() {
+ return this.isFinished;
+ }
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/friendtagging/FriendTaggingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/friendtagging/FriendTaggingPlugin.java
new file mode 100644
index 0000000000..c083887aca
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/friendtagging/FriendTaggingPlugin.java
@@ -0,0 +1,162 @@
+package net.runelite.client.plugins.friendtagging;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ObjectArrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import javax.inject.Inject;
+import lombok.NonNull;
+import net.runelite.api.Client;
+import net.runelite.api.Friend;
+import net.runelite.api.MenuAction;
+import net.runelite.api.MenuEntry;
+import net.runelite.api.Nameable;
+import net.runelite.api.events.MenuEntryAdded;
+import net.runelite.api.events.MenuOptionClicked;
+import net.runelite.api.events.NameableNameChanged;
+import net.runelite.api.events.RemovedFriend;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.game.chatbox.ChatboxPanelManager;
+import net.runelite.client.game.chatbox.ChatboxTextInput;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@PluginDescriptor(
+ name="Friend Tagging",
+ description="Tag people on your friends list."
+ )
+
+public class FriendTaggingPlugin extends Plugin
+{
+ private static final Logger log = LoggerFactory.getLogger(FriendTaggingPlugin.class);
+ public static ConcurrentHashMap taggedFriends = new ConcurrentHashMap();
+ private static final String CONFIG_GROUP = "friendtagging";
+ private static final int CHARACTER_LIMIT = 30;
+ private static final String KEY_PREFIX = "tag_";
+ private static final String ADD_TAG = "Add Tag";
+ private static final String DELETE_TAG = "Delete Tag";
+ @Inject
+ private Client client;
+ @Inject
+ private ConfigManager configManager;
+ @Inject
+ private ChatboxPanelManager chatboxPanelManager;
+
+ @Override
+ protected void startUp() throws Exception {
+ this.loadFriendTags();
+ }
+
+ @Override
+ protected void shutDown() throws Exception {
+ super.shutDown();
+ }
+
+ @Subscribe
+ public void onMenuEntryAdded(MenuEntryAdded event) {
+ int groupId = WidgetInfo.TO_GROUP(event.getActionParam1());
+ if (groupId == WidgetInfo.FRIENDS_LIST.getGroupId() && event.getOption().equals("Message")) {
+ String friendName = Text.removeTags(event.getTarget());
+ MenuEntry entry = new MenuEntry();
+ entry.setOption(friendName == null || this.getTag(friendName) == null ? ADD_TAG : DELETE_TAG);
+ entry.setType(MenuAction.RUNELITE.getId());
+ entry.setTarget(event.getTarget());
+ entry.setParam0(event.getActionParam0());
+ entry.setParam1(event.getActionParam1());
+ MenuEntry[] menuEntries = ObjectArrays.concat(this.client.getMenuEntries(), entry);
+ this.client.setMenuEntries(menuEntries);
+ }
+ }
+
+ @Subscribe
+ public void onRemovedFriend(RemovedFriend event) {
+ String displayName = event.getName().trim().toLowerCase();
+ this.deleteTag(displayName);
+ }
+
+ @Subscribe
+ public void onNameableNameChanged(NameableNameChanged event) {
+ Friend friend;
+ Nameable nameable = event.getNameable();
+ if (nameable instanceof Friend && (friend = (Friend)nameable).getName() != null && friend.getPrevName() != null) {
+ this.migrateFriendTag(friend.getName(), friend.getPrevName());
+ }
+ }
+
+ @Subscribe
+ public void onMenuOptionClicked(MenuOptionClicked event) {
+ if (WidgetInfo.TO_GROUP(event.getWidgetId()) == WidgetInfo.FRIENDS_LIST.getGroupId()) {
+ if (Strings.isNullOrEmpty(event.getMenuTarget())) {
+ return;
+ }
+ String sanitizedTarget = Text.removeTags(event.getMenuTarget());
+ if (event.getMenuOption().equals(ADD_TAG)) {
+ event.consume();
+ ChatboxTextInput chatboxTextInput = this.chatboxPanelManager.openTextInput("Enter the tag").value("").onDone(content -> {
+ if (content == null) {
+ return;
+ }
+ content = Text.removeTags(content).trim();
+ this.setTag(sanitizedTarget, (String)content);
+ }).build();
+ }
+ if (event.getMenuOption().equals(DELETE_TAG)) {
+ event.consume();
+ this.client.getLogger().info(sanitizedTarget);
+ taggedFriends.forEach((k, v) -> this.client.getLogger().info(k + ": ", v));
+ this.deleteTag(sanitizedTarget);
+ }
+ }
+ }
+
+ @NonNull
+ private String getTag(String name) {
+ name = name.trim().toLowerCase();
+ String keyName = KEY_PREFIX + name;
+ return taggedFriends.get(keyName);
+ }
+
+ private void setTag(String name, String tag) {
+ this.client.getLogger().info("SETTING " + name + ": " + tag);
+ name = name.trim().toLowerCase();
+ String keyName = KEY_PREFIX + name;
+ if (tag.length() <= 30) {
+ taggedFriends.put(keyName, tag);
+ this.configManager.setConfiguration(CONFIG_GROUP, keyName, tag);
+ }
+ }
+
+ private void deleteTag(String name) {
+ name = name.trim().toLowerCase();
+ String keyName = KEY_PREFIX + name;
+ this.configManager.unsetConfiguration(CONFIG_GROUP, keyName);
+ taggedFriends.remove(keyName);
+ }
+
+ private void loadFriendTags() {
+ String prefix = "friendtagging.tag_";
+ for (String key : this.configManager.getConfigurationKeys(prefix)) {
+ String result = this.configManager.getConfiguration(CONFIG_GROUP, key = key.replace("friendtagging.", ""));
+ if (!Objects.nonNull(result) || result.equals("")) continue;
+ taggedFriends.put(key, this.configManager.getConfiguration(CONFIG_GROUP, key));
+ }
+ }
+
+ private void migrateFriendTag(String currentDisplayName, String prevDisplayName) {
+ String prevTag;
+ String currentTag = this.getTag(currentDisplayName);
+ if (currentTag == null && (prevTag = this.getTag(prevDisplayName)) != null) {
+ this.setTag(prevDisplayName, "");
+ this.setTag(currentDisplayName, prevTag);
+ }
+ }
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grotesqueguardians/GrotesqueGuardiansOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/grotesqueguardians/GrotesqueGuardiansOverlay.java
new file mode 100644
index 0000000000..8f90c59363
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grotesqueguardians/GrotesqueGuardiansOverlay.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2018, Damen
+ * 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.grotesqueguardians;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Polygon;
+import javax.inject.Inject;
+import net.runelite.api.Client;
+import net.runelite.api.GraphicsObject;
+import net.runelite.api.Perspective;
+import net.runelite.api.coords.LocalPoint;
+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.OverlayPriority;
+import net.runelite.client.ui.overlay.OverlayUtil;
+
+class GrotesqueGuardiansOverlay extends Overlay
+{
+ private static final int GROTESQUE_GUARDIANS_REGION_ID = 6727;
+ private final Client client;
+ private static final int GROTESQUE_GUARDIANS_LIGHTNING_START = 1416;
+ private static final int GROTESQUE_GUARDIANS_LIGHTNING_END = 1431;
+ private static final int GROTESQUE_GUARDIANS_FALLING_ROCKS = 1436;
+ private static final int GROTESQUE_GUARDIANS_STONE_ORB = 160;
+
+ @Inject
+ private GrotesqueGuardiansOverlay(Client client)
+ {
+ this.client = client;
+ setPosition(OverlayPosition.DYNAMIC);
+ setLayer(OverlayLayer.ABOVE_SCENE);
+ setPriority(OverlayPriority.LOW);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if (!client.isInInstancedRegion() || client.getMapRegions()[0] != GROTESQUE_GUARDIANS_REGION_ID)
+ {
+ return null;
+ }
+
+ // TODO: Awaiting GraphicsObjectDespawn event to be tracked to make this more efficient.
+ for (GraphicsObject graphicsObject : client.getGraphicsObjects())
+ {
+ Color color = null;
+
+ if (graphicsObject.getId() >= GROTESQUE_GUARDIANS_LIGHTNING_START && graphicsObject.getId() <= GROTESQUE_GUARDIANS_LIGHTNING_END)
+ {
+ color = Color.ORANGE;
+ }
+ else if (graphicsObject.getId() == GROTESQUE_GUARDIANS_STONE_ORB)
+ {
+ color = Color.GRAY;
+ }
+ else if (graphicsObject.getId() == GROTESQUE_GUARDIANS_FALLING_ROCKS)
+ {
+ color = Color.YELLOW;
+ }
+ else
+ {
+ continue;
+ }
+
+ LocalPoint lp = graphicsObject.getLocation();
+ Polygon poly = Perspective.getCanvasTilePoly(client, lp);
+
+ if (poly != null)
+ {
+ OverlayUtil.renderPolygon(graphics, poly, color);
+ }
+ }
+ return null;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grotesqueguardians/GrotesqueGuardiansPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grotesqueguardians/GrotesqueGuardiansPlugin.java
new file mode 100644
index 0000000000..d6df9dc49f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grotesqueguardians/GrotesqueGuardiansPlugin.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018, Damen
+ * 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.grotesqueguardians;
+
+import javax.inject.Inject;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+
+@PluginDescriptor(
+ name = "!Grotesque Guardians",
+ description = "Display tile indicators for the Grotesque Guardian special attacks",
+ tags = {"grotesque", "guardians", "gargoyle", "garg"}
+)
+public class GrotesqueGuardiansPlugin extends Plugin
+{
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private GrotesqueGuardiansOverlay overlay;
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ overlayManager.add(overlay);
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ overlayManager.remove(overlay);
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hideprayers/HidePrayersConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/hideprayers/HidePrayersConfig.java
new file mode 100644
index 0000000000..8a92bed779
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/hideprayers/HidePrayersConfig.java
@@ -0,0 +1,35 @@
+package net.runelite.client.plugins.hideprayers;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("hideprayers")
+public interface HidePrayersConfig extends Config
+{
+ @ConfigItem(
+ position = 0,
+ keyName = "pk prayers",
+ name = "Hides none pk prayers",
+ description = "Hides widget icons."
+ )
+ default boolean showPrayers() { return false; }
+
+ @ConfigItem(
+ position = 1,
+ keyName = "eagle/mystic",
+ name = "Shows eagle and mystic prayers",
+ description = "Hides widget icons."
+ )
+ default boolean showEagleMystic() { return false; }
+
+ @ConfigItem(
+ position = 1,
+ keyName = "ultstr",
+ name = "Shows ultimate strength",
+ description = "Hides widget icons."
+ )
+ default boolean showUltStrength() { return false; }
+
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hideprayers/HidePrayersPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/hideprayers/HidePrayersPlugin.java
new file mode 100644
index 0000000000..df473a5d0d
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/hideprayers/HidePrayersPlugin.java
@@ -0,0 +1,169 @@
+package net.runelite.client.plugins.hideprayers;
+
+import com.google.common.collect.ImmutableList;
+import net.runelite.client.eventbus.Subscribe;
+import com.google.inject.Provides;
+import net.runelite.api.*;
+import net.runelite.api.events.ConfigChanged;
+import net.runelite.api.events.GameStateChanged;
+import net.runelite.api.events.WidgetLoaded;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetID;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+
+import javax.inject.Inject;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+@PluginDescriptor(
+ name = "!Hide Prayers",
+ description = "Hides specific Prayers in the Prayer tab."
+)
+public class HidePrayersPlugin extends Plugin {
+ private static final int PRAYER_COUNT = Prayer.values().length;
+
+ private static final List PRAYER_WIDGET_INFO_LIST = ImmutableList.of(WidgetInfo.PRAYER_THICK_SKIN,
+ WidgetInfo.PRAYER_BURST_OF_STRENGTH, WidgetInfo.PRAYER_CLARITY_OF_THOUGHT, WidgetInfo.PRAYER_SHARP_EYE,
+ WidgetInfo.PRAYER_MYSTIC_WILL, WidgetInfo.PRAYER_ROCK_SKIN, WidgetInfo.PRAYER_SUPERHUMAN_STRENGTH,
+ WidgetInfo.PRAYER_IMPROVED_REFLEXES, WidgetInfo.PRAYER_RAPID_RESTORE, WidgetInfo.PRAYER_RAPID_HEAL,
+ WidgetInfo.PRAYER_PROTECT_ITEM, WidgetInfo.PRAYER_HAWK_EYE, WidgetInfo.PRAYER_MYSTIC_LORE,
+ WidgetInfo.PRAYER_STEEL_SKIN, WidgetInfo.PRAYER_ULTIMATE_STRENGTH, WidgetInfo.PRAYER_INCREDIBLE_REFLEXES,
+ WidgetInfo.PRAYER_PROTECT_FROM_MAGIC, WidgetInfo.PRAYER_PROTECT_FROM_MISSILES,
+ WidgetInfo.PRAYER_PROTECT_FROM_MELEE, WidgetInfo.PRAYER_EAGLE_EYE, WidgetInfo.PRAYER_MYSTIC_MIGHT,
+ WidgetInfo.PRAYER_RETRIBUTION, WidgetInfo.PRAYER_REDEMPTION, WidgetInfo.PRAYER_SMITE,
+ WidgetInfo.PRAYER_PRESERVE, WidgetInfo.PRAYER_CHIVALRY, WidgetInfo.PRAYER_PIETY, WidgetInfo.PRAYER_RIGOUR,
+ WidgetInfo.PRAYER_AUGURY);
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private HidePrayersConfig config;
+
+ @Provides
+ HidePrayersConfig provideConfig(ConfigManager configManager) {
+ return configManager.getConfig(HidePrayersConfig.class);
+ }
+
+ @Override
+ protected void startUp() throws Exception {
+ hidePrayers();
+ }
+
+ @Override
+ protected void shutDown() throws Exception {
+ restorePrayers();
+ }
+
+ @Subscribe
+ public void onGameStateChanged(GameStateChanged event) {
+ if (event.getGameState() == GameState.LOGGED_IN) {
+ hidePrayers();
+ }
+ }
+
+ @Subscribe
+ public void onConfigChanged(ConfigChanged event) {
+ if (event.getGroup().equals("hideprayers")) {
+ hidePrayers();
+ }
+ }
+
+ @Subscribe
+ public void onWidgetLoaded(WidgetLoaded event) {
+ if (event.getGroupId() == WidgetID.PRAYER_GROUP_ID || event.getGroupId() == WidgetID.QUICK_PRAYERS_GROUP_ID) {
+ hidePrayers();
+ }
+ }
+
+ private PrayerTabState getPrayerTabState() {
+ HashTable componentTable = client.getComponentTable();
+ for (WidgetNode widgetNode : componentTable.getNodes()) {
+ if (widgetNode.getId() == WidgetID.PRAYER_GROUP_ID) {
+ return PrayerTabState.PRAYERS;
+ } else if (widgetNode.getId() == WidgetID.QUICK_PRAYERS_GROUP_ID) {
+ return PrayerTabState.QUICK_PRAYERS;
+ }
+ }
+ return PrayerTabState.NONE;
+ }
+
+ private void restorePrayers() {
+ if (client.getGameState() != GameState.LOGGED_IN)
+ return;
+
+ PrayerTabState prayerTabState = getPrayerTabState();
+
+ if (prayerTabState == PrayerTabState.PRAYERS) {
+ List prayerWidgets = PRAYER_WIDGET_INFO_LIST.stream().map(client::getWidget)
+ .filter(Objects::nonNull).collect(Collectors.toList());
+
+ if (prayerWidgets.size() != PRAYER_WIDGET_INFO_LIST.size())
+ return;
+
+ for (int index = 0; index < PRAYER_COUNT; index++)
+ prayerWidgets.get(Prayer.values()[index].ordinal()).setHidden(false);
+ }
+ }
+
+ private void hidePrayers() {
+ if (client.getGameState() != GameState.LOGGED_IN)
+ return;
+
+ PrayerTabState prayerTabState = getPrayerTabState();
+
+ if (prayerTabState == PrayerTabState.PRAYERS) {
+ List prayerWidgets = PRAYER_WIDGET_INFO_LIST.stream().map(client::getWidget)
+ .filter(Objects::nonNull).collect(Collectors.toList());
+
+ if (prayerWidgets.size() != PRAYER_WIDGET_INFO_LIST.size())
+ return;
+
+ for (int index = 0; index < PRAYER_COUNT; index++) {
+ Prayer prayer = Prayer.values()[index];
+ Widget prayerWidget = prayerWidgets.get(prayer.ordinal());
+
+ if (!config.showPrayers() && !config.showEagleMystic())
+ prayerWidget.setHidden(false);
+
+ if (config.showPrayers()) {
+ prayerWidget.setHidden(true);
+ prayerWidgets.get(Prayer.values()[10].ordinal()).setHidden(false);// protect item
+ prayerWidgets.get(Prayer.values()[16].ordinal()).setHidden(false);// mage
+ prayerWidgets.get(Prayer.values()[17].ordinal()).setHidden(false);// range
+ prayerWidgets.get(Prayer.values()[18].ordinal()).setHidden(false);// melee
+ prayerWidgets.get(Prayer.values()[23].ordinal()).setHidden(false);// smite
+ if (config.showEagleMystic()) {
+ prayerWidgets.get(Prayer.values()[27].ordinal()).setHidden(true);// rigour
+ prayerWidgets.get(Prayer.values()[28].ordinal()).setHidden(true);// augury
+ } else {
+ prayerWidgets.get(Prayer.values()[27].ordinal()).setHidden(false);// rigour
+ prayerWidgets.get(Prayer.values()[28].ordinal()).setHidden(false);// augury
+ }
+ if (config.showUltStrength()) {
+ prayerWidgets.get(Prayer.values()[26].ordinal()).setHidden(true);// piety
+ } else {
+ prayerWidgets.get(Prayer.values()[26].ordinal()).setHidden(false);// piety
+ }
+ }
+ if (config.showEagleMystic()) {
+ prayerWidget.setHidden(true);
+ prayerWidgets.get(Prayer.values()[19].ordinal()).setHidden(false);// eagle
+ prayerWidgets.get(Prayer.values()[20].ordinal()).setHidden(false);// mystic
+ prayerWidgets.get(Prayer.values()[27].ordinal()).setHidden(true);// rigour
+ prayerWidgets.get(Prayer.values()[28].ordinal()).setHidden(true);// augury
+ }
+ if (config.showUltStrength()) {
+ prayerWidget.setHidden(true);
+ prayerWidgets.get(Prayer.values()[14].ordinal()).setHidden(false);// Ult Strength
+ prayerWidgets.get(Prayer.values()[26].ordinal()).setHidden(true);// piety
+ }
+
+ }
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hideprayers/PrayerTabState.java b/runelite-client/src/main/java/net/runelite/client/plugins/hideprayers/PrayerTabState.java
new file mode 100644
index 0000000000..699300f8a9
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/hideprayers/PrayerTabState.java
@@ -0,0 +1,8 @@
+package net.runelite.client.plugins.hideprayers;
+
+public enum PrayerTabState
+{
+ NONE,
+ PRAYERS,
+ QUICK_PRAYERS
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraConfig.java
new file mode 100644
index 0000000000..8c3d8467b8
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraConfig.java
@@ -0,0 +1,41 @@
+package net.runelite.client.plugins.hydra;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("hydra")
+public interface HydraConfig extends Config {
+ @ConfigItem(
+ position = 0,
+ keyName = "hydraenable",
+ name = "Enable Hydra (194 cb) Helper",
+ description = "Configures whether or not to enable Hydra Helper. (For use on regular hydra's only, will not work with Alchemical Hydra)."
+ )
+ default boolean EnableHydra() { return true; }
+
+ @ConfigItem(
+ position = 1,
+ keyName = "textindicators",
+ name = "Text Indicator",
+ description = "Configures if text indicator is shown above hydra's or not."
+ )
+ default boolean TextIndicator() { return true; }
+
+ @ConfigItem(
+ position = 2,
+ keyName = "countersize",
+ name = "Bold indicator",
+ description = "Configures if text indicator is bold or not."
+ )
+ default boolean BoldText() { return false; }
+
+ @ConfigItem(
+ position = 3,
+ keyName = "prayerhelper",
+ name = "Prayer Helper",
+ description = "Configures if prayer helper is shown or not."
+ )
+ default boolean PrayerHelper() { return true; }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraIndicatorOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraIndicatorOverlay.java
new file mode 100644
index 0000000000..6c38c81b3f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraIndicatorOverlay.java
@@ -0,0 +1,52 @@
+package net.runelite.client.plugins.hydra;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import javax.inject.Inject;
+
+import net.runelite.api.*;
+import net.runelite.api.Point;
+import net.runelite.client.game.SpriteManager;
+import net.runelite.client.ui.overlay.*;
+import net.runelite.client.ui.overlay.components.ComponentConstants;
+import net.runelite.client.ui.overlay.components.ImageComponent;
+import net.runelite.client.ui.overlay.components.LineComponent;
+import net.runelite.client.ui.overlay.components.PanelComponent;
+
+import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG;
+import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE;
+
+public class HydraIndicatorOverlay extends Overlay {
+ private final HydraConfig config;
+ private final HydraPlugin plugin;
+
+ private final PanelComponent panelComponent = new PanelComponent();
+
+ @Inject
+ private HydraIndicatorOverlay(HydraConfig config, HydraPlugin plugin) {
+ this.config = config;
+ this.plugin = plugin;
+ setPosition(OverlayPosition.BOTTOM_RIGHT);
+ setPriority(OverlayPriority.MED);
+ panelComponent.setPreferredSize(new Dimension(14, 0));
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics) {
+ if (!config.PrayerHelper()) {
+ return null;
+ }
+
+ if (plugin.Hydra != null) {
+ if (plugin.hydras.containsKey(plugin.Hydra.getIndex())) {
+ int val = plugin.hydras.get(plugin.Hydra.getIndex());
+ if (val != 0) {
+ panelComponent.getChildren().clear();
+ panelComponent.getChildren().add(LineComponent.builder().right(Integer.toString(val)).build());
+ return panelComponent.render(graphics);
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraOverlay.java
new file mode 100644
index 0000000000..b499227ec2
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraOverlay.java
@@ -0,0 +1,76 @@
+package net.runelite.client.plugins.hydra;
+
+import java.awt.*;
+import javax.inject.Inject;
+
+import net.runelite.api.*;
+import net.runelite.api.Point;
+import net.runelite.client.ui.FontManager;
+import net.runelite.client.ui.overlay.*;
+import net.runelite.client.ui.overlay.components.PanelComponent;
+
+public class HydraOverlay extends Overlay {
+ private final HydraConfig config;
+ private final HydraPlugin plugin;
+ private final PanelComponent panelComponent = new PanelComponent();
+
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private HydraOverlay(HydraConfig config, HydraPlugin plugin) {
+ this.config = config;
+ this.plugin = plugin;
+ setLayer(OverlayLayer.ABOVE_SCENE);
+ setPosition(OverlayPosition.DYNAMIC);
+ setPriority(OverlayPriority.MED);
+ panelComponent.setPreferredSize(new Dimension(150, 0));
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics) {
+ if (!config.TextIndicator()) {
+ return null;
+ }
+
+ for (NPC hydra : client.getNpcs()) {
+ if (hydra == null || hydra.getName() == null) {
+ continue;
+ }
+ if (hydra.getName().equalsIgnoreCase("Hydra")) {
+ if (plugin.hydras.containsKey(hydra.getIndex())) {
+ int val = plugin.hydras.get(hydra.getIndex());
+ if (val != 0) {
+ if (config.BoldText()) {
+ graphics.setFont(FontManager.getRunescapeBoldFont());
+ }
+ if (plugin.hydraattacks.containsKey(hydra.getIndex())) {
+ int attack = plugin.hydraattacks.get(hydra.getIndex());
+ if (attack == 8261) {
+ if (val == 3) {
+ OverlayUtil.renderTextLocation(graphics, hydra.getCanvasTextLocation(graphics, "MAGE", hydra.getLogicalHeight() + 100), "MAGE", Color.BLUE);
+ } else {
+ OverlayUtil.renderTextLocation(graphics, hydra.getCanvasTextLocation(graphics, "RANGE", hydra.getLogicalHeight() + 100), "RANGE", Color.GREEN);
+ }
+ } else if (attack == 8262) {
+ if (val == 3) {
+ OverlayUtil.renderTextLocation(graphics, hydra.getCanvasTextLocation(graphics, "RANGE", hydra.getLogicalHeight() + 100), "RANGE", Color.GREEN);
+ } else {
+ OverlayUtil.renderTextLocation(graphics, hydra.getCanvasTextLocation(graphics, "MAGE", hydra.getLogicalHeight() + 100), "MAGE", Color.BLUE);
+ }
+ }
+ }
+ Point runelitepleaseexplainwhyineedtocheckthisfornullinsteadoftheentirehydravariablethisshitcostmelikeanhourofmylifeandiblameyouadam = hydra.getCanvasTextLocation(graphics, Integer.toString(val), hydra.getLogicalHeight() + 40);
+ if (runelitepleaseexplainwhyineedtocheckthisfornullinsteadoftheentirehydravariablethisshitcostmelikeanhourofmylifeandiblameyouadam != null) {
+ OverlayUtil.renderTextLocation(graphics, runelitepleaseexplainwhyineedtocheckthisfornullinsteadoftheentirehydravariablethisshitcostmelikeanhourofmylifeandiblameyouadam, Integer.toString(val), Color.WHITE);
+ }
+ }
+ }
+ }
+
+ }
+ graphics.setFont(FontManager.getRunescapeFont());
+ return null;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraPlugin.java
new file mode 100644
index 0000000000..ba8c0317a7
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraPlugin.java
@@ -0,0 +1,145 @@
+package net.runelite.client.plugins.hydra;
+
+import net.runelite.api.events.*;
+import net.runelite.client.eventbus.Subscribe;
+import com.google.inject.Provides;
+import javax.inject.Inject;
+import net.runelite.api.*;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.game.SpriteManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@PluginDescriptor(
+ name = "Hydra",
+ description = "Hydra Helper",
+ tags = {"Hydra", "Helper"}
+)
+public class HydraPlugin extends Plugin
+{
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private HydraConfig config;
+
+ @Inject
+ private HydraOverlay HydraOverlay;
+
+ @Inject
+ private HydraPrayOverlay HydraPrayOverlay;
+
+ @Inject
+ private HydraIndicatorOverlay HydraIndicatorOverlay;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private SpriteManager spriteManager;
+
+ @Provides
+ HydraConfig provideConfig(ConfigManager configManager) {
+ return configManager.getConfig(HydraConfig.class);
+ }
+
+ Map hydras = new HashMap<>();
+ Map hydraattacks = new HashMap<>();
+ NPC Hydra;
+
+ @Override
+ protected void startUp() throws Exception {
+ overlayManager.add(HydraOverlay);
+ overlayManager.add(HydraPrayOverlay);
+ overlayManager.add(HydraIndicatorOverlay);
+ }
+
+ @Override
+ protected void shutDown() throws Exception {
+ overlayManager.remove(HydraOverlay);
+ overlayManager.remove(HydraPrayOverlay);
+ overlayManager.remove(HydraIndicatorOverlay);
+ hydras.clear();
+ hydraattacks.clear();
+ }
+
+ @Subscribe
+ public void onNpcSpawned(NpcSpawned event) {
+ if (!config.EnableHydra()) {
+ return;
+ }
+ NPC hydra = event.getNpc();
+ if (hydra.getCombatLevel() != 0 && hydra.getName() != null) {
+ if (hydra.getName().equalsIgnoreCase("Hydra")) {
+ if (!hydras.containsKey(hydra.getIndex())) {
+ hydras.put(hydra.getIndex(), 3);
+ }
+ }
+ }
+ }
+
+ @Subscribe
+ public void onNpcDespawned(NpcDespawned event) {
+ if (!config.EnableHydra()) {
+ return;
+ }
+ NPC hydra = event.getNpc();
+ if (hydra.getCombatLevel() != 0 && hydra.getName() != null) {
+ if (hydra.getName().equalsIgnoreCase("Hydra")) {
+ if (hydras.containsKey(hydra.getIndex())) {
+ hydras.remove(hydra.getIndex());
+ }
+ if (hydraattacks.containsKey(hydra.getIndex())) {
+ hydraattacks.remove(hydra.getIndex());
+ }
+ }
+ }
+ }
+
+ @Subscribe
+ public void onAnimationChanged(AnimationChanged event) {
+ Actor monster = event.getActor();
+ Actor local = client.getLocalPlayer();
+ if (monster instanceof NPC) {
+ NPC hydra = (NPC) monster;
+ if (hydra.getCombatLevel() != 0 && hydra.getName() != null) {
+ if (hydra.getName().equalsIgnoreCase("Hydra")) {
+ if (hydras.containsKey(hydra.getIndex())) {
+ if (hydra.getAnimation() == 8261 || hydra.getAnimation() == 8262) {
+ if (hydra.getInteracting().equals(local)) {
+ Hydra = hydra;
+ }
+ if (hydraattacks.containsKey(hydra.getIndex())) {
+ int lastattack = hydraattacks.get(hydra.getIndex());
+ hydraattacks.replace(hydra.getIndex(), hydra.getAnimation());
+
+ if (lastattack != hydra.getAnimation()) {
+ hydras.replace(hydra.getIndex(), 2);
+ } else {
+ int currval = hydras.get(hydra.getIndex());
+ if (currval == 1) {
+ hydras.replace(hydra.getIndex(), 3);
+ } else {
+ hydras.replace(hydra.getIndex(), currval - 1);
+ }
+ }
+ } else {
+ hydraattacks.put(hydra.getIndex(), hydra.getAnimation());
+ int currval = hydras.get(hydra.getIndex());
+ if (currval == 1) {
+ hydras.replace(hydra.getIndex(), 3);
+ } else {
+ hydras.replace(hydra.getIndex(), currval - 1);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraPrayOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraPrayOverlay.java
new file mode 100644
index 0000000000..47a7657667
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/hydra/HydraPrayOverlay.java
@@ -0,0 +1,100 @@
+package net.runelite.client.plugins.hydra;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import javax.inject.Inject;
+
+import net.runelite.api.*;
+import net.runelite.api.Point;
+import net.runelite.client.game.SpriteManager;
+import net.runelite.client.ui.overlay.*;
+import net.runelite.client.ui.overlay.components.ComponentConstants;
+import net.runelite.client.ui.overlay.components.ImageComponent;
+import net.runelite.client.ui.overlay.components.PanelComponent;
+
+public class HydraPrayOverlay extends Overlay {
+ private final HydraConfig config;
+ private final HydraPlugin plugin;
+
+ private static final Color NOT_ACTIVATED_BACKGROUND_COLOR = new Color(150, 0, 0, 150);
+
+ private final SpriteManager spriteManager;
+ private final PanelComponent imagePanelComponent = new PanelComponent();
+
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private HydraPrayOverlay(HydraConfig config, HydraPlugin plugin, SpriteManager spriteManager) {
+ this.config = config;
+ this.plugin = plugin;
+ setPosition(OverlayPosition.BOTTOM_RIGHT);
+ setPriority(OverlayPriority.HIGH);
+ this.spriteManager = spriteManager;
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics) {
+ if (!config.PrayerHelper()) {
+ return null;
+ }
+
+ if (plugin.Hydra != null) {
+ if (plugin.hydras.containsKey(plugin.Hydra.getIndex())) {
+ int val = plugin.hydras.get(plugin.Hydra.getIndex());
+ if (val != 0) {
+ if (plugin.hydraattacks.containsKey(plugin.Hydra.getIndex())) {
+ int attack = plugin.hydraattacks.get(plugin.Hydra.getIndex());
+ if (attack == 8261) {
+ if (val == 3) {
+ final BufferedImage prayerImage = spriteManager.getSprite(SpriteID.PRAYER_PROTECT_FROM_MAGIC, 0);
+
+ imagePanelComponent.getChildren().clear();
+ imagePanelComponent.getChildren().add(new ImageComponent(prayerImage));
+ imagePanelComponent.setBackgroundColor(client.isPrayerActive(Prayer.PROTECT_FROM_MAGIC)
+ ? ComponentConstants.STANDARD_BACKGROUND_COLOR
+ : NOT_ACTIVATED_BACKGROUND_COLOR);
+
+ return imagePanelComponent.render(graphics);
+ } else {
+ final BufferedImage prayerImage = spriteManager.getSprite(SpriteID.PRAYER_PROTECT_FROM_MISSILES, 0);
+
+ imagePanelComponent.getChildren().clear();
+ imagePanelComponent.getChildren().add(new ImageComponent(prayerImage));
+ imagePanelComponent.setBackgroundColor(client.isPrayerActive(Prayer.PROTECT_FROM_MISSILES)
+ ? ComponentConstants.STANDARD_BACKGROUND_COLOR
+ : NOT_ACTIVATED_BACKGROUND_COLOR);
+
+ return imagePanelComponent.render(graphics);
+ }
+ } else if (attack == 8262) {
+ if (val == 3) {
+ final BufferedImage prayerImage = spriteManager.getSprite(SpriteID.PRAYER_PROTECT_FROM_MISSILES, 0);
+
+ imagePanelComponent.getChildren().clear();
+ imagePanelComponent.getChildren().add(new ImageComponent(prayerImage));
+ imagePanelComponent.setBackgroundColor(client.isPrayerActive(Prayer.PROTECT_FROM_MISSILES)
+ ? ComponentConstants.STANDARD_BACKGROUND_COLOR
+ : NOT_ACTIVATED_BACKGROUND_COLOR);
+
+ return imagePanelComponent.render(graphics);
+ } else {
+ final BufferedImage prayerImage = spriteManager.getSprite(SpriteID.PRAYER_PROTECT_FROM_MAGIC, 0);
+
+ imagePanelComponent.getChildren().clear();
+ imagePanelComponent.getChildren().add(new ImageComponent(prayerImage));
+ imagePanelComponent.setBackgroundColor(client.isPrayerActive(Prayer.PROTECT_FROM_MAGIC)
+ ? ComponentConstants.STANDARD_BACKGROUND_COLOR
+ : NOT_ACTIVATED_BACKGROUND_COLOR);
+
+ return imagePanelComponent.render(graphics);
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetup.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetup.java
new file mode 100644
index 0000000000..043d22a30c
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetup.java
@@ -0,0 +1,15 @@
+package net.runelite.client.plugins.inventorysetups;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.ArrayList;
+
+@AllArgsConstructor
+public class InventorySetup
+{
+ @Getter
+ private ArrayList inventory;
+ @Getter
+ private ArrayList equipment;
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupBankOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupBankOverlay.java
new file mode 100644
index 0000000000..34ba341fc7
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupBankOverlay.java
@@ -0,0 +1,113 @@
+package net.runelite.client.plugins.inventorysetups;
+
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.Point;
+import net.runelite.api.Query;
+import net.runelite.api.SpritePixels;
+import net.runelite.api.queries.BankItemQuery;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.api.widgets.WidgetItem;
+import net.runelite.client.ui.FontManager;
+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.OverlayPriority;
+import net.runelite.client.util.QueryRunner;
+
+import javax.inject.Inject;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.util.Arrays;
+import java.util.Objects;
+
+@Slf4j
+public class InventorySetupBankOverlay extends Overlay
+{
+ private final Client client;
+ private final QueryRunner queryRunner;
+ private final InventorySetupPlugin plugin;
+ private final InventorySetupConfig config;
+
+ @Inject
+ public InventorySetupBankOverlay(Client client, QueryRunner queryRunner, InventorySetupPlugin plugin, InventorySetupConfig config)
+ {
+ setPosition(OverlayPosition.DYNAMIC);
+ setPriority(OverlayPriority.LOW);
+ setLayer(OverlayLayer.ABOVE_WIDGETS);
+ this.client = client;
+ this.queryRunner = queryRunner;
+ this.plugin = plugin;
+ this.config = config;
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if (config.getBankHighlight())
+ {
+ int[] ids = plugin.getCurrentInventorySetupIds();
+ if (ids == null)
+ {
+ return null;
+ }
+ ids = Arrays.stream(ids)
+ .filter(Objects::nonNull)
+ .filter(id -> id != -1)
+ .toArray();
+ final Query query = new BankItemQuery().idEquals(ids);
+ final WidgetItem[] widgetItems = queryRunner.runQuery(query);
+ final Widget bankContainer = client.getWidget(WidgetInfo.BANK_CONTAINER);
+ for (final WidgetItem item : widgetItems)
+ {
+ Point canvasLocation = item.getCanvasLocation();
+ Rectangle canvasBounds = item.getCanvasBounds();
+ Point windowLocation = bankContainer.getCanvasLocation();
+
+ if (canvasLocation == null || windowLocation == null)
+ {
+ return null;
+ }
+
+ if (!(canvasLocation.getY() + 60 >= windowLocation.getY() + bankContainer.getHeight()) && !(canvasLocation.getY() + canvasBounds.getHeight() <= windowLocation.getY() + 90))
+ {
+ final Color color = config.getBankHighlightColor();
+
+ if (color != null)
+ {
+ final BufferedImage outline = loadItemOutline(item.getId(), item.getQuantity(), color);
+ graphics.drawImage(outline, item.getCanvasLocation().getX() + 1, item.getCanvasLocation().getY() + 1, null);
+ if (item.getQuantity() > 1)
+ {
+ drawQuantity(graphics, item, Color.YELLOW);
+ }
+ else if (item.getQuantity() == 0)
+ {
+ drawQuantity(graphics, item, Color.YELLOW.darker());
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private void drawQuantity(Graphics2D graphics, WidgetItem item, Color darker)
+ {
+ graphics.setColor(Color.BLACK);
+ graphics.drawString(String.valueOf(item.getQuantity()), item.getCanvasLocation().getX() + 2, item.getCanvasLocation().getY() + 11);
+ graphics.setColor(darker);
+ graphics.setFont(FontManager.getRunescapeSmallFont());
+ graphics.drawString(String.valueOf(item.getQuantity()), item.getCanvasLocation().getX() + 1, item.getCanvasLocation().getY() + 10);
+ }
+
+ private BufferedImage loadItemOutline(final int itemId, final int itemQuantity, final Color outlineColor)
+ {
+ final SpritePixels itemSprite = client.createItemSprite(itemId, itemQuantity, 2, 0, 0, true, 710);
+ return itemSprite.toBufferedOutline(outlineColor);
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupConfig.java
new file mode 100644
index 0000000000..edcc47cd9c
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupConfig.java
@@ -0,0 +1,84 @@
+package net.runelite.client.plugins.inventorysetups;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+import java.awt.Color;
+
+@ConfigGroup("inventorysetups")
+public interface InventorySetupConfig extends Config
+{
+ @ConfigItem(
+ keyName = "highlightDifferences",
+ name = "Highlight Differences",
+ description = "Highlight slots that don't match the selected setup",
+ position = 0
+ )
+
+ default boolean getHighlightDifferences()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "highlightDifferenceColor",
+ name = "Highlight Color",
+ description = "The color used to highlight differences between setups",
+ position = 1
+ )
+
+ default Color getHighlightColor()
+ {
+ return Color.RED;
+ }
+
+ @ConfigItem(
+ keyName = "stackDifference",
+ name = "Stack Difference",
+ description = "Differences between setups will be highlighted if the stack size is different",
+ position = 2
+ )
+
+ default boolean getStackDifference()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "variationDifference",
+ name = "Variation Difference",
+ description = "Variations of items (E.g., charged jewellery) will be counted as different",
+ position = 2
+ )
+
+ default boolean getVariationDifference()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "bankHighlight",
+ name = "Bank Highlight",
+ description = "Highlight setup items in bank",
+ position = 4
+ )
+
+ default boolean getBankHighlight()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "bankHighlightColor",
+ name = "Bank Highlight Color",
+ description = "The color used to highlight setup items in bank",
+ position = 5
+ )
+
+ default Color getBankHighlightColor()
+ {
+ return Color.RED;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupItem.java
new file mode 100644
index 0000000000..c1af4e68fd
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupItem.java
@@ -0,0 +1,15 @@
+package net.runelite.client.plugins.inventorysetups;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+public class InventorySetupItem
+{
+ @Getter
+ private final int id;
+ @Getter
+ private final String name;
+ @Getter
+ private final int quantity;
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupPlugin.java
new file mode 100644
index 0000000000..0830ec834a
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupPlugin.java
@@ -0,0 +1,402 @@
+package net.runelite.client.plugins.inventorysetups;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.inject.Provides;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.GameState;
+import net.runelite.api.InventoryID;
+import net.runelite.api.Item;
+import net.runelite.api.ItemComposition;
+import net.runelite.api.ItemContainer;
+import net.runelite.api.events.ConfigChanged;
+import net.runelite.api.events.GameStateChanged;
+import net.runelite.api.events.ItemContainerChanged;
+import net.runelite.client.callback.ClientThread;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.game.ItemVariationMapping;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.plugins.inventorysetups.ui.InventorySetupPluginPanel;
+import net.runelite.client.ui.ClientToolbar;
+import net.runelite.client.ui.NavigationButton;
+import net.runelite.client.ui.overlay.OverlayManager;
+import net.runelite.client.util.ImageUtil;
+
+import javax.inject.Inject;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+import java.awt.image.BufferedImage;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+@PluginDescriptor(
+ name = "Inventory Setups",
+ description = "Save inventory setups",
+ tags = { "items", "inventory", "setups"},
+ enabledByDefault = false
+)
+
+@Slf4j
+public class InventorySetupPlugin extends Plugin
+{
+
+ private static final String CONFIG_GROUP = "inventorysetups";
+ private static final String CONFIG_KEY = "setups";
+ private static final int NUM_INVENTORY_ITEMS = 28;
+ private static final int NUM_EQUIPMENT_ITEMS = 14;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private ItemManager itemManager;
+
+ @Inject
+ private InventorySetupBankOverlay overlay;
+
+ @Inject
+ private ClientToolbar clientToolbar;
+
+ @Inject
+ private InventorySetupConfig config;
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private ClientThread clientThread;
+
+ @Inject
+ private ConfigManager configManager;
+
+ private InventorySetupPluginPanel panel;
+
+ private HashMap inventorySetups;
+
+ private NavigationButton navButton;
+
+ private boolean highlightDifference;
+
+ @Override
+ public void startUp()
+ {
+ overlayManager.add(overlay);
+
+ panel = new InventorySetupPluginPanel(this, itemManager);
+
+ final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "inventorysetups_icon.png");
+
+ navButton = NavigationButton.builder()
+ .tooltip("Inventory Setups")
+ .icon(icon)
+ .priority(9)
+ .panel(panel)
+ .build();
+
+ clientToolbar.addNavigation(navButton);
+
+ // load all the inventory setups from the config file
+ clientThread.invokeLater(() ->
+ {
+ if (client.getGameState() != GameState.LOGIN_SCREEN)
+ {
+ return false;
+ }
+
+ loadConfig();
+ panel.showNoSetupsPanel();
+ return true;
+ });
+
+ }
+
+ public void addInventorySetup()
+ {
+ final String name = JOptionPane.showInputDialog(panel,
+ "Enter the name of this setup.",
+ "Add New Setup",
+ JOptionPane.PLAIN_MESSAGE);
+
+ // cancel button was clicked
+ if (name == null)
+ {
+ return;
+ }
+
+ if (name.isEmpty())
+ {
+ JOptionPane.showMessageDialog(panel,
+ "Invalid Setup Name",
+ "Names must not be empty.",
+ JOptionPane.PLAIN_MESSAGE);
+ return;
+ }
+
+ if (inventorySetups.containsKey(name))
+ {
+ String builder = "The setup " + name + " already exists. " +
+ "Would you like to replace it with the current setup?";
+ int confirm = JOptionPane.showConfirmDialog(panel,
+ builder,
+ "Warning",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.PLAIN_MESSAGE);
+
+ if (confirm == JOptionPane.CANCEL_OPTION)
+ {
+ return;
+ }
+
+ // delete the old setup, no need to ask for confirmation
+ // because the user confirmed above
+ removeInventorySetup(name, false);
+ }
+
+ clientThread.invoke(() ->
+ {
+ ArrayList inv = getNormalizedContainer(InventoryID.INVENTORY);
+ ArrayList eqp = getNormalizedContainer(InventoryID.EQUIPMENT);
+
+ final InventorySetup invSetup = new InventorySetup(inv, eqp);
+ SwingUtilities.invokeLater(() ->
+ {
+ inventorySetups.put(name, invSetup);
+ panel.addInventorySetup(name);
+ panel.setCurrentInventorySetup(name);
+
+ updateConfig();
+ });
+ });
+ }
+
+ public void removeInventorySetup(final String name, boolean askForConfirmation)
+ {
+ if (inventorySetups.containsKey(name))
+ {
+ int confirm = JOptionPane.YES_OPTION;
+
+ if (askForConfirmation)
+ {
+ confirm = JOptionPane.showConfirmDialog(panel,
+ "Are you sure you want to remove this setup?",
+ "Warning",
+ JOptionPane.YES_NO_OPTION,
+ JOptionPane.PLAIN_MESSAGE);
+ }
+
+ if (confirm == JOptionPane.YES_OPTION)
+ {
+ inventorySetups.remove(name);
+ panel.removeInventorySetup(name);
+ }
+
+ updateConfig();
+ }
+ }
+
+ public final InventorySetup getInventorySetup(final String name)
+ {
+ return inventorySetups.get(name);
+ }
+
+ @Provides
+ InventorySetupConfig provideConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(InventorySetupConfig.class);
+ }
+
+ @Subscribe
+ public void onConfigChanged(ConfigChanged event)
+ {
+ if (event.getGroup().equals(CONFIG_GROUP))
+ {
+ // only allow highlighting if the config is enabled and the player is logged in
+ highlightDifference = config.getHighlightDifferences() && client.getGameState() == GameState.LOGGED_IN;
+ final String setupName = panel.getSelectedInventorySetup();
+ if (highlightDifference && !setupName.isEmpty())
+ {
+ panel.setCurrentInventorySetup(setupName);
+ }
+ }
+ }
+
+ private void updateConfig()
+ {
+ if (inventorySetups.isEmpty())
+ {
+ configManager.unsetConfiguration(CONFIG_GROUP, CONFIG_KEY);
+ return;
+ }
+
+ final Gson gson = new Gson();
+ final String json = gson.toJson(inventorySetups);
+ configManager.setConfiguration(CONFIG_GROUP, CONFIG_KEY, json);
+ }
+
+ private void loadConfig()
+ {
+ // serialize the internal data structure from the json in the configuration
+ final String json = configManager.getConfiguration(CONFIG_GROUP, CONFIG_KEY);
+ if (json == null || json.isEmpty())
+ {
+ inventorySetups = new HashMap<>();
+ }
+ else
+ {
+ // TODO add last resort?, serialize exception just make empty map
+ final Gson gson = new Gson();
+ Type type = new TypeToken>()
+ {
+
+ }.getType();
+ inventorySetups = gson.fromJson(json, type);
+ }
+
+ for (final String key : inventorySetups.keySet())
+ {
+ panel.addInventorySetup(key);
+ }
+
+ highlightDifference = false;
+ }
+
+ @Subscribe
+ public void onItemContainerChanged(ItemContainerChanged event)
+ {
+
+ if (!highlightDifference || client.getGameState() != GameState.LOGGED_IN)
+ {
+ return;
+ }
+
+ // empty entry, no need to compare anything
+ final String selectedInventorySetup = panel.getSelectedInventorySetup();
+ if (selectedInventorySetup.isEmpty())
+ {
+ return;
+ }
+
+ // check to see that the container is the equipment or inventory
+ ItemContainer container = event.getItemContainer();
+
+ if (container == client.getItemContainer(InventoryID.INVENTORY))
+ {
+ ArrayList normContainer = getNormalizedContainer(InventoryID.INVENTORY);
+ final InventorySetup setup = inventorySetups.get(selectedInventorySetup);
+ panel.highlightDifferences(normContainer, setup, InventoryID.INVENTORY);
+ }
+ else if (container == client.getItemContainer(InventoryID.EQUIPMENT))
+ {
+ ArrayList normContainer = getNormalizedContainer(InventoryID.EQUIPMENT);
+ final InventorySetup setup = inventorySetups.get(selectedInventorySetup);
+ panel.highlightDifferences(normContainer, setup, InventoryID.EQUIPMENT);
+ }
+
+ }
+
+ @Subscribe
+ public void onGameStateChanged(GameStateChanged event)
+ {
+ switch (event.getGameState())
+ {
+ // set the highlighting off if login screen shows up
+ case LOGIN_SCREEN:
+ highlightDifference = false;
+ final String setupName = panel.getSelectedInventorySetup();
+ if (!setupName.isEmpty())
+ {
+ panel.setCurrentInventorySetup(setupName);
+ }
+ break;
+
+ // set highlighting
+ case LOGGED_IN:
+ highlightDifference = config.getHighlightDifferences();
+ break;
+ }
+ }
+
+ public ArrayList getNormalizedContainer(final InventoryID id)
+ {
+ assert id == InventoryID.INVENTORY || id == InventoryID.EQUIPMENT : "invalid inventory ID";
+
+ final ItemContainer container = client.getItemContainer(id);
+
+ ArrayList newContainer = new ArrayList<>();
+
+ Item[] items = null;
+ if (container != null)
+ {
+ items = container.getItems();
+ }
+
+ int size = id == InventoryID.INVENTORY ? NUM_INVENTORY_ITEMS : NUM_EQUIPMENT_ITEMS;
+
+ for (int i = 0; i < size; i++)
+ {
+ if (items == null || i >= items.length)
+ {
+ newContainer.add(new InventorySetupItem(-1, "", 0));
+ }
+ else
+ {
+ final Item item = items[i];
+ String itemName = "";
+ if (client.isClientThread())
+ {
+ itemName = itemManager.getItemComposition(item.getId()).getName();
+ }
+ newContainer.add(new InventorySetupItem(item.getId(), itemName, item.getQuantity()));
+ }
+ }
+
+ return newContainer;
+ }
+
+ public final InventorySetupConfig getConfig()
+ {
+ return config;
+ }
+
+ public boolean getHighlightDifference()
+ {
+ return highlightDifference;
+ }
+
+ @Override
+ public void shutDown()
+ {
+ overlayManager.remove(overlay);
+ clientToolbar.removeNavigation(navButton);
+ }
+
+ final int[] getCurrentInventorySetupIds()
+ {
+ InventorySetup setup = inventorySetups.get(panel.getSelectedInventorySetup());
+ if (setup == null)
+ {
+ return null;
+ }
+ ArrayList items = new ArrayList<>();
+ items.addAll(setup.getEquipment());
+ items.addAll(setup.getInventory());
+ ArrayList itemIds = new ArrayList<>();
+ for (InventorySetupItem item : items)
+ {
+ int id = item.getId();
+ ItemComposition itemComposition = itemManager.getItemComposition(id);
+ if (id > 0)
+ {
+ itemIds.add(ItemVariationMapping.map(id));
+ itemIds.add(itemComposition.getPlaceholderId());
+ }
+
+ }
+ return itemIds.stream().mapToInt(i -> i).toArray();
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupContainerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupContainerPanel.java
new file mode 100644
index 0000000000..d5eda3697f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupContainerPanel.java
@@ -0,0 +1,109 @@
+package net.runelite.client.plugins.inventorysetups.ui;
+
+import net.runelite.client.game.AsyncBufferedImage;
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.game.ItemVariationMapping;
+import net.runelite.client.plugins.inventorysetups.InventorySetupConfig;
+import net.runelite.client.plugins.inventorysetups.InventorySetupItem;
+import net.runelite.client.plugins.inventorysetups.InventorySetupPlugin;
+import net.runelite.client.ui.ColorScheme;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.util.ArrayList;
+
+public abstract class InventorySetupContainerPanel extends JPanel
+{
+
+ protected ItemManager itemManager;
+
+ private final InventorySetupPlugin plugin;
+
+ InventorySetupContainerPanel(final ItemManager itemManager, final InventorySetupPlugin plugin, String captionText)
+ {
+ this.itemManager = itemManager;
+ this.plugin = plugin;
+ JPanel containerPanel = new JPanel();
+
+ final JPanel containerSlotsPanel = new JPanel();
+
+ setupContainerPanel(containerSlotsPanel);
+
+ // caption
+ final JLabel caption = new JLabel(captionText);
+ caption.setHorizontalAlignment(JLabel.CENTER);
+ caption.setVerticalAlignment(JLabel.CENTER);
+
+ // panel that holds the caption and any other graphics
+ final JPanel captionPanel = new JPanel();
+ captionPanel.add(caption);
+
+ containerPanel.setLayout(new BorderLayout());
+ containerPanel.add(captionPanel, BorderLayout.NORTH);
+ containerPanel.add(containerSlotsPanel, BorderLayout.CENTER);
+
+ add(containerPanel);
+ }
+
+ void setContainerSlot(int index,
+ final InventorySetupSlot containerSlot,
+ final ArrayList items)
+ {
+ if (index >= items.size() || items.get(index).getId() == -1)
+ {
+ containerSlot.setImageLabel(null, null);
+ return;
+ }
+
+ int itemId = items.get(index).getId();
+ int quantity = items.get(index).getQuantity();
+ final String itemName = items.get(index).getName();
+ AsyncBufferedImage itemImg = itemManager.getImage(itemId, quantity, quantity > 1);
+ String toolTip = itemName;
+ if (quantity > 1)
+ {
+ toolTip += " (" + quantity + ")";
+ }
+ containerSlot.setImageLabel(toolTip, itemImg);
+ }
+
+ void highlightDifferentSlotColor(InventorySetupItem savedItem,
+ InventorySetupItem currItem,
+ final InventorySetupSlot containerSlot)
+ {
+ // important note: do not use item names for comparisons
+ // they are all empty to avoid clientThread usage when highlighting
+
+ final InventorySetupConfig config = plugin.getConfig();
+ final Color highlightColor = config.getHighlightColor();
+
+ if (config.getStackDifference() && currItem.getQuantity() != savedItem.getQuantity())
+ {
+ containerSlot.setBackground(highlightColor);
+ return;
+ }
+
+ int currId = currItem.getId();
+ int checkId = savedItem.getId();
+
+ if (!config.getVariationDifference())
+ {
+ currId = ItemVariationMapping.map(currId);
+ checkId = ItemVariationMapping.map(checkId);
+ }
+
+ if (currId != checkId)
+ {
+ containerSlot.setBackground(highlightColor);
+ return;
+ }
+
+ // set the color back to the original, because they match
+ containerSlot.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+ }
+
+ abstract public void setupContainerPanel(final JPanel containerSlotsPanel);
+
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupEquipmentPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupEquipmentPanel.java
new file mode 100644
index 0000000000..7e0fecfa2b
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupEquipmentPanel.java
@@ -0,0 +1,91 @@
+package net.runelite.client.plugins.inventorysetups.ui;
+
+import net.runelite.api.EquipmentInventorySlot;
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.plugins.inventorysetups.InventorySetup;
+import net.runelite.client.plugins.inventorysetups.InventorySetupItem;
+import net.runelite.client.plugins.inventorysetups.InventorySetupPlugin;
+import net.runelite.client.ui.ColorScheme;
+
+import javax.swing.JPanel;
+import java.awt.GridLayout;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class InventorySetupEquipmentPanel extends InventorySetupContainerPanel
+{
+ private HashMap equipmentSlots;
+
+ InventorySetupEquipmentPanel(final ItemManager itemManager, final InventorySetupPlugin plugin)
+ {
+ super(itemManager, plugin, "Equipment");
+ }
+
+ @Override
+ public void setupContainerPanel(final JPanel containerSlotsPanel)
+ {
+ this.equipmentSlots = new HashMap<>();
+ for (EquipmentInventorySlot slot : EquipmentInventorySlot.values())
+ {
+ equipmentSlots.put(slot, new InventorySetupSlot(ColorScheme.DARKER_GRAY_COLOR));
+ }
+
+ final GridLayout gridLayout = new GridLayout(5, 3, 1, 1);
+ containerSlotsPanel.setLayout(gridLayout);
+
+ // add the grid layouts, including invisible ones
+ containerSlotsPanel.add(new InventorySetupSlot(ColorScheme.DARK_GRAY_COLOR));
+ containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.HEAD));
+ containerSlotsPanel.add(new InventorySetupSlot(ColorScheme.DARK_GRAY_COLOR));
+ containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.CAPE));
+ containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.AMULET));
+ containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.AMMO));
+ containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.WEAPON));
+ containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.BODY));
+ containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.SHIELD));
+ containerSlotsPanel.add(new InventorySetupSlot(ColorScheme.DARK_GRAY_COLOR));
+ containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.LEGS));
+ containerSlotsPanel.add(new InventorySetupSlot(ColorScheme.DARK_GRAY_COLOR));
+ containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.GLOVES));
+ containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.BOOTS));
+ containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.RING));
+
+ }
+
+ void setEquipmentSetupSlots(final InventorySetup setup)
+ {
+ final ArrayList equipment = setup.getEquipment();
+
+ for (final EquipmentInventorySlot slot : EquipmentInventorySlot.values())
+ {
+ int i = slot.getSlotIdx();
+ super.setContainerSlot(i, equipmentSlots.get(slot), equipment);
+ }
+
+ validate();
+ repaint();
+
+ }
+
+ void highlightDifferences(final ArrayList currEquipment, final InventorySetup inventorySetup)
+ {
+ final ArrayList equipToCheck = inventorySetup.getEquipment();
+
+ assert currEquipment.size() == equipToCheck.size() : "size mismatch";
+
+ for (final EquipmentInventorySlot slot : EquipmentInventorySlot.values())
+ {
+
+ int slotIdx = slot.getSlotIdx();
+ super.highlightDifferentSlotColor(equipToCheck.get(slotIdx), currEquipment.get(slotIdx), equipmentSlots.get(slot));
+ }
+ }
+
+ void resetEquipmentSlotsColor()
+ {
+ for (final EquipmentInventorySlot slot : EquipmentInventorySlot.values())
+ {
+ equipmentSlots.get(slot).setBackground(ColorScheme.DARKER_GRAY_COLOR);
+ }
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupInventoryPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupInventoryPanel.java
new file mode 100644
index 0000000000..136f4603ea
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupInventoryPanel.java
@@ -0,0 +1,78 @@
+package net.runelite.client.plugins.inventorysetups.ui;
+
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.plugins.inventorysetups.InventorySetup;
+import net.runelite.client.plugins.inventorysetups.InventorySetupItem;
+import net.runelite.client.plugins.inventorysetups.InventorySetupPlugin;
+import net.runelite.client.ui.ColorScheme;
+
+import javax.swing.JPanel;
+import java.awt.GridLayout;
+import java.util.ArrayList;
+
+public class InventorySetupInventoryPanel extends InventorySetupContainerPanel
+{
+
+ private static final int ITEMS_PER_ROW = 4;
+ private static final int NUM_INVENTORY_ITEMS = 28;
+
+ private ArrayList inventorySlots;
+
+ InventorySetupInventoryPanel(final ItemManager itemManager, final InventorySetupPlugin plugin)
+ {
+ super(itemManager, plugin, "Inventory");
+ }
+
+
+ @Override
+ public void setupContainerPanel(final JPanel containerSlotsPanel)
+ {
+ this.inventorySlots = new ArrayList<>();
+ for (int i = 0; i < NUM_INVENTORY_ITEMS; i++)
+ {
+ inventorySlots.add(new InventorySetupSlot(ColorScheme.DARKER_GRAY_COLOR));
+ }
+
+ int numRows = (NUM_INVENTORY_ITEMS + ITEMS_PER_ROW - 1) / ITEMS_PER_ROW;
+ containerSlotsPanel.setLayout(new GridLayout(numRows, ITEMS_PER_ROW, 1, 1));
+ for (int i = 0; i < NUM_INVENTORY_ITEMS; i++)
+ {
+ containerSlotsPanel.add(inventorySlots.get(i));
+ }
+ }
+
+ void setInventorySetupSlots(final InventorySetup setup)
+ {
+ ArrayList inventory = setup.getInventory();
+
+ for (int i = 0; i < NUM_INVENTORY_ITEMS; i++)
+ {
+ super.setContainerSlot(i, inventorySlots.get(i), inventory);
+ }
+
+ validate();
+ repaint();
+
+ }
+
+ void highlightDifferentSlots(final ArrayList currInventory, final InventorySetup inventorySetup)
+ {
+
+ final ArrayList inventoryToCheck = inventorySetup.getInventory();
+
+ assert currInventory.size() == inventoryToCheck.size() : "size mismatch";
+
+ for (int i = 0; i < NUM_INVENTORY_ITEMS; i++)
+ {
+ super.highlightDifferentSlotColor(inventoryToCheck.get(i), currInventory.get(i), inventorySlots.get(i));
+ }
+ }
+
+ void resetInventorySlotsColor()
+ {
+ for (InventorySetupSlot inventorySlot : inventorySlots)
+ {
+ inventorySlot.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+ }
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupPluginPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupPluginPanel.java
new file mode 100644
index 0000000000..b1b88a02c7
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupPluginPanel.java
@@ -0,0 +1,287 @@
+package net.runelite.client.plugins.inventorysetups.ui;
+
+import net.runelite.api.InventoryID;
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.plugins.inventorysetups.InventorySetup;
+import net.runelite.client.plugins.inventorysetups.InventorySetupItem;
+import net.runelite.client.plugins.inventorysetups.InventorySetupPlugin;
+import net.runelite.client.ui.PluginPanel;
+import net.runelite.client.ui.components.PluginErrorPanel;
+import net.runelite.client.util.ImageUtil;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.border.EmptyBorder;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ItemEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+
+public class InventorySetupPluginPanel extends PluginPanel
+{
+
+ private static ImageIcon ADD_ICON;
+ private static ImageIcon ADD_HOVER_ICON;
+ private static ImageIcon REMOVE_ICON;
+ private static ImageIcon REMOVE_HOVER_ICON;
+
+ private final JPanel noSetupsPanel;
+ private final JPanel invEqPanel;
+
+ private final InventorySetupInventoryPanel invPanel;
+ private final InventorySetupEquipmentPanel eqpPanel;
+
+ private final JComboBox setupComboBox;
+
+ private final JLabel removeMarker;
+
+ private final InventorySetupPlugin plugin;
+
+ static
+ {
+ final BufferedImage addIcon = ImageUtil.getResourceStreamFromClass(InventorySetupPlugin.class, "add_icon.png");
+ ADD_ICON = new ImageIcon(addIcon);
+ ADD_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(addIcon, 0.53f));
+
+ final BufferedImage removeIcon = ImageUtil.getResourceStreamFromClass(InventorySetupPlugin.class, "remove_icon.png");
+ REMOVE_ICON = new ImageIcon(removeIcon);
+ REMOVE_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(removeIcon, 0.53f));
+ }
+
+ public InventorySetupPluginPanel(final InventorySetupPlugin plugin, final ItemManager itemManager)
+ {
+ super(false);
+ this.plugin = plugin;
+ this.removeMarker = new JLabel(REMOVE_ICON);
+ this.invPanel = new InventorySetupInventoryPanel(itemManager, plugin);
+ this.eqpPanel = new InventorySetupEquipmentPanel(itemManager, plugin);
+ this.noSetupsPanel = new JPanel();
+ this.invEqPanel = new JPanel();
+ this.setupComboBox = new JComboBox<>();
+
+ // setup the title
+ final JLabel addMarker = new JLabel(ADD_ICON);
+ final JLabel title = new JLabel();
+ title.setText("Inventory Setups");
+ title.setForeground(Color.WHITE);
+
+ // setup the add marker (+ sign in the top right)
+ addMarker.setToolTipText("Add a new inventory setup");
+ addMarker.addMouseListener(new MouseAdapter()
+ {
+ @Override
+ public void mouseClicked(MouseEvent e)
+ {
+ plugin.addInventorySetup();
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e)
+ {
+ addMarker.setIcon(ADD_HOVER_ICON);
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e)
+ {
+ addMarker.setIcon(ADD_ICON);
+ }
+ });
+
+ // setup the remove marker (X sign in the top right)
+ removeMarker.setToolTipText("Remove the current inventory setup");
+ removeMarker.addMouseListener(new MouseAdapter()
+ {
+ @Override
+ public void mouseClicked(MouseEvent e)
+ {
+ final String name = (String)setupComboBox.getSelectedItem();
+ plugin.removeInventorySetup(name, true);
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e)
+ {
+ if (removeMarker.isEnabled())
+ {
+ removeMarker.setIcon(REMOVE_HOVER_ICON);
+ }
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e)
+ {
+ removeMarker.setIcon(REMOVE_ICON);
+ }
+ });
+
+ // setup the combo box for selection switching
+ // add empty to indicate the empty position
+ setupComboBox.addItem("");
+ setupComboBox.setSelectedIndex(0);
+ setupComboBox.addItemListener(e ->
+ {
+ if (e.getStateChange() == ItemEvent.SELECTED)
+ {
+ String selection = (String)e.getItem();
+ setCurrentInventorySetup(selection);
+ }
+ });
+
+ // the panel on the top right that holds the add and delete buttons
+ final JPanel markersPanel = new JPanel();
+ markersPanel.setLayout(new FlowLayout(FlowLayout.RIGHT, 10, 0));
+ markersPanel.add(removeMarker);
+ markersPanel.add(addMarker);
+
+ // the top panel that has the title and the buttons
+ final JPanel titleAndMarkersPanel = new JPanel();
+ titleAndMarkersPanel.setLayout(new BorderLayout());
+ titleAndMarkersPanel.add(title, BorderLayout.WEST);
+ titleAndMarkersPanel.add(markersPanel, BorderLayout.EAST);
+
+ // the panel that stays at the top and doesn't scroll
+ // contains the title, buttons, and the combo box
+ final JPanel northAnchoredPanel = new JPanel();
+ northAnchoredPanel.setLayout(new BoxLayout(northAnchoredPanel, BoxLayout.Y_AXIS));
+ northAnchoredPanel.setBorder(new EmptyBorder(0, 0, 10, 0));
+ northAnchoredPanel.add(titleAndMarkersPanel);
+ northAnchoredPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+ northAnchoredPanel.add(setupComboBox);
+
+ // the panel that holds the inventory and equipment panels
+ final BoxLayout invEqLayout = new BoxLayout(invEqPanel, BoxLayout.Y_AXIS);
+ invEqPanel.setLayout(invEqLayout);
+ invEqPanel.add(invPanel);
+ invEqPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+ invEqPanel.add(eqpPanel);
+
+ // setup the error panel. It's wrapped around a normal panel
+ // so it doesn't stretch to fill the parent panel
+ final PluginErrorPanel errorPanel = new PluginErrorPanel();
+ errorPanel.setContent("Inventory Setups", "Select or create an inventory setup.");
+ noSetupsPanel.add(errorPanel);
+
+ // the panel that holds the inventory panels, and the error panel
+ final JPanel contentPanel = new JPanel();
+ final BoxLayout contentLayout = new BoxLayout(contentPanel, BoxLayout.Y_AXIS);
+ contentPanel.setLayout(contentLayout);
+ contentPanel.add(invEqPanel);
+ contentPanel.add(noSetupsPanel);
+
+ // wrapper for the main content panel to keep it from stretching
+ final JPanel contentWrapper = new JPanel(new BorderLayout());
+ contentWrapper.add(Box.createGlue(), BorderLayout.CENTER);
+ contentWrapper.add(contentPanel, BorderLayout.NORTH);
+ final JScrollPane contentWrapperPane = new JScrollPane(contentWrapper);
+ contentWrapperPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+
+ setLayout(new BorderLayout());
+ setBorder(new EmptyBorder(10, 10, 10, 10));
+ add(northAnchoredPanel, BorderLayout.NORTH);
+ add(contentWrapperPane, BorderLayout.CENTER);
+
+ // show the no setups panel on startup
+ showNoSetupsPanel();
+
+ }
+
+ public void showNoSetupsPanel()
+ {
+ setupComboBox.setSelectedIndex(0);
+ removeMarker.setEnabled(false);
+ noSetupsPanel.setVisible(true);
+ invEqPanel.setVisible(false);
+ }
+
+ private void showHasSetupPanel(final String name)
+ {
+ setupComboBox.setSelectedItem(name);
+ removeMarker.setEnabled(true);
+ noSetupsPanel.setVisible(false);
+ invEqPanel.setVisible(true);
+ }
+
+ public void setCurrentInventorySetup(final String name)
+ {
+ if (name.isEmpty())
+ {
+ showNoSetupsPanel();
+ return;
+ }
+
+ showHasSetupPanel(name);
+
+ final InventorySetup inventorySetup = plugin.getInventorySetup(name);
+
+ invPanel.setInventorySetupSlots(inventorySetup);
+ eqpPanel.setEquipmentSetupSlots(inventorySetup);
+
+ if (plugin.getHighlightDifference())
+ {
+ final ArrayList normInv = plugin.getNormalizedContainer(InventoryID.INVENTORY);
+ final ArrayList normEqp = plugin.getNormalizedContainer(InventoryID.EQUIPMENT);
+
+ highlightDifferences(normInv, inventorySetup, InventoryID.INVENTORY);
+ highlightDifferences(normEqp, inventorySetup, InventoryID.EQUIPMENT);
+ }
+ else
+ {
+ invPanel.resetInventorySlotsColor();
+ eqpPanel.resetEquipmentSlotsColor();
+ }
+
+ validate();
+ repaint();
+ }
+
+ public void addInventorySetup(final String name)
+ {
+ setupComboBox.addItem(name);
+ }
+
+ public void removeInventorySetup(final String name)
+ {
+ setupComboBox.removeItem(name);
+ showNoSetupsPanel();
+
+ invPanel.resetInventorySlotsColor();
+ eqpPanel.resetEquipmentSlotsColor();
+
+ validate();
+ repaint();
+ }
+
+ public void highlightDifferences(final ArrayList container,
+ final InventorySetup setupToCheck,
+ final InventoryID type)
+ {
+ switch (type)
+ {
+ case INVENTORY:
+ invPanel.highlightDifferentSlots(container, setupToCheck);
+ break;
+
+ case EQUIPMENT:
+ eqpPanel.highlightDifferences(container, setupToCheck);
+ break;
+ }
+ }
+
+ public final String getSelectedInventorySetup()
+ {
+ return (String)setupComboBox.getSelectedItem();
+ }
+
+
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupSlot.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupSlot.java
new file mode 100644
index 0000000000..13bbbaef14
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupSlot.java
@@ -0,0 +1,45 @@
+package net.runelite.client.plugins.inventorysetups.ui;
+
+import net.runelite.client.game.AsyncBufferedImage;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+import java.awt.Color;
+import java.awt.Dimension;
+
+public class InventorySetupSlot extends JPanel
+{
+
+ private final JLabel imageLabel;
+
+ public InventorySetupSlot(Color color)
+ {
+ imageLabel = new JLabel();
+ imageLabel.setVerticalAlignment(SwingConstants.CENTER);
+ setPreferredSize(new Dimension(46, 42));
+ setBackground(color);
+ add(imageLabel);
+
+ }
+
+ public void setImageLabel(String toolTip, AsyncBufferedImage itemImage)
+ {
+ if (itemImage == null || toolTip == null)
+ {
+ imageLabel.setToolTipText("");
+ imageLabel.setIcon(null);
+ imageLabel.revalidate();
+ return;
+ }
+
+ imageLabel.setToolTipText(toolTip);
+ itemImage.addTo(imageLabel);
+
+ validate();
+ repaint();
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerConfig.java
new file mode 100644
index 0000000000..1dbc0f7901
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerConfig.java
@@ -0,0 +1,19 @@
+package net.runelite.client.plugins.inventoryviewer;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("inventoryviewer")
+public interface InventoryViewerConfig extends Config
+{
+ @ConfigItem(
+ keyName = "hideWhenInvOpen",
+ name = "Hide when inventory is open",
+ description = "Hide the inventory viewer when the player's inventory is open"
+ )
+ default boolean hideWhenInvOpen()
+ {
+ return false;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerOverlay.java
index 71da3492aa..bb59bd41b4 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerOverlay.java
@@ -34,6 +34,7 @@ import net.runelite.api.InventoryID;
import net.runelite.api.Item;
import net.runelite.api.ItemComposition;
import net.runelite.api.ItemContainer;
+import net.runelite.api.VarClientInt;
import net.runelite.client.game.ItemManager;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayPosition;
@@ -48,24 +49,32 @@ class InventoryViewerOverlay extends Overlay
private static final ImageComponent PLACEHOLDER_IMAGE = new ImageComponent(new BufferedImage(PLACEHOLDER_WIDTH, PLACEHOLDER_HEIGHT, BufferedImage.TYPE_4BYTE_ABGR));
private final Client client;
+ private final InventoryViewerConfig config;
private final ItemManager itemManager;
private final PanelComponent panelComponent = new PanelComponent();
@Inject
- private InventoryViewerOverlay(Client client, ItemManager itemManager)
+ private InventoryViewerOverlay(Client client, InventoryViewerConfig config, ItemManager itemManager)
{
setPosition(OverlayPosition.BOTTOM_RIGHT);
panelComponent.setWrapping(4);
panelComponent.setGap(new Point(6, 4));
panelComponent.setOrientation(PanelComponent.Orientation.HORIZONTAL);
- this.itemManager = itemManager;
this.client = client;
+ this.config = config;
+ this.itemManager = itemManager;
}
@Override
public Dimension render(Graphics2D graphics)
{
+ if (config.hideWhenInvOpen()
+ && client.getVar(VarClientInt.PLAYER_INVENTORY_OPENED) == 3)
+ {
+ return null;
+ }
+
final ItemContainer itemContainer = client.getItemContainer(InventoryID.INVENTORY);
if (itemContainer == null)
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerPlugin.java
index a9aa9f6453..01bc51dc33 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryviewer/InventoryViewerPlugin.java
@@ -25,6 +25,8 @@
package net.runelite.client.plugins.inventoryviewer;
import javax.inject.Inject;
+import com.google.inject.Provides;
+import net.runelite.client.config.ConfigManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.overlay.OverlayManager;
@@ -43,6 +45,12 @@ public class InventoryViewerPlugin extends Plugin
@Inject
private OverlayManager overlayManager;
+ @Provides
+ InventoryViewerConfig getConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(InventoryViewerConfig.class);
+ }
+
@Override
public void startUp()
{
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/kittennotifier/KittenNotifierConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/kittennotifier/KittenNotifierConfig.java
new file mode 100644
index 0000000000..e9d28f6b26
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/kittennotifier/KittenNotifierConfig.java
@@ -0,0 +1,27 @@
+package net.runelite.client.plugins.kittennotifier;
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("kittennotifier")
+public interface KittenNotifierConfig extends Config{
+ @ConfigItem(
+ keyName = "absolutelyNeeded",
+ name = "Notify only on Absolute Need",
+ description = "Only notify when kitten absolutely needs food or attention."
+ )
+ default boolean absolutelyNeeded() { return false; }
+ @ConfigItem(
+ keyName = "catOwned",
+ name = "",
+ description = "",
+ hidden = true
+ )
+ default boolean catOwned() { return false; }
+ @ConfigItem(
+ keyName = "catOwned",
+ name = "",
+ description = ""
+ )
+ void catOwned(Boolean bool);
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/kittennotifier/KittenNotifierPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/kittennotifier/KittenNotifierPlugin.java
new file mode 100644
index 0000000000..921dcd4a46
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/kittennotifier/KittenNotifierPlugin.java
@@ -0,0 +1,84 @@
+package net.runelite.client.plugins.kittennotifier;
+import com.google.inject.Provides;
+import net.runelite.api.ChatMessageType;
+import net.runelite.api.events.ChatMessage;
+import net.runelite.api.events.NpcActionChanged;
+import net.runelite.api.events.NpcSpawned;
+import net.runelite.client.Notifier;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.api.NPC;
+import net.runelite.api.Client;
+import javax.inject.Inject;
+
+@PluginDescriptor(
+ name = "!Kitten Notifier",
+ description = "Sends a notification when your kitten needs food, attention, or is grown.",
+ tags = {"kitten, notifications"}
+)
+public class KittenNotifierPlugin extends Plugin{
+ @Inject
+ private Notifier notifier;
+ @Inject
+ private KittenNotifierConfig config;
+ @Inject
+ private Client client;
+ @Provides
+ KittenNotifierConfig getConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(KittenNotifierConfig.class);
+ }
+ @Subscribe
+ public void onChatMessage(ChatMessage event) {
+ if (event.getType() == ChatMessageType.ENGINE && !config.catOwned()) {
+ if (!config.absolutelyNeeded()) {
+ if (event.getMessage().contentEquals("Your kitten is hungry.")) {
+ notifier.notify("Your kitten is hungry.");
+ }
+ if (event.getMessage().contentEquals("Your kitten wants attention.")) {
+ notifier.notify("Your kitten wants attention.");
+ }
+ }
+ if (event.getMessage().contentEquals("Your kitten is very hungry.")) {
+ notifier.notify("Your kitten is very hungry.");
+ }
+ if (event.getMessage().contentEquals("Your kitten really wants attention.")) {
+ notifier.notify("Your kitten really wants attention.");
+ }
+ }
+ }
+ @Subscribe
+ public void onNpcActionChanged(NpcActionChanged event) {
+ if (!config.catOwned()) {
+ for (NPC npc : client.getNpcs()) {
+ if (npc.getInteracting() != null) {
+ if (npc.getName().contentEquals("Cat") && !config.catOwned()) {
+ // If this if statement is included in previous it could null.
+ if (npc.getInteracting().getName().contentEquals(client.getLocalPlayer().getName())) {
+ config.catOwned(true);
+ notifier.notify("Your kitten has grown into a cat.");
+ }
+ }
+ }
+ }
+ }
+ }
+ @Subscribe
+ public void onNpcSpawned(NpcSpawned event) {
+ NPC cat = event.getNpc();
+ if (cat.getName() != null) {
+ if (cat.getName().equalsIgnoreCase("Kitten")) {
+ if (cat.getInteracting().getName().contentEquals(client.getLocalPlayer().getName())) {
+ config.catOwned(false);
+ }
+ }
+ else if (cat.getName().contentEquals("Cat")) {
+ if (cat.getInteracting().getName().equalsIgnoreCase(client.getLocalPlayer().getName())) {
+ config.catOwned(true);
+ }
+ }
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/lizardmenshaman/LizardmenShamanConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/lizardmenshaman/LizardmenShamanConfig.java
new file mode 100644
index 0000000000..33e61c0d46
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/lizardmenshaman/LizardmenShamanConfig.java
@@ -0,0 +1,32 @@
+package net.runelite.client.plugins.lizardmenshaman;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("shaman")
+public interface LizardmenShamanConfig extends Config
+{
+ @ConfigItem(
+ position = 1,
+ keyName = "showTimer",
+ name = "Show timer",
+ description = "Display timer till for lizardman shaman spawns."
+ )
+ default boolean showTimer()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ position = 2,
+ keyName = "notifyOnSpawn",
+ name = "Notify on spawn",
+ description = "Notify user when lizardman summons spawns."
+ )
+ default boolean notifyOnSpawn()
+ {
+ return true;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/lizardmenshaman/LizardmenShamanPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/lizardmenshaman/LizardmenShamanPlugin.java
new file mode 100644
index 0000000000..e6b3923fea
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/lizardmenshaman/LizardmenShamanPlugin.java
@@ -0,0 +1,91 @@
+package net.runelite.client.plugins.lizardmenshaman;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Provides;
+import javax.inject.Inject;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Actor;
+import net.runelite.api.Client;
+import net.runelite.api.coords.LocalPoint;
+import net.runelite.api.events.AnimationChanged;
+import net.runelite.client.Notifier;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import java.util.HashMap;
+import java.util.Map;
+import net.runelite.client.ui.overlay.OverlayManager;
+
+@PluginDescriptor(
+ name = "!Lizard Shamans",
+ description = "Configures timer for lizardmen shaman spawns.",
+ enabledByDefault = false,
+ tags = {"shaman", "lizard", "lizardmen"}
+)
+@Slf4j
+public class LizardmenShamanPlugin extends Plugin
+{
+ private static final String SHAMAN = "Lizardman shaman";
+ private static final String MESSAGE = "A Lizardman shaman has summoned his spawn!";
+
+ @Getter(AccessLevel.PACKAGE)
+ private final Map spawns = new HashMap<>();
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private ShamanSpawnOverlay overlay;
+
+ @Inject
+ private LizardmenShamanConfig config;
+
+ @Inject
+ private Notifier notifier;
+
+ @Inject
+ private Client client;
+
+ @Provides
+ LizardmenShamanConfig getConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(LizardmenShamanConfig.class);
+ }
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ overlayManager.add(overlay);
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ overlayManager.remove(overlay);
+ spawns.clear();
+ }
+
+ @Subscribe
+ public void onAnimationChanged(AnimationChanged event)
+ {
+ Actor actor = event.getActor();
+ if (actor == null || actor.getName() == null)
+ {
+ return;
+ }
+ else if (actor.getName().equals(SHAMAN) && actor.getAnimation() == 7157)
+ {
+ if (config.showTimer())
+ {
+ spawns.put(event.getActor().getLocalLocation(), new LizardmenShamanSpawn(8.4, null));
+ }
+
+ if (config.notifyOnSpawn())
+ {
+ notifier.notify(MESSAGE);
+ }
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/lizardmenshaman/LizardmenShamanSpawn.java b/runelite-client/src/main/java/net/runelite/client/plugins/lizardmenshaman/LizardmenShamanSpawn.java
new file mode 100644
index 0000000000..d4297ed624
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/lizardmenshaman/LizardmenShamanSpawn.java
@@ -0,0 +1,18 @@
+package net.runelite.client.plugins.lizardmenshaman;
+
+import java.time.Instant;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@RequiredArgsConstructor
+@AllArgsConstructor
+class LizardmenShamanSpawn
+{
+ private final Instant start = Instant.now();
+ private double countdownTimer;
+ private Instant end;
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/lizardmenshaman/ShamanSpawnOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/lizardmenshaman/ShamanSpawnOverlay.java
new file mode 100644
index 0000000000..4d363e72c4
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/lizardmenshaman/ShamanSpawnOverlay.java
@@ -0,0 +1,90 @@
+package net.runelite.client.plugins.lizardmenshaman;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.time.Duration;
+import java.time.Instant;
+import javax.inject.Inject;
+import net.runelite.api.Client;
+import net.runelite.api.Perspective;
+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.components.ProgressPieComponent;
+
+class ShamanSpawnOverlay extends Overlay
+{
+ private final Client client;
+ private final LizardmenShamanPlugin plugin;
+
+ @Inject
+ private ShamanSpawnOverlay(Client client, LizardmenShamanPlugin plugin)
+ {
+ setPosition(OverlayPosition.DYNAMIC);
+ setLayer(OverlayLayer.ABOVE_SCENE);
+ this.client = client;
+ this.plugin = plugin;
+ }
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ plugin.getSpawns().forEach((localPoint, spawn) ->
+ {
+ final Instant now = Instant.now();
+ final long startCountdown = Duration.between(spawn.getStart(), now).getSeconds();
+ final double certainSec = spawn.getCountdownTimer() - startCountdown;
+
+ if (certainSec <= 0)
+ {
+ if (spawn.getEnd() == null)
+ {
+ spawn.setEnd(Instant.now());
+ }
+ }
+
+ final ProgressPieComponent pieComponent = new ProgressPieComponent();
+ final Point loc = Perspective.localToCanvas(client, localPoint, client.getPlane());
+
+ if (loc == null || certainSec < 0)
+ {
+ return;
+ }
+
+ pieComponent.setPosition(loc);
+ pieComponent.setProgress(certainSec / spawn.getCountdownTimer());
+ if (certainSec > 4.8)
+ {
+ pieComponent.setFill(Color.GREEN);
+ pieComponent.setBorderColor(Color.GREEN);
+ pieComponent.render(graphics);
+ }
+ else if (certainSec > 3.6)
+ {
+ pieComponent.setFill(Color.YELLOW);
+ pieComponent.setBorderColor(Color.YELLOW);
+ pieComponent.render(graphics);
+ }
+ else if (certainSec > 2.4)
+ {
+ pieComponent.setFill(Color.ORANGE);
+ pieComponent.setBorderColor(Color.ORANGE);
+ pieComponent.render(graphics);
+ }
+ else if (certainSec > 1.2)
+ {
+ pieComponent.setFill(new Color(255, 140, 0));
+ pieComponent.setBorderColor(new Color(255, 140, 0));
+ pieComponent.render(graphics);
+ }
+ else
+ {
+ pieComponent.setFill(Color.RED);
+ pieComponent.setBorderColor(Color.RED);
+ pieComponent.render(graphics);
+ }
+ });
+ return null;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/lootingbagviewer/LootingBagViewerOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/lootingbagviewer/LootingBagViewerOverlay.java
new file mode 100644
index 0000000000..85a07780de
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/lootingbagviewer/LootingBagViewerOverlay.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2018 AWPH-I
+ * 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.lootingbagviewer;
+
+import net.runelite.api.Client;
+import net.runelite.api.InventoryID;
+import net.runelite.api.Item;
+import net.runelite.api.ItemContainer;
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.components.ImageComponent;
+import net.runelite.client.ui.overlay.components.PanelComponent;
+
+import javax.inject.Inject;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+class LootingBagViewerOverlay extends Overlay
+{
+ private static final int INVENTORY_SIZE = 28;
+ private static final int PLACEHOLDER_WIDTH = 36;
+ private static final int PLACEHOLDER_HEIGHT = 32;
+ private static final ImageComponent PLACEHOLDER_IMAGE = new ImageComponent(new BufferedImage(PLACEHOLDER_WIDTH, PLACEHOLDER_HEIGHT, BufferedImage.TYPE_4BYTE_ABGR));
+
+ private final Client client;
+ private final ItemManager itemManager;
+
+ private final PanelComponent panelComponent = new PanelComponent();
+
+ private ItemContainer itemContainer;
+ private Item[] items;
+
+ @Inject
+ private LootingBagViewerOverlay(Client client, ItemManager itemManager)
+ {
+ setPosition(OverlayPosition.BOTTOM_RIGHT);
+ panelComponent.setWrapping(4);
+ panelComponent.setGap(new Point(6, 4));
+ panelComponent.setOrientation(PanelComponent.Orientation.HORIZONTAL);
+ this.itemManager = itemManager;
+ this.client = client;
+
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if (itemContainer == null)
+ {
+ if(client.getItemContainer(InventoryID.LOOTING_BAG) != null) {
+ itemContainer = client.getItemContainer(InventoryID.LOOTING_BAG);
+ items = itemContainer.getItems();
+ }
+ return null;
+ }
+ else if(itemContainer != null && client.getItemContainer(InventoryID.LOOTING_BAG) != null)
+ {
+ itemContainer = client.getItemContainer(InventoryID.LOOTING_BAG);
+ Item[] tempItems = itemContainer.getItems();
+
+ for(int i = 0; i < items.length; i++)
+ {
+ if(!items[i].equals(tempItems[i]))
+ {
+ items = tempItems;
+ }
+ }
+ }
+
+ panelComponent.getChildren().clear();
+
+ for (int i = 0; i < INVENTORY_SIZE; i++)
+ {
+ if (i < items.length)
+ {
+ final Item item = items[i];
+ if (item.getQuantity() > 0)
+ {
+ final BufferedImage image = getImage(item);
+ if (image != null)
+ {
+ panelComponent.getChildren().add(new ImageComponent(image));
+ continue;
+ }
+ }
+ }
+
+ // put a placeholder image so each item is aligned properly and the panel is not resized
+ panelComponent.getChildren().add(PLACEHOLDER_IMAGE);
+ }
+
+ return panelComponent.render(graphics);
+ }
+
+ private BufferedImage getImage(Item item)
+ {
+ return itemManager.getImage(item.getId(), item.getQuantity(), item.getQuantity() > 1);
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/lootingbagviewer/LootingBagViewerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/lootingbagviewer/LootingBagViewerPlugin.java
new file mode 100644
index 0000000000..d9cfdd7bf9
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/lootingbagviewer/LootingBagViewerPlugin.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018 AWPH-I
+ * 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.lootingbagviewer;
+
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+
+import javax.inject.Inject;
+
+@PluginDescriptor(
+ name = "PvP Looting Bag Viewer",
+ description = "Add an overlay showing the contents of your looting bag",
+ tags = {"alternate", "items", "overlay", "second"},
+ enabledByDefault = false
+)
+public class LootingBagViewerPlugin extends Plugin
+{
+ @Inject
+ private net.runelite.client.plugins.lootingbagviewer.LootingBagViewerOverlay overlay;
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Override
+ public void startUp()
+ {
+ overlayManager.add(overlay);
+ }
+
+ @Override
+ public void shutDown()
+ {
+ overlayManager.remove(overlay);
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java
index 23ab63df16..e60824686c 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java
@@ -1,284 +1,308 @@
-/*
- * Copyright (c) 2018, 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.menuentryswapper;
-
-import net.runelite.client.config.Config;
-import net.runelite.client.config.ConfigGroup;
-import net.runelite.client.config.ConfigItem;
-
-@ConfigGroup("menuentryswapper")
-public interface MenuEntrySwapperConfig extends Config
-{
- @ConfigItem(
- position = -2,
- keyName = "shiftClickCustomization",
- name = "Customizable shift-click",
- description = "Allows customization of shift-clicks on items"
- )
- default boolean shiftClickCustomization()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapAdmire",
- name = "Admire",
- description = "Swap Admire with Teleport, Spellbook and Perks (max cape) for mounted skill capes."
- )
- default boolean swapAdmire()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapAssignment",
- name = "Assignment",
- description = "Swap Talk-to with Assignment for Slayer Masters. This will take priority over swapping Trade."
- )
- default boolean swapAssignment()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapBanker",
- name = "Bank",
- description = "Swap Talk-to with Bank on Bank NPC
Example: Banker"
- )
- default boolean swapBank()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapBirdhouseEmpty",
- name = "Birdhouse",
- description = "Swap Interact with Empty for birdhouses on Fossil Island"
- )
- default boolean swapBirdhouseEmpty()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapBones",
- name = "Bury",
- description = "Swap Bury with Use on Bones"
- )
- default boolean swapBones()
- {
- return false;
- }
-
- @ConfigItem(
- keyName = "swapContract",
- name = "Contract",
- description = "Swap Talk-to with Contract on Guildmaster Jane"
- )
- default boolean swapContract()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapChase",
- name = "Chase",
- description = "Allows to left click your cat to chase"
- )
- default boolean swapChase()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "claimSlime",
- name = "Claim Slime",
- description = "Swap Talk-to with Claim Slime from Morytania diaries"
- )
- default boolean claimSlime()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapDarkMage",
- name = "Repairs",
- description = "Swap Talk-to with Repairs for Dark Mage"
- )
- default boolean swapDarkMage()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapDecant",
- name = "Decant",
- description = "Swap Talk-to with Decant for Bob Barter and Murky Matt at the Grand Exchange."
- )
- default boolean swapDecant()
- {
- return false;
- }
-
- @ConfigItem(
- keyName = "swapExchange",
- name = "Exchange",
- description = "Swap Talk-to with Exchange on NPC
Example: Grand Exchange Clerk, Tool Leprechaun, Void Knight"
- )
- default boolean swapExchange()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapFairyRing",
- name = "Fairy ring",
- description = "Swap Zanaris with Last-destination or Configure on Fairy rings"
- )
- default FairyRingMode swapFairyRing()
- {
- return FairyRingMode.LAST_DESTINATION;
- }
-
- @ConfigItem(
- keyName = "swapHarpoon",
- name = "Harpoon",
- description = "Swap Cage, Big Net with Harpoon on Fishing spot"
- )
- default boolean swapHarpoon()
- {
- return false;
- }
-
- @ConfigItem(
- keyName = "swapHomePortal",
- name = "Home",
- description = "Swap Enter with Home or Build or Friend's house on Portal"
- )
- default HouseMode swapHomePortal()
- {
- return HouseMode.HOME;
- }
-
- @ConfigItem(
- keyName = "swapPickpocket",
- name = "Pickpocket on H.A.M.",
- description = "Swap Talk-to with Pickpocket on H.A.M members"
- )
- default boolean swapPickpocket()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapPay",
- name = "Pay",
- description = "Swap Talk-to with Pay on NPC
Example: Elstan, Heskel, Fayeth"
- )
- default boolean swapPay()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapPrivate",
- name = "Private",
- description = "Swap Shared with Private on the Chambers of Xeric storage units."
- )
- default boolean swapPrivate()
- {
- return false;
- }
-
- @ConfigItem(
- keyName = "swapPick",
- name = "Pick",
- description = "Swap Pick with Pick-lots of the Gourd tree in the Chambers of Xeric"
- )
- default boolean swapPick()
- {
- return false;
- }
-
- @ConfigItem(
- keyName = "swapQuick",
- name = "Quick Pass/Open/Start/Travel",
- description = "Swap Pass with Quick-Pass, Open with Quick-Open, Ring with Quick-Start and Talk-to with Quick-Travel"
- )
- default boolean swapQuick()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapBoxTrap",
- name = "Reset",
- description = "Swap Check with Reset on box trap"
- )
- default boolean swapBoxTrap()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapTeleportItem",
- name = "Teleport item",
- description = "Swap Wear, Wield with Rub, Teleport on teleport item
Example: Amulet of glory, Explorer's ring, Chronicle"
- )
- default boolean swapTeleportItem()
- {
- return false;
- }
-
- @ConfigItem(
- keyName = "swapAbyssTeleport",
- name = "Teleport to Abyss",
- description = "Swap Talk-to with Teleport for the Mage of Zamorak"
- )
- default boolean swapAbyssTeleport()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapTrade",
- name = "Trade",
- description = "Swap Talk-to with Trade on NPC
Example: Shop keeper, Shop assistant"
- )
- default boolean swapTrade()
- {
- return true;
- }
-
- @ConfigItem(
- keyName = "swapTravel",
- name = "Travel",
- description = "Swap Talk-to with Travel, Take-boat, Pay-fare, Charter on NPC
Example: Squire, Monk of Entrana, Customs officer, Trader Crewmember"
- )
- default boolean swapTravel()
- {
- return true;
- }
-}
+/*
+ * Copyright (c) 2018, 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.menuentryswapper;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("menuentryswapper")
+public interface MenuEntrySwapperConfig extends Config
+{
+ @ConfigItem(
+ position = -2,
+ keyName = "shiftClickCustomization",
+ name = "Customizable shift-click",
+ description = "Allows customization of shift-clicks on items"
+ )
+ default boolean shiftClickCustomization()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapAdmire",
+ name = "Admire",
+ description = "Swap Admire with Teleport, Spellbook and Perks (max cape) for mounted skill capes."
+ )
+ default boolean swapAdmire()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapAssignment",
+ name = "Assignment",
+ description = "Swap Talk-to with Assignment for Slayer Masters. This will take priority over swapping Trade."
+ )
+ default boolean swapAssignment()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapBanker",
+ name = "Bank",
+ description = "Swap Talk-to with Bank on Bank NPC
Example: Banker"
+ )
+ default boolean swapBank()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapBirdhouseEmpty",
+ name = "Birdhouse",
+ description = "Swap Interact with Empty for birdhouses on Fossil Island"
+ )
+ default boolean swapBirdhouseEmpty()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapBones",
+ name = "Bury",
+ description = "Swap Bury with Use on Bones"
+ )
+ default boolean swapBones()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "swapContract",
+ name = "Contract",
+ description = "Swap Talk-to with Contract on Guildmaster Jane"
+ )
+ default boolean swapContract()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapChase",
+ name = "Chase",
+ description = "Allows to left click your cat to chase"
+ )
+ default boolean swapChase()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "claimSlime",
+ name = "Claim Slime",
+ description = "Swap Talk-to with Claim Slime from Morytania diaries"
+ )
+ default boolean claimSlime()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapDarkMage",
+ name = "Repairs",
+ description = "Swap Talk-to with Repairs for Dark Mage"
+ )
+ default boolean swapDarkMage()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapDecant",
+ name = "Decant",
+ description = "Swap Talk-to with Decant for Bob Barter and Murky Matt at the Grand Exchange."
+ )
+ default boolean swapDecant()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "swapExchange",
+ name = "Exchange",
+ description = "Swap Talk-to with Exchange on NPC
Example: Grand Exchange Clerk, Tool Leprechaun, Void Knight"
+ )
+ default boolean swapExchange()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapFairyRing",
+ name = "Fairy ring",
+ description = "Swap Zanaris with Last-destination or Configure on Fairy rings"
+ )
+ default FairyRingMode swapFairyRing()
+ {
+ return FairyRingMode.LAST_DESTINATION;
+ }
+
+ @ConfigItem(
+ keyName = "swapHarpoon",
+ name = "Harpoon",
+ description = "Swap Cage, Big Net with Harpoon on Fishing spot"
+ )
+ default boolean swapHarpoon()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "swapHomePortal",
+ name = "Home",
+ description = "Swap Enter with Home or Build or Friend's house on Portal"
+ )
+ default HouseMode swapHomePortal()
+ {
+ return HouseMode.HOME;
+ }
+
+ @ConfigItem(
+ keyName = "swapPickpocket",
+ name = "Pickpocket on H.A.M.",
+ description = "Swap Talk-to with Pickpocket on H.A.M members"
+ )
+ default boolean swapPickpocket()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapBlackjack",
+ name = "Blackjacking",
+ description = "Requires \"The Fued\" quest completed."
+ )
+
+ default boolean swapBlackjack() {return false; }
+
+
+ @ConfigItem(
+ keyName = "setDelay",
+ name = "Blackjacking Delay",
+ description = "Sets the delay for how long you can pickpocket after knocking target out in milliseconds"
+ )
+ default int setDelay()
+ {
+ return 2000;
+ }
+
+
+ @ConfigItem(
+ keyName = "swapPay",
+ name = "Pay",
+ description = "Swap Talk-to with Pay on NPC
Example: Elstan, Heskel, Fayeth"
+ )
+
+
+
+
+ default boolean swapPay()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapPrivate",
+ name = "Private",
+ description = "Swap Shared with Private on the Chambers of Xeric storage units."
+ )
+ default boolean swapPrivate()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "swapPick",
+ name = "Pick",
+ description = "Swap Pick with Pick-lots of the Gourd tree in the Chambers of Xeric"
+ )
+ default boolean swapPick()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "swapQuick",
+ name = "Quick Pass/Open/Start/Travel",
+ description = "Swap Pass with Quick-Pass, Open with Quick-Open, Ring with Quick-Start and Talk-to with Quick-Travel"
+ )
+ default boolean swapQuick()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapBoxTrap",
+ name = "Reset",
+ description = "Swap Check with Reset on box trap"
+ )
+ default boolean swapBoxTrap()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapTeleportItem",
+ name = "Teleport item",
+ description = "Swap Wear, Wield with Rub, Teleport on teleport item
Example: Amulet of glory, Explorer's ring, Chronicle"
+ )
+ default boolean swapTeleportItem()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ keyName = "swapAbyssTeleport",
+ name = "Teleport to Abyss",
+ description = "Swap Talk-to with Teleport for the Mage of Zamorak"
+ )
+ default boolean swapAbyssTeleport()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapTrade",
+ name = "Trade",
+ description = "Swap Talk-to with Trade on NPC
Example: Shop keeper, Shop assistant"
+ )
+ default boolean swapTrade()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "swapTravel",
+ name = "Travel",
+ description = "Swap Talk-to with Travel, Take-boat, Pay-fare, Charter on NPC
Example: Squire, Monk of Entrana, Customs officer, Trader Crewmember"
+ )
+ default boolean swapTravel()
+ {
+ return true;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java
index b164d10b86..0ff5e23915 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java
@@ -31,19 +31,8 @@ import java.util.Set;
import javax.inject.Inject;
import lombok.Getter;
import lombok.Setter;
-import net.runelite.api.Client;
-import net.runelite.api.GameState;
-import net.runelite.api.ItemComposition;
-import net.runelite.api.MenuAction;
-import net.runelite.api.MenuEntry;
-import net.runelite.api.NPC;
-import net.runelite.api.events.ConfigChanged;
-import net.runelite.api.events.FocusChanged;
-import net.runelite.api.events.MenuEntryAdded;
-import net.runelite.api.events.MenuOpened;
-import net.runelite.api.events.MenuOptionClicked;
-import net.runelite.api.events.PostItemComposition;
-import net.runelite.api.events.WidgetMenuOptionClicked;
+import net.runelite.api.*;
+import net.runelite.api.events.*;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
@@ -59,7 +48,7 @@ import net.runelite.client.util.Text;
import org.apache.commons.lang3.ArrayUtils;
@PluginDescriptor(
- name = "Menu Entry Swapper",
+ name = "!Menu Entry Swapper",
description = "Change the default option that is displayed when hovering over objects",
tags = {"npcs", "inventory", "items", "objects"},
enabledByDefault = false
@@ -100,6 +89,9 @@ public class MenuEntrySwapperPlugin extends Plugin
MenuAction.NPC_FIFTH_OPTION,
MenuAction.EXAMINE_NPC);
+ private static long timeSinceKnockout;
+ private static long timeSinceAggro;
+
@Inject
private Client client;
@@ -371,6 +363,19 @@ public class MenuEntrySwapperPlugin extends Plugin
return;
}
+ if (config.swapBlackjack() && (target.contains("bandit") | target.contains("menaphite thug"))) {
+ Quest quest = Quest.THE_FEUD;
+ if (quest.getState(client) == QuestState.FINISHED) {
+ if (System.currentTimeMillis() < (timeSinceKnockout + config.setDelay())) {
+ swap("pickpocket", option, target, true);
+ }
+ if (System.currentTimeMillis() < (timeSinceAggro + 1300)) {
+ swap("pickpocket", option, target, true);
+ }
+ swap("knock-out", option, target, true);
+ }
+ }
+
if (option.equals("talk-to"))
{
if (config.swapPickpocket() && target.contains("h.a.m."))
@@ -593,6 +598,17 @@ public class MenuEntrySwapperPlugin extends Plugin
}
}
+ @Subscribe
+ public void onChatMessage(ChatMessage event) {
+ if (event.getType() == ChatMessageType.SPAM) {
+ if (event.getMessage().contains("ou smack the bandit over the head and render them unconsci")) {
+ timeSinceKnockout = System.currentTimeMillis();
+ } else if (event.getMessage().contains("our blow only glances off the bandi")) {
+ timeSinceAggro = System.currentTimeMillis();
+ }
+ }
+ }
+
private int searchIndex(MenuEntry[] entries, String option, String target, boolean strict)
{
for (int i = entries.length - 1; i >= 0; i--)
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menumodifier/MenuModifierConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/menumodifier/MenuModifierConfig.java
new file mode 100644
index 0000000000..53f49012c2
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/menumodifier/MenuModifierConfig.java
@@ -0,0 +1,24 @@
+package net.runelite.client.plugins.menumodifier;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("menumodifier")
+public interface MenuModifierConfig extends Config
+{
+ @ConfigItem(position = 0, keyName = "hideCancel", name = "Hide Cancel", description = "Hides the 'cancel' option from the right click menu")
+ default boolean hideCancel() { return true; }
+
+ @ConfigItem(position = 1, keyName = "hideExamine", name = "Hide Examine", description = "Hides the 'examine' option from the right click menu")
+ default boolean hideExamine() { return true; }
+
+ @ConfigItem(position = 2, keyName = "hideTradeWith", name = "Hide Trade With", description = "Hides the 'trade with' option from the right click menu")
+ default boolean hideTradeWith() { return true; }
+
+ @ConfigItem(position = 3, keyName = "hideReport", name = "Hide Report", description = "Hides the 'report' option from the right click menu")
+ default boolean hideReport() { return true; }
+
+ @ConfigItem(position = 4, keyName = "hideLookup", name = "Hide Lookup", description = "Hides the 'lookup' option from the right click menu")
+ default boolean hideLookup() { return true; }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menumodifier/MenuModifierInputListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/menumodifier/MenuModifierInputListener.java
new file mode 100644
index 0000000000..cbb15161f8
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/menumodifier/MenuModifierInputListener.java
@@ -0,0 +1,39 @@
+package net.runelite.client.plugins.menumodifier;
+
+import net.runelite.client.input.KeyListener;
+import net.runelite.client.input.MouseAdapter;
+
+import javax.inject.Inject;
+import java.awt.event.KeyEvent;
+
+public class MenuModifierInputListener extends MouseAdapter implements KeyListener
+{
+ private static final int HOTKEY = KeyEvent.VK_CONTROL;
+
+ @Override
+ public void keyTyped(KeyEvent e)
+ {
+
+ }
+
+ @Inject
+ private MenuModifierPlugin plugin;
+
+ @Override
+ public void keyPressed(KeyEvent e)
+ {
+ if (e.getKeyCode() == HOTKEY)
+ {
+ plugin.setHotKeyPressed(true);
+ }
+ }
+
+ @Override
+ public void keyReleased(KeyEvent e)
+ {
+ if (e.getKeyCode() == HOTKEY)
+ {
+ plugin.setHotKeyPressed(false);
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menumodifier/MenuModifierPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/menumodifier/MenuModifierPlugin.java
new file mode 100644
index 0000000000..dc5b9e7a27
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/menumodifier/MenuModifierPlugin.java
@@ -0,0 +1,168 @@
+package net.runelite.client.plugins.menumodifier;
+
+import net.runelite.api.events.MenuOpened;
+import net.runelite.client.eventbus.Subscribe;
+import com.google.inject.Provides;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+import net.runelite.api.Client;
+import net.runelite.api.MenuEntry;
+import net.runelite.api.Player;
+import net.runelite.api.events.GameTick;
+import net.runelite.api.events.MenuEntryAdded;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.input.KeyManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.util.MiscUtils;
+import net.runelite.client.util.Text;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+@PluginDescriptor(
+ name = "!Menu Modifier",
+ description = "Changes right click menu for players",
+ tags = { "menu", "modifier", "right", "click", "pk", "bogla" },
+ enabledByDefault = false
+)
+public class MenuModifierPlugin extends Plugin
+{
+ @Inject
+ private Client client;
+
+ @Inject
+ private MenuModifierConfig config;
+
+ @Inject
+ private MenuModifierInputListener inputListener;
+
+ @Inject
+ private KeyManager keyManager;
+
+ @Provides
+ MenuModifierConfig provideConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(MenuModifierConfig.class);
+ }
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ keyManager.registerKeyListener(inputListener);
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ keyManager.unregisterKeyListener(inputListener);
+ }
+
+ @Getter(AccessLevel.PACKAGE)
+ @Setter(AccessLevel.PACKAGE)
+ private boolean hotKeyPressed;
+
+ @Subscribe
+ public void onMenuOpened(MenuOpened event)
+ {
+ Player localPlayer = client.getLocalPlayer();
+
+ if (localPlayer == null)
+ return;
+
+ if (!(MiscUtils.getWildernessLevelFrom(client, localPlayer.getWorldLocation()) >= 0))
+ return;
+
+ if (hotKeyPressed)
+ return;
+
+ List menu_entries = new ArrayList();
+
+ for (MenuEntry entry : event.getMenuEntries())
+ {
+ String option = Text.removeTags(entry.getOption()).toLowerCase();
+
+ if (option.contains("trade with") && config.hideTradeWith())
+ continue;
+
+ if (option.contains("lookup") && config.hideLookup())
+ continue;
+
+ if (option.contains("report") && config.hideReport())
+ continue;
+
+ if (option.contains("examine") && config.hideExamine())
+ continue;
+
+ int identifier = entry.getIdentifier();
+
+ Player[] players = client.getCachedPlayers();
+ Player player = null;
+
+ if (identifier >= 0 && identifier < players.length)
+ player = players[identifier];
+
+ if (player == null)
+ {
+ menu_entries.add(entry);
+ continue;
+ }
+
+ if ((option.contains("attack") || option.contains("cast")) && (player.isFriend() || player.isClanMember()))
+ continue;
+
+ menu_entries.add(entry);
+ }
+
+ MenuEntry[] updated_menu_entries = new MenuEntry[menu_entries.size()];
+ updated_menu_entries = menu_entries.toArray(updated_menu_entries);
+
+ client.setMenuEntries(updated_menu_entries);
+ }
+
+ /*@Subscribe
+ public void onMenuEntryAdded(MenuEntryAdded menuEntryAdded)
+ {
+ if (true)
+ return;
+
+ if (!inWilderness)
+ return;
+
+ if (hotKeyPressed)
+ return;
+
+ String option = Text.removeTags(menuEntryAdded.getOption()).toLowerCase();
+
+ if ((option.contains("trade with") && config.hideTradeWith())
+ || (option.contains("lookup") && config.hideLookup())
+ || (option.contains("report") && config.hideReport())
+ || (option.contains("examine") && config.hideExamine())
+ || (option.contains("cancel") && config.hideCancel()))
+ {
+ int identifier = menuEntryAdded.getIdentifier();
+
+ Player[] players = client.getCachedPlayers();
+ Player player = null;
+
+ if (identifier >= 0 && identifier < players.length)
+ player = players[identifier];
+
+ if (player == null)
+ return;
+
+ //allow trading with friends/clanmates
+ if (option.contains("trade with") && (player.isFriend() || player.isClanMember()))
+ return;
+
+ MenuEntry[] menuEntries = client.getMenuEntries();
+
+ if (menuEntries.length > 0)
+ client.setMenuEntries(Arrays.copyOf(menuEntries, menuEntries.length - 1));
+ }
+ }*/
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MinedRock.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MinedRock.java
new file mode 100644
index 0000000000..e970077dfa
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MinedRock.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2018, Craftiii4
+ * 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.mining;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+
+public class MinedRock
+{
+
+ @Getter(AccessLevel.PACKAGE)
+ private final MiningRockType type;
+
+ private final long minRespawnTime, maxRespawnTime;
+
+ public MinedRock(MiningRockType type, boolean halve)
+ {
+ this.type = type;
+ this.minRespawnTime = System.currentTimeMillis() + (int)((halve ? type.getMinRespawnTime() / 2 : type.getMinRespawnTime()) * 1000);
+ this.maxRespawnTime = type.getMaxRespawnTime() == -1 ? -1 : System.currentTimeMillis() + (int)((halve ? type.getMaxRespawnTime() / 2 : type.getMaxRespawnTime()) * 1000);
+ }
+
+ public int getMinSecondsUntilRespawn(boolean allowNegative)
+ {
+ long remaining = minRespawnTime - System.currentTimeMillis();
+ if (remaining > 0 || allowNegative)
+ {
+ return (int) (remaining / 1000) + (remaining % 1000 > 0 ? 1 : 0);
+ }
+ else
+ {
+ return (maxRespawnTime > minRespawnTime) ? 0 : 1;
+ }
+ }
+
+ public int getMaxSecondsUntilRespawn()
+ {
+ if (maxRespawnTime == -1)
+ {
+ return -1;
+ }
+ long remaining = maxRespawnTime - System.currentTimeMillis();
+ if (remaining > 0 && maxRespawnTime > minRespawnTime)
+ {
+ return (int) (remaining / 1000) + (remaining % 1000 > 0 ? 1 : 0);
+ }
+ else
+ {
+ // Return -1 if the ore does not have a maximum respawn time (no range)
+ return 1;
+ }
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningConfig.java
new file mode 100644
index 0000000000..960542aff0
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningConfig.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2018, Craftiii4
+ * 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.mining;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("miningplugin")
+public interface MiningConfig extends Config
+{
+
+ @ConfigItem(
+ position = 1,
+ keyName = "statTimeout",
+ name = "Reset stats (minutes)",
+ description = "The time until mining session data is reset in minutes."
+ )
+ default int statTimeout()
+ {
+ return 5;
+ }
+
+ @ConfigItem(
+ position = 2,
+ keyName = "showRespawn",
+ name = "Show ore respawn timers",
+ description = "If the amount of seconds left until a ore respawns should be shown."
+ )
+ default boolean showRespawnTimer()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ position = 3,
+ keyName = "showAllRespawns",
+ name = "Show timers for every ore",
+ description = "Show timers for every ore mined in your nearby vicinity"
+ )
+ default boolean showAllRespawnTimers()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 4,
+ keyName = "showMiningStats",
+ name = "Show mining session stats",
+ description = "Configures whether to display mining session stats"
+ )
+ default boolean showMiningStats()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ position = 5,
+ keyName = "showMiningState",
+ name = "Show current mining state",
+ description = "Shows current mining state. 'You are currently mining' / 'You are currently NOT mining'"
+ )
+ default boolean showMiningState()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ position = 6,
+ keyName = "disableInMLM",
+ name = "Disable in MLM",
+ description = "Disables the trackers if you're in the motherloade mine"
+ )
+ default boolean disableInMLM()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ position = 7,
+ keyName = "trackWorldRock",
+ name = "World Tracker",
+ description = "Tracks the respawn time of a certain ore as you switch worlds"
+ )
+ default MiningRockType.WorldRock trackWorldRock()
+ {
+ return MiningRockType.WorldRock.None;
+ }
+
+ @ConfigItem(
+ position = 8,
+ keyName = "trackTimeout",
+ name = "Stop tracking (seconds)",
+ description = "The time a world will keep on displaying after the ore has respawned in seconds"
+ )
+ default int trackTimeout()
+ {
+ return 60;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningOverlay.java
new file mode 100644
index 0000000000..bf19cb9bb1
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningOverlay.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2018, Craftiii4
+ * 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.mining;
+
+import com.google.common.collect.ImmutableSet;
+import static net.runelite.api.AnimationID.MINING_3A_PICKAXE;
+import static net.runelite.api.AnimationID.MINING_ADAMANT_PICKAXE;
+import static net.runelite.api.AnimationID.MINING_BLACK_PICKAXE;
+import static net.runelite.api.AnimationID.MINING_BRONZE_PICKAXE;
+import static net.runelite.api.AnimationID.MINING_DRAGON_PICKAXE;
+import static net.runelite.api.AnimationID.MINING_DRAGON_PICKAXE_ORN;
+import static net.runelite.api.AnimationID.MINING_INFERNAL_PICKAXE;
+import static net.runelite.api.AnimationID.MINING_IRON_PICKAXE;
+import static net.runelite.api.AnimationID.MINING_MITHRIL_PICKAXE;
+import static net.runelite.api.AnimationID.MINING_MOTHERLODE_ADAMANT;
+import static net.runelite.api.AnimationID.MINING_MOTHERLODE_BLACK;
+import static net.runelite.api.AnimationID.MINING_MOTHERLODE_BRONZE;
+import static net.runelite.api.AnimationID.MINING_MOTHERLODE_DRAGON;
+import static net.runelite.api.AnimationID.MINING_MOTHERLODE_DRAGON_ORN;
+import static net.runelite.api.AnimationID.MINING_MOTHERLODE_INFERNAL;
+import static net.runelite.api.AnimationID.MINING_MOTHERLODE_IRON;
+import static net.runelite.api.AnimationID.MINING_MOTHERLODE_MITHRIL;
+import static net.runelite.api.AnimationID.MINING_MOTHERLODE_RUNE;
+import static net.runelite.api.AnimationID.MINING_MOTHERLODE_STEEL;
+import static net.runelite.api.AnimationID.MINING_RUNE_PICKAXE;
+import static net.runelite.api.AnimationID.MINING_STEEL_PICKAXE;
+import net.runelite.api.Client;
+import net.runelite.client.game.SkillIconManager;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.components.LineComponent;
+import net.runelite.client.ui.overlay.components.PanelComponent;
+import net.runelite.client.ui.overlay.components.TitleComponent;
+import javax.inject.Inject;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Set;
+
+/**
+ * Displays information about the players current mining state and rocks mined
+ */
+public class MiningOverlay extends Overlay
+{
+
+ private static final Set MINING_ANIMATION_IDS = ImmutableSet.of(
+ MINING_3A_PICKAXE, MINING_ADAMANT_PICKAXE, MINING_BLACK_PICKAXE,
+ MINING_BRONZE_PICKAXE, MINING_DRAGON_PICKAXE, MINING_DRAGON_PICKAXE_ORN,
+ MINING_INFERNAL_PICKAXE, MINING_IRON_PICKAXE, MINING_MITHRIL_PICKAXE,
+ MINING_RUNE_PICKAXE, MINING_STEEL_PICKAXE, MINING_MOTHERLODE_BRONZE,
+ MINING_MOTHERLODE_IRON, MINING_MOTHERLODE_STEEL, MINING_MOTHERLODE_BLACK,
+ MINING_MOTHERLODE_MITHRIL, MINING_MOTHERLODE_ADAMANT, MINING_MOTHERLODE_RUNE,
+ MINING_MOTHERLODE_DRAGON, MINING_MOTHERLODE_DRAGON_ORN, MINING_MOTHERLODE_INFERNAL
+ );
+
+ private final MiningConfig config;
+ private final Client client;
+ private final MiningPlugin plugin;
+ private final PanelComponent panelComponent = new PanelComponent();
+
+ @Inject
+ MiningOverlay(Client client, MiningPlugin plugin, MiningConfig config, SkillIconManager iconManager)
+ {
+ setPosition(OverlayPosition.TOP_LEFT);
+ this.client = client;
+ this.config = config;
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ // Prevents conflicts with the motherloade plugin
+ if (config.disableInMLM() && plugin.isInMlm())
+ {
+ return null;
+ }
+
+ panelComponent.getChildren().clear();
+
+ if (config.showMiningState())
+ {
+ if (MINING_ANIMATION_IDS.contains(client.getLocalPlayer().getAnimation()))
+ {
+ panelComponent.getChildren().add(TitleComponent.builder()
+ .text("Mining")
+ .color(Color.GREEN)
+ .build());
+ }
+ else
+ {
+ panelComponent.getChildren().add(TitleComponent.builder()
+ .text("NOT mining")
+ .color(Color.RED)
+ .build());
+ }
+ }
+
+ MiningSession session = plugin.getSession();
+ if (session.getLastMined() != null)
+ {
+ Duration statTimeout = Duration.ofMinutes(config.statTimeout());
+ Duration sinceCut = Duration.between(session.getLastMined(), Instant.now());
+ if (sinceCut.compareTo(statTimeout) < 0)
+ {
+ if (config.showMiningStats())
+ {
+ for (MiningRockType rock : MiningRockType.values())
+ {
+ if (session.showRockRespawnTimes(rock))
+ {
+ panelComponent.getChildren().add(LineComponent.builder()
+ .left(rock.getName() + " mined:")
+ .right(Integer.toString(session.getSessionStats().get(rock).getTotalMined()))
+ .build());
+ panelComponent.getChildren().add(LineComponent.builder()
+ .left(rock.getName() + "/hr:")
+ .right(session.getSessionStats().get(rock).getRecentMined() > 2 ? Integer.toString(session.getSessionStats().get(rock).getPerHour()) : "")
+ .build());
+ }
+ }
+ }
+ if (panelComponent.getChildren().size() > 0)
+ {
+ return panelComponent.render(graphics);
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningPlugin.java
new file mode 100644
index 0000000000..4414464636
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningPlugin.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2018, Craftiii4
+ * 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.mining;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Provides;
+import lombok.AccessLevel;
+import lombok.Getter;
+import net.runelite.api.ChatMessageType;
+import net.runelite.api.Client;
+import net.runelite.api.Experience;
+import net.runelite.api.GameObject;
+import net.runelite.api.GameState;
+import net.runelite.api.Skill;
+import net.runelite.api.TileObject;
+import net.runelite.api.WallObject;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.api.events.ChatMessage;
+import net.runelite.api.events.ConfigChanged;
+import net.runelite.api.events.ExperienceChanged;
+import net.runelite.api.events.GameObjectDespawned;
+import net.runelite.api.events.GameObjectSpawned;
+import net.runelite.api.events.GameStateChanged;
+import net.runelite.api.events.WallObjectDespawned;
+import net.runelite.api.events.WallObjectSpawned;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.task.Schedule;
+import net.runelite.client.ui.overlay.OverlayManager;
+import javax.inject.Inject;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.HashMap;
+import java.util.Set;
+
+@PluginDescriptor(
+ name = "Mining",
+ description = "Show helpful information when mining",
+ tags = {"mining", "mine"},
+ enabledByDefault = false
+)
+public class MiningPlugin extends Plugin
+{
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ MiningRockOverlay oreOverlay;
+
+ @Inject
+ MiningWorldHopperOverlay worldHopperOverlay;
+
+ @Inject
+ MiningOverlay miningOverlay;
+
+ @Inject
+ MiningConfig config;
+
+ @Inject
+ private Client client;
+
+ @Getter(AccessLevel.PACKAGE)
+ private int miningLevel;
+
+ @Getter(AccessLevel.PACKAGE)
+ private final HashMap ores = new HashMap<>();
+
+ @Provides
+ MiningConfig getConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(MiningConfig.class);
+ }
+
+ @Getter(AccessLevel.PACKAGE)
+ private MiningSession session;
+
+ @Getter(AccessLevel.PUBLIC)
+ private MiningWorldTracker miningTracker;
+
+ private static final Set MOTHERLODE_MAP_REGIONS = ImmutableSet.of(14679, 14680, 14681, 14935, 14936, 14937, 15191, 15192, 15193);
+ private static final int P2P_MINING_GUILD_REGION = 12183;
+ private final static int MINING_GUILD_RESPAWN_RATE_HALVE_Y = 9727;
+
+ @Override
+ protected void startUp()
+ {
+ overlayManager.add(miningOverlay);
+ overlayManager.add(oreOverlay);
+ overlayManager.add(worldHopperOverlay);
+ session = new MiningSession();
+ if (config.trackWorldRock() != MiningRockType.WorldRock.None)
+ {
+ miningTracker = new MiningWorldTracker(config.trackWorldRock());
+ }
+ else
+ {
+ miningTracker = null;
+ }
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ overlayManager.remove(miningOverlay);
+ overlayManager.remove(oreOverlay);
+ overlayManager.remove(worldHopperOverlay);
+ ores.clear();
+ miningLevel = Experience.getLevelForXp(client.getSkillExperience(Skill.MINING));
+ session = null;
+ miningTracker = null;
+ }
+
+ @Subscribe
+ public void onConfigChanged(ConfigChanged event)
+ {
+ if (event.getKey().equals("trackWorldRock"))
+ {
+ MiningRockType.WorldRock worldRock = config.trackWorldRock();
+ if (worldRock == MiningRockType.WorldRock.None)
+ {
+ miningTracker = null;
+ }
+ else
+ {
+ miningTracker = new MiningWorldTracker(config.trackWorldRock());
+ }
+ }
+ }
+
+ @Subscribe
+ public void onGameObjectSpawned(GameObjectSpawned event)
+ {
+ GameObject object = event.getGameObject();
+ MiningRockType rock = MiningRockType.getTypeFromID(object.getId());
+ if (rock != null)
+ {
+ for (TileObject o : ores.keySet())
+ {
+ if (o.getX() == object.getX() && o.getY() == object.getY())
+ {
+ // Remove ground rock as it has respawned
+ ores.remove(o);
+ break;
+ }
+ }
+ }
+ }
+
+ @Subscribe
+ public void onGameObjectDespawned(GameObjectDespawned event)
+ {
+ Duration timeSinceStart = Duration.between(session.getIgnoreSpawn(), Instant.now());
+ // Ignore anything spawned within 1 second of logging in or changing regions (prevents timers appearing on already mined rocks)
+ if (timeSinceStart.getSeconds() > 1)
+ {
+ GameObject object = event.getGameObject();
+ MiningRockType rock = MiningRockType.getTypeFromID(object.getId());
+ if (rock != null && miningLevel >= rock.getRequiredMiningLevel())
+ {
+ if (!ores.containsKey(object))
+ {
+ ores.put(object, new MinedRock(rock, isInMiningGuildPay2Play(object.getWorldLocation())));
+ }
+ }
+ }
+ }
+
+ @Subscribe
+ public void onWallObjectSpawned(WallObjectSpawned event)
+ {
+ Duration timeSinceStart = Duration.between(session.getIgnoreSpawn(), Instant.now());
+ // Ignore anything spawned within 1 second of logging in or changing regions (prevents timers appearing on already mined rocks)
+ if (timeSinceStart.getSeconds() > 1)
+ {
+ WallObject object = event.getWallObject();
+ MiningRockType rock = MiningRockType.getTypeFromID(object.getId());
+ if (rock != null && miningLevel >= rock.getRequiredMiningLevel())
+ {
+ if (!ores.containsKey(object))
+ {
+ ores.put(object, new MinedRock(rock, isInMiningGuildPay2Play(object.getWorldLocation())));
+ }
+ }
+ }
+ }
+
+ @Subscribe
+ public void onWallObjectDespawned(WallObjectDespawned event)
+ {
+ WallObject object = event.getWallObject();
+ MiningRockType rock = MiningRockType.getTypeFromID(object.getId());
+ if (rock != null)
+ {
+ for (TileObject o : ores.keySet())
+ {
+ if (o.getX() == object.getX() && o.getY() == object.getY())
+ {
+ // Remove wall rock as it has respawned
+ ores.remove(o);
+ break;
+ }
+ }
+ }
+ }
+
+ @Subscribe
+ public void onGameStateChanged(GameStateChanged event)
+ {
+ GameState state = event.getGameState();
+ if (state == GameState.HOPPING)
+ {
+ MiningRockType.WorldRock worldRock = config.trackWorldRock();
+ if (worldRock != MiningRockType.WorldRock.None)
+ {
+ int world = client.getWorld();
+ for (TileObject rock : ores.keySet())
+ {
+ if (worldRock.getRockType() == ores.get(rock).getType())
+ {
+ // If the type matches the multi-world rock then add to the mining tracker for this world
+ miningTracker.addTracked(world, rock, ores.get(rock));
+ }
+ }
+ }
+ }
+ else if (state == GameState.LOADING)
+ {
+ ores.clear();
+ session.setIgnoreSpawn(Instant.now());
+ }
+ else if (state == GameState.LOGGED_IN)
+ {
+ int world = client.getWorld();
+ if (miningTracker != null && miningTracker.getTrackedWorlds().containsKey(world))
+ {
+ MiningWorld track = miningTracker.getTrackedWorlds().get(world);
+ track.clearNegativeRespawnTimes();
+ // Load all the tracked ores in this world into the current session. Causing their respawn times to be rendered
+ for (TileObject o : track.getRocks().keySet())
+ {
+ ores.put(o, track.getRocks().get(o));
+ }
+ // We're on this world now, so don't track it in the world tracker anymore
+ miningTracker.getTrackedWorlds().remove(world);
+ }
+ }
+ }
+
+ @Subscribe
+ public void onExperienceChanged(ExperienceChanged event)
+ {
+ if (event.getSkill() == Skill.MINING)
+ {
+ miningLevel = Experience.getLevelForXp(client.getSkillExperience(Skill.MINING));
+ }
+ }
+
+ @Subscribe
+ public void onChatMessage(ChatMessage event)
+ {
+ if (event.getType() != ChatMessageType.SPAM)
+ {
+ return;
+ }
+ if (event.getMessage().startsWith("You manage to mine some"))
+ {
+ String oreName = event.getMessage().substring(24).replace(".", "");
+ MiningRockType rock = MiningRockType.getTypeFromName(oreName);
+ if (rock != null)
+ {
+ session.increaseRockMine(rock);
+ }
+
+ }
+ }
+
+ @Schedule(
+ period = 1,
+ unit = ChronoUnit.SECONDS
+ )
+ public void checkIsMining()
+ {
+ for (MiningRockType rock : MiningRockType.values())
+ {
+ if (session.getSessionStats().get(rock).getLastOreMined() != null)
+ {
+ Duration statTimeout = Duration.ofMinutes(config.statTimeout());
+ Duration sinceMined = Duration.between(session.getSessionStats().get(rock).getLastOreMined(), Instant.now());
+ if (sinceMined.compareTo(statTimeout) >= 0)
+ {
+ session.clearSessionFor(rock);
+ }
+ }
+ }
+ }
+
+ public boolean isInMlm()
+ {
+ if (client.getGameState() != GameState.LOGGED_IN)
+ {
+ return false;
+ }
+
+ int[] currentMapRegions = client.getMapRegions();
+ // Verify that all regions exist in MOTHERLODE_MAP_REGIONS
+ for (int region : currentMapRegions)
+ {
+ if (!MOTHERLODE_MAP_REGIONS.contains(region))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public boolean isInMiningGuildPay2Play(WorldPoint point)
+ {
+ if (client.getGameState() != GameState.LOGGED_IN)
+ {
+ return false;
+ }
+
+ int[] currentMapRegions = client.getMapRegions();
+ for (int region : currentMapRegions)
+ {
+ if (region == P2P_MINING_GUILD_REGION)
+ {
+ return (point.getY() <= MINING_GUILD_RESPAWN_RATE_HALVE_Y);
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningRockOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningRockOverlay.java
new file mode 100644
index 0000000000..492e79b276
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningRockOverlay.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2018, Craftiii4
+ * 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.mining;
+
+import net.runelite.api.Client;
+import net.runelite.api.Perspective;
+import net.runelite.api.Player;
+import net.runelite.api.Point;
+import net.runelite.api.TileObject;
+import net.runelite.api.coords.LocalPoint;
+import net.runelite.client.game.SkillIconManager;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayLayer;
+import javax.inject.Inject;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import net.runelite.client.ui.overlay.OverlayPosition;
+
+/**
+ * Displays respawn timers over rocks
+ */
+public class MiningRockOverlay extends Overlay
+{
+
+ private static final int MAX_DISTANCE = 2350;
+
+ private final MiningConfig config;
+ private final Client client;
+ private final MiningPlugin plugin;
+
+ @Inject
+ MiningRockOverlay(Client client, MiningPlugin plugin, MiningConfig config, SkillIconManager iconManager)
+ {
+ setPosition(OverlayPosition.DYNAMIC);
+ setLayer(OverlayLayer.ABOVE_SCENE);
+ this.client = client;
+ this.config = config;
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ Player local = client.getLocalPlayer();
+ if (config.showRespawnTimer())
+ {
+ renderRespawnTimers(graphics, local);
+ }
+ return null;
+ }
+
+ private void renderRespawnTimers(Graphics2D graphics, Player local)
+ {
+ LocalPoint localLocation = local.getLocalLocation();
+ for (TileObject ore : plugin.getOres().keySet())
+ {
+ MinedRock rock = plugin.getOres().get(ore);
+ if ((config.showAllRespawnTimers() || plugin.getSession().showRockRespawnTimes(rock.getType())) && localLocation.distanceTo(ore.getLocalLocation()) <= MAX_DISTANCE)
+ {
+ // Check if we should display this rock to the user. Checks if the user has mined the rock within this session (or has all on within config) & is within range
+ renderRespawnTimerRock(graphics, ore, rock.getMinSecondsUntilRespawn(false), rock.getMaxSecondsUntilRespawn(), rock.getType().isGroundObject());
+ }
+ }
+ }
+
+ /**
+ * Renders a rocks respawn time
+ * @param rock The rock
+ * @param time Time until the rock respawns (minimum)
+ * @param max Maximum time until the rock respawns (-1 = no range)
+ * @param ground If the rock is on the ground (true = ground, false = wall) - used to offset the respawn time text
+ */
+ private void renderRespawnTimerRock(Graphics2D graphics, TileObject rock, int time, int max, boolean ground)
+ {
+ Point canvasLoc = Perspective.getCanvasTextLocation(client, graphics, rock.getLocalLocation(), "" + time, ground ? 0 : 150);
+ if (canvasLoc != null)
+ {
+ String timeMessage = "" + time;
+ // Check if this rock has a respawn time range
+ if (max != -1)
+ {
+ // Check if the rock has reached the minimum respawn time
+ if (time <= 0)
+ {
+ // Display the maximum possible time remaining
+ timeMessage = "~" + max;
+ graphics.setColor(Color.CYAN);
+ }
+ else
+ {
+ // Rock has not yet reached the minimum respawn time of the range
+ timeMessage += "~";
+ }
+
+ }
+ graphics.drawString(timeMessage, canvasLoc.getX(), canvasLoc.getY());
+ graphics.setColor(Color.WHITE);
+ }
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningRockType.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningRockType.java
new file mode 100644
index 0000000000..e094fc66c4
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningRockType.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2018, Craftiii4
+ * 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.mining;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import static net.runelite.api.ObjectID.EMPTY_WALL;
+import static net.runelite.api.ObjectID.ROCKS_7453;
+import static net.runelite.api.ObjectID.ROCKS_7455;
+import static net.runelite.api.ObjectID.ROCKS_7456;
+import static net.runelite.api.ObjectID.ROCKS_7457;
+import static net.runelite.api.ObjectID.ROCKS_7458;
+import static net.runelite.api.ObjectID.ROCKS_7459;
+import static net.runelite.api.ObjectID.ROCKS_7460;
+import static net.runelite.api.ObjectID.ROCKS_7461;
+import static net.runelite.api.ObjectID.ROCKS_7484;
+import static net.runelite.api.ObjectID.ROCKS_7485;
+import static net.runelite.api.ObjectID.ROCKS_7486;
+import static net.runelite.api.ObjectID.ROCKS_7488;
+import static net.runelite.api.ObjectID.ROCKS_7489;
+import static net.runelite.api.ObjectID.ROCKS_7490;
+import static net.runelite.api.ObjectID.ROCKS_7491;
+import static net.runelite.api.ObjectID.ROCKS_7492;
+import static net.runelite.api.ObjectID.ROCKS_7493;
+import static net.runelite.api.ObjectID.ROCKS_7494;
+import static net.runelite.api.ObjectID.DEPLETED_VEIN_26665;
+import static net.runelite.api.ObjectID.DEPLETED_VEIN_26666;
+import static net.runelite.api.ObjectID.DEPLETED_VEIN_26667;
+import static net.runelite.api.ObjectID.DEPLETED_VEIN_26668;
+
+/**
+ * All the possible rock types the user can mine.
+ */
+@AllArgsConstructor
+public enum MiningRockType
+{
+
+ COPPER("Copper", 1, 2.5, -1, false, true, new int[] {ROCKS_7453, ROCKS_7484}),
+ TIN("Tin", 1, 2.5, -1, false, true, new int[] {ROCKS_7485, ROCKS_7486}),
+ IRON( "Iron", 15, 5.5, -1, false, true, new int[] {ROCKS_7455, ROCKS_7488}),
+ SILVER("Silver", 15, 60, -1, false, true, new int[] {ROCKS_7457, ROCKS_7490}),
+ COAL("Coal", 30, 29.5, -1, false, true, new int[] {ROCKS_7456, ROCKS_7489}),
+ GOLD("Gold", 40, 59.5, -1, false, true, new int[] {ROCKS_7458, ROCKS_7491}),
+ MITHRIL("Mithril", 55, 119.5, -1, false, true, new int[] {ROCKS_7459, ROCKS_7492}),
+ ADAMANTITE("Adamantite", 70, 239.5, -1, false, true, new int[] {ROCKS_7460, ROCKS_7493}),
+ RUNITE("Runite", 85, 720, -1, false, true, new int[] {ROCKS_7461, ROCKS_7494}),
+ AMETHYST("Amethyst", 92, 150, -1, true, false, new int[] {EMPTY_WALL}),
+ PAY_DIRT("Pay-Dirt", 30, 95, 125, true, false, new int[] {DEPLETED_VEIN_26665, DEPLETED_VEIN_26666, DEPLETED_VEIN_26667, DEPLETED_VEIN_26668});
+
+ @Getter(AccessLevel.PACKAGE)
+ private final String name;
+
+ @Getter(AccessLevel.PACKAGE)
+ private final int requiredMiningLevel;
+
+ @Getter(AccessLevel.PACKAGE)
+ private final double minRespawnTime, maxRespawnTime;
+
+ @Getter(AccessLevel.PACKAGE)
+ private final boolean memberOnly;
+
+ @Getter(AccessLevel.PACKAGE)
+ private final boolean groundObject;
+
+ private final int[] rockIDs;
+
+ public static MiningRockType getTypeFromID(int id)
+ {
+ for (MiningRockType type : values())
+ {
+ for (int i : type.rockIDs)
+ {
+ if (i == id)
+ {
+ return type;
+ }
+ }
+ }
+ return null;
+ }
+
+ public static MiningRockType getTypeFromName(String name)
+ {
+ for (MiningRockType type : values())
+ {
+ if (type.getName().equalsIgnoreCase(name))
+ {
+ return type;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * The rocks which can be tracked across worlds
+ */
+ @AllArgsConstructor
+ public enum WorldRock
+ {
+ None(null),
+ Adamantite(MiningRockType.ADAMANTITE),
+ Gold(MiningRockType.GOLD),
+ Mithril(MiningRockType.MITHRIL),
+ Runite(MiningRockType.RUNITE);
+
+ @Getter(AccessLevel.PACKAGE)
+ private final MiningRockType rockType;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningSession.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningSession.java
new file mode 100644
index 0000000000..9d45a1b67f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningSession.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2018, Craftiii4
+ * 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.mining;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+import java.time.Instant;
+import java.util.HashMap;
+
+/**
+ * Holds information about the players current mining session
+ */
+public class MiningSession
+{
+
+ @Getter(AccessLevel.PACKAGE)
+ @Setter(AccessLevel.PACKAGE)
+ private Instant ignoreSpawn;
+
+ @Getter(AccessLevel.PACKAGE)
+ private Instant lastMined;
+
+ @Getter(AccessLevel.PACKAGE)
+ private HashMap sessionStats = new HashMap<>();
+
+ public MiningSession()
+ {
+ setupSession();
+ }
+
+ public void setupSession()
+ {
+ ignoreSpawn = Instant.now();
+ for (MiningRockType rock : MiningRockType.values())
+ {
+ sessionStats.put(rock, new MiningSessionRockStats());
+ }
+ }
+
+ public boolean showRockRespawnTimes(MiningRockType rock)
+ {
+ return sessionStats.get(rock).getRecentOreMined() != null;
+ }
+
+ public void clearSessionFor(MiningRockType rock)
+ {
+ sessionStats.get(rock).clearSession();
+ }
+
+ public void increaseRockMine(MiningRockType rock)
+ {
+ Instant now = Instant.now();
+ lastMined = now;
+ sessionStats.get(rock).increaseMined();
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningSessionRockStats.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningSessionRockStats.java
new file mode 100644
index 0000000000..523b1b18d1
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningSessionRockStats.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018, Craftiii4
+ * 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.mining;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+
+import java.time.Duration;
+import java.time.Instant;
+
+public class MiningSessionRockStats
+{
+
+ private static final Duration HOUR = Duration.ofHours(1);
+
+ @Getter(AccessLevel.PACKAGE)
+ private Instant lastOreMined;
+
+ @Getter(AccessLevel.PACKAGE)
+ private Instant recentOreMined;
+
+ @Getter(AccessLevel.PACKAGE)
+ private int totalMined;
+
+ @Getter(AccessLevel.PACKAGE)
+ private int perHour;
+
+ @Getter(AccessLevel.PACKAGE)
+ private int recentMined;
+
+ public MiningSessionRockStats()
+ {
+ lastOreMined = null;
+ recentOreMined = null;
+ totalMined = 0;
+ perHour = 0;
+ recentMined = 0;
+ }
+
+ public void clearSession()
+ {
+ recentOreMined = null;
+ perHour = 0;
+ recentMined = 0;
+ }
+
+ public void increaseMined()
+ {
+ Instant now = Instant.now();
+ lastOreMined = now;
+ totalMined++;
+ if (recentOreMined == null)
+ {
+ recentOreMined = now;
+ }
+ recentMined++;
+
+ Duration timeSinceStart = Duration.between(recentOreMined, now);
+ if (!timeSinceStart.isZero())
+ {
+ perHour = (int) ((double) recentMined * (double) HOUR.toMillis() / (double) timeSinceStart.toMillis());
+ }
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningWorld.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningWorld.java
new file mode 100644
index 0000000000..7ad4e2a612
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningWorld.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018, Craftiii4
+ * 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.mining;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import lombok.AccessLevel;
+import lombok.Getter;
+import net.runelite.api.TileObject;
+
+import java.util.ArrayList;
+
+/**
+ * Holds rocks mined in a certain world
+ */
+public class MiningWorld
+{
+
+ @Getter(AccessLevel.PACKAGE)
+ private int world;
+
+ @Getter(AccessLevel.PACKAGE)
+ private final BiMap rocks = HashBiMap.create();
+
+ public MiningWorld(int world)
+ {
+ this.world = world;
+ }
+
+ public void clearNegativeRespawnTimes()
+ {
+ // Create a new array list, cause sometimes a concurrent modification occurs
+ for (MinedRock rock : new ArrayList<>(rocks.values()))
+ {
+ if (rock.getMinSecondsUntilRespawn(true) < 0)
+ {
+ rocks.inverse().remove(rock);
+ }
+ }
+ }
+
+ public int getFirstSecondsUntilRespawn()
+ {
+ int least = Integer.MAX_VALUE;
+ for (MinedRock rock : rocks.values())
+ {
+ int seconds = rock.getMinSecondsUntilRespawn(true);
+ if (seconds < least)
+ {
+ least = seconds;
+ }
+ }
+ return least;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningWorldHopperOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningWorldHopperOverlay.java
new file mode 100644
index 0000000000..96bf6bc6c9
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningWorldHopperOverlay.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2018, Craftiii4
+ * 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.mining;
+
+import net.runelite.api.Client;
+import net.runelite.client.game.SkillIconManager;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.components.LineComponent;
+import net.runelite.client.ui.overlay.components.PanelComponent;
+import net.runelite.client.ui.overlay.components.TitleComponent;
+import javax.inject.Inject;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.stream.Collectors;
+
+/**
+ * Displays respawn timers on a per-world base for any rock recently mined by the player as they're hopping worlds
+ */
+public class MiningWorldHopperOverlay extends Overlay
+{
+
+ private final MiningConfig config;
+ private final Client client;
+ private final MiningPlugin plugin;
+ private final PanelComponent panelComponent = new PanelComponent();
+
+ @Inject
+ MiningWorldHopperOverlay(Client client, MiningPlugin plugin, MiningConfig config, SkillIconManager iconManager)
+ {
+ setPosition(OverlayPosition.TOP_LEFT);
+ this.client = client;
+ this.config = config;
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if (config.trackWorldRock() == MiningRockType.WorldRock.None)
+ {
+ return null;
+ }
+
+ MiningWorldTracker tracker = plugin.getMiningTracker();
+ for (int worldID : new ArrayList<>(tracker.getTrackedWorlds().keySet()))
+ {
+ MiningWorld world = tracker.getTrackedWorlds().get(worldID);
+ for (MinedRock rock : new ArrayList<>(world.getRocks().values()))
+ {
+ // If this rock has respawned & we've passed the config defined timeout, then remove this rock
+ if (rock.getMinSecondsUntilRespawn(true) < 0 - config.trackTimeout())
+ {
+ world.getRocks().inverse().remove(rock);
+ }
+ }
+ if (world.getRocks().size() == 0)
+ {
+ tracker.getTrackedWorlds().remove(worldID);
+ }
+ }
+ if (tracker.getTrackedWorlds().size() == 0)
+ {
+ return null;
+ }
+
+ panelComponent.getChildren().clear();
+ panelComponent.getChildren().add(TitleComponent.builder()
+ .text("Respawn Tracker")
+ .build());
+
+ List worlds = tracker.getTrackedWorlds().values()
+ .stream()
+ .sorted(Comparator.comparing(MiningWorld::getFirstSecondsUntilRespawn))
+ .collect(Collectors.toList());
+ for (MiningWorld world : worlds)
+ {
+ // Go through every remaining world, if they're here it means they have a rock that has not yet respawned (or timeout not yet passed)
+ int id = world.getWorld();
+ int seconds = world.getFirstSecondsUntilRespawn();
+ if (seconds < 0)
+ {
+ // If the time left until the rock respawns is less than zero, then it means it has respawned.
+ // However if it's not yet been cleared, it means the timeout has not yet passed so we still need to display this world to the user
+ seconds = 0;
+ }
+ panelComponent.getChildren().add(LineComponent.builder()
+ .left("World " + id)
+ .right(Integer.toString(seconds))
+ .rightColor(seconds == 0 ? Color.GREEN : Color.ORANGE)
+ .build());
+ }
+ return panelComponent.render(graphics);
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningWorldTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningWorldTracker.java
new file mode 100644
index 0000000000..290869c284
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningWorldTracker.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018, Craftiii4
+ * 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.mining;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import net.runelite.api.TileObject;
+
+import java.util.HashMap;
+
+/**
+ * Holds each world currently being tracked for respawn timers
+ */
+public class MiningWorldTracker
+{
+
+ @Getter(AccessLevel.PACKAGE)
+ private MiningRockType.WorldRock trackingRock;
+
+ @Getter(AccessLevel.PACKAGE)
+ private HashMap trackedWorlds = new HashMap<>();
+
+ public MiningWorldTracker(MiningRockType.WorldRock trackingRock)
+ {
+ this.trackingRock = trackingRock;
+ }
+
+ /**
+ * Adds a tracked rock to a world.
+ *
+ * @param world World ID
+ * @param object The TileObject of the rock to track
+ * @param mined The MinedRock of the rock, containing the Type and respawn time
+ */
+ public void addTracked(int world, TileObject object, MinedRock mined)
+ {
+ if (!trackedWorlds.containsKey(world))
+ {
+ trackedWorlds.put(world, new MiningWorld(world));
+ }
+ // Clear any rocks which have respawned, no point knowing about them if we are on this world & mining again
+ trackedWorlds.get(world).clearNegativeRespawnTimes();
+ trackedWorlds.get(world).getRocks().put(object, mined);
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/musicmodifier/MidiFileAdjuster.java b/runelite-client/src/main/java/net/runelite/client/plugins/musicmodifier/MidiFileAdjuster.java
new file mode 100644
index 0000000000..ee67b0a694
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/musicmodifier/MidiFileAdjuster.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2019, Rodolfo Ruiz-Velasco
+ * 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.musicmodifier;
+
+import javax.sound.midi.*;
+import java.io.IOException;
+
+public class MidiFileAdjuster {
+
+ private int bankLSBValue;
+ private int chPosition = -1;
+
+ private boolean customBank = false;
+
+ public Sequence reorderTracks(Sequence sequence) throws InvalidMidiDataException, IOException {
+ for (Track track : sequence.getTracks()) {
+ for (int i = 0; i < track.size(); i++) {
+ MidiEvent midiEvent = track.get(i);
+ MidiMessage midiMessage = midiEvent.getMessage();
+
+ if (midiMessage instanceof ShortMessage) {
+ ShortMessage sm = (ShortMessage) midiMessage;
+
+ if (sm.getChannel() < 16) {
+ getBankLSB(sm);
+
+ if (i == 0 & bankLSBValue != 1) {
+ chPosition++;
+ if (chPosition == 9) {
+ chPosition = 10;
+ }
+ }
+
+ if (!customBank) {
+
+ if (sm.getChannel() == 9) {
+ bankLSBValue = 1;
+ }
+
+ if (sm.getChannel() != 9) {
+ bankLSBValue = 0;
+ }
+ }
+ }
+
+ if (bankLSBValue == 1) {
+
+ int drumChannel = 9;
+ if (sm.getCommand() == ShortMessage.PROGRAM_CHANGE) {
+ sm.setMessage(ShortMessage.PROGRAM_CHANGE, drumChannel, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.CONTROL_CHANGE) {
+ sm.setMessage(ShortMessage.CONTROL_CHANGE, drumChannel, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.NOTE_OFF) {
+ sm.setMessage(ShortMessage.NOTE_OFF, drumChannel, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.NOTE_ON) {
+ sm.setMessage(ShortMessage.NOTE_ON, drumChannel, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.PROGRAM_CHANGE) {
+ sm.setMessage(ShortMessage.PROGRAM_CHANGE, drumChannel, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.CONTROL_CHANGE) {
+ sm.setMessage(ShortMessage.CONTROL_CHANGE, drumChannel, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.PITCH_BEND) {
+ sm.setMessage(ShortMessage.PITCH_BEND, drumChannel, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.CHANNEL_PRESSURE) {
+ sm.setMessage(ShortMessage.CHANNEL_PRESSURE, drumChannel, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.POLY_PRESSURE) {
+ sm.setMessage(ShortMessage.POLY_PRESSURE, drumChannel, sm.getData1(), sm.getData2());
+ }
+ } else {
+
+ if (sm.getCommand() == ShortMessage.PROGRAM_CHANGE) {
+ sm.setMessage(ShortMessage.PROGRAM_CHANGE, chPosition, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.CONTROL_CHANGE) {
+ sm.setMessage(ShortMessage.CONTROL_CHANGE, chPosition, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.NOTE_OFF) {
+ sm.setMessage(ShortMessage.NOTE_OFF, chPosition, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.NOTE_ON) {
+ sm.setMessage(ShortMessage.NOTE_ON, chPosition, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.PROGRAM_CHANGE) {
+ sm.setMessage(ShortMessage.PROGRAM_CHANGE, chPosition, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.CONTROL_CHANGE) {
+ sm.setMessage(ShortMessage.CONTROL_CHANGE, chPosition, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.PITCH_BEND) {
+ sm.setMessage(ShortMessage.PITCH_BEND, chPosition, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.CHANNEL_PRESSURE) {
+ sm.setMessage(ShortMessage.CHANNEL_PRESSURE, chPosition, sm.getData1(), sm.getData2());
+ }
+
+ if (sm.getCommand() == ShortMessage.POLY_PRESSURE) {
+ sm.setMessage(ShortMessage.POLY_PRESSURE, chPosition, sm.getData1(), sm.getData2());
+ }
+ }
+ }
+ }
+ }
+ return sequence;
+ }
+
+ private void getBankLSB(ShortMessage sm) throws InvalidMidiDataException
+ {
+ if (sm.getCommand() == ShortMessage.CONTROL_CHANGE)
+ {
+ if (sm.getData1() == 32)
+ {
+ bankLSBValue = sm.getData2();
+ customBank = true;
+ }
+ if (sm.getData1() == 0)
+ {
+ sm.setMessage(sm.getCommand(), sm.getChannel(), sm.getData1(), bankLSBValue);
+ }
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/musicmodifier/MusicCustomizerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/musicmodifier/MusicCustomizerPlugin.java
new file mode 100644
index 0000000000..a1202750bb
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/musicmodifier/MusicCustomizerPlugin.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) 2019, Rodolfo Ruiz-Velasco
+ * 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.musicmodifier;
+
+import net.runelite.api.*;
+import net.runelite.api.events.GameTick;
+import net.runelite.api.events.WidgetLoaded;
+import net.runelite.api.widgets.*;
+import net.runelite.client.callback.ClientThread;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.game.chatbox.ChatboxPanelManager;
+import net.runelite.client.game.chatbox.ChatboxTextInput;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+
+import javax.inject.Inject;
+import java.io.File;
+
+@PluginDescriptor(
+ name = "Music Track Customizer",
+ description = "Customize what track plays and how it sounds, with local files",
+ tags = {"music", "sound"},
+ enabledByDefault = false
+)
+
+public class MusicCustomizerPlugin extends Plugin
+{
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private ChatboxPanelManager chatboxPanelManager;
+
+ @Inject
+ private ClientThread clientThread;
+
+ private RealTimeMIDIPlayer realTimeMIDIPlayer = new RealTimeMIDIPlayer();
+
+ private String songName = "Scape Main";
+
+ private ChatboxTextInput songInput;
+
+ private Widget playlistModeButton;
+
+ private Widget playlistBox;
+
+ private Widget hidePlaylistButton;
+
+ private Widget addPlaylistSongButton;
+
+ private Widget playlistText;
+
+ private Widget playlistSong;
+
+ private String defaultUnlockedSongs;
+
+ private boolean isLooping = true;
+
+ private boolean playlistMode = false;
+
+ private int newSongY = 34;
+
+ private int playlistCount = 0;
+
+ @Override
+ public void startUp()
+ {
+ playSong(songName);
+ }
+
+ @Override
+ public void shutDown()
+ {
+ if (realTimeMIDIPlayer != null)
+ {
+ realTimeMIDIPlayer.stopSong();
+ }
+ }
+
+ @Subscribe
+ public void onGameTick(GameTick event)
+ {
+ try
+ {
+ if (!playlistMode)
+ {
+ String newSong = client.getWidget(WidgetInfo.MUSICTAB_CURRENT_SONG_NAME).getText();
+
+ if (!newSong.equals(songName))
+ {
+ songName = newSong;
+ playSongFromList(songName);
+ }
+ }
+ } catch (NullPointerException ignored)
+ {
+
+ }
+
+ }
+
+ private void playSong(String song)
+ {
+ File midiMusicFile = new File(System.getProperty("user.home") + "/RuneLiteAudio/MIDI Files/" +
+ "Music/" + song + ".mid/");
+ if (realTimeMIDIPlayer.midi == null)
+ {
+ realTimeMIDIPlayer.midi = midiMusicFile;
+ realTimeMIDIPlayer.run();
+ }
+
+ else
+ {
+ if (realTimeMIDIPlayer.isPlaying())
+ {
+ realTimeMIDIPlayer.stopSong();
+ }
+ realTimeMIDIPlayer.midi = midiMusicFile;
+ realTimeMIDIPlayer.run();
+ }
+ }
+
+ @Subscribe
+ private void onWidgetLoaded(WidgetLoaded widgetLoaded)
+ {
+ if (widgetLoaded.getGroupId() == WidgetID.MUSICTAB_GROUP_ID)
+ {
+ Widget musicPlayerSongs = client.getWidget(WidgetInfo.MUSICTAB_ALL_SONGS);
+ if (musicPlayerSongs != null)
+ {
+ playlistModeButton = musicPlayerSongs.createChild(-1, WidgetType.GRAPHIC);
+ playlistModeButton.setSpriteId(SpriteID.RS2_TAB_MUSIC);
+ playlistModeButton.setOriginalWidth(32);
+ playlistModeButton.setOriginalHeight(32);
+ playlistModeButton.setXPositionMode(WidgetPositionMode.ABSOLUTE_RIGHT);
+ playlistModeButton.setOriginalX(0);
+ playlistModeButton.setOriginalY(0);
+ playlistModeButton.setHasListener(true);
+ playlistModeButton.setAction(1, "Open");
+ playlistModeButton.setOnOpListener((JavaScriptCallback) e -> openPlaylist());
+ playlistModeButton.setName("Playlist");
+ playlistModeButton.setHidden(true); //Playlist is not enabled for this release (Unfinished).
+ playlistModeButton.revalidate();
+ }
+ }
+ }
+
+ private void openPlaylist()
+ {
+ playlistMode = true;
+
+ Widget currentPlayingSong = client.getWidget(WidgetInfo.MUSICTAB_CURRENT_SONG_NAME);
+ Widget allInGameSongs = client.getWidget(WidgetInfo.MUSICTAB_ALL_SONGS);
+ Widget musicScrollbar = client.getWidget(WidgetInfo.MUSICTAB_SCROLLBAR);
+ allInGameSongs.setHidden(true);
+ musicScrollbar.setHidden(true);
+
+ defaultUnlockedSongs = client.getWidget(WidgetInfo.MUSICTAB_UNLOCKED_SONGS).getText();
+
+ client.getWidget(WidgetInfo.MUSICTAB_UNLOCKED_SONGS).setText(playlistCount + " / 10");
+
+ playlistBox = client.getWidget(WidgetInfo.MUSICTAB_INTERFACE);
+
+ playlistText = playlistBox.createChild(-1, WidgetType.TEXT);
+ playlistText.setText("Music Playlist");
+ playlistText.setFontId(497);
+ playlistText.setXPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
+ playlistText.setOriginalX(40);
+ playlistText.setOriginalY(14);
+ playlistText.setOriginalHeight(1);
+ playlistText.setOriginalWidth(1);
+ playlistText.setTextColor(currentPlayingSong.getTextColor());
+ playlistText.revalidate();
+
+ hidePlaylistButton = playlistBox.createChild(-1, WidgetType.GRAPHIC);
+ hidePlaylistButton.setSpriteId(SpriteID.RS2_TAB_MUSIC);
+ hidePlaylistButton.setOriginalWidth(32);
+ hidePlaylistButton.setOriginalHeight(32);
+ hidePlaylistButton.setXPositionMode(WidgetPositionMode.ABSOLUTE_RIGHT);
+ hidePlaylistButton.setOriginalX(0);
+ hidePlaylistButton.setOriginalY(6);
+ hidePlaylistButton.setHasListener(true);
+ hidePlaylistButton.setAction(1, "Close");
+ hidePlaylistButton.setOnOpListener((JavaScriptCallback) e -> closePlaylist());
+ hidePlaylistButton.setName("Playlist");
+ hidePlaylistButton.revalidate();
+
+ addPlaylistSongButton = playlistBox.createChild(-1, WidgetType.GRAPHIC);
+ addPlaylistSongButton.setSpriteId(SpriteID.BANK_ADD_TAB_ICON);
+ addPlaylistSongButton.setOriginalWidth(36);
+ addPlaylistSongButton.setOriginalHeight(32);
+ addPlaylistSongButton.setXPositionMode(WidgetPositionMode.ABSOLUTE_LEFT);
+ addPlaylistSongButton.setOriginalX(0);
+ addPlaylistSongButton.setOriginalY(6);
+ addPlaylistSongButton.setHasListener(true);
+ addPlaylistSongButton.setAction(1, "Add to");
+ addPlaylistSongButton.setOnOpListener((JavaScriptCallback) e -> addSongFromInput());
+ addPlaylistSongButton.setName("Playlist");
+ addPlaylistSongButton.revalidate();
+
+ if (playlistSong != null)
+ {
+ playlistSong.setHidden(false);
+ }
+ }
+
+ private void closePlaylist()
+ {
+ playlistMode = false;
+
+ Widget allInGameSongs = client.getWidget(WidgetInfo.MUSICTAB_ALL_SONGS);
+ Widget musicScrollbar = client.getWidget(WidgetInfo.MUSICTAB_SCROLLBAR);
+ allInGameSongs.setHidden(false);
+ musicScrollbar.setHidden(false);
+
+ client.getWidget(WidgetInfo.MUSICTAB_UNLOCKED_SONGS).setText(defaultUnlockedSongs);
+ playlistText.setHidden(true);
+ addPlaylistSongButton.setHidden(true);
+ hidePlaylistButton.setHidden(true);
+
+ if (playlistSong != null)
+ {
+ playlistSong.setHidden(true);
+ }
+ }
+
+ private void addSongFromInput()
+ {
+ addPlaylistSongButton.setAction(1, "Close search");
+ addPlaylistSongButton.setOnOpListener((JavaScriptCallback) e -> closeInput());
+ songInput = chatboxPanelManager.openTextInput("Please type a valid song name")
+ .onChanged(s -> clientThread.invokeLater(() -> updateSongs(s)))
+ .onClose(() ->
+ {
+ clientThread.invokeLater(() -> updateSongs(songInput.getValue()));
+ addPlaylistSongButton.setOnOpListener((JavaScriptCallback) e -> addSongFromInput());
+ addPlaylistSongButton.setAction(1, "Add to");
+ })
+ .build();
+ }
+
+ private void updateSongs()
+ {
+ String song = "";
+ if (chatboxIsOpen())
+ {
+ song = songInput.getValue();
+ }
+ updateSongs(song);
+ }
+
+ private void updateSongs(String song)
+ {
+ if (playlistBox == null)
+ {
+ return;
+ }
+
+ if (new File(System.getProperty("user.home") + "/RuneLiteAudio/MIDI Files/" +
+ "Music/" + song + ".mid/").exists())
+ {
+ playListSongPlayer(song);
+ }
+ }
+
+ private void playListSongPlayer(String song)
+ {
+ if (!song.equals(songName) && !chatboxIsOpen() && playlistCount < 10)
+ {
+ Widget playlistWidget = client.getWidget(WidgetInfo.MUSICTAB_INTERFACE);
+ playlistSong = playlistWidget.createChild(-1, WidgetType.TEXT);
+ playlistSong.setText(song);
+ playlistSong.setFontId(495);
+ playlistSong.setOriginalX(12);
+ playlistSong.setOriginalY(newSongY);
+ playlistSong.setOriginalWidth(120);
+ playlistSong.setOriginalHeight(16);
+ playlistSong.setTextColor(client.getWidget(WidgetInfo.MUSICTAB_CURRENT_SONG_NAME).getTextColor());
+ playlistSong.setHasListener(true);
+ playlistSong.setAction(1, "Play");
+ playlistSong.setOnOpListener((JavaScriptCallback) e -> playSongFromList(song));
+ playlistSong.setName(song);
+ playlistSong.revalidate();
+
+ newSongY = newSongY + 15;
+
+ songName = song;
+
+ playlistCount++;
+ client.getWidget(WidgetInfo.MUSICTAB_UNLOCKED_SONGS).setText(playlistCount + " / 10");
+
+ if (playlistCount == 10)
+ {
+ addPlaylistSongButton.setHidden(true);
+ }
+ }
+ }
+
+ private boolean chatboxIsOpen()
+ {
+ return songInput != null && chatboxPanelManager.getCurrentInput() == songInput;
+ }
+
+ private void closeInput()
+ {
+ updateSongs();
+ chatboxPanelManager.close();
+ }
+
+ private void playSongFromList(String song)
+ {
+ client.getWidget(WidgetInfo.MUSICTAB_CURRENT_SONG_NAME).setName(song);
+ File midiMusicFile = new File(System.getProperty("user.home") + "/RuneLiteAudio/MIDI Files/" +
+ "Music/" + song + ".mid/");
+
+ if (realTimeMIDIPlayer.midi == null)
+ {
+ realTimeMIDIPlayer.midi = midiMusicFile;
+ realTimeMIDIPlayer.run();
+ }
+
+ else
+ {
+ if (realTimeMIDIPlayer.isPlaying())
+ {
+ realTimeMIDIPlayer.stopSong();
+ }
+ realTimeMIDIPlayer.midi = midiMusicFile;
+ realTimeMIDIPlayer.run();
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/musicmodifier/RealTimeMIDIPlayer.java b/runelite-client/src/main/java/net/runelite/client/plugins/musicmodifier/RealTimeMIDIPlayer.java
new file mode 100644
index 0000000000..858c7465fd
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/musicmodifier/RealTimeMIDIPlayer.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2019, Rodolfo Ruiz-Velasco
+ * 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.musicmodifier;
+
+import com.sun.media.sound.AudioSynthesizer;
+
+import javax.sound.midi.*;
+import javax.sound.sampled.*;
+import java.io.*;
+import java.util.HashMap;
+import java.util.Map;
+
+public class RealTimeMIDIPlayer implements Runnable
+{
+ private AudioFormat format;
+
+ private Sequence midiSequence;
+
+ private Soundbank soundbank;
+
+ private SourceDataLine sdl;
+
+ private MusicCustomizerPlugin customMusicPlugin;
+
+ private MidiFileAdjuster adjuster;
+
+ private Clip clip;
+
+ public boolean looping = true;
+
+ public File soundFont = new File(System.getProperty("user.home") + "/RuneLiteAudio/SoundFonts/" +
+ "RuneScape 2.sf2/");
+
+ public File midi;
+
+ @Override
+ public void run() {
+
+ try {
+
+ adjuster = new MidiFileAdjuster(); //Unfinished class
+
+ midiSequence = MidiSystem.getSequence(midi);
+ soundbank = MidiSystem.getSoundbank(soundFont);
+ init();
+ }
+ catch (IOException | InvalidMidiDataException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void stopSong()
+ {
+ if (sdl.isRunning())
+ {
+ sdl.stop();
+ }
+ }
+
+ public static AudioSynthesizer findAudioSynthesizer() throws MidiUnavailableException
+ {
+ Synthesizer synth = MidiSystem.getSynthesizer();
+ if (synth instanceof AudioSynthesizer)
+ return (AudioSynthesizer) synth;
+
+ double gain = 0.8D;
+
+ MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
+ MidiChannel[] channels = synth.getChannels();
+
+ for (int i = 0; i < channels.length; i++)
+ {
+ channels[i].controlChange(7, ((int) (channels[i].getController(7) * gain)));
+ }
+
+ for (int i = 0; i < infos.length; i++)
+ {
+ MidiDevice device = MidiSystem.getMidiDevice(infos[i]);
+
+ if (device instanceof AudioSynthesizer)
+ return (AudioSynthesizer) device;
+ }
+ return null;
+ }
+
+ public boolean isPlaying()
+ {
+ return sdl.isActive();
+ }
+
+ public static double send(Sequence sequence, Receiver receiver) {
+ float divtype = sequence.getDivisionType();
+ assert (sequence.getDivisionType() == Sequence.PPQ);
+ Track[] tracks = sequence.getTracks();
+ int[] trackspos = new int[tracks.length];
+ int mpq = 500000;
+ int seqres = sequence.getResolution();
+ long lasttick = 0;
+ long curtime = 0;
+ while (true) {
+ MidiEvent selevent = null;
+ int seltrack = -1;
+ for (int i = 0; i < tracks.length; i++) {
+ int trackpos = trackspos[i];
+ Track track = tracks[i];
+ if (trackpos < track.size()) {
+ MidiEvent event = track.get(trackpos);
+ if (selevent == null
+ || event.getTick() < selevent.getTick()) {
+ selevent = event;
+ seltrack = i;
+ }
+ }
+ }
+ if (seltrack == -1)
+ break;
+ trackspos[seltrack]++;
+ long tick = selevent.getTick();
+ if (divtype == Sequence.PPQ)
+ curtime += ((tick - lasttick) * mpq) / seqres;
+ else
+ curtime = (long) ((tick * 1000000.0 * divtype) / seqres);
+ lasttick = tick;
+ MidiMessage msg = selevent.getMessage();
+ if (msg instanceof MetaMessage) {
+ if (divtype == Sequence.PPQ)
+ if (((MetaMessage) msg).getType() == 0x51) {
+ byte[] data = ((MetaMessage) msg).getData();
+ mpq = ((data[0] & 0xff) << 16)
+ | ((data[1] & 0xff) << 8) | (data[2] & 0xff);
+ }
+ } else {
+ if (receiver != null)
+ receiver.send(msg, curtime);
+ }
+ }
+ return curtime / 1000000.0;
+ }
+
+ public void init() {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+
+ AudioSynthesizer synth = null;
+ try {
+ synth = findAudioSynthesizer();
+ format = new AudioFormat(44100, 16, 2, true, false);
+
+ Map info = new HashMap();
+ info.put("resamplerType", "sinc");
+ info.put("maxPolyphony", "8192");
+ AudioInputStream ais = synth.openStream(format, info);
+ synth.unloadAllInstruments(synth.getDefaultSoundbank());
+ synth.loadAllInstruments(soundbank);
+ double total = send(midiSequence, synth.getReceiver());
+ long length = (long) (ais.getFormat().getFrameRate() * (total + 4));
+ AudioInputStream stream = new AudioInputStream(ais, format, length);
+ sdl = AudioSystem.getSourceDataLine(format);
+ sdl.open(format);
+ sdl.start();
+ writeAudio(sdl, stream);
+ } catch (LineUnavailableException | MidiUnavailableException e) {
+ e.printStackTrace();
+ }
+ }
+ }).start();
+ }
+
+ public void writeAudio(SourceDataLine sdl, AudioInputStream stream)
+ {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+
+ byte[] sampledAudio = new byte[1024];
+
+ int numBytesRead = 0;
+
+ while (numBytesRead != -1) {
+ try
+ {
+ numBytesRead = stream.read(sampledAudio, 0, sampledAudio.length);
+
+ if (numBytesRead >= 0) {
+ sdl.write(sampledAudio, 0, numBytesRead);
+ }
+ }
+
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+
+ finally {
+
+ if (!isPlaying() && looping)
+ {
+ this.run();
+ }
+ }
+ }
+ }
+ }).start();
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/nexthitnotifier/NextHitNotifierConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/nexthitnotifier/NextHitNotifierConfig.java
new file mode 100644
index 0000000000..8bce5b84b7
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/nexthitnotifier/NextHitNotifierConfig.java
@@ -0,0 +1,11 @@
+package net.runelite.client.plugins.nexthitnotifier;
+
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+
+@ConfigGroup("nexthitnotifier")
+public interface NextHitNotifierConfig extends Config
+{
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/nexthitnotifier/NextHitNotifierOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/nexthitnotifier/NextHitNotifierOverlay.java
new file mode 100644
index 0000000000..fe47f37307
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/nexthitnotifier/NextHitNotifierOverlay.java
@@ -0,0 +1,59 @@
+package net.runelite.client.plugins.nexthitnotifier;
+
+import net.runelite.api.Client;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.components.PanelComponent;
+import net.runelite.client.ui.overlay.components.TitleComponent;
+import net.runelite.client.util.MiscUtils;
+
+import javax.inject.Inject;
+import java.awt.*;
+
+public class NextHitNotifierOverlay extends Overlay
+{
+ private final Client client;
+ private final NextHitNotifierPlugin plugin;
+ private final NextHitNotifierConfig config;
+
+ private final PanelComponent panelComponent = new PanelComponent();
+ private final Dimension panelSize = new Dimension(48, 0);
+
+ @Inject
+ private NextHitNotifierOverlay(Client client, NextHitNotifierPlugin plugin, NextHitNotifierConfig config)
+ {
+ setPosition(OverlayPosition.BOTTOM_LEFT);
+ //setPosition(OverlayPosition.DYNAMIC);
+ //setPosition(OverlayPosition.DETACHED);
+
+ this.client = client;
+ this.plugin = plugin;
+ this.config = config;
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ panelComponent.getChildren().clear();
+ panelComponent.setPreferredSize(panelSize);
+
+ String lastHitText = Integer.toString(plugin.lastHit);
+ int lastHit = plugin.lastHit;
+
+ if (plugin.showTime < 0)
+ {
+ lastHitText = "0";
+ lastHit = 0;
+ }
+
+ int g = (int)MiscUtils.clamp((float)Math.floor(lastHit / 30.f) * 255.f, 0.f, 255.f);
+ int r = 255 - g;
+
+ Color textColor = Color.getHSBColor(Color.RGBtoHSB(r, g, 0, null)[0], 1.f, 1.f);
+
+ panelComponent.getChildren().add(TitleComponent.builder().text("Next hit:").color(Color.YELLOW).build());
+ panelComponent.getChildren().add(TitleComponent.builder().text(lastHitText).color(textColor).build());
+
+ return panelComponent.render(graphics);
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/nexthitnotifier/NextHitNotifierPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/nexthitnotifier/NextHitNotifierPlugin.java
new file mode 100644
index 0000000000..ccece4d4a4
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/nexthitnotifier/NextHitNotifierPlugin.java
@@ -0,0 +1,116 @@
+package net.runelite.client.plugins.nexthitnotifier;
+
+import net.runelite.client.eventbus.Subscribe;
+import com.google.inject.Provides;
+import net.runelite.api.Client;
+import net.runelite.api.GameState;
+import net.runelite.api.Skill;
+import net.runelite.api.events.ExperienceChanged;
+import net.runelite.api.events.GameStateChanged;
+import net.runelite.api.events.GameTick;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+
+import javax.inject.Inject;
+
+@PluginDescriptor(
+ name = "!Next Hit Notifier",
+ description = "Shows estimated next hit based on xp drop.",
+ tags = { "experience", "damage", "overlay", "pking", "bogla" },
+ enabledByDefault = false
+)
+public class NextHitNotifierPlugin extends Plugin
+{
+ @Inject
+ private Client client;
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private NextHitNotifierOverlay overlay;
+
+ private int lastHpXp = 0;
+ int lastHit = 0;
+ int showTime = 0;
+
+ @Provides
+ NextHitNotifierConfig getConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(NextHitNotifierConfig.class);
+ }
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ overlayManager.add(overlay);
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ overlayManager.remove(overlay);
+ }
+
+ @Subscribe
+ public void onGameStateChanged(GameStateChanged event)
+ {
+ if (event.getGameState() == GameState.LOGGED_IN)
+ {
+ lastHpXp = client.getSkillExperience(Skill.HITPOINTS);
+ lastHit = 0;
+ showTime = 0;
+ }
+ else
+ {
+ lastHpXp = 0;
+ lastHit = 0;
+ showTime = 0;
+ }
+ }
+
+ @Subscribe
+ public void onGameTick(GameTick event)
+ {
+ if (showTime > 0)
+ showTime--;
+ else
+ lastHit = 0;
+ }
+
+ @Subscribe
+ public void onExperienceChanged(ExperienceChanged event)
+ {
+ if (client.getGameState() != GameState.LOGGED_IN)
+ {
+ lastHpXp = 0;
+ lastHit = 0;
+ showTime = 0;
+ return;
+ }
+
+ final Skill skill = event.getSkill();
+
+ if (skill != Skill.HITPOINTS)
+ return;
+
+ final int currentXp = client.getSkillExperience(skill);
+
+ int gainedXp = currentXp - lastHpXp;
+
+ //filter out big xp drops (such as login)
+ if (gainedXp > 1000)
+ {
+ lastHpXp = client.getSkillExperience(skill);
+ return;
+ }
+
+ lastHit = (int)Math.rint(gainedXp / 1.33f);
+ lastHpXp = currentXp;
+ showTime = 3;
+ }
+
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionConfig.java
new file mode 100644
index 0000000000..74a733a7eb
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionConfig.java
@@ -0,0 +1,55 @@
+package net.runelite.client.plugins.pkvision;
+
+import java.awt.Color;
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("pkvision")
+public interface PKVisionConfig extends Config
+{
+ @ConfigItem(position = 0, keyName = "drawOwnName", name = "Highlight own player", description = "Configures whether or not your own player should be highlighted")
+ default boolean highlightOwnPlayer()
+ {
+ return false;
+ }
+
+ @ConfigItem(position = 1, keyName = "ownNameColor", name = "Own player color", description = "Color of your own player")
+ default Color getOwnPlayerColor()
+ {
+ return new Color(0, 184, 212);
+ }
+
+ @ConfigItem(position = 2, keyName = "drawFriendNames", name = "Highlight friends", description = "Configures whether or not friends should be highlighted")
+ default boolean highlightFriends()
+ {
+ return true;
+ }
+
+ @ConfigItem(position = 3, keyName = "friendNameColor", name = "Friend color", description = "Color of friend names" )
+ default Color getFriendColor()
+ {
+ return new Color(0, 200, 80);
+ }
+
+ @ConfigItem(position = 4, keyName = "drawPlayerTiles", name = "Draw tiles under players", description = "Configures whether or not tiles under highlighted players should be drawn")
+ default boolean drawTiles()
+ {
+ return false;
+ }
+
+ @ConfigItem(position = 5, keyName = "drawPlayerNames", name = "Draw names above players", description = "Configures whether or not player names should be drawn above players")
+ default boolean drawPlayerNames() { return true; }
+
+ @ConfigItem(position = 6, keyName = "drawPlayerLevels", name = "Draw levels above players", description = "Configures whether or not player levels should be drawn above players")
+ default boolean drawPlayerLevels()
+ {
+ return true;
+ }
+
+ //@ConfigItem(position = 7, keyName = "drawPlayerHealth", name = "Draw health above players", description = "Configures whether or not player levels should be drawn above players")
+ //default boolean drawPlayerHealth()
+ //{
+ // return true;
+ //}
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionMinimapOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionMinimapOverlay.java
new file mode 100644
index 0000000000..fc844eb734
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionMinimapOverlay.java
@@ -0,0 +1,43 @@
+package net.runelite.client.plugins.pkvision;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import net.runelite.api.Player;
+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.OverlayPriority;
+import net.runelite.client.ui.overlay.OverlayUtil;
+
+@Singleton
+public class PKVisionMinimapOverlay extends Overlay
+{
+ private final PKVisionService pkVisionService;
+
+ @Inject
+ private PKVisionMinimapOverlay(PKVisionService pkVisionService)
+ {
+ this.pkVisionService = pkVisionService;
+ setLayer(OverlayLayer.ABOVE_WIDGETS);
+ setPosition(OverlayPosition.DYNAMIC);
+ setPriority(OverlayPriority.HIGH);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ pkVisionService.forEachPlayer((player, color) -> renderPlayerOverlay(graphics, player, color));
+ return null;
+}
+
+ private void renderPlayerOverlay(Graphics2D graphics, Player actor, Color color)
+ {
+ final net.runelite.api.Point minimapLocation = actor.getMinimapLocation();
+
+ if (minimapLocation != null)
+ OverlayUtil.renderTextLocation(graphics, minimapLocation, Integer.toString(actor.getCombatLevel()), color);
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionOverlay.java
new file mode 100644
index 0000000000..b36b91da7b
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionOverlay.java
@@ -0,0 +1,55 @@
+package net.runelite.client.plugins.pkvision;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import net.runelite.api.Player;
+import net.runelite.api.Point;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.OverlayPriority;
+import net.runelite.client.ui.overlay.OverlayUtil;
+
+@Singleton
+public class PKVisionOverlay extends Overlay
+{
+ private final PKVisionService pkVisionService;
+ private final PKVisionConfig config;
+
+ @Inject
+ private PKVisionOverlay(PKVisionConfig config, PKVisionService pkVisionService, PKVisionPlugin pkVisionPlugin)
+ {
+ this.config = config;
+ this.pkVisionService = pkVisionService;
+ setPosition(OverlayPosition.DYNAMIC);
+ setPriority(OverlayPriority.MED);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ pkVisionService.forEachPlayer((player, color) -> renderPlayerOverlay(graphics, player, color));
+ return null;
+ }
+
+ private void renderPlayerOverlay(Graphics2D graphics, Player actor, Color color)
+ {
+ if (!config.drawPlayerNames() && !config.drawPlayerLevels())
+ return;
+
+ String text = "";
+ if (config.drawPlayerLevels())
+ text += "(" + actor.getCombatLevel() + ") ";
+
+ if (config.drawPlayerNames())
+ text += actor.getName().replace('\u00A0', ' ');
+
+ Point textLocation = actor.getCanvasTextLocation(graphics, text, actor.getLogicalHeight() + 40);
+
+ if (textLocation != null)
+ OverlayUtil.renderTextLocation(graphics, textLocation, text, color);
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionPlugin.java
new file mode 100644
index 0000000000..6fc74fb2c2
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionPlugin.java
@@ -0,0 +1,135 @@
+package net.runelite.client.plugins.pkvision;
+
+import net.runelite.client.eventbus.Subscribe;
+import com.google.inject.Provides;
+import java.awt.Color;
+import javax.inject.Inject;
+import net.runelite.api.Client;
+import static net.runelite.api.MenuAction.*;
+import net.runelite.api.MenuEntry;
+import net.runelite.api.Player;
+import net.runelite.api.events.MenuEntryAdded;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+import net.runelite.client.util.ColorUtil;
+import net.runelite.client.util.MiscUtils;
+import net.runelite.client.util.Text;
+
+@PluginDescriptor(
+ name = "!PK Vision",
+ description = "Highlight players on-screen and/or on the minimap",
+ tags = {"highlight", "minimap", "overlay", "players", "pk", "helper", "vision", "bogla"},
+ enabledByDefault = false
+)
+public class PKVisionPlugin extends Plugin
+{
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private PKVisionConfig config;
+
+ @Inject
+ private PKVisionOverlay pkVisionOverlay;
+
+ @Inject
+ private PKVisionTileOverlay pkVisionTileOverlay;
+
+ @Inject
+ private PKVisionMinimapOverlay pkVisionMinimapOverlay;
+
+ @Inject
+ private Client client;
+
+ @Provides
+ PKVisionConfig provideConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(PKVisionConfig.class);
+ }
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ overlayManager.add(pkVisionOverlay);
+ overlayManager.add(pkVisionTileOverlay);
+ overlayManager.add(pkVisionMinimapOverlay);
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ overlayManager.remove(pkVisionOverlay);
+ overlayManager.remove(pkVisionTileOverlay);
+ overlayManager.remove(pkVisionMinimapOverlay);
+ }
+
+ @Subscribe
+ public void onMenuEntryAdded(MenuEntryAdded menuEntryAdded)
+ {
+ int type = menuEntryAdded.getType();
+ String option = Text.removeTags(menuEntryAdded.getOption()).toLowerCase();
+
+ if (type >= 2000)
+ type -= 2000;
+
+ int identifier = menuEntryAdded.getIdentifier();
+ if (type == FOLLOW.getId() || type == TRADE.getId()
+ || type == ITEM_USE_ON_PLAYER.getId() || type == PLAYER_FIRST_OPTION.getId()
+ || type == PLAYER_SECOND_OPTION.getId() || type == PLAYER_THIRD_OPTION.getId()
+ || type == PLAYER_FOURTH_OPTION.getId() || type == PLAYER_FIFTH_OPTION.getId()
+ || type == PLAYER_SIXTH_OPTION.getId() || type == PLAYER_SEVENTH_OPTION.getId()
+ || type == PLAYER_EIGTH_OPTION.getId() || type == SPELL_CAST_ON_PLAYER.getId()
+ || type == RUNELITE.getId())
+ {
+ final Player localPlayer = client.getLocalPlayer();
+ Player[] players = client.getCachedPlayers();
+ Player player = null;
+
+ if (identifier >= 0 && identifier < players.length)
+ player = players[identifier];
+
+ if (player == null)
+ return;
+
+ Color color = null;
+
+ if (config.highlightFriends() && (player.isFriend() || player.isClanMember()))
+ {
+ color = config.getFriendColor();
+ }
+ else if (!player.isFriend() && !player.isClanMember())
+ {
+ int lvlDelta = player.getCombatLevel() - localPlayer.getCombatLevel();
+ int wildyLvl = MiscUtils.getWildernessLevelFrom(client, player.getWorldLocation());
+
+ if (wildyLvl <= 0)
+ return;
+
+ int R = MiscUtils.clamp((int)(((float)(lvlDelta + wildyLvl) / (float)(wildyLvl * 2)) * 255.f), 0, 255);
+ int G = MiscUtils.clamp(255 - R, 0, 255);
+
+ if (Math.abs(lvlDelta) <= wildyLvl)
+ color = Color.getHSBColor(Color.RGBtoHSB(R, G, 0, null)[0], 1.f, 1.f);
+ }
+
+ if (color != null)
+ {
+ MenuEntry[] menuEntries = client.getMenuEntries();
+ MenuEntry lastEntry = menuEntries[menuEntries.length - 1];
+
+ // strip out existing ');
+ if (idx != -1)
+ target = target.substring(idx + 1);
+
+ lastEntry.setTarget(ColorUtil.prependColorTag(target, color));
+
+
+ client.setMenuEntries(menuEntries);
+ }
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionService.java b/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionService.java
new file mode 100644
index 0000000000..770cbd6505
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionService.java
@@ -0,0 +1,63 @@
+package net.runelite.client.plugins.pkvision;
+
+import java.awt.Color;
+import java.util.function.BiConsumer;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import net.runelite.api.Client;
+import net.runelite.api.Player;
+import net.runelite.client.util.MiscUtils;
+
+@Singleton
+public class PKVisionService
+{
+ private final Client client;
+ private final PKVisionConfig config;
+
+ @Inject
+ private PKVisionService(Client client, PKVisionConfig config)
+ {
+ this.config = config;
+ this.client = client;
+ }
+
+ public void forEachPlayer(final BiConsumer consumer)
+ {
+ final Player localPlayer = client.getLocalPlayer();
+
+ for (Player player : client.getPlayers())
+ {
+ if (player == null || player.getName() == null)
+ continue;
+
+ if (player == localPlayer)
+ {
+ if (config.highlightOwnPlayer())
+ consumer.accept(player, config.getOwnPlayerColor());
+
+ continue;
+ }
+
+ if (config.highlightFriends() && (player.isFriend() || player.isClanMember()))
+ {
+ consumer.accept(player, config.getFriendColor());
+ }
+ else if (player != localPlayer && !player.isFriend() && !player.isClanMember())
+ {
+ int lvlDelta = player.getCombatLevel() - localPlayer.getCombatLevel();
+ int wildyLvl = MiscUtils.getWildernessLevelFrom(client, player.getWorldLocation());
+
+ if (wildyLvl <= 0)
+ continue;
+
+ if (Math.abs(lvlDelta) > wildyLvl)
+ continue;
+
+ int R = MiscUtils.clamp((int)(((float)(lvlDelta + wildyLvl) / (float)(wildyLvl * 2)) * 255.f), 0, 255);
+ int G = MiscUtils.clamp(255 - R, 0, 255);
+
+ consumer.accept(player, Color.getHSBColor(Color.RGBtoHSB(R, G, 0, null)[0], 1.f, 1.f));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionTileOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionTileOverlay.java
new file mode 100644
index 0000000000..aba6c3fad6
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/pkvision/PKVisionTileOverlay.java
@@ -0,0 +1,44 @@
+package net.runelite.client.plugins.pkvision;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Polygon;
+import javax.inject.Inject;
+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.OverlayPriority;
+import net.runelite.client.ui.overlay.OverlayUtil;
+
+public class PKVisionTileOverlay extends Overlay
+{
+ private final PKVisionService pkVisionService;
+ private final PKVisionConfig config;
+
+ @Inject
+ private PKVisionTileOverlay(PKVisionConfig config, PKVisionService pkVisionService)
+ {
+ this.config = config;
+ this.pkVisionService = pkVisionService;
+ setLayer(OverlayLayer.ABOVE_SCENE);
+ setPosition(OverlayPosition.DYNAMIC);
+ setPriority(OverlayPriority.MED);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if (!config.drawTiles())
+ return null;
+
+ pkVisionService.forEachPlayer((player, color) ->
+ {
+ final Polygon poly = player.getCanvasTilePoly();
+
+ if (poly != null)
+ OverlayUtil.renderPolygon(graphics, poly, color);
+ });
+
+ return null;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/plankmakehelper/PlankMakeOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/plankmakehelper/PlankMakeOverlay.java
new file mode 100644
index 0000000000..8295f294b1
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/plankmakehelper/PlankMakeOverlay.java
@@ -0,0 +1,88 @@
+package net.runelite.client.plugins.plankmakehelper;
+
+import net.runelite.api.*;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.api.widgets.WidgetItem;
+import net.runelite.client.ui.overlay.*;
+
+import javax.inject.Inject;
+import java.awt.*;
+
+public class PlankMakeOverlay extends Overlay {
+
+ private final PlankMakePlugin plugin;
+ private final Client client;
+
+ @Inject
+ public PlankMakeOverlay(final PlankMakePlugin plugin, final Client client) {
+ super(plugin);
+ this.plugin = plugin;
+ this.client = client;
+
+ setPosition(OverlayPosition.DETACHED);
+ setLayer(OverlayLayer.ALWAYS_ON_TOP);
+ setPriority(OverlayPriority.MED);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics) {
+ if (hasPlankableItems()) {
+ renderInventory(graphics);
+ renderPlankMakeSpell(graphics);
+ }
+ return null;
+ }
+
+ private void renderInventory(Graphics2D graphics) {
+ Widget inventory = client.getWidget(WidgetInfo.INVENTORY);
+
+ int firstItemSeenIndex = -1;
+
+ if (inventory != null) {
+ for (WidgetItem item : inventory.getWidgetItems()) {
+ if (PlankMakePlugin.isLogAndPlankable(item.getId())) {
+ if (firstItemSeenIndex == -1) {
+ firstItemSeenIndex = item.getIndex();
+ }
+ if (!inventory.isHidden()) {
+ if (item.getIndex() != firstItemSeenIndex) {
+ OverlayUtil.renderPolygon(graphics, RectangleToPolygon(item.getCanvasBounds()), Color.BLUE);
+ }
+ }
+ }
+ }
+ if (firstItemSeenIndex != -1) {
+ OverlayUtil.renderPolygon(graphics, RectangleToPolygon(inventory.getWidgetItem(firstItemSeenIndex).getCanvasBounds()), Color.CYAN);
+ }
+ }
+ }
+
+ private void renderPlankMakeSpell(Graphics2D graphics) {
+ Widget plankMakeSpell = client.getWidget(218,128);
+ if (plankMakeSpell != null && (plankMakeSpell.getCanvasLocation().getX() != 29 & plankMakeSpell.getCanvasLocation().getY() != 32)) {
+ OverlayUtil.renderPolygon(graphics, RectangleToPolygon(plankMakeSpell.getBounds()), Color.CYAN);
+ }
+ }
+
+ private boolean hasPlankableItems() {
+ ItemContainer invo = client.getItemContainer(InventoryID.INVENTORY);
+ if (invo != null) {
+ if (invo.getItems().length > 0) {
+ for (Item item : invo.getItems()) {
+ if (PlankMakePlugin.isLogAndPlankable(item.getId())) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ static Polygon RectangleToPolygon(Rectangle rect) {
+ int[] xpoints = {rect.x, rect.x + rect.width, rect.x + rect.width, rect.x};
+ int[] ypoints = {rect.y, rect.y, rect.y + rect.height, rect.y + rect.height};
+ return new Polygon(xpoints, ypoints, 4);
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/plankmakehelper/PlankMakePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/plankmakehelper/PlankMakePlugin.java
new file mode 100644
index 0000000000..4c5a72001e
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/plankmakehelper/PlankMakePlugin.java
@@ -0,0 +1,49 @@
+package net.runelite.client.plugins.plankmakehelper;
+
+import net.runelite.api.Client;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+
+import javax.inject.Inject;
+
+@PluginDescriptor(
+ name = "Plank Make Helper",
+ description = "Highlights planks and plank make spell",
+ tags = {"overlay", "plankmaking", "lunar", "money", "moneymaking", "gp"}
+)
+
+public class PlankMakePlugin extends Plugin {
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private PlankMakeOverlay overlay;
+
+ @Override
+ protected void startUp() {
+ overlayManager.add(overlay);
+ }
+
+ @Override
+ protected void shutDown() {
+ overlayManager.remove(overlay);
+ }
+
+ static boolean isLogAndPlankable(int itemID) {
+ switch (itemID) {
+ case 6332: //mahogany
+ case 1521: //oak
+ case 6333: //teak
+ case 1511: //plain
+ return true;
+ default:
+ return false;
+ }
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsConfig.java
index 6b032bc8ee..a0d6bf7223 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsConfig.java
@@ -196,4 +196,15 @@ public interface PlayerIndicatorsConfig extends Config
{
return true;
}
+
+ @ConfigItem(
+ position = 15,
+ keyName = "showCombatLevels",
+ name = "Show combat levels",
+ description = "Add combat level to overhead name"
+ )
+ default boolean showCombatLevels()
+ {
+ return true;
+ }
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsOverlay.java
index 5373fc1eb4..98d88168a6 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsOverlay.java
@@ -46,7 +46,6 @@ public class PlayerIndicatorsOverlay extends Overlay
{
private static final int ACTOR_OVERHEAD_TEXT_MARGIN = 40;
private static final int ACTOR_HORIZONTAL_TEXT_MARGIN = 10;
-
private final PlayerIndicatorsService playerIndicatorsService;
private final PlayerIndicatorsConfig config;
private final ClanManager clanManager;
@@ -88,7 +87,11 @@ public class PlayerIndicatorsOverlay extends Overlay
zOffset = actor.getLogicalHeight() + ACTOR_OVERHEAD_TEXT_MARGIN;
}
- final String name = Text.sanitize(actor.getName());
+ String name = Text.sanitize(actor.getName());
+ if (config.showCombatLevels())
+ {
+ name = name + " (" + actor.getCombatLevel() + ")";
+ }
Point textLocation = actor.getCanvasTextLocation(graphics, name, zOffset);
if (drawPlayerNamesConfig == PlayerNameLocation.MODEL_RIGHT)
@@ -110,7 +113,7 @@ public class PlayerIndicatorsOverlay extends Overlay
if (config.showClanRanks() && actor.isClanMember())
{
- final ClanMemberRank rank = clanManager.getRank(name);
+ ClanMemberRank rank = clanManager.getRank(actor.getName());
if (rank != ClanMemberRank.UNRANKED)
{
@@ -145,4 +148,4 @@ public class PlayerIndicatorsOverlay extends Overlay
OverlayUtil.renderTextLocation(graphics, textLocation, name, color);
}
-}
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsPlugin.java
index e545f92dff..d1c65649ad 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsPlugin.java
@@ -24,6 +24,7 @@
*/
package net.runelite.client.plugins.playerindicators;
+import com.google.common.eventbus.Subscribe;
import com.google.inject.Provides;
import java.awt.Color;
import javax.inject.Inject;
@@ -35,7 +36,6 @@ import net.runelite.api.MenuEntry;
import net.runelite.api.Player;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.client.config.ConfigManager;
-import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.ClanManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
@@ -43,7 +43,7 @@ import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.util.ColorUtil;
@PluginDescriptor(
- name = "Player Indicators",
+ name = "!Player Indicators",
description = "Highlight players on-screen and/or on the minimap",
tags = {"highlight", "minimap", "overlay", "players"}
)
@@ -93,7 +93,7 @@ public class PlayerIndicatorsPlugin extends Plugin
}
@Subscribe
- public void onMenuEntryAdded(MenuEntryAdded menuEntryAdded)
+ public void onMenuEntryAdd(MenuEntryAdded menuEntryAdded)
{
int type = menuEntryAdded.getType();
@@ -132,7 +132,7 @@ public class PlayerIndicatorsPlugin extends Plugin
int image = -1;
Color color = null;
- if (config.highlightFriends() && player.isFriend())
+ if (config.highlightFriends() && client.isFriended(player.getName(), false))
{
color = config.getFriendColor();
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PlayerContainer.java b/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PlayerContainer.java
new file mode 100644
index 0000000000..6c1370ef76
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PlayerContainer.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019, gazivodag
+ * 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.prayagainstplayer;
+
+import net.runelite.api.Player;
+
+/**
+ * Contains a player object
+ * When they attacked me
+ * And (in milliseconds) when to expire the overlay around them
+ */
+public class PlayerContainer {
+
+ private Player player;
+ private long whenTheyAttackedMe;
+ private int millisToExpireHighlight;
+
+ public PlayerContainer(Player player, long whenTheyAttackedMe, int millisToExpireHighlight) {
+ this.player = player;
+ this.whenTheyAttackedMe = whenTheyAttackedMe;
+ this.millisToExpireHighlight = millisToExpireHighlight;
+ }
+
+
+ //getters
+ public Player getPlayer() {
+ return player;
+ }
+ public long getWhenTheyAttackedMe() {
+ return whenTheyAttackedMe;
+ }
+ public int getMillisToExpireHighlight() { return millisToExpireHighlight; };
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PrayAgainstPlayerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PrayAgainstPlayerConfig.java
new file mode 100644
index 0000000000..ce453fd3d9
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PrayAgainstPlayerConfig.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2019, gazivodag
+ * 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.prayagainstplayer;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+import java.awt.*;
+
+@ConfigGroup("prayagainstplayer")
+public interface PrayAgainstPlayerConfig extends Config {
+ @ConfigItem(
+ position = 0,
+ keyName = "attackerPlayerColor",
+ name = "Attacker color",
+ description = "This is the color that will be used to highlight attackers."
+ )
+ default Color attackerPlayerColor() { return new Color(0xFF0006); }
+
+ @ConfigItem(
+ position = 1,
+ keyName = "potentialPlayerColor",
+ name = "Potential Attacker color",
+ description = "This is the color that will be used to highlight potential attackers."
+ )
+ default Color potentialPlayerColor() { return new Color(0xFFFF00); }
+
+ ////
+ @ConfigItem(
+ position = 2,
+ keyName = "attackerTargetTimeout",
+ name = "Attacker Timeout",
+ description = "Seconds until attacker is no longer highlighted."
+ )
+ default int attackerTargetTimeout() { return 10; }
+
+ @ConfigItem(
+ position = 3,
+ keyName = "potentialTargetTimeout",
+ name = "Potential Attacker Timeout",
+ description = "Seconds until potential attacker is no longer highlighted."
+ )
+ default int potentialTargetTimeout() { return 10; }
+
+ @ConfigItem(
+ position = 4,
+ keyName = "newSpawnTimeout",
+ name = "New Player Timeout",
+ description = "Seconds until logged in/spawned player is no longer highlighted."
+ )
+ default int newSpawnTimeout() { return 5; }
+ ////
+
+ ////
+ @ConfigItem(
+ position = 5,
+ keyName = "ignoreFriends",
+ name = "Ignore Friends",
+ description = "This lets you decide whether you want friends to be highlighted by this plugin."
+ )
+ default boolean ignoreFriends() { return true; }
+
+ @ConfigItem(
+ position = 6,
+ keyName = "ignoreClanMates",
+ name = "Ignore Clan Mates",
+ description = "This lets you decide whether you want clan mates to be highlighted by this plugin."
+ )
+ default boolean ignoreClanMates() { return true; }
+ ////
+
+ @ConfigItem(
+ position = 7,
+ keyName = "markNewPlayer",
+ name = "Mark new player as potential attacker",
+ description = "Marks someone that logged in or teleported as a potential attacker for your safety\nDO NOT RUN THIS IN WORLD 1-2 GRAND EXCHANGE!"
+ )
+ default boolean markNewPlayer() { return false; }
+
+ @ConfigItem(
+ position = 8,
+ keyName = "drawTargetPrayAgainst",
+ name = "Draw what to pray on attacker",
+ description = "Tells you what to pray from what weapon the attacker is holding"
+ )
+ default boolean drawTargetPrayAgainst() { return true; }
+
+ @ConfigItem(
+ position = 9,
+ keyName = "drawPotentialTargetPrayAgainst",
+ name = "Draw what to pray on potential attacker",
+ description = "Tells you what to pray from what weapon the potential attacker is holding"
+ )
+ default boolean drawPotentialTargetPrayAgainst() { return true; }
+
+ @ConfigItem(
+ position = 10,
+ keyName = "drawTargetPrayAgainstPrayerTab",
+ name = "Draw what to pray from prayer tab",
+ description = "Tells you what to pray from what weapon the attacker is holding from the prayer tab"
+ )
+ default boolean drawTargetPrayAgainstPrayerTab() { return false; }
+
+ @ConfigItem(
+ position = 11,
+ keyName = "drawTargetsName",
+ name = "Draw name on attacker",
+ description = "Configures whether or not the attacker\'s name should be shown"
+ )
+ default boolean drawTargetsName() { return true; }
+
+ @ConfigItem(
+ position = 12,
+ keyName = "drawPotentialTargetsName",
+ name = "Draw name on potential attacker",
+ description = "Configures whether or not the potential attacker\'s name should be shown"
+ )
+ default boolean drawPotentialTargetsName() { return true; }
+
+ @ConfigItem(
+ position = 13,
+ keyName = "drawTargetHighlight",
+ name = "Draw highlight around attacker",
+ description = "Configures whether or not the attacker should be highlighted"
+ )
+ default boolean drawTargetHighlight() { return true; }
+
+ @ConfigItem(
+ position = 14,
+ keyName = "drawPotentialTargetHighlight",
+ name = "Draw highlight around potential attacker",
+ description = "Configures whether or not the potential attacker should be highlighted"
+ )
+ default boolean drawPotentialTargetHighlight() { return true; }
+
+ @ConfigItem(
+ position = 15,
+ keyName = "drawTargetTile",
+ name = "Draw tile under attacker",
+ description = "Configures whether or not the attacker\'s tile be highlighted"
+ )
+ default boolean drawTargetTile() { return false; }
+
+ @ConfigItem(
+ position = 16,
+ keyName = "drawPotentialTargetTile",
+ name = "Draw tile under potential attacker",
+ description = "Configures whether or not the potential attacker\'s tile be highlighted"
+ )
+ default boolean drawPotentialTargetTile() { return false; }
+
+ @ConfigItem(
+ position = 17,
+ keyName = "drawUnknownWeapons",
+ name = "Draw unknown weapons",
+ description = "Configures whether or not the unknown weapons should be shown when a player equips one"
+ )
+ default boolean drawUnknownWeapons() { return false; }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PrayAgainstPlayerOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PrayAgainstPlayerOverlay.java
new file mode 100644
index 0000000000..e54efd8127
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PrayAgainstPlayerOverlay.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2019, gazivodag
+ * 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.prayagainstplayer;
+
+import net.runelite.api.Client;
+import net.runelite.api.ItemComposition;
+import net.runelite.api.Player;
+import net.runelite.api.kit.KitType;
+import net.runelite.client.ui.overlay.*;
+import net.runelite.client.util.Text;
+import net.runelite.api.Point;
+
+import javax.inject.Inject;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.ConcurrentModificationException;
+
+class PrayAgainstPlayerOverlay extends Overlay {
+
+ private final PrayAgainstPlayerPlugin plugin;
+ private final PrayAgainstPlayerConfig config;
+ private final Client client;
+
+ @Inject
+ private PrayAgainstPlayerOverlay(PrayAgainstPlayerPlugin plugin, PrayAgainstPlayerConfig config, Client client) {
+ super(plugin);
+ this.plugin = plugin;
+ this.config = config;
+ this.client = client;
+
+ setLayer(OverlayLayer.ABOVE_SCENE);
+ setPosition(OverlayPosition.DYNAMIC);
+ setPriority(OverlayPriority.HIGH);
+ }
+
+
+ @Override
+ public Dimension render(Graphics2D graphics) {
+ renderPotentialPlayers(graphics);
+ renderAttackingPlayers(graphics);
+ return null;
+ }
+
+ private void renderPotentialPlayers(Graphics2D graphics) {
+ if (plugin.getPotentialPlayersAttackingMe() == null || !plugin.getPotentialPlayersAttackingMe().isEmpty()) {
+ try {
+ for (PlayerContainer container : plugin.getPotentialPlayersAttackingMe()) {
+ if ((System.currentTimeMillis() > (container.getWhenTheyAttackedMe() + container.getMillisToExpireHighlight())) && (container.getPlayer().getInteracting() != client.getLocalPlayer())) {
+ plugin.removePlayerFromPotentialContainer(container);
+ }
+ if (config.drawPotentialTargetsName()) renderNameAboveHead(graphics, container.getPlayer(), config.potentialPlayerColor());
+ if (config.drawPotentialTargetHighlight()) renderHighlightedPlayer(graphics, container.getPlayer(), config.potentialPlayerColor());
+ if (config.drawPotentialTargetTile()) renderTileUnderPlayer(graphics, container.getPlayer(), config.potentialPlayerColor());
+ if (config.drawPotentialTargetPrayAgainst()) renderPrayAgainstOnPlayer(graphics, container.getPlayer(), config.potentialPlayerColor());
+ }
+ } catch (ConcurrentModificationException e) {
+ }
+ }
+ }
+
+ private void renderAttackingPlayers(Graphics2D graphics) {
+ if (plugin.getPlayersAttackingMe() == null || !plugin.getPlayersAttackingMe().isEmpty()) {
+ try {
+ for (PlayerContainer container : plugin.getPlayersAttackingMe()) {
+ if ((System.currentTimeMillis() > (container.getWhenTheyAttackedMe() + container.getMillisToExpireHighlight())) && (container.getPlayer().getInteracting() != client.getLocalPlayer())) {
+ plugin.removePlayerFromAttackerContainer(container);
+ }
+
+ if (config.drawTargetsName()) renderNameAboveHead(graphics, container.getPlayer(), config.attackerPlayerColor());
+ if (config.drawTargetHighlight()) renderHighlightedPlayer(graphics, container.getPlayer(), config.attackerPlayerColor());
+ if (config.drawTargetTile()) renderTileUnderPlayer(graphics, container.getPlayer(), config.attackerPlayerColor());
+ if (config.drawTargetPrayAgainst()) renderPrayAgainstOnPlayer(graphics, container.getPlayer(), config.attackerPlayerColor());
+ }
+ } catch (ConcurrentModificationException e) {
+ }
+ }
+ }
+
+ private void renderNameAboveHead(Graphics2D graphics, Player player, Color color) {
+ final String name = Text.sanitize(player.getName());
+ final int offset = player.getLogicalHeight() + 40;
+ Point textLocation = player.getCanvasTextLocation(graphics, name, offset);
+ if (textLocation != null) {
+ OverlayUtil.renderTextLocation(graphics, textLocation, name, color);
+ }
+ }
+
+ private void renderHighlightedPlayer(Graphics2D graphics, Player player, Color color) {
+ try {
+ OverlayUtil.renderPolygon(graphics, player.getConvexHull(), color);
+ } catch (NullPointerException e) {
+ }
+ }
+
+ private void renderTileUnderPlayer(Graphics2D graphics, Player player, Color color) {
+ Polygon poly = player.getCanvasTilePoly();
+ OverlayUtil.renderPolygon(graphics, poly, color);
+ }
+
+ private void renderPrayAgainstOnPlayer(Graphics2D graphics, Player player, Color color) {
+ final int offset = (player.getLogicalHeight() / 2) + 75;
+ BufferedImage icon;
+
+ switch (WeaponType.checkWeaponOnPlayer(client, player)) {
+ case WEAPON_MELEE:
+ icon = plugin.getProtectionIcon(WeaponType.WEAPON_MELEE);
+ break;
+ case WEAPON_MAGIC:
+ icon = plugin.getProtectionIcon(WeaponType.WEAPON_MAGIC);
+ break;
+ case WEAPON_RANGED:
+ icon = plugin.getProtectionIcon(WeaponType.WEAPON_RANGED);
+ break;
+ default:
+ icon = null;
+ break;
+ }
+ try {
+ if (icon != null) {
+ Point point = player.getCanvasImageLocation(icon, offset);
+ OverlayUtil.renderImageLocation(graphics, point, icon);
+ } else {
+ if (config.drawUnknownWeapons()) {
+ int itemId = player.getPlayerComposition().getEquipmentId(KitType.WEAPON);
+ ItemComposition itemComposition = client.getItemDefinition(itemId);
+
+ final String str = itemComposition.getName().toUpperCase();
+ Point point = player.getCanvasTextLocation(graphics, str, offset);
+ OverlayUtil.renderTextLocation(graphics, point, str, color);
+ }
+ }
+ } catch (Exception e) {
+ }
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PrayAgainstPlayerOverlayPrayerTab.java b/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PrayAgainstPlayerOverlayPrayerTab.java
new file mode 100644
index 0000000000..4e505675f6
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PrayAgainstPlayerOverlayPrayerTab.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2019, gazivodag
+ * 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.prayagainstplayer;
+
+import net.runelite.api.Client;
+import net.runelite.api.Player;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.client.ui.overlay.*;
+
+import javax.inject.Inject;
+import java.awt.*;
+import java.util.ConcurrentModificationException;
+
+class PrayAgainstPlayerOverlayPrayerTab extends Overlay {
+
+ private final PrayAgainstPlayerPlugin plugin;
+ private final PrayAgainstPlayerConfig config;
+ private final Client client;
+
+ @Inject
+ private PrayAgainstPlayerOverlayPrayerTab (PrayAgainstPlayerPlugin plugin, PrayAgainstPlayerConfig config, Client client) {
+ super(plugin);
+ this.plugin = plugin;
+ this.config = config;
+ this.client = client;
+
+ setPosition(OverlayPosition.DETACHED);
+ setLayer(OverlayLayer.ALWAYS_ON_TOP);
+ setPriority(OverlayPriority.MED);
+ }
+
+
+ @Override
+ public Dimension render(Graphics2D graphics) {
+ if (plugin.getPlayersAttackingMe() == null || !plugin.getPlayersAttackingMe().isEmpty()) {
+ try {
+ for (PlayerContainer container : plugin.getPlayersAttackingMe()) {
+ if (plugin.getPlayersAttackingMe() != null && plugin.getPlayersAttackingMe().size() > 0) {
+ //no reason to show you what prayers to pray in your prayer tab if multiple people are attacking you
+ if ((plugin.getPlayersAttackingMe().size() == 1) && (config.drawTargetPrayAgainstPrayerTab())) {
+ renderPrayerToClick(graphics, container.getPlayer());
+ }
+ }
+ }
+ } catch (ConcurrentModificationException e) {
+ }
+ }
+ return null;
+ }
+
+ private void renderPrayerToClick(Graphics2D graphics, Player player) {
+ Widget PROTECT_FROM_MAGIC = client.getWidget(WidgetInfo.PRAYER_PROTECT_FROM_MAGIC);
+ Widget PROTECT_FROM_RANGED = client.getWidget(WidgetInfo.PRAYER_PROTECT_FROM_MISSILES);
+ Widget PROTECT_FROM_MELEE = client.getWidget(WidgetInfo.PRAYER_PROTECT_FROM_MELEE);
+ Color color = Color.RED;
+ if (PROTECT_FROM_MELEE.isHidden()) return;
+ switch (WeaponType.checkWeaponOnPlayer(client, player)) {
+ case WEAPON_MAGIC:
+ OverlayUtil.renderPolygon(graphics, rectangleToPolygon(PROTECT_FROM_MAGIC.getBounds()), color);
+ break;
+ case WEAPON_MELEE:
+ OverlayUtil.renderPolygon(graphics, rectangleToPolygon(PROTECT_FROM_MELEE.getBounds()), color);
+ break;
+ case WEAPON_RANGED:
+ OverlayUtil.renderPolygon(graphics, rectangleToPolygon(PROTECT_FROM_RANGED.getBounds()), color);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private static Polygon rectangleToPolygon(Rectangle rect) {
+ int[] xpoints = {rect.x, rect.x + rect.width, rect.x + rect.width, rect.x};
+ int[] ypoints = {rect.y, rect.y, rect.y + rect.height, rect.y + rect.height};
+ return new Polygon(xpoints, ypoints, 4);
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PrayAgainstPlayerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PrayAgainstPlayerPlugin.java
new file mode 100644
index 0000000000..5664621b60
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/PrayAgainstPlayerPlugin.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) 2019, gazivodag
+ * 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.prayagainstplayer;
+
+import com.google.inject.Provides;
+import net.runelite.api.*;
+import net.runelite.api.events.*;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.game.SpriteManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+import net.runelite.client.util.ImageUtil;
+
+import javax.inject.Inject;
+import java.awt.*;
+import java.awt.image.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+@PluginDescriptor(
+ name = "!Pray Against Player",
+ description = "Use plugin in PvP situations for best results!!",
+ tags = {"highlight", "pvp", "overlay", "players"}
+)
+
+/**
+ * I am fully aware that there is plenty of overhead and is a MESS!
+ * If you'd like to contribute please do!
+ */
+public class PrayAgainstPlayerPlugin extends Plugin {
+
+ private static final int[] PROTECTION_ICONS = {
+ SpriteID.PRAYER_PROTECT_FROM_MISSILES,
+ SpriteID.PRAYER_PROTECT_FROM_MELEE,
+ SpriteID.PRAYER_PROTECT_FROM_MAGIC
+ };
+ private static final Dimension PROTECTION_ICON_DIMENSION = new Dimension(33, 33);
+ private static final Color PROTECTION_ICON_OUTLINE_COLOR = new Color(33, 33, 33);
+ public final BufferedImage[] ProtectionIcons = new BufferedImage[PROTECTION_ICONS.length];
+
+ private ArrayList potentialPlayersAttackingMe;
+ private ArrayList playersAttackingMe;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private SpriteManager spriteManager;
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private PrayAgainstPlayerOverlay overlay;
+
+ @Inject
+ private PrayAgainstPlayerOverlayPrayerTab overlayPrayerTab;
+
+ @Inject
+ private PrayAgainstPlayerConfig config;
+
+ @Provides
+ PrayAgainstPlayerConfig provideConfig(ConfigManager configManager) {
+ return configManager.getConfig(PrayAgainstPlayerConfig.class);
+ }
+
+ @Subscribe
+ public void onGameStateChanged(GameStateChanged gameStateChanged) {
+ if (gameStateChanged.getGameState() == GameState.LOGGED_IN) {
+ loadProtectionIcons();
+ }
+ }
+
+ @Override
+ protected void startUp() {
+ potentialPlayersAttackingMe = new ArrayList<>();
+ playersAttackingMe = new ArrayList<>();
+ overlayManager.add(overlay);
+ overlayManager.add(overlayPrayerTab);
+ }
+
+ @Override
+ protected void shutDown() throws Exception {
+ overlayManager.remove(overlay);
+ overlayManager.remove(overlayPrayerTab);
+ }
+
+ @Subscribe
+ protected void onAnimationChanged(AnimationChanged animationChanged) {
+ if ((animationChanged.getActor() instanceof Player) && (animationChanged.getActor().getInteracting() instanceof Player) && (animationChanged.getActor().getInteracting() == client.getLocalPlayer())) {
+ Player sourcePlayer = (Player) animationChanged.getActor();
+
+ //is the client is a friend/clan and the config is set to ignore friends/clan dont add them to list
+ if (client.isFriended(sourcePlayer.getName(), true) && config.ignoreFriends()) return;
+ if (client.isClanMember(sourcePlayer.getName()) && config.ignoreClanMates()) return;
+
+ if ((sourcePlayer.getAnimation() != -1) && (!isBlockAnimation(sourcePlayer.getAnimation()))) {
+ //if attacker attacks again, reset his timer so overlay doesn't go away
+ if (findPlayerInAttackerList(sourcePlayer) != null) {
+ resetPlayerFromAttackerContainerTimer(findPlayerInAttackerList(sourcePlayer));
+ }
+ //if he attacks and he was in the potential attackers list, remove him
+ if (!potentialPlayersAttackingMe.isEmpty() && potentialPlayersAttackingMe.contains(findPlayerInPotentialList(sourcePlayer))) {
+ removePlayerFromPotentialContainer(findPlayerInPotentialList(sourcePlayer));
+ }
+ //if he's not in the attackers list, add him
+ if (findPlayerInAttackerList(sourcePlayer) == null) {
+ PlayerContainer container = new PlayerContainer(sourcePlayer, System.currentTimeMillis(), (config.attackerTargetTimeout() * 1000));
+ playersAttackingMe.add(container);
+ }
+ }
+ }
+ }
+
+ @Subscribe
+ protected void onInteractingChanged(InteractingChanged interactingChanged) {
+ //if someone interacts with you, add them to the potential attackers list
+ if ((interactingChanged.getSource() instanceof Player) && (interactingChanged.getTarget() instanceof Player)) {
+ Player sourcePlayer = (Player) interactingChanged.getSource();
+ Player targetPlayer = (Player) interactingChanged.getTarget();
+ if ((targetPlayer == client.getLocalPlayer()) && (findPlayerInPotentialList(sourcePlayer) == null)) { //we're being interacted with
+
+ //is the client is a friend/clan and the config is set to ignore friends/clan dont add them to list
+ if (client.isFriended(sourcePlayer.getName(), true) && config.ignoreFriends()) return;
+ if (client.isClanMember(sourcePlayer.getName()) && config.ignoreClanMates()) return;
+
+ PlayerContainer container = new PlayerContainer(sourcePlayer, System.currentTimeMillis(), (config.potentialTargetTimeout() * 1000));
+ potentialPlayersAttackingMe.add(container);
+ }
+ }
+ }
+
+ @Subscribe
+ protected void onPlayerDespawned(PlayerDespawned playerDespawned) {
+ PlayerContainer container = findPlayerInAttackerList(playerDespawned.getPlayer());
+ PlayerContainer container2 = findPlayerInPotentialList(playerDespawned.getPlayer());
+ if (container != null) {
+ playersAttackingMe.remove(container);
+ }
+ if (container2 != null) {
+ potentialPlayersAttackingMe.remove(container2);
+ }
+ }
+
+ @Subscribe
+ protected void onPlayerSpawned(PlayerSpawned playerSpawned) {
+ if (config.markNewPlayer()) {
+ Player p = playerSpawned.getPlayer();
+
+ if (client.isFriended(p.getName(), true) && config.ignoreFriends()) return;
+ if (client.isClanMember(p.getName()) && config.ignoreClanMates()) return;
+
+ PlayerContainer container = findPlayerInPotentialList(p);
+ if (container == null) {
+ container = new PlayerContainer(p, System.currentTimeMillis(), (config.newSpawnTimeout() * 1000));
+ potentialPlayersAttackingMe.add(container);
+ }
+ }
+ }
+
+ PlayerContainer findPlayerInAttackerList(Player player) {
+ if (playersAttackingMe.isEmpty()) {
+ return null;
+ }
+ for (int i = 0 ; i < playersAttackingMe.size() ; i++) {
+ PlayerContainer container = playersAttackingMe.get(i);
+ if (container.getPlayer() == player) {
+ return container;
+ }
+ }
+ return null;
+ }
+
+ PlayerContainer findPlayerInPotentialList(Player player) {
+ if (potentialPlayersAttackingMe.isEmpty()) {
+ return null;
+ }
+ for (int i = 0 ; i < potentialPlayersAttackingMe.size() ; i++) {
+ PlayerContainer container = potentialPlayersAttackingMe.get(i);
+ if (container.getPlayer() == player) {
+ return container;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Resets player timer in case he attacks again, so his highlight doesn't go away so easily
+ * @param container
+ */
+ public void resetPlayerFromAttackerContainerTimer(PlayerContainer container) {
+ removePlayerFromAttackerContainer(container);
+ PlayerContainer newContainer = new PlayerContainer(container.getPlayer(), System.currentTimeMillis(), (config.attackerTargetTimeout() * 1000));
+ playersAttackingMe.add(newContainer);
+ }
+
+
+ public void removePlayerFromPotentialContainer(PlayerContainer container) {
+ if ((potentialPlayersAttackingMe != null) && (!potentialPlayersAttackingMe.isEmpty()) && (potentialPlayersAttackingMe.contains(container))) {
+ potentialPlayersAttackingMe.remove(container);
+ }
+ }
+
+ public void removePlayerFromAttackerContainer(PlayerContainer container) {
+ if ((playersAttackingMe != null) && (!playersAttackingMe.isEmpty()) && (playersAttackingMe.contains(container))) {
+ playersAttackingMe.remove(container);
+ }
+ }
+
+ private boolean isBlockAnimation(int anim) {
+ switch (anim) {
+ case AnimationID.BLOCK_DEFENDER:
+ case AnimationID.BLOCK_NO_SHIELD:
+ case AnimationID.BLOCK_SHIELD:
+ case AnimationID.BLOCK_SWORD:
+ case AnimationID.BLOCK_UNARMED:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public ArrayList getPotentialPlayersAttackingMe() { return potentialPlayersAttackingMe; }
+ public ArrayList getPlayersAttackingMe() { return playersAttackingMe; }
+
+ //All of the methods below are from the Zulrah plugin!!! Credits to it's respective owner
+ private void loadProtectionIcons() {
+ final IndexedSprite[] protectionIcons = {};
+ final IndexedSprite[] newProtectionIcons = Arrays.copyOf(protectionIcons, PROTECTION_ICONS.length);
+ int curPosition = 0;
+
+ for (int i = 0; i < PROTECTION_ICONS.length; i++, curPosition++)
+ {
+ final int resource = PROTECTION_ICONS[i];
+ ProtectionIcons[i] = rgbaToIndexedBufferedImage(ProtectionIconFromSprite(spriteManager.getSprite(resource, 0)));
+ newProtectionIcons[curPosition] = createIndexedSprite(client, ProtectionIcons[i]);
+ }
+ }
+
+ private static IndexedSprite createIndexedSprite(final Client client, final BufferedImage bufferedImage) {
+ final IndexColorModel indexedCM = (IndexColorModel) bufferedImage.getColorModel();
+
+ final int width = bufferedImage.getWidth();
+ final int height = bufferedImage.getHeight();
+ final byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
+ final int[] palette = new int[indexedCM.getMapSize()];
+ indexedCM.getRGBs(palette);
+
+ final IndexedSprite newIndexedSprite = client.createIndexedSprite();
+ newIndexedSprite.setPixels(pixels);
+ newIndexedSprite.setPalette(palette);
+ newIndexedSprite.setWidth(width);
+ newIndexedSprite.setHeight(height);
+ newIndexedSprite.setOriginalWidth(width);
+ newIndexedSprite.setOriginalHeight(height);
+ newIndexedSprite.setOffsetX(0);
+ newIndexedSprite.setOffsetY(0);
+ return newIndexedSprite;
+ }
+
+ private static BufferedImage rgbaToIndexedBufferedImage(final BufferedImage sourceBufferedImage) {
+ final BufferedImage indexedImage = new BufferedImage(
+ sourceBufferedImage.getWidth(),
+ sourceBufferedImage.getHeight(),
+ BufferedImage.TYPE_BYTE_INDEXED);
+
+ final ColorModel cm = indexedImage.getColorModel();
+ final IndexColorModel icm = (IndexColorModel) cm;
+
+ final int size = icm.getMapSize();
+ final byte[] reds = new byte[size];
+ final byte[] greens = new byte[size];
+ final byte[] blues = new byte[size];
+ icm.getReds(reds);
+ icm.getGreens(greens);
+ icm.getBlues(blues);
+
+ final WritableRaster raster = indexedImage.getRaster();
+ final int pixel = raster.getSample(0, 0, 0);
+ final IndexColorModel resultIcm = new IndexColorModel(8, size, reds, greens, blues, pixel);
+ final BufferedImage resultIndexedImage = new BufferedImage(resultIcm, raster, sourceBufferedImage.isAlphaPremultiplied(), null);
+ resultIndexedImage.getGraphics().drawImage(sourceBufferedImage, 0, 0, null);
+ return resultIndexedImage;
+ }
+
+ private static BufferedImage ProtectionIconFromSprite(final BufferedImage freezeSprite) {
+ final BufferedImage freezeCanvas = ImageUtil.resizeCanvas(freezeSprite, PROTECTION_ICON_DIMENSION.width, PROTECTION_ICON_DIMENSION.height);
+ return ImageUtil.outlineImage(freezeCanvas, PROTECTION_ICON_OUTLINE_COLOR);
+ }
+
+ BufferedImage getProtectionIcon(WeaponType weaponType) {
+ switch (weaponType) {
+ case WEAPON_RANGED:
+ return ProtectionIcons[0];
+ case WEAPON_MELEE:
+ return ProtectionIcons[1];
+ case WEAPON_MAGIC:
+ return ProtectionIcons[2];
+ }
+ return null;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/WeaponType.java b/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/WeaponType.java
new file mode 100644
index 0000000000..1dc00c8311
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/prayagainstplayer/WeaponType.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2019, gazivodag
+ * 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.prayagainstplayer;
+
+import net.runelite.api.Client;
+import net.runelite.api.ItemComposition;
+import net.runelite.api.Player;
+import net.runelite.api.kit.KitType;
+
+enum WeaponType {
+
+ WEAPON_MELEE,
+ WEAPON_RANGED,
+ WEAPON_MAGIC,
+ WEAPON_UNKNOWN;
+
+ /**
+ * im fully aware this could of been done better!!!
+ * @param client
+ * @param attacker
+ * @return
+ */
+ public static WeaponType checkWeaponOnPlayer (Client client, Player attacker) {
+ int itemId = attacker.getPlayerComposition().getEquipmentId(KitType.WEAPON);
+ ItemComposition itemComposition = client.getItemDefinition(itemId);
+ String weaponNameGivenLowerCase = itemComposition.getName().toLowerCase();
+
+ if (itemId == -1) return WEAPON_MELEE;
+ if (weaponNameGivenLowerCase == null || weaponNameGivenLowerCase.toLowerCase().contains("null")) return WEAPON_MELEE;
+
+ for (String meleeWeaponName : meleeWeaponNames) {
+ if (weaponNameGivenLowerCase.contains(meleeWeaponName) && !weaponNameGivenLowerCase.contains("thrownaxe")) {
+ return WEAPON_MELEE;
+ }
+ }
+
+ for (String rangedWeaponName : rangedWeaponNames) {
+ if (weaponNameGivenLowerCase.contains(rangedWeaponName)) {
+ return WEAPON_RANGED;
+ }
+ }
+
+ for (String magicWeaponName : magicWeaponNames) {
+ if (weaponNameGivenLowerCase.contains(magicWeaponName)) {
+ return WEAPON_MAGIC;
+ }
+ }
+
+ return WEAPON_UNKNOWN;
+
+ }
+
+ private static String[] meleeWeaponNames = {
+ "sword",
+ "scimitar",
+ "dagger",
+ "spear",
+ "mace",
+ "axe",
+ "whip",
+ "tentacle",
+ "-ket-",
+ "-xil-",
+ "warhammer",
+ "halberd",
+ "claws",
+ "hasta",
+ "scythe",
+ "maul",
+ "anchor",
+ "sabre",
+ "excalibur",
+ "machete",
+ "dragon hunter lance",
+ "event rpg",
+ "silverlight",
+ "darklight",
+ "arclight",
+ "flail",
+ "granite hammer",
+ "rapier",
+ "bulwark"
+ };
+
+ private static String[] rangedWeaponNames = {
+ "bow",
+ "blowpipe",
+ "xil-ul",
+ "knife",
+ "dart",
+ "thrownaxe",
+ "chinchompa",
+ "ballista"
+ };
+
+ private static String[] magicWeaponNames = {
+ "staff",
+ "trident",
+ "wand",
+ "dawnbringer"
+ };
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/profiles/ProfilePanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/profiles/ProfilePanel.java
new file mode 100644
index 0000000000..876d27ff55
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/profiles/ProfilePanel.java
@@ -0,0 +1,132 @@
+/*
+ * Decompiled with CFR 0.139.
+ */
+package net.runelite.client.plugins.profiles;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Image;
+import java.awt.LayoutManager;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.image.BufferedImage;
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import net.runelite.api.Client;
+import net.runelite.api.GameState;
+import net.runelite.client.plugins.profiles.ProfilesConfig;
+import net.runelite.client.plugins.profiles.ProfilesPanel;
+import net.runelite.client.plugins.profiles.ProfilesPlugin;
+import net.runelite.client.ui.ColorScheme;
+import net.runelite.client.util.ImageUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class ProfilePanel
+extends JPanel {
+ private static final Logger log = LoggerFactory.getLogger(ProfilePanel.class);
+ private static final ImageIcon DELETE_ICON;
+ private static final ImageIcon DELETE_HOVER_ICON;
+ private final String loginText;
+ private String password = null;
+
+ ProfilePanel(final Client client, final String data, final ProfilesConfig config) {
+ String[] parts = data.split(":");
+ this.loginText = parts[1];
+ if (parts.length == 3) {
+ this.password = parts[2];
+ }
+ final ProfilePanel panel = this;
+ this.setLayout(new BorderLayout());
+ this.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+ JPanel labelWrapper = new JPanel(new BorderLayout());
+ labelWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+ labelWrapper.setBorder(new CompoundBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, ColorScheme.DARK_GRAY_COLOR), BorderFactory.createLineBorder(ColorScheme.DARKER_GRAY_COLOR)));
+ JPanel panelActions = new JPanel(new BorderLayout(3, 0));
+ panelActions.setBorder(new EmptyBorder(0, 0, 0, 8));
+ panelActions.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+ final JLabel delete = new JLabel();
+ delete.setIcon(DELETE_ICON);
+ delete.setToolTipText("Delete account profile");
+ delete.addMouseListener(new MouseAdapter(){
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ panel.getParent().remove(panel);
+ ProfilesPanel.removeProfile(data);
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ delete.setIcon(DELETE_HOVER_ICON);
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ delete.setIcon(DELETE_ICON);
+ }
+ });
+ panelActions.add((Component)delete, "East");
+ JLabel label = new JLabel();
+ label.setText(parts[0]);
+ label.setBorder(null);
+ label.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+ label.setPreferredSize(new Dimension(0, 24));
+ label.setForeground(Color.WHITE);
+ label.setBorder(new EmptyBorder(0, 8, 0, 0));
+ labelWrapper.add((Component)label, "Center");
+ labelWrapper.add((Component)panelActions, "East");
+ label.addMouseListener(new MouseAdapter(){
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (SwingUtilities.isLeftMouseButton(e) && client.getGameState() == GameState.LOGIN_SCREEN) {
+ client.setUsername(ProfilePanel.this.loginText);
+ if (config.rememberPassword() && ProfilePanel.this.password != null) {
+ client.setPassword(ProfilePanel.this.password);
+ }
+ }
+ }
+ });
+ JPanel bottomContainer = new JPanel(new BorderLayout());
+ bottomContainer.setBorder(new EmptyBorder(8, 0, 8, 0));
+ bottomContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+ bottomContainer.addMouseListener(new MouseAdapter(){
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (SwingUtilities.isLeftMouseButton(e) && client.getGameState() == GameState.LOGIN_SCREEN) {
+ client.setUsername(ProfilePanel.this.loginText);
+ }
+ }
+ });
+ JLabel login = new JLabel();
+ login.setText(config.isStreamerMode() ? "Hidden email" : this.loginText);
+ login.setBorder(null);
+ login.setPreferredSize(new Dimension(0, 24));
+ login.setForeground(Color.WHITE);
+ login.setBorder(new EmptyBorder(0, 8, 0, 0));
+ bottomContainer.add((Component)login, "Center");
+ this.add((Component)labelWrapper, "North");
+ this.add((Component)bottomContainer, "Center");
+ }
+
+ static {
+ BufferedImage deleteImg = ImageUtil.getResourceStreamFromClass(ProfilesPlugin.class, "delete_icon.png");
+ DELETE_ICON = new ImageIcon(deleteImg);
+ DELETE_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(deleteImg, -100));
+ }
+
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/profiles/ProfilesConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/profiles/ProfilesConfig.java
new file mode 100644
index 0000000000..2d6b171919
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/profiles/ProfilesConfig.java
@@ -0,0 +1,36 @@
+/*
+ * Decompiled with CFR 0.139.
+ */
+package net.runelite.client.plugins.profiles;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup(value="profiles")
+public interface ProfilesConfig
+extends Config {
+ @ConfigItem(keyName="profilesData", name="", description="", hidden=true)
+ default public String profilesData() {
+ return "";
+ }
+
+ @ConfigItem(keyName="profilesData", name="", description="")
+ public void profilesData(String var1);
+
+ @ConfigItem(keyName="rememberPassword", name="Remember Password", description="Remembers passwords for accounts")
+ default public boolean rememberPassword() {
+ return true;
+ }
+
+ @ConfigItem(keyName="streamerMode", name="Hide email addresses", description="Hides your account emails")
+ default public boolean isStreamerMode() {
+ return false;
+ }
+
+ @ConfigItem(keyName="switchPanel", name="Auto-open Panel", description="Automatically switch to the account switcher panel on the login screen")
+ default public boolean switchPanel() {
+ return true;
+ }
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/profiles/ProfilesPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/profiles/ProfilesPanel.java
new file mode 100644
index 0000000000..f3f6da6b67
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/profiles/ProfilesPanel.java
@@ -0,0 +1,248 @@
+/*
+ * Decompiled with CFR 0.139.
+ */
+package net.runelite.client.plugins.profiles;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.LayoutManager;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.util.Arrays;
+import java.util.function.Consumer;
+import javax.inject.Inject;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import net.runelite.api.Client;
+import net.runelite.client.plugins.profiles.ProfilePanel;
+import net.runelite.client.plugins.profiles.ProfilesConfig;
+import net.runelite.client.ui.ColorScheme;
+import net.runelite.client.ui.PluginPanel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class ProfilesPanel
+extends PluginPanel {
+ private static final Logger log = LoggerFactory.getLogger(ProfilesPanel.class);
+ private static final String ACCOUNT_USERNAME = "Account Username";
+ private static final String ACCOUNT_LABEL = "Account Label";
+ private static final String PASSWORD_LABEL = "Account Password";
+ private static final Dimension PREFERRED_SIZE = new Dimension(205, 30);
+ private static final Dimension MINIMUM_SIZE = new Dimension(0, 30);
+ private final Client client;
+ private static ProfilesConfig profilesConfig;
+ private final JTextField txtAccountLabel = new JTextField("Account Label");
+ private final JPasswordField txtAccountLogin = new JPasswordField("Account Username");
+ private final JPasswordField txtPasswordLogin = new JPasswordField("Account Password");
+ private final JPanel profilesPanel = new JPanel();
+ private GridBagConstraints c;
+
+ @Inject
+ public ProfilesPanel(Client client, final ProfilesConfig config) {
+ this.client = client;
+ profilesConfig = config;
+ this.setBorder(new EmptyBorder(18, 10, 0, 10));
+ this.setBackground(ColorScheme.DARK_GRAY_COLOR);
+ this.setLayout(new GridBagLayout());
+ this.c = new GridBagConstraints();
+ this.c.fill = 2;
+ this.c.gridx = 0;
+ this.c.gridy = 0;
+ this.c.weightx = 1.0;
+ this.c.weighty = 0.0;
+ this.c.insets = new Insets(0, 0, 4, 0);
+ this.txtAccountLabel.setPreferredSize(PREFERRED_SIZE);
+ this.txtAccountLabel.setForeground(ColorScheme.MEDIUM_GRAY_COLOR);
+ this.txtAccountLabel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+ this.txtAccountLabel.setMinimumSize(MINIMUM_SIZE);
+ this.txtAccountLabel.addFocusListener(new FocusListener(){
+
+ @Override
+ public void focusGained(FocusEvent e) {
+ if (ProfilesPanel.this.txtAccountLabel.getText().equals(ProfilesPanel.ACCOUNT_LABEL)) {
+ ProfilesPanel.this.txtAccountLabel.setText("");
+ ProfilesPanel.this.txtAccountLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
+ }
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ if (ProfilesPanel.this.txtAccountLabel.getText().isEmpty()) {
+ ProfilesPanel.this.txtAccountLabel.setForeground(ColorScheme.MEDIUM_GRAY_COLOR);
+ ProfilesPanel.this.txtAccountLabel.setText(ProfilesPanel.ACCOUNT_LABEL);
+ }
+ }
+ });
+ this.add((Component)this.txtAccountLabel, this.c);
+ ++this.c.gridy;
+ this.txtAccountLogin.setEchoChar('\u0000');
+ this.txtAccountLogin.setPreferredSize(PREFERRED_SIZE);
+ this.txtAccountLogin.setForeground(ColorScheme.MEDIUM_GRAY_COLOR);
+ this.txtAccountLogin.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+ this.txtAccountLogin.setMinimumSize(MINIMUM_SIZE);
+ this.txtAccountLogin.addFocusListener(new FocusListener(){
+
+ @Override
+ public void focusGained(FocusEvent e) {
+ if (ProfilesPanel.ACCOUNT_USERNAME.equals(String.valueOf(ProfilesPanel.this.txtAccountLogin.getPassword()))) {
+ ProfilesPanel.this.txtAccountLogin.setText("");
+ if (config.isStreamerMode()) {
+ ProfilesPanel.this.txtAccountLogin.setEchoChar('*');
+ }
+ ProfilesPanel.this.txtAccountLogin.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
+ }
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ if (ProfilesPanel.this.txtAccountLogin.getPassword().length == 0) {
+ ProfilesPanel.this.txtAccountLogin.setForeground(ColorScheme.MEDIUM_GRAY_COLOR);
+ ProfilesPanel.this.txtAccountLogin.setText(ProfilesPanel.ACCOUNT_USERNAME);
+ ProfilesPanel.this.txtAccountLogin.setEchoChar('\u0000');
+ }
+ }
+ });
+ this.add((Component)this.txtAccountLogin, this.c);
+ ++this.c.gridy;
+ this.txtPasswordLogin.setEchoChar('\u0000');
+ this.txtPasswordLogin.setPreferredSize(PREFERRED_SIZE);
+ this.txtPasswordLogin.setForeground(ColorScheme.MEDIUM_GRAY_COLOR);
+ this.txtPasswordLogin.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+ this.txtPasswordLogin.setToolTipText("Account password");
+ this.txtPasswordLogin.setMinimumSize(MINIMUM_SIZE);
+ this.txtPasswordLogin.addFocusListener(new FocusListener(){
+
+ @Override
+ public void focusGained(FocusEvent e) {
+ if (ProfilesPanel.PASSWORD_LABEL.equals(String.valueOf(ProfilesPanel.this.txtPasswordLogin.getPassword()))) {
+ ProfilesPanel.this.txtPasswordLogin.setText("");
+ ProfilesPanel.this.txtPasswordLogin.setEchoChar('*');
+ ProfilesPanel.this.txtPasswordLogin.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
+ }
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ if (ProfilesPanel.this.txtPasswordLogin.getPassword().length == 0) {
+ ProfilesPanel.this.txtPasswordLogin.setForeground(ColorScheme.MEDIUM_GRAY_COLOR);
+ ProfilesPanel.this.txtPasswordLogin.setText(ProfilesPanel.PASSWORD_LABEL);
+ ProfilesPanel.this.txtPasswordLogin.setEchoChar('\u0000');
+ }
+ }
+ });
+ if (config.rememberPassword()) {
+ this.add((Component)this.txtPasswordLogin, this.c);
+ ++this.c.gridy;
+ }
+ this.c.insets = new Insets(0, 0, 15, 0);
+ final JButton btnAddAccount = new JButton("Add Account");
+ btnAddAccount.setPreferredSize(PREFERRED_SIZE);
+ btnAddAccount.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+ btnAddAccount.setMinimumSize(MINIMUM_SIZE);
+ btnAddAccount.addActionListener(e -> {
+ String labelText = String.valueOf(this.txtAccountLabel.getText());
+ String loginText = String.valueOf(this.txtAccountLogin.getPassword());
+ String passwordText = String.valueOf(this.txtPasswordLogin.getPassword());
+ if (labelText.equals(ACCOUNT_LABEL) || loginText.equals(ACCOUNT_USERNAME)) {
+ return;
+ }
+ String data = config.rememberPassword() && this.txtPasswordLogin.getPassword() != null ? labelText + ":" + loginText + ":" + passwordText : labelText + ":" + loginText;
+ log.info(data);
+ this.addAccount(data);
+ ProfilesPanel.addProfile(data);
+ this.txtAccountLabel.setText(ACCOUNT_LABEL);
+ this.txtAccountLabel.setForeground(ColorScheme.MEDIUM_GRAY_COLOR);
+ this.txtAccountLogin.setText(ACCOUNT_USERNAME);
+ this.txtAccountLogin.setEchoChar('\u0000');
+ this.txtAccountLogin.setForeground(ColorScheme.MEDIUM_GRAY_COLOR);
+ this.txtPasswordLogin.setText(PASSWORD_LABEL);
+ this.txtPasswordLogin.setEchoChar('\u0000');
+ this.txtPasswordLogin.setForeground(ColorScheme.MEDIUM_GRAY_COLOR);
+ });
+ this.txtAccountLogin.addKeyListener(new KeyAdapter(){
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == 10) {
+ btnAddAccount.doClick();
+ btnAddAccount.requestFocus();
+ }
+ }
+ });
+ this.txtAccountLogin.addMouseListener(new MouseListener(){
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ }
+ });
+ this.add((Component)btnAddAccount, this.c);
+ ++this.c.gridy;
+ this.profilesPanel.setLayout(new GridBagLayout());
+ this.add((Component)this.profilesPanel, this.c);
+ this.c.gridy = 0;
+ this.c.insets = new Insets(0, 0, 5, 0);
+ this.addAccounts(config.profilesData());
+ }
+
+ void redrawProfiles() {
+ this.profilesPanel.removeAll();
+ this.c.gridy = 0;
+ this.addAccounts(profilesConfig.profilesData());
+ }
+
+ private void addAccount(String data) {
+ ProfilePanel profile = new ProfilePanel(this.client, data, profilesConfig);
+ ++this.c.gridy;
+ this.profilesPanel.add((Component)profile, this.c);
+ this.revalidate();
+ this.repaint();
+ }
+
+ void addAccounts(String data) {
+ if (!(data = data.trim()).contains(":")) {
+ return;
+ }
+ Arrays.stream(data.split("\\n")).forEach(this::addAccount);
+ }
+
+ static void addProfile(String data) {
+ profilesConfig.profilesData(profilesConfig.profilesData() + data + "\n");
+ }
+
+ static void removeProfile(String data) {
+ profilesConfig.profilesData(profilesConfig.profilesData().replaceAll(data + "\\n", ""));
+ }
+
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/profiles/ProfilesPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/profiles/ProfilesPlugin.java
new file mode 100644
index 0000000000..467c2b820f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/profiles/ProfilesPlugin.java
@@ -0,0 +1,135 @@
+/*
+ * Decompiled with CFR 0.139.
+ */
+package net.runelite.client.plugins.profiles;
+
+import com.google.inject.Injector;
+import com.google.inject.Provides;
+import java.awt.image.BufferedImage;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.util.logging.Logger;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.SecretKeySpec;
+import javax.inject.Inject;
+import net.runelite.api.Client;
+import net.runelite.api.GameState;
+import net.runelite.api.events.ConfigChanged;
+import net.runelite.api.events.GameStateChanged;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.plugins.profiles.ProfilesConfig;
+import net.runelite.client.plugins.profiles.ProfilesPanel;
+import net.runelite.client.ui.ClientToolbar;
+import net.runelite.client.ui.NavigationButton;
+import net.runelite.client.ui.PluginPanel;
+import net.runelite.client.util.ImageUtil;
+
+@PluginDescriptor(name="Account Switcher", description="Allow for a allows you to easily switch between multiple OSRS Accounts", tags={"profile", "account", "login", "log in"})
+public class ProfilesPlugin
+extends Plugin {
+ @Inject
+ private ClientToolbar clientToolbar;
+ @Inject
+ private Client client;
+ @Inject
+ private ProfilesConfig config;
+ private ProfilesPanel panel;
+ private NavigationButton navButton;
+ String text = "Hello World";
+ private static String key = "Bar12345Bar12345";
+ private static Key aesKey = new SecretKeySpec(key.getBytes(), "AES");
+
+ @Provides
+ ProfilesConfig getConfig(ConfigManager configManager) {
+ return configManager.getConfig(ProfilesConfig.class);
+ }
+
+ @Override
+ protected void startUp() throws Exception {
+ this.panel = this.injector.getInstance(ProfilesPanel.class);
+ BufferedImage icon = ImageUtil.getResourceStreamFromClass(this.getClass(), "profiles_icon.png");
+ this.navButton = NavigationButton.builder().tooltip("Profiles").icon(icon).priority(8).panel(this.panel).build();
+ this.clientToolbar.addNavigation(this.navButton);
+ }
+
+ @Override
+ protected void shutDown() {
+ this.clientToolbar.removeNavigation(this.navButton);
+ }
+
+ @Subscribe
+ private void onConfigChanged(ConfigChanged event) throws Exception {
+ if (event.getGroup().equals("profiles") && event.getKey().equals("rememberPassword")) {
+ this.panel = this.injector.getInstance(ProfilesPanel.class);
+ this.shutDown();
+ this.startUp();
+ }
+ }
+
+ public static String decryptText(String text) {
+ byte[] bb = new byte[text.length()];
+ for (int i = 0; i < text.length(); ++i) {
+ bb[i] = (byte)text.charAt(i);
+ }
+ Cipher cipher = null;
+ try {
+ cipher = Cipher.getInstance("AES");
+ }
+ catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+ e.printStackTrace();
+ }
+ try {
+ cipher.init(2, aesKey);
+ }
+ catch (InvalidKeyException e) {
+ e.printStackTrace();
+ }
+ try {
+ Logger.getLogger("EncryptionLogger").info("Decrypted " + text + " to " + new String(cipher.doFinal(bb)));
+ return new String(cipher.doFinal(bb));
+ }
+ catch (BadPaddingException | IllegalBlockSizeException e) {
+ e.printStackTrace();
+ return "";
+ }
+ }
+
+ public static String encryptText(String text) {
+ try {
+ Cipher cipher = Cipher.getInstance("AES");
+ cipher.init(1, aesKey);
+ byte[] encrypted = cipher.doFinal(text.getBytes());
+ StringBuilder sb = new StringBuilder();
+ for (byte b : encrypted) {
+ sb.append((char)b);
+ }
+ Logger.getLogger("EncryptionLogger").info("Encrypted " + text + " to " + sb.toString());
+ return sb.toString();
+ }
+ catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ catch (NoSuchPaddingException e) {
+ e.printStackTrace();
+ }
+ catch (BadPaddingException e) {
+ e.printStackTrace();
+ }
+ catch (IllegalBlockSizeException e) {
+ e.printStackTrace();
+ }
+ catch (InvalidKeyException e) {
+ e.printStackTrace();
+ }
+ return "";
+ }
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/profiles/delete_icon.png b/runelite-client/src/main/java/net/runelite/client/plugins/profiles/delete_icon.png
new file mode 100644
index 0000000000..18b67f23f3
Binary files /dev/null and b/runelite-client/src/main/java/net/runelite/client/plugins/profiles/delete_icon.png differ
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/profiles/profiles_icon.png b/runelite-client/src/main/java/net/runelite/client/plugins/profiles/profiles_icon.png
new file mode 100644
index 0000000000..a733eaf4a1
Binary files /dev/null and b/runelite-client/src/main/java/net/runelite/client/plugins/profiles/profiles_icon.png differ
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/protectitemreminder/ProtectItemReminderConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/protectitemreminder/ProtectItemReminderConfig.java
new file mode 100644
index 0000000000..6385326ec6
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/protectitemreminder/ProtectItemReminderConfig.java
@@ -0,0 +1,15 @@
+package net.runelite.client.plugins.protectitemreminder;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("protectitemreminder")
+public interface ProtectItemReminderConfig extends Config
+{
+ @ConfigItem(position = 0, keyName = "skulledOnly", name = "Only when skulled", description = "Only show the reminder when you are skulled")
+ default boolean skulledOnly() { return false; }
+
+ @ConfigItem(position = 1, keyName = "getHeight", name = "Text Height", description = "Sets the reminder text height")
+ default int getHeight() { return 60; }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/protectitemreminder/ProtectItemReminderOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/protectitemreminder/ProtectItemReminderOverlay.java
new file mode 100644
index 0000000000..499bfa5efb
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/protectitemreminder/ProtectItemReminderOverlay.java
@@ -0,0 +1,49 @@
+package net.runelite.client.plugins.protectitemreminder;
+
+import net.runelite.api.Client;
+import net.runelite.api.Player;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.OverlayPriority;
+import net.runelite.client.ui.overlay.OverlayUtil;
+
+import javax.inject.Inject;
+import java.awt.*;
+
+public class ProtectItemReminderOverlay extends Overlay
+{
+ private final Client client;
+ private final ProtectItemReminderPlugin plugin;
+ private final ProtectItemReminderConfig config;
+
+ private final String protItemString = "ENABLE PROTECT ITEM";
+
+ @Inject
+ private ProtectItemReminderOverlay(Client client, ProtectItemReminderPlugin plugin, ProtectItemReminderConfig config)
+ {
+ setPosition(OverlayPosition.DYNAMIC);
+ setPriority(OverlayPriority.HIGHEST);
+ this.client = client;
+ this.plugin = plugin;
+ this.config = config;
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if (!plugin.shouldRemind)
+ return null;
+
+ Player localPlayer = client.getLocalPlayer();
+
+ if (localPlayer == null)
+ return null;
+
+ net.runelite.api.Point drawPos = localPlayer.getCanvasTextLocation(graphics, protItemString, localPlayer.getLogicalHeight() + config.getHeight());
+
+ if (drawPos != null)
+ OverlayUtil.renderTextLocation(graphics, drawPos, protItemString, Color.RED);
+
+ return null;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/protectitemreminder/ProtectItemReminderPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/protectitemreminder/ProtectItemReminderPlugin.java
new file mode 100644
index 0000000000..bebdcb96c1
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/protectitemreminder/ProtectItemReminderPlugin.java
@@ -0,0 +1,99 @@
+package net.runelite.client.plugins.protectitemreminder;
+
+import net.runelite.client.eventbus.Subscribe;
+import com.google.inject.Provides;
+import net.runelite.api.*;
+import net.runelite.api.events.GameStateChanged;
+import net.runelite.api.events.GameTick;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.client.util.MiscUtils;
+
+import javax.inject.Inject;
+
+@PluginDescriptor(
+ name = "!Protect Item Reminder",
+ description = "Reminds you to protect item when in the wilderness.",
+ tags = { "wilderness", "prayer", "protect", "item", "pking" },
+ enabledByDefault = false
+)
+public class ProtectItemReminderPlugin extends Plugin
+{
+ @Inject
+ private Client client;
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private ProtectItemReminderOverlay overlay;
+
+ @Inject
+ private ProtectItemReminderConfig config;
+
+ private Player localPlayer;
+
+ public boolean shouldRemind = false;
+
+ @Provides
+ ProtectItemReminderConfig getConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(ProtectItemReminderConfig.class);
+ }
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ overlayManager.add(overlay);
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ overlayManager.remove(overlay);
+ shouldRemind = false;
+ localPlayer = null;
+ }
+
+ @Subscribe
+ public void onGameStateChanged(GameStateChanged event)
+ {
+ if (event.getGameState() == GameState.LOGGED_IN)
+ {
+ localPlayer = client.getLocalPlayer();
+ }
+ }
+
+ @Subscribe
+ public void onGameTick(GameTick event)
+ {
+ if (client.getGameState() == GameState.LOGIN_SCREEN)
+ return;
+
+ if (localPlayer == null)
+ {
+ shouldRemind = false;
+ return;
+ }
+ if (config.skulledOnly() && localPlayer.getSkullIcon() != SkullIcon.SKULL)
+ {
+ shouldRemind = false;
+ return;
+ }
+ if (MiscUtils.getWildernessLevelFrom(client, localPlayer.getWorldLocation()) <= 0)
+ {
+ shouldRemind = false;
+ return;
+ }
+
+ int value = client.getVar(Prayer.PROTECT_ITEM.getVarbit());
+
+ if (value == 0)
+ shouldRemind = true;
+ else
+ shouldRemind = false;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/Obstacles.java b/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/Obstacles.java
new file mode 100644
index 0000000000..fff21802f0
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/Obstacles.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018, Steffen Hauge
+ * 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.pyramidplunder;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import static net.runelite.api.ObjectID.SPEARTRAP_21280;
+
+public class Obstacles
+{
+ static final Set WALL_OBSTACLE_IDS = ImmutableSet.of(
+ 26618, 26619, 26620, 26621
+ );
+
+ static final Set TRAP_OBSTACLE_IDS = ImmutableSet.of(
+ SPEARTRAP_21280
+ );
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderConfig.java
new file mode 100644
index 0000000000..4b3038762c
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderConfig.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2018, Steffen Hauge
+ * 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.pyramidplunder;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("pyramidplunder")
+public interface PyramidPlunderConfig extends Config
+{
+ @ConfigItem(
+ position = 1,
+ keyName = "highlightDoors",
+ name = "Highlights doors",
+ description = "Highlights the four doors in each room"
+ )
+ default boolean highlightDoors()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ position = 2,
+ keyName = "highlightSpearTrap",
+ name = "Highlights spear traps",
+ description = "Highlights the spear traps in each room"
+ )
+ default boolean highlightSpearTrap()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 3,
+ keyName = "showTimer",
+ name = "Display numerical timer",
+ description = "Displays a numerical timer instead of the default timer"
+ )
+ default boolean showTimer()
+ {
+ return true;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderOverlay.java
new file mode 100644
index 0000000000..fd61a03229
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderOverlay.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2018, Steffen Hauge
+ * 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.pyramidplunder;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.geom.Area;
+import javax.inject.Inject;
+import net.runelite.api.Client;
+import net.runelite.api.ObjectComposition;
+import static net.runelite.api.ObjectID.SPEARTRAP_21280;
+import static net.runelite.api.ObjectID.TOMB_DOOR_20948;
+import static net.runelite.api.ObjectID.TOMB_DOOR_20949;
+import net.runelite.api.Point;
+import net.runelite.api.coords.LocalPoint;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayLayer;
+import net.runelite.client.ui.overlay.OverlayPosition;
+
+public class PyramidPlunderOverlay extends Overlay
+{
+ private static final int MAX_DISTANCE = 2400;
+ private static final Color COLOR_DOOR = Color.GREEN;
+ private static final Color COLOR_SPEAR_TRAP = Color.ORANGE;
+
+ private final Client client;
+ private final PyramidPlunderPlugin plugin;
+ private final PyramidPlunderConfig config;
+
+ @Inject
+ private PyramidPlunderOverlay(Client client, PyramidPlunderPlugin plugin, PyramidPlunderConfig config)
+ {
+ this.client = client;
+ this.plugin = plugin;
+ this.config = config;
+ setPosition(OverlayPosition.DYNAMIC);
+ setLayer(OverlayLayer.ABOVE_SCENE);
+ }
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if (!plugin.isInGame())
+ {
+ return null;
+ }
+
+ LocalPoint playerLocation = client.getLocalPlayer().getLocalLocation();
+ Point mousePosition = client.getMouseCanvasPosition();
+
+ plugin.getObstacles().forEach((object, tile) ->
+ {
+ if (Obstacles.WALL_OBSTACLE_IDS.contains(object.getId()) && !config.highlightDoors() ||
+ Obstacles.TRAP_OBSTACLE_IDS.contains(object.getId()) && !config.highlightSpearTrap())
+ {
+ return;
+ }
+
+ if (tile.getPlane() == client.getPlane() &&
+ object.getLocalLocation().distanceTo(playerLocation) < MAX_DISTANCE)
+ {
+ int objectID = object.getId();
+ if (Obstacles.WALL_OBSTACLE_IDS.contains(object.getId()))
+ {
+ //Impostor
+ ObjectComposition comp = client.getObjectDefinition(objectID);
+ ObjectComposition impostor = comp.getImpostor();
+
+ if (impostor == null)
+ {
+ return;
+ }
+ objectID = impostor.getId();
+ }
+
+ Area objectClickbox = object.getClickbox();
+ if (objectClickbox != null)
+ {
+ Color configColor = Color.GREEN;
+ switch (objectID)
+ {
+ case SPEARTRAP_21280:
+ configColor = COLOR_SPEAR_TRAP;
+ break;
+ case TOMB_DOOR_20948:
+ case TOMB_DOOR_20949:
+ configColor = COLOR_DOOR;
+ break;
+ }
+
+ if (objectClickbox.contains(mousePosition.getX(), mousePosition.getY()))
+ {
+ graphics.setColor(configColor.darker());
+ }
+ else
+ {
+ graphics.setColor(configColor);
+ }
+
+ graphics.draw(objectClickbox);
+ graphics.setColor(new Color(configColor.getRed(), configColor.getGreen(), configColor.getBlue(), 50));
+ graphics.fill(objectClickbox);
+ }
+ }
+ });
+
+ return null;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderPlugin.java
new file mode 100644
index 0000000000..64a009f2cb
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderPlugin.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2018, Steffen Hauge
+ * 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.pyramidplunder;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Provides;
+import java.time.temporal.ChronoUnit;
+import java.util.HashMap;
+import java.util.Map;
+import javax.inject.Inject;
+import lombok.Getter;
+import net.runelite.api.Client;
+import static net.runelite.api.ItemID.PHARAOHS_SCEPTRE;
+import net.runelite.api.Player;
+import net.runelite.api.Tile;
+import net.runelite.api.TileObject;
+import net.runelite.api.Varbits;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.api.events.ConfigChanged;
+import net.runelite.api.events.GameObjectChanged;
+import net.runelite.api.events.GameObjectDespawned;
+import net.runelite.api.events.GameObjectSpawned;
+import net.runelite.api.events.GameStateChanged;
+import net.runelite.api.events.VarbitChanged;
+import net.runelite.api.events.WallObjectChanged;
+import net.runelite.api.events.WallObjectDespawned;
+import net.runelite.api.events.WallObjectSpawned;
+import net.runelite.client.config.ConfigManager;
+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;
+
+@PluginDescriptor(
+ name = "PyramidPlunder",
+ description = "Highlights doors and spear traps in pyramid plunder and adds a numerical timer",
+ tags = {"pyramidplunder", "pyramid", "plunder", "overlay", "skilling", "thieving"},
+ enabledByDefault = false
+)
+
+public class PyramidPlunderPlugin extends Plugin
+{
+ private static final int PYRAMIND_PLUNDER_REGION_ID = 7749;
+ private static final int PYRAMIND_PLUNDER_TIMER_MAX = 500;
+ private static final double GAMETICK_SECOND = 0.6;
+
+ @Getter
+ private final Map obstacles = new HashMap<>();
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private PyramidPlunderConfig config;
+
+ @Inject
+ private InfoBoxManager infoBoxManager;
+
+ @Inject
+ private ItemManager itemManager;
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private PyramidPlunderOverlay pyramidPlunderOverlay;
+
+ @Getter
+ private boolean isInGame;
+
+ private int pyramidTimer = 0;
+
+ @Provides
+ PyramidPlunderConfig getConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(PyramidPlunderConfig.class);
+ }
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ overlayManager.add(pyramidPlunderOverlay);
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ overlayManager.remove(pyramidPlunderOverlay);
+ obstacles.clear();
+ reset();
+ }
+
+ @Subscribe
+ public void onConfigChanged(ConfigChanged event)
+ {
+ if (!config.showTimer())
+ {
+ removeTimer();
+ }
+
+ if (config.showTimer() && isInGame)
+ {
+ int remainingTime = PYRAMIND_PLUNDER_TIMER_MAX - pyramidTimer;
+
+ if (remainingTime >= 2)
+ {
+ double timeInSeconds = remainingTime * GAMETICK_SECOND;
+ showTimer((int)timeInSeconds, ChronoUnit.SECONDS);
+ }
+ }
+ }
+
+ private void removeTimer()
+ {
+ infoBoxManager.removeIf(infoBox -> infoBox instanceof PyramidPlunderTimer);
+ }
+
+ private void showTimer()
+ {
+ showTimer(5, ChronoUnit.MINUTES);
+ }
+
+ private void showTimer(int period, ChronoUnit chronoUnit)
+ {
+ removeTimer();
+ infoBoxManager.addInfoBox(new PyramidPlunderTimer(this, itemManager.getImage(PHARAOHS_SCEPTRE), period, chronoUnit));
+ }
+
+ @Subscribe
+ public void onGameStateChange(GameStateChanged event)
+ {
+ switch (event.getGameState())
+ {
+ case HOPPING:
+ case LOGIN_SCREEN:
+ reset();
+ break;
+ case LOADING:
+ obstacles.clear();
+ case LOGGED_IN:
+ if (!isInRegion())
+ {
+ reset();
+ }
+ break;
+ }
+ }
+
+ private boolean isInRegion()
+ {
+ Player local = client.getLocalPlayer();
+ if (local == null)
+ {
+ return false;
+ }
+
+ WorldPoint location = local.getWorldLocation();
+ if (location.getRegionID() != PYRAMIND_PLUNDER_REGION_ID)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Subscribe
+ public void onVarbitChanged(VarbitChanged event)
+ {
+ int lastValue = pyramidTimer;
+ pyramidTimer = client.getVar(Varbits.PYRAMID_PLUNDER_TIMER);
+
+ if (lastValue == pyramidTimer)
+ {
+ return;
+ }
+
+ if (pyramidTimer == 0)
+ {
+ reset();
+ }
+ if (pyramidTimer == 1)
+ {
+ isInGame = true;
+ if (config.showTimer())
+ {
+ showTimer();
+ }
+ }
+ }
+
+ private void reset()
+ {
+ isInGame = false;
+ removeTimer();
+ }
+
+ @Subscribe
+ public void onGameObjectSpawned(GameObjectSpawned event)
+ {
+ onTileObject(event.getTile(), null, event.getGameObject());
+ }
+
+ @Subscribe
+ public void onGameObjectChanged(GameObjectChanged event)
+ {
+ onTileObject(event.getTile(), event.getPrevious(), event.getGameObject());
+ }
+
+ @Subscribe
+ public void onGameObjectDeSpawned(GameObjectDespawned event)
+ {
+ onTileObject(event.getTile(), event.getGameObject(), null);
+ }
+
+ @Subscribe
+ public void onWallObjectSpawned(WallObjectSpawned event)
+ {
+ onTileObject(event.getTile(), null, event.getWallObject());
+ }
+
+ @Subscribe
+ public void onWallObjectChanged(WallObjectChanged event)
+ {
+ onTileObject(event.getTile(), event.getPrevious(), event.getWallObject());
+ }
+
+ @Subscribe
+ public void onWallObjectDeSpawned(WallObjectDespawned event)
+ {
+ onTileObject(event.getTile(), event.getWallObject(), null);
+ }
+
+ private void onTileObject(Tile tile, TileObject oldObject, TileObject newObject)
+ {
+ obstacles.remove(oldObject);
+
+ if (newObject == null)
+ {
+ return;
+ }
+
+ if (Obstacles.WALL_OBSTACLE_IDS.contains(newObject.getId()) ||
+ Obstacles.TRAP_OBSTACLE_IDS.contains(newObject.getId()))
+ {
+ obstacles.put(newObject, tile);
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderTimer.java b/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderTimer.java
new file mode 100644
index 0000000000..a9f73cf8c3
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderTimer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018, Steffen Hauge
+ * 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.pyramidplunder;
+
+import java.awt.image.BufferedImage;
+import java.time.temporal.ChronoUnit;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.ui.overlay.infobox.Timer;
+
+public class PyramidPlunderTimer extends Timer
+{
+ PyramidPlunderTimer(Plugin plugin, BufferedImage image, int period, ChronoUnit chronoUnit)
+ {
+ super(period, chronoUnit, image, plugin);
+ setTooltip("Time left until minigame ends");
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsConfig.java
index 2323a15a49..58a19463f3 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsConfig.java
@@ -24,9 +24,13 @@
*/
package net.runelite.client.plugins.raids;
+import java.awt.Color;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
+import net.runelite.client.config.Keybind;
@ConfigGroup("raids")
public interface RaidsConfig extends Config
@@ -154,6 +158,160 @@ public interface RaidsConfig extends Config
@ConfigItem(
position = 11,
+ keyName = "showScavsFarms",
+ name = "Show scavengers and farming",
+ description = "Adds scavengers and farming to the room breakdown"
+ )
+ default boolean showScavsFarms()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 12,
+ keyName = "enhanceScouterTitle",
+ name = "Enhance scouter title",
+ description = "Adds #combat and good puzzles to scouter title"
+ )
+ default boolean enhanceScouterTitle()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 13,
+ keyName = "enableSharableImage",
+ name = "Enable sharable image",
+ description = "Use the specified hotkey to capture the raid scouter"
+ )
+ default boolean enableSharableImage()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 14,
+ keyName = "hotkey",
+ name = "Capture hotkey",
+ description = "Hotkey used to capture the scouter"
+ )
+ default Keybind hotkey()
+ {
+ return new Keybind(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK);
+ }
+
+ @ConfigItem(
+ position = 15,
+ keyName = "enableTrayNotification",
+ name = "Enable tray notification",
+ description = "Adds a system tray notification on successful screen capture"
+ )
+ default boolean enableTrayNotification()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 16,
+ keyName = "showRecommendedItems",
+ name = "Show recommended items",
+ description = "Adds overlay with recommended items to scouter"
+ )
+ default boolean showRecommendedItems()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 17,
+ keyName = "recommendedItems",
+ name = "Recommended items",
+ description = "User-set recommended items in the form: [muttadiles,ice barrage,zamorak godsword],[tekton,elder maul], ..."
+ )
+ default String recommendedItems()
+ {
+ return "";
+ }
+
+ @ConfigItem(
+ position = 18,
+ keyName = "scavsBeforeIce",
+ name = "Show last scavs for Ice Demon",
+ description = "Highlights final scavengers before Ice Demon"
+ )
+ default boolean scavsBeforeIce()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 19,
+ keyName = "scavsBeforeOlm",
+ name = "Show last scavs for Olm",
+ description = "Highlights final scavengers before Olm"
+ )
+ default boolean scavsBeforeOlm()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 20,
+ keyName = "scavPrepColor",
+ name = "Last scavs color",
+ description = "The color of the final scavs before Ice Demon/Olm"
+ )
+ default Color scavPrepColor()
+ {
+ return new Color(130, 222, 255); //light blue
+ }
+
+ @ConfigItem(
+ position = 21,
+ keyName = "alwaysShowWorldAndCC",
+ name = "Always show CC and World",
+ description = "The CC and World are not removed from being in the in-game scouter"
+ )
+ default boolean alwaysShowWorldAndCC()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 22,
+ keyName = "colorTightrope",
+ name = "Color tightrope",
+ description = "Colors tightrope a separate color"
+ )
+ default boolean colorTightrope()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 23,
+ keyName = "tightropeColor",
+ name = "Tightrope color",
+ description = "The color of tightropes"
+ )
+ default Color tightropeColor()
+ {
+ return Color.MAGENTA;
+ }
+
+ @ConfigItem(
+ position = 24,
+ keyName = "hideRopeless",
+ name = "Hide no Tightrope raids",
+ description = "Completely hides raids with no tightrope"
+ )
+ default boolean hideRopeless()
+ {
+ return false;
+ }
+
+ @ConfigItem(
+ position = 25,
keyName = "layoutMessage",
name = "Send raid layout message when entering raid",
description = "Sends game message with raid layout on entering new raid"
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsOverlay.java
index 5ca7214b57..9c851e5dc6 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsOverlay.java
@@ -24,12 +24,23 @@
*/
package net.runelite.client.plugins.raids;
+import java.awt.Point;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import lombok.Getter;
+import lombok.Setter;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import javax.inject.Inject;
-import lombok.Setter;
import net.runelite.api.Client;
+import net.runelite.api.SpriteID;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.game.SpriteManager;
import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG;
import net.runelite.client.plugins.raids.solver.Room;
import net.runelite.client.ui.overlay.Overlay;
@@ -37,24 +48,48 @@ import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE;
import net.runelite.client.ui.overlay.OverlayMenuEntry;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayPriority;
+import net.runelite.client.ui.overlay.components.ImageComponent;
import net.runelite.client.ui.overlay.components.LineComponent;
import net.runelite.client.ui.overlay.components.PanelComponent;
import net.runelite.client.ui.overlay.components.TitleComponent;
+import net.runelite.client.util.ImageUtil;
+import net.runelite.client.util.Text;
public class RaidsOverlay extends Overlay
{
private static final int OLM_PLANE = 0;
+ private static final int BORDER_OFFSET = 2;
+ private static final int ICON_SIZE = 32;
+ private static final int SMALL_ICON_SIZE = 21;
+ //might need to edit these if they are not standard
+ private static final int TITLE_COMPONENT_HEIGHT = 20;
+ private static final int LINE_COMPONENT_HEIGHT = 16;
private Client client;
private RaidsPlugin plugin;
private RaidsConfig config;
private final PanelComponent panelComponent = new PanelComponent();
+ private final ItemManager itemManager;
+ private final SpriteManager spriteManager;
+ private final PanelComponent panelImages = new PanelComponent();
+
+ @Setter
+ private boolean sharable = false;
@Setter
private boolean scoutOverlayShown = false;
+ @Getter
+ private boolean scouterActive = false;
+
+ @Getter
+ private int width;
+
+ @Getter
+ private int height;
+
@Inject
- private RaidsOverlay(Client client, RaidsPlugin plugin, RaidsConfig config)
+ private RaidsOverlay(Client client, RaidsPlugin plugin, RaidsConfig config, ItemManager itemManager, SpriteManager spriteManager)
{
super(plugin);
setPosition(OverlayPosition.TOP_LEFT);
@@ -62,6 +97,8 @@ public class RaidsOverlay extends Overlay
this.client = client;
this.plugin = plugin;
this.config = config;
+ this.itemManager = itemManager;
+ this.spriteManager = spriteManager;
getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Raids overlay"));
}
@@ -73,6 +110,7 @@ public class RaidsOverlay extends Overlay
return null;
}
+ scouterActive = false;
panelComponent.getChildren().clear();
if (plugin.getRaid() == null || plugin.getRaid().getLayout() == null)
@@ -93,19 +131,117 @@ public class RaidsOverlay extends Overlay
color = Color.RED;
}
+ int combatCount = 0;
+ int roomCount = 0;
+ List iceRooms = new ArrayList<>();
+ List scavRooms = new ArrayList<>();
+ List scavsBeforeIceRooms = new ArrayList<>();
+ boolean crabs = false;
+ boolean iceDemon = false;
+ boolean tightrope = false;
+ boolean thieving = false;
+ String puzzles = "";
+ if (config.enhanceScouterTitle() || config.scavsBeforeIce() || sharable)
+ {
+ for (Room layoutRoom : plugin.getRaid().getLayout().getRooms())
+ {
+ int position = layoutRoom.getPosition();
+ RaidRoom room = plugin.getRaid().getRoom(position);
+
+ if (room == null)
+ {
+ continue;
+ }
+
+ switch (room.getType())
+ {
+ case COMBAT:
+ combatCount++;
+ break;
+ case PUZZLE:
+ String roomName = room.getPuzzle().getName();
+ switch (RaidRoom.Puzzle.fromString(roomName))
+ {
+ case CRABS:
+ crabs = true;
+ break;
+ case ICE_DEMON:
+ iceDemon = true;
+ iceRooms.add(roomCount);
+ break;
+ case THIEVING:
+ thieving = true;
+ break;
+ case TIGHTROPE:
+ tightrope = true;
+ break;
+ }
+ break;
+ case SCAVENGERS:
+ scavRooms.add(roomCount);
+ break;
+ }
+ roomCount++;
+ }
+ if (tightrope)
+ puzzles = crabs ? "cr" : iceDemon ? "ri" : thieving ? "tr" : "?r";
+ else if (config.hideRopeless())
+ {
+ panelComponent.getChildren().add(TitleComponent.builder()
+ .text("No Tightrope!")
+ .color(Color.RED)
+ .build());
+
+ return panelComponent.render(graphics);
+ }
+
+ scouterActive = true;
+ layout = (config.enhanceScouterTitle() ? "" + combatCount + "c " + puzzles + " " : "") + layout;
+
+ for (Integer i : iceRooms)
+ {
+ int prev = 0;
+ for (Integer s : scavRooms)
+ {
+ if (s > i)
+ break;
+ prev = s;
+ }
+ scavsBeforeIceRooms.add(prev);
+ }
+ }
+ int lastScavs = scavRooms.get(scavRooms.size() - 1);
panelComponent.getChildren().add(TitleComponent.builder()
.text(layout)
.color(color)
.build());
+ color = Color.ORANGE;
+ if (sharable || config.alwaysShowWorldAndCC())
+ {
+ String clanOwner = Text.removeTags(client.getWidget(WidgetInfo.CLAN_CHAT_OWNER).getText());
+ if (clanOwner.equals("None"))
+ {
+ clanOwner = "Open CC tab...";
+ color = Color.RED;
+ }
+ panelComponent.getChildren().add(LineComponent.builder()
+ .left("W" + client.getWorld())
+ .right("" + clanOwner)
+ .leftColor(Color.ORANGE)
+ .rightColor(color)
+ .build());
+ }
int bossMatches = 0;
int bossCount = 0;
+ roomCount = 0;
if (config.enableRotationWhitelist())
{
bossMatches = plugin.getRotationMatches();
}
+ Set imageIds = new HashSet<>();
for (Room layoutRoom : plugin.getRaid().getLayout().getRooms())
{
int position = layoutRoom.getPosition();
@@ -127,38 +263,144 @@ public class RaidsOverlay extends Overlay
color = Color.GREEN;
}
else if (plugin.getRoomBlacklist().contains(room.getBoss().getName().toLowerCase())
- || config.enableRotationWhitelist() && bossCount > bossMatches)
+ || config.enableRotationWhitelist() && bossCount > bossMatches)
{
color = Color.RED;
}
+ String bossName = room.getBoss().getName();
+ String bossNameLC = bossName.toLowerCase();
+ if (config.showRecommendedItems())
+ {
+ if (plugin.getRecommendedItemsList().get(bossNameLC) != null)
+ imageIds.addAll(plugin.getRecommendedItemsList().get(bossNameLC));
+ }
+
panelComponent.getChildren().add(LineComponent.builder()
- .left(room.getType().getName())
- .right(room.getBoss().getName())
+ .left(config.showRecommendedItems() ? "" : room.getType().getName())
+ .right(bossName)
.rightColor(color)
.build());
break;
case PUZZLE:
- if (plugin.getRoomWhitelist().contains(room.getPuzzle().getName().toLowerCase()))
+ String puzzleName = room.getPuzzle().getName();
+ String puzzleNameLC = puzzleName.toLowerCase();
+ if (plugin.getRecommendedItemsList().get(puzzleNameLC) != null)
+ imageIds.addAll(plugin.getRecommendedItemsList().get(puzzleNameLC));
+ if (plugin.getRoomWhitelist().contains(puzzleNameLC))
{
color = Color.GREEN;
}
- else if (plugin.getRoomBlacklist().contains(room.getPuzzle().getName().toLowerCase()))
+ else if (plugin.getRoomBlacklist().contains(puzzleNameLC))
{
color = Color.RED;
}
+ if (config.colorTightrope() && puzzleNameLC.equals("tightrope"))
+ {
+ color = config.tightropeColor();
+ }
panelComponent.getChildren().add(LineComponent.builder()
- .left(room.getType().getName())
- .right(room.getPuzzle().getName())
+ .left(config.showRecommendedItems() ? "" : room.getType().getName())
+ .right(puzzleName)
.rightColor(color)
.build());
break;
+ case FARMING:
+ if (config.showScavsFarms())
+ {
+ panelComponent.getChildren().add(LineComponent.builder()
+ .left("")
+ .right(room.getType().getName())
+ .rightColor(new Color(181, 230, 29)) //yellow green
+ .build());
+ }
+ break;
+ case SCAVENGERS:
+ if (config.scavsBeforeOlm() && roomCount == lastScavs)
+ {
+ panelComponent.getChildren().add(LineComponent.builder()
+ .left(config.showRecommendedItems() ? "" : "OlmPrep")
+ .right("Scavs")
+ .rightColor(config.scavPrepColor())
+ .build());
+ }
+ else if (config.scavsBeforeIce() && scavsBeforeIceRooms.contains(roomCount))
+ {
+ panelComponent.getChildren().add(LineComponent.builder()
+ .left(config.showRecommendedItems() ? "" : "IcePrep")
+ .right("Scavs")
+ .rightColor(config.scavPrepColor())
+ .build());
+ }
+ else if (config.showScavsFarms())
+ {
+ panelComponent.getChildren().add(LineComponent.builder()
+ .left("")
+ .right("Scavs")
+ .rightColor(new Color(181, 230, 29)) //yellow green
+ .build());
+ }
+ break;
}
+ roomCount++;
}
- return panelComponent.render(graphics);
+ Dimension panelDims = panelComponent.render(graphics);
+ width = (int) panelDims.getWidth();
+ height = (int) panelDims.getHeight();
+
+ //add recommended items
+ if (config.showRecommendedItems() && imageIds.size() > 0)
+ {
+ panelImages.getChildren().clear();
+ Integer[] idArray = imageIds.toArray(new Integer[0]);
+ int imagesVerticalOffset = TITLE_COMPONENT_HEIGHT + (sharable || config.alwaysShowWorldAndCC() ? LINE_COMPONENT_HEIGHT : 0) - BORDER_OFFSET;
+ int imagesMaxHeight = height - 2 * BORDER_OFFSET - TITLE_COMPONENT_HEIGHT - (sharable || config.alwaysShowWorldAndCC() ? LINE_COMPONENT_HEIGHT : 0);
+ boolean smallImages = false;
+
+ panelImages.setPreferredLocation(new Point(0, imagesVerticalOffset));
+ panelImages.setBackgroundColor(null);
+ if (2 * (imagesMaxHeight / ICON_SIZE) >= idArray.length )
+ {
+ panelImages.setWrapping(2);
+ }
+ else
+ {
+ panelImages.setWrapping(3);
+ smallImages = true;
+ }
+
+ panelImages.setOrientation(PanelComponent.Orientation.HORIZONTAL);
+ for (Integer e : idArray)
+ {
+ final BufferedImage image = getImage(e, smallImages);
+ if (image != null)
+ {
+ panelImages.getChildren().add(new ImageComponent(image));
+ }
+ }
+
+ panelImages.render(graphics);
+ }
+ return panelDims;
+ }
+
+ private BufferedImage getImage(int id, boolean small)
+ {
+ BufferedImage bim;
+ if (id != SpriteID.SPELL_ICE_BARRAGE)
+ bim = itemManager.getImage(id);
+ else
+ bim = spriteManager.getSprite(id, 0);
+ if (bim == null)
+ return null;
+ if (!small)
+ return ImageUtil.resizeCanvas(bim, ICON_SIZE, ICON_SIZE);
+ if (id != SpriteID.SPELL_ICE_BARRAGE)
+ return ImageUtil.resizeImage(bim, SMALL_ICON_SIZE, SMALL_ICON_SIZE);
+ return ImageUtil.resizeCanvas(bim, SMALL_ICON_SIZE, SMALL_ICON_SIZE);
}
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java
index 34db989c71..80723bd0f5 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java
@@ -26,10 +26,18 @@ package net.runelite.client.plugins.raids;
import com.google.inject.Binder;
import com.google.inject.Provides;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
import java.text.DecimalFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
@@ -39,9 +47,11 @@ import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.InstanceTemplates;
+import net.runelite.api.ItemID;
import net.runelite.api.NullObjectID;
import static net.runelite.api.Perspective.SCENE_SIZE;
import net.runelite.api.Point;
+import net.runelite.api.SpriteID;
import static net.runelite.api.SpriteID.TAB_QUESTS_BROWN_RAIDING_PARTY;
import net.runelite.api.Tile;
import net.runelite.api.VarPlayer;
@@ -49,27 +59,34 @@ import net.runelite.api.Varbits;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.ConfigChanged;
import net.runelite.api.events.VarbitChanged;
+import net.runelite.api.widgets.Widget;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.chat.ChatColorType;
import net.runelite.client.chat.ChatMessageBuilder;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.ConfigManager;
+import net.runelite.client.game.ItemManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.SpriteManager;
+import net.runelite.client.input.KeyManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.raids.solver.Layout;
import net.runelite.client.plugins.raids.solver.LayoutSolver;
import net.runelite.client.plugins.raids.solver.RotationSolver;
+import net.runelite.client.ui.DrawManager;
+import net.runelite.client.ui.FontManager;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
+import net.runelite.client.util.ScreenCapture;
import net.runelite.client.util.Text;
+import net.runelite.client.util.HotkeyListener;
@PluginDescriptor(
name = "Chambers Of Xeric",
description = "Show helpful information for the Chambers of Xeric raid",
- tags = {"combat", "raid", "overlay", "pve", "pvm", "bosses"}
+ tags = {"combat", "raid", "overlay", "pve", "pvm", "bosses", "cox", "olm"}
)
@Slf4j
public class RaidsPlugin extends Plugin
@@ -82,6 +99,10 @@ public class RaidsPlugin extends Plugin
static final DecimalFormat POINTS_FORMAT = new DecimalFormat("#,###");
private static final String SPLIT_REGEX = "\\s*,\\s*";
private static final Pattern ROTATION_REGEX = Pattern.compile("\\[(.*?)]");
+ private static final int LINE_COMPONENT_HEIGHT = 16;
+
+ @Inject
+ private ItemManager itemManager;
@Inject
private ChatMessageManager chatMessageManager;
@@ -92,6 +113,12 @@ public class RaidsPlugin extends Plugin
@Inject
private Client client;
+ @Inject
+ private DrawManager drawManager;
+
+ @Inject
+ private ScheduledExecutorService executor;
+
@Inject
private RaidsConfig config;
@@ -110,6 +137,12 @@ public class RaidsPlugin extends Plugin
@Inject
private ClientThread clientThread;
+ @Inject
+ private KeyManager keyManager;
+
+ @Inject
+ private ScreenCapture screenCapture;
+
@Getter
private final ArrayList roomWhitelist = new ArrayList<>();
@@ -122,6 +155,9 @@ public class RaidsPlugin extends Plugin
@Getter
private final ArrayList layoutWhitelist = new ArrayList<>();
+ @Getter
+ private final Map> recommendedItemsList = new HashMap<>();
+
@Getter
private Raid raid;
@@ -148,6 +184,7 @@ public class RaidsPlugin extends Plugin
overlayManager.add(overlay);
updateLists();
clientThread.invokeLater(() -> checkRaidPresence(true));
+ keyManager.registerKeyListener(hotkeyListener);
}
@Override
@@ -158,6 +195,7 @@ public class RaidsPlugin extends Plugin
inRaidChambers = false;
raid = null;
timer = null;
+ keyManager.unregisterKeyListener(hotkeyListener);
}
@Subscribe
@@ -346,6 +384,41 @@ public class RaidsPlugin extends Plugin
updateList(roomBlacklist, config.blacklistedRooms());
updateList(rotationWhitelist, config.whitelistedRotations());
updateList(layoutWhitelist, config.whitelistedLayouts());
+ updateMap(recommendedItemsList, config.recommendedItems());
+ }
+
+ private void updateMap(Map> map, String input)
+ {
+ map.clear();
+
+ Matcher m = ROTATION_REGEX.matcher(input);
+ while (m.find())
+ {
+ String everything = m.group(1).toLowerCase();
+ int split = everything.indexOf(',');
+ if (split < 0)
+ continue;
+ String key = everything.substring(0, split);
+ if (key.length() < 1)
+ continue;
+ String[] itemNames = everything.substring(split).split(SPLIT_REGEX);
+
+ map.computeIfAbsent(key, k -> new ArrayList<>());
+
+ for (String itemName : itemNames)
+ {
+ if (itemName.equals(""))
+ continue;
+ if (itemName.equals("ice barrage"))
+ map.get(key).add(SpriteID.SPELL_ICE_BARRAGE);
+ else if (itemName.startsWith("salve"))
+ map.get(key).add(ItemID.SALVE_AMULETEI);
+ else if (itemManager.search(itemName).size() > 0)
+ map.get(key).add(itemManager.search(itemName).get(0).getId());
+ else
+ log.info("RaidsPlugin: Could not find an item ID for item: " + itemName);
+ }
+ }
}
private void updateList(ArrayList list, String input)
@@ -602,4 +675,42 @@ public class RaidsPlugin extends Plugin
return room;
}
+
+ private final HotkeyListener hotkeyListener = new HotkeyListener(() -> config.hotkey())
+ {
+ @Override
+ public void hotkeyPressed()
+ {
+ initiateCopyImage();
+ }
+ };
+
+ private void initiateCopyImage()
+ {
+ if (!config.enableSharableImage() || !overlay.isScouterActive())
+ return;
+
+ Rectangle overlaySize = overlay.getBounds();
+ if (overlaySize.width <= 0 || overlaySize.height <= 0)
+ return;
+ if (!config.alwaysShowWorldAndCC())
+ overlaySize.height += LINE_COMPONENT_HEIGHT;
+
+ BufferedImage bim = new BufferedImage(overlaySize.width, overlaySize.height, BufferedImage.TYPE_INT_ARGB);
+ overlay.setSharable(true);
+ Graphics2D g = bim.createGraphics();
+ g.setFont(FontManager.getRunescapeFont());
+
+ //this is needed to update the PanelComponent childDimensions, because they are a frame behind
+ if (!config.alwaysShowWorldAndCC())
+ overlay.render(g);
+ g.setColor(Color.BLACK);
+ g.fillRect(0, 0, overlaySize.width, overlaySize.height);
+
+ overlay.render(g);
+ screenCapture.takeScreenshot(bim, config.enableTrayNotification(), "Chambers");
+ g.dispose();
+
+ overlay.setSharable(false);
+ }
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/BatSolver/BatSolver.java b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/BatSolver/BatSolver.java
new file mode 100644
index 0000000000..ebbad81a54
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/BatSolver/BatSolver.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2018, Tim Lehner
+ * 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.raidsthieving.BatSolver;
+
+import java.util.Map;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.TreeSet;
+import java.util.List;
+import java.util.ArrayList;
+import static net.runelite.client.plugins.raidsthieving.BatSolver.SolutionSet.SOLUTION_SETS;
+
+public class BatSolver
+{
+ private Map numberOfSolutionsWithPoison;
+ private final SolutionSet solution;
+
+ private final HashSet grubsChests;
+
+ public BatSolver(ThievingRoomType roomType)
+ {
+ solution = new SolutionSet(roomType);
+ grubsChests = new HashSet<>();
+ }
+
+ public void addEmptyChest(int chestId)
+ {
+ // When a new empty chest is found, add it to the current solution set
+ solution.addEmptyChest(chestId);
+ calculateChanceOfPoison();
+ }
+
+ public void addGrubsChest(int chestId)
+ {
+ // When a chest with grubs is found, keep track of it to invalidate solutions
+ grubsChests.add(chestId);
+ calculateChanceOfPoison();
+ }
+
+ public TreeSet matchSolutions()
+ {
+ TreeSet possibleEmptyChests = new TreeSet<>();
+ for (SolutionSet knownSolution : SolutionSet.SOLUTION_SETS)
+ {
+ if (knownSolution.getType() == solution.getType() && matchSolution(knownSolution))
+ {
+ possibleEmptyChests.addAll(knownSolution.getEmptyChests());
+ }
+ }
+
+ return possibleEmptyChests;
+ }
+
+ private boolean matchSolution(SolutionSet testSolution)
+ {
+ for (Integer grubsChest : grubsChests)
+ {
+ if (testSolution.containsChest(grubsChest))
+ {
+ // If one of the chests is known to have grubs, it cannot be a solution
+ return false;
+ }
+ }
+
+ boolean matchesAll = true;
+ boolean everMatched = false;
+ for (int i : solution.getEmptyChests())
+ {
+ if (!testSolution.containsChest(i))
+ {
+ matchesAll = false;
+ }
+ else
+ {
+ everMatched = true;
+ }
+ }
+ return matchesAll && everMatched;
+ }
+
+ public ThievingRoomType getType()
+ {
+ return solution.getType();
+ }
+
+
+ public void calculateChanceOfPoison()
+ {
+ if (getType() == null)
+ {
+ numberOfSolutionsWithPoison = null;
+ return;
+ }
+
+ numberOfSolutionsWithPoison = new HashMap<>();
+ for (SolutionSet sol : getPosssibleSolutions())
+ {
+ if (getType() == sol.getType() && (solution.getEmptyChests().size() == 0 || matchSolution(sol)))
+ {
+ for (Integer i : sol.getEmptyChests())
+ {
+ if (numberOfSolutionsWithPoison.containsKey(i))
+ {
+ numberOfSolutionsWithPoison.put(i, numberOfSolutionsWithPoison.get(i) + 1);
+ }
+ else
+ {
+ numberOfSolutionsWithPoison.put(i, 1);
+ }
+ }
+ }
+ }
+ }
+
+ private List getPosssibleSolutions()
+ {
+ List possibleSolutions = new ArrayList<>();
+ for (SolutionSet soln : SOLUTION_SETS)
+ {
+ // Check if we've found grubs in one of the chests, invalidating it as an solution
+ boolean foundMatch = false;
+ for (int i : grubsChests)
+ {
+ if (soln.containsChest(i))
+ {
+ foundMatch = true;
+ }
+ }
+ if (!foundMatch)
+ {
+ possibleSolutions.add(soln);
+ }
+ }
+ return possibleSolutions;
+ }
+
+ public double relativeLikelihoodPoison(int chestId)
+ {
+ // Returns a double between 0 and 1 of how likely the chest has poison based on the number of possible solutions
+ // Uses a Sigmoid like function to give good contrast in drawn opacity,
+ // perhaps could be changed to something more accurate quantitavely.
+ if (numberOfSolutionsWithPoison == null)
+ {
+ calculateChanceOfPoison();
+ }
+ if (numberOfSolutionsWithPoison == null)
+ {
+ return 1.0;
+ }
+ int mostFrequentPoison = 0;
+ for (Map.Entry entry : numberOfSolutionsWithPoison.entrySet())
+ {
+ if (entry.getValue() > mostFrequentPoison)
+ {
+ mostFrequentPoison = entry.getValue();
+ }
+ }
+ int timesFound = 0;
+ if (numberOfSolutionsWithPoison.containsKey(chestId))
+ {
+ timesFound = numberOfSolutionsWithPoison.get(chestId);
+ }
+ double chestChance = (double) (timesFound) / (double) (mostFrequentPoison);
+ return 1. / (1 + Math.exp(5 - 10 * chestChance));
+ }
+
+ public int getNumberOfEmptyChests()
+ {
+ return solution.getEmptyChests().size();
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/BatSolver/ChestIdentifier.java b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/BatSolver/ChestIdentifier.java
new file mode 100644
index 0000000000..7346e4ecf6
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/BatSolver/ChestIdentifier.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2018, Tim Lehner
+ * 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.raidsthieving.BatSolver;
+
+import net.runelite.client.plugins.raidsthieving.InstancePoint;
+import net.runelite.client.plugins.raidsthieving.ThievingChest;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ChestIdentifier
+{
+ public ChestIdentifier(ThievingRoomType roomType)
+ {
+ chestIds = new HashMap<>();
+ switch (roomType)
+ {
+ case LEFT_TURN:
+ chestIds.put(new InstancePoint(3283, 5379), 1);
+ chestIds.put(new InstancePoint(3285, 5380), 2);
+ chestIds.put(new InstancePoint(3279, 5381), 3);
+ chestIds.put(new InstancePoint(3287, 5382), 4);
+ chestIds.put(new InstancePoint(3281, 5382), 5);
+ chestIds.put(new InstancePoint(3284, 5383), 6);
+ chestIds.put(new InstancePoint(3283, 5384), 7);
+ chestIds.put(new InstancePoint(3286, 5384), 8);
+ chestIds.put(new InstancePoint(3288, 5384), 9);
+ chestIds.put(new InstancePoint(3277, 5385), 10);
+ chestIds.put(new InstancePoint(3280, 5385), 11);
+ chestIds.put(new InstancePoint(3285, 5386), 12);
+ chestIds.put(new InstancePoint(3290, 5386), 13);
+ chestIds.put(new InstancePoint(3275, 5387), 14);
+ chestIds.put(new InstancePoint(3287, 5387), 15);
+ chestIds.put(new InstancePoint(3288, 5387), 16);
+ chestIds.put(new InstancePoint(3281, 5388), 17);
+ chestIds.put(new InstancePoint(3291, 5388), 18);
+ chestIds.put(new InstancePoint(3280, 5389), 19);
+ chestIds.put(new InstancePoint(3285, 5389), 20);
+ chestIds.put(new InstancePoint(3289, 5389), 21);
+ chestIds.put(new InstancePoint(3283, 5390), 22);
+ chestIds.put(new InstancePoint(3285, 5390), 23);
+ chestIds.put(new InstancePoint(3288, 5390), 24);
+ chestIds.put(new InstancePoint(3290, 5390), 25);
+ chestIds.put(new InstancePoint(3282, 5391), 26);
+ chestIds.put(new InstancePoint(3289, 5391), 27);
+ chestIds.put(new InstancePoint(3292, 5391), 28);
+ chestIds.put(new InstancePoint(3279, 5392), 29);
+ chestIds.put(new InstancePoint(3276, 5393), 30);
+ chestIds.put(new InstancePoint(3279, 5393), 31);
+ chestIds.put(new InstancePoint(3284, 5393), 32);
+ chestIds.put(new InstancePoint(3285, 5393), 33);
+ chestIds.put(new InstancePoint(3291, 5393), 34);
+ chestIds.put(new InstancePoint(3275, 5394), 35);
+ chestIds.put(new InstancePoint(3277, 5394), 36);
+ chestIds.put(new InstancePoint(3288, 5394), 37);
+ chestIds.put(new InstancePoint(3276, 5395), 38);
+ chestIds.put(new InstancePoint(3281, 5395), 39);
+ chestIds.put(new InstancePoint(3285, 5395), 40);
+ chestIds.put(new InstancePoint(3287, 5395), 41);
+ chestIds.put(new InstancePoint(3289, 5395), 42);
+ chestIds.put(new InstancePoint(3274, 5396), 43);
+ chestIds.put(new InstancePoint(3283, 5396), 44);
+ chestIds.put(new InstancePoint(3285, 5396), 45);
+ chestIds.put(new InstancePoint(3288, 5396), 46);
+ chestIds.put(new InstancePoint(3272, 5397), 47);
+ chestIds.put(new InstancePoint(3280, 5397), 48);
+ chestIds.put(new InstancePoint(3277, 5398), 49);
+ chestIds.put(new InstancePoint(3281, 5398), 50);
+ chestIds.put(new InstancePoint(3284, 5398), 51);
+ chestIds.put(new InstancePoint(3276, 5399), 52);
+ chestIds.put(new InstancePoint(3278, 5399), 53);
+ chestIds.put(new InstancePoint(3283, 5399), 54);
+ chestIds.put(new InstancePoint(3285, 5399), 55);
+ chestIds.put(new InstancePoint(3277, 5400), 56);
+ chestIds.put(new InstancePoint(3284, 5400), 57);
+ chestIds.put(new InstancePoint(3288, 5400), 58);
+ chestIds.put(new InstancePoint(3281, 5401), 59);
+ chestIds.put(new InstancePoint(3286, 5401), 60);
+ chestIds.put(new InstancePoint(3279, 5402), 61);
+ chestIds.put(new InstancePoint(3285, 5402), 62);
+ chestIds.put(new InstancePoint(3280, 5403), 63);
+ chestIds.put(new InstancePoint(3283, 5403), 64);
+ break;
+ case RIGHT_TURN:
+ chestIds.put(new InstancePoint(3338, 5405), 1);
+ chestIds.put(new InstancePoint(3334, 5405), 2);
+ chestIds.put(new InstancePoint(3342, 5404), 3);
+ chestIds.put(new InstancePoint(3340, 5404), 4);
+ chestIds.put(new InstancePoint(3345, 5403), 5);
+ chestIds.put(new InstancePoint(3334, 5403), 6);
+ chestIds.put(new InstancePoint(3330, 5403), 7);
+ chestIds.put(new InstancePoint(3343, 5402), 8);
+ chestIds.put(new InstancePoint(3342, 5402), 9);
+ chestIds.put(new InstancePoint(3339, 5402), 10);
+ chestIds.put(new InstancePoint(3338, 5402), 11);
+ chestIds.put(new InstancePoint(3336, 5402), 12);
+ chestIds.put(new InstancePoint(3347, 5401), 13);
+ chestIds.put(new InstancePoint(3330, 5401), 14);
+ chestIds.put(new InstancePoint(3345, 5400), 15);
+ chestIds.put(new InstancePoint(3341, 5400), 16);
+ chestIds.put(new InstancePoint(3337, 5400), 17);
+ chestIds.put(new InstancePoint(3334, 5400), 18);
+ chestIds.put(new InstancePoint(3345, 5399), 19);
+ chestIds.put(new InstancePoint(3343, 5399), 20);
+ chestIds.put(new InstancePoint(3340, 5399), 21);
+ chestIds.put(new InstancePoint(3335, 5399), 22);
+ chestIds.put(new InstancePoint(3331, 5399), 23);
+ chestIds.put(new InstancePoint(3338, 5398), 24);
+ chestIds.put(new InstancePoint(3337, 5398), 25);
+ chestIds.put(new InstancePoint(3345, 5397), 26);
+ chestIds.put(new InstancePoint(3341, 5397), 27);
+ chestIds.put(new InstancePoint(3334, 5397), 28);
+ chestIds.put(new InstancePoint(3331, 5397), 29);
+ chestIds.put(new InstancePoint(3346, 5396), 30);
+ chestIds.put(new InstancePoint(3343, 5396), 31);
+ chestIds.put(new InstancePoint(3339, 5396), 32);
+ chestIds.put(new InstancePoint(3335, 5396), 33);
+ chestIds.put(new InstancePoint(3333, 5396), 34);
+ chestIds.put(new InstancePoint(3340, 5395), 35);
+ chestIds.put(new InstancePoint(3337, 5395), 36);
+ chestIds.put(new InstancePoint(3334, 5395), 37);
+ chestIds.put(new InstancePoint(3345, 5394), 38);
+ chestIds.put(new InstancePoint(3342, 5394), 39);
+ chestIds.put(new InstancePoint(3332, 5394), 40);
+ chestIds.put(new InstancePoint(3343, 5393), 41);
+ chestIds.put(new InstancePoint(3341, 5393), 42);
+ chestIds.put(new InstancePoint(3338, 5393), 43);
+ chestIds.put(new InstancePoint(3335, 5393), 44);
+ chestIds.put(new InstancePoint(3334, 5393), 45);
+ chestIds.put(new InstancePoint(3346, 5392), 46);
+ chestIds.put(new InstancePoint(3342, 5392), 47);
+ chestIds.put(new InstancePoint(3332, 5392), 48);
+ chestIds.put(new InstancePoint(3350, 5391), 49);
+ chestIds.put(new InstancePoint(3346, 5391), 50);
+ chestIds.put(new InstancePoint(3340, 5391), 51);
+ chestIds.put(new InstancePoint(3339, 5391), 52);
+ chestIds.put(new InstancePoint(3336, 5391), 53);
+ chestIds.put(new InstancePoint(3333, 5391), 54);
+ chestIds.put(new InstancePoint(3349, 5390), 55);
+ chestIds.put(new InstancePoint(3343, 5390), 56);
+ chestIds.put(new InstancePoint(3337, 5390), 57);
+ chestIds.put(new InstancePoint(3335, 5390), 58);
+ chestIds.put(new InstancePoint(3344, 5389), 59);
+ chestIds.put(new InstancePoint(3340, 5389), 60);
+ chestIds.put(new InstancePoint(3336, 5389), 61);
+ chestIds.put(new InstancePoint(3333, 5389), 62);
+ chestIds.put(new InstancePoint(3346, 5388), 63);
+ chestIds.put(new InstancePoint(3340, 5387), 64);
+ chestIds.put(new InstancePoint(3337, 5386), 65);
+ chestIds.put(new InstancePoint(3333, 5386), 66);
+ chestIds.put(new InstancePoint(3338, 5385), 67);
+ chestIds.put(new InstancePoint(3336, 5385), 68);
+ chestIds.put(new InstancePoint(3337, 5384), 69);
+ chestIds.put(new InstancePoint(3340, 5382), 70);
+ chestIds.put(new InstancePoint(3334, 5383), 71);
+ chestIds.put(new InstancePoint(3340, 5379), 72);
+ chestIds.put(new InstancePoint(3338, 5380), 73);
+ chestIds.put(new InstancePoint(3336, 5381), 74);
+ break;
+ case STRAIGHT:
+ chestIds.put(new InstancePoint(3308, 5378), 1);
+ chestIds.put(new InstancePoint(3305, 5379), 2);
+ chestIds.put(new InstancePoint(3307, 5379), 3);
+ chestIds.put(new InstancePoint(3304, 5381), 4);
+ chestIds.put(new InstancePoint(3310, 5381), 5);
+ chestIds.put(new InstancePoint(3302, 5382), 6);
+ chestIds.put(new InstancePoint(3307, 5382), 7);
+ chestIds.put(new InstancePoint(3312, 5382), 8);
+ chestIds.put(new InstancePoint(3317, 5382), 9);
+ chestIds.put(new InstancePoint(3319, 5382), 10);
+ chestIds.put(new InstancePoint(3304, 5383), 11);
+ chestIds.put(new InstancePoint(3305, 5383), 12);
+ chestIds.put(new InstancePoint(3307, 5383), 13);
+ chestIds.put(new InstancePoint(3310, 5383), 14);
+ chestIds.put(new InstancePoint(3315, 5383), 15);
+ chestIds.put(new InstancePoint(3320, 5383), 16);
+ chestIds.put(new InstancePoint(3300, 5384), 17);
+ chestIds.put(new InstancePoint(3309, 5384), 18);
+ chestIds.put(new InstancePoint(3311, 5384), 19);
+ chestIds.put(new InstancePoint(3313, 5384), 20);
+ chestIds.put(new InstancePoint(3317, 5384), 21);
+ chestIds.put(new InstancePoint(3318, 5384), 22);
+ chestIds.put(new InstancePoint(3302, 5385), 23);
+ chestIds.put(new InstancePoint(3306, 5385), 24);
+ chestIds.put(new InstancePoint(3310, 5385), 25);
+ chestIds.put(new InstancePoint(3313, 5385), 26);
+ chestIds.put(new InstancePoint(3320, 5385), 27);
+ chestIds.put(new InstancePoint(3302, 5386), 28);
+ chestIds.put(new InstancePoint(3305, 5386), 29);
+ chestIds.put(new InstancePoint(3316, 5386), 30);
+ chestIds.put(new InstancePoint(3321, 5386), 31);
+ chestIds.put(new InstancePoint(3300, 5387), 32);
+ chestIds.put(new InstancePoint(3308, 5387), 33);
+ chestIds.put(new InstancePoint(3314, 5387), 34);
+ chestIds.put(new InstancePoint(3317, 5387), 35);
+ chestIds.put(new InstancePoint(3301, 5388), 36);
+ chestIds.put(new InstancePoint(3306, 5388), 37);
+ chestIds.put(new InstancePoint(3312, 5388), 38);
+ chestIds.put(new InstancePoint(3322, 5388), 39);
+ chestIds.put(new InstancePoint(3309, 5389), 40);
+ chestIds.put(new InstancePoint(3311, 5389), 41);
+ chestIds.put(new InstancePoint(3313, 5389), 42);
+ chestIds.put(new InstancePoint(3316, 5389), 43);
+ chestIds.put(new InstancePoint(3320, 5389), 44);
+ chestIds.put(new InstancePoint(3300, 5390), 45);
+ chestIds.put(new InstancePoint(3303, 5390), 46);
+ chestIds.put(new InstancePoint(3304, 5390), 47);
+ chestIds.put(new InstancePoint(3312, 5390), 48);
+ chestIds.put(new InstancePoint(3320, 5390), 49);
+ chestIds.put(new InstancePoint(3307, 5391), 50);
+ chestIds.put(new InstancePoint(3310, 5391), 51);
+ chestIds.put(new InstancePoint(3317, 5391), 52);
+ chestIds.put(new InstancePoint(3318, 5391), 53);
+ chestIds.put(new InstancePoint(3323, 5391), 54);
+ chestIds.put(new InstancePoint(3301, 5392), 55);
+ chestIds.put(new InstancePoint(3303, 5392), 56);
+ chestIds.put(new InstancePoint(3309, 5392), 57);
+ chestIds.put(new InstancePoint(3314, 5392), 58);
+ chestIds.put(new InstancePoint(3322, 5392), 59);
+ chestIds.put(new InstancePoint(3305, 5393), 60);
+ chestIds.put(new InstancePoint(3307, 5393), 61);
+ chestIds.put(new InstancePoint(3316, 5393), 62);
+ chestIds.put(new InstancePoint(3309, 5394), 63);
+ chestIds.put(new InstancePoint(3312, 5394), 64);
+ chestIds.put(new InstancePoint(3322, 5394), 65);
+ chestIds.put(new InstancePoint(3310, 5379), 66);
+ break;
+ }
+
+ }
+
+ public int indentifyChest(ThievingChest chest)
+ {
+ int id = chestIds.get(chest.getInstancePoint());
+ chest.setChestId(id);
+ return id;
+ }
+
+ private Map chestIds;
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/BatSolver/SolutionSet.java b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/BatSolver/SolutionSet.java
new file mode 100644
index 0000000000..dc1c3c3dc2
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/BatSolver/SolutionSet.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2018, Tim Lehner
+ * 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.raidsthieving.BatSolver;
+
+import lombok.Getter;
+import java.util.HashSet;
+import java.util.Arrays;
+import java.util.Set;
+
+// Each Thieving room has 4 empty chests
+// User-reported data shows these 4 come in groups,
+//
+// e.g. if there is an empty chest in L room chest 1, the other empty chests could be 16, 17, 38, 54, 55
+// See https://dikkenoob.github.io/ for more information
+
+public class SolutionSet
+{
+ public static final SolutionSet[] SOLUTION_SETS =
+ {
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 1, 16, 17, 55),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 1, 17, 38, 54),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 2, 7, 21, 37),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 3, 5, 19, 30),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 3, 11, 15, 40),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 4, 22, 27, 46),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 5, 9, 19, 45),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 6, 24, 26, 41),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 6, 26, 32, 52),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 7, 13, 44, 59),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 8, 14, 41, 43),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 8, 10, 28, 33),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 8, 31, 47, 50),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 10, 35, 54, 63),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 10, 30, 32, 59),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 12, 40, 53, 56),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 12, 13, 42, 54),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 13, 22, 27, 46),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 14, 18, 23, 51),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 15, 43, 44, 58),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 15, 16, 42, 45),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 20, 29, 45, 51),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 20, 25, 32, 34),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 20, 28, 51, 62),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 21, 39, 41, 58),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 22, 25, 54, 64),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 23, 31, 47, 55),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 23, 33, 37, 60),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 24, 34, 55),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 26, 50, 63, 27),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 29, 39, 41, 61),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 33, 46, 52, 57),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 34, 45, 49, 60),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 36, 40, 42, 62),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 37, 38, 51, 64),
+ new SolutionSet(ThievingRoomType.LEFT_TURN, 48, 53, 55, 56),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 1, 6, 28, 41),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 1, 42, 55, 60),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 2, 10, 31, 44),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 2, 33, 51, 68),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 3, 31, 43, 46),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 3, 5, 21, 48),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 4, 20, 24, 33),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 4, 38, 47),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 5, 21, 48),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 5, 17, 35, 63),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 7, 17, 45, 47),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 7, 37, 41, 52),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 8, 13, 40, 42),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 8, 20, 24, 30),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 9, 15, 23, 35),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 11, 13, 21, 50),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 11, 18, 37, 39),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 12, 14, 27, 34),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 14, 45, 67, 71),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 16, 22, 29, 32),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 18, 28, 31, 64),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 19, 21, 63, 69),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 20, 51, 68, 72),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 22, 29, 56, 61),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 23, 53, 66, 74),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 26, 35, 53, 59),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 27, 30, 55, 57),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 31, 58, 60, 73),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 34, 57, 58, 70),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 38, 56, 61, 70),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 40, 54, 65, 72),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 42, 46, 65),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 47, 49, 66, 67),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 48, 62, 69),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 9, 19, 32, 41),
+ new SolutionSet(ThievingRoomType.RIGHT_TURN, 16, 26, 36, 39),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 1, 39, 43, 51),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 2, 15, 20, 53),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 3, 10, 42, 44),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 4, 14, 38, 52),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 5, 6, 35, 41),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 7, 16, 34, 49),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 9, 12, 26, 27),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 13, 25, 30, 31),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 15, 20, 53),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 17, 24, 34, 58),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 18, 23, 35, 57),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 19, 26, 47, 65),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 21, 33, 36, 61),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 21, 54, 66),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 22, 25, 46, 55),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 24, 34, 58),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 28, 40, 52, 62),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 29, 41, 42, 63),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 30, 32, 37, 64),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 39, 43, 51),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 43, 45, 50, 60),
+ new SolutionSet(ThievingRoomType.STRAIGHT, 51, 53, 56, 59)
+ };
+
+ SolutionSet(ThievingRoomType type)
+ {
+ this.type = type;
+ emptyChests = new HashSet<>();
+ }
+
+ private SolutionSet(ThievingRoomType type, Integer... emptyChests)
+ {
+ this.type = type;
+ this.emptyChests = new HashSet<>(Arrays.asList(emptyChests));
+ }
+
+ public void addEmptyChest(int chestId)
+ {
+ emptyChests.add(chestId);
+ }
+
+ public boolean containsChest(int chestId)
+ {
+ return emptyChests.contains(chestId);
+ }
+
+ @Getter
+ private ThievingRoomType type;
+
+ @Getter
+ private Set emptyChests;
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/BatSolver/ThievingRoomType.java b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/BatSolver/ThievingRoomType.java
new file mode 100644
index 0000000000..f709b2d435
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/BatSolver/ThievingRoomType.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018, Tim Lehner
+ * 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.raidsthieving.BatSolver;
+
+// There are three distinct Thieving rooms, distinguished by the position of the entrance relative to the exit
+// e.g. If you enter the room and must turn left to get to the exit and trough, this is a LEFT_TURN
+
+import net.runelite.client.plugins.raidsthieving.InstancePoint;
+
+public enum ThievingRoomType
+{
+ LEFT_TURN(3271, 5389),
+ RIGHT_TURN(3350, 5399),
+ STRAIGHT(3317, 5397);
+
+ private final int x;
+ private final int y;
+
+ ThievingRoomType(int x, int y)
+ {
+ this.x = x;
+ this.y = y;
+ }
+
+ public static ThievingRoomType IdentifyByInstancePoint(InstancePoint point)
+ {
+ for (ThievingRoomType type : ThievingRoomType.values())
+ {
+ if (Math.abs(type.x - point.getX()) <= 1 &&
+ Math.abs(type.y - point.getY()) <= 1)
+ {
+ return type;
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/ChestOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/ChestOverlay.java
new file mode 100644
index 0000000000..5e09486f89
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/ChestOverlay.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2018, Tim Lehner
+ * 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.raidsthieving;
+
+import net.runelite.api.Client;
+import net.runelite.api.Perspective;
+import net.runelite.api.Point;
+import net.runelite.api.coords.LocalPoint;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.client.plugins.raidsthieving.BatSolver.BatSolver;
+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.components.ProgressPieComponent;
+import javax.inject.Inject;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.util.Map;
+import java.util.TreeSet;
+
+/**
+ * Represents the overlay that shows timers on traps that are placed by the
+ * player.
+ */
+public class ChestOverlay extends Overlay
+{
+
+ private final Client client;
+ private final RaidsThievingPlugin plugin;
+ private final RaidsThievingConfig config;
+
+ @Inject
+ ChestOverlay(Client client, RaidsThievingPlugin plugin, RaidsThievingConfig config)
+ {
+ setPosition(OverlayPosition.DYNAMIC);
+ setLayer(OverlayLayer.ABOVE_SCENE);
+ this.plugin = plugin;
+ this.config = config;
+ this.client = client;
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ drawChests(graphics);
+ return null;
+ }
+
+ /**
+ * Updates the timer colors.
+ */
+ public void updateConfig()
+ {
+ }
+
+ /**
+ * Iterates over all the traps that were placed by the local player, and
+ * draws a circle or a timer on the trap, depending on the trap state.
+ *
+ * @param graphics
+ */
+ private void drawChests(Graphics2D graphics)
+ {
+
+ for (Map.Entry entry : plugin.getChests().entrySet())
+ {
+ ThievingChest chest = entry.getValue();
+ WorldPoint pos = entry.getKey();
+
+
+ if (chest != null)
+ {
+ if (!plugin.isBatsFound() && !chest.isEverOpened())
+ {
+ if (shouldDrawChest(pos))
+ {
+ Color drawColor = new Color(config.getPotentialBatColor().getRed(),
+ config.getPotentialBatColor().getGreen(),
+ config.getPotentialBatColor().getBlue(),
+ getChestOpacity(pos));
+ drawCircleOnTrap(graphics, chest, drawColor);
+ }
+ }
+ if (chest.isPoison())
+ {
+ drawCircleOnTrap(graphics, chest, config.getPoisonTrapColor());
+ }
+ }
+ }
+ }
+
+ private boolean shouldDrawChest(WorldPoint chestPos)
+ {
+ if (plugin.numberOfEmptyChestsFound() == 0)
+ {
+ return true;
+ }
+ int chestId = plugin.getChestId(chestPos);
+ BatSolver solver = plugin.getSolver();
+ if (solver != null && chestId != -1)
+ {
+ TreeSet matches = solver.matchSolutions();
+ return matches.contains(chestId) || matches.size() == 0;
+ }
+ return true;
+ }
+
+ /**
+ * Draws a timer on a given trap.
+ *
+ * @param graphics
+ * @param chest The chest on which the circle needs to be drawn
+ * @param fill The fill color of the timer
+ */
+ private void drawCircleOnTrap(Graphics2D graphics, ThievingChest chest, Color fill)
+ {
+ if (chest.getLocalPoint().getPlane() != client.getPlane())
+ {
+ return;
+ }
+ LocalPoint localLoc = LocalPoint.fromWorld(client, chest.getLocalPoint());
+ if (localLoc == null)
+ {
+ return;
+ }
+ Point loc = Perspective.localToCanvas(client, localLoc, chest.getLocalPoint().getPlane());
+
+ ProgressPieComponent pie = new ProgressPieComponent();
+ pie.setFill(fill);
+ pie.setBorderColor(Color.BLACK);
+ pie.setPosition(loc);
+ pie.setProgress(1);
+ if (graphics != null && loc != null)
+ {
+ pie.render(graphics);
+ }
+ }
+
+ private int getChestOpacity(WorldPoint chestPos)
+ {
+ int chestId = plugin.getChestId(chestPos);
+ BatSolver solver = plugin.getSolver();
+ if (solver != null && chestId != -1)
+ {
+ return (int) (255 * solver.relativeLikelihoodPoison(chestId));
+ }
+ return 255;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/InstancePoint.java b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/InstancePoint.java
new file mode 100644
index 0000000000..0df80aeb65
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/InstancePoint.java
@@ -0,0 +1,98 @@
+package net.runelite.client.plugins.raidsthieving;
+
+import lombok.Getter;
+import net.runelite.api.Client;
+import net.runelite.api.Point;
+import net.runelite.api.coords.WorldPoint;
+
+import java.util.Objects;
+
+/**
+ * Represents a point in the instance chunk, invariant of rotation.
+ */
+@Getter
+public class InstancePoint
+{
+ private static final int CHUNK_SIZE = 8;
+ private static final double CHUNK_OFFSET = 3.5;
+
+ public InstancePoint(int x, int y, int rot)
+ {
+ this.x = x;
+ this.y = y;
+ this.rot = rot;
+ }
+
+ public InstancePoint(int x, int y)
+ {
+ this.x = x;
+ this.y = y;
+ this.rot = 0;
+ }
+
+ public static InstancePoint buildFromPoint(WorldPoint worldPoint, Client client)
+ {
+ Point point = new Point(worldPoint.getX(), worldPoint.getY());
+ Point base = new Point(client.getBaseX(), client.getBaseY());
+ int plane = worldPoint.getPlane();
+
+ int deltaX = point.getX() - base.getX();
+ int deltaY = point.getY() - base.getY();
+ int chunkIndexX = deltaX / CHUNK_SIZE;
+ int chunkIndexY = deltaY / CHUNK_SIZE;
+
+ int chunkData = client.getInstanceTemplateChunks()[plane][chunkIndexX][chunkIndexY];
+ int rotation = chunkData >> 1 & 0x3;
+ int y = (chunkData >> 3 & 0x7FF) * 8;
+ int x = (chunkData >> 14 & 0x3FF) * 8;
+
+ return buildFromTile(base, point, rotation, new Point(x, y));
+ }
+
+ public static InstancePoint buildFromTile(Point base, Point tile, int rot, Point chunkOrigin)
+ {
+ int deltaX = tile.getX() - base.getX();
+ int deltaY = tile.getY() - base.getY();
+
+ double chunkOffsetX = (deltaX % CHUNK_SIZE) - CHUNK_OFFSET;
+ double chunkOffsetY = (deltaY % CHUNK_SIZE) - CHUNK_OFFSET;
+
+ for (int i = 0; i < rot; i++)
+ {
+ double temp = chunkOffsetX;
+ chunkOffsetX = -chunkOffsetY;
+ chunkOffsetY = temp;
+ }
+
+ chunkOffsetX += CHUNK_OFFSET;
+ chunkOffsetY += CHUNK_OFFSET;
+
+ int invariantChunkOffsetX = (int) chunkOffsetX;
+ int invariantChunkOffsetY = (int) chunkOffsetY;
+
+ return new InstancePoint(
+ chunkOrigin.getX() + invariantChunkOffsetX,
+ chunkOrigin.getY() + invariantChunkOffsetY,
+ rot);
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ InstancePoint that = (InstancePoint) o;
+ return x == that.x &&
+ y == that.y;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(x, y);
+ }
+
+ private int x;
+ private int y;
+ private int rot;
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/RaidsThievingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/RaidsThievingConfig.java
new file mode 100644
index 0000000000..bb055edf4e
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/RaidsThievingConfig.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2017, Tim Lehner
+ * 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.raidsthieving;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+import java.awt.Color;
+
+@ConfigGroup("raidsthievingplugin")
+public interface RaidsThievingConfig extends Config
+{
+ @ConfigItem(
+ position = 1,
+ keyName = "hexColorPotentialBat",
+ name = "Potential Bat",
+ description = "Color of marker for chests which could have bat"
+ )
+ default Color getPotentialBatColor()
+ {
+ return Color.YELLOW;
+ }
+
+ @ConfigItem(
+ position = 2,
+ keyName = "hexColorPoison",
+ name = "Poison trap",
+ description = "Color of chest with poison"
+ )
+ default Color getPoisonTrapColor()
+ {
+ return Color.GREEN;
+ }
+
+ @ConfigItem(
+ position = 5,
+ keyName = "batNotify",
+ name = "Notify when found",
+ description = "Send notification if you see bats being found."
+ )
+ default boolean batFoundNotify()
+ {
+ return false;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/RaidsThievingConstants.java b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/RaidsThievingConstants.java
new file mode 100644
index 0000000000..965934a01b
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/RaidsThievingConstants.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2017, Tim Lehner
+ * 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.raidsthieving;
+
+public class RaidsThievingConstants
+{
+ public static final int CLOSED_CHEST_ID = 29742;
+ public static final int OPEN_EMPTY_CHEST = 29743;
+ public static final int OPEN_FULL_CHEST_1 = 29744;
+ public static final int OPEN_FULL_CHEST_2 = 29745;
+ public static final int EMPTY_TROUGH = 29746;
+ public static final int[] STORAGE = {29769, 29770, 29771, 29772};
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/RaidsThievingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/RaidsThievingPlugin.java
new file mode 100644
index 0000000000..d9f7de646c
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/RaidsThievingPlugin.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2017, Tim Lehner
+ * 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.raidsthieving;
+
+import com.google.inject.Provides;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.GameObject;
+import net.runelite.api.GraphicsObject;
+import net.runelite.api.Varbits;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.api.events.ConfigChanged;
+import net.runelite.api.events.GameObjectSpawned;
+import net.runelite.api.events.GraphicsObjectCreated;
+import net.runelite.api.events.VarbitChanged;
+import net.runelite.client.Notifier;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.plugins.raidsthieving.BatSolver.BatSolver;
+import net.runelite.client.plugins.raidsthieving.BatSolver.ChestIdentifier;
+import net.runelite.client.plugins.raidsthieving.BatSolver.ThievingRoomType;
+import net.runelite.client.ui.overlay.OverlayManager;
+import javax.inject.Inject;
+import java.text.MessageFormat;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@PluginDescriptor(
+ name = "!Raids Bat Finder",
+ description = "Tracks which chests need to be searched for bats and which poison",
+ tags = {"overlay", "skilling", "raid"}
+)
+public class RaidsThievingPlugin extends Plugin
+{
+ @Inject
+ private Client client;
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private ChestOverlay overlay;
+
+ @Inject
+ private Notifier notifier;
+
+ @Inject
+ private RaidsThievingConfig config;
+
+ @Getter
+ private final Map chests = new HashMap<>();
+
+ @Getter
+ private Instant lastActionTime = Instant.ofEpochMilli(0);
+
+ private boolean inRaidChambers;
+
+ @Getter
+ private boolean batsFound;
+
+ @Getter
+ private BatSolver solver;
+
+ @Getter
+ private ChestIdentifier mapper;
+
+
+ @Provides
+ RaidsThievingConfig provideConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(RaidsThievingConfig.class);
+ }
+
+ @Override
+ protected void startUp()
+ {
+ overlayManager.add(overlay);
+ overlay.updateConfig();
+ reset();
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ overlayManager.remove(overlay);
+ lastActionTime = Instant.ofEpochMilli(0);
+ chests.clear();
+ }
+
+
+ @Subscribe
+ public void onGameObjectSpawned(GameObjectSpawned event)
+ {
+ GameObject obj = event.getGameObject();
+ WorldPoint loc = obj.getWorldLocation();
+ InstancePoint absLoc = InstancePoint.buildFromPoint(loc, client);
+
+ if (obj.getId() == RaidsThievingConstants.EMPTY_TROUGH)
+ {
+ ThievingRoomType type = ThievingRoomType.IdentifyByInstancePoint(absLoc);
+
+ if (type != null)
+ {
+ solver = new BatSolver(type);
+ mapper = new ChestIdentifier(type);
+ for (ThievingChest chest : chests.values())
+ {
+ mapper.indentifyChest(chest);
+ }
+ }
+ else
+ {
+ log.error(MessageFormat.format("Unable to identify room type with: {0} {1} {2} {3} {4}.",
+ loc.getX(), loc.getY(), absLoc.getX(), absLoc.getY(), absLoc.getRot()));
+ log.error("Please report this @https://github.com/runelite/runelite/pull/4914!");
+ }
+ }
+ if (obj.getId() == RaidsThievingConstants.CLOSED_CHEST_ID)
+ {
+ if (!chests.containsKey(loc))
+ {
+ ThievingChest chest = new ThievingChest(obj, absLoc);
+
+ if (mapper != null)
+ {
+ mapper.indentifyChest(chest);
+ }
+
+ chests.put(loc, chest);
+ }
+ else
+ {
+ checkForBats();
+ }
+ }
+
+ if (obj.getId() == RaidsThievingConstants.OPEN_FULL_CHEST_1 ||
+ obj.getId() == RaidsThievingConstants.OPEN_FULL_CHEST_2)
+ {
+ ThievingChest chest = chests.get(obj.getWorldLocation());
+ // We found a chest that has grubs
+ log.info(MessageFormat.format("Found grubs at {0}, {1} chestId: {2}", loc.getX(), loc.getY(), chest.getChestId()));
+ if (solver != null && chest.getChestId() != -1)
+ {
+ chest.setEverOpened(true);
+ solver.addGrubsChest(chest.getChestId());
+ }
+ checkForBats();
+ }
+
+ if (obj.getId() == RaidsThievingConstants.OPEN_EMPTY_CHEST)
+ {
+ ThievingChest chest = chests.get(obj.getWorldLocation());
+ // We found a chest that could have poison
+ if (solver != null && chest.getChestId() != -1)
+ {
+ chest.setEmpty(true);
+ chest.setEverOpened(true);
+ solver.addEmptyChest(chest.getChestId());
+ }
+ }
+ }
+
+
+ @Subscribe
+ public void onGraphicsObjectCreated(GraphicsObjectCreated event)
+ {
+ GraphicsObject obj = event.getGraphicsObject();
+ if (obj.getId() == 184)
+ {
+ log.debug("Found poison splat");
+ WorldPoint loc = WorldPoint.fromLocal(client, obj.getLocation());
+ chests.get(loc).setPoison(true);
+ }
+ }
+
+ @Subscribe
+ public void onVarbitChanged(VarbitChanged event)
+ {
+ boolean setting = client.getVar(Varbits.IN_RAID) == 1;
+
+ if (inRaidChambers != setting)
+ {
+ inRaidChambers = setting;
+ reset();
+ }
+
+ }
+
+ @Subscribe
+ public void onConfigChanged(ConfigChanged event)
+ {
+ if (event.getGroup().equals("raidsthievingplugin"))
+ {
+ overlay.updateConfig();
+ }
+ }
+
+ private void reset()
+ {
+ chests.clear();
+ batsFound = false;
+ solver = null;
+ mapper = null;
+ }
+
+ public int numberOfEmptyChestsFound()
+ {
+ int total = 0;
+ for (ThievingChest chest : chests.values())
+ {
+ if (chest.isEmpty())
+ {
+ total++;
+ }
+ }
+ return total;
+ }
+
+
+ private boolean checkForBats()
+ {
+ for (ThievingChest chest : chests.values())
+ {
+ if (chest.isEmpty() && !chest.isPoison())
+ {
+ batsFound = true;
+ if (config.batFoundNotify())
+ {
+ notifier.notify("Bats have been found!");
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int getChestId(WorldPoint worldPoint)
+ {
+ return chests.get(worldPoint).getChestId();
+ }
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/ThievingChest.java b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/ThievingChest.java
new file mode 100644
index 0000000000..05a58d554e
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/raidsthieving/ThievingChest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2019, Tim Lehner
+ * 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.raidsthieving;
+
+import lombok.Getter;
+import lombok.Setter;
+import net.runelite.api.GameObject;
+import net.runelite.api.coords.WorldPoint;
+
+/**
+ * Wrapper class for a GameObject that represents a chest in the thieving room of Chambers of Xeric.
+ */
+@Getter
+public class ThievingChest
+{
+ /**
+ * If the chest has never been opened, it could have bats.
+ */
+ @Setter
+ private boolean everOpened;
+
+ /**
+ * If the chest is empty, it could have bats.
+ */
+ @Setter
+ private boolean empty;
+
+ /**
+ * If the chest contains a poison trap instead.
+ */
+ @Setter
+ private boolean poison;
+
+
+ @Setter
+ private int chestId;
+
+ private final WorldPoint localPoint;
+ private final InstancePoint instancePoint;
+
+ /**
+ * Constructor for a ThievingChest object
+ *
+ * @param gameObject The gameobject thats corresponds with this trap.
+ */
+ ThievingChest(GameObject gameObject, InstancePoint instancePoint)
+ {
+ this.everOpened = false;
+ this.poison = false;
+ this.empty = false;
+ localPoint = gameObject.getWorldLocation();
+ this.instancePoint = instancePoint;
+ this.chestId = -1;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/rememberclan/RememberClanConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/rememberclan/RememberClanConfig.java
new file mode 100644
index 0000000000..20775b1d35
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/rememberclan/RememberClanConfig.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018, Infinitay
+ * 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.rememberclan;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("rememberclan")
+public interface RememberClanConfig extends Config
+{
+ @ConfigItem(
+ position = 1,
+ keyName = "clanname",
+ name = "Clan Name",
+ description = "Clanname to always remember"
+ )
+ default String clanname()
+ {
+ return "";
+ }
+
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/rememberclan/RememberClanPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/rememberclan/RememberClanPlugin.java
new file mode 100644
index 0000000000..35d209a4f7
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/rememberclan/RememberClanPlugin.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2018, Infinitay
+ * 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.rememberclan;
+
+import com.google.inject.Provides;
+import javax.inject.Inject;
+
+import net.runelite.api.*;
+import net.runelite.api.events.GameStateChanged;
+import net.runelite.api.events.GameTick;
+import net.runelite.api.vars.AccountType;
+import net.runelite.client.chat.ChatColorType;
+import net.runelite.client.chat.ChatMessageBuilder;
+import net.runelite.client.chat.ChatMessageManager;
+import net.runelite.client.chat.QueuedMessage;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+
+@PluginDescriptor(
+ name = "!Remember Clan",
+ description = "Remembers a specific clan!"
+)
+public class RememberClanPlugin extends Plugin
+{
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private RememberClanConfig config;
+
+ @Inject
+ private ChatMessageManager chatMessageManager;
+
+ private boolean loggingIn;
+
+ @Provides
+ RememberClanConfig provideConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(RememberClanConfig.class);
+ }
+
+ @Subscribe
+ public void onGameTick(GameTick event)
+ {
+ client.setVar(VarClientStr.RECENT_CLAN_CHAT,config.clanname());
+
+ }
+
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/shayzieninfirmary/ShayzienInfirmaryOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/shayzieninfirmary/ShayzienInfirmaryOverlay.java
new file mode 100644
index 0000000000..726702f8eb
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/shayzieninfirmary/ShayzienInfirmaryOverlay.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2019, Yani
+ * 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.shayzieninfirmary;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Polygon;
+import java.awt.image.BufferedImage;
+import javax.inject.Inject;
+import net.runelite.api.Client;
+import net.runelite.api.ItemID;
+import net.runelite.api.NPC;
+import net.runelite.api.Point;
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.OverlayUtil;
+
+public class ShayzienInfirmaryOverlay extends Overlay
+{
+ private final ShayzienInfirmaryPlugin plugin;
+ private final Client client;
+
+ private BufferedImage medPackImage;
+
+ @Inject
+ public ShayzienInfirmaryOverlay(ShayzienInfirmaryPlugin plugin, Client client, ItemManager itemManager)
+ {
+ setPosition(OverlayPosition.DYNAMIC);
+ this.plugin = plugin;
+ this.client = client;
+
+ medPackImage = itemManager.getImage(ItemID.SHAYZIEN_MEDPACK);
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics)
+ {
+ if (!plugin.isAtInfirmary())
+ {
+ return null;
+ }
+
+ for (NPC npc : plugin.getUnhealedSoldiers())
+ {
+
+ Polygon tilePoly = npc.getCanvasTilePoly();
+
+ if (tilePoly == null)
+ {
+ continue;
+ }
+
+ OverlayUtil.renderPolygon(graphics, npc.getCanvasTilePoly(), Color.ORANGE);
+
+ Point imageLocation = npc.getCanvasImageLocation(medPackImage, 25);
+
+ if (imageLocation == null)
+ {
+ continue;
+ }
+
+ Composite originalComposite = graphics.getComposite();
+ Composite translucentComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f);
+
+ graphics.setComposite(translucentComposite);
+
+ OverlayUtil.renderImageLocation(graphics, imageLocation, medPackImage);
+
+ graphics.setComposite(originalComposite);
+ }
+
+ return null;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/shayzieninfirmary/ShayzienInfirmaryPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/shayzieninfirmary/ShayzienInfirmaryPlugin.java
new file mode 100644
index 0000000000..f4ff6078f1
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/shayzieninfirmary/ShayzienInfirmaryPlugin.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2019, Yani
+ * 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.shayzieninfirmary;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.inject.Inject;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.NPC;
+import net.runelite.api.events.GameTick;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.OverlayManager;
+
+@Slf4j
+@PluginDescriptor(
+ name = "Shayzien Infirmary",
+ description = "Shows the status of wounded soldiers",
+ tags = {"shayzien", "infirmary", "soldiers"}
+)
+public class ShayzienInfirmaryPlugin extends Plugin
+{
+ @Getter(AccessLevel.PACKAGE)
+ private List unhealedSoldiers = new ArrayList();
+
+ @Inject
+ private OverlayManager overlayManager;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private ShayzienInfirmaryOverlay overlay;
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ loadPlugin();
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ unloadPlugin();
+ }
+
+ private void loadPlugin()
+ {
+ overlayManager.add(overlay);
+ }
+
+ private void unloadPlugin()
+ {
+ overlayManager.remove(overlay);
+ }
+
+ @Subscribe
+ public void onGameTick(GameTick event)
+ {
+ if(!isAtInfirmary())
+ {
+ return;
+ }
+
+ unhealedSoldiers.clear();
+
+ for (NPC npc : client.getNpcs())
+ {
+ if (isUnhealedSoldierId(npc.getId()))
+ {
+ unhealedSoldiers.add(npc);
+ }
+ }
+ }
+
+ public boolean isSoldierId(int npcId)
+ {
+ return (npcId >= 6826 && npcId <= 6857);
+ }
+
+ public boolean isUnhealedSoldierId(int npcId)
+ {
+ return (isSoldierId(npcId) && npcId % 2 == 0);
+ }
+
+ public boolean isHealedSoldierId(int npcId)
+ {
+ return (isSoldierId(npcId) && npcId % 2 == 1);
+ }
+
+ public boolean isAtInfirmary()
+ {
+ return client.getLocalPlayer().getWorldLocation().getRegionID() == 6200;
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/shiftwalker/ShiftWalkerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/shiftwalker/ShiftWalkerConfig.java
new file mode 100644
index 0000000000..dfd21986ba
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/shiftwalker/ShiftWalkerConfig.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2018, Plinko60
+ * 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.shiftwalker;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("shiftwalkhere")
+public interface ShiftWalkerConfig extends Config
+{
+
+ @ConfigItem(
+ keyName = "shiftWalkEverything",
+ name = "Walk Under Everything",
+ description = "Enable this option when you do not want to interact with anything while Shift is pressed. " +
+ "If Walk Here is an option it will be the action taken."
+ )
+ default boolean shiftWalkEverything()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "shiftWalkBoxTraps",
+ name = "Walk Under Box Traps",
+ description = "Press \"Shift\" to be able to walk under instead of picking up a Box Trap."
+ )
+ default boolean shiftWalkBoxTraps()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "shiftWalkAttackOption",
+ name = "Walk Under Attack Options",
+ description = "Press \"Shift\" to be able to walk instead of attacking. Make sure Left Click Attack is on."
+ )
+ default boolean shiftWalkAttackOption()
+ {
+ return true;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/shiftwalker/ShiftWalkerGroups.java b/runelite-client/src/main/java/net/runelite/client/plugins/shiftwalker/ShiftWalkerGroups.java
new file mode 100644
index 0000000000..aff9c0efdb
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/shiftwalker/ShiftWalkerGroups.java
@@ -0,0 +1,32 @@
+package net.runelite.client.plugins.shiftwalker;
+
+import java.util.HashSet;
+
+public final class ShiftWalkerGroups
+{
+ //Specific Targets to limit the walking to
+ private static final String BOX_TRAP = "BOX TRAP";
+ private static final String BOX_TRAP_SHAKING = "SHAKING BOX";
+
+ //Specific menu options to replace
+ private static final String BOX_TRAP_DISMANTLE = "DISMANTLE";
+ private static final String BOX_TRAP_CHECK = "CHECK";
+
+ private static final String ATTACK_OPTIONS_ATTACK = "ATTACK";
+
+ public static final HashSet BOX_TRAP_TARGETS = new HashSet<>();
+ public static final HashSet BOX_TRAP_KEYWORDS = new HashSet<>();
+ public static final HashSet ATTACK_OPTIONS_KEYWORDS = new HashSet<>();
+
+ static
+ {
+ BOX_TRAP_TARGETS.add(BOX_TRAP);
+ BOX_TRAP_TARGETS.add(BOX_TRAP_SHAKING);
+
+ BOX_TRAP_KEYWORDS.add(BOX_TRAP_DISMANTLE);
+ BOX_TRAP_KEYWORDS.add(BOX_TRAP_CHECK);
+
+ ATTACK_OPTIONS_KEYWORDS.add(ATTACK_OPTIONS_ATTACK);
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/shiftwalker/ShiftWalkerInputListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/shiftwalker/ShiftWalkerInputListener.java
new file mode 100644
index 0000000000..677f30357d
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/shiftwalker/ShiftWalkerInputListener.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018, Plinko60
+ * 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.shiftwalker;
+
+import net.runelite.client.input.KeyListener;
+
+import javax.inject.Inject;
+import java.awt.event.KeyEvent;
+
+public class ShiftWalkerInputListener implements KeyListener
+{
+
+ @Inject
+ private ShiftWalkerPlugin plugin;
+
+ @Override
+ public void keyTyped(KeyEvent event)
+ {
+
+ }
+
+ @Override
+ public void keyPressed(KeyEvent event)
+ {
+ if (event.getKeyCode() == KeyEvent.VK_SHIFT)
+ {
+ plugin.setHotKeyPressed(true);
+ }
+ }
+
+ @Override
+ public void keyReleased(KeyEvent event)
+ {
+ if (event.getKeyCode() == KeyEvent.VK_SHIFT)
+ {
+ plugin.setHotKeyPressed(false);
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/shiftwalker/ShiftWalkerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/shiftwalker/ShiftWalkerPlugin.java
new file mode 100644
index 0000000000..aef2adb45e
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/shiftwalker/ShiftWalkerPlugin.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2018, Plinko60
+ * 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.shiftwalker;
+
+import com.google.inject.Provides;
+import lombok.Setter;
+import net.runelite.api.*;
+import net.runelite.api.events.*;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.input.KeyManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.util.Text;
+
+import javax.inject.Inject;
+
+/**
+ * Shift Walker Plugin. Credit to MenuEntrySwapperPlugin for code some code structure used here.
+ */
+@PluginDescriptor(
+ name = "!Shift To Walk Here",
+ description = "Use Shift to toggle the Walk Here menu option. While pressed you will Walk rather than interact with objects.",
+ tags = {"npcs", "items", "objects"},
+ enabledByDefault = false
+)
+public class ShiftWalkerPlugin extends Plugin
+{
+
+ private static final String WALK_HERE = "WALK HERE";
+ private static final String CANCEL = "CANCEL";
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private ShiftWalkerConfig config;
+
+ @Inject
+ private ShiftWalkerInputListener inputListener;
+
+ @Inject
+ private ConfigManager configManager;
+
+ @Inject
+ private KeyManager keyManager;
+
+ @Setter
+ private boolean hotKeyPressed = false;
+
+ @Provides
+ ShiftWalkerConfig provideConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(ShiftWalkerConfig.class);
+ }
+
+ @Override
+ public void startUp()
+ {
+ keyManager.registerKeyListener(inputListener);
+ }
+
+ @Override
+ public void shutDown()
+ {
+ keyManager.unregisterKeyListener(inputListener);
+ }
+
+ @Subscribe
+ public void onFocusChanged(FocusChanged event)
+ {
+ if (!event.isFocused())
+ {
+ hotKeyPressed = false;
+ }
+ }
+
+ /**
+ * Event when a new menu entry was added.
+ * @param event {@link MenuEntryAdded}.
+ */
+ @Subscribe
+ public void onMenuEntryAdded(MenuEntryAdded event)
+ {
+ if (client.getGameState() != GameState.LOGGED_IN || !hotKeyPressed)
+ {
+ return;
+ }
+
+ final String pOptionToReplace = Text.removeTags(event.getOption()).toUpperCase();
+
+ //If the option is already to walk there, or cancel we don't need to swap it with anything
+ if (pOptionToReplace.equals(CANCEL) || pOptionToReplace.equals(WALK_HERE))
+ {
+ return;
+ }
+
+ String target = Text.removeTags(event.getTarget().toUpperCase());
+
+ if (config.shiftWalkEverything())
+ {
+ //swap(pOptionToReplace); //Swap everything with walk here
+ stripEntries();
+ }
+ else if (config.shiftWalkBoxTraps() && ShiftWalkerGroups.BOX_TRAP_TARGETS.contains(target)
+ && ShiftWalkerGroups.BOX_TRAP_KEYWORDS.contains(pOptionToReplace))
+ {
+ //swap(pOptionToReplace); //Swap only on box traps
+ stripEntries();
+ }
+ else if (config.shiftWalkAttackOption() && ShiftWalkerGroups.ATTACK_OPTIONS_KEYWORDS.contains(pOptionToReplace))
+ {
+ //swap(pOptionToReplace); //Swap on everything that has an attack keyword as the first option
+ stripEntries();
+ }
+ }
+
+ /**
+ * Strip everything except "Walk here"
+ * Other way was unconventional because if there was multiple targets in the menu entry it wouldn't swap correctly
+ */
+ private void stripEntries() {
+ MenuEntry walkkHereEntry = null;
+
+ for (MenuEntry entry : client.getMenuEntries()) {
+ switch (entry.getOption()) {
+ case "Walk here":
+ walkkHereEntry = entry;
+ break;
+ }
+ }
+ if (walkkHereEntry != null) {
+ MenuEntry[] newEntries = new MenuEntry[1];
+ newEntries[0] = walkkHereEntry;
+ client.setMenuEntries(newEntries);
+ }
+ }
+
+ /**
+ * Swaps menu entries if the entries could be found. This places Walk Here where the top level menu option was.
+ * @param pOptionToReplace The String containing the Menu Option that needs to be replaced. IE: "Attack", "Chop Down".
+ */
+ private void swap(String pOptionToReplace)
+ {
+ MenuEntry[] entries = client.getMenuEntries();
+
+ Integer walkHereEntry = searchIndex(entries, WALK_HERE);
+ Integer entryToReplace = searchIndex(entries, pOptionToReplace);
+
+ if (walkHereEntry != null
+ && entryToReplace != null)
+ {
+ MenuEntry walkHereMenuEntry = entries[walkHereEntry];
+ entries[walkHereEntry] = entries[entryToReplace];
+ entries[entryToReplace] = walkHereMenuEntry;
+
+ client.setMenuEntries(entries);
+ }
+ }
+
+ /**
+ * Finds the index of the menu that contains the verbiage we are looking for.
+ * @param pMenuEntries The list of {@link MenuEntry}s.
+ * @param pMenuEntryToSearchFor The Option in the menu to search for.
+ * @return The index location or null if it was not found.
+ */
+ private Integer searchIndex(MenuEntry[] pMenuEntries, String pMenuEntryToSearchFor)
+ {
+ Integer indexLocation = 0;
+
+ for (MenuEntry menuEntry : pMenuEntries)
+ {
+ String entryOption = Text.removeTags(menuEntry.getOption()).toUpperCase();
+
+ if (entryOption.equals(pMenuEntryToSearchFor))
+ {
+ return indexLocation;
+ }
+
+ indexLocation++;
+ }
+
+ return null;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayermusiq/QuestGuideLinks.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayermusiq/QuestGuideLinks.java
new file mode 100644
index 0000000000..078491bb4d
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayermusiq/QuestGuideLinks.java
@@ -0,0 +1,207 @@
+package net.runelite.client.plugins.slayermusiq;
+
+import net.runelite.client.util.LinkBrowser;
+import net.runelite.api.ChatMessageType;
+import net.runelite.api.events.ChatMessage;
+import net.runelite.client.chat.ChatColorType;
+import net.runelite.client.chat.ChatMessageBuilder;
+import net.runelite.client.chat.ChatMessageManager;
+import net.runelite.client.chat.QueuedMessage;
+
+public class QuestGuideLinks {
+ private static final Link[] QUEST_GUIDE_LINKS = {
+ // Free Quests
+ new Link("Cook's Assistant", "https://www.youtube.com/watch?v=ehmtDRelj3c"),
+ new Link("Romeo & Juliet", "https://www.youtube.com/watch?v=rH_biWSNWVY"),
+ new Link("Demon Slayer", "https://www.youtube.com/watch?v=hgACrzJSiQk"),
+ new Link("Shield of Arrav", "https://www.youtube.com/watch?v=a_imLDKUdzg"),
+ new Link("Sheep Shearer", "https://www.youtube.com/watch?v=XFG3aNwK68s"),
+ new Link("The Restless Ghost", "https://www.youtube.com/watch?v=UkWNcsG_pXM"),
+ new Link("Ernest the Chicken", "https://www.youtube.com/watch?v=cq8NIVhSqh4"),
+ new Link("Vampire Slayer", "https://www.youtube.com/watch?v=FcEuxsDJWCU"),
+ new Link("Imp Catcher", "https://www.youtube.com/watch?v=LHgnl0FbOzk"),
+ new Link("Prince Ali Rescue", "https://www.youtube.com/watch?v=hrSPl1GfFaw"),
+ new Link("Doric's Quest", "https://www.youtube.com/watch?v=5TYyxHU27a4"),
+ new Link("Black Knights' Fortress", "https://www.youtube.com/watch?v=aekoZi3f9cU"),
+ new Link("Witch's Potion", "https://www.youtube.com/watch?v=XV4i5sPUvXo"),
+ new Link("The Knight's Sword", "https://www.youtube.com/watch?v=UkBWaI0rOqE"),
+ new Link("Goblin Diplomacy", "https://www.youtube.com/watch?v=P9BKOb_dLoY"),
+ new Link("Pirate's Treasure", "https://www.youtube.com/watch?v=zcD87PQW8Qk"),
+ new Link("Dragon Slayer", "https://www.youtube.com/watch?v=bMtCjlFOaBI"),
+ new Link("Rune Mysteries", "https://www.youtube.com/watch?v=l8ZhaN8uoS0"),
+ new Link("Misthalin Mystery", "https://www.youtube.com/watch?v=QlFqVAobAlQ"),
+ new Link("The Corsair Curse", "https://www.youtube.com/watch?v=wi7mUAHExz4"),
+ new Link("X Marks the Spot", "https://www.youtube.com/watch?v=GhRgvEG5jxQ"),
+ // Members Quests
+ new Link("Druidic Ritual", "https://www.youtube.com/watch?v=QIfU6HSmH4w"),
+ new Link("Lost City", "https://www.youtube.com/watch?v=T-kQNUSjFZI"),
+ new Link("Witch's House", "https://www.youtube.com/watch?v=TLsg7Wa-LUA"),
+ new Link("Merlin's Crystal", "https://www.youtube.com/watch?v=ESX-qriNtCE"),
+ new Link("Heroes' Quest", "https://www.youtube.com/watch?v=hK2N0WLKviE"),
+ new Link("Scorpion Catcher", "https://www.youtube.com/watch?v=xpqdec7_ZWg"),
+ new Link("Family Crest", "https://www.youtube.com/watch?v=0mk_Cgjr738"),
+ new Link("Monk's Friend", "https://www.youtube.com/watch?v=avi4y4G3Hcw"),
+ new Link("Temple of Ikov", "https://www.youtube.com/watch?v=5K7jDgr_4Z4"),
+ new Link("Clock Tower", "https://www.youtube.com/watch?v=GUCkkQFzyDw"),
+ new Link("Holy Grail", "https://www.youtube.com/watch?v=cgXoV1QlYco"),
+ new Link("Tree Gnome Village", "https://www.youtube.com/watch?v=T6Su__yuyRI"),
+ new Link("Fight Arena", "https://www.youtube.com/watch?v=4Nqjep2E5pw"),
+ new Link("Hazeel Cult", "https://www.youtube.com/watch?v=2_fhFJW6cNY"),
+ new Link("Sheep Herder", "https://www.youtube.com/watch?v=akC9FeYCG1Q"),
+ new Link("Plague City", "https://www.youtube.com/watch?v=Hf2wQQZL5CU"),
+ new Link("Waterfall Quest", "https://www.youtube.com/watch?v=xWBSnGkQTi4"),
+ new Link("Jungle Potion", "https://www.youtube.com/watch?v=xqLKsFz08As"),
+ new Link("The Grand Tree", "https://www.youtube.com/watch?v=N5e_Jus_E-Y"),
+ new Link("Underground Pass", "https://www.youtube.com/watch?v=5klGJg1wY8k"),
+ new Link("Observatory Quest", "https://www.youtube.com/watch?v=yxa9B6svv44"),
+ new Link("Watchtower", "https://www.youtube.com/watch?v=Vb10GoYP7FE"),
+ new Link("Dwarf Cannon", "https://www.youtube.com/watch?v=pROFg5jcCR0"),
+ new Link("Murder Mystery", "https://www.youtube.com/watch?v=P1IDGCA2f9o"),
+ new Link("The Dig Site", "https://www.youtube.com/watch?v=TOdcWV4MzuU"),
+ new Link("Gertrude's Cat", "https://www.youtube.com/watch?v=g7S09wA8EAY"),
+ new Link("Legends' Quest", "https://www.youtube.com/watch?v=Lid8enDEF_U"),
+ new Link("Death Plateau", "https://www.youtube.com/watch?v=SIQFmTvnb6w"),
+ new Link("Big Chompy Bird Hunting", "https://www.youtube.com/watch?v=s2fytMOHJXI"),
+ new Link("Elemental Workshop I", "https://www.youtube.com/watch?v=tbZD2RDqvfQ"),
+ new Link("Nature Spirit", "https://www.youtube.com/watch?v=Enf8vUWb5o0"),
+ new Link("Priest in Peril", "https://www.youtube.com/watch?v=fyYri6wUQIU"),
+ new Link("Regicide", "https://www.youtube.com/watch?v=KkWM-ok3C4Y"),
+ new Link("Tai Bwo Wannai Trio", "https://www.youtube.com/watch?v=Mdair5mvZL0"),
+ new Link("Troll Stronghold", "https://www.youtube.com/watch?v=zqmUs-f3AKA"),
+ new Link("Horror from the Deep", "https://www.youtube.com/watch?v=9htK8kb6DR8"),
+ new Link("Throne of Miscellania", "https://www.youtube.com/watch?v=fzGMnv2skBE"),
+ new Link("Monkey Madness I", "https://www.youtube.com/watch?v=VnoRfeBnPFA"),
+ new Link("Haunted Mine", "https://www.youtube.com/watch?v=cIc6loJHm9Q"),
+ new Link("Troll Romance", "https://www.youtube.com/watch?v=j2zifZVu7Gc"),
+ new Link("In Search of the Myreque", "https://www.youtube.com/watch?v=5nmYFHdAXAQ"),
+ new Link("Creature of Fenkenstrain", "https://www.youtube.com/watch?v=swqUVIs7B7M"),
+ new Link("Roving Elves", "https://www.youtube.com/watch?v=J3qf9DnT9cA"),
+ new Link("One Small Favour", "https://www.youtube.com/watch?v=ix_0-W3e9ps"),
+ new Link("Mountain Daughter", "https://www.youtube.com/watch?v=HETx_LX7aiY"),
+ new Link("Between a Rock...", "https://www.youtube.com/watch?v=cB11I45EGgA"),
+ new Link("The Golem", "https://www.youtube.com/watch?v=qpEHpiO6lLw"),
+ new Link("Desert Treasure", "https://www.youtube.com/watch?v=BuIqulIsICo"),
+ new Link("Icthlarin's Little Helper", "https://www.youtube.com/watch?v=wpNKm8_vUOM"),
+ new Link("Tears of Guthix", "https://www.youtube.com/watch?v=EMonDNI0uPk"),
+ new Link("The Lost Tribe", "https://www.youtube.com/watch?v=spZErjRnCdc"),
+ new Link("The Giant Dwarf", "https://www.youtube.com/watch?v=Z7PsGpOYgxY"),
+ new Link("Recruitment Drive", "https://www.youtube.com/watch?v=sOuzMpA_xtw"),
+ new Link("Mourning's Ends Part I", "https://www.youtube.com/watch?v=vuzAdk-h3c0"),
+ new Link("Garden of Tranquillity", "https://www.youtube.com/watch?v=7hbCzYnLCsQ"),
+ new Link("A Tail of Two Cats", "https://www.youtube.com/watch?v=SgN9Yw_YqHk"),
+ new Link("Wanted!", "https://www.youtube.com/watch?v=ZHZAKDCfXGs"),
+ new Link("Mourning's Ends Part II", "https://www.youtube.com/watch?v=FK5sLogGbU8"),
+ new Link("Rum Deal", "https://www.youtube.com/watch?v=I14CIu5x2S8"),
+ new Link("Shadow of the Storm", "https://www.youtube.com/watch?v=5ZvWd3XCQjI"),
+ new Link("Ratcatchers", "https://www.youtube.com/watch?v=s7G22fEuhTc"),
+ new Link("Spirits of the Elid", "https://www.youtube.com/watch?v=A1zAX55hZC0"),
+ new Link("Devious Minds", "https://www.youtube.com/watch?v=_UtlFmrWt1w"),
+ new Link("Enakhra's Lament", "https://www.youtube.com/watch?v=Y3kEIPYVaVE"),
+ new Link("Cabin Fever", "https://www.youtube.com/watch?v=k5DtxNXhOaw"),
+ new Link("Fairytale I - Growing Pains", "https://www.youtube.com/watch?v=cfGI9qFOmsg"),
+ new Link("Recipe for Disaster", "https://www.youtube.com/watch?v=hrAyyInJaTA"),
+ new Link("In Aid of the Myreque", "https://www.youtube.com/watch?v=O2Ru2NmuTaA"),
+ new Link("A Soul's Bane", "https://www.youtube.com/watch?v=dp8dp79qp6I"),
+ new Link("Rag and Bone Man", "https://www.youtube.com/watch?v=3owXSeN56W8"),
+ new Link("Swan Song", "https://www.youtube.com/watch?v=IpmERThXv2g"),
+ new Link("Royal Trouble", "https://www.youtube.com/watch?v=bVWUlKzNXEg"),
+ new Link("Death to the Dorgeshuun", "https://www.youtube.com/watch?v=2XJHuLhig98"),
+ new Link("Fairytale II - Cure a Queen", "https://www.youtube.com/watch?v=P6KkRk4_e3U"),
+ new Link("Lunar Diplomacy", "https://www.youtube.com/watch?v=vmeSKb7IBgQ"),
+ new Link("The Eyes of Glouphrie", "https://www.youtube.com/watch?v=0YCPwmZcxKA"),
+ new Link("Darkness of Hallowvale", "https://www.youtube.com/watch?v=QziKl99qdtU"),
+ new Link("Elemental Workshop II", "https://www.youtube.com/watch?v=Bb4E7ecIgv0"),
+ new Link("My Arm's Big Adventure", "https://www.youtube.com/watch?v=xa1KWOewgYA"),
+ new Link("Enlightened Journey", "https://www.youtube.com/watch?v=XAPthC8d7k0"),
+ new Link("Eagles' Peak", "https://www.youtube.com/watch?v=KDxIrrwXp7U"),
+ new Link("Animal Magnetism", "https://www.youtube.com/watch?v=kUyjXA7TaFU"),
+ new Link("Contact!", "https://www.youtube.com/watch?v=czn-yWABBWs"),
+ new Link("Cold War", "https://www.youtube.com/watch?v=0m1KpP-qKWI"),
+ new Link("The Fremennik Isles", "https://www.youtube.com/watch?v=EvxhiOWmraY"),
+ new Link("The Great Brain Robbery", "https://www.youtube.com/watch?v=ImHFASuNUN8"),
+ new Link("What Lies Below", "https://www.youtube.com/watch?v=f_9nVMGTtuo"),
+ new Link("Olaf's Quest", "https://www.youtube.com/watch?v=mXV5bM1NFMM"),
+ new Link("Dream Mentor", "https://www.youtube.com/watch?v=XDLUu0Kf0sE"),
+ new Link("Grim Tales", "https://www.youtube.com/watch?v=dFB0Q6v8Apw"),
+ new Link("King's Ransom", "https://www.youtube.com/watch?v=UJz9ZfF3uCY"),
+ new Link("Shilo Village", "https://www.youtube.com/watch?v=bDvBi8FT-QI"),
+ new Link("Biohazard", "https://www.youtube.com/watch?v=n9k87LwOGMk"),
+ new Link("Tower of Life", "https://www.youtube.com/watch?v=KReMcWpeY3k"),
+ new Link("Rag and Bone Man II", "https://www.youtube.com/watch?v=KGdHiDDUX_U"),
+ new Link("Zogre Flesh Eaters", "https://www.youtube.com/watch?v=vzm4949kXP4"),
+ new Link("Monkey Madness II", "https://www.youtube.com/watch?v=ykE5LbjABaI"),
+ new Link("Client of Kourend", "https://www.youtube.com/watch?v=Y-KIHF-cL9w"),
+ new Link("The Queen of Thieves", "https://www.youtube.com/watch?v=W94zFZVrHkQ"),
+ new Link("Bone Voyage", "https://www.youtube.com/watch?v=-VTR4p8kPmI"),
+ new Link("Dragon Slayer II", "https://www.youtube.com/watch?v=4BMb3Zwzk_U"),
+ new Link("The Depths of Despair", "https://www.youtube.com/watch?v=CaVUk2eAsKs"),
+ new Link("A Taste of Hope", "https://www.youtube.com/watch?v=VjdgEIizdSc"),
+ new Link("Tale of the Righteous", "https://www.youtube.com/watch?v=99yiv0tPl58"),
+ new Link("Making Friends with My Arm", "https://www.youtube.com/watch?v=DltzzhIsM_Q"),
+ new Link("The Ascent of Arceuus", "https://www.youtube.com/watch?v=4VQnfrv6S18"),
+ new Link("The Forsaken Tower", "https://www.youtube.com/watch?v=con0sXl5NBY"),
+ new Link("Fishing Contest", "https://www.youtube.com/watch?v=XYSv37A_l5w"),
+ new Link("Tribal Totem", "https://www.youtube.com/watch?v=XkUEIjr886M"),
+ new Link("Sea Slug", "https://www.youtube.com/watch?v=oOZVfa5SkVQ"),
+ new Link("The Tourist Trap", "https://www.youtube.com/watch?v=0bmSCCepMvo"),
+ new Link("Eadgar's Ruse", "https://www.youtube.com/watch?v=aVQ3DjTElXg"),
+ new Link("Shades of Mort'ton", "https://www.youtube.com/watch?v=eF05R8OMxgg"),
+ new Link("The Fremennik Trials", "https://www.youtube.com/watch?v=YUIvEgcvl5c"),
+ new Link("Ghosts Ahoy", "https://www.youtube.com/watch?v=aNBkLOywDfM"),
+ new Link("The Feud", "https://www.youtube.com/watch?v=nlBSc9IUklA"),
+ new Link("Forgettable Tale...", "https://www.youtube.com/watch?v=3HvFd6AxNU0"),
+ new Link("Making History", "https://www.youtube.com/watch?v=bOTGi2zAuhs"),
+ new Link("The Hand in the Sand", "https://www.youtube.com/watch?v=gdNLcZ-l1Lw"),
+ new Link("The Slug Menace", "https://www.youtube.com/watch?v=BRQbdr3JEZ8"),
+ new Link("Another Slice of H.A.M.", "https://www.youtube.com/watch?v=Yq3db7827Lk")
+ };
+
+ private static class Link {
+
+ private String questName;
+ private String url;
+
+ public Link(String questName, String url) {
+ this.questName = questName;
+ this.url = url;
+ }
+
+ public String getQuestName() {
+ return questName;
+ }
+
+ public void openURL() {
+ LinkBrowser.browse(this.url);
+ }
+
+ }
+
+ private static boolean openGuide(String questName) {
+ for (Link link : QUEST_GUIDE_LINKS) {
+ if (link.getQuestName().equals(questName)) {
+ link.openURL();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static void logQuestNotFoundError(String questName, ChatMessageManager chatMessageManager) {
+ String chatMessage = new ChatMessageBuilder()
+ .append(ChatColorType.HIGHLIGHT)
+ .append("Could not find Slayermusiq1 guide for " + questName)
+ .build();
+
+ chatMessageManager.queue(QueuedMessage.builder()
+ .type(ChatMessageType.CONSOLE)
+ .runeLiteFormattedMessage(chatMessage)
+ .build());
+ }
+
+ public static void tryOpenGuide(String questName, ChatMessageManager chatMessageManager) {
+ boolean success = openGuide(questName);
+ if (!success) {
+ logQuestNotFoundError(questName, chatMessageManager);
+ }
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayermusiq/SlayermusiqPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayermusiq/SlayermusiqPlugin.java
new file mode 100644
index 0000000000..7e07a0564d
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayermusiq/SlayermusiqPlugin.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2018, Jeremy Berchtold
+ * 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.
+ */
+
+
+// Based off RuneLite's Wiki Plugin
+/*
+ * Copyright (c) 2018 Abex
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.plugins.slayermusiq;
+
+import com.google.inject.Provides;
+import com.google.common.primitives.Ints;
+import java.awt.Dimension;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.inject.Inject;
+import javax.swing.SwingUtilities;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.MenuAction;
+import net.runelite.api.MenuEntry;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.api.events.MenuEntryAdded;
+import net.runelite.api.events.MenuOptionClicked;
+import net.runelite.client.util.Text;
+import net.runelite.client.chat.ChatMessageManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+
+@PluginDescriptor(
+ name = "!Slayermusiq1 Guides",
+ description = "Adds a right-click option to go to Slayermusiq1's guides from the quest tab",
+ tags = {"quest", "guide", "slayermusiq"}
+)
+@Slf4j
+public class SlayermusiqPlugin extends Plugin
+{
+
+ private static final int[] QUESTLIST_WIDGET_IDS = new int[]
+ {
+ WidgetInfo.QUESTLIST_FREE_CONTAINER.getId(),
+ WidgetInfo.QUESTLIST_MEMBERS_CONTAINER.getId(),
+ WidgetInfo.QUESTLIST_MINIQUEST_CONTAINER.getId(),
+ };
+
+ private static final String MENUOP_SLAYERMUSIQ = "Slayermusiq";
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private ChatMessageManager chatMessageManager;
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ //
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ //
+ }
+
+ @Subscribe
+ public void onMenuEntryAdded(MenuEntryAdded event)
+ {
+ int widgetID = event.getActionParam1();
+ if (Ints.contains(QUESTLIST_WIDGET_IDS, widgetID) && "Read Journal:".equals(event.getOption())) {
+ MenuEntry[] menuEntries = client.getMenuEntries();
+
+ MenuEntry newMenuEntry = createSlayermusiqOptionMenuEntry(event);
+ menuEntries = Arrays.copyOf(menuEntries, menuEntries.length + 1);
+ menuEntries[menuEntries.length - 1] = newMenuEntry;
+
+ client.setMenuEntries(menuEntries);
+ }
+ }
+
+ @Subscribe
+ private void onMenuOptionClicked(MenuOptionClicked ev) {
+ if (ev.getMenuAction() == MenuAction.RUNELITE && ev.getMenuOption().equals(MENUOP_SLAYERMUSIQ)) {
+ ev.consume();
+ String quest = Text.removeTags(ev.getMenuTarget());
+ QuestGuideLinks.tryOpenGuide(quest, chatMessageManager);
+ }
+ }
+
+ private MenuEntry createSlayermusiqOptionMenuEntry(MenuEntryAdded event) {
+ int widgetIndex = event.getActionParam0();
+ int widgetID = event.getActionParam1();
+
+ MenuEntry menuEntry = new MenuEntry();
+ menuEntry.setTarget(event.getTarget());
+ menuEntry.setOption(MENUOP_SLAYERMUSIQ);
+ menuEntry.setParam0(widgetIndex);
+ menuEntry.setParam1(widgetID);
+ menuEntry.setType(MenuAction.RUNELITE.getId());
+
+ return menuEntry;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/spellbookfixer/SpellbookFixerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/spellbookfixer/SpellbookFixerConfig.java
new file mode 100644
index 0000000000..bb16cd3709
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/spellbookfixer/SpellbookFixerConfig.java
@@ -0,0 +1,110 @@
+package net.runelite.client.plugins.spellbookfixer;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("spellbookfixer")
+public interface SpellbookFixerConfig extends Config
+{
+ @ConfigItem(position = 0, keyName = "shouldHideOthers", name = "Hide Others", description = "Toggle on to hide spells not useful for pking that cannot be filtered otherwise.")
+ default boolean shouldHideOthers()
+ {
+ return false;
+ }
+
+ //ice blitz
+ @ConfigItem(position = 1, keyName = "shouldModifyIceBlitz", name = "Ice Blitz", description = "Toggle on to enable Ice Blitz modifications.")
+ default boolean shouldModifyIceBlitz() { return false; }
+ @ConfigItem(position = 2, keyName = "getBlitzPositionX", name = "Ice Blitz Pos X", description = "Modifies the X-axis position of Ice Blitz.")
+ default int getBlitzPositionX()
+ {
+ return 0;
+ }
+ @ConfigItem(position = 3, keyName = "getBlitzPositionY", name = "Ice Blitz Pos Y", description = "Modifies the Y-axis position of Ice Blitz.")
+ default int getBlitzPositionY()
+ {
+ return 118;
+ }
+ @ConfigItem(position = 4, keyName = "getBlitzSize", name = "Ice Blitz Size", description = "Modifies the width of Ice Blitz.")
+ default int getBlitzSize()
+ {
+ return 80;
+ }
+
+ //ice barrage
+ @ConfigItem(position = 5, keyName = "shouldModifyIceBarrage", name = "Ice Barrage", description = "Toggle on to enable Ice Barrage modifications.")
+ default boolean shouldModifyIceBarrage() { return false; }
+ @ConfigItem(position = 6, keyName = "getBarragePositionX", name = "Ice Barrage Pos X", description = "Modifies the X-axis position of Ice Barrage.")
+ default int getBarragePositionX()
+ {
+ return 0;
+ }
+ @ConfigItem(position = 7, keyName = "getBarragePositionY", name = "Ice Barrage Pos X", description = "Modifies the X-axis position of Ice Barrage.")
+ default int getBarragePositionY()
+ {
+ return 0;
+ }
+ @ConfigItem(position = 8, keyName = "getBarrageSize", name = "Ice Barrage Size", description = "Modifies the width position of Ice Barrage.")
+ default int getBarrageSize()
+ {
+ return 80;
+ }
+
+ //vengeance
+ @ConfigItem(position = 9, keyName = "shouldModifyVengeance", name = "Vengeance", description = "Toggle on to enable Vengeance modifications.")
+ default boolean shouldModifyVengeance() { return false; }
+ @ConfigItem(position = 10, keyName = "getVengeancePositionX", name = "Vengeance Pos X", description = "Modifies the X-axis position of Vengeance.")
+ default int getVengeancePositionX()
+ {
+ return 0;
+ }
+ @ConfigItem(position = 11, keyName = "getVengeancePositionY", name = "Vengeance Pos X", description = "Modifies the X-axis position of Vengeance.")
+ default int getVengeancePositionY()
+ {
+ return 0;
+ }
+ @ConfigItem(position = 12, keyName = "getVengeanceSize", name = "Vengeance Size", description = "Modifies the width position of Vengeance.")
+ default int getVengeanceSize()
+ {
+ return 80;
+ }
+
+ //teleblock
+ @ConfigItem(position = 13, keyName = "shouldModifyTeleBlock", name = "TeleBlock", description = "Toggle on to enable TeleBlock modifications.")
+ default boolean shouldModifyTeleBlock() { return false; }
+ @ConfigItem(position = 14, keyName = "getTeleBlockPositionX", name = "TeleBlock Pos X", description = "Modifies the X-axis position of TeleBlock.")
+ default int getTeleBlockPositionX()
+ {
+ return 0;
+ }
+ @ConfigItem(position = 15, keyName = "getTeleBlockPositionY", name = "TeleBlock Pos X", description = "Modifies the X-axis position of TeleBlock.")
+ default int getTeleBlockPositionY()
+ {
+ return 0;
+ }
+ @ConfigItem(position = 16, keyName = "getTeleBlockSize", name = "TeleBlock Size", description = "Modifies the width position of TeleBlock.")
+ default int getTeleBlockSize()
+ {
+ return 80;
+ }
+
+ //entangle
+ @ConfigItem(position = 17, keyName = "shouldModifyEntangle", name = "Entangle", description = "Toggle on to enable Entangle modifications.")
+ default boolean shouldModifyEntangle() { return false; }
+ @ConfigItem(position = 18, keyName = "getEntanglePositionX", name = "Entangle Pos X", description = "Modifies the X-axis position of Entangle.")
+ default int getEntanglePositionX()
+ {
+ return 0;
+ }
+ @ConfigItem(position = 19, keyName = "getEntanglePositionY", name = "Entangle Pos X", description = "Modifies the X-axis position of Entangle.")
+ default int getEntanglePositionY()
+ {
+ return 118;
+ }
+ @ConfigItem(position = 20, keyName = "getEntangleSize", name = "Entangle Size", description = "Modifies the width position of Entangle.")
+ default int getEntangleSize()
+ {
+ return 80;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/spellbookfixer/SpellbookFixerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/spellbookfixer/SpellbookFixerPlugin.java
new file mode 100644
index 0000000000..7d71cef371
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/spellbookfixer/SpellbookFixerPlugin.java
@@ -0,0 +1,170 @@
+package net.runelite.client.plugins.spellbookfixer;
+
+import com.google.inject.Provides;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.GameState;
+import net.runelite.api.events.GameStateChanged;
+import net.runelite.api.events.GameTick;
+import net.runelite.api.events.WidgetLoaded;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetID;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+
+import javax.inject.Inject;
+
+
+@PluginDescriptor(
+ name = "Spellbook Fixer",
+ description = "Resize and filter spellbook for PKing",
+ tags = {"resize", "spellbook", "magic", "spell", "pk", "book", "filter", "bogla"}
+)
+@Slf4j
+public class SpellbookFixerPlugin extends Plugin
+{
+ @Inject
+ private Client client;
+
+ @Inject
+ SpellbookFixerConfig config;
+
+ @Provides
+ SpellbookFixerConfig provideConfig(ConfigManager configManager) { return configManager.getConfig(SpellbookFixerConfig.class); }
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ adjustSpellbook();
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ resetSpellbook();
+ }
+
+ @Subscribe
+ public void onGameStateChanged(GameStateChanged event)
+ {
+ if (event.getGameState() == GameState.LOGGED_IN)
+ adjustSpellbook();
+ }
+
+ @Subscribe
+ public void onWidgetLoaded(WidgetLoaded event)
+ {
+ if (event.getGroupId() == WidgetID.SPELLBOOK_GROUP_ID)
+ adjustSpellbook();
+ }
+
+ @Subscribe
+ public void onGameTick(GameTick event)
+ {
+ adjustSpellbook();
+ }
+
+ private void adjustSpellbook()
+ {
+ if (client.getGameState() != GameState.LOGGED_IN)
+ return;
+
+ try
+ {
+ if (config.shouldModifyIceBarrage())
+ modifySpell(WidgetInfo.SPELL_ICE_BARRAGE, config.getBarragePositionX(), config.getBarragePositionY(), config.getBarrageSize());
+
+ if (config.shouldModifyIceBlitz())
+ modifySpell(WidgetInfo.SPELL_ICE_BLITZ, config.getBlitzPositionX(), config.getBlitzPositionY(), config.getBlitzSize());
+
+ if (config.shouldModifyVengeance())
+ modifySpell(WidgetInfo.SPELL_VENGEANCE, config.getVengeancePositionX(), config.getVengeancePositionY(), config.getVengeanceSize());
+
+ if (config.shouldModifyTeleBlock())
+ modifySpell(WidgetInfo.SPELL_TELE_BLOCK, config.getTeleBlockPositionX(), config.getTeleBlockPositionY(), config.getTeleBlockSize());
+
+ if (config.shouldModifyEntangle())
+ modifySpell(WidgetInfo.SPELL_ENTANGLE, config.getEntanglePositionX(), config.getEntanglePositionY(), config.getEntangleSize());
+
+ setSpellHidden(WidgetInfo.SPELL_BLOOD_BLITZ, config.shouldHideOthers());
+ setSpellHidden(WidgetInfo.SPELL_VENGEANCE_OTHER, config.shouldHideOthers());
+ setSpellHidden(WidgetInfo.SPELL_BIND, config.shouldHideOthers());
+ setSpellHidden(WidgetInfo.SPELL_SNARE, config.shouldHideOthers());
+ }
+ catch (Exception e)
+ {
+ //swallow
+ }
+
+
+ }
+
+ private void resetSpellbook()
+ {
+ if (client.getGameState() != GameState.LOGGED_IN)
+ return;
+
+ try
+ {
+ if (config.shouldModifyIceBarrage())
+ modifySpell(WidgetInfo.SPELL_ICE_BARRAGE, config.getBarragePositionX(), config.getBarragePositionY(), 24);
+
+ if (config.shouldModifyIceBlitz())
+ modifySpell(WidgetInfo.SPELL_ICE_BLITZ, config.getBlitzPositionX(), config.getBlitzPositionY(), 24);
+
+ if (config.shouldModifyVengeance())
+ modifySpell(WidgetInfo.SPELL_VENGEANCE, config.getVengeancePositionX(), config.getVengeancePositionY(), 24);
+
+ if (config.shouldModifyTeleBlock())
+ modifySpell(WidgetInfo.SPELL_TELE_BLOCK, config.getTeleBlockPositionX(), config.getTeleBlockPositionY(), 24);
+
+ if (config.shouldModifyEntangle())
+ modifySpell(WidgetInfo.SPELL_ENTANGLE, config.getEntanglePositionX(), config.getEntanglePositionY(), 24);
+
+ setSpellHidden(WidgetInfo.SPELL_BLOOD_BLITZ, false);
+ setSpellHidden(WidgetInfo.SPELL_VENGEANCE_OTHER, false);
+ setSpellHidden(WidgetInfo.SPELL_BIND, false);
+ setSpellHidden(WidgetInfo.SPELL_SNARE, false);
+ }
+ catch (Exception e)
+ {
+ //swallow
+ }
+ }
+
+ private void modifySpell(WidgetInfo widgetInfo, int posX, int posY, int size)
+ {
+ Widget widget = client.getWidget(widgetInfo);
+
+ if (widget == null)
+ return;
+
+ try
+ {
+ widget.setOriginalX(posX);
+ widget.setOriginalY(posY);
+ widget.setOriginalWidth(size);
+ widget.setOriginalHeight(size);
+ widget.revalidate();
+ }
+ catch (Exception e)
+ {
+ //swallow
+ }
+
+ }
+
+ private void setSpellHidden(WidgetInfo widgetInfo, boolean hidden)
+ {
+ Widget widget = client.getWidget(widgetInfo);
+
+ if (widget == null)
+ return;
+
+ widget.setHidden(hidden);
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/ActionType.java b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/ActionType.java
new file mode 100644
index 0000000000..b801e8b2a8
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/ActionType.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018, Davis Cook
+ * Copyright (c) 2018, Daddy Dozer
+ * 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.suppliestracker;
+
+/**
+ * Type of action performed in a menu
+ */
+public enum ActionType
+{
+
+ CONSUMABLE, TELEPORT, CAST;
+
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/BlowpipeDartType.java b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/BlowpipeDartType.java
new file mode 100644
index 0000000000..09182d6cde
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/BlowpipeDartType.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018, Davis Cook
+ * 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.suppliestracker;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import static net.runelite.api.ItemID.*;
+
+/**
+ * Type of darts that can be put into the blowpipe
+ */
+@AllArgsConstructor
+public enum BlowpipeDartType
+{
+ BRONZE(BRONZE_DART), IRON(IRON_DART),
+ STEEL(STEEL_DART), MITHRIL(MITHRIL_DART),
+ ADAMANT(ADAMANT_DART), RUNE(RUNE_DART),
+ DRAGON(DRAGON_DART);
+
+ @Getter
+ private int dartID;
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/ItemType.java b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/ItemType.java
new file mode 100644
index 0000000000..8479937589
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/ItemType.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2018, Davis Cook
+ * Copyright (c) 2018, Daddy Dozer
+ * 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.suppliestracker;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * The potential types that supplies can be along with a categorization function
+ * that assigns the supplies to these categories
+ */
+@AllArgsConstructor
+public enum ItemType
+{
+ FOOD("Food"),
+ POTION("Potions"),
+ RUNE("Runes"),
+ AMMO("Ammo"),
+ TELEPORT("Teleports");
+
+ @Getter
+ private String label;
+
+ /**
+ * Takes an item and determines what ItemType it should categorize into
+ * @param item the item to determine category for
+ * @return our best guess for what category this item goes into
+ * note that if the guess is wrong (per say) it won't break anything because it will be
+ * consistently wrong but it could have an item that is clearly not food in the food section
+ */
+ public static ItemType categorize(SuppliesTrackerItem item)
+ {
+ if (item.getName().contains("(4)"))
+ {
+ return ItemType.POTION;
+ }
+ if (item.getName().toLowerCase().contains("bolt") || item.getName().toLowerCase().contains("dart")
+ || item.getName().toLowerCase().contains("arrow") || item.getName().toLowerCase().contains("javelin")
+ || item.getName().toLowerCase().contains("knive") || item.getName().toLowerCase().contains("throwing")
+ || item.getName().toLowerCase().contains("zulrah's scale") || item.getName().toLowerCase().contains("cannonball"))
+ {
+ return ItemType.AMMO;
+ }
+ if (item.getName().contains("rune"))
+ {
+ return ItemType.RUNE;
+ }
+ if (item.getName().toLowerCase().contains("teleport"))
+ {
+ return ItemType.TELEPORT;
+ }
+ return ItemType.FOOD;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/MenuAction.java b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/MenuAction.java
new file mode 100644
index 0000000000..42a942e74b
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/MenuAction.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018, Davis Cook
+ * 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.suppliestracker;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import net.runelite.api.Item;
+
+/**
+ * Data class that tracks all info related to a menu click action
+ */
+@AllArgsConstructor
+public class MenuAction
+{
+
+ @Getter
+ private ActionType type;
+ @Getter
+ private Item[] oldInventory;
+
+ public static class ItemAction extends MenuAction
+ {
+
+ @Getter
+ private int itemID;
+ @Getter
+ private int slot;
+
+ public ItemAction(ActionType type, Item[] oldInventory, int itemID, int slot)
+ {
+ super(type, oldInventory);
+ this.itemID = itemID;
+ this.slot = slot;
+ }
+
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesBox.java
new file mode 100644
index 0000000000..76dc060a9f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesBox.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (c) 2018, Davis Cook
+ * Copyright (c) 2018, Daddy Dozer
+ * 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.suppliestracker;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import net.runelite.client.game.AsyncBufferedImage;
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.ui.ColorScheme;
+import net.runelite.client.ui.FontManager;
+import net.runelite.client.util.StackFormatter;
+import net.runelite.client.util.Text;
+import net.runelite.http.api.item.ItemPrice;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+
+import static net.runelite.api.ItemID.*;
+import static net.runelite.api.ItemID.HALF_A_MEAT_PIE;
+
+public class SuppliesBox extends JPanel
+{
+ private static final int ITEMS_PER_ROW = 5;
+
+ private final JPanel itemContainer = new JPanel();
+ private final JLabel priceLabel = new JLabel();
+ private final JLabel subTitleLabel = new JLabel();
+ private final ItemManager itemManager;
+ @Getter(AccessLevel.PACKAGE)
+ private final String id;
+ private final SuppliesTrackerPlugin plugin;
+ private final SuppliesTrackerPanel panel;
+
+ @Getter
+ private final List trackedItems = new ArrayList<>();
+
+ private long totalPrice;
+
+ @Getter
+ private final ItemType type;
+
+ SuppliesBox(final ItemManager itemManager, final String id,
+ final SuppliesTrackerPlugin plugin, final SuppliesTrackerPanel panel,
+ final ItemType type)
+ {
+ this.id = id;
+ this.itemManager = itemManager;
+ this.plugin = plugin;
+ this.panel = panel;
+ this.type = type;
+
+ setLayout(new BorderLayout(0, 1));
+ setBorder(new EmptyBorder(5, 0, 0, 0));
+
+ final JPanel logTitle = new JPanel(new BorderLayout(5, 0));
+ logTitle.setBorder(new EmptyBorder(7, 7, 7, 7));
+ logTitle.setBackground(ColorScheme.DARKER_GRAY_COLOR.darker());
+
+ final JLabel titleLabel = new JLabel(Text.removeTags(id));
+ titleLabel.setFont(FontManager.getRunescapeSmallFont());
+ titleLabel.setForeground(Color.WHITE);
+
+ logTitle.add(titleLabel, BorderLayout.WEST);
+
+ subTitleLabel.setFont(FontManager.getRunescapeSmallFont());
+ subTitleLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
+ logTitle.add(subTitleLabel, BorderLayout.CENTER);
+
+ priceLabel.setFont(FontManager.getRunescapeSmallFont());
+ priceLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
+ logTitle.add(priceLabel, BorderLayout.EAST);
+
+ add(logTitle, BorderLayout.NORTH);
+ add(itemContainer, BorderLayout.CENTER);
+
+ // Create popup menu
+ final JPopupMenu popupMenu = new JPopupMenu();
+ popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5));
+ setComponentPopupMenu(popupMenu);
+
+ // Create reset menu
+ final JMenuItem reset = new JMenuItem("Reset Category");
+ reset.addActionListener(e ->
+ {
+ for (SuppliesTrackerItem item : trackedItems)
+ {
+ plugin.clearItem(item.getId());
+ }
+ clearAll();
+ rebuild();
+ panel.updateOverall();
+ });
+
+ popupMenu.add(reset);
+
+ setVisible(false);
+ }
+
+ void update(SuppliesTrackerItem item)
+ {
+ trackedItems.removeIf(r -> r.getId() == item.getId());
+ trackedItems.add(item);
+ setVisible(trackedItems.size() > 0);
+ }
+
+ void remove(SuppliesTrackerItem item)
+ {
+ trackedItems.removeIf(r -> r.getId() == item.getId());
+ plugin.clearItem(item.getId());
+ setVisible(trackedItems.size() > 0);
+ }
+
+ void clearAll()
+ {
+ trackedItems.clear();
+ setVisible(false);
+ }
+
+ public long getTotalSupplies()
+ {
+ long totalSupplies = 0;
+ for (SuppliesTrackerItem item : trackedItems)
+ {
+ totalSupplies += item.getQuantity();
+ }
+ return totalSupplies;
+ }
+
+ public long getTotalPrice()
+ {
+ return totalPrice;
+ }
+
+ void rebuild()
+ {
+ buildItems();
+
+ priceLabel.setText(StackFormatter.quantityToStackSize(totalPrice) + " gp");
+ priceLabel.setToolTipText(StackFormatter.formatNumber(totalPrice) + " gp");
+
+ final long supplies = getTotalSupplies();
+ if (supplies > 0)
+ {
+ subTitleLabel.setText("x " + supplies);
+ }
+ else
+ {
+ subTitleLabel.setText("");
+ }
+
+ validate();
+ repaint();
+ }
+
+ private void buildItems()
+ {
+ final List items = new ArrayList<>(trackedItems);
+ totalPrice = 0;
+
+ for (SuppliesTrackerItem item : items)
+ {
+ totalPrice += item.getPrice();
+ }
+
+ items.sort((i1, i2) -> Long.compare(i2.getPrice(), i1.getPrice()));
+
+ // calculates how many rows need to be displayed to fit all item
+ final int rowSize = ((items.size() % ITEMS_PER_ROW == 0) ? 0 : 1) + items.size() / ITEMS_PER_ROW;
+
+ itemContainer.removeAll();
+ itemContainer.setLayout(new GridLayout(rowSize, ITEMS_PER_ROW, 1, 1));
+
+ for (int i = 0; i < rowSize * ITEMS_PER_ROW; i++)
+ {
+ final JPanel slotContainer = new JPanel();
+ slotContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+
+ if (i < items.size())
+ {
+ final SuppliesTrackerItem item = items.get(i);
+ final JLabel imageLabel = new JLabel();
+ imageLabel.setToolTipText(buildToolTip(item));
+ imageLabel.setVerticalAlignment(SwingConstants.CENTER);
+ imageLabel.setHorizontalAlignment(SwingConstants.CENTER);
+
+ AsyncBufferedImage itemImage = itemManager.getImage(getModifiedItemId(item.getName(), item.getId()), item.getQuantity(), item.getQuantity() > 1);
+ itemImage.addTo(imageLabel);
+ slotContainer.add(imageLabel);
+
+ // create popup menu
+ final JPopupMenu popupMenu = new JPopupMenu();
+ popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5));
+ slotContainer.setComponentPopupMenu(popupMenu);
+
+ final JMenuItem reset = new JMenuItem("Reset");
+ reset.addActionListener(e ->
+ {
+ remove(item);
+ rebuild();
+ panel.updateOverall();
+ });
+
+ popupMenu.add(reset);
+ }
+ itemContainer.add(slotContainer);
+ }
+ itemContainer.repaint();
+ }
+
+ private int getModifiedItemId(String name, int itemId)
+ {
+ if (SuppliesTrackerPlugin.isPotion(name))
+ {
+ return getSingleDose(name);
+ }
+ if (SuppliesTrackerPlugin.isCake(name, itemId))
+ {
+ return getSlice(itemId);
+ }
+ if (SuppliesTrackerPlugin.isPizzaPie(name))
+ {
+ return getHalf(itemId);
+ }
+
+ return itemId;
+ }
+
+ //Switches full cake ids to get the image for slice
+ private int getSlice(int itemId)
+ {
+ switch (itemId)
+ {
+ case CAKE:
+ itemId = SLICE_OF_CAKE;
+ break;
+ case CHOCOLATE_CAKE:
+ itemId = CHOCOLATE_SLICE;
+ break;
+ }
+ return itemId;
+ }
+
+ //Switches full pizza and pie ids to get the image for half
+ private int getHalf(int itemId)
+ {
+ switch (itemId)
+ {
+ case ANCHOVY_PIZZA:
+ itemId = _12_ANCHOVY_PIZZA;
+ break;
+ case MEAT_PIZZA:
+ itemId = _12_MEAT_PIZZA;
+ break;
+ case PINEAPPLE_PIZZA:
+ itemId = _12_PINEAPPLE_PIZZA;
+ break;
+ case PLAIN_PIZZA:
+ itemId = _12_PLAIN_PIZZA;
+ break;
+ case REDBERRY_PIE:
+ itemId = HALF_A_REDBERRY_PIE;
+ break;
+ case GARDEN_PIE:
+ itemId = HALF_A_GARDEN_PIE;
+ break;
+ case SUMMER_PIE:
+ itemId = HALF_A_SUMMER_PIE;
+ break;
+ case FISH_PIE:
+ itemId = HALF_A_FISH_PIE;
+ break;
+ case BOTANICAL_PIE:
+ itemId = HALF_A_BOTANICAL_PIE;
+ break;
+ case MUSHROOM_PIE:
+ itemId = HALF_A_MUSHROOM_PIE;
+ break;
+ case ADMIRAL_PIE:
+ itemId = HALF_AN_ADMIRAL_PIE;
+ break;
+ case WILD_PIE:
+ itemId = HALF_A_WILD_PIE;
+ break;
+ case APPLE_PIE:
+ itemId = HALF_AN_APPLE_PIE;
+ break;
+ case MEAT_PIE:
+ itemId = HALF_A_MEAT_PIE;
+ break;
+
+ }
+ return itemId;
+ }
+
+ private int getSingleDose(String name)
+ {
+ String nameModified = name.replace("(4)", "(1)");
+ int itemId = 0;
+ List itemList = itemManager.search(nameModified);
+ for (ItemPrice item: itemList)
+ {
+ itemId = item.getId();
+ }
+ return itemId;
+ }
+
+ private static String buildToolTip(SuppliesTrackerItem item)
+ {
+ final String name = item.getName();
+ final int quantity = item.getQuantity();
+ final long price = item.getPrice();
+ return name + " x " + quantity + " (" + StackFormatter.quantityToStackSize(price) + ") ";
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesTrackerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesTrackerConfig.java
new file mode 100644
index 0000000000..f14160db15
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesTrackerConfig.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018, Davis Cook
+ * 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.suppliestracker;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup("suppliestracker")
+public interface SuppliesTrackerConfig extends Config
+{
+ @ConfigItem(
+ keyName = "blowpipeAmmo",
+ name = "Ammo used in your blowpipe",
+ description = "What type of dart are you using in your toxic blowpipe"
+ )
+ default BlowpipeDartType blowpipeAmmo()
+ {
+ return BlowpipeDartType.MITHRIL;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesTrackerItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesTrackerItem.java
new file mode 100644
index 0000000000..270d3b08b1
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesTrackerItem.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018, Daddy Dozer
+ * 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.suppliestracker;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+
+class SuppliesTrackerItem
+{
+ @Getter
+ private int id;
+ @Getter
+ private String name;
+ @Getter
+ private int quantity;
+ @Getter
+ private long price;
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesTrackerPanel.java
new file mode 100644
index 0000000000..6eea00105f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesTrackerPanel.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2018, Psikoi
+ * Copyright (c) 2018, Tomas Slusny
+ * Copyright (c) 2018, Daddy Dozer
+ * 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.suppliestracker;
+
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.ui.ColorScheme;
+import net.runelite.client.ui.FontManager;
+import net.runelite.client.ui.PluginPanel;
+import net.runelite.client.ui.components.PluginErrorPanel;
+import net.runelite.client.util.ColorUtil;
+import net.runelite.client.util.StackFormatter;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.border.EmptyBorder;
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+
+
+class SuppliesTrackerPanel extends PluginPanel
+{
+ private static final String HTML_LABEL_TEMPLATE =
+ "%s%s";
+
+ // Handle loot logs
+ private final JPanel logsContainer = new JPanel();
+
+ private final List boxList = new ArrayList<>();
+
+ private final PluginErrorPanel errorPanel = new PluginErrorPanel();
+
+ private final ScheduledExecutorService executor;
+
+ // Handle overall session data
+ private final JPanel overallPanel = new JPanel();
+ private final JLabel overallSuppliesUsedLabel = new JLabel();
+ private final JLabel overallCostLabel = new JLabel();
+ private final JLabel overallIcon = new JLabel();
+ private final ItemManager itemManager;
+ private final SuppliesTrackerPlugin plugin;
+ private int overallSuppliesUsed;
+ private int overallCost;
+
+ SuppliesTrackerPanel(final ItemManager itemManager, ScheduledExecutorService executor, SuppliesTrackerPlugin plugin)
+ {
+ this.executor = executor;
+ this.itemManager = itemManager;
+ this.plugin = plugin;
+ setBorder(new EmptyBorder(6, 6, 6, 6));
+ setBackground(ColorScheme.DARK_GRAY_COLOR);
+ setLayout(new BorderLayout());
+
+ // Create layout panel for wrapping
+ final JPanel layoutPanel = new JPanel();
+ layoutPanel.setLayout(new BoxLayout(layoutPanel, BoxLayout.Y_AXIS));
+ add(layoutPanel, BorderLayout.NORTH);
+
+ // Create panel that will contain overall data
+ overallPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
+ overallPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+ overallPanel.setLayout(new BorderLayout());
+ overallPanel.setVisible(true);
+
+ // Add icon and contents
+ final JPanel overallInfo = new JPanel();
+ overallInfo.setBackground(ColorScheme.DARKER_GRAY_COLOR);
+ overallInfo.setLayout(new GridLayout(2, 1));
+ overallInfo.setBorder(new EmptyBorder(0, 10, 0, 0));
+ overallSuppliesUsedLabel.setFont(FontManager.getRunescapeSmallFont());
+ overallCostLabel.setFont(FontManager.getRunescapeSmallFont());
+ overallInfo.add(overallSuppliesUsedLabel);
+ overallInfo.add(overallCostLabel);
+ overallPanel.add(overallIcon, BorderLayout.WEST);
+ overallPanel.add(overallInfo, BorderLayout.CENTER);
+
+ for (ItemType type : ItemType.values())
+ {
+ SuppliesBox newBox = new SuppliesBox(itemManager, type.getLabel(), plugin, this, type);
+ logsContainer.add(newBox);
+ boxList.add(newBox);
+ }
+
+ // Create reset all menu
+ final JMenuItem reset = new JMenuItem("Reset All");
+ reset.addActionListener(e ->
+ {
+ overallSuppliesUsed = 0;
+ overallCost = 0;
+ plugin.clearSupplies();
+ for (SuppliesBox box : boxList)
+ {
+ box.clearAll();
+ }
+ updateOverall();
+ logsContainer.repaint();
+ });
+
+ // Create popup menu
+ final JPopupMenu popupMenu = new JPopupMenu();
+ popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5));
+ popupMenu.add(reset);
+ overallPanel.setComponentPopupMenu(popupMenu);
+
+ // Create Supply Rows wrapper
+ logsContainer.setLayout(new BoxLayout(logsContainer, BoxLayout.Y_AXIS));
+ layoutPanel.add(overallPanel);
+ layoutPanel.add(logsContainer);
+
+ errorPanel.setContent("Supply trackers", "You have not used any supplies yet.");
+ add(errorPanel);
+ overallPanel.setVisible(false);
+ }
+
+ /**
+ * loads an img to the icon on the header
+ * @param img the img for the header icon
+ */
+ public void loadHeaderIcon(BufferedImage img)
+ {
+ overallIcon.setIcon(new ImageIcon(img));
+ }
+
+ /**
+ * convert key value pair to html formatting needed to display nicely
+ * @param key key
+ * @param value value
+ * @return key: value in html
+ */
+ private static String htmlLabel(String key, long value)
+ {
+ final String valueStr = StackFormatter.quantityToStackSize(value);
+ return String.format(HTML_LABEL_TEMPLATE, ColorUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR), key, valueStr);
+ }
+
+ /**
+ * Add an item to the supply panel by placing it into the correct box
+ * @param item the item to add
+ */
+ public void addItem(SuppliesTrackerItem item)
+ {
+ ItemType category = ItemType.categorize(item);
+ for (SuppliesBox box : boxList)
+ {
+ if (box.getType() == category)
+ {
+ box.update(item);
+ box.rebuild();
+ break;
+ }
+ }
+ updateOverall();
+ }
+
+ /**
+ * Updates overall stats to calculate overall used and overall cost from
+ * the info in each box
+ */
+ public void updateOverall()
+ {
+ overallSuppliesUsed = 0;
+ for (SuppliesBox box : boxList)
+ {
+ overallSuppliesUsed += box.getTotalSupplies();
+ }
+
+ overallCost = 0;
+ for (SuppliesBox box : boxList)
+ {
+ overallCost += box.getTotalPrice();
+ }
+
+ overallSuppliesUsedLabel.setText(htmlLabel("Total Supplies: ", overallSuppliesUsed));
+ overallCostLabel.setText(htmlLabel("Total Cost: ", overallCost));
+
+ if (overallSuppliesUsed <= 0)
+ {
+ add(errorPanel);
+ overallPanel.setVisible(false);
+ }
+ else
+ {
+ remove(errorPanel);
+ overallPanel.setVisible(true);
+ }
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesTrackerPlugin.java
new file mode 100644
index 0000000000..f7034af63f
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/suppliestracker/SuppliesTrackerPlugin.java
@@ -0,0 +1,781 @@
+/*
+ * Copyright (c) 2018, Psikoi
+ * Copyright (c) 2018, Adam
+ * Copyright (c) 2018, Sir Girion
+ * Copyright (c) 2018, Davis Cook
+ * Copyright (c) 2018, Daddy Dozer
+ * 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.suppliestracker;
+
+
+import com.google.inject.Provides;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.*;
+import net.runelite.api.events.*;
+import net.runelite.client.callback.ClientThread;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.game.SpriteManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.ClientToolbar;
+import net.runelite.client.ui.NavigationButton;
+import net.runelite.client.util.ImageUtil;
+import net.runelite.http.api.item.ItemPrice;
+
+import static net.runelite.api.AnimationID.*;
+import static net.runelite.api.ItemID.*;
+import static net.runelite.client.plugins.suppliestracker.ActionType.CONSUMABLE;
+import static net.runelite.client.plugins.suppliestracker.ActionType.TELEPORT;
+import static net.runelite.client.plugins.suppliestracker.ActionType.CAST;
+
+import java.util.*;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.regex.Pattern;
+import javax.inject.Inject;
+import javax.swing.SwingUtilities;
+import java.awt.image.BufferedImage;
+
+
+@PluginDescriptor(
+ name = "Supplies Used Tracker",
+ description = "Tracks supplies used during the session",
+ tags = {"cost"},
+ enabledByDefault = false
+)
+@Slf4j
+public class SuppliesTrackerPlugin extends Plugin
+{
+
+ private static final String POTION_PATTERN = "[(]\\d[)]";
+
+ private static final String EAT_PATTERN = "^eat";
+ private static final String DRINK_PATTERN = "^drink";
+ private static final String TELEPORT_PATTERN = "^teleport";
+ private static final String TELETAB_PATTERN = "^break";
+ private static final String SPELL_PATTERN = "^cast|^grand\\sexchange|^outside|^seers|^yanille";
+
+ private static final int EQUIPMENT_MAINHAND_SLOT = EquipmentInventorySlot.WEAPON.getSlotIdx();
+ private static final int EQUIPMENT_AMMO_SLOT = EquipmentInventorySlot.AMMO.getSlotIdx();
+ private static final int EQUIPMENT_CAPE_SLOT = EquipmentInventorySlot.CAPE.getSlotIdx();
+
+ private static final double NO_AVAS_PERCENT = 1.0;
+ private static final double ASSEMBLER_PERCENT = 0.20;
+ private static final double ACCUMULATOR_PERCENT = 0.28;
+ private static final double ATTRACTOR_PERCENT = 0.40;
+
+ private static final int BLOWPIPE_TICKS_RAPID_PVM = 2;
+ private static final int BLOWPIPE_TICKS_RAPID_PVP = 3;
+ private static final int BLOWPIPE_TICKS_NORMAL_PVM = 3;
+ private static final int BLOWPIPE_TICKS_NORMAL_PVP = 4;
+
+ private static final double SCALES_PERCENT = 0.66;
+
+ private static final int POTION_DOSES = 4, CAKE_DOSES = 3, PIZZA_PIE_DOSES = 2;
+
+ private static final Random random = new Random();
+
+ private static final int[] THROWING_IDS = new int[]{BRONZE_DART, IRON_DART, STEEL_DART, BLACK_DART, MITHRIL_DART, ADAMANT_DART, RUNE_DART, DRAGON_DART, BRONZE_KNIFE, IRON_KNIFE, STEEL_KNIFE, BLACK_KNIFE, MITHRIL_KNIFE, ADAMANT_KNIFE, RUNE_KNIFE, BRONZE_THROWNAXE, IRON_THROWNAXE, STEEL_THROWNAXE, MITHRIL_THROWNAXE, ADAMANT_THROWNAXE, RUNE_THROWNAXE, DRAGON_KNIFE, DRAGON_KNIFE_22812, DRAGON_KNIFE_22814, DRAGON_KNIFEP_22808, DRAGON_KNIFEP_22810, DRAGON_KNIFEP , DRAGON_THROWNAXE, CHINCHOMPA_10033, RED_CHINCHOMPA_10034, BLACK_CHINCHOMPA};
+ private static final int[] RUNE_IDS = new int[]{AIR_RUNE, WATER_RUNE, EARTH_RUNE, MIND_RUNE, BODY_RUNE, COSMIC_RUNE, CHAOS_RUNE, NATURE_RUNE, LAW_RUNE, DEATH_RUNE, ASTRAL_RUNE, BLOOD_RUNE, SOUL_RUNE, WRATH_RUNE, MIST_RUNE, DUST_RUNE, MUD_RUNE, SMOKE_RUNE, STEAM_RUNE, LAVA_RUNE};
+
+ //Hold Supply Data
+ private static HashMap suppliesEntry = new HashMap<>();
+ private ItemContainer old;
+ private Deque actionStack = new ArrayDeque<>();
+ private int ammoId = 0;
+ private int ammoAmount = 0;
+ private int thrownId = 0;
+ private int thrownAmount = 0;
+ private boolean ammoLoaded = false;
+ private boolean throwingAmmoLoaded = false;
+ private boolean mainHandThrowing = false;
+ private int mainHand = 0;
+ private SuppliesTrackerPanel panel;
+ private NavigationButton navButton;
+ private String[] RAIDS_CONSUMABLES = new String[]{"xeric's", "elder", "twisted", "revitalisation", "overload", "prayer enhance", "pysk", "suphi", "leckish", "brawk", "mycil", "roqed", "kyren", "guanic", "prael", "giral", "phluxia", "kryket", "murng", "psykk"};
+
+ private int attackStyleVarbit = -1;
+ private int ticks = 0;
+ private int ticksInAnimation;
+
+ @Inject
+ private ClientToolbar clientToolbar;
+
+ @Inject
+ private ItemManager itemManager;
+
+ @Inject
+ private SpriteManager spriteManager;
+
+ @Inject
+ private SuppliesTrackerConfig config;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private ScheduledExecutorService executorService;
+
+ @Inject
+ private ClientThread clientThread;
+
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ panel = new SuppliesTrackerPanel(itemManager, executorService, this);
+ final BufferedImage header = ImageUtil.getResourceStreamFromClass(getClass(), "panel_icon.png");
+ panel.loadHeaderIcon(header);
+ final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "panel_icon.png");
+
+ navButton = NavigationButton.builder()
+ .tooltip("Supplies Tracker")
+ .icon(icon)
+ .priority(5)
+ .panel(panel)
+ .build();
+
+ clientToolbar.addNavigation(navButton);
+ }
+
+ @Override
+ protected void shutDown()
+ {
+ clientToolbar.removeNavigation(navButton);
+ }
+
+ @Provides
+ SuppliesTrackerConfig provideConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(SuppliesTrackerConfig.class);
+ }
+
+ @Subscribe
+ public void onGameTick(GameTick tick)
+ {
+ Player player = client.getLocalPlayer();
+ if (player.getAnimation() == BLOWPIPE_ATTACK)
+ {
+ ticks++;
+ }
+ if (ticks == ticksInAnimation && (player.getAnimation() == BLOWPIPE_ATTACK))
+ {
+ double ava_percent = getAccumulatorPercent();
+ double scale_percent = SCALES_PERCENT;
+ // randomize the usage of supplies since we CANNOT actually get real supplies used
+ if (random.nextDouble() <= ava_percent)
+ {
+ buildEntries(config.blowpipeAmmo().getDartID());
+
+ }
+ if (random.nextDouble() <= scale_percent)
+ {
+ buildEntries(ZULRAHS_SCALES);
+ }
+ ticks = 0;
+ }
+ }
+
+ /**
+ * checks the player's cape slot to determine what percent of their darts are lost
+ * - where lost means either break or drop to floor
+ * @return the percent lost
+ */
+ private double getAccumulatorPercent()
+ {
+ double percent = NO_AVAS_PERCENT;
+ ItemContainer equipment = client.getItemContainer(InventoryID.EQUIPMENT);
+ if (equipment.getItems().length > EQUIPMENT_CAPE_SLOT)
+ {
+ int capeID = equipment.getItems()[EQUIPMENT_CAPE_SLOT].getId();
+ switch (capeID)
+ {
+ case AVAS_ASSEMBLER:
+ case ASSEMBLER_MAX_CAPE:
+ percent = ASSEMBLER_PERCENT;
+ break;
+ case AVAS_ACCUMULATOR:
+ case ACCUMULATOR_MAX_CAPE:
+ // TODO: the ranging cape can be used as an attractor so this could be wrong
+ case RANGING_CAPE:
+ percent = ACCUMULATOR_PERCENT;
+ break;
+ case AVAS_ATTRACTOR:
+ percent = ATTRACTOR_PERCENT;
+ break;
+ }
+ }
+ return percent;
+ }
+
+ @Subscribe
+ public void onVarbitChanged(VarbitChanged event)
+ {
+ if (attackStyleVarbit == -1 || attackStyleVarbit != client.getVar(VarPlayer.ATTACK_STYLE))
+ {
+ attackStyleVarbit = client.getVar(VarPlayer.ATTACK_STYLE);
+ if (attackStyleVarbit == 0 || attackStyleVarbit == 3)
+ {
+ ticksInAnimation = BLOWPIPE_TICKS_NORMAL_PVM;
+ if (client.getLocalPlayer() != null &&
+ client.getLocalPlayer().getInteracting() instanceof Player) {
+ ticksInAnimation = BLOWPIPE_TICKS_NORMAL_PVP;
+ }
+ }
+ else if (attackStyleVarbit == 1)
+ {
+ ticksInAnimation = BLOWPIPE_TICKS_RAPID_PVM;
+ if (client.getLocalPlayer() != null &&
+ client.getLocalPlayer().getInteracting() instanceof Player) {
+ ticksInAnimation = BLOWPIPE_TICKS_RAPID_PVP;
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks for changes between the provided inventories in runes specifically to add those runes
+ * to the supply tracker
+ *
+ * we can't in general just check for when inventory slots change but this method is only run
+ * immediately after the player performs a cast animation or cast menu click/entry
+ * @param itemContainer the new inventory
+ * @param oldInv the old inventory
+ */
+ private void checkUsedRunes(ItemContainer itemContainer, Item[] oldInv)
+ {
+ for (int i = 0; i < itemContainer.getItems().length; i++)
+ {
+ Item newItem = itemContainer.getItems()[i];
+ Item oldItem = oldInv[i];
+ boolean isRune = false;
+ for (int j = 0; j < RUNE_IDS.length; j++)
+ {
+ if (oldItem.getId() == RUNE_IDS[j])
+ {
+ isRune = true;
+ }
+ }
+ if (isRune && (newItem.getId() != oldItem.getId() || newItem.getQuantity() != oldItem.getQuantity()))
+ {
+ int quantity = oldItem.getQuantity();
+ if (newItem.getId() == oldItem.getId())
+ {
+ quantity -= newItem.getQuantity();
+ }
+ buildEntries(oldItem.getId(), quantity);
+ }
+ }
+ }
+
+ @Subscribe
+ public void onCannonballFired(CannonballFired cannonballFired)
+ {
+ buildEntries(CANNONBALL);
+ }
+
+ @Subscribe
+ public void onAnimationChanged(AnimationChanged animationChanged)
+ {
+ if (animationChanged.getActor() == client.getLocalPlayer())
+ {
+ if (animationChanged.getActor().getAnimation() == HIGH_LEVEL_MAGIC_ATTACK)
+ {
+ //Trident of the seas
+ if (mainHand == TRIDENT_OF_THE_SEAS || mainHand == TRIDENT_OF_THE_SEAS_E || mainHand == TRIDENT_OF_THE_SEAS_FULL )
+ {
+ buildEntries(CHAOS_RUNE);
+ buildEntries(DEATH_RUNE);
+ buildEntries(FIRE_RUNE, 5);
+ buildEntries(COINS_995, 10);
+ }
+ //Trident of the swamp
+ else if (mainHand == TRIDENT_OF_THE_SWAMP_E || mainHand == TRIDENT_OF_THE_SWAMP || mainHand == UNCHARGED_TOXIC_TRIDENT_E || mainHand == UNCHARGED_TOXIC_TRIDENT)
+ {
+ buildEntries(CHAOS_RUNE);
+ buildEntries(DEATH_RUNE);
+ buildEntries(FIRE_RUNE, 5);
+ buildEntries(ZULRAHS_SCALES);
+ }
+ //Sang Staff
+ else if (mainHand == SANGUINESTI_STAFF || mainHand == SANGUINESTI_STAFF_UNCHARGED)
+ {
+ buildEntries(BLOOD_RUNE, 3);
+ }
+ else
+ {
+ old = client.getItemContainer(InventoryID.INVENTORY);
+
+ if (old.getItems() != null && !actionStack.stream().anyMatch(a ->
+ a.getType() == CAST))
+ {
+ MenuAction newAction = new MenuAction(CAST, old.getItems());
+ actionStack.push(newAction);
+ }
+ }
+ }
+ else if (animationChanged.getActor().getAnimation() == LOW_LEVEL_MAGIC_ATTACK)
+ {
+ old = client.getItemContainer(InventoryID.INVENTORY);
+
+ if (old.getItems() != null && !actionStack.stream().anyMatch(a ->
+ a.getType() == CAST))
+ {
+ MenuAction newAction = new MenuAction(CAST, old.getItems());
+ actionStack.push(newAction);
+ }
+ }
+ }
+ }
+
+ @Subscribe
+ public void onItemContainerChanged(ItemContainerChanged itemContainerChanged)
+ {
+ ItemContainer itemContainer = itemContainerChanged.getItemContainer();
+
+ for (MenuAction action : actionStack)
+ {
+ System.out.println(action.getType());
+ }
+
+ if (itemContainer == client.getItemContainer(InventoryID.INVENTORY) && old != null && !actionStack.isEmpty())
+ {
+ while (!actionStack.isEmpty())
+ {
+ MenuAction frame = actionStack.pop();
+ ActionType type = frame.getType();
+ MenuAction.ItemAction itemFrame;
+ Item[] oldInv = frame.getOldInventory();
+ switch (type)
+ {
+ case CONSUMABLE:
+ itemFrame = (MenuAction.ItemAction) frame;
+ int nextItem = itemFrame.getItemID();
+ int nextSlot = itemFrame.getSlot();
+ if (itemContainer.getItems()[nextSlot].getId() != oldInv[nextSlot].getId())
+ {
+ buildEntries(nextItem);
+ }
+ break;
+ case TELEPORT:
+ itemFrame = (MenuAction.ItemAction) frame;
+ int teleid = itemFrame.getItemID();
+ int slot = itemFrame.getSlot();
+ if (itemContainer.getItems()[slot].getId() != oldInv[slot].getId() || itemContainer.getItems()[slot].getQuantity() != oldInv[slot].getQuantity())
+ {
+ buildEntries(teleid);
+ }
+ break;
+ case CAST:
+ checkUsedRunes(itemContainer, oldInv);
+ break;
+ }
+ }
+ }
+
+ if (itemContainer == client.getItemContainer(InventoryID.EQUIPMENT))
+ {
+ //set mainhand for trident tracking
+ if (itemContainer.getItems().length > EQUIPMENT_MAINHAND_SLOT)
+ {
+ mainHand = itemContainer.getItems()[EQUIPMENT_MAINHAND_SLOT].getId();
+ net.runelite.api.Item mainHandItem = itemContainer.getItems()[EQUIPMENT_MAINHAND_SLOT];
+ for (int throwingIDs: THROWING_IDS)
+ {
+ if (mainHand == throwingIDs)
+ {
+ mainHandThrowing = true;
+ break;
+ }
+ else
+ {
+ mainHandThrowing = false;
+ }
+ }
+ if (mainHandThrowing)
+ {
+ if (throwingAmmoLoaded)
+ {
+ if (thrownId == mainHandItem.getId())
+ {
+ if (thrownAmount - 1 == mainHandItem.getQuantity())
+ {
+ buildEntries(mainHandItem.getId());
+ thrownAmount = mainHandItem.getQuantity();
+ }
+ else
+ {
+ thrownAmount = mainHandItem.getQuantity();
+ }
+ }
+ else
+ {
+ thrownId = mainHandItem.getId();
+ thrownAmount = mainHandItem.getQuantity();
+ }
+ }
+ else
+ {
+ thrownId = mainHandItem.getId();
+ thrownAmount = mainHandItem.getQuantity();
+ throwingAmmoLoaded = true;
+ }
+ }
+ }
+ //Ammo tracking
+ if (itemContainer.getItems().length > EQUIPMENT_AMMO_SLOT)
+ {
+ net.runelite.api.Item ammoSlot = itemContainer.getItems()[EQUIPMENT_AMMO_SLOT];
+ if (ammoSlot != null)
+ {
+ if (ammoLoaded)
+ {
+ if (ammoId == ammoSlot.getId())
+ {
+ if (ammoAmount - 1 == ammoSlot.getQuantity())
+ {
+ buildEntries(ammoSlot.getId());
+ ammoAmount = ammoSlot.getQuantity();
+ }
+ else
+ {
+ ammoAmount = ammoSlot.getQuantity();
+ }
+ }
+ else
+ {
+ ammoId = ammoSlot.getId();
+ ammoAmount = ammoSlot.getQuantity();
+ }
+ }
+ else
+ {
+ ammoId = ammoSlot.getId();
+ ammoAmount = ammoSlot.getQuantity();
+ ammoLoaded = true;
+ }
+ }
+ }
+
+ }
+ }
+
+ @Subscribe
+ public void onMenuOptionClicked(final MenuOptionClicked event)
+ {
+ System.out.println(event.getMenuAction().getId());
+ System.out.println(event.getActionParam());
+ System.out.println(event.getMenuOption());
+ System.out.println(event.getMenuTarget());
+
+ // Uses stacks to push/pop for tick eating
+ // Create pattern to find eat/drink at beginning
+ Pattern eatPattern = Pattern.compile(EAT_PATTERN);
+ Pattern drinkPattern = Pattern.compile(DRINK_PATTERN);
+ if (eatPattern.matcher(event.getMenuTarget().toLowerCase()).find() || drinkPattern.matcher(event.getMenuTarget().toLowerCase()).find())
+ {
+ if (!actionStack.stream().anyMatch(a ->
+ {
+ if (a instanceof MenuAction.ItemAction)
+ {
+ MenuAction.ItemAction i = (MenuAction.ItemAction) a;
+ return i.getItemID() == event.getId();
+ }
+ return false;
+ }))
+ {
+ old = client.getItemContainer(InventoryID.INVENTORY);
+ int slot = event.getActionParam();
+ if (old.getItems() != null)
+ {
+ int pushItem = old.getItems()[event.getActionParam()].getId();
+ MenuAction newAction = new MenuAction.ItemAction(CONSUMABLE, old.getItems(), pushItem, slot);
+ actionStack.push(newAction);
+ }
+ }
+ }
+
+ // Create pattern for teleport scrolls and tabs
+ Pattern teleportPattern = Pattern.compile(TELEPORT_PATTERN);
+ Pattern teletabPattern = Pattern.compile(TELETAB_PATTERN);
+ if (teleportPattern.matcher(event.getMenuTarget().toLowerCase()).find() ||
+ teletabPattern.matcher(event.getMenuTarget().toLowerCase()).find())
+ {
+ old = client.getItemContainer(InventoryID.INVENTORY);
+
+ // Makes stack only contains one teleport type to stop from adding multiple of one teleport
+ if (old.getItems() != null && !actionStack.stream().anyMatch(a ->
+ a.getType() == TELEPORT))
+ {
+ int teleid = event.getId();
+ MenuAction newAction = new MenuAction.ItemAction(TELEPORT, old.getItems(), teleid, event.getActionParam());
+ actionStack.push(newAction);
+ }
+ }
+
+ // Create pattern for spell cast
+ Pattern spellPattern = Pattern.compile(SPELL_PATTERN);
+ // note that here we look at the menuOption not menuTarget b/c the option for all spells is cast
+ // but the target differs based on each spell name
+ if (spellPattern.matcher(event.getMenuOption().toLowerCase()).find())
+ {
+ old = client.getItemContainer(InventoryID.INVENTORY);
+
+ if (old.getItems() != null && !actionStack.stream().anyMatch(a ->
+ a.getType() == CAST))
+ {
+ MenuAction newAction = new MenuAction(CAST, old.getItems());
+ actionStack.push(newAction);
+ }
+ }
+ }
+
+ /**
+ * Checks if item name is potion
+ * @param name the name of the item
+ * @return if the item is a potion - i.e. has a (1) (2) (3) or (4) in the name
+ */
+ static boolean isPotion(String name)
+ {
+ return name.contains("(4)") || name.contains("(3)") || name.contains("(2)") || name.contains("(1)");
+ }
+
+ /**
+ * Checks if item name is pizza or pie
+ * @param name the name of the item
+ * @return if the item is a pizza or a pie - i.e. has pizza or pie in the name
+ */
+ static boolean isPizzaPie(String name)
+ {
+ return name.toLowerCase().contains("pizza") || name.toLowerCase().contains(" pie");
+ }
+
+ static boolean isCake(String name, int itemId)
+ {
+ return name.toLowerCase().contains("cake") || itemId == ItemID.CHOCOLATE_SLICE;
+ }
+
+ /**
+ * correct prices for potions, pizzas pies, and cakes
+ * tracker tracks each dose of a potion/pizza/pie/cake as an entire one
+ * so must divide price by total amount of doses in each
+ * this is necessary b/c the most correct/accurate price for these resources is the
+ * full price not the 1-dose price
+ * @param name the item name
+ * @param itemId the item id
+ * @param price the current calculated price
+ * @return the price modified by the number of doses
+ */
+ private long scalePriceByDoses(String name, int itemId, long price)
+ {
+ if (isPotion(name))
+ {
+ return price / POTION_DOSES;
+ }
+ if (isPizzaPie(name))
+ {
+ return price / PIZZA_PIE_DOSES;
+ }
+ if (isCake(name, itemId))
+ {
+ return price / CAKE_DOSES;
+ }
+ return price;
+ }
+
+ /**
+ * Add an item to the supply tracker (with 1 count for that item)
+ * @param itemId the id of the item
+ */
+ void buildEntries(int itemId)
+ {
+ buildEntries(itemId, 1);
+ }
+
+ /**
+ * Add an item to the supply tracker
+ * @param itemId the id of the item
+ * @param count the amount of the item to add to the tracker
+ */
+ void buildEntries(int itemId, int count)
+ {
+ final ItemComposition itemComposition = itemManager.getItemComposition(itemId);
+ String name = itemComposition.getName();
+ long calculatedPrice;
+
+ for (String raidsConsumables: RAIDS_CONSUMABLES)
+ {
+ if (name.toLowerCase().contains(raidsConsumables)) return;
+ }
+
+ // convert potions, pizzas/pies, and cakes to their full equivalents
+ // e.g. a half pizza becomes full pizza, 3 dose potion becomes 4, etc...
+ if (isPotion(name))
+ {
+ name = name.replaceAll(POTION_PATTERN, "(4)");
+ itemId = getPotionID(name);
+ }
+ if (isPizzaPie(name))
+ {
+ itemId = getFullVersionItemID(itemId);
+ name = itemManager.getItemComposition(itemId).getName();
+ }
+ if (isCake(name, itemId))
+ {
+ itemId = getFullVersionItemID(itemId);
+ name = itemManager.getItemComposition(itemId).getName();
+ }
+
+ int newQuantity;
+ if (suppliesEntry.containsKey(itemId))
+ {
+ newQuantity = suppliesEntry.get(itemId).getQuantity() + count;
+ }
+ else
+ {
+ newQuantity = count;
+ }
+
+ // calculate price for amount of doses used
+ calculatedPrice = ((long) itemManager.getItemPrice(itemId)) * ((long) newQuantity);
+ calculatedPrice = scalePriceByDoses(name, itemId, calculatedPrice);
+
+ // write the new quantity and calculated price for this entry
+ SuppliesTrackerItem newEntry = new SuppliesTrackerItem(
+ itemId,
+ name,
+ newQuantity,
+ calculatedPrice);
+
+ suppliesEntry.put(itemId, newEntry);
+ SwingUtilities.invokeLater(() ->
+ {
+ panel.addItem(newEntry);
+ });
+ }
+
+ /**
+ * reset all item stacks
+ */
+ public void clearSupplies()
+ {
+ suppliesEntry.clear();
+ }
+
+ /**
+ * reset an individual item stack
+ * @param itemId the id of the item stack
+ */
+ public void clearItem(int itemId)
+ {
+ suppliesEntry.remove(itemId);
+ }
+
+ /**
+ * Gets the item id that matches the provided name within the itemManager
+ * @param name the given name
+ * @return the item id for this name
+ */
+ private int getPotionID(String name)
+ {
+ int itemId = 0;
+
+ List items = itemManager.search(name);
+ for (ItemPrice item: items)
+ {
+ if (item.getName().contains(name))
+ {
+ itemId = item.getId();
+ }
+ }
+ return itemId;
+ }
+
+ /**
+ * Takes the item id of a partial item (e.g. 1 dose potion, 1/2 a pizza, etc...) and returns
+ * the corresponding full item
+ * @param itemId the partial item id
+ * @return the full item id
+ */
+ private int getFullVersionItemID(int itemId)
+ {
+ switch (itemId)
+ {
+ case _12_ANCHOVY_PIZZA:
+ itemId = ANCHOVY_PIZZA;
+ break;
+ case _12_MEAT_PIZZA:
+ itemId = MEAT_PIZZA;
+ break;
+ case _12_PINEAPPLE_PIZZA:
+ itemId = PINEAPPLE_PIZZA;
+ break;
+ case _12_PLAIN_PIZZA:
+ itemId = PLAIN_PIZZA;
+ break;
+ case HALF_A_REDBERRY_PIE:
+ itemId = REDBERRY_PIE;
+ break;
+ case HALF_A_GARDEN_PIE:
+ itemId = GARDEN_PIE;
+ break;
+ case HALF_A_SUMMER_PIE:
+ itemId = SUMMER_PIE;
+ break;
+ case HALF_A_FISH_PIE:
+ itemId = FISH_PIE;
+ break;
+ case HALF_A_BOTANICAL_PIE:
+ itemId = BOTANICAL_PIE;
+ break;
+ case HALF_A_MUSHROOM_PIE:
+ itemId = MUSHROOM_PIE;
+ break;
+ case HALF_AN_ADMIRAL_PIE:
+ itemId = ADMIRAL_PIE;
+ break;
+ case HALF_A_WILD_PIE:
+ itemId = WILD_PIE;
+ break;
+ case HALF_AN_APPLE_PIE:
+ itemId = APPLE_PIE;
+ break;
+ case HALF_A_MEAT_PIE:
+ itemId = MEAT_PIE;
+ break;
+ // note behavior of case means both below cases return CAKE
+ case _23_CAKE:
+ case SLICE_OF_CAKE:
+ itemId = CAKE;
+ break;
+ case _23_CHOCOLATE_CAKE:
+ case CHOCOLATE_SLICE:
+ itemId = CHOCOLATE_CAKE;
+ break;
+ }
+ return itemId;
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/templetrek/TempleTrekBogOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/templetrek/TempleTrekBogOverlay.java
new file mode 100644
index 0000000000..119a70f245
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/templetrek/TempleTrekBogOverlay.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018, Frosty Fridge