From 8a4d6172b2e15b78b9321d79bc691577f97fa087 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 8 Jul 2019 17:12:06 -0400 Subject: [PATCH 01/10] keyremapping: fix escape from chat triggering game keybindings Co-authored-by: Chet Powers --- .../client/plugins/keyremapping/KeyRemappingListener.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java index 56cf3bbedc..a6ad6e8638 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java @@ -190,8 +190,12 @@ class KeyRemappingListener extends MouseAdapter implements KeyListener { switch (e.getKeyCode()) { - case KeyEvent.VK_ENTER: case KeyEvent.VK_ESCAPE: + // When existing typing mode, block the escape key + // so that it doesn't trigger the in-game hotkeys + e.consume(); + // FALLTHROUGH + case KeyEvent.VK_ENTER: plugin.setTyping(false); clientThread.invoke(plugin::lockChat); break; From 3125b74f4fb4cbfb05e90d78a61c9ad216b1b08e Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 8 Jul 2019 18:12:57 -0400 Subject: [PATCH 02/10] keyremapping: allow custom keybinds for F keys Co-authored-by: Chet Powers --- .../keyremapping/KeyRemappingConfig.java | 134 +++++++++++++++++- .../keyremapping/KeyRemappingListener.java | 62 ++++---- 2 files changed, 157 insertions(+), 39 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingConfig.java index 4774bc67e7..9702f6c40c 100755 --- a/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingConfig.java @@ -92,10 +92,142 @@ public interface KeyRemappingConfig extends Config position = 6, keyName = "fkeyRemap", name = "Remap F Keys", - description = "Configures whether F-Keys are Remapped to 1 (F1) through 0 (F10), '-' (F11), and '=' (F12)" + description = "Configures whether F-Keys use remapped keys" ) default boolean fkeyRemap() { return false; } + + @ConfigItem( + position = 7, + keyName = "f1", + name = "F1", + description = "The key which will replace {F1}." + ) + default ModifierlessKeybind f1() + { + return new ModifierlessKeybind(KeyEvent.VK_1, 0); + } + + @ConfigItem( + position = 8, + keyName = "f2", + name = "F2", + description = "The key which will replace {F2}." + ) + default ModifierlessKeybind f2() + { + return new ModifierlessKeybind(KeyEvent.VK_2, 0); + } + + @ConfigItem( + position = 9, + keyName = "f3", + name = "F3", + description = "The key which will replace {F3}." + ) + default ModifierlessKeybind f3() + { + return new ModifierlessKeybind(KeyEvent.VK_3, 0); + } + + @ConfigItem( + position = 10, + keyName = "f4", + name = "F4", + description = "The key which will replace {F4}." + ) + default ModifierlessKeybind f4() + { + return new ModifierlessKeybind(KeyEvent.VK_4, 0); + } + + @ConfigItem( + position = 11, + keyName = "f5", + name = "F5", + description = "The key which will replace {F5}." + ) + default ModifierlessKeybind f5() + { + return new ModifierlessKeybind(KeyEvent.VK_5, 0); + } + + @ConfigItem( + position = 12, + keyName = "f6", + name = "F6", + description = "The key which will replace {F6}." + ) + default ModifierlessKeybind f6() + { + return new ModifierlessKeybind(KeyEvent.VK_6, 0); + } + + @ConfigItem( + position = 13, + keyName = "f7", + name = "F7", + description = "The key which will replace {F7}." + ) + default ModifierlessKeybind f7() + { + return new ModifierlessKeybind(KeyEvent.VK_7, 0); + } + + @ConfigItem( + position = 14, + keyName = "f8", + name = "F8", + description = "The key which will replace {F8}." + ) + default ModifierlessKeybind f8() + { + return new ModifierlessKeybind(KeyEvent.VK_8, 0); + } + + @ConfigItem( + position = 15, + keyName = "f9", + name = "F9", + description = "The key which will replace {F9}." + ) + default ModifierlessKeybind f9() + { + return new ModifierlessKeybind(KeyEvent.VK_9, 0); + } + + @ConfigItem( + position = 16, + keyName = "f10", + name = "F10", + description = "The key which will replace {F10}." + ) + default ModifierlessKeybind f10() + { + return new ModifierlessKeybind(KeyEvent.VK_0, 0); + } + + @ConfigItem( + position = 17, + keyName = "f11", + name = "F11", + description = "The key which will replace {F11}." + ) + default ModifierlessKeybind f11() + { + return new ModifierlessKeybind(KeyEvent.VK_MINUS, 0); + } + + @ConfigItem( + position = 18, + keyName = "f12", + name = "F12", + description = "The key which will replace {F12}." + ) + default ModifierlessKeybind f12() + { + return new ModifierlessKeybind(KeyEvent.VK_EQUALS, 0); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java index a6ad6e8638..e3257f8e73 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java @@ -34,25 +34,11 @@ import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.VarClientStr; import net.runelite.client.callback.ClientThread; -import net.runelite.client.config.Keybind; -import net.runelite.client.config.ModifierlessKeybind; import net.runelite.client.input.KeyListener; import net.runelite.client.input.MouseAdapter; class KeyRemappingListener extends MouseAdapter implements KeyListener { - private static final Keybind ONE = new ModifierlessKeybind(KeyEvent.VK_1, 0); - private static final Keybind TWO = new ModifierlessKeybind(KeyEvent.VK_2, 0); - private static final Keybind THREE = new ModifierlessKeybind(KeyEvent.VK_3, 0); - private static final Keybind FOUR = new ModifierlessKeybind(KeyEvent.VK_4, 0); - private static final Keybind FIVE = new ModifierlessKeybind(KeyEvent.VK_5, 0); - private static final Keybind SIX = new ModifierlessKeybind(KeyEvent.VK_6, 0); - private static final Keybind SEVEN = new ModifierlessKeybind(KeyEvent.VK_7, 0); - private static final Keybind EIGHT = new ModifierlessKeybind(KeyEvent.VK_8, 0); - private static final Keybind NINE = new ModifierlessKeybind(KeyEvent.VK_9, 0); - private static final Keybind ZERO = new ModifierlessKeybind(KeyEvent.VK_0, 0); - private static final Keybind MINUS = new ModifierlessKeybind(KeyEvent.VK_MINUS, 0); - private static final Keybind EQUALS = new ModifierlessKeybind(KeyEvent.VK_EQUALS, 0); @Inject private KeyRemappingPlugin plugin; @@ -112,62 +98,62 @@ class KeyRemappingListener extends MouseAdapter implements KeyListener // to select options if (config.fkeyRemap() && !plugin.isDialogOpen()) { - if (ONE.matches(e)) + if (config.f1().matches(e)) { modified.put(e.getKeyCode(), KeyEvent.VK_F1); e.setKeyCode(KeyEvent.VK_F1); } - else if (TWO.matches(e)) + else if (config.f2().matches(e)) { modified.put(e.getKeyCode(), KeyEvent.VK_F2); e.setKeyCode(KeyEvent.VK_F2); } - else if (THREE.matches(e)) + else if (config.f3().matches(e)) { modified.put(e.getKeyCode(), KeyEvent.VK_F3); e.setKeyCode(KeyEvent.VK_F3); } - else if (FOUR.matches(e)) + else if (config.f4().matches(e)) { modified.put(e.getKeyCode(), KeyEvent.VK_F4); e.setKeyCode(KeyEvent.VK_F4); } - else if (FIVE.matches(e)) + else if (config.f5().matches(e)) { modified.put(e.getKeyCode(), KeyEvent.VK_F5); e.setKeyCode(KeyEvent.VK_F5); } - else if (SIX.matches(e)) + else if (config.f6().matches(e)) { modified.put(e.getKeyCode(), KeyEvent.VK_F6); e.setKeyCode(KeyEvent.VK_F6); } - else if (SEVEN.matches(e)) + else if (config.f7().matches(e)) { modified.put(e.getKeyCode(), KeyEvent.VK_F7); e.setKeyCode(KeyEvent.VK_F7); } - else if (EIGHT.matches(e)) + else if (config.f8().matches(e)) { modified.put(e.getKeyCode(), KeyEvent.VK_F8); e.setKeyCode(KeyEvent.VK_F8); } - else if (NINE.matches(e)) + else if (config.f9().matches(e)) { modified.put(e.getKeyCode(), KeyEvent.VK_F9); e.setKeyCode(KeyEvent.VK_F9); } - else if (ZERO.matches(e)) + else if (config.f10().matches(e)) { modified.put(e.getKeyCode(), KeyEvent.VK_F10); e.setKeyCode(KeyEvent.VK_F10); } - else if (MINUS.matches(e)) + else if (config.f11().matches(e)) { modified.put(e.getKeyCode(), KeyEvent.VK_F11); e.setKeyCode(KeyEvent.VK_F11); } - else if (EQUALS.matches(e)) + else if (config.f12().matches(e)) { modified.put(e.getKeyCode(), KeyEvent.VK_F12); e.setKeyCode(KeyEvent.VK_F12); @@ -245,51 +231,51 @@ class KeyRemappingListener extends MouseAdapter implements KeyListener if (config.fkeyRemap()) { - if (ONE.matches(e)) + if (config.f1().matches(e)) { e.setKeyCode(KeyEvent.VK_F1); } - else if (TWO.matches(e)) + else if (config.f2().matches(e)) { e.setKeyCode(KeyEvent.VK_F2); } - else if (THREE.matches(e)) + else if (config.f3().matches(e)) { e.setKeyCode(KeyEvent.VK_F3); } - else if (FOUR.matches(e)) + else if (config.f4().matches(e)) { e.setKeyCode(KeyEvent.VK_F4); } - else if (FIVE.matches(e)) + else if (config.f5().matches(e)) { e.setKeyCode(KeyEvent.VK_F5); } - else if (SIX.matches(e)) + else if (config.f6().matches(e)) { e.setKeyCode(KeyEvent.VK_F6); } - else if (SEVEN.matches(e)) + else if (config.f7().matches(e)) { e.setKeyCode(KeyEvent.VK_F7); } - else if (EIGHT.matches(e)) + else if (config.f8().matches(e)) { e.setKeyCode(KeyEvent.VK_F8); } - else if (NINE.matches(e)) + else if (config.f9().matches(e)) { e.setKeyCode(KeyEvent.VK_F9); } - else if (ZERO.matches(e)) + else if (config.f10().matches(e)) { e.setKeyCode(KeyEvent.VK_F10); } - else if (MINUS.matches(e)) + else if (config.f11().matches(e)) { e.setKeyCode(KeyEvent.VK_F11); } - else if (EQUALS.matches(e)) + else if (config.f12().matches(e)) { e.setKeyCode(KeyEvent.VK_F12); } From 39e66af62346949dc77e339ebfb34014b0a3b220 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 8 Jul 2019 18:10:45 -0400 Subject: [PATCH 03/10] keyremapping: add F-key remap for escape Co-authored-by: Chet Powers --- .../plugins/keyremapping/KeyRemappingConfig.java | 11 +++++++++++ .../plugins/keyremapping/KeyRemappingListener.java | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingConfig.java index 9702f6c40c..0ee4c58e22 100755 --- a/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingConfig.java @@ -230,4 +230,15 @@ public interface KeyRemappingConfig extends Config { return new ModifierlessKeybind(KeyEvent.VK_EQUALS, 0); } + + @ConfigItem( + position = 19, + keyName = "esc", + name = "ESC", + description = "The key which will replace {ESC}." + ) + default ModifierlessKeybind esc() + { + return new ModifierlessKeybind(KeyEvent.VK_ESCAPE, 0); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java index e3257f8e73..9ffbd27e77 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java @@ -158,6 +158,11 @@ class KeyRemappingListener extends MouseAdapter implements KeyListener modified.put(e.getKeyCode(), KeyEvent.VK_F12); e.setKeyCode(KeyEvent.VK_F12); } + else if (config.esc().matches(e)) + { + modified.put(e.getKeyCode(), KeyEvent.VK_ESCAPE); + e.setKeyCode(KeyEvent.VK_ESCAPE); + } } switch (e.getKeyCode()) @@ -279,6 +284,10 @@ class KeyRemappingListener extends MouseAdapter implements KeyListener { e.setKeyCode(KeyEvent.VK_F12); } + else if (config.esc().matches(e)) + { + e.setKeyCode(KeyEvent.VK_ESCAPE); + } } } else From b874b18abebe14aa375151cefbd3725eb0456155 Mon Sep 17 00:00:00 2001 From: Decorth <38436593+Decorth@users.noreply.github.com> Date: Tue, 9 Jul 2019 10:47:11 +0100 Subject: [PATCH 04/10] Add Sarachnis respawn timer to Boss timers plugin (#9313) --- .../main/java/net/runelite/client/plugins/bosstimer/Boss.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java b/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java index 47752f415b..81ae775d12 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java @@ -57,7 +57,8 @@ enum Boss KRAKEN(NpcID.KRAKEN, 8400, ChronoUnit.MILLIS, ItemID.PET_KRAKEN), KALPHITE_QUEEN(NpcID.KALPHITE_QUEEN_965, 30, ChronoUnit.SECONDS, ItemID.KALPHITE_PRINCESS), DUSK(NpcID.DUSK_7889, 2, ChronoUnit.MINUTES, ItemID.NOON), - ALCHEMICAL_HYDRA(NpcID.ALCHEMICAL_HYDRA_8622, 25200, ChronoUnit.MILLIS, ItemID.IKKLE_HYDRA); + ALCHEMICAL_HYDRA(NpcID.ALCHEMICAL_HYDRA_8622, 25200, ChronoUnit.MILLIS, ItemID.IKKLE_HYDRA), + SARACHNIS(NpcID.SARACHNIS, 30, ChronoUnit.SECONDS, ItemID.SRARACHA); private static final Map bosses; From 7f7a71eb386acd122948d1b4faaf5757dee59080 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 9 Jul 2019 09:23:38 -0400 Subject: [PATCH 05/10] api: add item container id to item container changed event --- .../java/net/runelite/api/events/ItemContainerChanged.java | 5 +++++ .../main/java/net/runelite/client/util/GameEventManager.java | 2 +- .../client/plugins/motherlode/MotherlodePluginTest.java | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/runelite-api/src/main/java/net/runelite/api/events/ItemContainerChanged.java b/runelite-api/src/main/java/net/runelite/api/events/ItemContainerChanged.java index f4c5d8163f..f2ff89a679 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ItemContainerChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ItemContainerChanged.java @@ -41,6 +41,11 @@ import net.runelite.api.ItemContainer; @Value public class ItemContainerChanged { + /** + * The modified container's ID. + */ + private final int containerId; + /** * The modified item container. */ diff --git a/runelite-client/src/main/java/net/runelite/client/util/GameEventManager.java b/runelite-client/src/main/java/net/runelite/client/util/GameEventManager.java index edaedc856f..e837bb54d9 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/GameEventManager.java +++ b/runelite-client/src/main/java/net/runelite/client/util/GameEventManager.java @@ -118,7 +118,7 @@ public class GameEventManager if (itemContainer != null) { - eventBus.post(new ItemContainerChanged(itemContainer)); + eventBus.post(new ItemContainerChanged(inventory.getId(), itemContainer)); } } diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/motherlode/MotherlodePluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/motherlode/MotherlodePluginTest.java index 9ceaf15247..602a58f3b6 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/motherlode/MotherlodePluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/motherlode/MotherlodePluginTest.java @@ -147,7 +147,7 @@ public class MotherlodePluginTest when(client.getItemContainer(InventoryID.INVENTORY)).thenReturn(inventory); // Trigger comparison - motherlodePlugin.onItemContainerChanged(new ItemContainerChanged(inventory)); + motherlodePlugin.onItemContainerChanged(new ItemContainerChanged(InventoryID.INVENTORY.getId(), inventory)); verify(motherlodeSession).updateOreFound(ItemID.RUNITE_ORE, 1); verify(motherlodeSession).updateOreFound(ItemID.GOLDEN_NUGGET, 4); From 62e877de5ca53d284c1c64165e495e4562bb200e Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 9 Jul 2019 13:53:33 -0400 Subject: [PATCH 06/10] chat filter: add option to filter login notifications Co-authored-by: gregg1494 --- .../client/plugins/chatfilter/ChatFilterConfig.java | 11 +++++++++++ .../client/plugins/chatfilter/ChatFilterPlugin.java | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterConfig.java index 264e5ae425..dbdb4d6478 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterConfig.java @@ -86,4 +86,15 @@ public interface ChatFilterConfig extends Config { return false; } + + @ConfigItem( + keyName = "filterLogin", + name = "Filter Logged In/Out Messages", + description = "Filter your private chat to remove logged in/out messages", + position = 6 + ) + default boolean filterLogin() + { + return false; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java index 53f4e43ef6..6a3db9266f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java @@ -116,6 +116,13 @@ public class ChatFilterPlugin extends Plugin case MODPRIVATECHAT: case FRIENDSCHAT: break; + case LOGINLOGOUTNOTIFICATION: + if (config.filterLogin()) + { + // Block the message + intStack[intStackSize - 3] = 0; + } + return; default: return; } From 3dc8522d53635042d41c6f7cbbd8594402414520 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 9 Jul 2019 19:28:47 +0100 Subject: [PATCH 07/10] idle notifier: add farming harvest animations --- runelite-api/src/main/java/net/runelite/api/AnimationID.java | 1 + .../client/plugins/idlenotifier/IdleNotifierPlugin.java | 5 +++++ 2 files changed, 6 insertions(+) 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 316feda5d0..1dc49ca455 100644 --- a/runelite-api/src/main/java/net/runelite/api/AnimationID.java +++ b/runelite-api/src/main/java/net/runelite/api/AnimationID.java @@ -171,6 +171,7 @@ public final class AnimationID public static final int FARMING_PLANT_SEED = 2291; public static final int FARMING_HARVEST_FLOWER = 2292; public static final int FARMING_MIX_ULTRACOMPOST = 7699; + public static final int FARMING_HARVEST_ALLOTMENT = 830; // Lunar spellbook public static final int ENERGY_TRANSFER_VENGEANCE_OTHER = 4411; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java index bb7dee8b47..45b37e60f7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java @@ -217,6 +217,11 @@ public class IdleNotifierPlugin extends Plugin case USING_GILDED_ALTAR: /* Farming */ case FARMING_MIX_ULTRACOMPOST: + case FARMING_HARVEST_BUSH: + case FARMING_HARVEST_HERB: + case FARMING_HARVEST_FRUIT_TREE: + case FARMING_HARVEST_FLOWER: + case FARMING_HARVEST_ALLOTMENT: /* Misc */ case PISCARILIUS_CRANE_REPAIR: case HOME_MAKE_TABLET: From d39bfcb50feadeb782c076131e3598a18dec4b25 Mon Sep 17 00:00:00 2001 From: TheStonedTurtle Date: Thu, 20 Jun 2019 12:46:31 -0700 Subject: [PATCH 08/10] itemskeptondeath: fix many edge cases and add tests This adds support for most degradeable and imbued items, which have death prices that are fixed offsets of the prices of some other reference item, clue boxes, and jewelery. The plugin was refactored largely to be able to add the tests, and now works on our internal DeathItem and ItemStack classes instead of widgets and items. Co-authored-by: Adam --- .../net/runelite/client/game/ItemManager.java | 21 +- .../itemskeptondeath/AlwaysLostItem.java | 4 +- .../itemskeptondeath/DynamicPriceItem.java | 107 +++ .../itemskeptondeath/FixedPriceItem.java | 167 ++++- .../plugins/itemskeptondeath/ItemStack.java | 36 + .../ItemsKeptOnDeathPlugin.java | 342 ++++++---- .../itemskeptondeath/LostIfNotProtected.java | 45 ++ .../ItemsKeptOnDeathPluginTest.java | 616 ++++++++++++++++++ 8 files changed, 1215 insertions(+), 123 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/DynamicPriceItem.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/ItemStack.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/LostIfNotProtected.java create mode 100644 runelite-client/src/test/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPluginTest.java diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java index bbc07771c2..a257dccba1 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java @@ -273,6 +273,18 @@ public class ItemManager * @return item price */ public int getItemPrice(int itemID) + { + return getItemPrice(itemID, false); + } + + /** + * Look up an item's price + * + * @param itemID item id + * @param ignoreUntradeableMap should the price returned ignore the {@link UntradeableItemMapping} + * @return item price + */ + public int getItemPrice(int itemID, boolean ignoreUntradeableMap) { if (itemID == ItemID.COINS_995) { @@ -283,10 +295,13 @@ public class ItemManager return 1000; } - UntradeableItemMapping p = UntradeableItemMapping.map(ItemVariationMapping.map(itemID)); - if (p != null) + if (!ignoreUntradeableMap) { - return getItemPrice(p.getPriceID()) * p.getQuantity(); + UntradeableItemMapping p = UntradeableItemMapping.map(ItemVariationMapping.map(itemID)); + if (p != null) + { + return getItemPrice(p.getPriceID()) * p.getQuantity(); + } } int price = 0; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/AlwaysLostItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/AlwaysLostItem.java index 37066622bc..140b524cfe 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/AlwaysLostItem.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/AlwaysLostItem.java @@ -41,7 +41,9 @@ enum AlwaysLostItem { RUNE_POUCH(ItemID.RUNE_POUCH, true), LOOTING_BAG(ItemID.LOOTING_BAG, false), - CLUE_BOX(ItemID.CLUE_BOX, false); + CLUE_BOX(ItemID.CLUE_BOX, false), + BRACELET_OF_ETHEREUM(ItemID.BRACELET_OF_ETHEREUM, false), + BRACELET_OF_ETHEREUM_UNCHARGED(ItemID.BRACELET_OF_ETHEREUM_UNCHARGED, false); private final int itemID; private final boolean keptOutsideOfWilderness; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/DynamicPriceItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/DynamicPriceItem.java new file mode 100644 index 0000000000..4726555485 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/DynamicPriceItem.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2019, TheStonedTurtle + * 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.itemskeptondeath; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import javax.annotation.Nullable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.runelite.api.ItemID; + +/** + * Degradable/Non-rechargeable Jewelry death prices are usually determined by the amount of charges the item has left. + * The price of each charge is based on the GE price of the fully charged item divided by the maximum item charges + * Charge price = GE Price / Max Charges + * Death Price = Charge price * Current Charges + */ +@AllArgsConstructor +@Getter +enum DynamicPriceItem +{ + GAMES_NECKLACE1(ItemID.GAMES_NECKLACE1, 1, 8, ItemID.GAMES_NECKLACE8), + GAMES_NECKLACE2(ItemID.GAMES_NECKLACE2, 2, 8, ItemID.GAMES_NECKLACE8), + GAMES_NECKLACE3(ItemID.GAMES_NECKLACE3, 3, 8, ItemID.GAMES_NECKLACE8), + GAMES_NECKLACE4(ItemID.GAMES_NECKLACE4, 4, 8, ItemID.GAMES_NECKLACE8), + GAMES_NECKLACE5(ItemID.GAMES_NECKLACE5, 5, 8, ItemID.GAMES_NECKLACE8), + GAMES_NECKLACE6(ItemID.GAMES_NECKLACE6, 6, 8, ItemID.GAMES_NECKLACE8), + GAMES_NECKLACE7(ItemID.GAMES_NECKLACE7, 7, 8, ItemID.GAMES_NECKLACE8), + + RING_OF_DUELING1(ItemID.RING_OF_DUELING1, 1, 8, ItemID.RING_OF_DUELING8), + RING_OF_DUELING2(ItemID.RING_OF_DUELING2, 2, 8, ItemID.RING_OF_DUELING8), + RING_OF_DUELING3(ItemID.RING_OF_DUELING3, 3, 8, ItemID.RING_OF_DUELING8), + RING_OF_DUELING4(ItemID.RING_OF_DUELING4, 4, 8, ItemID.RING_OF_DUELING8), + RING_OF_DUELING5(ItemID.RING_OF_DUELING5, 5, 8, ItemID.RING_OF_DUELING8), + RING_OF_DUELING6(ItemID.RING_OF_DUELING6, 6, 8, ItemID.RING_OF_DUELING8), + RING_OF_DUELING7(ItemID.RING_OF_DUELING7, 7, 8, ItemID.RING_OF_DUELING8), + + RING_OF_RETURNING1(ItemID.RING_OF_RETURNING1, 1, 5, ItemID.RING_OF_RETURNING5), + RING_OF_RETURNING2(ItemID.RING_OF_RETURNING2, 2, 5, ItemID.RING_OF_RETURNING5), + RING_OF_RETURNING3(ItemID.RING_OF_RETURNING3, 3, 5, ItemID.RING_OF_RETURNING5), + RING_OF_RETURNING4(ItemID.RING_OF_RETURNING4, 4, 5, ItemID.RING_OF_RETURNING5), + + NECKLACE_OF_PASSAGE1(ItemID.NECKLACE_OF_PASSAGE1, 1, 5, ItemID.NECKLACE_OF_PASSAGE5), + NECKLACE_OF_PASSAGE2(ItemID.NECKLACE_OF_PASSAGE2, 2, 5, ItemID.NECKLACE_OF_PASSAGE5), + NECKLACE_OF_PASSAGE3(ItemID.NECKLACE_OF_PASSAGE3, 3, 5, ItemID.NECKLACE_OF_PASSAGE5), + NECKLACE_OF_PASSAGE4(ItemID.NECKLACE_OF_PASSAGE4, 4, 5, ItemID.NECKLACE_OF_PASSAGE5), + + BURNING_AMULET1(ItemID.BURNING_AMULET1, 1, 5, ItemID.BURNING_AMULET5), + BURNING_AMULET2(ItemID.BURNING_AMULET2, 2, 5, ItemID.BURNING_AMULET5), + BURNING_AMULET3(ItemID.BURNING_AMULET3, 3, 5, ItemID.BURNING_AMULET5), + BURNING_AMULET4(ItemID.BURNING_AMULET4, 4, 5, ItemID.BURNING_AMULET5); + + private final int itemId; + private final int currentCharges; + private final int maxCharges; + private final int chargedId; + + private static final Map DYNAMIC_ITEMS; + + static + { + final ImmutableMap.Builder map = ImmutableMap.builder(); + for (final DynamicPriceItem p : values()) + { + map.put(p.itemId, p); + } + DYNAMIC_ITEMS = map.build(); + } + + /** + * Calculates the price off the partially charged jewelry based on the base items price + * @param basePrice price of the base item, usually the trade-able variant + * @return death price of the current DynamicPriceItem + */ + int calculateDeathPrice(final int basePrice) + { + return (basePrice / maxCharges) * currentCharges; + } + + @Nullable + static DynamicPriceItem find(int itemId) + { + return DYNAMIC_ITEMS.get(itemId); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/FixedPriceItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/FixedPriceItem.java index a40851dc65..74120a47d6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/FixedPriceItem.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/FixedPriceItem.java @@ -28,7 +28,6 @@ package net.runelite.client.plugins.itemskeptondeath; import com.google.common.collect.ImmutableMap; import java.util.Map; import javax.annotation.Nullable; -import lombok.AllArgsConstructor; import lombok.Getter; import net.runelite.api.ItemID; @@ -36,7 +35,6 @@ import net.runelite.api.ItemID; * Some items have a fixed price that is added to its default value when calculating death prices. * These are typically imbued items, such as Berserker ring (i), to help it protect over the non-imbued variants. */ -@AllArgsConstructor @Getter enum FixedPriceItem { @@ -66,10 +64,161 @@ enum FixedPriceItem IMBUED_RING_OF_THE_GODS_I(ItemID.RING_OF_THE_GODS_I, 2000), IMBUED_TREASONOUS_RING_I(ItemID.TREASONOUS_RING_I, 2000), - IMBUED_TYRANNICAL_RING_I(ItemID.TYRANNICAL_RING_I, 2000); + IMBUED_TYRANNICAL_RING_I(ItemID.TYRANNICAL_RING_I, 2000), + + GRACEFUL_HOOD(ItemID.GRACEFUL_HOOD, 1965), + GRACEFUL_CAPE(ItemID.GRACEFUL_CAPE, 2460), + GRACEFUL_TOP(ItemID.GRACEFUL_TOP, 2345), + GRACEFUL_LEGS(ItemID.GRACEFUL_LEGS, 2290), + GRACEFUL_GLOVES(ItemID.GRACEFUL_GLOVES, 1970), + GRACEFUL_BOOTS(ItemID.GRACEFUL_BOOTS, 2060), + + ANGLER_HAT(ItemID.ANGLER_HAT, 2600), + ANGLER_TOP(ItemID.ANGLER_TOP, 3550), + ANGLER_WADERS(ItemID.ANGLER_WADERS, 4400), + ANGLER_BOOTS(ItemID.ANGLER_BOOTS, 5300), + + PROSPECTOR_HELMET(ItemID.PROSPECTOR_HELMET, 2640), + PROSPECTOR_JACKET(ItemID.PROSPECTOR_JACKET, 3550), + PROSPECTOR_LEGS(ItemID.PROSPECTOR_LEGS, 4460), + PROSPECTOR_BOOTS(ItemID.PROSPECTOR_BOOTS, 5370), + + LUMBERJACK_HAT(ItemID.LUMBERJACK_HAT, 19950), + LUMBERJACK_TOP(ItemID.LUMBERJACK_TOP, 19950), + LUMBERJACK_LEGS(ItemID.LUMBERJACK_LEGS, 19950), + LUMBERJACK_BOOTS(ItemID.LUMBERJACK_BOOTS, 19950), + + ROGUE_MASK(ItemID.ROGUE_MASK, 725), + ROGUE_TOP(ItemID.ROGUE_TOP, 575), + ROGUE_TROUSERS(ItemID.ROGUE_TROUSERS, 500), + ROGUE_GLOVES(ItemID.ROGUE_GLOVES, 650), + ROGUE_BOOTS(ItemID.ROGUE_BOOTS, 650), + + RING_OF_WEALTH_1(ItemID.RING_OF_WEALTH_1, 500, ItemID.RING_OF_WEALTH), + RING_OF_WEALTH_2(ItemID.RING_OF_WEALTH_2, 1000, ItemID.RING_OF_WEALTH), + RING_OF_WEALTH_3(ItemID.RING_OF_WEALTH_3, 1500, ItemID.RING_OF_WEALTH), + RING_OF_WEALTH_4(ItemID.RING_OF_WEALTH_4, 2000, ItemID.RING_OF_WEALTH), + + AMULET_OF_GLORY1(ItemID.AMULET_OF_GLORY1, 500, ItemID.AMULET_OF_GLORY), + AMULET_OF_GLORY2(ItemID.AMULET_OF_GLORY2, 1000, ItemID.AMULET_OF_GLORY), + AMULET_OF_GLORY3(ItemID.AMULET_OF_GLORY3, 1500, ItemID.AMULET_OF_GLORY), + AMULET_OF_GLORY5(ItemID.AMULET_OF_GLORY5, 2500, ItemID.AMULET_OF_GLORY), + + COMBAT_BRACELET1(ItemID.COMBAT_BRACELET1, 500, ItemID.COMBAT_BRACELET), + COMBAT_BRACELET2(ItemID.COMBAT_BRACELET2, 1000, ItemID.COMBAT_BRACELET), + COMBAT_BRACELET3(ItemID.COMBAT_BRACELET3, 1500, ItemID.COMBAT_BRACELET), + COMBAT_BRACELET5(ItemID.COMBAT_BRACELET5, 2500, ItemID.COMBAT_BRACELET), + + SKILLS_NECKLACE1(ItemID.SKILLS_NECKLACE1, 500, ItemID.SKILLS_NECKLACE), + SKILLS_NECKLACE2(ItemID.SKILLS_NECKLACE2, 1000, ItemID.SKILLS_NECKLACE), + SKILLS_NECKLACE3(ItemID.SKILLS_NECKLACE3, 1500, ItemID.SKILLS_NECKLACE), + SKILLS_NECKLACE4(ItemID.SKILLS_NECKLACE5, 2500, ItemID.SKILLS_NECKLACE), + + AHRIMS_HOOD_25(ItemID.AHRIMS_HOOD_25, 2500, ItemID.AHRIMS_HOOD_0), + AHRIMS_HOOD_50(ItemID.AHRIMS_HOOD_50, 5000, ItemID.AHRIMS_HOOD_0), + AHRIMS_HOOD_75(ItemID.AHRIMS_HOOD_75, 7500, ItemID.AHRIMS_HOOD_0), + AHRIMS_HOOD_100(ItemID.AHRIMS_HOOD_100, 10000, ItemID.AHRIMS_HOOD_0), + AHRIMS_ROBETOP_25(ItemID.AHRIMS_ROBETOP_25, 2500, ItemID.AHRIMS_ROBETOP_0), + AHRIMS_ROBETOP_50(ItemID.AHRIMS_ROBETOP_50, 5000, ItemID.AHRIMS_ROBETOP_0), + AHRIMS_ROBETOP_75(ItemID.AHRIMS_ROBETOP_75, 7500, ItemID.AHRIMS_ROBETOP_0), + AHRIMS_ROBETOP_100(ItemID.AHRIMS_ROBETOP_100, 10000, ItemID.AHRIMS_ROBETOP_0), + AHRIMS_ROBESKIRT_25(ItemID.AHRIMS_ROBESKIRT_25, 2500, ItemID.AHRIMS_ROBESKIRT_0), + AHRIMS_ROBESKIRT_50(ItemID.AHRIMS_ROBESKIRT_50, 5000, ItemID.AHRIMS_ROBESKIRT_0), + AHRIMS_ROBESKIRT_75(ItemID.AHRIMS_ROBESKIRT_75, 7500, ItemID.AHRIMS_ROBESKIRT_0), + AHRIMS_ROBESKIRT_100(ItemID.AHRIMS_ROBESKIRT_100, 10000, ItemID.AHRIMS_ROBESKIRT_0), + AHRIMS_STAFF_25(ItemID.AHRIMS_STAFF_25, 2500, ItemID.AHRIMS_STAFF_0), + AHRIMS_STAFF_50(ItemID.AHRIMS_STAFF_50, 5000, ItemID.AHRIMS_STAFF_0), + AHRIMS_STAFF_75(ItemID.AHRIMS_STAFF_75, 7500, ItemID.AHRIMS_STAFF_0), + AHRIMS_STAFF_100(ItemID.AHRIMS_STAFF_100, 10000, ItemID.AHRIMS_STAFF_0), + + KARILS_COIF_25(ItemID.KARILS_COIF_25, 2500, ItemID.KARILS_COIF_0), + KARILS_COIF_50(ItemID.KARILS_COIF_50, 5000, ItemID.KARILS_COIF_0), + KARILS_COIF_75(ItemID.KARILS_COIF_75, 7500, ItemID.KARILS_COIF_0), + KARILS_COIF_100(ItemID.KARILS_COIF_100, 10000, ItemID.KARILS_COIF_0), + KARILS_LEATHERTOP_25(ItemID.KARILS_LEATHERTOP_25, 2500, ItemID.KARILS_LEATHERTOP_0), + KARILS_LEATHERTOP_50(ItemID.KARILS_LEATHERTOP_50, 5000, ItemID.KARILS_LEATHERTOP_0), + KARILS_LEATHERTOP_75(ItemID.KARILS_LEATHERTOP_75, 7500, ItemID.KARILS_LEATHERTOP_0), + KARILS_LEATHERTOP_100(ItemID.KARILS_LEATHERTOP_100, 10000, ItemID.KARILS_LEATHERTOP_0), + KARILS_LEATHERSKIRT_25(ItemID.KARILS_LEATHERSKIRT_25, 2500, ItemID.KARILS_LEATHERSKIRT_0), + KARILS_LEATHERSKIRT_50(ItemID.KARILS_LEATHERSKIRT_50, 5000, ItemID.KARILS_LEATHERSKIRT_0), + KARILS_LEATHERSKIRT_75(ItemID.KARILS_LEATHERSKIRT_75, 7500, ItemID.KARILS_LEATHERSKIRT_0), + KARILS_LEATHERSKIRT_100(ItemID.KARILS_LEATHERSKIRT_100, 10000, ItemID.KARILS_LEATHERSKIRT_0), + KARILS_CROSSBOW_25(ItemID.KARILS_CROSSBOW_25, 2500, ItemID.KARILS_CROSSBOW_0), + KARILS_CROSSBOW_50(ItemID.KARILS_CROSSBOW_50, 5000, ItemID.KARILS_CROSSBOW_0), + KARILS_CROSSBOW_75(ItemID.KARILS_CROSSBOW_75, 7500, ItemID.KARILS_CROSSBOW_0), + KARILS_CROSSBOW_100(ItemID.KARILS_CROSSBOW_100, 10000, ItemID.KARILS_CROSSBOW_0), + + DHAROKS_HELM_25(ItemID.DHAROKS_HELM_25, 2500, ItemID.DHAROKS_HELM_0), + DHAROKS_HELM_50(ItemID.DHAROKS_HELM_50, 5000, ItemID.DHAROKS_HELM_0), + DHAROKS_HELM_75(ItemID.DHAROKS_HELM_75, 7500, ItemID.DHAROKS_HELM_0), + DHAROKS_HELM_100(ItemID.DHAROKS_HELM_100, 10000, ItemID.DHAROKS_HELM_0), + DHAROKS_PLATEBODY_25(ItemID.DHAROKS_PLATEBODY_25, 2500, ItemID.DHAROKS_PLATEBODY_0), + DHAROKS_PLATEBODY_50(ItemID.DHAROKS_PLATEBODY_50, 5000, ItemID.DHAROKS_PLATEBODY_0), + DHAROKS_PLATEBODY_75(ItemID.DHAROKS_PLATEBODY_75, 7500, ItemID.DHAROKS_PLATEBODY_0), + DHAROKS_PLATEBODY_100(ItemID.DHAROKS_PLATEBODY_100, 10000, ItemID.DHAROKS_PLATEBODY_0), + DHAROKS_PLATELEGS_25(ItemID.DHAROKS_PLATELEGS_25, 2500, ItemID.DHAROKS_PLATELEGS_0), + DHAROKS_PLATELEGS_50(ItemID.DHAROKS_PLATELEGS_50, 5000, ItemID.DHAROKS_PLATELEGS_0), + DHAROKS_PLATELEGS_75(ItemID.DHAROKS_PLATELEGS_75, 7500, ItemID.DHAROKS_PLATELEGS_0), + DHAROKS_PLATELEGS_100(ItemID.DHAROKS_PLATELEGS_100, 10000, ItemID.DHAROKS_PLATELEGS_0), + DHAROKS_GREATAXE_25(ItemID.DHAROKS_GREATAXE_25, 2500, ItemID.DHAROKS_GREATAXE_0), + DHAROKS_GREATAXE_50(ItemID.DHAROKS_GREATAXE_50, 5000, ItemID.DHAROKS_GREATAXE_0), + DHAROKS_GREATAXE_75(ItemID.DHAROKS_GREATAXE_75, 7500, ItemID.DHAROKS_GREATAXE_0), + DHAROKS_GREATAXE_100(ItemID.DHAROKS_GREATAXE_100, 10000, ItemID.DHAROKS_GREATAXE_0), + + GUTHANS_HELM_25(ItemID.GUTHANS_HELM_25, 2500, ItemID.GUTHANS_HELM_0), + GUTHANS_HELM_50(ItemID.GUTHANS_HELM_50, 5000, ItemID.GUTHANS_HELM_0), + GUTHANS_HELM_75(ItemID.GUTHANS_HELM_75, 7500, ItemID.GUTHANS_HELM_0), + GUTHANS_HELM_100(ItemID.GUTHANS_HELM_100, 10000, ItemID.GUTHANS_HELM_0), + GUTHANS_PLATEBODY_25(ItemID.GUTHANS_PLATEBODY_25, 2500, ItemID.GUTHANS_PLATEBODY_0), + GUTHANS_PLATEBODY_50(ItemID.GUTHANS_PLATEBODY_50, 5000, ItemID.GUTHANS_PLATEBODY_0), + GUTHANS_PLATEBODY_75(ItemID.GUTHANS_PLATEBODY_75, 7500, ItemID.GUTHANS_PLATEBODY_0), + GUTHANS_PLATEBODY_100(ItemID.GUTHANS_PLATEBODY_100, 10000, ItemID.GUTHANS_PLATEBODY_0), + GUTHANS_CHAINSKIRT_25(ItemID.GUTHANS_CHAINSKIRT_25, 2500, ItemID.GUTHANS_CHAINSKIRT_0), + GUTHANS_CHAINSKIRT_50(ItemID.GUTHANS_CHAINSKIRT_50, 5000, ItemID.GUTHANS_CHAINSKIRT_0), + GUTHANS_CHAINSKIRT_75(ItemID.GUTHANS_CHAINSKIRT_75, 7500, ItemID.GUTHANS_CHAINSKIRT_0), + GUTHANS_CHAINSKIRT_100(ItemID.GUTHANS_CHAINSKIRT_100, 10000, ItemID.GUTHANS_CHAINSKIRT_0), + GUTHANS_WARSPEAR_25(ItemID.GUTHANS_WARSPEAR_25, 2500, ItemID.GUTHANS_WARSPEAR_0), + GUTHANS_WARSPEAR_50(ItemID.GUTHANS_WARSPEAR_50, 5000, ItemID.GUTHANS_WARSPEAR_0), + GUTHANS_WARSPEAR_75(ItemID.GUTHANS_WARSPEAR_75, 7500, ItemID.GUTHANS_WARSPEAR_0), + GUTHANS_WARSPEAR_100(ItemID.GUTHANS_WARSPEAR_100, 10000, ItemID.GUTHANS_WARSPEAR_0), + + TORAGS_HELM_25(ItemID.TORAGS_HELM_25, 2500, ItemID.TORAGS_HELM_0), + TORAGS_HELM_50(ItemID.TORAGS_HELM_50, 5000, ItemID.TORAGS_HELM_0), + TORAGS_HELM_75(ItemID.TORAGS_HELM_75, 7500, ItemID.TORAGS_HELM_0), + TORAGS_HELM_100(ItemID.TORAGS_HELM_100, 10000, ItemID.TORAGS_HELM_0), + TORAGS_PLATEBODY_25(ItemID.TORAGS_PLATEBODY_25, 2500, ItemID.TORAGS_PLATEBODY_0), + TORAGS_PLATEBODY_50(ItemID.TORAGS_PLATEBODY_50, 5000, ItemID.TORAGS_PLATEBODY_0), + TORAGS_PLATEBODY_75(ItemID.TORAGS_PLATEBODY_75, 7500, ItemID.TORAGS_PLATEBODY_0), + TORAGS_PLATEBODY_100(ItemID.TORAGS_PLATEBODY_100, 10000, ItemID.TORAGS_PLATEBODY_0), + TORAGS_PLATELEGS_25(ItemID.TORAGS_PLATELEGS_25, 2500, ItemID.TORAGS_PLATELEGS_0), + TORAGS_PLATELEGS_50(ItemID.TORAGS_PLATELEGS_50, 5000, ItemID.TORAGS_PLATELEGS_0), + TORAGS_PLATELEGS_75(ItemID.TORAGS_PLATELEGS_75, 7500, ItemID.TORAGS_PLATELEGS_0), + TORAGS_PLATELEGS_100(ItemID.TORAGS_PLATELEGS_100, 10000, ItemID.TORAGS_PLATELEGS_0), + TORAGS_HAMMERS_25(ItemID.TORAGS_HAMMERS_25, 2500, ItemID.TORAGS_HAMMERS_0), + TORAGS_HAMMERS_50(ItemID.TORAGS_HAMMERS_50, 5000, ItemID.TORAGS_HAMMERS_0), + TORAGS_HAMMERS_75(ItemID.TORAGS_HAMMERS_75, 7500, ItemID.TORAGS_HAMMERS_0), + TORAGS_HAMMERS_100(ItemID.TORAGS_HAMMERS_100, 10000, ItemID.TORAGS_HAMMERS_0), + + VERACS_HELM_25(ItemID.VERACS_HELM_25, 2500, ItemID.VERACS_HELM_0), + VERACS_HELM_50(ItemID.VERACS_HELM_50, 5000, ItemID.VERACS_HELM_0), + VERACS_HELM_75(ItemID.VERACS_HELM_75, 7500, ItemID.VERACS_HELM_0), + VERACS_HELM_100(ItemID.VERACS_HELM_100, 10000, ItemID.VERACS_HELM_0), + VERACS_BRASSARD_25(ItemID.VERACS_BRASSARD_25, 2500, ItemID.VERACS_BRASSARD_0), + VERACS_BRASSARD_50(ItemID.VERACS_BRASSARD_50, 5000, ItemID.VERACS_BRASSARD_0), + VERACS_BRASSARD_75(ItemID.VERACS_BRASSARD_75, 7500, ItemID.VERACS_BRASSARD_0), + VERACS_BRASSARD_100(ItemID.VERACS_BRASSARD_100, 10000, ItemID.VERACS_BRASSARD_0), + VERACS_PLATESKIRT_25(ItemID.VERACS_PLATESKIRT_25, 2500, ItemID.VERACS_PLATESKIRT_0), + VERACS_PLATESKIRT_50(ItemID.VERACS_PLATESKIRT_50, 5000, ItemID.VERACS_PLATESKIRT_0), + VERACS_PLATESKIRT_75(ItemID.VERACS_PLATESKIRT_75, 7500, ItemID.VERACS_PLATESKIRT_0), + VERACS_PLATESKIRT_100(ItemID.VERACS_PLATESKIRT_100, 10000, ItemID.VERACS_PLATESKIRT_0), + VERACS_FLAIL_25(ItemID.VERACS_FLAIL_25, 2500, ItemID.VERACS_FLAIL_0), + VERACS_FLAIL_50(ItemID.VERACS_FLAIL_50, 5000, ItemID.VERACS_FLAIL_0), + VERACS_FLAIL_75(ItemID.VERACS_FLAIL_75, 7500, ItemID.VERACS_FLAIL_0), + VERACS_FLAIL_100(ItemID.VERACS_FLAIL_100, 10000, ItemID.VERACS_FLAIL_0); private final int itemId; private final int offset; + private final int baseId; private static final Map FIXED_ITEMS; @@ -83,6 +232,18 @@ enum FixedPriceItem FIXED_ITEMS = map.build(); } + FixedPriceItem(final int itemId, final int offset, final int baseId) + { + this.itemId = itemId; + this.offset = offset; + this.baseId = baseId; + } + + FixedPriceItem(final int itemId, final int offset) + { + this(itemId, offset, -1); + } + @Nullable static FixedPriceItem find(int itemId) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/ItemStack.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/ItemStack.java new file mode 100644 index 0000000000..d2f09dd856 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/ItemStack.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019, TheStonedTurtle + * 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.itemskeptondeath; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +class ItemStack +{ + private int id; + private int qty; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPlugin.java index dd9a5403e5..05089bc898 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPlugin.java @@ -25,6 +25,7 @@ */ package net.runelite.client.plugins.itemskeptondeath; +import com.google.common.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -32,7 +33,10 @@ import java.util.EnumSet; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import javax.inject.Inject; +import lombok.AllArgsConstructor; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.Constants; @@ -54,6 +58,7 @@ import net.runelite.api.widgets.WidgetInfo; import net.runelite.api.widgets.WidgetType; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.game.ItemManager; +import net.runelite.client.game.ItemMapping; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.util.StackFormatter; @@ -69,6 +74,16 @@ public class ItemsKeptOnDeathPlugin extends Plugin private static final int DEEP_WILDY = 20; private static final Pattern WILDERNESS_LEVEL_PATTERN = Pattern.compile("^Level: (\\d+).*"); + @AllArgsConstructor + @Getter + @VisibleForTesting + static class DeathItems + { + private final List keptItems; + private final List lostItems; + private final boolean hasAlwaysLost; + } + // Item Container helpers private static final int MAX_ROW_ITEMS = 8; private static final int ITEM_X_OFFSET = 5; @@ -98,9 +113,12 @@ public class ItemsKeptOnDeathPlugin extends Plugin private WidgetButton deepWildyButton; private WidgetButton lowWildyButton; - private boolean isSkulled; - private boolean protectingItem; - private int wildyLevel; + @VisibleForTesting + boolean isSkulled; + @VisibleForTesting + boolean protectingItem; + @VisibleForTesting + int wildyLevel; @Subscribe public void onScriptCallbackEvent(ScriptCallbackEvent event) @@ -223,97 +241,12 @@ public class ItemsKeptOnDeathPlugin extends Plugin final ItemContainer equipment = client.getItemContainer(InventoryID.EQUIPMENT); final Item[] equip = equipment == null ? new Item[0] : equipment.getItems(); - final List items = new ArrayList<>(); - Collections.addAll(items, inv); - Collections.addAll(items, equip); + final DeathItems deathItems = calculateKeptLostItems(inv, equip); - // Sort by item price - items.sort(Comparator.comparing(this::getDeathPrice).reversed()); - - boolean hasAlwaysLost = false; - int keepCount = getDefaultItemsKept(); - - final List keptItems = new ArrayList<>(); - final List lostItems = new ArrayList<>(); - for (final Item i : items) - { - final int id = i.getId(); - int itemQuantity = i.getQuantity(); - - if (id == -1) - { - continue; - } - - final ItemComposition c = itemManager.getItemComposition(i.getId()); - - // Bonds are always kept and do not count towards the limit. - if (id == ItemID.OLD_SCHOOL_BOND || id == ItemID.OLD_SCHOOL_BOND_UNTRADEABLE) - { - final Widget itemWidget = createItemWidget(kept, itemQuantity, c); - itemWidget.setOnOpListener(ScriptID.DEATH_KEEP_ITEM_EXAMINE, 1, itemQuantity, c.getName()); - keptItems.add(itemWidget); - continue; - } - - // Certain items are always lost on death and have a white outline which we need to add - final AlwaysLostItem alwaysLostItem = AlwaysLostItem.getByItemID(i.getId()); - if (alwaysLostItem != null) - { - // Some of these items are kept on death (outside wildy), like the Rune pouch. Ignore them - if (!alwaysLostItem.isKeptOutsideOfWilderness() || wildyLevel > 0) - { - final Widget itemWidget = createItemWidget(lost, itemQuantity, c); - itemWidget.setOnOpListener(ScriptID.DEATH_KEEP_ITEM_EXAMINE, 0, itemQuantity, c.getName()); - itemWidget.setBorderType(2); // white outline - lostItems.add(itemWidget); - hasAlwaysLost = true; - continue; - } - // the rune pouch is "always lost" but its kept outside of pvp, and does not count towards your keep count - } - else if (keepCount > 0) - { - // Keep most valuable items regardless of trade-ability. - if (i.getQuantity() > keepCount) - { - final Widget itemWidget = createItemWidget(kept, keepCount, c); - itemWidget.setOnOpListener(ScriptID.DEATH_KEEP_ITEM_EXAMINE, 1, keepCount, c.getName()); - keptItems.add(itemWidget); - itemQuantity -= keepCount; - keepCount = 0; - // Fall through to below to drop the rest of the stack - } - else - { - final Widget itemWidget = createItemWidget(kept, itemQuantity, c); - itemWidget.setOnOpListener(ScriptID.DEATH_KEEP_ITEM_EXAMINE, 1, itemQuantity, c.getName()); - keptItems.add(itemWidget); - keepCount -= i.getQuantity(); - continue; - } - } - - // Items are kept if: - // 1) is not tradeable - // 2) is under the deep wilderness line - // 3) is outside of the wilderness, or item has a broken form - if (!Pets.isPet(id) - && !isTradeable(c) && wildyLevel <= DEEP_WILDY - && (wildyLevel <= 0 || BrokenOnDeathItem.isBrokenOnDeath(i.getId()))) - { - final Widget itemWidget = createItemWidget(kept, itemQuantity, c); - itemWidget.setOnOpListener(ScriptID.DEATH_KEEP_ITEM_EXAMINE, 1, itemQuantity, c.getName()); - keptItems.add(itemWidget); - } - else - { - // Otherwise, the item is lost - final Widget itemWidget = createItemWidget(lost, itemQuantity, c); - itemWidget.setOnOpListener(ScriptID.DEATH_KEEP_ITEM_EXAMINE, 0, itemQuantity, c.getName()); - lostItems.add(itemWidget); - } - } + final List keptItems = deathItems.getKeptItems().stream() + .map(item -> createItemWidget(kept, item, true)).collect(Collectors.toList()); + final List lostItems = deathItems.getLostItems().stream() + .map(item -> createItemWidget(lost, item, false)).collect(Collectors.toList()); int rows = (keptItems.size() + MAX_ROW_ITEMS - 1) / MAX_ROW_ITEMS; // Show an empty row if there isn't anything @@ -328,36 +261,205 @@ public class ItemsKeptOnDeathPlugin extends Plugin positionWidgetItems(kept, keptItems); positionWidgetItems(lost, lostItems); - updateKeptWidgetInfoText(hasAlwaysLost, keptItems, lostItems); + updateKeptWidgetInfoText(deathItems.isHasAlwaysLost(), keptItems, lostItems); + } + + /** + * Calculates which items will be kept/lost. first list is kept items, second is lost. + * + * @param inv players inventory + * @param equip players equipement + * @return list of items kept followed by a list of items lost + */ + @VisibleForTesting + DeathItems calculateKeptLostItems(final Item[] inv, final Item[] equip) + { + final List items = new ArrayList<>(); + Collections.addAll(items, inv); + Collections.addAll(items, equip); + + // Sort by item price + items.sort(Comparator.comparing(this::getDeathPrice).reversed()); + + boolean hasClueBox = false; + boolean hasAlwaysLost = false; + int keepCount = getDefaultItemsKept(); + + final List keptItems = new ArrayList<>(); + final List lostItems = new ArrayList<>(); + + for (final Item i : items) + { + final int id = i.getId(); + int qty = i.getQuantity(); + if (id == -1) + { + continue; + } + + // Bonds are always kept and do not count towards the limit. + if (id == ItemID.OLD_SCHOOL_BOND || id == ItemID.OLD_SCHOOL_BOND_UNTRADEABLE) + { + keptItems.add(new ItemStack(id, qty)); + continue; + } + + final AlwaysLostItem alwaysLostItem = AlwaysLostItem.getByItemID(id); + if (alwaysLostItem != null && (!alwaysLostItem.isKeptOutsideOfWilderness() || wildyLevel > 0)) + { + hasAlwaysLost = true; + hasClueBox = hasClueBox || id == ItemID.CLUE_BOX; + lostItems.add(new ItemStack(id, qty)); + continue; + } + + if (keepCount > 0) + { + // Keep most valuable items regardless of trade-ability. + if (i.getQuantity() > keepCount) + { + keptItems.add(new ItemStack(id, keepCount)); + qty -= keepCount; + keepCount = 0; + // Fall through to determine if the rest of the stack should drop + } + else + { + keptItems.add(new ItemStack(id, qty)); + keepCount -= qty; + continue; + } + } + + // Items are kept if: + // 1) is not tradeable + // 2) is under the deep wilderness line + // 3) is outside of the wilderness, or item has a broken form + if (!Pets.isPet(id) + && !LostIfNotProtected.isLostIfNotProtected(id) + && !isTradeable(itemManager.getItemComposition(id)) && wildyLevel <= DEEP_WILDY + && (wildyLevel <= 0 || BrokenOnDeathItem.isBrokenOnDeath(i.getId()))) + { + keptItems.add(new ItemStack(id, qty)); + } + else + { + // Otherwise, the item is lost + lostItems.add(new ItemStack(id, qty)); + } + } + + if (hasClueBox) + { + boolean alreadyProtectingClue = false; + for (final ItemStack item : keptItems) + { + if (isClueBoxable(item.getId())) + { + alreadyProtectingClue = true; + break; + } + } + + if (!alreadyProtectingClue) + { + int clueId = -1; + // Clue box protects the last clue in your inventory so loop over the players inv + for (final Item i : inv) + { + final int id = i.getId(); + if (id != -1 && isClueBoxable(id)) + { + clueId = id; + } + } + + if (clueId != -1) + { + // Move the boxed item to the kept items container and remove it from the lost items container + for (final ItemStack boxableItem : lostItems) + { + if (boxableItem.getId() == clueId) + { + if (boxableItem.getQty() > 1) + { + boxableItem.setQty(boxableItem.getQty() - 1); + keptItems.add(new ItemStack(clueId, 1)); + } + else + { + lostItems.remove(boxableItem); + keptItems.add(boxableItem); + } + break; + } + } + } + } + } + + return new DeathItems(keptItems, lostItems, hasAlwaysLost); + } + + @VisibleForTesting + boolean isClueBoxable(final int itemID) + { + final String name = itemManager.getItemComposition(itemID).getName(); + return name.contains("Clue scroll (") || name.contains("Reward casket ("); } /** * Get the price of an item + * * @param item * @return */ - private int getDeathPrice(Item item) + @VisibleForTesting + int getDeathPrice(Item item) { + // 1) Check if the death price is dynamically calculated, if so return that value + // 2) If death price is based off another item default to that price, otherwise apply normal ItemMapping GE price + // 3) If still no price, default to store price + // 4) Apply fixed price offset if applicable + int itemId = item.getId(); // Unnote/unplaceholder item int canonicalizedItemId = itemManager.canonicalize(itemId); - int exchangePrice = itemManager.getItemPrice(canonicalizedItemId); + int exchangePrice = 0; + + final DynamicPriceItem dynamicPrice = DynamicPriceItem.find(canonicalizedItemId); + if (dynamicPrice != null) + { + final int basePrice = itemManager.getItemPrice(dynamicPrice.getChargedId(), true); + return dynamicPrice.calculateDeathPrice(basePrice); + } + + // Some items have artificially offset death prices - such as ring imbues + // which are +2k over the non imbues. Check if the item has a fixed price offset + final FixedPriceItem fixedPrice = FixedPriceItem.find(canonicalizedItemId); + if (fixedPrice != null && fixedPrice.getBaseId() != -1) + { + // Grab base item price + exchangePrice = itemManager.getItemPrice(fixedPrice.getBaseId(), true); + } + else + { + // Account for items whose death value comes from their tradeable variant (barrows) or components (ornate kits) + for (final int mappedID : ItemMapping.map(canonicalizedItemId)) + { + exchangePrice += itemManager.getItemPrice(mappedID, true); + } + } + if (exchangePrice == 0) { final ItemComposition c1 = itemManager.getItemComposition(canonicalizedItemId); exchangePrice = c1.getPrice(); } - else - { - // Some items have artifically applied death prices - such as ring imbues - // which are +2k over the non imbues. Check if the item has a fixed price. - FixedPriceItem fixedPrice = FixedPriceItem.find(canonicalizedItemId); - if (fixedPrice != null) - { - // Apply fixed price offset - exchangePrice += fixedPrice.getOffset(); - } - } + + // Apply fixed price offset + exchangePrice += fixedPrice == null ? 0 : fixedPrice.getOffset(); + return exchangePrice; } @@ -589,21 +691,29 @@ public class ItemsKeptOnDeathPlugin extends Plugin /** * Creates an Item Widget for use inside the Kept on Death Interface * - * @param qty Amount of item - * @param c Items Composition - * @return + * @param parent Widget to add element too as a child + * @param item the TempItem representing the item + * @param kept is the item being shown in the kept items container + * @return the Widget that was added to the `parent` */ - private static Widget createItemWidget(final Widget parent, final int qty, final ItemComposition c) + private Widget createItemWidget(final Widget parent, final ItemStack item, boolean kept) { + final int id = item.getId(); + final int qty = item.getQty(); + final ItemComposition c = itemManager.getItemComposition(id); + final Widget itemWidget = parent.createChild(-1, WidgetType.GRAPHIC); - itemWidget.setItemId(c.getId()); - itemWidget.setItemQuantity(qty); - itemWidget.setHasListener(true); itemWidget.setOriginalWidth(Constants.ITEM_SPRITE_WIDTH); itemWidget.setOriginalHeight(Constants.ITEM_SPRITE_HEIGHT); - itemWidget.setBorderType(1); - + itemWidget.setItemId(id); + itemWidget.setItemQuantity(qty); itemWidget.setAction(1, String.format("Item: %s", c.getName())); + itemWidget.setOnOpListener(ScriptID.DEATH_KEEP_ITEM_EXAMINE, kept ? 1 : 0, qty, c.getName()); + itemWidget.setHasListener(true); + + final AlwaysLostItem alwaysLostItem = AlwaysLostItem.getByItemID(id); + final boolean whiteBorder = alwaysLostItem != null && (!alwaysLostItem.isKeptOutsideOfWilderness() || wildyLevel > 0); + itemWidget.setBorderType(whiteBorder ? 2 : 1); return itemWidget; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/LostIfNotProtected.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/LostIfNotProtected.java new file mode 100644 index 0000000000..56a12c4b79 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/LostIfNotProtected.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019, TheStonedTurtle + * 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.itemskeptondeath; + +import com.google.common.collect.ImmutableSet; +import java.util.Set; +import net.runelite.api.ItemID; + +final class LostIfNotProtected +{ + private static final Set ITEMS = ImmutableSet.of( + ItemID.AMULET_OF_THE_DAMNED, + ItemID.RING_OF_CHAROS, ItemID.RING_OF_CHAROSA, + ItemID.LUNAR_STAFF, + ItemID.SHADOW_SWORD, + ItemID.KERIS, ItemID.KERISP, ItemID.KERISP_10583, ItemID.KERISP_10584 + ); + + public static boolean isLostIfNotProtected(int id) + { + return ITEMS.contains(id); + } +} diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPluginTest.java new file mode 100644 index 0000000000..37e8a7d99d --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPluginTest.java @@ -0,0 +1,616 @@ +/* + * Copyright (c) 2019, TheStonedTurtle + * 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.itemskeptondeath; + +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import net.runelite.api.Client; +import net.runelite.api.Item; +import net.runelite.api.ItemComposition; +import net.runelite.api.ItemID; +import net.runelite.client.game.ItemManager; +import static net.runelite.client.plugins.itemskeptondeath.ItemsKeptOnDeathPlugin.DeathItems; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ItemsKeptOnDeathPluginTest +{ + @Mock + @Bind + private Client client; + + @Mock + @Bind + private ItemManager itemManager; + + @Inject + private ItemsKeptOnDeathPlugin plugin; + + @Before + public void before() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + resetBuffs(); + } + + private void resetBuffs() + { + plugin.isSkulled = false; + plugin.protectingItem = false; + plugin.wildyLevel = -1; + } + + // Mocks an item and the necessary itemManager functions for it + private Item mItem(final int id, final int qty, final String name, final boolean tradeable, final int price) + { + // Mock Item Composition and necessary ItemManager methods for this item + ItemComposition c = mock(ItemComposition.class); + when(c.getId()) + .thenReturn(id); + when(c.getName()) + .thenReturn(name); + when(c.isTradeable()) + .thenReturn(tradeable); + when(c.getPrice()) + .thenReturn(price); + + if (!tradeable) + { + when(c.getNote()).thenReturn(-1); + when(c.getLinkedNoteId()).thenReturn(-1); + } + + when(itemManager.getItemComposition(id)).thenReturn(c); + when(itemManager.canonicalize(id)).thenReturn(id); + when(itemManager.getItemPrice(id, true)).thenReturn(price); + + return mockItem(id, qty); + } + + // Creates a mocked item + private Item mockItem(final int id, final int qty) + { + Item item = mock(Item.class); + + when(item.getId()).thenReturn(id); + when(item.getQuantity()).thenReturn(qty); + + return item; + } + + @Test + public void deathPriceTestRegularItems() + { + final Item acs = mItem(ItemID.ARMADYL_CHAINSKIRT, 1, "Armadyl chainskirt", true, 27837495); + assertEquals(27837495, plugin.getDeathPrice(acs)); + + final Item karambwan = mItem(ItemID.COOKED_KARAMBWAN, 1, "Cooked karambwan", true, 608); + assertEquals(608, plugin.getDeathPrice(karambwan)); + + final Item defender = mItem(ItemID.RUNE_DEFENDER, 1, "Rune defender", false, 35000); + assertEquals(35000, plugin.getDeathPrice(defender)); + } + + @Test + public void deathPriceTestItemMapping() + { + mItem(ItemID.OCCULT_NECKLACE, 1, "Occult necklace", true, 1000000); + mItem(ItemID.OCCULT_ORNAMENT_KIT, 1, "Occult ornament kit", true, 3000000); + final Item occult = mItem(ItemID.OCCULT_NECKLACE_OR, 1, "Occult necklace (or)", false, 0); + assertEquals(4000000, plugin.getDeathPrice(occult)); + + mItem(ItemID.BLACK_MASK, 1, "Black mask", true, 1000000); + final Item blackMask8 = mItem(ItemID.BLACK_MASK_8, 1, "Black mask (8)", false, 0); + assertEquals(1000000, plugin.getDeathPrice(blackMask8)); + final Item slayerHelm = mItem(ItemID.SLAYER_HELMET, 1, "Slayer helmet", false, 0); + assertEquals(1000000, plugin.getDeathPrice(slayerHelm)); + } + + @Test + public void deathPriceTestFixedPriceItems() + { + mItem(ItemID.KARILS_COIF_0, 1, "Karil's coif 0", true, 35000); + final Item coif = mItem(ItemID.KARILS_COIF_100, 1, "Karil's coif 100", false, 0); + final int coifOffset = FixedPriceItem.KARILS_COIF_100.getOffset(); + assertEquals(35000 + coifOffset, plugin.getDeathPrice(coif)); + + mItem(ItemID.AHRIMS_ROBETOP_0, 1, "Ahrim's robetop 0", true, 2500000); + final Item robetop = mItem(ItemID.AHRIMS_ROBETOP_25, 1, "Ahrim's robetop 100", false, 0); + final int robetopOffset = FixedPriceItem.AHRIMS_ROBETOP_25.getOffset(); + assertEquals(2500000 + robetopOffset, plugin.getDeathPrice(robetop)); + + mItem(ItemID.AMULET_OF_GLORY, 1, "Amulet of glory", true, 13000); + final Item glory = mItem(ItemID.AMULET_OF_GLORY3, 1, "Amulet of glory(3)", true, 0); + final int gloryOffset = FixedPriceItem.AMULET_OF_GLORY3.getOffset(); + assertEquals(13000 + gloryOffset, plugin.getDeathPrice(glory)); + + mItem(ItemID.COMBAT_BRACELET, 1, "Combat bracelet", true, 13500); + final Item brace = mItem(ItemID.COMBAT_BRACELET1, 1, "Combat bracelet(1)", true, 0); + final int braceletOffset = FixedPriceItem.COMBAT_BRACELET1.getOffset(); + assertEquals(13500 + braceletOffset, plugin.getDeathPrice(brace)); + } + + @Test + public void deathPriceTestDynamicPriceItems() + { + final Item rod8 = mItem(ItemID.RING_OF_DUELING8, 1, "Ring of dueling(8)", true, 725); + final Item rod3 = mItem(ItemID.RING_OF_DUELING3, 1, "Ring of dueling(3)", true, 0); + final Item rod1 = mItem(ItemID.RING_OF_DUELING1, 1, "Ring of dueling(1)", true, 0); + // Dynamic price items + final int rodPrice = 725 / 8; + assertEquals(rodPrice, plugin.getDeathPrice(rod1)); + assertEquals(725, plugin.getDeathPrice(rod8)); + assertEquals(rodPrice * 3, plugin.getDeathPrice(rod3)); + + final Item nop5 = mItem(ItemID.NECKLACE_OF_PASSAGE5, 1, "Necklace of passage(5)", true, 1250); + final Item nop4 = mItem(ItemID.NECKLACE_OF_PASSAGE4, 1, "Necklace of passage(4)", true, 0); + final Item nop2 = mItem(ItemID.NECKLACE_OF_PASSAGE2, 1, "Necklace of passage(2)", true, 0); + + final int nopPrice = 1250 / 5; + assertEquals(nopPrice * 2, plugin.getDeathPrice(nop2)); + assertEquals(nopPrice * 4, plugin.getDeathPrice(nop4)); + assertEquals(1250, plugin.getDeathPrice(nop5)); + } + + private Item[] getFourExpensiveItems() + { + return new Item[] + { + mItem(ItemID.TWISTED_BOW, 1, "Twister bow", true, Integer.MAX_VALUE), + mItem(ItemID.SCYTHE_OF_VITUR, 1, "Scythe of vitur", true, Integer.MAX_VALUE), + mItem(ItemID.ELYSIAN_SPIRIT_SHIELD, 1, "Elysian spirit shield", true, 800000000), + mItem(ItemID.ARCANE_SPIRIT_SHIELD, 1, "Arcane spirit shield", true, 250000000) + }; + } + + @Test + public void alwaysLostTestRunePouch() + { + final Item[] inv = getFourExpensiveItems(); + final Item[] equip = new Item[] + { + mItem(ItemID.RUNE_POUCH, 1, "Rune pouch", false, 1) + }; + + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + assertFalse(deathItems.isHasAlwaysLost()); + } + + @Test + public void alwaysLostTestRunePouchWildy() + { + final Item[] inv = getFourExpensiveItems(); + final Item[] equip = new Item[] + { + mItem(ItemID.RUNE_POUCH, 1, "Rune pouch", false, 1) + }; + + plugin.wildyLevel = 1; + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + assertTrue(deathItems.isHasAlwaysLost()); + } + + @Test + public void alwaysLostTestLootBag() + { + final Item[] inv = getFourExpensiveItems(); + final Item[] equip = new Item[] + { + mItem(ItemID.LOOTING_BAG, 1, "Looting bag", false, 1) + }; + + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + assertTrue(deathItems.isHasAlwaysLost()); + + } + + @Test + public void alwaysLostTestLootBagWildy() + { + final Item[] inv = getFourExpensiveItems(); + final Item[] equip = new Item[] + { + mItem(ItemID.LOOTING_BAG, 1, "Looting bag", false, 1) + }; + + plugin.wildyLevel = 1; + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + assertTrue(deathItems.isHasAlwaysLost()); + } + + private Item[] getClueBoxTestInventory() + { + return new Item[] + { + mItem(ItemID.BLACK_DHIDE_BODY, 1, "Black d'hide body", true, 7552), + mItem(ItemID.ARMADYL_CHAINSKIRT, 1, "Armadyl chainskirt", true, 27837495), + mItem(ItemID.PEGASIAN_BOOTS, 1, "Pegasian boots", true, 30542187), + mItem(ItemID.DRAGON_SCIMITAR, 1, "Dragon scimitar", true, 63123), + + mItem(ItemID.HELM_OF_NEITIZNOT, 1, "Helm of neitiznot", true, 45519), + mItem(ItemID.RUNE_DEFENDER, 1, "Rune defender", false, 35000), + mItem(ItemID.SPADE, 1, "Spade", true, 104), + mItem(ItemID.CLUE_SCROLL_EASY, 1, "Clue scroll (easy)", false, 50), + + mItem(ItemID.CLUE_BOX, 1, "Clue box", false, 50), + mItem(ItemID.COOKED_KARAMBWAN, 1, "Cooked karambwan", true, 608), + mItem(ItemID.COOKED_KARAMBWAN, 1, "Cooked karambwan", true, 608), + mItem(ItemID.COOKED_KARAMBWAN, 1, "Cooked karambwan", true, 608), + + mItem(ItemID.COOKED_KARAMBWAN, 1, "Cooked karambwan", true, 608), + mItem(ItemID.COOKED_KARAMBWAN, 1, "Cooked karambwan", true, 608), + mItem(ItemID.LAW_RUNE, 200, "Law rune", true, 212), + mItem(ItemID.DUST_RUNE, 200, "Dust rune", true, 3), + + mItem(ItemID.CLUE_SCROLL_MASTER, 1, "Clue scroll (master)", false, 50), + mItem(ItemID.CLUELESS_SCROLL, 1, "Clueless scroll", false, 50), + }; + } + + @Test + public void isClueBoxableTest() + { + getClueBoxTestInventory(); + mItem(ItemID.REWARD_CASKET_EASY, 1, "Reward casket (easy)", false, 50); + + assertTrue(plugin.isClueBoxable(ItemID.CLUE_SCROLL_EASY)); + assertTrue(plugin.isClueBoxable(ItemID.CLUE_SCROLL_MASTER)); + assertTrue(plugin.isClueBoxable(ItemID.REWARD_CASKET_EASY)); + + assertFalse(plugin.isClueBoxable(ItemID.CLUELESS_SCROLL)); + assertFalse(plugin.isClueBoxable(ItemID.LAW_RUNE)); + assertFalse(plugin.isClueBoxable(ItemID.SPADE)); + } + + @Test + public void clueBoxTestDefault() + { + final Item[] inv = getClueBoxTestInventory(); + final Item[] equip = new Item[0]; + + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + + final List kept = deathItems.getKeptItems(); + final List expectedKept = Arrays.asList( + new ItemStack(ItemID.PEGASIAN_BOOTS, 1), + new ItemStack(ItemID.ARMADYL_CHAINSKIRT, 1), + new ItemStack(ItemID.DRAGON_SCIMITAR, 1), + new ItemStack(ItemID.RUNE_DEFENDER, 1), + new ItemStack(ItemID.CLUE_SCROLL_EASY, 1), + new ItemStack(ItemID.CLUE_SCROLL_MASTER, 1), + new ItemStack(ItemID.CLUELESS_SCROLL, 1) + ); + assertEquals(expectedKept, kept); + + final List lost = deathItems.getLostItems(); + assertEquals((inv.length + equip.length) - expectedKept.size(), lost.size()); + } + + @Test + public void clueBoxTestDeepWildy() + { + final Item[] inv = getClueBoxTestInventory(); + final Item[] equip = new Item[0]; + + plugin.wildyLevel = 21; + + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + + final List kept = deathItems.getKeptItems(); + final List expectedKept = Arrays.asList( + new ItemStack(ItemID.PEGASIAN_BOOTS, 1), + new ItemStack(ItemID.ARMADYL_CHAINSKIRT, 1), + new ItemStack(ItemID.DRAGON_SCIMITAR, 1), + new ItemStack(ItemID.CLUE_SCROLL_MASTER, 1) + ); + assertEquals(expectedKept, kept); + + final List lost = deathItems.getLostItems(); + final int keptOffset = expectedKept.size(); + assertEquals((inv.length + equip.length) - keptOffset, lost.size()); + } + + @Test + public void clueBoxTestDeepWildyProtectItem() + { + final Item[] inv = getClueBoxTestInventory(); + final Item[] equip = new Item[0]; + + plugin.wildyLevel = 21; + plugin.protectingItem = true; + + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + + final List kept = deathItems.getKeptItems(); + final List expectedKept = Arrays.asList( + new ItemStack(ItemID.PEGASIAN_BOOTS, 1), + new ItemStack(ItemID.ARMADYL_CHAINSKIRT, 1), + new ItemStack(ItemID.DRAGON_SCIMITAR, 1), + new ItemStack(ItemID.HELM_OF_NEITIZNOT, 1), + new ItemStack(ItemID.CLUE_SCROLL_MASTER, 1) // Clue box + ); + assertEquals(expectedKept, kept); + + final List lost = deathItems.getLostItems(); + final int keptOffset = expectedKept.size(); + assertEquals((inv.length + equip.length) - keptOffset, lost.size()); + } + + @Test + public void clueBoxTestDeepWildySkulled() + { + final Item[] inv = getClueBoxTestInventory(); + final Item[] equip = new Item[0]; + + plugin.wildyLevel = 21; + plugin.isSkulled = true; + + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + + final List kept = deathItems.getKeptItems(); + final List expectedKept = Collections.singletonList( + new ItemStack(ItemID.CLUE_SCROLL_MASTER, 1) + ); + assertEquals(expectedKept, kept); + + final List lost = deathItems.getLostItems(); + final int keptOffset = expectedKept.size(); + assertEquals(lost.size(), (inv.length + equip.length) - keptOffset); + } + + @Test + public void clueBoxTestLowWildy() + { + final Item[] inv = getClueBoxTestInventory(); + final Item[] equip = new Item[0]; + + plugin.wildyLevel = 1; + + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + + final List kept = deathItems.getKeptItems(); + final List expectedKept = Arrays.asList( + new ItemStack(ItemID.PEGASIAN_BOOTS, 1), + new ItemStack(ItemID.ARMADYL_CHAINSKIRT, 1), + new ItemStack(ItemID.DRAGON_SCIMITAR, 1), + new ItemStack(ItemID.RUNE_DEFENDER, 1), // Rune defender protected because of broken variant + new ItemStack(ItemID.CLUE_SCROLL_MASTER, 1) + ); + assertEquals(expectedKept, kept); + + final List lost = deathItems.getLostItems(); + final int keptOffset = expectedKept.size(); + assertEquals(lost.size(), (inv.length + equip.length) - keptOffset); + } + + @Test + public void clueBoxTestLowWildyProtectItem() + { + final Item[] inv = getClueBoxTestInventory(); + final Item[] equip = new Item[0]; + + plugin.wildyLevel = 1; + plugin.protectingItem = true; + + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + + final List kept = deathItems.getKeptItems(); + final List expectedKept = Arrays.asList( + new ItemStack(ItemID.PEGASIAN_BOOTS, 1), + new ItemStack(ItemID.ARMADYL_CHAINSKIRT, 1), + new ItemStack(ItemID.DRAGON_SCIMITAR, 1), + new ItemStack(ItemID.HELM_OF_NEITIZNOT, 1), + new ItemStack(ItemID.RUNE_DEFENDER, 1), // Rune defender protected because of broken variant + new ItemStack(ItemID.CLUE_SCROLL_MASTER, 1) + ); + assertEquals(expectedKept, kept); + + final List lost = deathItems.getLostItems(); + final int keptOffset = expectedKept.size(); + assertEquals((inv.length + equip.length) - keptOffset, lost.size()); + } + + @Test + public void clueBoxTestLowWildySkulled() + { + final Item[] inv = getClueBoxTestInventory(); + final Item[] equip = new Item[0]; + + plugin.wildyLevel = 1; + plugin.isSkulled = true; + + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + + final List kept = deathItems.getKeptItems(); + final List expectedKept = Arrays.asList( + new ItemStack(ItemID.RUNE_DEFENDER, 1), // Rune defender protected because of broken variant + new ItemStack(ItemID.CLUE_SCROLL_MASTER, 1) + ); + assertEquals(expectedKept, kept); + + final List lost = deathItems.getLostItems(); + final int keptOffset = expectedKept.size(); + assertEquals((inv.length + equip.length) - keptOffset, lost.size()); + } + + private Item[] getClueBoxCasketTestInventory() + { + // Reward caskets can stack but the clue box should only protect one + return new Item[] + { + mItem(ItemID.BLACK_DHIDE_BODY, 1, "Black d'hide body", true, 7552), + mItem(ItemID.ARMADYL_CHAINSKIRT, 1, "Armadyl chainskirt", true, 27837495), + mItem(ItemID.PEGASIAN_BOOTS, 1, "Pegasian boots", true, 30542187), + mItem(ItemID.DRAGON_SCIMITAR, 1, "Dragon scimitar", true, 63123), + + mItem(ItemID.SPADE, 1, "Spade", true, 104), + mItem(ItemID.CLUE_SCROLL_EASY, 1, "Clue scroll (easy)", false, 50), + mItem(ItemID.REWARD_CASKET_EASY, 20, "Reward casket (easy)", false, 50), + mItem(ItemID.CLUE_BOX, 1, "Clue box", false, 50), + + mItem(ItemID.COOKED_KARAMBWAN, 1, "Cooked karambwan", true, 608), + mItem(ItemID.COOKED_KARAMBWAN, 1, "Cooked karambwan", true, 608), + mItem(ItemID.COOKED_KARAMBWAN, 1, "Cooked karambwan", true, 608), + mItem(ItemID.COOKED_KARAMBWAN, 1, "Cooked karambwan", true, 608), + + mItem(ItemID.LAW_RUNE, 200, "Law rune", true, 212), + mItem(ItemID.DUST_RUNE, 200, "Dust rune", true, 3), + }; + } + + @Test + public void clueBoxTestCasketProtect() + { + final Item[] inv = getClueBoxCasketTestInventory(); + final Item[] equip = new Item[0]; + + plugin.wildyLevel = 1; + + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + + final List kept = deathItems.getKeptItems(); + final List expectedKept = Arrays.asList( + new ItemStack(ItemID.PEGASIAN_BOOTS, 1), + new ItemStack(ItemID.ARMADYL_CHAINSKIRT, 1), + new ItemStack(ItemID.DRAGON_SCIMITAR, 1), + new ItemStack(ItemID.REWARD_CASKET_EASY, 1) // Clue box + ); + assertEquals(expectedKept, kept); + + final List lost = deathItems.getLostItems(); + final int keptOffset = expectedKept.size() - 1; // We are still losing some reward caskets. + assertEquals((inv.length + equip.length) - keptOffset, lost.size()); + } + + private Item[] getFullGracefulItems() + { + return new Item[] + { + mItem(ItemID.GRACEFUL_HOOD, 1, "Graceful hood", false, 35), + mItem(ItemID.GRACEFUL_CAPE, 1, "Graceful cape", false, 40), + mItem(ItemID.GRACEFUL_TOP, 1, "Graceful top", false, 55), + mItem(ItemID.GRACEFUL_LEGS, 1, "Graceful legs", false, 60), + mItem(ItemID.GRACEFUL_BOOTS, 1, "Graceful boots", false, 40), + mItem(ItemID.GRACEFUL_GLOVES, 1, "Graceful gloves", false, 30), + }; + } + + @Test + public void gracefulValueTest() + { + final Item[] inv = getFullGracefulItems(); + final Item[] equip = new Item[] + { + mItem(ItemID.AMULET_OF_GLORY6, 1, "Amulet of glory (6)", true, 20000) + }; + + + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + + final List kept = deathItems.getKeptItems(); + final List expectedKept = Arrays.asList( + new ItemStack(ItemID.AMULET_OF_GLORY6, 1), + new ItemStack(ItemID.GRACEFUL_CAPE, 1), + new ItemStack(ItemID.GRACEFUL_TOP, 1), + new ItemStack(ItemID.GRACEFUL_LEGS, 1), + new ItemStack(ItemID.GRACEFUL_BOOTS, 1), + new ItemStack(ItemID.GRACEFUL_HOOD, 1), + new ItemStack(ItemID.GRACEFUL_GLOVES, 1) + ); + assertEquals(expectedKept, kept); + + final List lost = deathItems.getLostItems(); + assertEquals((inv.length + equip.length) - expectedKept.size(), lost.size()); + } + + @Test + public void gracefulValueTestWildy() + { + final Item[] inv = getFullGracefulItems(); + final Item[] equip = new Item[] + { + mItem(ItemID.AMULET_OF_GLORY6, 1, "Amulet of glory (6)", true, 20000) + }; + + plugin.wildyLevel = 1; + + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + + final List kept = deathItems.getKeptItems(); + final List expectedKept = Arrays.asList( + new ItemStack(ItemID.AMULET_OF_GLORY6, 1), + new ItemStack(ItemID.GRACEFUL_CAPE, 1), + new ItemStack(ItemID.GRACEFUL_TOP, 1) + ); + assertEquals(expectedKept, kept); + + final List lost = deathItems.getLostItems(); + assertEquals((inv.length + equip.length) - expectedKept.size(), lost.size()); + } + + @Test + public void lostIfNotProtectedTestLost() + { + final Item[] inv = getFourExpensiveItems(); + final Item[] equip = new Item[] + { + mItem(ItemID.SHADOW_SWORD, 1, "Shadow sword", false, 1) + }; + + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + + final List lost = deathItems.getLostItems(); + assertTrue(lost.contains(new ItemStack(ItemID.SHADOW_SWORD, 1))); + } + + @Test + public void lostIfNotProtectedTestKept() + { + final Item[] inv = new Item[] + { + mItem(ItemID.SHADOW_SWORD, 1, "Shadow sword", false, 1) + }; + final Item[] equip = new Item[0]; + + final DeathItems deathItems = plugin.calculateKeptLostItems(inv, equip); + + final List kept = deathItems.getKeptItems(); + assertTrue(kept.contains(new ItemStack(ItemID.SHADOW_SWORD, 1))); + } +} From 7ba7bb64dd92d8ba57852cee5960a3c7c26ab61b Mon Sep 17 00:00:00 2001 From: Zeruth Date: Wed, 10 Jul 2019 23:25:39 -0400 Subject: [PATCH 09/10] finish bringup --- .../java/net/runelite/api/events/ItemContainerChanged.java | 2 +- .../plugins/itemskeptondeath/ItemsKeptOnDeathPlugin.java | 2 +- .../client/plugins/keyremapping/KeyRemappingListener.java | 3 +++ .../itemskeptondeath/ItemsKeptOnDeathPluginTest.java | 6 +++--- .../main/java/net/runelite/mixins/RSItemContainerMixin.java | 4 ++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/runelite-api/src/main/java/net/runelite/api/events/ItemContainerChanged.java b/runelite-api/src/main/java/net/runelite/api/events/ItemContainerChanged.java index a46692f416..1b0a80fa4e 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ItemContainerChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ItemContainerChanged.java @@ -28,7 +28,7 @@ import net.runelite.api.ItemContainer; import lombok.Value; /** - * An event called whenever the stack size of an {@link api.Item} + * An event called whenever the stack size of an {@link net.runelite.api.Item} * in an {@link ItemContainer} is modified. *

* Examples of when this event may trigger include: diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPlugin.java index 176eb708db..89115daa29 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPlugin.java @@ -706,7 +706,7 @@ public class ItemsKeptOnDeathPlugin extends Plugin { final int id = item.getId(); final int qty = item.getQty(); - final ItemDefinition c = itemManager.getItemComposition(id); + final ItemDefinition c = itemManager.getItemDefinition(id); final Widget itemWidget = parent.createChild(-1, WidgetType.GRAPHIC); itemWidget.setOriginalWidth(Constants.ITEM_SPRITE_WIDTH); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java index 1bc2141c06..185545ba27 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingListener.java @@ -45,6 +45,9 @@ class KeyRemappingListener extends MouseAdapter implements KeyListener @Inject private KeyRemappingPlugin plugin; + @Inject + private KeyRemappingConfig config; + @Inject private Client client; diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPluginTest.java index 37e8a7d99d..4025ccf1b7 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPluginTest.java @@ -33,7 +33,7 @@ import java.util.Collections; import java.util.List; import net.runelite.api.Client; import net.runelite.api.Item; -import net.runelite.api.ItemComposition; +import net.runelite.api.ItemDefinition; import net.runelite.api.ItemID; import net.runelite.client.game.ItemManager; import static net.runelite.client.plugins.itemskeptondeath.ItemsKeptOnDeathPlugin.DeathItems; @@ -80,7 +80,7 @@ public class ItemsKeptOnDeathPluginTest private Item mItem(final int id, final int qty, final String name, final boolean tradeable, final int price) { // Mock Item Composition and necessary ItemManager methods for this item - ItemComposition c = mock(ItemComposition.class); + ItemDefinition c = mock(ItemDefinition.class); when(c.getId()) .thenReturn(id); when(c.getName()) @@ -96,7 +96,7 @@ public class ItemsKeptOnDeathPluginTest when(c.getLinkedNoteId()).thenReturn(-1); } - when(itemManager.getItemComposition(id)).thenReturn(c); + when(itemManager.getItemDefinition(id)).thenReturn(c); when(itemManager.canonicalize(id)).thenReturn(id); when(itemManager.getItemPrice(id, true)).thenReturn(price); diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSItemContainerMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSItemContainerMixin.java index 74c1bf29a3..f3505f1b80 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSItemContainerMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSItemContainerMixin.java @@ -64,7 +64,7 @@ public abstract class RSItemContainerMixin implements RSItemContainer @FieldHook("quantities") @Inject - public void stackSizesChanged(int idx) + public void stackSizesChanged(int containerID) { int cycle = client.getGameCycle(); if (rl$lastCycle == cycle) @@ -75,7 +75,7 @@ public abstract class RSItemContainerMixin implements RSItemContainer rl$lastCycle = cycle; - ItemContainerChanged event = new ItemContainerChanged(this); + ItemContainerChanged event = new ItemContainerChanged(containerID, this); client.getCallbacks().postDeferred(event); } From a017961e99978e01d41c7e177bd24a44a6338ff2 Mon Sep 17 00:00:00 2001 From: Zeruth Date: Thu, 11 Jul 2019 00:28:53 -0400 Subject: [PATCH 10/10] fixes ItemContainerChanged event --- .../java/net/runelite/api/InventoryID.java | 12 ++++++++++ .../runelite/mixins/RSItemContainerMixin.java | 24 ++++++++++++------- 2 files changed, 28 insertions(+), 8 deletions(-) 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 df3f995df9..ec170bbc2e 100644 --- a/runelite-api/src/main/java/net/runelite/api/InventoryID.java +++ b/runelite-api/src/main/java/net/runelite/api/InventoryID.java @@ -82,4 +82,16 @@ public enum InventoryID { return id; } + + public static InventoryID getValue(int value) + { + for (InventoryID e: InventoryID.values()) + { + if (e.id == value) + { + return e; + } + } + return null; + } } \ No newline at end of file diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSItemContainerMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSItemContainerMixin.java index f3505f1b80..25ddd7e681 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSItemContainerMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSItemContainerMixin.java @@ -24,11 +24,13 @@ */ package net.runelite.mixins; +import net.runelite.api.InventoryID; import net.runelite.api.Item; import net.runelite.api.events.ItemContainerChanged; -import net.runelite.api.mixins.FieldHook; +import net.runelite.api.mixins.Copy; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; +import net.runelite.api.mixins.Replace; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSGroundItem; @@ -41,7 +43,7 @@ public abstract class RSItemContainerMixin implements RSItemContainer private static RSClient client; @Inject - private int rl$lastCycle; + static private int rl$lastCycle; @Inject @Override @@ -62,21 +64,27 @@ public abstract class RSItemContainerMixin implements RSItemContainer return items; } - @FieldHook("quantities") - @Inject - public void stackSizesChanged(int containerID) + @Copy("itemContainerSetItem") + static void rs$itemContainerSetItem(int itemContainerId, int index, int itemId, int itemQuantity) { + + } + + @Replace("itemContainerSetItem") + static void rl$itemContainerSetItem(int itemContainerId, int index, int itemId, int itemQuantity) + { + rs$itemContainerSetItem(itemContainerId, index, itemId, itemQuantity); + int cycle = client.getGameCycle(); if (rl$lastCycle == cycle) { // Limit item container updates to one per cycle + // No need to repeatedly update. The game just needs to know that containers changed once per cycle return; } rl$lastCycle = cycle; - - ItemContainerChanged event = new ItemContainerChanged(containerID, this); + ItemContainerChanged event = new ItemContainerChanged(itemContainerId, client.getItemContainer(InventoryID.getValue(itemContainerId))); client.getCallbacks().postDeferred(event); } - }