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 07a2f68ce6..ad913a88ce 100644
--- a/runelite-api/src/main/java/net/runelite/api/AnimationID.java
+++ b/runelite-api/src/main/java/net/runelite/api/AnimationID.java
@@ -197,6 +197,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-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-api/src/main/java/net/runelite/api/events/ItemContainerChanged.java b/runelite-api/src/main/java/net/runelite/api/events/ItemContainerChanged.java
index ec0ff6627b..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:
@@ -41,6 +41,11 @@ import lombok.Value;
@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/game/ItemManager.java b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java
index 4a77eff91b..f06de90882 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
@@ -385,6 +385,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)
{
@@ -395,10 +407,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/bosstimer/Boss.java b/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java
index f315956a1b..46e52bdf9e 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;
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 d9e97c441a..8a24ade869 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
@@ -133,6 +133,13 @@ public class ChatFilterPlugin extends Plugin
case FRIENDSCHAT:
case GAMEMESSAGE:
break;
+ case LOGINLOGOUTNOTIFICATION:
+ if (config.filterLogin())
+ {
+ // Block the message
+ intStack[intStackSize - 3] = 0;
+ }
+ return;
default:
return;
}
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 10a72b7667..3d84a2f099 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
@@ -253,7 +253,12 @@ public class IdleNotifierPlugin extends Plugin
case USING_GILDED_ALTAR:
/* Farming */
case FARMING_MIX_ULTRACOMPOST:
- /* Misc */
+ 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:
case SAND_COLLECTION:
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 2e9ac325cc..bbb8e1b66a 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
@@ -42,7 +42,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 5afb14c931..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,8 +28,6 @@ package net.runelite.client.plugins.itemskeptondeath;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import javax.annotation.Nullable;
-import lombok.AccessLevel;
-import lombok.AllArgsConstructor;
import lombok.Getter;
import net.runelite.api.ItemID;
@@ -37,8 +35,7 @@ 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(AccessLevel.PACKAGE)
+@Getter
enum FixedPriceItem
{
IMBUED_BLACK_MASK_I(ItemID.BLACK_MASK_I, 5000),
@@ -67,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;
@@ -84,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 f6d817b54c..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
@@ -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,8 +33,11 @@ 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 javax.inject.Singleton;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.Constants;
@@ -55,6 +59,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;
@@ -71,6 +76,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;
@@ -100,9 +115,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)
@@ -225,97 +243,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 ItemDefinition c = itemManager.getItemDefinition(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
@@ -330,36 +263,209 @@ 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;
+ }
+
+ final ItemDefinition c = itemManager.getItemDefinition(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)
+ {
+ 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.getItemDefinition(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.getItemDefinition(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 ItemDefinition c1 = itemManager.getItemDefinition(canonicalizedItemId);
+ exchangePrice = c1.getPrice();
+ 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 ItemDefinition c1 = itemManager.getItemDefinition(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;
}
@@ -591,21 +697,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 ItemDefinition c)
+ private Widget createItemWidget(final Widget parent, final ItemStack item, boolean kept)
{
+ final int id = item.getId();
+ final int qty = item.getQty();
+ final ItemDefinition c = itemManager.getItemDefinition(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/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingConfig.java
index cc14b5ff67..e025cd6fad 100644
--- 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
@@ -103,10 +103,153 @@ 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);
+ }
+
+ @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 d4a493769f..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
@@ -35,30 +35,19 @@ 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;
@Singleton
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;
+ @Inject
+ private KeyRemappingConfig config;
+
@Inject
private Client client;
@@ -111,66 +100,71 @@ class KeyRemappingListener extends MouseAdapter implements KeyListener
// to select options
if (plugin.isFkeyRemap() && !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);
}
+ else if (config.esc().matches(e))
+ {
+ modified.put(e.getKeyCode(), KeyEvent.VK_ESCAPE);
+ e.setKeyCode(KeyEvent.VK_ESCAPE);
+ }
}
switch (e.getKeyCode())
@@ -189,8 +183,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;
@@ -240,54 +238,58 @@ class KeyRemappingListener extends MouseAdapter implements KeyListener
if (plugin.isFkeyRemap())
{
- 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);
}
+ else if (config.esc().matches(e))
+ {
+ e.setKeyCode(KeyEvent.VK_ESCAPE);
+ }
}
}
else
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/itemskeptondeath/ItemsKeptOnDeathPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/itemskeptondeath/ItemsKeptOnDeathPluginTest.java
new file mode 100644
index 0000000000..4025ccf1b7
--- /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.ItemDefinition;
+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
+ ItemDefinition c = mock(ItemDefinition.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.getItemDefinition(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)));
+ }
+}
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 7f4c3bcbb0..d14e3ae301 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
@@ -162,7 +162,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);
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..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 idx)
+ @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(this);
+ ItemContainerChanged event = new ItemContainerChanged(itemContainerId, client.getItemContainer(InventoryID.getValue(itemContainerId)));
client.getCallbacks().postDeferred(event);
}
-
}