Implemented POH bookshelf, fancy dress box, armour case, magic wardrobe, treasure chests, cape rack, and toy box

Implemented ::ifaces commmand
Implemented ::pohstorableitems command to fetch all storable items
Implemented storing books in house
POH now included in item checks
This commit is contained in:
oftheshire
2026-04-04 13:14:41 +00:00
committed by Ryan
parent d642c23a2c
commit 37d3ec002d
39 changed files with 1519 additions and 56 deletions
@@ -5,6 +5,7 @@ import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.ServerConstants
import core.api.*
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -244,8 +245,9 @@ class GoblinBook : InteractionListener {
}
override fun defineListeners() {
on(Items.GOBLIN_BOOK_10999, IntType.ITEM, "read") { player, _ ->
on(Items.GOBLIN_BOOK_10999, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_26, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -5,6 +5,7 @@ import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -126,8 +127,9 @@ class SecurityBookPlugin : InteractionListener {
}
override fun defineListeners() {
on(Items.SECURITY_BOOK_9003, IntType.ITEM, "read") { player, _ ->
on(Items.SECURITY_BOOK_9003, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_2_27, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -5,6 +5,7 @@ import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -128,8 +129,9 @@ class StrongholdNotes : InteractionListener {
}
override fun defineListeners() {
on(Items.STRONGHOLD_NOTES_9004, IntType.ITEM, "read") { player, _ ->
on(Items.STRONGHOLD_NOTES_9004, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_2_27, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -1,5 +1,6 @@
package content.global.handlers.item.toys;
import content.global.skill.construction.decoration.pohstorage.StorableFamily;
import core.game.component.Component;
import core.game.component.ComponentDefinition;
import core.game.component.ComponentPlugin;
@@ -46,7 +47,7 @@ public class DiangoReclaimInterface extends ComponentPlugin {
Item[] reclaimables = getEligibleItems(player);
player.setAttribute("diango-reclaimables", reclaimables);
//filter out items the player already has in their bank, inventory, or equipped
//filter out items the player already has in their bank, POH, inventory, or equipped
//only send items if there are some to send
if(reclaimables.length > 0) {
@@ -90,7 +91,7 @@ public class DiangoReclaimInterface extends ComponentPlugin {
public static Item[] getEligibleItems (Player player) {
return ITEMS.stream().filter(Objects::nonNull)
.filter(item -> !player.getEquipment().containsItem(item) && !player.getInventory().containsItem(item) && !player.getBank().containsItem(item)
.filter(item -> !player.getEquipment().containsItem(item) && !player.getInventory().containsItem(item) && !player.getBank().containsItem(item) && !player.getPOHStorageState().getContainer(StorableFamily.TOY_BOX).contains(item.getId())
&& (item.getId() != 14654
|| (!(inInventory(player, 14655, 1) || inEquipment(player, 14656, 1)) && player.getAttribute("sotr:purchased",false))
))
@@ -1,6 +1,7 @@
package content.global.skill.construction
import core.api.sendMessage
import core.api.storeBookInHouse
import core.game.dialogue.DialogueInterpreter
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
@@ -52,7 +53,7 @@ class ConstructionGuideBook : InteractionListener {
override fun defineListeners() {
// There is supposedly a book here.
on(Items.CONSTRUCTION_GUIDE_8463, IntType.ITEM, "read") { player, _ ->
on(Items.CONSTRUCTION_GUIDE_8463, IntType.ITEM, "read") { player, node ->
if (settings!!.isDevMode && settings!!.isBeta) {
for (item in RESOURCES) {
if (!player.inventory.contains(item.id, item.amount)) {
@@ -60,6 +61,7 @@ class ConstructionGuideBook : InteractionListener {
}
}
}
storeBookInHouse(player, node)
sendMessage(player, "Upon reading the book you discover you're supposed to use these resources to test out construction. Report all bugs on the forums.")
return@on true
}
@@ -1,29 +0,0 @@
package content.global.skill.construction.decoration.costume
import core.cache.def.impl.SceneryDefinition
import core.game.interaction.OptionHandler
import content.global.handlers.item.toys.DiangoReclaimInterface
import core.game.node.Node
import core.game.node.entity.player.Player
import core.plugin.Initializable
import core.plugin.Plugin
/**
* Handles the Toy Box POH
* ToyBoxPlugin.java
* @author Lee
* @date 10/2/2017
*/
@Initializable
class ToyBoxPlugin : OptionHandler() {
@Throws(Throwable::class)
override fun newInstance(arg: Any?): Plugin<Any?> {
SceneryDefinition.forId(18802).handlers["option:open"] = this
return this
}
override fun handle(player: Player, node: Node, option: String): Boolean {
DiangoReclaimInterface.open(player)
return true
}
}
@@ -0,0 +1,28 @@
package content.global.skill.construction.decoration.pohstorage;
import core.game.container.Container;
import core.game.container.ContainerType;
import core.game.node.entity.player.Player;
import core.game.node.item.Item;
/**
* A proxy Container used to indicate an item was found in POH storage.
* This is not a real container, just a dummy so that hasAnItem() can be easily extended to check POH storage
*/
public class POHStorageProxyContainer extends Container {
public POHStorageProxyContainer(Player player) {
super(1, ContainerType.DEFAULT);
}
// Prevents external code from trying to fetch items from this container because it doesn't actually store anything
@Override
public Item get(int slot) {
return null;
}
@Override
public String toString() {
return "POHStorageProxyContainer";
}
}
@@ -0,0 +1,444 @@
package content.global.skill.construction.decoration.pohstorage
import content.data.Quests
import core.api.getItemName
import core.api.isQuestComplete
import core.game.node.entity.player.Player
import core.game.world.GameWorld
import org.rs09.consts.Items
/**
* Represents a database of all the storable items in the POH Costume Room and Bookcase across all box types.
*/
/*
* The below function should be copied into each book's listener to store the book in the POH. It is part of ContentAPI.
* import core.api.*
* storeBookInHouse(player, node)
*
* Example: src/main/content/region/kandarin/quest/observatoryquest/AstronomyBook.kt
*
* todo: Periodically, this storables list should be audited as new books are added to the game. Make sure the new books are going in the bookcase.
*/
// todo: Right now there is no logic to check:
// 1. for complete or incomplete sets
// 2. for single instances of an item
// This allows things like storing multiple god capes, a Rune (g) set with both platelegs and plateskirt,
// an elegant shirt/pants and blouse/skirt at once, and all 3 sets of castle wars armour at once. I don't
// think any of this is game-breaking, so have chosen to omit this logic for now.
enum class Storable(
val displayId: Int, // display ID is the item that is shown in the UI
val takeIds: IntArray, // take IDs is the item(s) that are taken from the player's inventory
val type: StorableFamily, // the family (mapped to container) that the items get stored in
val setName: String = getItemName(displayId), // the set/book name for each item. defaults to display item
// special properties just for the books
val bitIndex: Int = -1, // This index is used to save the big list of books the player has read, instead of having an individual attribute per book.
// todo make the quest requirement check part of a separate object against this list of books. only a subset of books are applicable.
val questReq: Quests? = null // Once read, books should only show up in the POH after completing their attached quest. https://runescape.wiki/w/Bookcase?oldid=1650176
) {
// Books - https://runescape.wiki/w/List_of_texts_and_tomes?oldid=848552
AbyssalBook(Items.ABYSSAL_BOOK_5520, intArrayOf(Items.ABYSSAL_BOOK_5520), type = StorableFamily.BOOKCASE, "Abyssal Research Notes", bitIndex = 0), // abyss miniquest - IMPLEMENTED
AncientBook(Items.ANCIENT_BOOK_7633, intArrayOf(Items.ANCIENT_BOOK_7633), type = StorableFamily.BOOKCASE, "The Sleeping Seven", bitIndex = 1, Quests.HORROR_FROM_THE_DEEP), // horror from the deep quest
AncientDiary(Items.DIARY_3846, intArrayOf(Items.DIARY_3846), type = StorableFamily.BOOKCASE, "Ancient Diary", bitIndex = 2, Quests.HORROR_FROM_THE_DEEP), // horror from the deep quest
ArenaBook(Items.ARENA_BOOK_6891, intArrayOf(Items.ARENA_BOOK_6891), type = StorableFamily.BOOKCASE, "Magic Training Arena Lore Book", bitIndex = 3), // mage training arena minigame
AstronomyBook(Items.ASTRONOMY_BOOK_600, intArrayOf(Items.ASTRONOMY_BOOK_600), type = StorableFamily.BOOKCASE, "Astronomy Book", bitIndex = 4, Quests.OBSERVATORY_QUEST), // observatory quest - IMPLEMENTED
BatteredBook(Items.BATTERED_BOOK_2886, intArrayOf(Items.BATTERED_BOOK_2886), type = StorableFamily.BOOKCASE, "Book of the Elemental Shield", bitIndex = 5, Quests.ELEMENTAL_WORKSHOP_I), // elemental workshop I quest - IMPLEMENTED
BatteredTome(Items.BATTERED_TOME_7634, intArrayOf(Items.BATTERED_TOME_7634), type = StorableFamily.BOOKCASE, "Histories of Hallowland", bitIndex = 6, Quests.IN_AID_OF_THE_MYREQUE), // in aid of myreque quest
BeatenBook(Items.BEATEN_BOOK_9717, intArrayOf(Items.BEATEN_BOOK_9717), type = StorableFamily.BOOKCASE, "Book of the Elemental Helm", bitIndex = 7, Quests.ELEMENTAL_WORKSHOP_II), // elemental workshop II quest
BigBookOfBangs(Items.BIG_BOOK_OF_BANGS_3230, intArrayOf(Items.BIG_BOOK_OF_BANGS_3230), type = StorableFamily.BOOKCASE, "Big Book of Bangs", bitIndex = 8, Quests.REGICIDE), // regicide quest
BindingBook(Items.BINDING_BOOK_730, intArrayOf(Items.BINDING_BOOK_730), type = StorableFamily.BOOKCASE, "Book of Binding", bitIndex = 9, Quests.LEGENDS_QUEST), // legends quest - IMPLEMENTED
BirdBook(Items.BIRD_BOOK_10173, intArrayOf(Items.BIRD_BOOK_10173), type = StorableFamily.BOOKCASE, "William Oddity's Guide to the Avian", bitIndex = 10, Quests.EAGLES_PEAK), // eagles peak quest
BookOnBaxtorian(Items.BOOK_ON_BAXTORIAN_292, intArrayOf(Items.BOOK_ON_BAXTORIAN_292), type = StorableFamily.BOOKCASE, "Book on Baxtorian", bitIndex = 11, Quests.WATERFALL_QUEST), // waterfall quest - IMPLEMENTED
BookOnChemicals(Items.BOOK_ON_CHEMICALS_711, intArrayOf(Items.BOOK_ON_CHEMICALS_711), type = StorableFamily.BOOKCASE, "Volatile chemicals - Experimental Test Notes", bitIndex = 12, Quests.THE_DIG_SITE), // dig site quest - IMPLEMENTED
BookOPiracy(Items.BOOK_O_PIRACY_7144, intArrayOf(Items.BOOK_O_PIRACY_7144), type = StorableFamily.BOOKCASE, "Book o' Piracy", bitIndex = 13, Quests.CABIN_FEVER), // cabin fever quest
BrewinGuide(Items.BREWIN_GUIDE_8989, intArrayOf(Items.BREWIN_GUIDE_8989), type = StorableFamily.BOOKCASE, "Brewin' Guide", bitIndex = 14), // trouble brewing quest/minigame
BurntDiary(Items.BURNT_DIARY_7965, intArrayOf(Items.BURNT_DIARY_7965), type = StorableFamily.BOOKCASE, "Burnt Diary", bitIndex = 15, Quests.ROYAL_TROUBLE), // royal trouble quest
CadarnLineage(Items.CADARN_LINEAGE_4209, intArrayOf(Items.CADARN_LINEAGE_4209), type = StorableFamily.BOOKCASE, "Cadarn Lineage", bitIndex = 16, Quests.ROVING_ELVES), // roving elves quest
ClockworkBook(Items.CLOCKWORK_BOOK_10594, intArrayOf(Items.CLOCKWORK_BOOK_10594), type = StorableFamily.BOOKCASE, "Clockwork Toys - A Clockwork Mechanism, Chapter 1.0", bitIndex = 17, Quests.COLD_WAR), // cold war quest
CocktailGuide(Items.COCKTAIL_GUIDE_2023, intArrayOf(Items.COCKTAIL_GUIDE_2023), type = StorableFamily.BOOKCASE, "The Blurberry Cocktail Guide", bitIndex = 18), // gnome restaurant minigame
ConstructionGuide(Items.CONSTRUCTION_GUIDE_8463, intArrayOf(Items.CONSTRUCTION_GUIDE_8463), type = StorableFamily.BOOKCASE, "Guide to Construction", bitIndex = 19), // after you buy a house from estate agent - IMPLEMENTED ish
CrumblingTome(Items.CRUMBLING_TOME_4707, intArrayOf(Items.CRUMBLING_TOME_4707), type = StorableFamily.BOOKCASE, "Legend of the Brothers", bitIndex = 20), // barrows minigame
CrystalSinging(Items.CRYSTAL_OF_SEREN_4313, intArrayOf(Items.CRYSTAL_OF_SEREN_4313), type = StorableFamily.BOOKCASE, "Crystal singing for beginners", bitIndex = 21, Quests.ROVING_ELVES), // roving elves quest
DagonhaiHistory(Items.DAGONHAI_HISTORY_11001, intArrayOf(Items.DAGONHAI_HISTORY_11001), type = StorableFamily.BOOKCASE, "History of the Dagon'hai", bitIndex = 22), // found in varrock castle library
DemonicTome(Items.DEMONIC_TOME_6749, intArrayOf(Items.DEMONIC_TOME_6749), type = StorableFamily.BOOKCASE, "The Confessions of Ellemar", bitIndex = 23, Quests.SHADOW_OF_THE_STORM), // shadow of the storm quest
DiaryHerbi(Items.DIARY_3395, intArrayOf(Items.DIARY_3395), type = StorableFamily.BOOKCASE, "Diary of Herbi Flax", bitIndex = 24, Quests.SHADES_OF_MORTTON), // shades of mort'ton quest
DrozalsJournal(Items.JOURNAL_6755, intArrayOf(Items.JOURNAL_6755), type = StorableFamily.BOOKCASE, "Drozal's Journal", bitIndex = 25, Quests.MAKING_HISTORY), // making history quest
DwarvenLore(Items.DWARVEN_LORE_4568, intArrayOf(Items.DWARVEN_LORE_4568), type = StorableFamily.BOOKCASE, "The Arzinian Being of Bordanzan", bitIndex = 26, Quests.BETWEEN_A_ROCK), // between a rock quest
EasternDiscovery(Items.EASTERN_DISCOVERY_6075, intArrayOf(Items.EASTERN_DISCOVERY_6075), type = StorableFamily.BOOKCASE, "The Exploration of the Eastern Realm", bitIndex = 27, Quests.MOURNINGS_END_PART_I), // mourning's end I quest
EasternSettlement(Items.EASTERN_SETTLEMENT_6077, intArrayOf(Items.EASTERN_SETTLEMENT_6077), type = StorableFamily.BOOKCASE, "The Settlement of the East", bitIndex = 28, Quests.MOURNINGS_END_PART_I), // mourning's end I quest
EdernsJournal(Items.EDERNS_JOURNAL_6649, intArrayOf(Items.EDERNS_JOURNAL_6649), type = StorableFamily.BOOKCASE, "Journal of Nissyen Edern", bitIndex = 29, Quests.MOURNINGS_END_PART_II), // mourning's end II quest
ExplorersNotes(Items.EXPLORERS_NOTES_11677, intArrayOf(Items.EXPLORERS_NOTES_11677), type = StorableFamily.BOOKCASE, "Beyond Trollheim", bitIndex = 30), // found in keldagrim library
EmbalmingManual(Items.EMBALMING_MANUAL_4686, intArrayOf(Items.EMBALMING_MANUAL_4686), type = StorableFamily.BOOKCASE, "The Little Book of Embalming", bitIndex = 31, Quests.ICTHLARINS_LITTLE_HELPER), // icthlarin's little helper quest
FarmingManual(Items.FARMING_MANUAL_9903, intArrayOf(Items.FARMING_MANUAL_9903), type = StorableFamily.BOOKCASE, "Farmer Gricoller's Farming Manual", bitIndex = 32, Quests.MY_ARMS_BIG_ADVENTURE), // my arm's big adventure quest
FeatheredJournal(Items.FEATHERED_JOURNAL_10179, intArrayOf(Items.FEATHERED_JOURNAL_10179), type = StorableFamily.BOOKCASE, "Feathered Journal of Arthur Artimus", bitIndex = 33, Quests.EAGLES_PEAK), // eagle's peak
GameBook(Items.GAME_BOOK_7681, intArrayOf(Items.GAME_BOOK_7681), type = StorableFamily.BOOKCASE, "Party Pete's Bumper Book of Games", bitIndex = 34), // poh game room. I think the bookshelf is the only source for this
GhrimsBook(Items.GHRIMS_BOOK_3901, intArrayOf(Items.GHRIMS_BOOK_3901), type = StorableFamily.BOOKCASE, "Managing Thine Kingdom for Noobes", bitIndex = 35, Quests.THRONE_OF_MISCELLANIA), // kingdom of misc quest
GiannesCookBook(Items.GIANNES_COOK_BOOK_2167, intArrayOf(Items.GIANNES_COOK_BOOK_2167), type = StorableFamily.BOOKCASE, "Gianne's Cook Book", bitIndex = 36), // gnome retaurant minigame - IMPLEMENTED
GlassblowingBook(Items.GLASSBLOWING_BOOK_11656, intArrayOf(Items.GLASSBLOWING_BOOK_11656), type = StorableFamily.BOOKCASE, "Glassblowing Book", bitIndex = 37), // found at the observatory
GoblinBook(Items.GOBLIN_BOOK_10999, intArrayOf(Items.GOBLIN_BOOK_10999), type = StorableFamily.BOOKCASE, "The Book of the Big High War God", bitIndex = 38), // dropped by goblins - IMPLEMENTED
GoblinSymbolBook(Items.GOBLIN_SYMBOL_BOOK_5009, intArrayOf(Items.GOBLIN_SYMBOL_BOOK_5009), type = StorableFamily.BOOKCASE, "A History of the Goblin Race", bitIndex = 39, Quests.THE_LOST_TRIBE), // lost tribe quest - IMPLEMENTED
ElvenBook(Items.THE_GREAT_DIVIDE_6079, intArrayOf(Items.THE_GREAT_DIVIDE_6079), type = StorableFamily.BOOKCASE, "The Great Divide", bitIndex = 40, Quests.MOURNINGS_END_PART_I), // mourning's end I quest
GuideBook(Items.GUIDE_BOOK_1856, intArrayOf(Items.GUIDE_BOOK_1856), type = StorableFamily.BOOKCASE, "Tourist Guide to Ardougne", bitIndex = 41), // found by estate agent in ardy - IMPLEMENTED
AHandwrittenBook(Items.A_HANDWRITTEN_BOOK_9627, intArrayOf(Items.A_HANDWRITTEN_BOOK_9627), type = StorableFamily.BOOKCASE, "Crystal Singing for Beginners (trans. Oaknock) (Transcribed from the original Elvish by Oaknock the Engineer)", bitIndex = 42, Quests.THE_EYES_OF_GLOUPHRIE), // eyes of glouphrie quest
HermansBook(Items.HERMANS_BOOK_7951, intArrayOf(Items.HERMANS_BOOK_7951), type = StorableFamily.BOOKCASE, "Dionysius: A Legend in His Own Lifetime", bitIndex = 43, Quests.SWAN_SONG), // swan song quest
HistoryOfIban(Items.HISTORY_OF_IBAN_1494, intArrayOf(Items.HISTORY_OF_IBAN_1494), type = StorableFamily.BOOKCASE, "The Tale of Iban", bitIndex = 44, Quests.UNDERGROUND_PASS), // underground pass quest
InstructionManual(Items.INSTRUCTION_MANUAL_5, intArrayOf(Items.INSTRUCTION_MANUAL_5), type = StorableFamily.BOOKCASE, "Dwarf Multicannon Manual", bitIndex = 45, Quests.DWARF_CANNON), // dwarf cannon quest - IMPLEMENTED
JossiksJournal(Items.JOURNAL_3845, intArrayOf(Items.JOURNAL_3845), type = StorableFamily.BOOKCASE, "Jossik's Diary", bitIndex = 46, Quests.HORROR_FROM_THE_DEEP), // horror from the deep quest
LeatherBook(Items.LEATHER_BOOK_7635, intArrayOf(Items.LEATHER_BOOK_7635), type = StorableFamily.BOOKCASE, "Modern Day Morytania", bitIndex = 47, Quests.IN_AID_OF_THE_MYREQUE), // in aid of the myreque quest
Manual(Items.MANUAL_3847, intArrayOf(Items.MANUAL_3847), type = StorableFamily.BOOKCASE, "Lighthouse Manual", bitIndex = 48, Quests.HORROR_FROM_THE_DEEP), // horror from the deep quest
MoonclanManual(Items.MOONCLAN_MANUAL_9078, intArrayOf(Items.MOONCLAN_MANUAL_9078), type = StorableFamily.BOOKCASE, "Basics of Magic", bitIndex = 49, Quests.LUNAR_DIPLOMACY), // lunar diplomacy quest
MyNotes(Items.MY_NOTES_11339, intArrayOf(Items.MY_NOTES_11339), type = StorableFamily.BOOKCASE, "'My notes'", bitIndex = 50), // during barbarian training
OldJournal(Items.OLD_JOURNAL_1493, intArrayOf(Items.OLD_JOURNAL_1493), type = StorableFamily.BOOKCASE, "The Journal of Randas", bitIndex = 51, Quests.UNDERGROUND_PASS), // underground pass quest
OldTome(Items.OLD_TOME_13593, intArrayOf(Items.OLD_TOME_13593), type = StorableFamily.BOOKCASE, "Old Tome", bitIndex = 52, Quests.MEETING_HISTORY), // meeting history quest
PrayerBook(Items.PRAYER_BOOK_10890, intArrayOf(Items.PRAYER_BOOK_10890), type = StorableFamily.BOOKCASE, "Prayer of Deliverance from Poisons", bitIndex = 53, Quests.THE_GREAT_BRAIN_ROBBERY), // the great brain robbery quest
PieRecipeBook(Items.PIE_RECIPE_BOOK_7162, intArrayOf(Items.PIE_RECIPE_BOOK_7162), type = StorableFamily.BOOKCASE, "Pie Recipe Book", bitIndex = 54), // from romily weaklax
PrifddinasHistory(Items.PRIFDDINAS_HISTORY_6073, intArrayOf(Items.PRIFDDINAS_HISTORY_6073), type = StorableFamily.BOOKCASE, "The Creation of Prifddinas", bitIndex = 55), // found in lletya bookshelves
QueenHelpBook(Items.QUEEN_HELP_BOOK_10562, intArrayOf(Items.QUEEN_HELP_BOOK_10562), type = StorableFamily.BOOKCASE, "Queen Help Book", bitIndex = 56), // barbarian assault minigame
RatPitsManual(Items.BOOK_6767, intArrayOf(Items.BOOK_6767), type = StorableFamily.BOOKCASE, "The RatPits Manual", bitIndex = 57, Quests.RATCATCHERS), // rat catchers quest
ScabariteNotes(Items.SCABARITE_NOTES_11975, intArrayOf(Items.SCABARITE_NOTES_11975), type = StorableFamily.BOOKCASE, "My Notes, Archaeological exploration and the cult of Scabaras", bitIndex = 58, Quests.DEALING_WITH_SCABARAS), // dealing with scabaras quest
SecurityBook(Items.SECURITY_BOOK_9003, intArrayOf(Items.SECURITY_BOOK_9003), type = StorableFamily.BOOKCASE, "" + GameWorld.settings!!.name + " Account Security", bitIndex = 59), // from stronghold of security - IMPLEMENTED
ShamansTome(Items.SHAMANS_TOME_729, intArrayOf(Items.SHAMANS_TOME_729), type = StorableFamily.BOOKCASE, "Shaman's Tome", bitIndex = 60, Quests.LEGENDS_QUEST), // legends quest - IMPLEMENTED
TheShieldOfArravBook(Items.BOOK_757, intArrayOf(Items.BOOK_757), type = StorableFamily.BOOKCASE, "The Shield of Arrav", bitIndex = 61, Quests.SHIELD_OF_ARRAV), // shield of arrav quest - IMPLEMENTED
SinkethsDiary(Items.SINKETHS_DIARY_11002, intArrayOf(Items.SINKETHS_DIARY_11002), type = StorableFamily.BOOKCASE, "Sin'keth's Diary", bitIndex = 62, Quests.WHAT_LIES_BELOW), // what lies below quest - IMPLEMENTED
StrongholdNotes(Items.STRONGHOLD_NOTES_9004, intArrayOf(Items.STRONGHOLD_NOTES_9004), type = StorableFamily.BOOKCASE, "Stronghold of Security - Notes", bitIndex = 63), // from stronghold of security - IMPLEMENTED
TarnsDiary(Items.TARNS_DIARY_10587, intArrayOf(Items.TARNS_DIARY_10587), type = StorableFamily.BOOKCASE, "The Diary of Tarn Razorlor", bitIndex = 64), // lair of tarn miniquest
Translation(Items.TRANSLATION_4655, intArrayOf(Items.TRANSLATION_4655), type = StorableFamily.BOOKCASE, "Translation Primer (Four Diamonds Translation)", bitIndex = 65, Quests.DESERT_TREASURE), // desert treasure quest - IMPLEMENTED
TranslationBook(Items.TRANSLATION_BOOK_784, intArrayOf(Items.TRANSLATION_BOOK_784), type = StorableFamily.BOOKCASE, "Gnome-English Translation Dictionary", bitIndex = 66, Quests.THE_EYES_OF_GLOUPHRIE), // eyes of glouphrie quest - IMPLEMENTED
TzhaarTouristGuide(Items.TZHAAR_TOURIST_GUIDE_13244, intArrayOf(Items.TZHAAR_TOURIST_GUIDE_13244), type = StorableFamily.BOOKCASE, "Tzhaar Tourist Guide", bitIndex = 67, Quests.TOKTZ_KET_DILL), // toktz-ket-dill quest
VarmensNotes(Items.VARMENS_NOTES_4616, intArrayOf(Items.VARMENS_NOTES_4616), type = StorableFamily.BOOKCASE, "Notes from Varmen's Expedition to Uzer", bitIndex = 68, Quests.THE_GOLEM), // the golem quest - IMPLEMENTED
WitchesDiary(Items.DIARY_2408, intArrayOf(Items.DIARY_2408), type = StorableFamily.BOOKCASE, "Witch's Diary", bitIndex = 69, Quests.WITCHS_HOUSE), // witch's house quest - IMPLEMENTED
YewnocksNotes(Items.YEWNOCKS_NOTES_11750, intArrayOf(Items.YEWNOCKS_NOTES_11750), type = StorableFamily.BOOKCASE, "Yewnock's Notes on Crystals", bitIndex = 70, Quests.THE_PATH_OF_GLOUPHRIE), // path of glouphrie quest
// Capes - https://runescape.wiki/w/Cape_rack?oldid=848849
LegendsCape(Items.CAPE_OF_LEGENDS_1052, intArrayOf(Items.CAPE_OF_LEGENDS_1052), type = StorableFamily.CAPE_RACK, "Legends cape"),
ObsidianCape(Items.OBSIDIAN_CAPE_6568, intArrayOf(Items.OBSIDIAN_CAPE_6568), type = StorableFamily.CAPE_RACK),
FireCape(Items.FIRE_CAPE_6570, intArrayOf(Items.FIRE_CAPE_6570), type = StorableFamily.CAPE_RACK),
TeamCape(Items.TEAM_1_CAPE_10638, (4315..4413 step 2).toList().toIntArray(), type = StorableFamily.CAPE_RACK, "Wilderness cape"),
GodCape(Items.GUTHIX_CAPE_2413, intArrayOf(Items.GUTHIX_CAPE_2413, Items.SARADOMIN_CAPE_2412, Items.ZAMORAK_CAPE_2414), type = StorableFamily.CAPE_RACK, "God cape"),
AttackCape(Items.ATTACK_CAPE_10639, intArrayOf(Items.ATTACK_CAPE_9747, Items.ATTACK_HOOD_9749, Items.ATTACK_CAPET_9748), type = StorableFamily.CAPE_RACK_SKILL),
DefenceCape(Items.DEFENCE_CAPE_10641, intArrayOf(Items.DEFENCE_CAPE_9753, Items.DEFENCE_CAPET_9754, Items.DEFENCE_HOOD_9755), type = StorableFamily.CAPE_RACK_SKILL),
StrengthCape(Items.STRENGTH_CAPE_10640, intArrayOf(Items.STRENGTH_CAPE_9750, Items.STRENGTH_CAPET_9751, Items.STRENGTH_HOOD_9752), type = StorableFamily.CAPE_RACK_SKILL),
HitpointsCape(Items.HITPOINTS_CAPE_10647, intArrayOf(Items.HITPOINTS_CAPE_9768, Items.HITPOINTS_CAPET_9769, Items.HITPOINTS_HOOD_9770), type = StorableFamily.CAPE_RACK_SKILL),
AgilityCape(Items.AGILITY_CAPE_10648, intArrayOf(Items.AGILITY_CAPE_9771, Items.AGILITY_CAPET_9772, Items.AGILITY_HOOD_9773), type = StorableFamily.CAPE_RACK_SKILL),
CookingCape(Items.COOKING_CAPE_10658, intArrayOf(Items.COOKING_CAPE_9801, Items.COOKING_CAPET_9802, Items.COOKING_HOOD_9803), type = StorableFamily.CAPE_RACK_SKILL),
ConstructionCape(Items.CONSTRUCT_CAPE_10654, intArrayOf(Items.CONSTRUCT_CAPE_9789, Items.CONSTRUCT_CAPET_9790, Items.CONSTRUCT_HOOD_9791), type = StorableFamily.CAPE_RACK_SKILL, "Construction cape"),
CraftingCape(Items.CRAFTING_CAPE_10651, intArrayOf(Items.CRAFTING_CAPE_9780, Items.CRAFTING_CAPET_9781, Items.CRAFTING_HOOD_9782), type = StorableFamily.CAPE_RACK_SKILL),
FarmingCape(Items.FARMING_CAPE_10661, intArrayOf(Items.FARMING_CAPE_9810, Items.FARMING_CAPET_9811, Items.FARMING_HOOD_9812), type = StorableFamily.CAPE_RACK_SKILL),
FiremakingCape(Items.FIREMAKING_CAPE_10659, intArrayOf(Items.FIREMAKING_CAPE_9804, Items.FIREMAKING_CAPET_9805, Items.FIREMAKING_HOOD_9806), type = StorableFamily.CAPE_RACK_SKILL),
FishingCape(Items.FISHING_CAPE_10657, intArrayOf(Items.FISHING_CAPE_9798, Items.FISHING_CAPET_9799, Items.FISHING_HOOD_9800), type = StorableFamily.CAPE_RACK_SKILL),
FletchingCape(Items.FLETCHING_CAPE_10652, intArrayOf(Items.FLETCHING_CAPE_9783, Items.FLETCHING_CAPET_9784, Items.FLETCHING_HOOD_9785), type = StorableFamily.CAPE_RACK_SKILL),
HerbloreCape(Items.HERBLORE_CAPE_10649, intArrayOf(Items.HERBLORE_CAPE_9774, Items.HERBLORE_CAPET_9775, Items.HERBLORE_HOOD_9776), type = StorableFamily.CAPE_RACK_SKILL),
MagicCape(Items.MAGIC_CAPE_10644, intArrayOf(Items.MAGIC_CAPE_9762, Items.MAGIC_CAPET_9763, Items.MAGIC_HOOD_9764), type = StorableFamily.CAPE_RACK_SKILL),
MiningCape(Items.MINING_CAPE_10655, intArrayOf(Items.MINING_CAPE_9792, Items.MINING_CAPET_9793, Items.MINING_HOOD_9794), type = StorableFamily.CAPE_RACK_SKILL),
PrayerCape(Items.PRAYER_CAPE_10643, intArrayOf(Items.PRAYER_CAPE_9759, Items.PRAYER_CAPET_9760, Items.PRAYER_HOOD_9761), type = StorableFamily.CAPE_RACK_SKILL),
RangingCape(Items.RANGING_CAPE_10642, intArrayOf(Items.RANGING_CAPE_9756, Items.RANGING_CAPET_9757, Items.RANGING_HOOD_9758), type = StorableFamily.CAPE_RACK_SKILL),
RunecraftCape(Items.RUNECRAFT_CAPE_10645, intArrayOf(Items.RUNECRAFT_CAPE_9765, Items.RUNECRAFT_CAPET_9766, Items.RUNECRAFTING_HOOD_9767), type = StorableFamily.CAPE_RACK_SKILL, "Runecrafting cape"),
SlayerCape(Items.SLAYER_CAPE_10653, intArrayOf(Items.SLAYER_CAPE_9786, Items.SLAYER_CAPET_9787, Items.SLAYER_HOOD_9788), type = StorableFamily.CAPE_RACK_SKILL),
SmithingCape(Items.SMITHING_CAPE_10656, intArrayOf(Items.SMITHING_CAPE_9795, Items.SMITHING_CAPET_9796, Items.SMITHING_HOOD_9797), type = StorableFamily.CAPE_RACK_SKILL),
ThievingCape(Items.THIEVING_CAPE_10650, intArrayOf(Items.THIEVING_CAPE_9777, Items.THIEVING_CAPET_9778, Items.THIEVING_HOOD_9779), type = StorableFamily.CAPE_RACK_SKILL),
WoodcuttingCape(Items.WOODCUTTING_CAPE_10660, intArrayOf(Items.WOODCUTTING_CAPE_9807, Items.WOODCUT_CAPET_9808, Items.WOODCUTTING_HOOD_9809), type = StorableFamily.CAPE_RACK_SKILL),
HunterCape(Items.HUNTER_CAPE_10646, intArrayOf(Items.HUNTER_CAPE_9948, Items.HUNTER_CAPET_9949, Items.HUNTER_HOOD_9950), type = StorableFamily.CAPE_RACK_SKILL),
QuestCape(Items.QUEST_POINT_CAPE_10662, intArrayOf(Items.QUEST_POINT_CAPE_9813, Items.QUEST_POINT_HOOD_9814), type = StorableFamily.CAPE_RACK_SKILL),
SummoningCape(Items.SUMMONING_CAPE_12524, intArrayOf(Items.SUMMONING_CAPE_12169, Items.SUMMONING_CAPET_12170, Items.SUMMONING_HOOD_12171), type = StorableFamily.CAPE_RACK_SKILL),
SpottedCape(Items.SPOTTED_CAPE_10663, intArrayOf(Items.SPOTTED_CAPE_10663), type = StorableFamily.CAPE_RACK, "Spotted hunting cape"),
SpottierCape(Items.SPOTTIER_CAPE_10664, intArrayOf(Items.SPOTTIER_CAPE_10664), type = StorableFamily.CAPE_RACK, "Spottier hunting cape"),
// Fancy Dress - https://runescape.wiki/w/Fancy_dress_box?oldid=848873
MimeCostume(Items.MIME_MASK_10629, intArrayOf(Items.MIME_MASK_3057, Items.MIME_TOP_3058, Items.MIME_LEGS_3059, Items.MIME_GLOVES_3060, Items.MIME_BOOTS_3061), type = StorableFamily.FANCY_DRESS, "Mime Costume"),
RoyalFrogCostume(Items.PRINCESS_BLOUSE_10630, intArrayOf(Items.PRINCE_TUNIC_6184, Items.PRINCE_LEGGINGS_6185, Items.PRINCESS_BLOUSE_6186, Items.PRINCESS_SKIRT_6187), type = StorableFamily.FANCY_DRESS, "Royal frog costume"),
FrogMask(Items.FROG_MASK_10721, intArrayOf(Items.FROG_MASK_6188), type = StorableFamily.FANCY_DRESS),
ZombieOutfit(Items.ZOMBIE_SHIRT_10631, intArrayOf(Items.ZOMBIE_MASK_7594, Items.ZOMBIE_SHIRT_7592, Items.ZOMBIE_TROUSERS_7593, Items.ZOMBIE_GLOVES_7595, Items.ZOMBIE_BOOTS_7596), type = StorableFamily.FANCY_DRESS, "Zombie Outfit"),
CamoOutfit(Items.CAMO_TOP_10632, intArrayOf(Items.CAMO_HELMET_6656, Items.CAMO_TOP_6654, Items.CAMO_BOTTOMS_6655), type = StorableFamily.FANCY_DRESS, "Camo outfit"),
LederhosenOutfit(Items.LEDERHOSEN_TOP_10633, intArrayOf(Items.LEDERHOSEN_TOP_6180, Items.LEDERHOSEN_SHORTS_6181, Items.LEDERHOSEN_HAT_6182), type = StorableFamily.FANCY_DRESS, "Lederhosen outfit"),
ShadeRobes(Items.SHADE_ROBE_10634, intArrayOf(Items.SHADE_ROBE_546, Items.SHADE_ROBE_548), type = StorableFamily.FANCY_DRESS, "Shade robes"),
// Toys - https://runescape.wiki/w/Toy_box?oldid=848855
BunnyEars(Items.BUNNY_EARS_10734, intArrayOf(Items.BUNNY_EARS_1037), type = StorableFamily.TOY_BOX),
Scythe(Items.SCYTHE_10735, intArrayOf(Items.SCYTHE_1419), type = StorableFamily.TOY_BOX),
YoYo(Items.YO_YO_10733, intArrayOf(Items.YO_YO_4079), type = StorableFamily.TOY_BOX),
RubberChicken(Items.RUBBER_CHICKEN_10732, intArrayOf(Items.RUBBER_CHICKEN_4566), type = StorableFamily.TOY_BOX),
ZombieHead(Items.ZOMBIE_HEAD_10731, intArrayOf(Items.ZOMBIE_HEAD_6722), type = StorableFamily.TOY_BOX),
EasterRing(Items.EASTER_RING_10729, intArrayOf(Items.EASTER_RING_7927), type = StorableFamily.TOY_BOX),
BobbleHat(Items.BOBBLE_HAT_9815, intArrayOf(Items.BOBBLE_HAT_6856), type = StorableFamily.TOY_BOX),
BobbleScarf(Items.BOBBLE_SCARF_9816, intArrayOf(Items.BOBBLE_SCARF_6857), type = StorableFamily.TOY_BOX),
JesterHat(Items.JESTER_HAT_6858, intArrayOf(Items.JESTER_HAT_6858), type = StorableFamily.TOY_BOX),
JesterScarf(Items.JESTER_SCARF_6859, intArrayOf(Items.JESTER_SCARF_6859), type = StorableFamily.TOY_BOX),
TriJesterHat(Items.TRI_JESTER_HAT_6860, intArrayOf(Items.TRI_JESTER_HAT_6860), type = StorableFamily.TOY_BOX),
TriJesterScarf(Items.TRI_JESTER_SCARF_6861, intArrayOf(Items.TRI_JESTER_SCARF_6861), type = StorableFamily.TOY_BOX),
WoollyHat(Items.WOOLLY_HAT_6862, intArrayOf(Items.WOOLLY_HAT_6862), type = StorableFamily.TOY_BOX),
WoollyScarf(Items.WOOLLY_SCARF_6863, intArrayOf(Items.WOOLLY_SCARF_6863), type = StorableFamily.TOY_BOX),
Marionette(Items.BLUE_MARIONETTE_10730, intArrayOf(Items.BLUE_MARIONETTE_6865,Items.GREEN_MARIONETTE_6866,Items.RED_MARIONETTE_6867), type = StorableFamily.TOY_BOX), // this authentically displays as 'blue marionette' in the source video
JackLanternMask(Items.JACK_LANTERN_MASK_10723, intArrayOf(Items.JACK_LANTERN_MASK_9920), type = StorableFamily.TOY_BOX),
SkeletonBoots(Items.SKELETON_BOOTS_10724, intArrayOf(Items.SKELETON_BOOTS_9921), type = StorableFamily.TOY_BOX),
SkeletonGloves(Items.SKELETON_GLOVES_10725, intArrayOf(Items.SKELETON_GLOVES_9922), type = StorableFamily.TOY_BOX),
SkeletonLeggings(Items.SKELETON_LEGGINGS_10726, intArrayOf(Items.SKELETON_LEGGINGS_9923), type = StorableFamily.TOY_BOX),
SkeletonShirt(Items.SKELETON_SHIRT_10727, intArrayOf(Items.SKELETON_SHIRT_9924), type = StorableFamily.TOY_BOX),
SkeletonMask(Items.SKELETON_MASK_10728, intArrayOf(Items.SKELETON_MASK_9925), type = StorableFamily.TOY_BOX),
ReindeerHat(Items.REINDEER_HAT_10722, intArrayOf(Items.REINDEER_HAT_10507), type = StorableFamily.TOY_BOX),
WintumberTree(Items.WINTUMBER_TREE_10508, intArrayOf(Items.WINTUMBER_TREE_10508), type = StorableFamily.TOY_BOX),
ChickenFeet(Items.CHICKEN_FEET_11019, intArrayOf(Items.CHICKEN_FEET_11019), type = StorableFamily.TOY_BOX),
ChickenLegs(Items.CHICKEN_LEGS_11022, intArrayOf(Items.CHICKEN_LEGS_11022), type = StorableFamily.TOY_BOX),
ChickenWings(Items.CHICKEN_WINGS_11020, intArrayOf(Items.CHICKEN_WINGS_11020), type = StorableFamily.TOY_BOX),
ChickenHead(Items.CHICKEN_HEAD_11021, intArrayOf(Items.CHICKEN_HEAD_11021), type = StorableFamily.TOY_BOX),
GrimReaperHood(Items.GRIM_REAPER_HOOD_11789, intArrayOf(Items.GRIM_REAPER_HOOD_11789), type = StorableFamily.TOY_BOX),
SnowGlobe(Items.SNOW_GLOBE_11949, intArrayOf(Items.SNOW_GLOBE_11949), type = StorableFamily.TOY_BOX),
ChocaticeCape(Items.CHOCATRICE_CAPE_12634, intArrayOf(Items.CHOCATRICE_CAPE_12645), type = StorableFamily.TOY_BOX),
WarlockTop(Items.WARLOCK_TOP_14076, intArrayOf(Items.WARLOCK_TOP_14076, Items.WITCH_TOP_14078), type = StorableFamily.TOY_BOX),
WarlockLegs(Items.WARLOCK_LEGS_14077, intArrayOf(Items.WARLOCK_LEGS_14077, Items.WITCH_SKIRT_14079), type = StorableFamily.TOY_BOX),
WarlockCloak(Items.WARLOCK_CLOAK_14081, intArrayOf(Items.WARLOCK_CLOAK_14081, Items.WITCH_CLOAK_14088), type = StorableFamily.TOY_BOX),
SantaCostumeTop(Items.SANTA_COSTUME_TOP_14595, intArrayOf(Items.SANTA_COSTUME_TOP_14595, Items.SANTA_COSTUME_TOP_14600), type = StorableFamily.TOY_BOX),
SantaCostumeLegs(Items.SANTA_COSTUME_LEGS_14603, intArrayOf(Items.SANTA_COSTUME_LEGS_14603, Items.SANTA_COSTUME_LEGS_14604), type = StorableFamily.TOY_BOX),
SantaCostumeGloves(Items.SANTA_COSTUME_GLOVES_14602, intArrayOf(Items.SANTA_COSTUME_GLOVES_14602), type = StorableFamily.TOY_BOX),
SantaCostumeBoots(Items.SANTA_COSTUME_BOOTS_14605, intArrayOf(Items.SANTA_COSTUME_BOOTS_14605), type = StorableFamily.TOY_BOX),
IceAmulet(Items.ICE_AMULET_14596, intArrayOf(Items.ICE_AMULET_14596), type = StorableFamily.TOY_BOX),
Cornucopia(Items.CORNUCOPIA_14570, intArrayOf(Items.CORNUCOPIA_14537), type = StorableFamily.TOY_BOX),
// Treasure Trails - low - https://runescape.wiki/w/Treasure_chest?oldid=848831
TrimmedBlackArmour(Items.BLACK_PLATEBODY_T_10690, intArrayOf(Items.BLACK_FULL_HELMT_2587, Items.BLACK_PLATEBODY_T_2583, Items.BLACK_PLATELEGS_T_2585, Items.BLACK_KITESHIELD_T_2589, Items.BLACK_PLATESKIRT_T_3472), type = StorableFamily.TREASURE_CHEST_LOW, "Trimmed black armour"),
GoldTrimmedBlackArmour(Items.BLACK_PLATEBODY_G_10691, intArrayOf(Items.BLACK_FULL_HELMG_2595, Items.BLACK_PLATEBODY_G_2591, Items.BLACK_PLATELEGS_G_2593, Items.BLACK_KITESHIELD_G_2597, Items.BLACK_PLATESKIRT_G_3473), type = StorableFamily.TREASURE_CHEST_LOW, "Gold-trimmed black armour"),
BlackHeraldicH1(Items.BLACK_HELM_H1_10699, intArrayOf(Items.BLACK_HELM_H1_10306), type = StorableFamily.TREASURE_CHEST_LOW, "Black heraldic helm"),
BlackHeraldicH2(Items.BLACK_HELM_H2_10700, intArrayOf(Items.BLACK_HELM_H2_10308), type = StorableFamily.TREASURE_CHEST_LOW, "Black heraldic helm"),
BlackHeraldicH3(Items.BLACK_HELM_H3_10701, intArrayOf(Items.BLACK_HELM_H3_10310), type = StorableFamily.TREASURE_CHEST_LOW, "Black heraldic helm"),
BlackHeraldicH4(Items.BLACK_HELM_H4_10702, intArrayOf(Items.BLACK_HELM_H4_10312), type = StorableFamily.TREASURE_CHEST_LOW, "Black heraldic helm"),
BlackHeraldicH5(Items.BLACK_HELM_H5_10703, intArrayOf(Items.BLACK_HELM_H5_10314), type = StorableFamily.TREASURE_CHEST_LOW, "Black heraldic helm"),
BlackHeraldicKiteshieldH1(Items.BLACK_SHIELDH1_10665, intArrayOf(Items.BLACK_SHIELDH1_7332), type = StorableFamily.TREASURE_CHEST_LOW, "Black heraldic kiteshield"),
BlackHeraldicKiteshieldH2(Items.BLACK_SHIELDH2_10668, intArrayOf(Items.BLACK_SHIELDH2_7338), type = StorableFamily.TREASURE_CHEST_LOW, "Black heraldic kiteshield"),
BlackHeraldicKiteshieldH3(Items.BLACK_SHIELDH3_10671, intArrayOf(Items.BLACK_SHIELDH3_7344), type = StorableFamily.TREASURE_CHEST_LOW, "Black heraldic kiteshield"),
BlackHeraldicKiteshieldH4(Items.BLACK_SHIELDH4_10674, intArrayOf(Items.BLACK_SHIELDH4_7350), type = StorableFamily.TREASURE_CHEST_LOW, "Black heraldic kiteshield"),
BlackHeraldicKiteshieldH5(Items.BLACK_SHIELDH5_10677, intArrayOf(Items.BLACK_SHIELDH5_7356), type = StorableFamily.TREASURE_CHEST_LOW, "Black heraldic kiteshield"),
TrimmedStuddedArmour(Items.STUDDED_BODY_T_10681, intArrayOf(Items.STUDDED_BODY_T_7364, Items.STUDDED_CHAPS_T_7368), type = StorableFamily.TREASURE_CHEST_LOW, "Fur-trimmed studded leather"),
GoldTrimmedStuddedArmour(Items.STUDDED_BODY_G_10680, intArrayOf(Items.STUDDED_BODY_G_7362, Items.STUDDED_CHAPS_G_7366), type = StorableFamily.TREASURE_CHEST_LOW, "Gold-trimmed studded leather"),
TrimmedWizardRobes(Items.WIZARD_ROBE_T_10687, intArrayOf(Items.WIZARD_HAT_T_7396, Items.WIZARD_ROBE_T_7392, Items.BLUE_SKIRT_T_7388), type = StorableFamily.TREASURE_CHEST_LOW, "Trimmed wizard's robes"),
GoldTrimmedWizardRobes(Items.WIZARD_ROBE_G_10686, intArrayOf(Items.WIZARD_HAT_G_7394, Items.WIZARD_ROBE_G_7390, Items.BLUE_SKIRT_G_7386), type = StorableFamily.TREASURE_CHEST_LOW, "Gold-trimmed wizard's robes"),
WizardBoots(Items.WIZARD_BOOTS_10689, intArrayOf(Items.WIZARD_BOOTS_2579), type = StorableFamily.TREASURE_CHEST_LOW),
TrimmedAmuletOfMagic(Items.AMULET_OF_MAGICT_10738, intArrayOf(Items.AMULET_OF_MAGICT_10366), type = StorableFamily.TREASURE_CHEST_LOW, "Trimmed amulet of magic"),
HighwaymanMask(Items.HIGHWAYMAN_MASK_10692, intArrayOf(Items.HIGHWAYMAN_MASK_2631), type = StorableFamily.TREASURE_CHEST_LOW),
Pantaloons(Items.PANTALOONS_10744, intArrayOf(Items.PANTALOONS_10396), type = StorableFamily.TREASURE_CHEST_LOW),
PowderedWig(Items.A_POWDERED_WIG_10740, intArrayOf(Items.A_POWDERED_WIG_10392), type = StorableFamily.TREASURE_CHEST_LOW),
FlaredTrousers(Items.FLARED_TROUSERS_10742, intArrayOf(Items.FLARED_TROUSERS_10394), type = StorableFamily.TREASURE_CHEST_LOW),
SleepingCap(Items.SLEEPING_CAP_10746, intArrayOf(Items.SLEEPING_CAP_10398), type = StorableFamily.TREASURE_CHEST_LOW),
BlackBeret(Items.BLACK_BERET_10694, intArrayOf(Items.BLACK_BERET_2635), type = StorableFamily.TREASURE_CHEST_LOW),
WhiteBeret(Items.WHITE_BERET_10695, intArrayOf(Items.WHITE_BERET_2637), type = StorableFamily.TREASURE_CHEST_LOW),
BlueBeret(Items.BLUE_BERET_10693, intArrayOf(Items.BLUE_BERET_2633), type = StorableFamily.TREASURE_CHEST_LOW),
BobTheCatShirt(Items.BOB_SHIRT_10714, intArrayOf(Items.BOB_SHIRT_10316), type = StorableFamily.TREASURE_CHEST_LOW, "Bob the Cat shirt (red)"),
BobTheCatShirt1(Items.BOB_SHIRT_10715, intArrayOf(Items.BOB_SHIRT_10318), type = StorableFamily.TREASURE_CHEST_LOW, "Bob the Cat shirt (blue)"),
BobTheCatShirt2(Items.BOB_SHIRT_10716, intArrayOf(Items.BOB_SHIRT_10320), type = StorableFamily.TREASURE_CHEST_LOW, "Bob the Cat shirt (green)"),
BobTheCatShirt3(Items.BOB_SHIRT_10717, intArrayOf(Items.BOB_SHIRT_10322), type = StorableFamily.TREASURE_CHEST_LOW, "Bob the Cat shirt (black)"),
BobTheCatShirt4(Items.BOB_SHIRT_10718, intArrayOf(Items.BOB_SHIRT_10324), type = StorableFamily.TREASURE_CHEST_LOW, "Bob the Cat shirt (purple)"),
RedElegant(Items.ELEGANT_SHIRT_10750, intArrayOf(Items.RED_ELE_SHIRT_10404, Items.RED_ELE_LEGS_10406, Items.RED_ELE_BLOUSE_10424, Items.RED_ELE_SKIRT_10426), type = StorableFamily.TREASURE_CHEST_LOW, "Elegant clothes (red)"),
BlueElegant(Items.ELEGANT_SHIRT_10752, intArrayOf(Items.BLUE_ELE_SHIRT_10408, Items.BLUE_ELE_LEGS_10410, Items.BLUE_ELE_BLOUSE_10428, Items.BLUE_ELE_SKIRT_10430), type = StorableFamily.TREASURE_CHEST_LOW, "Elegant clothes (blue)"),
GreenElegant(Items.ELEGANT_SHIRT_10754, intArrayOf(Items.GREEN_ELE_SHIRT_10412,Items.GREEN_ELE_LEGS_10414, Items.GREEN_ELE_BLOUSE_10432, Items.GREEN_ELE_SKIRT_10434), type = StorableFamily.TREASURE_CHEST_LOW, "Elegant clothes (green)"),
BeretAndMask(Items.BERET_MASK_11278, intArrayOf(Items.BERET_AND_MASK_11282), type = StorableFamily.TREASURE_CHEST_LOW),
BlackCane(Items.BLACK_CANE_13163, intArrayOf(Items.BLACK_CANE_13095), type = StorableFamily.TREASURE_CHEST_LOW),
SpikedHelmet(Items.SPIKED_HELMET_13168, intArrayOf(Items.SPIKED_HELMET_13105), type = StorableFamily.TREASURE_CHEST_LOW),
// Treasure Trails - medium - https://runescape.wiki/w/Treasure_chest?oldid=848831
TrimmedAdamantiteArmour(Items.ADAM_PLATEBODY_T_10697, intArrayOf(Items.ADAM_FULL_HELMT_2605, Items.ADAM_PLATEBODY_T_2599, Items.ADAM_PLATELEGS_T_2601, Items.ADAM_KITESHIELD_T_2603, Items.ADAM_PLATESKIRT_T_3474), type = StorableFamily.TREASURE_CHEST_MED, "Trimmed adamantite armour"),
GoldTrimmedAdamantiteArmour(Items.ADAM_PLATEBODY_G_10698, intArrayOf(Items.ADAM_FULL_HELMG_2613, Items.ADAM_PLATEBODY_G_2607, Items.ADAM_PLATELEGS_G_2609, Items.ADAM_KITESHIELD_G_2611, Items.ADAM_PLATESKIRT_G_3475), type = StorableFamily.TREASURE_CHEST_MED, "Gold-trimmed adamantite armour"),
AdamantHeraldicH1(Items.ADAMANT_HELM_H1_10709, intArrayOf(Items.ADAMANT_HELM_H1_10296), type = StorableFamily.TREASURE_CHEST_MED, "Adamant heraldic helm"),
AdamantHeraldicH2(Items.ADAMANT_HELM_H2_10710, intArrayOf(Items.ADAMANT_HELM_H2_10298), type = StorableFamily.TREASURE_CHEST_MED, "Adamant heraldic helm"),
AdamantHeraldicH3(Items.ADAMANT_HELM_H3_10711, intArrayOf(Items.ADAMANT_HELM_H3_10300), type = StorableFamily.TREASURE_CHEST_MED, "Adamant heraldic helm"),
AdamantHeraldicH4(Items.ADAMANT_HELM_H4_10712, intArrayOf(Items.ADAMANT_HELM_H4_10302), type = StorableFamily.TREASURE_CHEST_MED, "Adamant heraldic helm"),
AdamantHeraldicH5(Items.ADAMANT_HELM_H5_10713, intArrayOf(Items.ADAMANT_HELM_H5_10304), type = StorableFamily.TREASURE_CHEST_MED, "Adamant heraldic helm"),
AdamantKiteshieldH1(Items.ADAMANT_SHIELDH1_10666, intArrayOf(Items.ADAMANT_SHIELDH1_7334), type = StorableFamily.TREASURE_CHEST_MED, "Adamant heraldic kiteshield"),
AdamantKiteshieldH2(Items.ADAMANT_SHIELDH2_10669, intArrayOf(Items.ADAMANT_SHIELDH2_7340), type = StorableFamily.TREASURE_CHEST_MED, "Adamant heraldic kiteshield"),
AdamantKiteshieldH3(Items.ADAMANT_SHIELDH3_10672, intArrayOf(Items.ADAMANT_SHIELDH3_7346), type = StorableFamily.TREASURE_CHEST_MED, "Adamant heraldic kiteshield"),
AdamantKiteshieldH4(Items.ADAMANT_SHIELDH4_10675, intArrayOf(Items.ADAMANT_SHIELDH4_7352), type = StorableFamily.TREASURE_CHEST_MED, "Adamant heraldic kiteshield"),
AdamantKiteshieldH5(Items.ADAMANT_SHIELDH5_10678, intArrayOf(Items.ADAMANT_SHIELDH5_7358), type = StorableFamily.TREASURE_CHEST_MED, "Adamant heraldic kiteshield"),
TrimmedGreenDhide(Items.DHIDE_BODY_T_10683, intArrayOf(Items.DHIDE_BODY_T_7372, Items.DHIDE_CHAPS_T_7380), type = StorableFamily.TREASURE_CHEST_MED, "Trimmed green dragonhide armour"),
GoldTrimmedGreenDhide(Items.DHIDE_BODYG_10682, intArrayOf(Items.DHIDE_BODYG_7370, Items.DHIDE_CHAPS_G_7378), type = StorableFamily.TREASURE_CHEST_MED, "Gold-trimmed green dragonhide armour"),
RangerBoots(Items.RANGER_BOOTS_10696, intArrayOf(Items.RANGER_BOOTS_2577), type = StorableFamily.TREASURE_CHEST_MED),
TrimmedAmuletOfStrength(Items.STRENGTH_AMULETT_10736, intArrayOf(Items.STRENGTH_AMULETT_10364), type = StorableFamily.TREASURE_CHEST_MED, "Trimmed amulet of strength"),
RedHeadband(Items.RED_HEADBAND_10768, intArrayOf(Items.RED_HEADBAND_2645), type = StorableFamily.TREASURE_CHEST_MED),
BlackHeadband(Items.BLACK_HEADBAND_10770, intArrayOf(Items.BLACK_HEADBAND_2647), type = StorableFamily.TREASURE_CHEST_MED),
BrownHeadband(Items.BROWN_HEADBAND_10772, intArrayOf(Items.BROWN_HEADBAND_2649), type = StorableFamily.TREASURE_CHEST_MED),
RedBoater(Items.RED_BOATER_10758, intArrayOf(Items.RED_BOATER_7319), type = StorableFamily.TREASURE_CHEST_MED, "Red straw boater"),
OrangeBoater(Items.ORANGE_BOATER_10760, intArrayOf(Items.ORANGE_BOATER_7321), type = StorableFamily.TREASURE_CHEST_MED, "Orange straw boater"),
GreenBoater(Items.GREEN_BOATER_10762, intArrayOf(Items.GREEN_BOATER_7323), type = StorableFamily.TREASURE_CHEST_MED, "Green straw boater"),
BlueBoater(Items.BLUE_BOATER_10764, intArrayOf(Items.BLUE_BOATER_7325), type = StorableFamily.TREASURE_CHEST_MED, "Blue straw boater"),
BlackBoater(Items.BLACK_BOATER_10766, intArrayOf(Items.BLACK_BOATER_7327), type = StorableFamily.TREASURE_CHEST_MED, "Black straw boater"),
BlackElegant(Items.ELEGANT_SHIRT_10748, intArrayOf(Items.BLACK_ELE_SHIRT_10400, Items.BLACK_ELE_LEGS_10402, Items.WHITE_ELE_BLOUSE_10420, Items.WHITE_ELE_SKIRT_10422), type = StorableFamily.TREASURE_CHEST_MED, "Elegant clothes (black)"),
PurpleElegant(Items.ELEGANT_SHIRT_10756, intArrayOf(Items.PURPLE_ELE_BLOUSE_10436, Items.PURPLE_ELE_SKIRT_10438), type = StorableFamily.TREASURE_CHEST_MED, "Elegant clothes (purple)"),
AdamantCane(Items.ADAMANT_CANE_13164, intArrayOf(Items.ADAMANT_CANE_13097), type = StorableFamily.TREASURE_CHEST_MED),
SheepMask(Items.SHEEP_MASK_13169, intArrayOf(Items.SHEEP_MASK_13107), type = StorableFamily.TREASURE_CHEST_MED),
BatMask(Items.BAT_MASK_13171, intArrayOf(Items.BAT_MASK_13111), type = StorableFamily.TREASURE_CHEST_MED),
WolfMask(Items.WOLF_MASK_13173, intArrayOf(Items.WOLF_MASK_13115), type = StorableFamily.TREASURE_CHEST_MED),
PenguinMask(Items.PENGUIN_MASK_13170, intArrayOf(Items.PENGUIN_MASK_13109), type = StorableFamily.TREASURE_CHEST_MED),
CatMask(Items.CAT_MASK_13172, intArrayOf(Items.CAT_MASK_13113), type = StorableFamily.TREASURE_CHEST_MED),
PithHelmet(Items.PITH_HELMET_13167, intArrayOf(Items.PITH_HELMET_13103), type = StorableFamily.TREASURE_CHEST_MED),
// Treasure Trails - hard - https://runescape.wiki/w/Treasure_chest?oldid=848831
TrimmedRuneArmour(Items.RUNE_PLATEBODY_T_10800, intArrayOf(Items.RUNE_FULL_HELM_T_2627, Items.RUNE_PLATEBODY_T_2623, Items.RUNE_PLATELEGS_T_2625, Items.RUNE_KITESHIELD_T_2629, Items.RUNE_PLATESKIRT_T_3477), type = StorableFamily.TREASURE_CHEST_HIGH, "Trimmed rune armour"),
GoldTrimmedRuneArmour(Items.RUNE_PLATEBODY_G_10798, intArrayOf(Items.RUNE_FULL_HELMG_2619, Items.RUNE_PLATEBODY_G_2615, Items.RUNE_PLATELEGS_G_2617, Items.RUNE_KITESHIELD_G_2621, Items.RUNE_PLATESKIRT_G_3476), type = StorableFamily.TREASURE_CHEST_HIGH, "Gold-trimmed rune armour"),
GildedArmour(Items.GILDED_PLATEBODY_10782, intArrayOf(Items.GILDED_FULL_HELM_3486, Items.GILDED_PLATEBODY_3481, Items.GILDED_PLATELEGS_3483, Items.GILDED_KITESHIELD_3488, Items.GILDED_PLATESKIRT_3485), type = StorableFamily.TREASURE_CHEST_HIGH, "Gold-plated rune armour"),
RuneHelmH1(Items.RUNE_HELM_H1_10704, intArrayOf(Items.RUNE_HELM_H1_10286), type = StorableFamily.TREASURE_CHEST_HIGH, "Rune heraldic helm"),
RuneHelmH2(Items.RUNE_HELM_H2_10705, intArrayOf(Items.RUNE_HELM_H2_10288), type = StorableFamily.TREASURE_CHEST_HIGH, "Rune heraldic helm"),
RuneHelmH3(Items.RUNE_HELM_H3_10706, intArrayOf(Items.RUNE_HELM_H3_10290), type = StorableFamily.TREASURE_CHEST_HIGH, "Rune heraldic helm"),
RuneHelmH4(Items.RUNE_HELM_H4_10707, intArrayOf(Items.RUNE_HELM_H4_10292), type = StorableFamily.TREASURE_CHEST_HIGH, "Rune heraldic helm"),
RuneHelmH5(Items.RUNE_HELM_H5_10708, intArrayOf(Items.RUNE_HELM_H5_10294), type = StorableFamily.TREASURE_CHEST_HIGH, "Rune heraldic helm"),
RuneShieldH1(Items.RUNE_SHIELDH1_10667, intArrayOf(Items.RUNE_SHIELDH1_7336), type = StorableFamily.TREASURE_CHEST_HIGH, "Rune heraldic kiteshield"),
RuneShieldH2(Items.RUNE_SHIELDH2_10670, intArrayOf(Items.RUNE_SHIELDH2_7342), type = StorableFamily.TREASURE_CHEST_HIGH, "Rune heraldic kiteshield"),
RuneShieldH3(Items.RUNE_SHIELDH3_10673, intArrayOf(Items.RUNE_SHIELDH3_7348), type = StorableFamily.TREASURE_CHEST_HIGH, "Rune heraldic kiteshield"),
RuneShieldH4(Items.RUNE_SHIELDH4_10676, intArrayOf(Items.RUNE_SHIELDH4_7354), type = StorableFamily.TREASURE_CHEST_HIGH, "Rune heraldic kiteshield"),
RuneShieldH5(Items.RUNE_SHIELDH5_10679, intArrayOf(Items.RUNE_SHIELDH5_7360), type = StorableFamily.TREASURE_CHEST_HIGH, "Rune heraldic kiteshield"),
ZamorakRuneArmour(Items.ZAMORAK_PLATEBODY_10776, intArrayOf(Items.ZAMORAK_FULL_HELM_2657, Items.ZAMORAK_PLATEBODY_2653, Items.ZAMORAK_PLATELEGS_2655, Items.ZAMORAK_KITESHIELD_2659, Items.ZAMORAK_PLATESKIRT_3478), type = StorableFamily.TREASURE_CHEST_HIGH, "Zamorak rune armour"),
SaradominRuneArmour(Items.SARADOMIN_PLATE_10778, intArrayOf(Items.SARADOMIN_FULL_HELM_2665, Items.SARADOMIN_PLATEBODY_2661, Items.SARADOMIN_PLATELEGS_2663, Items.SARADOMIN_KITESHIELD_2667, Items.SARADOMIN_PLATESKIRT_3479), type = StorableFamily.TREASURE_CHEST_HIGH, "Saradomin rune armour"),
GuthixRuneArmour(Items.GUTHIX_PLATEBODY_10780, intArrayOf(Items.GUTHIX_FULL_HELM_2673, Items.GUTHIX_PLATEBODY_2669, Items.GUTHIX_PLATELEGS_2671, Items.GUTHIX_KITESHIELD_2675, Items.GUTHIX_PLATESKIRT_3480), type = StorableFamily.TREASURE_CHEST_HIGH, "Guthix rune armour"),
TrimmedBlueDhide(Items.DHIDE_BODY_T_10685, intArrayOf(Items.DHIDE_BODY_T_7376, Items.DHIDE_CHAPS_T_7384), type = StorableFamily.TREASURE_CHEST_HIGH, "Trimmed blue dragonhide armour"),
GoldTrimmedBlueDhide(Items.DHIDE_BODY_G_10684, intArrayOf(Items.DHIDE_BODY_G_7374, Items.DHIDE_CHAPS_G_7382), type = StorableFamily.TREASURE_CHEST_HIGH, "Gold-trimmed blue dragonhide armour"),
SaradominBlessedDhide(Items.SARADOMIN_DHIDE_10792, intArrayOf(Items.SARADOMIN_DHIDE_10386, Items.SARADOMIN_CHAPS_10388, Items.SARADOMIN_BRACERS_10384, Items.SARADOMIN_COIF_10390), type = StorableFamily.TREASURE_CHEST_HIGH, "Saradomin blessed dragonhide"),
GuthixBlessedDhide(Items.GUTHIX_DRAGONHIDE_10794, intArrayOf(Items.GUTHIX_DRAGONHIDE_10378, Items.GUTHIX_CHAPS_10380, Items.GUTHIX_BRACERS_10376, Items.GUTHIX_COIF_10382), type = StorableFamily.TREASURE_CHEST_HIGH, "Guthix blessed dragonhide"),
ZamorakBlessedDhide(Items.ZAMORAK_DHIDE_10790, intArrayOf(Items.ZAMORAK_DHIDE_10370, Items.ZAMORAK_CHAPS_10372, Items.ZAMORAK_BRACERS_10368, Items.ZAMORAK_COIF_10374), type = StorableFamily.TREASURE_CHEST_HIGH, "Zamorak blessed dragonhide"),
RobinHoodHat(Items.ROBIN_HOOD_HAT_10796, intArrayOf(Items.ROBIN_HOOD_HAT_2581), type = StorableFamily.TREASURE_CHEST_HIGH),
EnchantedRobes(Items.ENCHANTED_TOP_10688, intArrayOf(Items.ENCHANTED_HAT_7400, Items.ENCHANTED_TOP_7399, Items.ENCHANTED_ROBE_7398), type = StorableFamily.TREASURE_CHEST_HIGH, "Enchanted robes"),
SaradominVestments(Items.SARADOMIN_ROBE_TOP_10784, intArrayOf(Items.SARADOMIN_MITRE_10452, Items.SARADOMIN_ROBE_TOP_10458, Items.SARADOMIN_ROBE_LEGS_10464, Items.SARADOMIN_STOLE_10470, Items.SARADOMIN_CLOAK_10446, Items.SARADOMIN_CROZIER_10440), type = StorableFamily.TREASURE_CHEST_HIGH, "Saradomin vestments"),
ZamorakVestments(Items.ZAMORAK_ROBE_TOP_10786, intArrayOf(Items.ZAMORAK_MITRE_10456, Items.ZAMORAK_ROBE_TOP_10460, Items.ZAMORAK_ROBE_LEGS_10468, Items.ZAMORAK_STOLE_10474, Items.ZAMORAK_CLOAK_10450, Items.ZAMORAK_CROZIER_10444), type = StorableFamily.TREASURE_CHEST_HIGH, "Zamorak vestments"),
GuthixVestments(Items.GUTHIX_ROBE_TOP_10788, intArrayOf(Items.GUTHIX_MITRE_10454, Items.GUTHIX_ROBE_TOP_10462, Items.GUTHIX_ROBE_LEGS_10466, Items.GUTHIX_STOLE_10472, Items.GUTHIX_CLOAK_10448, Items.GUTHIX_CROZIER_10442), type = StorableFamily.TREASURE_CHEST_HIGH, "Guthix vestments"),
TrimmedAmuletOfGlory(Items.AMULET_OF_GLORYT_10719, intArrayOf(Items.AMULET_OF_GLORYT_10362), type = StorableFamily.TREASURE_CHEST_HIGH, "Trimmed amulet of glory"),
PiratesHat(Items.PIRATES_HAT_10774, intArrayOf(Items.PIRATES_HAT_2651), type = StorableFamily.TREASURE_CHEST_HIGH),
CavalierMask(Items.CAVALIER_MASK_11277, intArrayOf(Items.CAVALIER_AND_MASK_11280), type = StorableFamily.TREASURE_CHEST_HIGH),
DarkCavalier(Items.DARK_CAVALIER_10804, intArrayOf(Items.DARK_CAVALIER_2641), type = StorableFamily.TREASURE_CHEST_HIGH, "Dark brown cavalier"),
TanCavalier(Items.TAN_CAVALIER_10802, intArrayOf(Items.TAN_CAVALIER_2639), type = StorableFamily.TREASURE_CHEST_HIGH),
BlackCavalier(Items.BLACK_CAVALIER_10806, intArrayOf(Items.BLACK_CAVALIER_2643), type = StorableFamily.TREASURE_CHEST_HIGH),
RuneCane(Items.RUNE_CANE_13165, intArrayOf(Items.RUNE_CANE_13099), type = StorableFamily.TREASURE_CHEST_HIGH),
TopHat(Items.TOP_HAT_13166, intArrayOf(Items.TOP_HAT_13101), type = StorableFamily.TREASURE_CHEST_HIGH),
// Magic Wardrobe - https://runescape.wiki/w/Magic_wardrobe?oldid=848846
MysticBlueSet(Items.MYSTIC_HAT_10601, intArrayOf(Items.MYSTIC_HAT_4089, Items.MYSTIC_ROBE_TOP_4091, Items.MYSTIC_ROBE_BOTTOM_4093, Items.MYSTIC_GLOVES_4095, Items.MYSTIC_BOOTS_4097), type = StorableFamily.MAGIC_WARDROBE, "Mystic robes"),
MysticDarkSet(Items.MYSTIC_HAT_10602, intArrayOf(Items.MYSTIC_HAT_4099, Items.MYSTIC_ROBE_TOP_4101, Items.MYSTIC_ROBE_BOTTOM_4103, Items.MYSTIC_GLOVES_4105, Items.MYSTIC_BOOTS_4107), type = StorableFamily.MAGIC_WARDROBE, "Dark mystic robes"),
MysticLightSet(Items.MYSTIC_HAT_10603, intArrayOf(Items.MYSTIC_HAT_4109, Items.MYSTIC_ROBE_TOP_4111, Items.MYSTIC_ROBE_BOTTOM_4113, Items.MYSTIC_GLOVES_4115, Items.MYSTIC_BOOTS_4117), type = StorableFamily.MAGIC_WARDROBE, "Light mystic robes"),
SkeletalSet(Items.SKELETAL_HELM_10604, intArrayOf(Items.SKELETAL_HELM_6137, Items.SKELETAL_TOP_6139, Items.SKELETAL_BOTTOMS_6141, Items.SKELETAL_GLOVES_6153, Items.SKELETAL_BOOTS_6147), type = StorableFamily.MAGIC_WARDROBE, "Skeletal"),
InfinitySet(Items.INFINITY_TOP_10605, intArrayOf(Items.INFINITY_HAT_6918, Items.INFINITY_TOP_6916, Items.INFINITY_BOTTOMS_6924, Items.INFINITY_GLOVES_6922, Items.INFINITY_BOOTS_6920), type = StorableFamily.MAGIC_WARDROBE, "Infinity robes"),
SplitbarkSet(Items.SPLITBARK_HELM_10606, intArrayOf(Items.SPLITBARK_HELM_3385, Items.SPLITBARK_BODY_3387, Items.SPLITBARK_LEGS_3389, Items.SPLITBARK_GAUNTLETS_3391, Items.SPLITBARK_BOOTS_3393), type = StorableFamily.MAGIC_WARDROBE, "Splitbark armour"),
GhostlySet(Items.GHOSTLY_BOOTS_10607, intArrayOf(Items.GHOSTLY_ROBE_6107, Items.GHOSTLY_ROBE_6108, Items.GHOSTLY_HOOD_6109, Items.GHOSTLY_GLOVES_6110, Items.GHOSTLY_BOOTS_6106, Items.GHOSTLY_CLOAK_6111), type = StorableFamily.MAGIC_WARDROBE, "Ghostly robes"),
MoonclanSet(Items.MOONCLAN_HAT_10608, intArrayOf(Items.MOONCLAN_HELM_9068, Items.MOONCLAN_ARMOUR_9070, Items.MOONCLAN_SKIRT_9071, Items.MOONCLAN_GLOVES_9072, Items.MOONCLAN_BOOTS_9073), type = StorableFamily.MAGIC_WARDROBE, "Moonclan robes"),
LunarSet(Items.LUNAR_HELM_10609, intArrayOf(Items.LUNAR_HELM_9096, Items.LUNAR_TORSO_9097, Items.LUNAR_LEGS_9098, Items.LUNAR_GLOVES_9099, Items.LUNAR_BOOTS_9100, Items.LUNAR_CAPE_9101, Items.LUNAR_AMULET_9102, Items.LUNAR_RING_9104), type = StorableFamily.MAGIC_WARDROBE, "Lunar robes"),
RunecrafterYellow(Items.RUNECRAFTER_HAT_13656, intArrayOf(Items.RUNECRAFTER_HAT_13615, Items.RUNECRAFTER_ROBE_13614, Items.RUNECRAFTER_SKIRT_13617), type = StorableFamily.MAGIC_WARDROBE, "Runecrafter robes (yellow)"),
RunecrafterGreen(Items.RUNECRAFTER_HAT_13657, intArrayOf(Items.RUNECRAFTER_HAT_13620, Items.RUNECRAFTER_ROBE_13619, Items.RUNECRAFTER_SKIRT_13622), type = StorableFamily.MAGIC_WARDROBE, "Runecrafter robes (green)"),
RunecrafterBlue(Items.RUNECRAFTER_HAT_13658, intArrayOf(Items.RUNECRAFTER_HAT_13625, Items.RUNECRAFTER_ROBE_13624, Items.RUNECRAFTER_SKIRT_13627), type = StorableFamily.MAGIC_WARDROBE, "Runecrafter robes (blue)"),
DagonHaiSet(Items.DAGONHAI_ROBE_TOP_14497, intArrayOf(Items.DAGONHAI_HAT_14499, Items.DAGONHAI_ROBE_TOP_14497, Items.DAGONHAI_ROBE_BOTTOM_14501), type = StorableFamily.MAGIC_WARDROBE, "Dagon'Hai Robes"),
// Armour Case - https://runescape.wiki/w/Armour_case?oldid=848835
DecorativeArmour(Items.DECORATIVE_ARMOUR_10610, intArrayOf(Items.DECORATIVE_ARMOUR_4069, Items.DECORATIVE_SWORD_4068, Items.DECORATIVE_ARMOUR_4070, Items.DECORATIVE_HELM_4071, Items.DECORATIVE_SHIELD_4072, Items.DECORATIVE_ARMOUR_4504, Items.DECORATIVE_SWORD_4503, Items.DECORATIVE_ARMOUR_4505, Items.DECORATIVE_HELM_4506, Items.DECORATIVE_SHIELD_4507, Items.DECORATIVE_ARMOUR_4509, Items.DECORATIVE_SWORD_4508, Items.DECORATIVE_ARMOUR_4510, Items.DECORATIVE_HELM_4511, Items.DECORATIVE_SHIELD_4512), type = StorableFamily.ARMOUR_CASE, "Castlewars armour"), // should be any one colour set per wiki
VoidKnightArmour(Items.VOID_KNIGHT_TOP_10611, intArrayOf(Items.VOID_KNIGHT_TOP_8839, Items.VOID_KNIGHT_ROBE_8840, Items.VOID_KNIGHT_GLOVES_8842), type = StorableFamily.ARMOUR_CASE, "Void Knight armour"),
RogueArmour(Items.ROGUE_MASK_10612, intArrayOf(Items.ROGUE_MASK_5554, Items.ROGUE_TOP_5553, Items.ROGUE_TROUSERS_5555), type = StorableFamily.ARMOUR_CASE, "Rogue armour"),
SpinedArmour(Items.SPINED_HELM_10614, intArrayOf(Items.SPINED_HELM_6131, Items.SPINED_BODY_6133, Items.SPINED_CHAPS_6135, Items.SPINED_BOOTS_6143, Items.SPINED_GLOVES_6149), type = StorableFamily.ARMOUR_CASE, "Spined armour"),
RockshellArmour(Items.ROCK_SHELL_HELM_10613, intArrayOf(Items.ROCK_SHELL_HELM_6128, Items.ROCK_SHELL_PLATE_6129, Items.ROCK_SHELL_LEGS_6130, Items.ROCK_SHELL_BOOTS_6145, Items.ROCK_SHELL_GLOVES_6151), type = StorableFamily.ARMOUR_CASE, "Rockshell armour"),
TribalMaskP(Items.TRIBAL_MASK_10615, intArrayOf(Items.TRIBAL_MASK_6335), type = StorableFamily.ARMOUR_CASE, "Broodoo mask (poison)"),
TribalMaskD(Items.TRIBAL_MASK_10616, intArrayOf(Items.TRIBAL_MASK_6337), type = StorableFamily.ARMOUR_CASE, "Broodoo mask (disease)"),
TribalMaskC(Items.TRIBAL_MASK_10617, intArrayOf(Items.TRIBAL_MASK_6339), type = StorableFamily.ARMOUR_CASE, "Broodoo mask (combat)"),
WhiteKnightArmour(Items.WHITE_PLATEBODY_10618, intArrayOf(Items.WHITE_PLATEBODY_6617, Items.WHITE_FULL_HELM_6623, Items.WHITE_PLATELEGS_6625, Items.WHITE_PLATESKIRT_6627), type = StorableFamily.ARMOUR_CASE, "White knight armour"),
TempleKnightInitiateArmour(Items.INITIATE_HAUBERK_10619, intArrayOf(Items.INITIATE_SALLET_5574, Items.INITIATE_HAUBERK_5575, Items.INITIATE_CUISSE_5576), type = StorableFamily.ARMOUR_CASE, "Temple Knight initiate armour"),
TempleKnightProselyteArmour(Items.PROSELYTE_HAUBERK_10620, intArrayOf(Items.PROSELYTE_SALLET_9672, Items.PROSELYTE_HAUBERK_9674, Items.PROSELYTE_CUISSE_9676, Items.PROSELYTE_TASSET_9678), type = StorableFamily.ARMOUR_CASE, "Temple Knight proselyte armour"),
MournerGear(Items.MOURNER_TOP_10621, intArrayOf(Items.MOURNER_TOP_6065, Items.MOURNER_TROUSERS_6066, Items.MOURNER_BOOTS_6069, Items.MOURNER_CLOAK_6070), type = StorableFamily.ARMOUR_CASE, "Mourner gear"),
GraahkHunterGear(Items.GRAAHK_TOP_10624, intArrayOf(Items.GRAAHK_TOP_10049, Items.GRAAHK_LEGS_10047, Items.GRAAHK_HEADDRESS_10051), type = StorableFamily.ARMOUR_CASE, "Graahk hunter gear"),
LarupiaHunterGear(Items.LARUPIA_TOP_10623, intArrayOf(Items.LARUPIA_TOP_10043, Items.LARUPIA_LEGS_10041, Items.LARUPIA_HAT_10045), type = StorableFamily.ARMOUR_CASE, "Larupia hunter gear"),
KyattHunterGear(Items.KYATT_TOP_10622, intArrayOf(Items.KYATT_TOP_10037, Items.KYATT_LEGS_10035, Items.KYATT_HAT_10039), type = StorableFamily.ARMOUR_CASE, "Kyatt hunter gear"),
PolarCamouflageGear(Items.POLAR_CAMO_TOP_10628, intArrayOf(Items.POLAR_CAMO_TOP_10628, Items.POLAR_CAMO_LEGS_10067), type = StorableFamily.ARMOUR_CASE, "Polar camouflage gear"),
JungleCamouflageGear(Items.JUNGLE_CAMO_TOP_10626, intArrayOf(Items.JUNGLE_CAMO_TOP_10057, Items.JUNGLE_CAMO_LEGS_10059), type = StorableFamily.ARMOUR_CASE, "Jungle camouflage gear"),
WoodCamouflageGear(Items.WOOD_CAMO_TOP_10625, intArrayOf(Items.WOOD_CAMO_TOP_10053, Items.WOOD_CAMO_LEGS_10055), type = StorableFamily.ARMOUR_CASE, "Wood camouflage gear"),
DesertCamouflageGear(Items.DESERT_CAMO_TOP_10627, intArrayOf(Items.DESERT_CAMO_TOP_10061, Items.DESERT_CAMO_LEGS_10063), type = StorableFamily.ARMOUR_CASE, "Desert camouflage gear"),
BuildersCostume(Items.BUILDERS_SHIRT_10863, intArrayOf(Items.BUILDERS_SHIRT_10863, Items.BUILDERS_TROUSERS_10864, Items.BUILDERS_BOOTS_10865), type = StorableFamily.ARMOUR_CASE, "Builder's costume"),
LumberjackCostume(Items.LUMBERJACK_TOP_10945, intArrayOf(Items.LUMBERJACK_TOP_10939, Items.LUMBERJACK_LEGS_10940, Items.LUMBERJACK_HAT_10941), type = StorableFamily.ARMOUR_CASE, "Lumberjack costume"),
BomberJacketCostume(Items.BOMBER_JACKET_11135, intArrayOf(Items.BOMBER_JACKET_9944, Items.BOMBER_CAP_9945), type = StorableFamily.ARMOUR_CASE, "Bomber jacket costume"),
HAMRobes(Items.HAM_SHIRT_11274, intArrayOf(Items.HAM_SHIRT_4298, Items.HAM_ROBE_4300, Items.HAM_HOOD_4302, Items.HAM_CLOAK_4304, Items.HAM_LOGO_4306, Items.BOOTS_4310, Items.GLOVES_4308), type = StorableFamily.ARMOUR_CASE, "H.A.M. robes"),
VoidMelee(Items.VOID_MELEE_HELM_11676, intArrayOf(Items.VOID_MELEE_HELM_11665), type = StorableFamily.ARMOUR_CASE, "Void melee helm"),
VoidRanger(Items.VOID_RANGER_HELM_11675, intArrayOf(Items.VOID_RANGER_HELM_11664), type = StorableFamily.ARMOUR_CASE, "Void ranger helm"),
VoidMage(Items.VOID_MAGE_HELM_11674, intArrayOf(Items.VOID_MAGE_HELM_11663), type = StorableFamily.ARMOUR_CASE, "Void mage helm"),
BlackNaval(Items.BLACK_NAVAL_SHIRT_13185, intArrayOf(Items.BLACK_NAVAL_SHIRT_8956, Items.BLACK_NAVY_SLACKS_8995, Items.BLACK_TRICORN_HAT_8963),type = StorableFamily.ARMOUR_CASE, "Black naval costume"),
RedNaval(Items.RED_NAVAL_SHIRT_13183, intArrayOf(Items.RED_TRICORN_HAT_8961, Items.RED_NAVAL_SHIRT_8954, Items.RED_NAVY_SLACKS_8993), type = StorableFamily.ARMOUR_CASE, "Red naval costume"),
BlueNaval(Items.BLUE_NAVAL_SHIRT_13181, intArrayOf(Items.BLUE_TRICORN_HAT_8959, Items.BLUE_NAVAL_SHIRT_8952, Items.BLUE_NAVY_SLACKS_8991), type = StorableFamily.ARMOUR_CASE, "Blue naval costume"),
BrownNaval(Items.BROWN_NAVAL_SHIRT_13184, intArrayOf(Items.BROWN_TRICORN_HAT_8962, Items.BROWN_NAVAL_SHIRT_8955, Items.BROWN_NAVY_SLACKS_8994), type = StorableFamily.ARMOUR_CASE, "Brown naval costume"),
GreenNaval(Items.GREEN_NAVAL_SHIRT_13182, intArrayOf(Items.GREEN_TRICORN_HAT_8960, Items.GREEN_NAVAL_SHIRT_8953, Items.GREEN_NAVY_SLACKS_8992), type = StorableFamily.ARMOUR_CASE, "Green naval costume"),
GreyNaval(Items.GREY_NAVAL_SHIRT_13187, intArrayOf(Items.GREY_TRICORN_HAT_8965, Items.GREY_NAVAL_SHIRT_8958, Items.GREY_NAVY_SLACKS_8997), type = StorableFamily.ARMOUR_CASE, "Grey naval costume"),
PurpleNaval(Items.PURPLE_NAVAL_SHIRT_13186, intArrayOf(Items.PURPLE_TRICORN_HAT_8964, Items.PURPLE_NAVAL_SHIRT_8957, Items.PURPLE_NAVY_SLACKS_8996), type = StorableFamily.ARMOUR_CASE, "Purple naval costume"),
EliteBlackArmour(Items.ELITE_BLACK_PLATEBODY_14492, intArrayOf(Items.ELITE_BLACK_FULL_HELM_14494, Items.ELITE_BLACK_PLATEBODY_14492, Items.ELITE_BLACK_PLATELEGS_14490), type = StorableFamily.ARMOUR_CASE, "Elite Black Armour")
;
companion object {
/**
* Map of all item ids to their [Storable].
*/
private val idToStorable: Map<Int, Storable> by lazy {
values().flatMap { storable ->
storable.takeIds.map { id -> id to storable }
}.toMap()
}
/**
* Set of all item ids across all [Storable].
*/
private val allItemIds: Set<Int> by lazy { idToStorable.keys }
/**
* Precomputed set of item ids for each Storable for fast lookup.
*/
private val storableIdSets: Map<Storable, Set<Int>> by lazy {
values().associateWith { it.takeIds.toSet() }
}
/**
* Returns all item ids from all storables.
*/
fun allIds(): Set<Int> = allItemIds
/**
* Finds the storable for the given item id.
*/
fun fromId(id: Int): Storable? = idToStorable[id]
/**
* Checks if the player owns at least one item from
* the storable in equipment or bank.
*/
fun hasStorable(player: Player, storable: Storable): Boolean {
val ownedIds = (player.equipment.toArray() + player.bank.toArray())
.mapNotNull { it?.id }
.toSet()
return storableIdSets[storable]?.any { it in ownedIds } ?: false
}
/**
* Checks if the player owns any item
* from any storable in equipment or bank.
*/
fun hasAnyStorable(player: Player): Boolean {
val ownedIds = (player.equipment.toArray() + player.bank.toArray())
.mapNotNull { it?.id }
.toSet()
return allItemIds.any { it in ownedIds }
}
/**
* Checks if the player has at least one
* item from the given storable equipped.
*/
fun hasStorableEquipped(player: Player, storable: Storable): Boolean {
val equippedIds = player.equipment.toArray().mapNotNull { it?.id }.toSet()
return storableIdSets[storable]?.any { it in equippedIds } ?: false
}
/**
* Checks if the player has at least one
* item from the given storable in inventory.
*/
fun hasStorableInInventory(player: Player, storable: Storable): Boolean {
val inventoryIds = player.inventory.toArray().mapNotNull { it?.id }.toSet()
return storableIdSets[storable]?.any { it in inventoryIds } ?: false
}
/**
* Gets all [Storable]s that the player owns in their bank.
*/
fun getStorablesInBank(player: Player): List<Storable> {
val bankIds = player.bank.toArray().mapNotNull { it?.id }.toSet()
return values().filter { storable ->
storableIdSets[storable]?.any { it in bankIds } ?: false
}
}
}
}
@@ -0,0 +1,18 @@
package content.global.skill.construction.decoration.pohstorage
/**
* Types (families) of storables in Construction's POH Costume Room or Bookcase.
*/
enum class StorableFamily {
FANCY_DRESS,
ARMOUR_CASE,
MAGIC_WARDROBE,
TREASURE_CHEST_LOW,
TREASURE_CHEST_MED,
TREASURE_CHEST_HIGH,
BOOKCASE,
CAPE_RACK,
CAPE_RACK_SKILL,
TOY_BOX
}
@@ -0,0 +1,74 @@
package content.global.skill.construction.decoration.pohstorage
import org.json.simple.JSONArray
import org.json.simple.JSONObject
/**
* This represents the container where all the POH storable items are.
*/
class StorageContainer {
// item IDs stored in this container
private val stored: MutableSet<Int> = mutableSetOf()
// page index for the active interface
private var currentPage: Int = 0
// functions to add item, withdraw, check for, etc.
fun addItem(id: Int) {
stored.add(id)
}
fun withdraw(id: Int): Boolean {
return stored.remove(id)
}
fun contains(id: Int): Boolean = id in stored
fun getItems(): List<Int> = stored.toList()
// paging
fun getPageIndex(): Int = currentPage
fun nextPage(totalItems: Int, pageSize: Int) {
val next = currentPage + 1
if (next * pageSize < totalItems) {
currentPage = next
}
}
fun prevPage() {
if (currentPage > 0) {
currentPage--
}
}
fun resetPage() {
currentPage = 0
}
// saving to json and reading back
fun toJson(): JSONObject {
val json = JSONObject()
val items = JSONArray()
stored.forEach { items.add(it) }
json["items"] = items
return json
}
fun readJson(json: JSONObject) {
stored.clear()
val items = json["items"] as? JSONArray ?: return
for (id in items) {
stored.add((id as Number).toInt())
}
currentPage = 0
}
// clear storage
fun clear() {
stored.clear()
currentPage = 0
}
}
@@ -0,0 +1,385 @@
package content.global.skill.construction.decoration.pohstorage
import core.api.*
import core.game.interaction.InterfaceListener
import core.game.node.entity.player.Player
import core.game.node.item.Item
import core.net.packet.PacketRepository
import core.net.packet.context.ContainerContext
import core.net.packet.out.ContainerPacket
import org.rs09.consts.Items
/**
* Handles the interface and interactions for
* the Construction Costume Room storage boxes and Bookshelves
* https://youtu.be/tAIDQRh4U2g?si=oDaKEJT-ZPs4MoZa&t=261
* https://www.youtube.com/watch?v=D2aVfpPCAgY&t=41s - great view of all the treasure chests
* https://youtu.be/AK6wVwumvHY?si=XAJsQeSdRlBZSM1f - mid-level costume room
* https://www.youtube.com/watch?v=sNEOq_Barlg&t=126s - 2011, but a good video
*/
class StorageInterface : InterfaceListener {
companion object {
private const val INTERFACE = 467
private const val COMPONENT = 164
private const val SIZE = 30
private const val PAGE_SIZE = SIZE - 1
private const val BUTTON_MORE = Items.MORE_10165
private const val BUTTON_BACK = Items.BACK_10166
/**
* Singleton instance of the storage interface.
*/
lateinit var instance: StorageInterface
private set
/**
* Public method to get the storage container for a specific family.
*/
fun getStorageContainer(player: Player, type: StorableFamily) =
player.getPOHStorageState().getContainer(type)
/**
* Opens the storage box.
*
* @param player The player.
* @param type The type of storage to open.
* @param box the specific tier of storage we are opening.
*/
fun openStorage(player: Player, type: StorableFamily, box: StorageOptionPlugin.StorageBox) {
instance.openStorageForType(player, type, box)
}
/**
* Finds the Storable enum entry associated with a given display item ID.
*/
fun findStorableByDisplayId(id: Int): Storable? =
Storable.values().find { it.displayId == id }
}
/**
* The attribute of whether the player has read a book or not is stored in a set of two bitfields.
* There are 70 books so that's why two longs are needed.
*/
object BookcaseBitfields {
// 0-63
const val BOOKCASE_LOW_ATTR = "/save:bookcase_bitfield_low"
// 64-127
const val BOOKCASE_HIGH_ATTR = "/save:bookcase_bitfield_high"
data class BookcaseBits(var low: Long, var high: Long)
// Retrieves both Long bitfields.
fun get(player: Player): BookcaseBits {
val low = getAttribute(player, BOOKCASE_LOW_ATTR, 0L) as? Long ?: 0L
val high = getAttribute(player, BOOKCASE_HIGH_ATTR, 0L) as? Long ?: 0L
return BookcaseBits(low, high)
}
// Sets both Long bitfields.
fun set(player: Player, bits: BookcaseBits) {
setAttribute(player, BOOKCASE_LOW_ATTR, bits.low)
setAttribute(player, BOOKCASE_HIGH_ATTR, bits.high)
}
// Checks if a specific bit is set within the two Longs.
fun isBitSet(bits: BookcaseBits, bitIndex: Int): Boolean {
return if (bitIndex < 64) {
(bits.low and (1L shl bitIndex)) != 0L
} else {
val highIndex = bitIndex - 64
(bits.high and (1L shl highIndex)) != 0L
}
}
// Sets a specific bit within the two Longs.
fun setBit(bits: BookcaseBits, bitIndex: Int): BookcaseBits {
if (bitIndex < 64) {
bits.low = bits.low or (1L shl bitIndex)
} else {
val highIndex = bitIndex - 64
bits.high = bits.high or (1L shl highIndex)
}
return bits
}
}
init {
instance = this
}
override fun defineInterfaceListeners() {
on(INTERFACE) { player, _, _, buttonId, _, _ ->
val typeName = getAttribute(player, "con:storage:type", null) as? String ?: return@on true
val type = StorableFamily.valueOf(typeName.uppercase())
handleStorageInteraction(player, buttonId, type)
return@on true
}
}
/**
* Gets the StorableFamilies that should be displayed in the interface for a given primary type.
* This handles the special case for the Cape Rack because capes are classified as two different families/types
* (normal and skillcape) but displayed in the same storage.
*/
private fun getFamiliesToDisplay(primaryType: StorableFamily): List<StorableFamily> {
return when (primaryType) {
// if the cape rack can hold skill capes, also show normal capes
StorableFamily.CAPE_RACK_SKILL -> listOf(StorableFamily.CAPE_RACK, StorableFamily.CAPE_RACK_SKILL)
// else only display the items for the primary family
else -> listOf(primaryType)
}
}
/**
* This handles filtering the list for books.
* Only items are shown that have already been read.
* If the book is part of a quest, the quest must be complete before it shows up in the POH.
* If the index is -1 it is not a book, and not filtered.
*/
private fun getFilteredItems(player: Player, families: List<StorableFamily>, type: StorableFamily): List<Storable> {
// filter for normal items
var allItems = Storable.values().filter { it.type in families }
// filter for bookcase
if (type == StorableFamily.BOOKCASE) {
val bookcaseBits = BookcaseBitfields.get(player)
allItems = allItems.filter { storable ->
// check if read
val isRead = if (storable.bitIndex == -1) true else BookcaseBitfields.isBitSet(bookcaseBits, storable.bitIndex)
// check if quest is done
val questMet = storable.questReq == null || isQuestComplete(player, storable.questReq)
isRead && questMet
}
}
return allItems
}
/**
* Handles interaction with storage interface.
*/
private fun handleStorageInteraction(player: Player, buttonId: Int, type: StorableFamily) {
val container = getStorageContainer(player, type)
val families = getFamiliesToDisplay(type)
val allItems = getFilteredItems(player, families, type)
val pageIndex = container.getPageIndex()
val pageItems = allItems.drop(pageIndex * PAGE_SIZE).take(PAGE_SIZE)
val slots = MutableList<Any?>(SIZE) { null }
var idx = 0
pageItems.forEach {
if (idx >= SIZE) return@forEach
slots[idx++] = it
}
val hasPrev = pageIndex > 0
val hasNext = allItems.size > (pageIndex + 1) * PAGE_SIZE
if (hasNext && idx < SIZE) slots[idx++] = "MORE"
if (hasPrev && idx < SIZE) slots[idx] = "BACK"
val slotIndex = when {
buttonId in 56..(56 + (SIZE - 1) * 2) step 2 -> (buttonId - 56) / 2
buttonId in 165..223 step 2 -> (buttonId - 165) / 2
else -> return
}
when (val clicked = slots.getOrNull(slotIndex)) {
"MORE" -> {
container.nextPage(allItems.size, PAGE_SIZE)
openInterface(player, INTERFACE)
updateStorageInterface(player, type)
}
"BACK" -> {
container.prevPage()
openInterface(player, INTERFACE)
updateStorageInterface(player, type)
}
is Storable -> processItemTransaction(player, clicked, type)
}
}
/**
* Handles taking or depositing an item from/to the storage.
*/
private fun processItemTransaction(player: Player, item: Storable, type: StorableFamily) {
// container for the interface/capacity check based on the object the player clicked
val interfaceContainer = getStorageContainer(player, type)
// container for the actual storage/retrieval based on the item's family
val targetContainer = getStorageContainer(player, item.type)
// get stored items from the target container
val storedItems = targetContainer.getItems().toSet()
val box = getAttribute(player, "con:storage:box", null) as? StorageOptionPlugin.StorageBox ?: return
val capacity = box.capacity
// gets the currently stored IDs
val storedTakeIds = item.takeIds.filter { it in storedItems }
if (storedTakeIds.isNotEmpty()) {
// withdraw items
if (freeSlots(player) < storedTakeIds.size) {
sendMessage(player, "You don't have enough inventory space.")
return
}
sendMessage(player, "You take the item from the box.")
storedTakeIds.forEach { id ->
addItem(player, id, 1)
targetContainer.withdraw(id)
}
} else {
// deposit items
// capacity check
if (capacity < Int.MAX_VALUE) {
val familyToCheck = when (type) {
// for cape racks, only skillcapes are counted against the capacity
StorableFamily.CAPE_RACK_SKILL -> StorableFamily.CAPE_RACK_SKILL
// for all other storage, check against the primary family type for capacity
else -> type
}
// check if skillcape
if (item.type == familyToCheck) {
val capacityCheckItems = interfaceContainer.getItems().toSet()
// calculate stored skillcapes
val storedSetsCount = Storable.values()
.filter { it.type == familyToCheck }
.count { storable ->
storable.takeIds.any { it in capacityCheckItems }
}
// enforce capacity limits
if (storedSetsCount >= capacity) {
val message = when (familyToCheck) {
StorableFamily.CAPE_RACK_SKILL -> "You cannot store any more skillcapes in this container."
else -> "You cannot store any more items in this container."
}
sendMessage(player, message)
return
}
}
}
val presentTakeIds = item.takeIds.filter { player.inventory.contains(it, 1) }
if (presentTakeIds.isEmpty()) {
sendMessage(player, "You don't have that item in your inventory.")
return
}
sendMessage(player, "You put the item into the box.")
presentTakeIds.forEach { id ->
removeItem(player, Item(id))
targetContainer.addItem(id)
}
}
updateStorageInterface(player, type)
}
private fun updateStorageInterface(player: Player, type: StorableFamily) {
val container = getStorageContainer(player, type)
val families = getFamiliesToDisplay(type)
val allItems = getFilteredItems(player, families, type)
val allStoredItems = mutableSetOf<Int>()
families.forEach { family ->
val familyContainer = getStorageContainer(player, family)
allStoredItems.addAll(familyContainer.getItems())
}
val stored = allStoredItems.toSet()
val pageIndex = container.getPageIndex()
val pageItems = allItems.drop(pageIndex * PAGE_SIZE).take(PAGE_SIZE)
val slots = MutableList<Any?>(SIZE) { null }
var idx = 0
pageItems.forEach {
if (idx >= SIZE) return@forEach
slots[idx++] = it
}
val hasPrev = pageIndex > 0
val hasNext = allItems.size > (pageIndex + 1) * PAGE_SIZE
if (hasNext && idx < SIZE) slots[idx++] = "MORE"
if (hasPrev && idx < SIZE) slots[idx] = "BACK"
// names each box
val title = when {
type.name.contains("FANCY_DRESS") -> "Fancy dress box"
type.name.contains("TOY_BOX") -> "Toy box"
type.name.contains("MAGIC_WARDROBE") -> "Magic wardrobe"
type.name.contains("ARMOUR_CASE") -> "Armour case"
type.name.contains("BOOKCASE") -> "Bookcase"
type.name.contains("CAPE_RACK") -> "Cape rack"
type.name.contains("TREASURE_CHEST_LOW") -> "Low-level Treasure Trail rewards"
type.name.contains("TREASURE_CHEST_MED") -> "Medium-level Treasure Trail rewards"
type.name.contains("TREASURE_CHEST_HIGH") -> "High-level Treasure Trail rewards"
// to catch any other name
else -> type.name.lowercase().replaceFirstChar(Char::titlecase) + " box"
}
// child 225 is the title block
setInterfaceText(player, title, INTERFACE, 225)
val itemsArray = slots.mapNotNull {
when (it) {
is Storable -> Item(it.displayId)
"MORE" -> Item(BUTTON_MORE)
"BACK" -> Item(BUTTON_BACK)
else -> null
}
}.toTypedArray()
PacketRepository.send(
ContainerPacket::class.java,
ContainerContext(player, INTERFACE, COMPONENT, SIZE, itemsArray, false)
)
repeat(SIZE) { i ->
val nameComponent = 55 + i * 2
val iconComponent = 165 + i * 2
val hiddenIconComponent = 166 + i * 2
val obj = slots[i]
val (name, isStored) = when (obj) {
is Storable -> {
val isStored = obj.takeIds.any { it in stored }
obj.setName to isStored
}
"MORE" -> "More..." to true
"BACK" -> "Back..." to true
else -> "" to false
}
setInterfaceText(player, name, INTERFACE, nameComponent)
setComponentVisibility(player, INTERFACE, nameComponent, false)
if (obj is Storable) {
// this displays if the items are stored or not stored
setComponentVisibility(player, INTERFACE, iconComponent, !isStored)
setComponentVisibility(player, INTERFACE, hiddenIconComponent, isStored)
} else {
// this is for navigation buttons or empty slots
setComponentVisibility(player, INTERFACE, iconComponent, true)
setComponentVisibility(player, INTERFACE, hiddenIconComponent, true)
}
}
}
private fun openStorageForType(player: Player, type: StorableFamily, box: StorageOptionPlugin.StorageBox) {
setAttribute(player, "con:storage:type", type.name)
setAttribute(player, "con:storage:box", box)
openInterface(player, INTERFACE)
updateStorageInterface(player, type)
}
}
@@ -0,0 +1,182 @@
package content.global.skill.construction.decoration.pohstorage
import core.api.*
import core.cache.def.impl.SceneryDefinition
import core.game.interaction.OptionHandler
import core.game.node.Node
import core.game.node.entity.player.Player
import core.game.node.scenery.Scenery
import core.plugin.Initializable
import core.plugin.Plugin
import org.rs09.consts.Animations
import org.rs09.consts.Items
import org.rs09.consts.Sounds
import org.rs09.consts.Scenery as Obj
/**
* Handles interactions with storage objects.
*/
@Initializable
class StorageOptionPlugin : OptionHandler() {
/**
* Represents all storage boxes & configuration.
*/
enum class StorageBox(val objectIds: IntArray, val storableFamily: StorableFamily? = null, val openable: Boolean = false, val closable: Boolean = false, val capacity: Int = Int.MAX_VALUE) {
/* Bookcase
* Wooden: all
* Oak: all
* Mahogany: all */
BOOKCASE(intArrayOf(Obj.BOOKCASE_13597, Obj.BOOKCASE_13598, Obj.BOOKCASE_13599), StorableFamily.BOOKCASE),
/* Cape Rack
* Oak: normal capes
* Teak: 1 skillcape
* Mahogany: 5 skillcapes
* Gilded: 10 skillcapes
* Marble: all
* Magic: all */
CAPE_RACK(intArrayOf(Obj.OAK_CAPE_RACK_18766), StorableFamily.CAPE_RACK),
CAPE_RACK_ONE(intArrayOf(Obj.TEAK_CAPE_RACK_18767), StorableFamily.CAPE_RACK_SKILL, capacity = 1),
CAPE_RACK_FIVE(intArrayOf(Obj.MAHOGANY_CAPE_RACK_18768), StorableFamily.CAPE_RACK_SKILL, capacity = 5),
CAPE_RACK_TEN(intArrayOf(Obj.GILDED_CAPE_RACK_18769), StorableFamily.CAPE_RACK_SKILL, capacity = 10),
CAPE_RACK_ALL(intArrayOf(Obj.MARBLE_CAPE_RACK_18770, Obj.MAGIC_CAPE_RACK_18771), StorableFamily.CAPE_RACK_SKILL),
/* Fancy Dress Box
* Oak: 2
* Teak: 4
* Mahogany: all */
FANCY_BOX_2(intArrayOf(Obj.FANCY_DRESS_BOX_18773), StorableFamily.FANCY_DRESS, false, true, 2),
FANCY_BOX_4(intArrayOf(Obj.FANCY_DRESS_BOX_18775), StorableFamily.FANCY_DRESS, false, true, 4),
FANCY_BOX_ALL(intArrayOf(Obj.FANCY_DRESS_BOX_18777), StorableFamily.FANCY_DRESS, false, true),
FANCY_BOX_OPEN(intArrayOf(Obj.FANCY_DRESS_BOX_18772, Obj.FANCY_DRESS_BOX_18774, Obj.FANCY_DRESS_BOX_18776), openable = true),
/* Toy Box
* Oak: all
* Teak: all
* Mahogany: all */
TOY_BOX_OPEN(intArrayOf(Obj.TOY_BOX_18798, Obj.TOY_BOX_18800, Obj.TOY_BOX_18802), openable = true),
TOY_BOX_CLOSE(intArrayOf(Obj.TOY_BOX_18799, Obj.TOY_BOX_18801, Obj.TOY_BOX_18803), closable = true, storableFamily = StorableFamily.TOY_BOX),
/* Treasure Chest
* Oak: Low
* Teak: Low, Medium
* Mahogany: Low, Medium, High */
TREASURE_LOW(intArrayOf(Obj.TREASURE_CHEST_18805), StorableFamily.TREASURE_CHEST_LOW),
TREASURE_MED(intArrayOf(Obj.TREASURE_CHEST_18807), StorableFamily.TREASURE_CHEST_MED),
TREASURE_HIGH(intArrayOf(Obj.TREASURE_CHEST_18809), StorableFamily.TREASURE_CHEST_HIGH),
TREASURE_OPEN(intArrayOf(Obj.TREASURE_CHEST_18804, Obj.TREASURE_CHEST_18806, Obj.TREASURE_CHEST_18808), openable = true),
/* Magic Wardobe
* Oak: 1 set
* Carved oak: 2 sets
* Teak: 3 sets
* Carved Teak: 4 sets
* Mahogany: 5 sets
* Gilded: 6 sets
* Marble: all sets */
WARDROBE_1(intArrayOf(Obj.MAGIC_WARDROBE_18785), StorableFamily.MAGIC_WARDROBE, false, true, 1),
WARDROBE_2(intArrayOf(Obj.MAGIC_WARDROBE_18787), StorableFamily.MAGIC_WARDROBE, false, true, 2),
WARDROBE_3(intArrayOf(Obj.MAGIC_WARDROBE_18789), StorableFamily.MAGIC_WARDROBE, false, true, 3),
WARDROBE_4(intArrayOf(Obj.MAGIC_WARDROBE_18791), StorableFamily.MAGIC_WARDROBE, false, true, 4),
WARDROBE_5(intArrayOf(Obj.MAGIC_WARDROBE_18793), StorableFamily.MAGIC_WARDROBE, false, true, 5),
WARDROBE_6(intArrayOf(Obj.MAGIC_WARDROBE_18795), StorableFamily.MAGIC_WARDROBE, false, true, 6),
WARDROBE_ALL(intArrayOf(Obj.MAGIC_WARDROBE_18797), StorableFamily.MAGIC_WARDROBE),
WARDROBE_OPEN(intArrayOf(Obj.MAGIC_WARDROBE_18784, Obj.MAGIC_WARDROBE_18786, Obj.MAGIC_WARDROBE_18788, Obj.MAGIC_WARDROBE_18790, Obj.MAGIC_WARDROBE_18792, Obj.MAGIC_WARDROBE_18794, Obj.MAGIC_WARDROBE_18796), openable = true),
/* Armour Case
* Oak: 2
* Teak: 4
* Mahogany: all */
ARMOUR_2(intArrayOf(Obj.ARMOUR_CASE_18779), StorableFamily.ARMOUR_CASE, false, true, 2),
ARMOUR_4(intArrayOf(Obj.ARMOUR_CASE_18781), StorableFamily.ARMOUR_CASE, false, true, 4),
ARMOUR_ALL(intArrayOf(Obj.ARMOUR_CASE_18783), StorableFamily.ARMOUR_CASE),
ARMOUR_OPEN(intArrayOf(Obj.ARMOUR_CASE_18778, Obj.ARMOUR_CASE_18780, Obj.ARMOUR_CASE_18782), openable = true)
}
private val allBoxes = StorageBox.values()
override fun newInstance(arg: Any?): Plugin<Any> {
allBoxes.forEach { box ->
box.objectIds.forEach { id ->
SceneryDefinition.forId(id)?.let { def ->
if (box.storableFamily != null) def.handlers["option:search"] = this
if (box.openable) def.handlers["option:open"] = this
if (box.closable) def.handlers["option:close"] = this
}
}
}
return this
}
override fun handle(player: Player, node: Node, option: String): Boolean {
val obj = node as Scenery
val box = allBoxes.firstOrNull { obj.id in it.objectIds } ?: return true
when (option) {
"search" -> {
when (obj.id) {
// special handling for med and high level treasure chests since they have a dialogue option first
Obj.TREASURE_CHEST_18807 -> {
sendDialogueOptions(player, "What sort of Treasure Trail items do you want to see?", "Low-level rewards", "Medium-level rewards")
addDialogueAction(player) { p, button ->
val family = when (button) {
2 -> StorableFamily.TREASURE_CHEST_LOW
3 -> StorableFamily.TREASURE_CHEST_MED
else -> null
}
family?.let { StorageInterface.openStorage(p, it, box) }
return@addDialogueAction
}
}
Obj.TREASURE_CHEST_18809 -> {
sendDialogueOptions(player, "What sort of Treasure Trail items do you want to see?", "Low-level rewards", "Medium-level rewards", "High-level rewards")
addDialogueAction(player) { p, button ->
val family = when (button) {
2 -> StorableFamily.TREASURE_CHEST_LOW
3 -> StorableFamily.TREASURE_CHEST_MED
4 -> StorableFamily.TREASURE_CHEST_HIGH
else -> null
}
family?.let { StorageInterface.openStorage(p, it, box) }
return@addDialogueAction
}
}
// bookcases need to re-store the games book in them on open. I think the bookshelf is the only source.
Obj.BOOKCASE_13597 -> {
storeBookInHouse(player, Items.GAME_BOOK_7681)
box.storableFamily?.let { StorageInterface.openStorage(player, it, box) }
}
Obj.BOOKCASE_13598 -> {
storeBookInHouse(player, Items.GAME_BOOK_7681)
box.storableFamily?.let { StorageInterface.openStorage(player, it, box) }
}
Obj.BOOKCASE_13599 -> {
storeBookInHouse(player, Items.GAME_BOOK_7681)
box.storableFamily?.let { StorageInterface.openStorage(player, it, box) }
}
else -> {
// default open for rest
box.storableFamily?.let { StorageInterface.openStorage(player, it, box) }
}
}
}
"open" -> if (box.openable) openBox(player, obj)
"close" -> if (box.closable) closeBox(player, obj)
}
return true
}
private fun openBox(player: Player, obj: Scenery) {
playAudio(player, Sounds.CHEST_OPEN_52)
animate(player, Animations.HUMAN_OPEN_CHEST_536)
replaceScenery(obj, obj.id + 1, -1)
}
private fun closeBox(player: Player, obj: Scenery) {
playAudio(player, Sounds.CHEST_CLOSE_51)
animate(player, 538)
replaceScenery(obj, obj.id - 1, -1)
}
}
@@ -0,0 +1,37 @@
package content.global.skill.construction.decoration.pohstorage
import org.json.simple.JSONObject
import core.game.node.entity.player.Player
class StorageState(val player: Player) {
private val containers = StorableFamily.values().associateWith { StorageContainer() }.toMutableMap()
fun getContainer(type: StorableFamily): StorageContainer = containers[type]!!
fun toJson(): JSONObject {
val root = JSONObject()
val containerData = JSONObject()
containers.forEach { (group, container) ->
containerData[group.name.lowercase()] = container.toJson()
}
root["containers"] = containerData
return root
}
fun readJson(data: JSONObject) {
val containerData = data["containers"] as? JSONObject ?: return
for (group in StorableFamily.values()) {
val json = containerData[group.name.lowercase()] as? JSONObject ?: continue
containers[group]?.readJson(json)
}
}
// clear storage
fun clear() {
containers.values.forEach { it.clear() }
}
}
@@ -5,6 +5,7 @@ import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -129,8 +130,9 @@ class WitchesDiaryBook : InteractionListener {
}
override fun defineListeners() {
on(Items.DIARY_2408, IntType.ITEM, "read") { player, _ ->
on(Items.DIARY_2408, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -4,6 +4,7 @@ import content.global.handlers.iface.BookInterface
import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -177,8 +178,9 @@ class TranslationBook : InteractionListener {
}
override fun defineListeners() {
on(Items.TRANSLATION_4655, IntType.ITEM, "read") { player, _ ->
on(Items.TRANSLATION_4655, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -5,6 +5,7 @@ import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.api.storeBookInHouse
import core.game.interaction.InteractionListener
import core.game.interaction.IntType
import core.game.node.entity.player.Player
@@ -288,8 +289,9 @@ class VarmensNotes : InteractionListener {
}
override fun defineListeners() {
on(Items.VARMENS_NOTES_4616, IntType.ITEM, "read") { player, _ ->
on(Items.VARMENS_NOTES_4616, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -5,6 +5,7 @@ import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -141,8 +142,9 @@ class BindingBook : InteractionListener {
}
override fun defineListeners() {
on(Items.BINDING_BOOK_730, IntType.ITEM, "read") { player, _ ->
on(Items.BINDING_BOOK_730, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -5,6 +5,7 @@ import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -330,8 +331,9 @@ class GiannesCookBook : InteractionListener {
}
override fun defineListeners() {
on(Items.GIANNES_COOK_BOOK_2167, IntType.ITEM, "read") { player, _ ->
on(Items.GIANNES_COOK_BOOK_2167, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -5,6 +5,7 @@ import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -47,8 +48,9 @@ class ShamansTomeBook : InteractionListener {
}
override fun defineListeners() {
on(Items.SHAMANS_TOME_729, IntType.ITEM, "read") { player, _ ->
on(Items.SHAMANS_TOME_729, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -5,6 +5,7 @@ import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -87,8 +88,9 @@ class DMCManual : InteractionListener {
override fun defineListeners() {
on(Items.INSTRUCTION_MANUAL_5, IntType.ITEM, "read") { player, _ ->
on(Items.INSTRUCTION_MANUAL_5, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -5,6 +5,7 @@ import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -131,8 +132,9 @@ class TranslationBook : InteractionListener {
}
override fun defineListeners() {
on(Items.TRANSLATION_BOOK_784, IntType.ITEM, "read") { player, _ ->
on(Items.TRANSLATION_BOOK_784, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -2,6 +2,7 @@ package content.region.kandarin.quest.observatoryquest
import content.global.handlers.iface.*
import core.ServerConstants
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -161,8 +162,9 @@ class AstronomyBook : InteractionListener {
}
override fun defineListeners() {
on(Items.ASTRONOMY_BOOK_600, IntType.ITEM, "read") { player, _ ->
on(Items.ASTRONOMY_BOOK_600, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -6,6 +6,7 @@ import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -152,8 +153,9 @@ class BaxtorianBook : InteractionListener {
}
override fun defineListeners() {
on(Items.BOOK_ON_BAXTORIAN_292, IntType.ITEM, "read") { player, _ ->
on(Items.BOOK_ON_BAXTORIAN_292, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -7,6 +7,7 @@ import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
import org.rs09.consts.Items
import content.data.Quests
import core.api.*
/**
* Battered book handler for the Elemental Workshop I quest
@@ -31,8 +32,9 @@ class BatteredBookHandler : InteractionListener {
}
override fun defineListeners() {
on(Items.BATTERED_BOOK_2886, IntType.ITEM, "read") { player, _ ->
on(Items.BATTERED_BOOK_2886, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -5,6 +5,7 @@ import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -116,8 +117,9 @@ class ArdougneGuideBook : InteractionListener {
}
override fun defineListeners() {
on(Items.GUIDE_BOOK_1856, IntType.ITEM, "read") { player, _ ->
on(Items.GUIDE_BOOK_1856, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -5,6 +5,7 @@ import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -70,8 +71,9 @@ class ChemicalsBook : InteractionListener {
}
override fun defineListeners() {
on(Items.BOOK_ON_CHEMICALS_711, IntType.ITEM, "read") { player, _ ->
on(Items.BOOK_ON_CHEMICALS_711, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -13,6 +13,7 @@ import core.game.node.item.Item
import core.plugin.Initializable
import core.plugin.Plugin
import org.rs09.consts.Items
import core.api.*
private val BOOK = Item(Items.GOBLIN_SYMBOL_BOOK_5009)
@Initializable
@@ -38,7 +39,7 @@ class LostTribeOptionHandler : OptionHandler(){
node ?: return false
when(node.id){
5008 -> player.interfaceManager.open(Component(50))
5009 -> player.interfaceManager.open(Component(183))
5009 -> player.interfaceManager.open(Component(183)).also { storeBookInHouse(player, node) }
6916 -> {
if(!player.inventory.containsItem(BOOK) && !player.bank.containsItem(BOOK) && player.questRepository.getQuest(
Quests.THE_LOST_TRIBE).getStage(player) >= 41){
@@ -5,6 +5,7 @@ import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -192,10 +193,11 @@ class SinkethsDiary : InteractionListener {
}
override fun defineListeners() {
on(Items.SINKETHS_DIARY_11002, IntType.ITEM, "read") { player, _ ->
on(Items.SINKETHS_DIARY_11002, IntType.ITEM, "read") { player, node ->
setAttribute(player, "bookInterfaceCallback", ::display)
setAttribute(player, "bookInterfaceCurrentPage", 0)
display(player, 0, 0)
storeBookInHouse(player, node)
return@on true
}
}
@@ -4,7 +4,7 @@ import content.global.handlers.iface.BookInterface
import content.global.handlers.iface.BookLine
import content.global.handlers.iface.Page
import content.global.handlers.iface.PageSet
import core.api.setAttribute
import core.api.storeBookInHouse
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.entity.player.Player
@@ -371,8 +371,9 @@ class AbyssalBook : InteractionListener {
}
override fun defineListeners() {
on(Items.ABYSSAL_BOOK_5520, IntType.ITEM, "read") { player, _ ->
on(Items.ABYSSAL_BOOK_5520, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
@@ -11,6 +11,7 @@ import org.rs09.consts.Items
import content.data.Quests
import core.api.getQuestStage
import core.api.setQuestStage
import core.api.storeBookInHouse
/**
* Shield of Arrav Book
@@ -97,8 +98,9 @@ class ShieldofArravBook : InteractionListener {
}
override fun defineListeners() {
on(Items.BOOK_757, IntType.ITEM, "read") { player, _ ->
on(Items.BOOK_757, IntType.ITEM, "read") { player, node ->
BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display)
storeBookInHouse(player, node)
return@on true
}
}
+79
View File
@@ -5,6 +5,8 @@ import content.data.Quests
import content.data.consumables.*
import content.data.skill.SkillingTool
import content.global.handlers.iface.ge.StockMarket
import content.global.skill.construction.decoration.pohstorage.StorableFamily
import content.global.skill.construction.decoration.pohstorage.StorageInterface
import content.global.skill.slayer.SlayerEquipmentFlags
import content.global.skill.slayer.SlayerFlags
import content.global.skill.slayer.SlayerManager
@@ -287,6 +289,7 @@ fun hasAnItem(player: Player, id: Int, checkSecondBank: Boolean): ContainerisedI
* @return A ContainerisedItem containing the container and the item ID if found, otherwise ContainerisedItem(null, -1) if not found
*/
fun hasAnItem(player: Player, ids: Array<Int>, checkSecondBank: Boolean): ContainerisedItem {
// Search bank/equip/inv and return if item is found there.
val searchSpace = if (checkSecondBank) {
arrayOf(player.inventory, player.equipment, player.bankPrimary, player.bankSecondary)
} else {
@@ -299,6 +302,20 @@ fun hasAnItem(player: Player, ids: Array<Int>, checkSecondBank: Boolean): Contai
}
}
}
// Check all costume storage containers and return if item is found there.
for (id in ids) {
if (StorableFamily.values().any { family ->
player.getPOHStorageState()
.getContainer(family)
.contains(id)
}) {
// Note that this proxy container is a fake container just used to get the correct return type.
return ContainerisedItem(player.POHStorageProxyContainer, id)
}
}
// Item is not found.
return ContainerisedItem(null, -1)
}
@@ -2377,6 +2394,68 @@ fun Player.getCutscene(): Cutscene? {
return getAttribute<Cutscene?>(this, Cutscene.ATTRIBUTE_CUTSCENE, null)
}
/**
* Stores a book in the POH storage
* If this is the first time reading the book, store it in POH bookcase if eligible
* @param player the player
* @param itemID the ID of the book that was interacted with
* @return True if the item was identified and processed (whether read or not)
* False if the node was not a book or did not have a bitIndex
*/
fun storeBookInHouse(player: Player, itemId: Int): Boolean {
// find the corresponding Storable object
val storable = StorageInterface.findStorableByDisplayId(itemId) ?: return false
// check if the storable is tracked
if (storable.bitIndex == -1) {
return true
}
// retrieve bitfields where the values of already-read books are stored, and if the book has been read previously
val bits = StorageInterface.BookcaseBitfields.get(player)
val isRead = StorageInterface.BookcaseBitfields.isBitSet(bits, storable.bitIndex)
// special check for games book. It isn't normally available and is only obtainable from the bookcase. You can't read it without first having it, and if you lose it you can't go back to somewhere else to get a new copy. Thus, it is always spawned in.
if(itemId == Items.GAME_BOOK_7681) {
val updatedBits = StorageInterface.BookcaseBitfields.setBit(bits, storable.bitIndex)
val container = StorageInterface.getStorageContainer(player, StorableFamily.BOOKCASE)
if (!isRead) {
StorageInterface.BookcaseBitfields.set(player, updatedBits)
container.addItem(itemId)
} else {
container.addItem(itemId)
}
return true
}
// if book is not read, store it in house
if (!isRead) {
// update bitfields
val updatedBits = StorageInterface.BookcaseBitfields.setBit(bits, storable.bitIndex)
StorageInterface.BookcaseBitfields.set(player, updatedBits)
// add to the POH container
val container = StorageInterface.getStorageContainer(player, StorableFamily.BOOKCASE)
container.addItem(itemId)
}
return true
}
/**
* Stores a book in the POH storage
* If this is the first time reading the book, store it in POH bookcase if eligible
* Used inside interaction listeners ("read") on nodes (books)
* @param player the player
* @param node the node (book) that was interacted with
* @return True if the item was identified and processed (whether read or not)
* False if the node was not a book or did not have a bitIndex
*/
fun storeBookInHouse(player: Player, node: Node): Boolean {
val bookId = (node as? Item)?.id ?: return false
return storeBookInHouse(player, bookId)
}
fun Player.getCutsceneStage(): Int {
return getAttribute(this, Cutscene.ATTRIBUTE_CUTSCENE_STAGE, 0)
}
@@ -81,6 +81,7 @@ fun permadeath(target: Player) {
// House data
target.houseManager.createNewHouseAt(HouseLocation.NOWHERE)
target.getPOHStorageState().clear()
// Achievements
for (type in DiaryType.values()) {
@@ -1,5 +1,6 @@
package core.game.global;
import content.global.skill.construction.decoration.pohstorage.StorableFamily;
import core.game.container.Container;
import core.game.dialogue.FacialExpression;
import core.game.dialogue.FacialExpression;
@@ -66,6 +67,25 @@ public final class Skillcape {
}
}
}
// trim any skillcapes in the player's POH storage
content.global.skill.construction.decoration.pohstorage.StorageState poh = player.getPOHStorageState();
content.global.skill.construction.decoration.pohstorage.StorageContainer rack = poh.getContainer(StorableFamily.CAPE_RACK_SKILL);
// count the capes
java.util.List<Integer> toReplace = new java.util.ArrayList<>();
for (int itemId : rack.getItems()) {
if (getCapeIndex(new Item(itemId)) != -1) {
toReplace.add(itemId);
}
}
// replace the untrimmed IDs with trimmed IDs
for (int oldId : toReplace) {
int skillIndex = getCapeIndex(new Item(oldId));
rack.withdraw(oldId);
rack.addItem(getTrimmed(skillIndex).getId());
}
}
/**
@@ -2,6 +2,8 @@ package core.game.node.entity.player;
import content.global.handlers.item.equipment.BarrowsEquipment;
import content.global.handlers.item.equipment.special.SalamanderSwingHandler;
import content.global.skill.construction.decoration.pohstorage.POHStorageProxyContainer;
import content.global.skill.construction.decoration.pohstorage.StorageState;
import content.global.skill.runecrafting.PouchManager;
import content.global.skill.slayer.SlayerEquipmentFlags;
import core.api.ContentAPIKt;
@@ -129,6 +131,21 @@ public class Player extends Entity {
public Boolean isAfkLogout;
private StorageState POHStorageState;
public final Container POHStorageProxyContainer = new POHStorageProxyContainer(this);
public StorageState getPOHStorageState() {
if (POHStorageState == null) {
POHStorageState = new StorageState(this);
}
return POHStorageState;
}
public void setPOHStorageState(StorageState state) {
this.POHStorageState = state;
}
/**
* The inventory.
*/
@@ -16,7 +16,6 @@ import core.ServerConstants
import core.api.log
import core.game.node.entity.combat.graves.GraveController
import core.game.node.entity.combat.graves.GraveType
import core.game.world.GameWorld
import core.tools.Log
import java.io.File
import java.io.FileReader
@@ -74,6 +73,7 @@ class PlayerSaveParser(val player: Player) {
parseStatistics()
parseAchievements()
parsePouches()
parsePOHStorable()
}
fun runContentHooks()
@@ -93,6 +93,12 @@ class PlayerSaveParser(val player: Player) {
player.pouchManager.parse(saveFile!!["pouches"] as JSONArray)
}
fun parsePOHStorable() {
val storableRaw = saveFile?.get("pohstorage") ?: return
val storableJson = storableRaw as JSONObject
player.getPOHStorageState().readJson(storableJson)
}
fun parseAttributes() {
if (saveFile!!.containsKey("attributes")) {
val attrData = saveFile!!["attributes"] as JSONArray
@@ -55,6 +55,7 @@ class PlayerSaver (val player: Player){
saveStatManager(saveFile)
saveAttributes(saveFile)
savePouches(saveFile)
savePOHStorageRoom(saveFile)
contentHooks.forEach { it.savePlayer(player, saveFile) }
return saveFile
}
@@ -95,6 +96,10 @@ class PlayerSaver (val player: Player){
player.pouchManager.save(root)
}
fun savePOHStorageRoom(root: JSONObject) {
root.put("pohstorage", player.getPOHStorageState().toJson())
}
fun saveVersion(root: JSONObject){
root.put("version", player.version)
}
@@ -4,6 +4,7 @@ import core.plugin.Initializable
import core.game.system.command.Command
import core.game.system.command.Privilege
import core.api.*
import core.game.interaction.QueueStrength
@Initializable
class ConfigCommandSet : CommandSet(Privilege.ADMIN){
@@ -50,5 +51,27 @@ class ConfigCommandSet : CommandSet(Privilege.ADMIN){
val id = args[1].toIntOrNull() ?: reject(player, "INVALID INTERFACE ID")
player.interfaceManager.openComponent(id as Int)
}
define("ifaces", Privilege.ADMIN, "::ifaces <From Interface ID> <To Interface ID> <(opt) Duration Per Interface>", "Opens interfaces from the From ID to the To ID, with a delay of 3 between each, unless specified otherwise"){ player, args ->
if (args.size < 3) {
reject(player, "Syntax error: ::anims <From Interface ID> <To Interface ID> <(opt) Duration Per Interface>")
}
val ifaceFrom = args[1].toInt()
val ifaceTo = args[2].toInt()
val ifaceDelay = (args.getOrNull(3) ?: "3").toInt()
queueScript(player, 1, QueueStrength.WEAK) { stage: Int ->
val ifaceId = ifaceFrom + stage
player.interfaceManager.openComponent(ifaceId)
notify(player, "Opening interface $ifaceId")
if (ifaceId == ifaceTo) {
return@queueScript stopExecuting(player)
}
return@queueScript delayScript(player, ifaceDelay)
}
}
}
}
@@ -1,6 +1,8 @@
package core.game.system.command.sets
import content.global.activity.jobs.JobManager
import content.global.skill.construction.decoration.pohstorage.Storable
import content.global.skill.construction.decoration.pohstorage.StorableFamily
import core.api.*
import core.cache.Cache
import core.cache.def.impl.DataMap
@@ -54,6 +56,57 @@ class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) {
}
}
/**
* Adds a subset of POH storable item takeIds to the player's bank, filtered by StorableFamily type. These are stored in the costume room or bookcase.
* Usage: ::pohstorableitems [TYPE]
* Example: ::pohstorableitems BOOKCASE
* Example: ::pohstorableitems TREASURE_CHEST_MED
*/
define("pohstorableitems", Privilege.ADMIN, "::pohstorableitems TYPE", "Adds POH storable items to bank. To see TYPEs, use the command with no argument.") { player, args ->
// Check for the argument (StorableFamily type)
val typeArg = if (args.size > 1) args[1].uppercase() else null
val allStorables = Storable.values()
// Filter the list of items based on the provided argument
val itemsToProcess = if (typeArg != null) {
try {
val targetFamily = StorableFamily.valueOf(typeArg)
allStorables.filter { it.type == targetFamily }
} catch (e: IllegalArgumentException) {
sendMessage(player, "Error: Unknown StorableFamily type '$typeArg'. Valid types are:")
StorableFamily.values().forEach { family ->
sendMessage(player, family.toString())
}
return@define
}
} else {
// If no argument is provided, default to all storables
sendMessage(player, "Valid types are:")
StorableFamily.values().forEach { family ->
sendMessage(player, family.toString())
}
return@define
}
if (itemsToProcess.isEmpty()) {
sendMessage(player, "No storable items found for type: '$typeArg'.")
return@define
}
var totalItemsAdded = 0
// Process the filtered list of storables
itemsToProcess.forEach { storable ->
storable.takeIds.forEach { itemId ->
if (player.bank.add(Item(itemId))) {
totalItemsAdded++
}
}
}
return@define
}
define("cs2", Privilege.ADMIN, "::cs2 id args", "Allows you to call arbitrary cs2 scripts during runtime") {player, args ->
var scriptArgs = ArrayList<Any>()
if (args.size == 2) {
+80 -1
View File
@@ -10,10 +10,15 @@
9 ahoy_runedraw
10 ahoy_windspeed
11 bank_deposit_box
12 pulsing yellow rectangle
13 bankpin_main
14 bankpin_settings
15 banner_padlock_keys
16 banner_anti_virus
17 nothing
18 heads for the welcome screen?
19 heads for the welcome screen?
20 heads for the welcome screen?
21 banner_scamming
22 banner_security
23 banner_xmas
@@ -85,6 +90,7 @@
89 stab slash lunge block
90 staff with autocast
91 darts or knives
92 nothing
93 whip
94 confirm_destroy
95 sailing_transport_world_map
@@ -315,6 +321,7 @@
320 stats
321 swamp_boatjourney
322 swamp_boatjourney_back
323 tai bwo wannai parcel service
324 tanner
325 target
326 teleport_other
@@ -458,7 +465,7 @@
464 emotes
465 ooglog ogress opens health spa
466 switch diagram
467 menu with big buttons
467 POH_costume room
468 diango item return
469 zep_balloon_map
470 zep_interface
@@ -511,6 +518,7 @@
517 turn off black
518 old follower inv?
519 objbox
520 dream_cyrisus's courage
521 dream_cyrisus
522 dream_monster_stat
523 dream_player_stats
@@ -565,6 +573,7 @@
572 cws_warning_13
573 cws_warning_12
574 cws_warning_1
575 all fired up beacon status
576 cws_warning_8
577 cws_warning_18
578 cws_warning_15
@@ -575,6 +584,8 @@
583 cws_doomsayer
584 kr_king_statue
585 kr_pyramid_escape
586 change of address form for the citizens of seers' village
587 meet at camel left half
588 kr_picklock
589 clanjoin
590 clansetup
@@ -694,7 +705,21 @@
704 p safety 6
705 p safety 7
706 fuck this
707 player safety
708 player safety
709 player safety
710 player safety
711 player safety
712 meet at legend's guild from kent
713 slight darkness overlay
714 nothing
715 banner_easter08
716 summoning_chocatrice
717 incubator controls
718 select an option
719 operating your incubator
720 chocatrice hatching
721 chocatrice hatching
722 summoning_side
723 carpet_info
724 carpet_runesquares
@@ -703,6 +728,18 @@
727 carpet_draughts
728 carpet_main
729 carpet_ticket
730 bounty hunter?
731 bounty hunter?
732 fog rewards shop
733 ivandis flail
734 fairy ring main
735 fairy ring log
736 dragon hatchling forge
737 pick a kitten
738 dillo_tok-xil-im
739 dillo_tzhaar numbers
740 open your stats prompt
741 congratulations!
742 graphics_options
743 sound_options
744 loginscreen
@@ -714,42 +751,79 @@
750 topstat_run
751 filterbuttons
752 chattop
753 nothing
754 pmchat
755 worldmap
756 boardgames_options
757 npcchatlarge
758 canoe_travel
759 display mode prompt
760 tutorial2_objbox
761 tutorial2_sir vant's requests
762 bank_v2_main
763 bank_v2_side
764 tutorial2_minimap tutorial
765 tutorial2_switch_task
766 tutorial2_death
767 bank_v2_help
768 nothing
769 tutorial2_text
770 black screen
771 tutorial character creation
772 returned items screen
773 luc2?_guthix and swirling runes cutscene
774 where do you want to rub the key
775 sum1?_spooky overlay, spirit of summer?
776 sum1?_spooky overlay fade to white
777 sum1?_sepia overlay
778 rcguild_side
779 rcguild_rewards
780 rcguild_map
781 rcguild_overlay
782 telescope star
783 crcs_tightrope
784 crcs_side
785 crcs_rewards
786 crcs_equipment
787 star landing time discovered by board
788 crcs_scoreboard
789 set/clear caller
790 clanwars_end
791 clanwars_setup
792 clanwars_setup_side
793 clanwars_free for all safe
794 varrock census
795 black overlay
796 ghostly mage
797 ancient magicks
798 warning you are currently carrying a lot of items
799 holding pen
800 banner_halloween
801 quickchat_locked
802 sc_tutorial_overlay
803 sc_scores_border
804 nothing
805 sc_item_transfer
806 sc_remaining_clay
807 nothing
808 yellow squares overlay
809 sc_score overlay and timer
810 sc_scores
811 sc_reward_shop
812 clan leader and allied leaders
813 sc_processing
814 luc2_chapter2
815 luc2_door opening text
816 luc2_telescope_view
817 luc2_door opening
818 luc2_descending cliff
819 luc2_probably
820 luc2_book pages
821 luc2_swirling mass
822 luc2_chapter3
823 luc2_hand with opening orb
824 luc2_pulsing black screen
825 luc2_ascending cliff
826 luc2_chapter1
827 luc2_lucien_projectiles
828 journal with map?
@@ -758,3 +832,8 @@
831 drawings on ice again
832 again
833 again ALL SEEM TO BE FOR BARB ASSAULT
834 nothing
835 nothing
836 nothing
837 2009scape rewards shop
838 anything beyond this is a crash