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))); + } +}