diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java
index 17dd82f7bc..57d0c92600 100644
--- a/runelite-api/src/main/java/net/runelite/api/Client.java
+++ b/runelite-api/src/main/java/net/runelite/api/Client.java
@@ -491,6 +491,12 @@ public interface Client extends GameEngine
*/
int[] getWidgetPositionsY();
+ /**
+ * Creates a new widget element
+ * @return
+ */
+ Widget createWidget();
+
/**
* Gets the current run energy of the logged in player.
*
diff --git a/runelite-api/src/main/java/net/runelite/api/ScriptID.java b/runelite-api/src/main/java/net/runelite/api/ScriptID.java
index 68abc5e0f7..f88cba4e3f 100644
--- a/runelite-api/src/main/java/net/runelite/api/ScriptID.java
+++ b/runelite-api/src/main/java/net/runelite/api/ScriptID.java
@@ -95,6 +95,16 @@ public final class ScriptID
*/
public static final int CHAT_PROMPT_INIT = 223;
+ /**
+ * Displays the game messages when clicking on an item inside the Items Kept on Death interface
+ *
+ * - int (boolean) Item kept on death
+ * - int Item Quantity
+ * - String Item Name
+ *
+ */
+ public static final int KEPT_LOST_ITEM_EXAMINE = 1603;
+
/**
* Queries the completion state of a quest by its struct id
*
diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java b/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java
index ed4277a2a3..41b843f417 100644
--- a/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java
+++ b/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java
@@ -821,4 +821,24 @@ public interface Widget
* Can widgets under this widgets be scrolled in this widgets bounding box
*/
void setNoScrollThrough(boolean noScrollThrough);
+
+ /**
+ * Changes the parent ID for the widget
+ */
+ void setParentId(int id);
+
+ /**
+ * Changes the ID of the widget
+ */
+ void setId(int id);
+
+ /**
+ * Sets the index of this element
+ */
+ void setIndex(int index);
+
+ /**
+ * Seems like this needs to set to true when creating new widgets
+ */
+ void setIsIf3(boolean isIf3);
}
diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java
index b532865c86..ba9e8ea8cc 100644
--- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java
+++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java
@@ -134,6 +134,7 @@ public class WidgetID
public static final int MUSIC_GROUP_ID = 239;
public static final int MUSICTAB_GROUP_ID = 239;
public static final int BARROWS_PUZZLE_GROUP_ID = 25;
+ public static final int ITEMS_KEPT_ON_DEATH_GROUP_ID = 4;
static class WorldMap
{
@@ -382,6 +383,7 @@ public class WidgetID
static class ResizableViewport
{
+ static final int ITEMS_KEPT_ON_DEATH = 13;
static final int CLAN_CHAT_TAB = 35;
static final int FRIENDS_TAB = 37;
static final int IGNORES_TAB = 36;
@@ -987,4 +989,16 @@ static final int WIND_STRIKE = 5;
static final int ANSWER3_CONTAINER = 16;
static final int ANSWER3 = 17;
}
+
+ static class KeptOnDeath
+ {
+ static final int KEPT_ITEMS_CONTAINER = 18;
+ static final int LOST_ITEMS_CONTAINER = 21;
+ static final int LOST_ITEMS_VALUE = 23;
+ static final int INFORMATION_CONTAINER = 29;
+ static final int MAX_ITEMS_KEPT_ON_DEATH = 30;
+ static final int SAFE_ZONE_CONTAINER = 31;
+
+ static final int CUSTOM_TEXT_CONTAINER = 33;
+ }
}
diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java
index 16b2002bc2..579007c2f4 100644
--- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java
+++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java
@@ -251,6 +251,7 @@ public enum WidgetInfo
RESIZABLE_VIEWPORT_OPTIONS_ICON(WidgetID.RESIZABLE_VIEWPORT_OLD_SCHOOL_BOX_GROUP_ID, WidgetID.ResizableViewport.OPTIONS_ICON),
RESIZABLE_VIEWPORT_EMOTES_ICON(WidgetID.RESIZABLE_VIEWPORT_OLD_SCHOOL_BOX_GROUP_ID, WidgetID.ResizableViewport.EMOTES_ICON),
RESIZABLE_VIEWPORT_MUSIC_ICON(WidgetID.RESIZABLE_VIEWPORT_OLD_SCHOOL_BOX_GROUP_ID, WidgetID.ResizableViewport.MUSIC_ICON),
+ RESIZABLE_VIEWPORT_KEPT_ON_DEATH(WidgetID.RESIZABLE_VIEWPORT_OLD_SCHOOL_BOX_GROUP_ID, WidgetID.ResizableViewport.ITEMS_KEPT_ON_DEATH),
RESIZABLE_VIEWPORT_BOTTOM_LINE(WidgetID.RESIZABLE_VIEWPORT_BOTTOM_LINE_GROUP_ID, WidgetID.Viewport.RESIZABLE_VIEWPORT_BOTTOM_LINE),
RESIZABLE_VIEWPORT_BOTTOM_LINE_LOGOUT_BUTTON(WidgetID.RESIZABLE_VIEWPORT_BOTTOM_LINE_GROUP_ID, WidgetID.ResizableViewportBottomLine.LOGOUT_BUTTON_OVERLAY),
@@ -562,7 +563,15 @@ public enum WidgetInfo
EQUIPMENT_MELEE_STRENGTH(WidgetID.EQUIPMENT_PAGE_GROUP_ID, WidgetID.EquipmentWidgetIdentifiers.MELEE_STRENGTH),
EQUIPMENT_RANGED_STRENGTH(WidgetID.EQUIPMENT_PAGE_GROUP_ID, WidgetID.EquipmentWidgetIdentifiers.RANGED_STRENGTH),
EQUIPMENT_MAGIC_DAMAGE(WidgetID.EQUIPMENT_PAGE_GROUP_ID, WidgetID.EquipmentWidgetIdentifiers.MAGIC_DAMAGE),
- EQUIP_YOUR_CHARACTER(WidgetID.EQUIPMENT_PAGE_GROUP_ID, WidgetID.EquipmentWidgetIdentifiers.EQUIP_YOUR_CHARACTER);
+ EQUIP_YOUR_CHARACTER(WidgetID.EQUIPMENT_PAGE_GROUP_ID, WidgetID.EquipmentWidgetIdentifiers.EQUIP_YOUR_CHARACTER),
+
+ ITEMS_KEPT_ON_DEATH_CONTAINER(WidgetID.ITEMS_KEPT_ON_DEATH_GROUP_ID, WidgetID.KeptOnDeath.KEPT_ITEMS_CONTAINER),
+ ITEMS_LOST_ON_DEATH_CONTAINER(WidgetID.ITEMS_KEPT_ON_DEATH_GROUP_ID, WidgetID.KeptOnDeath.LOST_ITEMS_CONTAINER),
+ ITEMS_KEPT_INFORMATION_CONTAINER(WidgetID.ITEMS_KEPT_ON_DEATH_GROUP_ID, WidgetID.KeptOnDeath.INFORMATION_CONTAINER),
+ ITEMS_KEPT_SAFE_ZONE_CONTAINER(WidgetID.ITEMS_KEPT_ON_DEATH_GROUP_ID, WidgetID.KeptOnDeath.SAFE_ZONE_CONTAINER),
+ ITEMS_KEPT_CUSTOM_TEXT_CONTAINER(WidgetID.ITEMS_KEPT_ON_DEATH_GROUP_ID, WidgetID.KeptOnDeath.CUSTOM_TEXT_CONTAINER),
+ ITEMS_LOST_VALUE(WidgetID.ITEMS_KEPT_ON_DEATH_GROUP_ID, WidgetID.KeptOnDeath.LOST_ITEMS_VALUE),
+ ITEMS_KEPT_MAX(WidgetID.ITEMS_KEPT_ON_DEATH_GROUP_ID, WidgetID.KeptOnDeath.MAX_ITEMS_KEPT_ON_DEATH);
private final int groupId;
private final int childId;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/ActuallyTradeableItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/ActuallyTradeableItem.java
new file mode 100644
index 0000000000..8f9dddd40b
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/ActuallyTradeableItem.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018, 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.keptondeath;
+
+import java.util.HashSet;
+import static net.runelite.api.ItemID.*;
+
+/**
+ * Certain items aren't tradeable via the GE but can be traded between players.
+ * The {@link net.runelite.api.ItemComposition}'s `isTradeable` value is based on GE trade-ability so we need
+ * to account for these items. These items should only be kept if protected based on item value.
+ */
+public enum ActuallyTradeableItem
+{
+ // Item Packs
+ RUNE_PACKS(AIR_RUNE_PACK, WATER_RUNE_PACK, EARTH_RUNE_PACK, FIRE_RUNE_PACK, CHAOS_RUNE_PACK, MIND_RUNE_PACK),
+ TZHAAR_PACKS(TZHAAR_AIR_RUNE_PACK, TZHAAR_WATER_RUNE_PACK, TZHAAR_EARTH_RUNE_PACK, TZHAAR_FIRE_RUNE_PACK),
+ OTHER_PACKS(BASKET_PACK, FEATHER_PACK, PLANT_POT_PACK, SACK_PACK, UNFINISHED_BROAD_BOLT_PACK),
+ // Equipment
+ BLACK_MASK(BLACK_MASK_1, BLACK_MASK_2, BLACK_MASK_3, BLACK_MASK_4, BLACK_MASK_5, BLACK_MASK_6, BLACK_MASK_7, BLACK_MASK_8, BLACK_MASK_9),
+ SATCHELS(BLACK_SATCHEL, GOLD_SATCHEL, GREEN_SATCHEL, PLAIN_SATCHEL, RED_SATCHEL, RUNE_SATCHEL),
+ FIRE_ARROWS(BRONZE_FIRE_ARROWS, IRON_FIRE_ARROWS, STEEL_FIRE_ARROWS, MITHRIL_FIRE_ARROWS, ADAMANT_FIRE_ARROWS, RUNE_FIRE_ARROWS, AMETHYST_FIRE_ARROWS, DRAGON_FIRE_ARROWS),
+ FIRE_ARROWS_2(BRONZE_FIRE_ARROWS_942, IRON_FIRE_ARROWS_2533, STEEL_FIRE_ARROWS_2535, MITHRIL_FIRE_ARROWS_2537, ADAMANT_FIRE_ARROWS_2539, RUNE_FIRE_ARROWS_2541, AMETHYST_FIRE_ARROWS_21330, DRAGON_FIRE_ARROWS_11222),
+ // Food Items
+ HALF_A(HALF_A_GARDEN_PIE, HALF_A_MEAT_PIE, HALF_A_MUSHROOM_PIE, HALF_A_REDBERRY_PIE, HALF_A_BOTANICAL_PIE, HALF_A_FISH_PIE, HALF_A_SUMMER_PIE, HALF_A_WILD_PIE, HALF_AN_ADMIRAL_PIE, HALF_AN_APPLE_PIE),
+ PIZZA(_12_ANCHOVY_PIZZA, _12_MEAT_PIZZA, _12_PINEAPPLE_PIZZA, _12_PLAIN_PIZZA),
+ CAKES(CAKE, _23_CAKE, SLICE_OF_CAKE, CHOCOLATE_CAKE, _23_CHOCOLATE_CAKE, CHOCOLATE_SLICE),
+ BASKETS(APPLES1, APPLES2, APPLES3, APPLES4, BANANAS1, BANANAS2, BANANAS3, BANANAS4, ORANGES1, ORANGES2, ORANGES3, ORANGES4, STRAWBERRIES1, STRAWBERRIES2, STRAWBERRIES3, STRAWBERRIES4, TOMATOES1, TOMATOES2, TOMATOES3, TOMATOES4),
+ // Charged Jewelery
+ BURNING_AMULET(BURNING_AMULET1, BURNING_AMULET2, BURNING_AMULET3, BURNING_AMULET4),
+ NECKLACE_OF_PASSAGE(NECKLACE_OF_PASSAGE1, NECKLACE_OF_PASSAGE2, NECKLACE_OF_PASSAGE3, NECKLACE_OF_PASSAGE4),
+ RING_OF_DUELING(RING_OF_DUELING1, RING_OF_DUELING2, RING_OF_DUELING3, RING_OF_DUELING4, RING_OF_DUELING5, RING_OF_DUELING6, RING_OF_DUELING7),
+ GAMES_NECKLACE(GAMES_NECKLACE1, GAMES_NECKLACE2, GAMES_NECKLACE3, GAMES_NECKLACE4, GAMES_NECKLACE5, GAMES_NECKLACE6, GAMES_NECKLACE7),
+ COMBAT_BRACELET(COMBAT_BRACELET1, COMBAT_BRACELET2, COMBAT_BRACELET3, COMBAT_BRACELET5),
+ RING_OF_WEALTH(RING_OF_WEALTH_I, RING_OF_WEALTH_1, RING_OF_WEALTH_I1, RING_OF_WEALTH_2, RING_OF_WEALTH_I2, RING_OF_WEALTH_3, RING_OF_WEALTH_I3, RING_OF_WEALTH_4, RING_OF_WEALTH_I4, RING_OF_WEALTH_I5),
+ AMULET_OF_GLORY(AMULET_OF_GLORY1, AMULET_OF_GLORY2, AMULET_OF_GLORY3, AMULET_OF_GLORY5),
+ AMULET_OF_GLORY_T(AMULET_OF_GLORY_T1, AMULET_OF_GLORY_T2, AMULET_OF_GLORY_T3, AMULET_OF_GLORY_T5),
+ SKILLS_NECKLACE(SKILLS_NECKLACE1, SKILLS_NECKLACE2, SKILLS_NECKLACE3, SKILLS_NECKLACE5),
+ ;
+
+ private final int[] ids;
+
+ private static final HashSet ID_SET;
+ static
+ {
+ ID_SET = new HashSet<>();
+ for (ActuallyTradeableItem p : values())
+ {
+ for (int id : p.ids)
+ {
+ ID_SET.add(id);
+ }
+ }
+ }
+
+ ActuallyTradeableItem(int... ids)
+ {
+ this.ids = ids;
+ }
+
+ public static Boolean check(int id)
+ {
+ return ID_SET.contains(id);
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/AlwaysLostItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/AlwaysLostItem.java
new file mode 100644
index 0000000000..02a8e26df0
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/AlwaysLostItem.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2018, 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.keptondeath;
+
+import com.google.common.collect.ImmutableMap;
+import lombok.Getter;
+import net.runelite.api.ItemID;
+
+/**
+ * Certain Items receive a white outline by Jagex as they are always lost on death. This is sometimes incorrectly
+ * added to Items by Jagex as the item is actually kept in non-pvp areas of the game, such as the Rune Pouch.
+ *
+ * The white outline will be added to these items when they are lost on death.
+ */
+public enum AlwaysLostItem
+{
+ RUNE_POUCH(ItemID.RUNE_POUCH, true),
+ LOOTING_BAG(ItemID.LOOTING_BAG, false),
+ LOOTING_BAG_22586(ItemID.LOOTING_BAG_22586, false),
+ CLUE_BOX(ItemID.CLUE_BOX, false);
+
+ private final int itemID;
+ @Getter
+ private final boolean kept;
+
+ private static final ImmutableMap ID_MAP;
+ static
+ {
+ ImmutableMap.Builder map = ImmutableMap.builder();
+ for (AlwaysLostItem p : values())
+ {
+ map.put(p.itemID, p);
+ }
+ ID_MAP = map.build();
+ }
+
+ AlwaysLostItem(int itemID, boolean kept)
+ {
+ this.itemID = itemID;
+ this.kept = kept;
+ }
+
+ public static AlwaysLostItem getByItemID(int itemID)
+ {
+ return ID_MAP.get(itemID);
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/BrokenOnDeathItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/BrokenOnDeathItem.java
new file mode 100644
index 0000000000..bcb629f89e
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/BrokenOnDeathItem.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2018, 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.keptondeath;
+
+import java.util.HashSet;
+import net.runelite.api.ItemID;
+
+/**
+ * Some non tradeable items are kept on death inside low level wilderness (1-20) but are turned into a broken variant.
+ *
+ * The non-broken variant will be shown inside the interface.
+ */
+public enum BrokenOnDeathItem
+{
+ // Capes
+ FIRE_CAPE(ItemID.FIRE_CAPE),
+ FIRE_MAX_CAPE(ItemID.FIRE_MAX_CAPE),
+ INFERNAL_CAPE(ItemID.INFERNAL_CAPE),
+ INFERNAL_MAX_CAPE(ItemID.INFERNAL_MAX_CAPE),
+ AVAS_ASSEMBLER(ItemID.AVAS_ASSEMBLER),
+ ASSEMBLER_MAX_CAPE(ItemID.ASSEMBLER_MAX_CAPE),
+
+ // Defenders
+ BRONZE_DEFENDER(ItemID.BRONZE_DEFENDER),
+ IRON_DEFENDER(ItemID.IRON_DEFENDER),
+ STEEL_DEFENDER(ItemID.STEEL_DEFENDER),
+ BLACK_DEFENDER(ItemID.BLACK_DEFENDER),
+ MITHRIL_DEFENDER(ItemID.MITHRIL_DEFENDER),
+ ADAMANT_DEFENDER(ItemID.ADAMANT_DEFENDER),
+ RUNE_DEFENDER(ItemID.RUNE_DEFENDER),
+ DRAGON_DEFENDER(ItemID.DRAGON_DEFENDER),
+ AVERNIC_DEFENDER(ItemID.AVERNIC_DEFENDER),
+
+ // Void
+ VOID_MAGE_HELM(ItemID.VOID_MAGE_HELM),
+ VOID_RANGER_HELM(ItemID.VOID_RANGER_HELM),
+ VOID_MELEE_HELM(ItemID.VOID_MELEE_HELM),
+ VOID_KNIGHT_TOP(ItemID.VOID_KNIGHT_TOP),
+ VOID_KNIGHT_ROBE(ItemID.VOID_KNIGHT_ROBE),
+ VOID_KNIGHT_GLOVES(ItemID.VOID_KNIGHT_GLOVES),
+ VOID_KNIGHT_MACE(ItemID.VOID_KNIGHT_MACE),
+ ELITE_VOID_TOP(ItemID.ELITE_VOID_TOP),
+ ELITE_VOID_ROBE(ItemID.ELITE_VOID_ROBE),
+
+ // Barb Assault
+ FIGHTER_HAT(ItemID.FIGHTER_HAT),
+ RANGER_HAT(ItemID.RANGER_HAT),
+ HEALER_HAT(ItemID.HEALER_HAT),
+ FIGHTER_TORSO(ItemID.FIGHTER_TORSO),
+ PENANCE_SKIRT(ItemID.PENANCE_SKIRT),
+
+ // Castle Wars
+ SARADOMIN_HALO(ItemID.SARADOMIN_HALO),
+ ZAMORAK_HALO(ItemID.ZAMORAK_HALO),
+ GUTHIX_HALO(ItemID.GUTHIX_HALO),
+ DECORATIVE_MAGIC_HAT(ItemID.DECORATIVE_ARMOUR_11898),
+ DECORATIVE_MAGIC_ROBE_TOP(ItemID.DECORATIVE_ARMOUR_11896),
+ DECORATIVE_MAGIC_ROBE_LEGS(ItemID.DECORATIVE_ARMOUR_11897),
+ DECORATIVE_RANGE_TOP(ItemID.DECORATIVE_ARMOUR_11899),
+ DECORATIVE_RANGE_BOTTOM(ItemID.DECORATIVE_ARMOUR_11900),
+ DECORATIVE_RANGE_QUIVER(ItemID.DECORATIVE_ARMOUR_11901),
+ GOLD_DECORATIVE_HELM(ItemID.DECORATIVE_HELM_4511),
+ GOLD_DECORATIVE_BODY(ItemID.DECORATIVE_ARMOUR_4509),
+ GOLD_DECORATIVE_LEGS(ItemID.DECORATIVE_ARMOUR_4510),
+ GOLD_DECORATIVE_SKIRT(ItemID.DECORATIVE_ARMOUR_11895),
+ GOLD_DECORATIVE_SHIELD(ItemID.DECORATIVE_SHIELD_4512),
+ GOLD_DECORATIVE_SWORD(ItemID.DECORATIVE_SWORD_4508);
+
+ private final int itemID;
+
+ private static final HashSet ID_SET;
+ static
+ {
+ ID_SET = new HashSet<>();
+ for (BrokenOnDeathItem p : values())
+ {
+ ID_SET.add(p.itemID);
+ }
+ }
+
+ BrokenOnDeathItem(int itemID)
+ {
+ this.itemID = itemID;
+ }
+
+ public static boolean check(int itemID)
+ {
+ return ID_SET.contains(itemID);
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/KeptOnDeathPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/KeptOnDeathPlugin.java
new file mode 100644
index 0000000000..8ec3e75da3
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/KeptOnDeathPlugin.java
@@ -0,0 +1,598 @@
+/*
+ * Copyright (c) 2018, 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.keptondeath;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import javax.inject.Inject;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.FontID;
+import net.runelite.api.InventoryID;
+import net.runelite.api.Item;
+import net.runelite.api.ItemComposition;
+import net.runelite.api.ItemContainer;
+import net.runelite.api.ItemID;
+import net.runelite.api.ScriptID;
+import net.runelite.api.SkullIcon;
+import net.runelite.api.SpriteID;
+import net.runelite.api.Varbits;
+import net.runelite.api.WorldType;
+import net.runelite.api.events.ScriptCallbackEvent;
+import net.runelite.api.vars.AccountType;
+import net.runelite.api.widgets.Widget;
+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.ItemVariationMapping;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.plugins.PluginType;
+
+@PluginDescriptor(
+ name = "Kept on Death",
+ description = "Reworks the Items Kept on Death interface to be more accurate",
+ enabledByDefault = false,
+ type = PluginType.UTILITY
+)
+@Slf4j
+public class KeptOnDeathPlugin extends Plugin
+{
+ // Handles Clicking on items in Kept on Death Interface
+ private static final int SCRIPT_ID = ScriptID.KEPT_LOST_ITEM_EXAMINE;
+ private static final double HIGH_ALCH = 0.6;
+
+ // Item Container helpers
+ private static final int MAX_ROW_ITEMS = 8;
+ private static final int STARTING_X = 5;
+ private static final int STARTING_Y = 25;
+ private static final int X_INCREMENT = 40;
+ private static final int Y_INCREMENT = 38;
+ private static final int ORIGINAL_WIDTH = 36;
+ private static final int ORIGINAL_HEIGHT = 32;
+ private static final int ORIGINAL_LOST_HEIGHT = 209;
+ private static final int ORIGINAL_LOST_Y = 107;
+
+ // Information panel text helpers
+ private static final DecimalFormat NUMBER_FORMAT = new DecimalFormat("#,###");
+ private static final String MAX_KEPT_ITEMS_FORMAT = "Max items kept on death :
~ %s ~";
+ private static final String ACTION_TEXT = "Item: %s";
+ private static final String DEFAULT = "3 items protected by default";
+ private static final String IS_SKULLED = "PK skull -3";
+ private static final String PROTECTING_ITEM = "Protect Item prayer +1";
+ private static final String ACTUAL = "Actually protecting %s items";
+ private static final String WHITE_OUTLINE = "Items with a white outline will always be lost.";
+ private static final String CHANGED_MECHANICS = "Untradeable items are kept on death in non-pvp scenarios.";
+ private static final String NON_PVP = "You will have 1 hour to retrieve your lost items.";
+ private static final String LINE_BREAK = "
";
+ private static final String UIM_DEFAULT = "You are an UIM which means 0 items are protected by default";
+ private static final int ORIGINAL_INFO_HEIGHT = 183;
+ private static final int FONT_COLOR = 0xFF981F;
+
+ // Button Names and Images
+ private static final String PROTECT_ITEM_BUTTON_NAME = "Protect Item Prayer";
+ private static final String SKULLED_BUTTON_NAME = "Skulled";
+ private static final String LOW_WILDY_BUTTON_NAME = "Low Wildy (1-20)";
+ private static final String DEEP_WILDY_BUTTON_NAME = "Deep Wildy (21+)";
+ private static final int PROTECT_ITEM_SPRITE_ID = SpriteID.PRAYER_PROTECT_ITEM;
+ private static final int SKULL_SPRITE_ID = SpriteID.PLAYER_KILLER_SKULL_523;
+ private static final int SWORD_SPRITE_ID = SpriteID.MULTI_COMBAT_ZONE_CROSSED_SWORDS;
+ private static final int SKULL_2_SPRITE_ID = SpriteID.FIGHT_PITS_WINNER_SKULL_RED;
+
+ @Inject
+ private Client client;
+
+ @Inject
+ private ItemManager itemManager;
+
+ @Getter
+ private boolean widgetVisible = false;
+
+ private LinkedHashMap buttonMap = new LinkedHashMap<>();
+ private boolean isSkulled = false;
+ private boolean protectingItem = false;
+ private boolean hasAlwaysLost = false;
+ private int wildyLevel = -1;
+
+ @Subscribe
+ protected void onScriptCallbackEvent(ScriptCallbackEvent event)
+ {
+ if (event.getEventName().equals("deathKeepBuild"))
+ {
+ // The script in charge of building the Items Kept on Death interface has finished running.
+ // Make all necessary changes now.
+
+ // Players inside Safe Areas (POH/Clan Wars) & playing DMM see the default interface
+ if (isInSafeArea() || client.getWorldType().contains(WorldType.DEADMAN))
+ {
+ return;
+ }
+
+ syncSettings();
+ createWidgetButtons();
+ recreateItemsKeptOnDeathWidget();
+ }
+ }
+
+ // Sync user settings
+ private void syncSettings()
+ {
+ SkullIcon s = client.getLocalPlayer().getSkullIcon();
+ // Ultimate iron men deaths are treated like they are always skulled
+ isSkulled = (s != null && s.equals(SkullIcon.SKULL)) || isUltimateIronman();
+ protectingItem = client.getVar(Varbits.PRAYER_PROTECT_ITEM) == 1;
+ syncCurrentWildyLevel();
+ }
+
+ private void syncCurrentWildyLevel()
+ {
+ if (client.getVar(Varbits.IN_WILDERNESS) != 1)
+ {
+ // if they are in a PvP world and not in a safe zone act like in lvl 1 wildy
+ if (isInPvpWorld() && !isInPvPSafeZone())
+ {
+ wildyLevel = 1;
+ return;
+ }
+ wildyLevel = -1;
+ return;
+ }
+
+ int y = client.getLocalPlayer().getWorldLocation().getY();
+
+ // Credits to atomicint_#5069 (Discord)
+ int underLevel = ((y - 9920) / 8) + 1;
+ int upperLevel = ((y - 3520) / 8) + 1;
+ wildyLevel = (y > 6400 ? underLevel : upperLevel);
+ }
+
+ private boolean isInPvpWorld()
+ {
+ EnumSet world = client.getWorldType();
+ return world.contains(WorldType.PVP) || world.contains(WorldType.PVP_HIGH_RISK);
+ }
+
+ private boolean isInPvPSafeZone()
+ {
+ Widget w = client.getWidget(WidgetInfo.PVP_WORLD_SAFE_ZONE);
+ return w != null && !w.isHidden();
+ }
+
+ private boolean isInSafeArea()
+ {
+ Widget w = client.getWidget(WidgetInfo.ITEMS_KEPT_SAFE_ZONE_CONTAINER);
+ return w != null && !w.isHidden();
+ }
+
+ private boolean isUltimateIronman()
+ {
+ return client.getAccountType().equals(AccountType.ULTIMATE_IRONMAN);
+ }
+
+ private int getDefaultItemsKept()
+ {
+ int count = isSkulled ? 0 : 3;
+
+ if (protectingItem)
+ {
+ count++;
+ }
+
+ return count;
+ }
+
+ private void recreateItemsKeptOnDeathWidget()
+ {
+ // Text flags based on items should be reset everytime the widget is recreated
+ hasAlwaysLost = false;
+
+ Widget lost = client.getWidget(WidgetInfo.ITEMS_LOST_ON_DEATH_CONTAINER);
+ Widget kept = client.getWidget(WidgetInfo.ITEMS_KEPT_ON_DEATH_CONTAINER);
+ if (lost != null && kept != null)
+ {
+ // Grab all items on player and sort by price.
+ List- items = new ArrayList<>();
+ ItemContainer inventory = client.getItemContainer(InventoryID.INVENTORY);
+ Item[] inv = inventory == null ? new Item[0] : inventory.getItems();
+ ItemContainer equipment = client.getItemContainer(InventoryID.EQUIPMENT);
+ Item[] equip = equipment == null ? new Item[0] : equipment.getItems();
+ Collections.addAll(items, inv);
+ Collections.addAll(items, equip);
+ // Sort by item price
+ items.sort((o1, o2) ->
+ {
+ int o1ID = ItemVariationMapping.map(itemManager.canonicalize(o1.getId()));
+ int o2ID = ItemVariationMapping.map(itemManager.canonicalize(o2.getId()));
+ ItemComposition c1 = itemManager.getItemComposition(o1ID);
+ ItemComposition c2 = itemManager.getItemComposition(o2ID);
+ int exchangePrice1 = c1.isTradeable() ? itemManager.getItemPrice(c1.getId()) : c1.getPrice();
+ int exchangePrice2 = c2.isTradeable() ? itemManager.getItemPrice(c2.getId()) : c2.getPrice();
+ return exchangePrice2 - exchangePrice1;
+ });
+
+ int keepCount = getDefaultItemsKept();
+
+ List keptItems = new ArrayList<>();
+ List lostItems = new ArrayList<>();
+ for (Item i : items)
+ {
+ int id = i.getId();
+ if (id == -1)
+ {
+ continue;
+ }
+
+ ItemComposition c = itemManager.getItemComposition(i.getId());
+ Widget itemWidget = createItemWidget(i.getQuantity(), c);
+
+ // 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(itemWidget);
+ continue;
+ }
+
+ // Certain items are always lost on death and have a white outline which we need to readd
+ AlwaysLostItem item = AlwaysLostItem.getByItemID(i.getId());
+ if (item != null)
+ {
+ // Some of these items are kept on death (outside wildy), like the Rune pouch. Ignore them
+ if (!item.isKept() || wildyLevel > 0)
+ {
+ itemWidget.setOnOpListener(SCRIPT_ID, 0, i.getQuantity(), c.getName());
+ itemWidget.setBorderType(2);
+ lostItems.add(itemWidget);
+ hasAlwaysLost = true;
+ continue;
+ }
+ }
+
+ // Keep most valuable items regardless of trade-ability.
+ if (keepCount > 0)
+ {
+ if (i.getQuantity() > keepCount)
+ {
+ keptItems.add(createItemWidget(keepCount, c));
+ itemWidget.setItemQuantity(i.getQuantity() - keepCount);
+ keepCount = 0;
+ }
+ else
+ {
+ keptItems.add(itemWidget);
+ keepCount -= i.getQuantity();
+ continue;
+ }
+ }
+
+
+ if (!checkTradeable(i.getId(), c) && wildyLevel < 21)
+ {
+ // Certain items are turned into broken variants inside the wilderness.
+ if (BrokenOnDeathItem.check(i.getId()))
+ {
+ keptItems.add(itemWidget);
+ continue;
+ }
+
+ // Ignore all non tradeables in wildy except for the above case(s).
+ if (wildyLevel > 0)
+ {
+ lostItems.add(itemWidget);
+ continue;
+ }
+
+ keptItems.add(itemWidget);
+ }
+ else
+ {
+ itemWidget.setOnOpListener(SCRIPT_ID, 0, i.getQuantity(), c.getName());
+ lostItems.add(itemWidget);
+ }
+ }
+
+ int rows = keptItems.size() > MAX_ROW_ITEMS ? keptItems.size() / MAX_ROW_ITEMS : 0;
+ // Adjust items lost container position if new rows were added to kept items container
+ lost.setOriginalY(ORIGINAL_LOST_Y + (rows * Y_INCREMENT));
+ lost.setOriginalHeight(ORIGINAL_LOST_HEIGHT - (rows * Y_INCREMENT));
+ setWidgetChildren(kept, keptItems);
+ setWidgetChildren(lost, lostItems);
+
+ updateKeptWidgetInfoText();
+ }
+ }
+
+ /**
+ * Wrapper for widget.setChildren() but updates the child index and original positions
+ * Used for Items Kept and Lost containers
+ *
+ * @param parent Widget to override children
+ * @param widgets Children to set on parent
+ */
+ private void setWidgetChildren(Widget parent, List widgets)
+ {
+ Widget[] children = parent.getChildren();
+ if (children == null)
+ {
+ // Create a child so we can copy the returned Widget[] and avoid hn casting issues from creating a new Widget[]
+ parent.createChild(0, WidgetType.GRAPHIC);
+ children = parent.getChildren();
+ }
+ Widget[] itemsArray = Arrays.copyOf(children, widgets.size());
+
+ int parentId = parent.getId();
+ int startingIndex = 0;
+ for (Widget w : widgets)
+ {
+ int originalX = STARTING_X + ((startingIndex % MAX_ROW_ITEMS) * X_INCREMENT);
+ int originalY = STARTING_Y + ((startingIndex / MAX_ROW_ITEMS) * Y_INCREMENT);
+
+ w.setParentId(parentId);
+ w.setId(parentId);
+ w.setIndex(startingIndex);
+
+ w.setOriginalX(originalX);
+ w.setOriginalY(originalY);
+ w.revalidate();
+
+ itemsArray[startingIndex] = w;
+ startingIndex++;
+ }
+
+ parent.setChildren(itemsArray);
+ parent.revalidate();
+ }
+
+ /**
+ * Creates the text to be displayed in the right side of the interface based on current selections
+ */
+ private String getUpdatedInfoText()
+ {
+ String textToAdd = DEFAULT;
+
+ if (isUltimateIronman())
+ {
+ textToAdd = UIM_DEFAULT;
+ }
+ else
+ {
+ if (isSkulled)
+ {
+ textToAdd += LINE_BREAK + IS_SKULLED;
+ }
+
+ if (protectingItem)
+ {
+ textToAdd += LINE_BREAK + PROTECTING_ITEM;
+ }
+
+ textToAdd += LINE_BREAK + String.format(ACTUAL, getDefaultItemsKept());
+ }
+
+
+ if (wildyLevel < 1)
+ {
+ textToAdd += LINE_BREAK + LINE_BREAK + NON_PVP;
+ }
+
+ if (hasAlwaysLost)
+ {
+ textToAdd += LINE_BREAK + LINE_BREAK + WHITE_OUTLINE;
+ }
+
+ textToAdd += LINE_BREAK + LINE_BREAK + CHANGED_MECHANICS;
+
+ return textToAdd;
+ }
+
+ /**
+ * Corrects the Information panel based on the item containers
+ */
+ private void updateKeptWidgetInfoText()
+ {
+ // Add Information text widget
+ createNewTextWidget();
+
+ // Update Items lost total value
+ Widget lost = client.getWidget(WidgetInfo.ITEMS_LOST_ON_DEATH_CONTAINER);
+ double total = 0;
+ for (Widget w : lost.getChildren())
+ {
+ if (w.getItemId() == -1)
+ {
+ continue;
+ }
+ double price = itemManager.getItemPrice(w.getItemId());
+ if (price == 0)
+ {
+ // Default to alch price
+ price = itemManager.getItemComposition(w.getItemId()).getPrice() * HIGH_ALCH;
+ }
+ total += price;
+ }
+ Widget lostValue = client.getWidget(WidgetInfo.ITEMS_LOST_VALUE);
+ lostValue.setText(NUMBER_FORMAT.format(total) + " gp");
+
+ // Update Max items kept
+ Widget kept = client.getWidget(WidgetInfo.ITEMS_KEPT_ON_DEATH_CONTAINER);
+ Widget max = client.getWidget(WidgetInfo.ITEMS_KEPT_MAX);
+ max.setText(String.format(MAX_KEPT_ITEMS_FORMAT, kept.getChildren().length));
+ }
+
+ // isTradeable checks if they are traded on the grand exchange, some items are trade-able but not via GE
+ private boolean checkTradeable(int id, ItemComposition c)
+ {
+ // If the item is a note check the unnoted variants trade ability
+ if (c.getNote() != -1)
+ {
+ return checkTradeable(c.getLinkedNoteId(), itemManager.getItemComposition(c.getLinkedNoteId()));
+ }
+
+ switch (id)
+ {
+ case ItemID.COINS_995:
+ case ItemID.PLATINUM_TOKEN:
+ return true;
+ default:
+ if (ActuallyTradeableItem.check(id))
+ {
+ return true;
+ }
+ }
+
+ return c.isTradeable();
+ }
+
+ private void createNewTextWidget()
+ {
+ // The text use to be put inside this container but since we can't create LAYER widgets
+ // We needed to edit this to be a layer for adding buttons
+ Widget old = client.getWidget(WidgetInfo.ITEMS_KEPT_INFORMATION_CONTAINER);
+
+ // Update the existing TEXT container if it exists. It should be the last child of the old text widget
+ // client.getWidget() seems to not find indexed child widgets
+ Widget[] children = old.getChildren();
+ if (children != null && children.length > 0)
+ {
+ Widget x = old.getChild(children.length - 1);
+ if (x.getId() == WidgetInfo.ITEMS_KEPT_CUSTOM_TEXT_CONTAINER.getId())
+ {
+ x.setText(getUpdatedInfoText());
+ x.revalidate();
+ return;
+ }
+ }
+
+ Widget w = old.createChild(-1, WidgetType.TEXT);
+ // Position under buttons taking remaining space
+ w.setOriginalWidth(old.getOriginalWidth());
+ w.setOriginalHeight(ORIGINAL_INFO_HEIGHT - old.getOriginalHeight());
+ w.setOriginalY(old.getOriginalHeight());
+
+ w.setFontId(FontID.PLAIN_11);
+ w.setTextShadowed(true);
+ w.setTextColor(FONT_COLOR);
+
+ w.setText(getUpdatedInfoText());
+ w.setId(WidgetInfo.ITEMS_KEPT_CUSTOM_TEXT_CONTAINER.getId());
+ w.revalidate();
+
+ // Need to reset height so text is visible?
+ old.setOriginalHeight(ORIGINAL_INFO_HEIGHT);
+ old.revalidate();
+ }
+
+ private void createWidgetButtons()
+ {
+ buttonMap.clear();
+
+ // Ultimate Iron men are always skulled and can't use the protect item prayer
+ if (!isUltimateIronman())
+ {
+ createButton(PROTECT_ITEM_BUTTON_NAME, PROTECT_ITEM_SPRITE_ID, protectingItem);
+ createButton(SKULLED_BUTTON_NAME, SKULL_SPRITE_ID, isSkulled);
+ }
+ createButton(LOW_WILDY_BUTTON_NAME, SWORD_SPRITE_ID, wildyLevel > 0 && wildyLevel <= 20);
+ createButton(DEEP_WILDY_BUTTON_NAME, SKULL_2_SPRITE_ID, wildyLevel > 20);
+
+ Widget parent = client.getWidget(WidgetInfo.ITEMS_KEPT_INFORMATION_CONTAINER);
+ parent.setType(WidgetType.LAYER);
+ parent.revalidate();
+ WidgetButton.addButtonsToContainerWidget(parent, buttonMap.values());
+ }
+
+ private void createButton(String name, int spriteID, boolean startingFlag)
+ {
+ WidgetButton button = new WidgetButton(name, spriteID, startingFlag, this::buttonCallback, client);
+ buttonMap.put(name, button);
+ }
+
+ private void buttonCallback(String name, boolean selected)
+ {
+ log.debug("Clicked Widget Button {}. New value: {}", name, selected);
+ switch (name)
+ {
+ case PROTECT_ITEM_BUTTON_NAME:
+ protectingItem = selected;
+ break;
+ case SKULLED_BUTTON_NAME:
+ isSkulled = selected;
+ break;
+ case LOW_WILDY_BUTTON_NAME:
+ if (!selected)
+ {
+ syncCurrentWildyLevel();
+ break;
+ }
+ wildyLevel = 1;
+ buttonMap.get(DEEP_WILDY_BUTTON_NAME).setSelected(false);
+ break;
+ case DEEP_WILDY_BUTTON_NAME:
+ if (!selected)
+ {
+ syncCurrentWildyLevel();
+ break;
+ }
+ wildyLevel = 21;
+ buttonMap.get(LOW_WILDY_BUTTON_NAME).setSelected(false);
+ break;
+ default:
+ log.warn("Unhandled Button Name: {}", name);
+ return;
+ }
+
+ recreateItemsKeptOnDeathWidget();
+ }
+
+ /**
+ * Creates an Item Widget for use inside the Kept on Death Interface
+ * @param qty Amount of item
+ * @param c Items Composition
+ * @return
+ */
+ private Widget createItemWidget(int qty, ItemComposition c)
+ {
+ Widget itemWidget = client.createWidget();
+ itemWidget.setType(WidgetType.GRAPHIC);
+ itemWidget.setItemId(c.getId());
+ itemWidget.setItemQuantity(qty);
+ itemWidget.setHasListener(true);
+ itemWidget.setIsIf3(true);
+ itemWidget.setOriginalWidth(ORIGINAL_WIDTH);
+ itemWidget.setOriginalHeight(ORIGINAL_HEIGHT);
+ itemWidget.setBorderType(1);
+
+ itemWidget.setAction(1, String.format(ACTION_TEXT, c.getName()));
+ itemWidget.setOnOpListener(SCRIPT_ID, 1, qty, c.getName());
+
+ return itemWidget;
+ }
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/WidgetButton.java b/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/WidgetButton.java
new file mode 100644
index 0000000000..6385faf8aa
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/keptondeath/WidgetButton.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2018, 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.keptondeath;
+
+import java.util.Arrays;
+import java.util.Collection;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.SpriteID;
+import net.runelite.api.widgets.JavaScriptCallback;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetType;
+
+@Slf4j
+public class WidgetButton
+{
+ private static final int ICON_HEIGHT = 26;
+ private static final int ICON_WIDTH = 26;
+ private static final int BACKGROUND_HEIGHT = 32;
+ private static final int BACKGROUND_WIDTH = 32;
+ private static final int PADDING = 5;
+ private static final int ICON_PADDING = (BACKGROUND_HEIGHT - ICON_HEIGHT) / 2;
+
+ private static final int BACKGROUND_SPRITE_ID = SpriteID.EQUIPMENT_SLOT_TILE;
+ private static final int SELECTED_BACKGROUND_SPRITE_ID = SpriteID.EQUIPMENT_SLOT_SELECTED;
+
+ public interface WidgetButtonCallback
+ {
+ void run(String name, boolean newState);
+ }
+
+ private String name;
+ private int spriteID;
+ @Getter
+ private Widget icon;
+ @Getter
+ private Widget background;
+ private boolean selected;
+ private WidgetButtonCallback callback;
+
+ WidgetButton(String name, int spriteID, boolean selectedStartState, WidgetButtonCallback callback, Client client)
+ {
+ this.name = name;
+ this.spriteID = spriteID;
+ this.selected = selectedStartState;
+ this.callback = callback;
+ createBackgroundWidget(client);
+ createIconWidget(client);
+ }
+
+ private void createBackgroundWidget(Client client)
+ {
+ background = createWidget(client);
+ background.setOriginalWidth(BACKGROUND_WIDTH);
+ background.setOriginalHeight(BACKGROUND_HEIGHT);
+ syncBackgroundSprite();
+ }
+
+ private void createIconWidget(Client client)
+ {
+ icon = createWidget(client);
+ icon.setAction(1, "Toggle:");
+ icon.setOnOpListener((JavaScriptCallback) ev -> onButtonClicked());
+ icon.setHasListener(true);
+ icon.setSpriteId(spriteID);
+ }
+
+ private Widget createWidget(Client client)
+ {
+ Widget w = client.createWidget();
+ w.setType(WidgetType.GRAPHIC);
+ w.setOriginalWidth(ICON_WIDTH);
+ w.setOriginalHeight(ICON_HEIGHT);
+ w.setName("" + this.name);
+ w.setIsIf3(true);
+ return w;
+ }
+
+ public void setSelected(boolean selected)
+ {
+ this.selected = selected;
+ syncBackgroundSprite();
+ }
+
+ private void syncBackgroundSprite()
+ {
+ background.setSpriteId(selected ? SELECTED_BACKGROUND_SPRITE_ID : BACKGROUND_SPRITE_ID);
+ }
+
+ /**
+ * Adds the collection of WidgetButtons to the container overriding any existing children.
+ * @param container Widget to add buttons too
+ * @param buttons buttons to add
+ */
+ static void addButtonsToContainerWidget(Widget container, Collection buttons)
+ {
+ Widget[] children = container.getChildren();
+ if (children == null)
+ {
+ // Create a child so we can copy the returned Widget[] and avoid hn casting issues from creating a new Widget[]
+ container.createChild(0, WidgetType.GRAPHIC);
+ children = container.getChildren();
+ }
+ // Each button has two widgets, Icon and Background
+ Widget[] itemsArray = Arrays.copyOf(children, buttons.size() * 2);
+ int parentId = container.getId();
+
+ int xIncrement = BACKGROUND_WIDTH + PADDING;
+ int yIncrement = BACKGROUND_HEIGHT + PADDING;
+ int maxRowItems = container.getWidth() / xIncrement;
+ // Ensure at least 1 button per row
+ maxRowItems = maxRowItems < 1 ? 1 : maxRowItems;
+
+ int startingIndex = 0;
+ for (WidgetButton w : buttons)
+ {
+ int originalX = (((startingIndex / 2) % maxRowItems) * xIncrement);
+ int originalY = (((startingIndex / 2) / maxRowItems) * yIncrement);
+ Widget background = updateWidgetPosition(w.getBackground(), parentId, startingIndex, originalX, originalY);
+ itemsArray[startingIndex] = background;
+ startingIndex++;
+ // Icon must be padded to center inside image
+ Widget icon = updateWidgetPosition(w.getIcon(), parentId, startingIndex, originalX + ICON_PADDING, originalY + ICON_PADDING);
+ itemsArray[startingIndex] = icon;
+ startingIndex++;
+ }
+
+ int rows = 1 + (buttons.size() > maxRowItems ? buttons.size() / maxRowItems : 0);
+ container.setOriginalHeight(yIncrement * rows);
+ container.setChildren(itemsArray);
+ container.revalidate();
+ }
+
+ private static Widget updateWidgetPosition(Widget w, int id, int index, int originalX, int originalY)
+ {
+ w.setParentId(id);
+ w.setId(id);
+ w.setIndex(index);
+
+ w.setOriginalX(originalX);
+ w.setOriginalY(originalY);
+ w.revalidate();
+
+ return w;
+ }
+
+ private void onButtonClicked()
+ {
+ setSelected(!selected);
+ callback.run(name, selected);
+ }
+}
diff --git a/runelite-client/src/main/scripts/DeathKeepBuildScript.hash b/runelite-client/src/main/scripts/DeathKeepBuildScript.hash
new file mode 100644
index 0000000000..18f92dce5c
--- /dev/null
+++ b/runelite-client/src/main/scripts/DeathKeepBuildScript.hash
@@ -0,0 +1 @@
+15F58F5939D9311F3D76FA2F0F3441B7B0DA1E8EAE23C654948095A7D51E07F0
\ No newline at end of file
diff --git a/runelite-client/src/main/scripts/DeathKeepBuildScript.rs2asm b/runelite-client/src/main/scripts/DeathKeepBuildScript.rs2asm
new file mode 100644
index 0000000000..520f2c7e9e
--- /dev/null
+++ b/runelite-client/src/main/scripts/DeathKeepBuildScript.rs2asm
@@ -0,0 +1,622 @@
+.id 1601
+.int_stack_count 4
+.string_stack_count 2
+.int_var_count 13
+.string_var_count 3
+ sload 1
+ iconst 262167
+ if_settext
+ iconst 0
+ istore 4
+ iconst 0
+ istore 5
+ iconst -1
+ istore 6
+ iconst 0
+ istore 7
+ sconst ""
+ sstore 2
+ iconst 0
+ istore 8
+ iconst 0
+ istore 9
+ iconst 0
+ istore 10
+ iload 1
+ define_array 111
+ iconst 0
+ istore 11
+ iconst 0
+ istore 12
+ iload 0
+ iconst 0
+ if_icmpeq LABEL29
+ jump LABEL517
+LABEL29:
+ iconst 93
+ iconst 13190
+ inv_total
+ iconst 0
+ if_icmpgt LABEL40
+ iconst 93
+ iconst 13192
+ inv_total
+ iconst 0
+ if_icmpgt LABEL40
+ jump LABEL42
+LABEL40:
+ iconst 1
+ istore 9
+LABEL42:
+ iload 10
+ iload 1
+ if_icmplt LABEL46
+ jump LABEL82
+LABEL46:
+ iconst 584
+ iload 10
+ inv_getobj
+ istore 6
+ iload 6
+ iconst -1
+ if_icmpne LABEL54
+ jump LABEL79
+LABEL54:
+ iconst 584
+ iload 10
+ inv_getnum
+ istore 7
+LABEL58:
+ iload 10
+ iload 1
+ if_icmplt LABEL62
+ jump LABEL78
+LABEL62:
+ iload 7
+ iconst 0
+ if_icmpgt LABEL66
+ jump LABEL78
+LABEL66:
+ iload 10
+ iload 6
+ set_array_int
+ iload 7
+ iconst 1
+ sub
+ istore 7
+ iload 10
+ iconst 1
+ add
+ istore 10
+ jump LABEL58
+LABEL78:
+ jump LABEL81
+LABEL79:
+ iload 1
+ istore 10
+LABEL81:
+ jump LABEL42
+LABEL82:
+ iload 4
+ iload 1
+ if_icmplt LABEL86
+ jump LABEL141
+LABEL86:
+ iconst 262162
+ iconst 5
+ iload 4
+ cc_create
+ iconst 36
+ iconst 32
+ iconst 0
+ iconst 0
+ cc_setsize
+ iconst 5
+ iload 4
+ iconst 40
+ multiply
+ add
+ iconst 25
+ iconst 0
+ iconst 0
+ cc_setposition
+ iload 4
+ get_array_int
+ istore 6
+ iload 6
+ iconst -1
+ if_icmpne LABEL111
+ jump LABEL138
+LABEL111:
+ iload 6
+ iconst 1
+ 1200
+ sconst ""
+ iload 6
+ oc_name
+ join_string 2
+ cc_setopbase
+ iconst 1
+ sconst "Item:"
+ cc_setop
+ iconst 1603
+ iconst 1
+ iconst 1
+ iload 6
+ oc_name
+ sconst "1is"
+ cc_setonop
+ iconst 1118481
+ cc_setgraphicshadow
+ iconst 1
+ cc_setoutline
+ iload 4
+ iconst 1
+ add
+ istore 4
+ jump LABEL140
+LABEL138:
+ iload 1
+ istore 4
+LABEL140:
+ jump LABEL82
+LABEL141:
+ iconst 0
+ istore 4
+LABEL143:
+ iload 4
+ iconst 468
+ inv_size
+ if_icmplt LABEL148
+ jump LABEL342
+LABEL148:
+ iconst 468
+ iload 4
+ inv_getobj
+ istore 6
+ iload 6
+ iconst -1
+ if_icmpne LABEL156
+ jump LABEL337
+LABEL156:
+ iconst 262165
+ iconst 5
+ iload 5
+ cc_create
+ iconst 36
+ iconst 32
+ iconst 0
+ iconst 0
+ cc_setsize
+ iconst 5
+ iload 5
+ iconst 8
+ mod
+ iconst 38
+ multiply
+ add
+ iconst 25
+ iconst 38
+ iload 5
+ iconst 8
+ div
+ multiply
+ add
+ iconst 0
+ iconst 0
+ cc_setposition
+ iload 6
+ iconst 468
+ iload 4
+ inv_getnum
+ 1200
+ sconst ""
+ iload 6
+ oc_name
+ join_string 2
+ cc_setopbase
+ iconst 1
+ sconst "Item:"
+ cc_setop
+ iconst 1603
+ iconst 0
+ iconst 468
+ iload 4
+ inv_getnum
+ iload 6
+ oc_name
+ sconst "1is"
+ cc_setonop
+ iconst 1118481
+ cc_setgraphicshadow
+ iconst 111
+ iconst 49
+ iconst 879
+ iload 6
+ enum
+ iconst 1
+ if_icmpeq LABEL214
+ jump LABEL219
+LABEL214:
+ iconst 2
+ cc_setoutline
+ iconst 1
+ istore 8
+ jump LABEL221
+LABEL219:
+ iconst 1
+ cc_setoutline
+LABEL221:
+ iload 5
+ iconst 1
+ add
+ istore 5
+ iload 6
+ oc_stackable
+ iconst 1
+ if_icmpeq LABEL230
+ jump LABEL337
+LABEL230:
+ iconst 0
+ istore 10
+ iconst 0
+ istore 12
+LABEL234:
+ iload 10
+ iload 1
+ if_icmplt LABEL238
+ jump LABEL252
+LABEL238:
+ iload 10
+ get_array_int
+ iload 6
+ if_icmpeq LABEL243
+ jump LABEL247
+LABEL243:
+ iload 12
+ iconst 1
+ add
+ istore 12
+LABEL247:
+ iload 10
+ iconst 1
+ add
+ istore 10
+ jump LABEL234
+LABEL252:
+ iconst 2147483647
+ iconst 94
+ iload 6
+ inv_total
+ sub
+ iconst 93
+ iload 6
+ inv_total
+ sub
+ iload 12
+ add
+ istore 11
+ iconst 0
+ iload 11
+ sub
+ istore 11
+ iload 11
+ iconst 0
+ if_icmpgt LABEL272
+ jump LABEL337
+LABEL272:
+ iconst 262165
+ iconst 5
+ iload 5
+ cc_create
+ iconst 36
+ iconst 32
+ iconst 0
+ iconst 0
+ cc_setsize
+ iconst 5
+ iload 5
+ iconst 8
+ mod
+ iconst 38
+ multiply
+ add
+ iconst 25
+ iconst 38
+ iload 5
+ iconst 8
+ div
+ multiply
+ add
+ iconst 0
+ iconst 0
+ cc_setposition
+ iload 6
+ iload 11
+ 1200
+ sconst ""
+ iload 6
+ oc_name
+ join_string 2
+ cc_setopbase
+ iconst 1
+ sconst "Item:"
+ cc_setop
+ iconst 1603
+ iconst 0
+ iload 11
+ iload 6
+ oc_name
+ sconst "1is"
+ cc_setonop
+ iconst 1118481
+ cc_setgraphicshadow
+ iconst 111
+ iconst 49
+ iconst 879
+ iload 6
+ enum
+ iconst 1
+ if_icmpeq LABEL326
+ jump LABEL331
+LABEL326:
+ iconst 2
+ cc_setoutline
+ iconst 1
+ istore 8
+ jump LABEL333
+LABEL331:
+ iconst 1
+ cc_setoutline
+LABEL333:
+ iload 5
+ iconst 1
+ add
+ istore 5
+LABEL337:
+ iload 4
+ iconst 1
+ add
+ istore 4
+ jump LABEL143
+LABEL342:
+ sconst "The normal amount of items kept is "
+ sconst "three"
+ sconst "."
+ sconst "
"
+ sconst "
"
+ join_string 5
+ sstore 2
+ iload 3
+ iconst 1
+ if_icmpeq LABEL353
+ jump LABEL363
+LABEL353:
+ sload 2
+ sconst "You're an "
+ sconst ""
+ sconst "Ultimate Iron Man"
+ sconst ""
+ sconst ", so you will always keep zero items."
+ join_string 5
+ append
+ sstore 2
+ jump LABEL426
+LABEL363:
+ iload 1
+ iconst 0
+ if_icmpeq LABEL367
+ jump LABEL379
+LABEL367:
+ sload 2
+ sconst "You're marked with a "
+ sconst ""
+ sconst "PK skull"
+ sconst ""
+ sconst ". This reduces the items you keep from "
+ sconst "three"
+ sconst " to zero!"
+ join_string 7
+ append
+ sstore 2
+ jump LABEL426
+LABEL379:
+ iload 1
+ iconst 1
+ if_icmpeq LABEL383
+ jump LABEL402
+LABEL383:
+ sload 2
+ sconst "You're marked with a "
+ sconst ""
+ sconst "PK skull"
+ sconst ""
+ sconst ". This reduces the items you keep from "
+ sconst "three"
+ sconst " to zero!"
+ sconst "
"
+ sconst "
"
+ sconst "However, you also have the "
+ sconst ""
+ sconst "Protect Items"
+ sconst ""
+ sconst " prayer active, which saves you one extra item!"
+ join_string 14
+ append
+ sstore 2
+ jump LABEL426
+LABEL402:
+ iload 1
+ iconst 3
+ if_icmpeq LABEL406
+ jump LABEL411
+LABEL406:
+ sload 2
+ sconst "You have no factors affecting the items you keep."
+ append
+ sstore 2
+ jump LABEL426
+LABEL411:
+ iload 1
+ iconst 3
+ iconst 1
+ add
+ if_icmpeq LABEL417
+ jump LABEL426
+LABEL417:
+ sload 2
+ sconst "You have the "
+ sconst ""
+ sconst "Protect Items"
+ sconst ""
+ sconst " prayer active, which saves you one extra item!"
+ join_string 5
+ append
+ sstore 2
+LABEL426:
+ iload 8
+ iconst 1
+ if_icmpeq LABEL433
+ iload 9
+ iconst 1
+ if_icmpeq LABEL433
+ jump LABEL484
+LABEL433:
+ iload 8
+ iconst 1
+ if_icmpeq LABEL437
+ jump LABEL458
+LABEL437:
+ iload 9
+ iconst 1
+ if_icmpeq LABEL441
+ jump LABEL458
+LABEL441:
+ sload 2
+ sconst "
"
+ sconst "
"
+ sconst "Items with a "
+ sconst ""
+ sconst "white outline"
+ sconst ""
+ sconst " will always be lost."
+ sconst "
"
+ sconst ""
+ sconst "Bonds"
+ sconst ""
+ sconst " are always protected."
+ join_string 12
+ append
+ sstore 2
+ jump LABEL484
+LABEL458:
+ iload 8
+ iconst 1
+ if_icmpeq LABEL462
+ jump LABEL474
+LABEL462:
+ sload 2
+ sconst "
"
+ sconst "
"
+ sconst "Items with a "
+ sconst ""
+ sconst "white outline"
+ sconst ""
+ sconst " will always be lost."
+ join_string 7
+ append
+ sstore 2
+ jump LABEL484
+LABEL474:
+ sload 2
+ sconst "
"
+ sconst "
"
+ sconst ""
+ sconst "Bonds"
+ sconst ""
+ sconst " are always protected, so are not shown here."
+ join_string 6
+ append
+ sstore 2
+LABEL484:
+ sload 2
+ iconst 262173
+ if_settext
+ sconst ""
+ sconst "Max items kept on death :"
+ sconst "
"
+ sconst "
"
+ sconst ""
+ sconst "~ "
+ iload 1
+ tostring
+ sconst " ~"
+ join_string 8
+ iconst 262174
+ if_settext
+ iload 2
+ iconst 0
+ if_icmpgt LABEL503
+ jump LABEL510
+LABEL503:
+ sconst "Items you will keep on death:"
+ iconst 262161
+ if_settext
+ sconst "Items you will lose on death:"
+ iconst 262164
+ if_settext
+ jump LABEL516
+LABEL510:
+ sconst "Items you will keep on death if not skulled:"
+ iconst 262161
+ if_settext
+ sconst "Items you will lose on death if not skulled:"
+ iconst 262164
+ if_settext
+LABEL516:
+ jump LABEL557
+LABEL517:
+ iconst 1
+ iconst 262165
+ if_sethide
+ iconst 1
+ iconst 262162
+ if_sethide
+ iconst 0
+ iconst 262175
+ if_sethide
+ sload 0
+ iconst 262176
+ if_settext
+ sconst "The normal amount of items kept is "
+ sconst "three"
+ sconst "."
+ sconst "
"
+ sconst "
"
+ join_string 5
+ sstore 2
+ sload 2
+ sconst "You're in a "
+ sconst ""
+ sconst "safe area"
+ sconst ""
+ sconst ". See information to the left for a more detailed description."
+ join_string 5
+ append
+ sstore 2
+ sload 2
+ iconst 262173
+ if_settext
+ sconst ""
+ sconst "Max items kept on death :"
+ sconst "
"
+ sconst "
"
+ sconst ""
+ sconst "All items!"
+ join_string 6
+ iconst 262174
+ if_settext
+LABEL557:
+ sconst "deathKeepBuild" ; push event name
+ runelite_callback ; invoke callback
+ return