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,7 +131,22 @@ 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.
*/
private final Container inventory = new Container(28).register(new InventoryListener(this));
@@ -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) {