diff --git a/.env.test b/.env.test index 63f5c51628..5509019b74 100644 --- a/.env.test +++ b/.env.test @@ -1,6 +1,9 @@ TZ="UTC" -CLIENT_ID=111398433321891634 +CLIENT_ID=1111111111111111 BOT_TOKEN=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA TEST=true CI=true -YARN_ENABLE_HARDENED_MODE=0 \ No newline at end of file +YARN_ENABLE_HARDENED_MODE=0 +ANNOUNCEMENTS_CHANNEL=111111111111111111 +MAIN_SERVER=111111111111111111 +GENERAL_CHANNEL=111111111111111111 \ No newline at end of file diff --git a/activities.txt b/activities.txt new file mode 100644 index 0000000000..79e0af022a --- /dev/null +++ b/activities.txt @@ -0,0 +1,111 @@ +AerialFishing +Birdhouse +DriftNet +Hunter +AnimatedArmour +Cyclops +AgilityArena +BarbarianAssault +CastleWars +ChampionsChallenge +BigChompyBirdHunting +FightCaves +Gauntlet +GnomeRestaurant +Inferno +LastManStanding +MageArena +MageArena2 +MahoganyHomes +Nightmare +PestControl +Plunder +PuroPuro +Raids +TheatreOfBlood +TearsOfGuthix +FishingTrawler +TroubleBrewing +RoguesDenMaze +SoulWars +Wintertodt +Zalcano +Burying +Scattering +Offering +Agility +Alching +Casting +ClueCompletion +Collecting +Construction +Cooking +Crafting +DarkAltar +Enchanting +Farming +Firemaking +Fishing +Fletching +GloryCharging +GroupMonsterKilling +Herblore +Fletching +Mining +MotherlodeMining +Runecraft +Sawmill +Woodcutting +WealthCharging +TokkulShop +Smelting +Pickpocket +Questing +MonsterKilling +VolcanicMine +Trekking +MageTrainingArena +Sepulchre +TitheFarm +Tempoross +Smithing +ShootingStars +GiantsFoundry +GuardiansOfTheRift +Butler +TiaraRunecraft +NightmareZone +ShadesOfMorton +Nex +BaxtorianBathhouses +Disassembling +FishingContest +Ignecarus +KalphiteKing +KibbleMaking +KingGoldemar +Moktang +MonkeyRumble +Naxxus +OuraniaDeliveryService +Research +VasaMagus +Dungeoneering +FistOfGuthix +StealingCreation +TinkeringWorkshop +CutLeapingFish +TombsOfAmascut +BalthazarsBigBonanza +UnderwaterAgilityThieving +DepthsOfAtlantis +StrongholdOfSecurity +CombatRing +SpecificQuest +CamdozaalMining +CamdozaalSmithing +CamdozaalFishing +MemoryHarvest +GuthixianCache +TuraelsTrials +Colosseum diff --git a/all_collection_log_items.txt b/all_collection_log_items.txt new file mode 100644 index 0000000000..025ced75b3 --- /dev/null +++ b/all_collection_log_items.txt @@ -0,0 +1,2017 @@ +Abyssal orphan +Unsired +Abyssal head +Bludgeon spine +Bludgeon claw +Bludgeon axon +Jar of miasma +Abyssal dagger +Abyssal whip +Ikkle hydra +Hydra's claw +Hydra tail +Hydra leather +Hydra's fang +Hydra's eye +Hydra's heart +Dragon knife +Dragon thrownaxe +Jar of chemicals +Alchemical hydra heads +Karil's coif +Karil's leathertop +Karil's leatherskirt +Karil's crossbow +Ahrim's hood +Ahrim's robetop +Ahrim's robeskirt +Ahrim's staff +Dharok's helm +Dharok's platebody +Dharok's platelegs +Dharok's greataxe +Guthan's helm +Guthan's platebody +Guthan's chainskirt +Guthan's warspear +Torag's helm +Torag's platebody +Torag's platelegs +Torag's hammers +Verac's helm +Verac's brassard +Verac's plateskirt +Verac's flail +Bolt rack +Bryophyta's essence +Callisto cub +Tyrannical ring +Dragon pickaxe +Dragon 2h sword +Claws of callisto +Voidwaker hilt +Hellpuppy +Eternal crystal +Pegasian crystal +Primordial crystal +Jar of souls +Smouldering stone +Key master teleport +Pet chaos elemental +Odium shard 1 +Malediction shard 1 +Pet zilyana +Armadyl crossbow +Saradomin hilt +Saradomin sword +Saradomin's light +Godsword shard 1 +Godsword shard 2 +Godsword shard 3 +Pet dark core +Elysian sigil +Spectral sigil +Arcane sigil +Divine sigil +Holy elixir +Spirit shield +Jar of spirits +Odium shard 2 +Malediction shard 2 +Fedora +Pet dagannoth prime +Pet dagannoth supreme +Pet dagannoth rex +Berserker ring +Archers ring +Seers ring +Warrior ring +Dragon axe +Seercull +Mud battlestaff +Baron +Eye of the duke +Frozen tablet +Magus vestige +Chromium ingot +Awakener's orb +Ice quartz +Lil'viathan +Leviathan's lure +Venator vestige +Smoke quartz +Scarred tablet +Butch +Executioner's axe head +Ultor vestige +Blood quartz +Strangled tablet +Vampyre hunter boots +Vampyre hunter legs +Vampyre hunter top +Vampyre hunter cuffs +Vampyre hunter hat +Blightbrand +Vampyric plushie +Echo +Wisp +Siren's staff +Bellator vestige +Shadow quartz +Sirenic tablet +Tzrek-jad +Fire cape +Smol heredit +Dizana's quiver (uncharged) +Sunfire fanatic cuirass +Sunfire fanatic chausses +Sunfire fanatic helm +Echo crystal +Tonalztics of ralos (uncharged) +Sunfire splinters +Youngllef +Crystal armour seed +Crystal weapon seed +Enhanced crystal weapon seed +Gauntlet cape +Pet general graardor +Bandos chestplate +Bandos tassets +Bandos boots +Bandos hilt +Baby mole +Mole skin +Mole claw +Noon +Black tourmaline core +Granite gloves +Granite ring +Granite hammer +Jar of stone +Granite dust +Bottomless compost bucket +Iasor seed +Kronos seed +Attas seed +Jal-nib-rek +Infernal cape +Jal-MejJak +Head of TzKal Zuk +TzKal-Zuk's skin +Infernal core +Kalphite princess +Kq head +Jar of sand +Dragon chainbody +Prince black dragon +Kbd heads +Draconic visage +Pet kraken +Kraken tentacle +Trident of the seas (full) +Jar of dirt +Pet kree'arra +Armadyl helmet +Armadyl chestplate +Armadyl chainskirt +Armadyl hilt +Pet k'ril tsutsaroth +Staff of the dead +Zamorakian spear +Steam battlestaff +Zamorak hilt +Little nightmare +Inquisitor's mace +Inquisitor's great helm +Inquisitor's hauberk +Inquisitor's plateskirt +Nightmare staff +Volatile orb +Harmonised orb +Eldritch orb +Jar of dreams +Slepey tablet +Parasitic egg +Hill giant club +Muphin +Venator shard +Ancient icon +Charged ice +Frozen cache +Ancient essence +Sraracha +Jar of eyes +Giant egg sac(full) +Sarachnis cudgel +Scorpia's offspring +Odium shard 3 +Malediction shard 3 +Scurry +Scurrius' spine +Skotos +Jar of darkness +Dark claw +Dark totem +Uncut onyx +Ancient shard +Tiny tempor +Big harpoonfish +Spirit angler headband +Spirit angler top +Spirit angler waders +Spirit angler boots +Tome of water (empty) +Soaked page +Tackle box +Fish barrel +Dragon harpoon +Spirit flakes +Pet smoke devil +Occult necklace +Smoke battlestaff +Jar of smoke +Venenatis spiderling +Treasonous ring +Fangs of venenatis +Voidwaker gem +Vet'ion jr. +Ring of the gods +Skull of vet'ion +Voidwaker blade +Vorki +Vorkath's head +Skeletal visage +Jar of decay +Dragonbone necklace +Mini moktang +Volcanic dye +Igne gear frame +Volcanic shards +Dragonstone upgrade kit +Phoenix +Wintertoad +Tome of fire +Burnt page +Pyromancer garb +Pyromancer hood +Pyromancer robe +Pyromancer boots +Warm gloves +Bruma torch +Smolcano +Crystal tool seed +Zalcano shard +Pet snakeling +Brock +Tanzanite mutagen +Magma mutagen +Jar of swamp +Magic fang +Serpentine visage +Tanzanite fang +Zul-andra teleport +Zulrah's scales +Broken dwarven warhammer +Dwarven ore +Dwarven crate +Athelas seed +Abyssal thread +Abyssal cape +Dragon hunter lance +Ori +Nihil horn +Zaryte vambraces +Nihil shard +Nexling +Drygore mace +Offhand drygore mace +Drygore longsword +Offhand drygore longsword +Drygore rapier +Offhand drygore rapier +Perfect chitin +Baby kalphite king +Dark crystal +Abyssal gem +Tattered tome +Spellbound ring +Korulsi seed +Torva full helm (broken) +Torva platebody (broken) +Torva platelegs (broken) +Torva boots (broken) +Torva gloves (broken) +Pernix cowl (broken) +Pernix body (broken) +Pernix chaps (broken) +Pernix boots (broken) +Pernix gloves (broken) +Virtus mask (broken) +Virtus robe top (broken) +Virtus robe legs (broken) +Virtus boots (broken) +Virtus gloves (broken) +Virtus crystal +Zaryte bow +Frozen key +Bloodsoaked feather +Ancient emblem +Ancient hilt +Tattered robes of Vasa +Jar of magic +Voidling +Magus scroll +Magical artifact +Ignecarus scales +Ignecarus dragonclaw +Dragon egg +Ignis ring +Tangleroot +Crystal tangleroot +Dragonfruit tangleroot +Herb tangleroot +White lily tangleroot +Redwood tangleroot +Mysterious seed +Grand crystal acorn +Ent hide +Fish sack +Pufferfish +Squid dye +Queen black dragonling +Royal dragon kiteshield +Dragonbone upgrade kit +Royal torsion spring +Royal sight +Royal frame +Royal bolt stabiliser +Mini akumu +Nightmarish ashes +Cursed onyx +Demon statuette +Baby venatrix +Venatrix eggs +Venatrix webbing +Spiders leg bottom +Olmlet +Twisted bow +Elder maul +Kodai insignia +Dragon claws +Ancestral hat +Ancestral robe top +Ancestral robe bottom +Dinh's bulwark +Dexterous prayer scroll +Arcane prayer scroll +Dragon hunter crossbow +Twisted buckler +Torn prayer scroll +Dark relic +Onyx +Metamorphic dust +Twisted ancestral colour kit +Xeric's guard +Xeric's warrior +Xeric's sentinel +Xeric's general +Xeric's champion +Lil' zik +Scythe of vitur (uncharged) +Ghrazi rapier +Sanguinesti staff (uncharged) +Justiciar faceguard +Justiciar chestguard +Justiciar legguards +Avernic defender hilt +Vial of blood +Sinhaza shroud tier 1 +Sinhaza shroud tier 2 +Sinhaza shroud tier 3 +Sinhaza shroud tier 4 +Sinhaza shroud tier 5 +Sanguine dust +Holy ornament kit +Sanguine ornament kit +Lil' maiden +Lil' bloat +Lil' nylo +Lil' sot +Lil' xarp +Tumeken's guardian +Tumeken's shadow (uncharged) +Elidinis' ward +Masori mask +Masori body +Masori chaps +Lightbearer +Osmumten's fang +Thread of elidinis +Breach of the scarab +Eye of the corruptor +Jewel of the sun +Menaphite ornament kit +Cursed phalanx +Masori crafting kit +Cache of runes +Icthlarin's shroud (tier 1) +Icthlarin's shroud (tier 2) +Icthlarin's shroud (tier 3) +Icthlarin's shroud (tier 4) +Icthlarin's shroud (tier 5) +Remnant of akkha +Remnant of ba-ba +Remnant of kephri +Remnant of zebak +Ancient remnant +Shark jaw +Shark tooth +Oceanic relic +Aquifer aegis +Oceanic dye +Crush +Bruce +Pearl +Bluey +Oceanic shroud (tier 1) +Oceanic shroud (tier 2) +Oceanic shroud (tier 3) +Oceanic shroud (tier 4) +Oceanic shroud (tier 5) +Dragon claw +Offhand dragon claw +Ruined dragon armour slice +Ruined dragon armour lump +Ruined dragon armour shard +Dragon limbs +Earth warrior champion scroll +Ghoul champion scroll +Giant champion scroll +Goblin champion scroll +Hobgoblin champion scroll +Imp champion scroll +Jogre champion scroll +Lesser demon champion scroll +Skeleton champion scroll +Zombie champion scroll +Champion's cape +Elder chaos top +Elder chaos robe +Elder chaos hood +Viggora's chainmace (u) +Craw's bow (u) +Thammaron's sceptre (u) +Amulet of avarice +Bracelet of ethereum (uncharged) +Ancient crystal +Ancient relic +Ancient effigy +Ancient medallion +Ancient statuette +Ancient totem +Revenant cave teleport +Revenant ether +Mycelium leggings web +Mycelium poncho web +Mycelium visor web +Neem drupe +Polypore spore +Grifolic flake +Grifolic gloves +Grifolic orb +Gorajian mushroom +Tombshroom spore +Ganodermic flake +Ganodermic gloves +Ganodermic boots +Crawling hand +Cockatrice head +Basilisk head +Kurask head +Imbued heart +Eternal gem +Dust battlestaff +Mist battlestaff +Granite maul +Mudskipper hat +Flippers +Brine sabre +Leaf-bladed sword +Leaf-bladed battleaxe +Black mask (10) +Granite longsword +Granite boots +Wyvern visage +Granite legs +Granite helm +Bronze boots +Iron boots +Steel boots +Black boots +Mithril boots +Adamant boots +Rune boots +Dragon boots +Uncharged trident +Dark bow +Dragon sword +Broken dragon hasta +Drake's tooth +Drake's claw +Mystic hat (light) +Mystic robe top (light) +Mystic robe bottom (light) +Mystic gloves (light) +Mystic boots (light) +Mystic hat (dark) +Mystic robe top (dark) +Mystic robe bottom (dark) +Mystic gloves (dark) +Mystic boots (dark) +Mystic hat (dusk) +Mystic robe top (dusk) +Mystic robe bottom (dusk) +Mystic gloves (dusk) +Mystic boots (dusk) +Basilisk jaw +Dagon'hai hat +Dagon'hai robe top +Dagon'hai robe bottom +Blood shard +Copper stone spirit +Tin stone spirit +Iron stone spirit +Coal stone spirit +Silver stone spirit +Mithril stone spirit +Adamantite stone spirit +Gold stone spirit +Runite stone spirit +Brackish blade +Ancient ceremonial mask +Ancient ceremonial top +Ancient ceremonial legs +Ancient ceremonial gloves +Ancient ceremonial boots +Dagannoth slayer helm +Dagannoth mask +Jelly slayer helm +Jelly mask +Abyssal slayer helm +Abyssal mask +Black demonical slayer helm +Black demonical mask +Troll slayer helm +Troll mask +Ganodermic slayer helm +Ganodermic mask +Gargoyle slayer helm +Gargoyle mask +Dark beast slayer helm +Dark beast mask +Dust devil slayer helm +Dust devil mask +Crawling hand slayer helm +Crawling hand mask +Basilisk slayer helm +Basilisk mask +Bloodveld slayer helm +Bloodveld mask +Banshee slayer helm +Banshee's mask +Cockatrice slayer helm +Cockatrice mask +Aberrant slayer helm +Aberrant mask +Kurask slayer helm +Kurask mask +Obsidian cape +Toktz-ket-xil +Tzhaar-ket-om +Toktz-xil-ak +Toktz-xil-ek +Toktz-mej-tal +Toktz-xil-ul +Obsidian helmet +Obsidian platebody +Obsidian platelegs +Solite +Eagle egg +Sun-metal scraps +Lunite +Moonlight essence +Moondash charm +Noom +Mole slippers +Frog slippers +Bear feet +Demon feet +Jester cape +Shoulder parrot +Monk's robe top (t) +Monk's robe (t) +Amulet of defence (t) +Sandwich lady hat +Sandwich lady top +Sandwich lady bottom +Rune scimitar ornament kit (guthix) +Rune scimitar ornament kit (saradomin) +Rune scimitar ornament kit (zamorak) +Black pickaxe +Team cape zero +Team cape i +Team cape x +Cape of skulls +Golden chef's hat +Golden apron +Wooden shield (g) +Black full helm (t) +Black platebody (t) +Black platelegs (t) +Black plateskirt (t) +Black kiteshield (t) +Black full helm (g) +Black platebody (g) +Black platelegs (g) +Black plateskirt (g) +Black kiteshield (g) +Black shield (h1) +Black shield (h2) +Black shield (h3) +Black shield (h4) +Black shield (h5) +Black helm (h1) +Black helm (h2) +Black helm (h3) +Black helm (h4) +Black helm (h5) +Black platebody (h1) +Black platebody (h2) +Black platebody (h3) +Black platebody (h4) +Black platebody (h5) +Steel full helm (t) +Steel platebody (t) +Steel platelegs (t) +Steel plateskirt (t) +Steel kiteshield (t) +Steel full helm (g) +Steel platebody (g) +Steel platelegs (g) +Steel plateskirt (g) +Steel kiteshield (g) +Iron platebody (t) +Iron platelegs (t) +Iron plateskirt (t) +Iron kiteshield (t) +Iron full helm (t) +Iron platebody (g) +Iron platelegs (g) +Iron plateskirt (g) +Iron kiteshield (g) +Iron full helm (g) +Bronze platebody (t) +Bronze platelegs (t) +Bronze plateskirt (t) +Bronze kiteshield (t) +Bronze full helm (t) +Bronze platebody (g) +Bronze platelegs (g) +Bronze plateskirt (g) +Bronze kiteshield (g) +Bronze full helm (g) +Studded body (g) +Studded chaps (g) +Studded body (t) +Studded chaps (t) +Leather body (g) +Leather chaps (g) +Blue wizard hat (g) +Blue wizard robe (g) +Blue skirt (g) +Blue wizard hat (t) +Blue wizard robe (t) +Blue skirt (t) +Black wizard hat (g) +Black wizard robe (g) +Black skirt (g) +Black wizard hat (t) +Black wizard robe (t) +Black skirt (t) +Monk's robe top (g) +Monk's robe (g) +Saradomin robe top +Saradomin robe legs +Guthix robe top +Guthix robe legs +Zamorak robe top +Zamorak robe legs +Ancient robe top +Ancient robe legs +Armadyl robe top +Armadyl robe legs +Bandos robe top +Bandos robe legs +Bob's red shirt +Bob's green shirt +Bob's blue shirt +Bob's black shirt +Bob's purple shirt +Highwayman mask +Blue beret +Black beret +White beret +Red beret +A powdered wig +Beanie +Imp mask +Goblin mask +Sleeping cap +Flared trousers +Pantaloons +Black cane +Staff of bob the cat +Red elegant shirt +Red elegant blouse +Red elegant legs +Red elegant skirt +Green elegant shirt +Green elegant blouse +Green elegant legs +Green elegant skirt +Blue elegant shirt +Blue elegant blouse +Blue elegant legs +Blue elegant skirt +Amulet of magic (t) +Amulet of power (t) +Ham joint +Rain bow +Willow comp bow +Ranger boots +Wizard boots +Holy sandals +Climbing boots (g) +Spiked manacles +Adamant full helm (t) +Adamant platebody (t) +Adamant platelegs (t) +Adamant plateskirt (t) +Adamant kiteshield (t) +Adamant full helm (g) +Adamant platebody (g) +Adamant platelegs (g) +Adamant plateskirt (g) +Adamant kiteshield (g) +Adamant shield (h1) +Adamant shield (h2) +Adamant shield (h3) +Adamant shield (h4) +Adamant shield (h5) +Adamant helm (h1) +Adamant helm (h2) +Adamant helm (h3) +Adamant helm (h4) +Adamant helm (h5) +Adamant platebody (h1) +Adamant platebody (h2) +Adamant platebody (h3) +Adamant platebody (h4) +Adamant platebody (h5) +Mithril full helm (t) +Mithril platebody (t) +Mithril platelegs (t) +Mithril plateskirt (t) +Mithril kiteshield (t) +Mithril full helm (g) +Mithril platebody (g) +Mithril platelegs (g) +Mithril plateskirt (g) +Mithril kiteshield (g) +Green d'hide body (g) +Green d'hide body (t) +Green d'hide chaps (g) +Green d'hide chaps (t) +Saradomin mitre +Saradomin cloak +Guthix mitre +Guthix cloak +Zamorak mitre +Zamorak cloak +Ancient mitre +Ancient cloak +Ancient stole +Ancient crozier +Armadyl mitre +Armadyl cloak +Armadyl stole +Armadyl crozier +Bandos mitre +Bandos cloak +Bandos stole +Bandos crozier +Red boater +Green boater +Orange boater +Black boater +Blue boater +Pink boater +Purple boater +White boater +Red headband +Black headband +Brown headband +White headband +Blue headband +Gold headband +Pink headband +Green headband +Crier hat +Crier coat +Crier bell +Adamant cane +Arceuus banner +Piscarilius banner +Hosidius banner +Shayzien banner +Lovakengj banner +Cabbage round shield +Black unicorn mask +White unicorn mask +Cat mask +Penguin mask +Leprechaun hat +Black leprechaun hat +Wolf mask +Wolf cloak +Purple elegant shirt +Purple elegant blouse +Purple elegant legs +Purple elegant skirt +Black elegant shirt +White elegant blouse +Black elegant legs +White elegant skirt +Pink elegant shirt +Pink elegant blouse +Pink elegant legs +Pink elegant skirt +Gold elegant shirt +Gold elegant blouse +Gold elegant legs +Gold elegant skirt +Gnomish firelighter +Strength amulet (t) +Yew comp bow +Robin hood hat +Dragon boots ornament kit +Rune defender ornament kit +Tzhaar-ket-om ornament kit +Berserker necklace ornament kit +Rune full helm (t) +Rune platebody (t) +Rune platelegs (t) +Rune plateskirt (t) +Rune kiteshield (t) +Rune full helm (g) +Rune platebody (g) +Rune platelegs (g) +Rune plateskirt (g) +Rune kiteshield (g) +Zamorak full helm +Zamorak platebody +Zamorak platelegs +Zamorak plateskirt +Zamorak kiteshield +Guthix full helm +Guthix platebody +Guthix platelegs +Guthix plateskirt +Guthix kiteshield +Saradomin full helm +Saradomin platebody +Saradomin platelegs +Saradomin plateskirt +Saradomin kiteshield +Ancient full helm +Ancient platebody +Ancient platelegs +Ancient plateskirt +Ancient kiteshield +Armadyl full helm +Armadyl platebody +Armadyl platelegs +Armadyl plateskirt +Armadyl kiteshield +Bandos full helm +Bandos platebody +Bandos platelegs +Bandos plateskirt +Bandos kiteshield +Rune shield (h1) +Rune shield (h2) +Rune shield (h3) +Rune shield (h4) +Rune shield (h5) +Rune helm (h1) +Rune helm (h2) +Rune helm (h3) +Rune helm (h4) +Rune helm (h5) +Rune platebody (h1) +Rune platebody (h2) +Rune platebody (h3) +Rune platebody (h4) +Rune platebody (h5) +Saradomin coif +Saradomin d'hide body +Saradomin chaps +Saradomin bracers +Saradomin d'hide boots +Saradomin d'hide shield +Guthix coif +Guthix d'hide body +Guthix chaps +Guthix bracers +Guthix d'hide boots +Guthix d'hide shield +Zamorak coif +Zamorak d'hide body +Zamorak chaps +Zamorak bracers +Zamorak d'hide boots +Zamorak d'hide shield +Bandos coif +Bandos d'hide body +Bandos chaps +Bandos bracers +Bandos d'hide boots +Bandos d'hide shield +Armadyl coif +Armadyl d'hide body +Armadyl chaps +Armadyl bracers +Armadyl d'hide boots +Armadyl d'hide shield +Ancient coif +Ancient d'hide body +Ancient chaps +Ancient bracers +Ancient d'hide boots +Ancient d'hide shield +Red d'hide body (t) +Red d'hide chaps (t) +Red d'hide body (g) +Red d'hide chaps (g) +Blue d'hide body (t) +Blue d'hide chaps (t) +Blue d'hide body (g) +Blue d'hide chaps (g) +Enchanted hat +Enchanted top +Enchanted robe +Saradomin stole +Saradomin crozier +Guthix stole +Guthix crozier +Zamorak stole +Zamorak crozier +Zombie head +Cyclops head +Pirate's hat +Red cavalier +White cavalier +Navy cavalier +Tan cavalier +Dark cavalier +Black cavalier +Pith helmet +Explorer backpack +Thieving bag +Green dragon mask +Blue dragon mask +Red dragon mask +Black dragon mask +Nunchaku +Dual sai +Rune cane +Amulet of glory (t4) +Magic comp bow +Ring of 3rd age +Fury ornament kit +Dragon chainbody ornament kit +Dragon legs/skirt ornament kit +Dragon sq shield ornament kit +Dragon full helm ornament kit +Dragon scimitar ornament kit +Light infinity colour kit +Dark infinity colour kit +Holy wraps +Ranger gloves +Rangers' tunic +Rangers' tights +Black d'hide body (g) +Black d'hide chaps (g) +Black d'hide body (t) +Black d'hide chaps (t) +Royal crown +Royal sceptre +Royal gown top +Royal gown bottom +Musketeer hat +Musketeer tabard +Musketeer pants +Dark tuxedo jacket +Dark trousers +Dark tuxedo shoes +Dark tuxedo cuffs +Dark bow tie +Light tuxedo jacket +Light trousers +Light tuxedo shoes +Light tuxedo cuffs +Light bow tie +Arceuus scarf +Hosidius scarf +Piscarilius scarf +Shayzien scarf +Lovakengj scarf +Bronze dragon mask +Iron dragon mask +Steel dragon mask +Mithril dragon mask +Adamant dragon mask +Rune dragon mask +Katana +Dragon cane +Briefcase +Bucket helm +Blacksmith's helm +Deerstalker +Afro +Big pirate hat +Top hat +Monocle +Sagacious spectacles +Fremennik kilt +Giant boot +Uri's hat +Bloodhound +Armadyl godsword ornament kit +Bandos godsword ornament kit +Saradomin godsword ornament kit +Zamorak godsword ornament kit +Occult ornament kit +Torture ornament kit +Anguish ornament kit +Dragon defender ornament kit +Dragon kiteshield ornament kit +Dragon platebody ornament kit +Tormented ornament kit +Hood of darkness +Robe top of darkness +Robe bottom of darkness +Gloves of darkness +Boots of darkness +Samurai kasa +Samurai shirt +Samurai greaves +Samurai boots +Samurai gloves +Ankou mask +Ankou top +Ankou gloves +Ankou socks +Ankou's leggings +Mummy's head +Mummy's feet +Mummy's hands +Mummy's legs +Mummy's body +Shayzien hood +Hosidius hood +Arceuus hood +Piscarilius hood +Lovakengj hood +Lesser demon mask +Greater demon mask +Black demon mask +Jungle demon mask +Old demon mask +Left eye patch +Bowl wig +Ale of the gods +Obsidian cape (r) +Half moon spectacles +Fancy tiara +Lord marshal boots +Lord marshal gloves +Lord marshal trousers +Lord marshal top +Lord marshal cap +Akumu mask +Commander boots +Commander gloves +Commander trousers +Commander top +Commander cap +Apple parasol +Watermelon parasol +Lemon parasol +Strawberry parasol +Blueberry parasol +Grape parasol +Coconut parasol +Detective hat +Detective trenchcoat +Detective pants +2nd age range legs +2nd age range top +2nd age range coif +2nd age bow +2nd age mage top +2nd age mage bottom +2nd age mage mask +2nd age staff +First age robe top +First age robe bottom +Clue bag +Inventors tools +Elder knowledge +Octo +Dwarven blessing +Ring of luck +Holiday Mystery Box +Deathtouched dart +Ignecarus mask +Malygos mask +Blabberbeak +Helm of raedwald +Clue hunter garb +Clue hunter gloves +Clue hunter trousers +Clue hunter boots +Clue hunter cloak +First age tiara +First age amulet +First age cape +First age bracelet +First age ring +Blood dye +Third age dye +Ice dye +Shadow dye +3rd age range coif +3rd age range top +3rd age range legs +3rd age vambraces +3rd age robe top +3rd age robe +3rd age mage hat +3rd age amulet +3rd age plateskirt +3rd age platelegs +3rd age platebody +3rd age full helmet +3rd age kiteshield +Gilded platebody +Gilded platelegs +Gilded plateskirt +Gilded full helm +Gilded kiteshield +Gilded med helm +Gilded chainbody +Gilded sq shield +Gilded 2h sword +Gilded spear +Gilded hasta +3rd age longsword +3rd age wand +3rd age cloak +3rd age bow +Gilded scimitar +Gilded boots +Gilded coif +Gilded d'hide vambraces +Gilded d'hide body +Gilded d'hide chaps +Gilded pickaxe +Gilded axe +Gilded spade +Ring of nature +Lava dragon mask +3rd age pickaxe +3rd age axe +3rd age druidic robe bottoms +3rd age druidic robe top +3rd age druidic staff +3rd age druidic cloak +Bucket helm (g) +Ring of coins +Saradomin page 1 +Saradomin page 2 +Saradomin page 3 +Saradomin page 4 +Zamorak page 1 +Zamorak page 2 +Zamorak page 3 +Zamorak page 4 +Guthix page 1 +Guthix page 2 +Guthix page 3 +Guthix page 4 +Bandos page 1 +Bandos page 2 +Bandos page 3 +Bandos page 4 +Armadyl page 1 +Armadyl page 2 +Armadyl page 3 +Armadyl page 4 +Ancient page 1 +Ancient page 2 +Ancient page 3 +Ancient page 4 +Holy blessing +Unholy blessing +Peaceful blessing +War blessing +Honourable blessing +Ancient blessing +Nardah teleport +Mos le'harmless teleport +Mort'ton teleport +Feldip hills teleport +Lunar isle teleport +Digsite teleport +Piscatoris teleport +Pest control teleport +Tai bwo wannai teleport +Lumberyard teleport +Iorwerth camp teleport +Master scroll book (empty) +Red firelighter +Green firelighter +Blue firelighter +Purple firelighter +White firelighter +Charge dragonstone jewellery scroll +Purple sweets +Pet penance queen +Fighter hat +Ranger hat +Runner hat +Healer hat +Fighter torso +Penance skirt +Runner boots +Penance gloves +Granite body +Agility arena ticket +Pirate's hook +Brimhaven graceful hood +Brimhaven graceful top +Brimhaven graceful legs +Brimhaven graceful gloves +Brimhaven graceful boots +Brimhaven graceful cape +Red decorative full helm +Red decorative helm +Red decorative body +Red decorative legs +Red decorative skirt +Red decorative boots +Red decorative shield +Red decorative sword +White decorative full helm +White decorative helm +White decorative body +White decorative legs +White decorative skirt +White decorative boots +White decorative shield +White decorative sword +Gold decorative full helm +Gold decorative helm +Gold decorative body +Gold decorative legs +Gold decorative skirt +Gold decorative boots +Gold decorative shield +Gold decorative sword +Zamorak castlewars hood +Zamorak castlewars cloak +Saradomin castlewars hood +Saradomin castlewars cloak +Saradomin banner +Zamorak banner +Decorative magic hat +Decorative magic top +Decorative magic robe +Decorative ranged top +Decorative ranged legs +Decorative quiver +Saradomin halo +Zamorak halo +Guthix halo +Castle wars cape (beginner) +Castle wars cape (intermediate) +Castle wars cape (advanced) +Castle wars cape (expert) +Castle wars cape (legend) +Angler hat +Angler top +Angler waders +Angler boots +Smiths tunic +Smiths trousers +Smiths boots +Smiths gloves +Colossal blade +Double ammo mould +Kovac's grog +Smithing catalyst +Ore pack (Giant's Foundry) +Grand seed pod +Gnome scarf +Gnome goggles +Mint cake +Abyssal protector +Abyssal pearls +Catalytic talisman +Abyssal needle +Abyssal green dye +Abyssal blue dye +Abyssal red dye +Hat of the eye +Robe top of the eye +Robe bottoms of the eye +Boots of the eye +Ring of the elements +Abyssal lantern +Guardian's eye +Intricate pouch +Lost bag +Tarnished locket +Hallowed mark +Hallowed token +Hallowed grapple +Hallowed focus +Hallowed symbol +Hallowed hammer +Hallowed ring +Dark dye +Dark acorn +Strange old lockpick +Ring of endurance (uncharged) +Mysterious page 1 +Mysterious page 2 +Mysterious page 3 +Mysterious page 4 +Mysterious page 5 +Deadman's chest +Deadman's legs +Deadman's cape +Armadyl halo +Bandos halo +Seren halo +Ancient halo +Brassica halo +Golden armadyl special attack +Golden bandos special attack +Golden saradomin special attack +Golden zamorak special attack +Victor's cape (1) +Victor's cape (10) +Victor's cape (50) +Victor's cape (100) +Victor's cape (500) +Victor's cape (1000) +Granite clamp +Ornate maul handle +Steam staff upgrade kit +Lava staff upgrade kit +Dragon pickaxe upgrade kit +Ward upgrade kit +Green dark bow paint +Yellow dark bow paint +White dark bow paint +Blue dark bow paint +Volcanic whip mix +Frozen whip mix +Guthixian icon +Swift blade +Beginner wand +Apprentice wand +Teacher wand +Master wand +Infinity hat +Infinity top +Infinity bottoms +Infinity boots +Infinity gloves +Mage's book +Builders supply crate +Carpenter's helmet +Carpenter's shirt +Carpenter's trousers +Carpenter's boots +Amy's saw +Plank sack +Hosidius blueprints +Void knight mace +Void knight top +Void knight robe +Void knight gloves +Void mage helm +Void melee helm +Void ranger helm +Void seal(8) +Elite void top +Elite void robe +Rogue mask +Rogue top +Rogue trousers +Rogue boots +Rogue gloves +Amulet of the damned (full) +Flamtaer bag +Fine cloth +Bronze locks +Steel locks +Black locks +Silver locks +Gold locks +Zealot's helm +Zealot's robe top +Zealot's robe bottom +Zealot's boots +Tree wizards' journal +Bloody notes +Gary +Lil' creator +Red soul cape +Ectoplasmator +Lumberjack hat +Lumberjack top +Lumberjack legs +Lumberjack boots +Farmer's strawhat +Farmer's jacket +Farmer's boro trousers +Farmer's boots +Seed box +Gricoller's can +Herb sack +Blue naval shirt +Blue tricorn hat +Blue navy slacks +Green naval shirt +Green tricorn hat +Green navy slacks +Red naval shirt +Red tricorn hat +Red navy slacks +Brown naval shirt +Brown tricorn hat +Brown navy slacks +Black naval shirt +Black tricorn hat +Black navy slacks +Purple naval shirt +Purple tricorn hat +Purple navy slacks +Grey naval shirt +Grey tricorn hat +Grey navy slacks +Golden naval shirt +Golden tricorn hat +Golden navy slacks +Cutthroat flag +Gilded smile flag +Bronze fist flag +Lucky shot flag +Treasure flag +Phasmatys flag +The stuff +Red rum (trouble brewing) +Blue rum (trouble brewing) +Jolly roger cape +Ash covered tome +Large water container +Volcanic mine teleport +Dragon pickaxe (broken) +Master runecrafter hat +Master runecrafter robe +Master runecrafter skirt +Master runecrafter boots +Elder thread +Elder talisman +Magic crate +Lychee seed +Avocado seed +Mango seed +Monkey egg +Marimbo statue +Monkey dye +Banana enchantment scroll +Rumble token +Big banana +Monkey crate +Gorilla rumble greegree +Beginner rumble greegree +Intermediate rumble greegree +Ninja rumble greegree +Expert ninja rumble greegree +Elder rumble greegree +Fishing hat +Fishing jacket +Fishing waders +Fishing boots +Contest rod +Beginner's tackle box +Basic tackle box +Standard tackle box +Professional tackle box +Champion's tackle box +Golden fishing trophy +Inferno adze +Flame gloves +Ring of fire +Phoenix eggling +Rune spikeshield +Rune berserker shield +Adamant spikeshield +Adamant berserker shield +Guthix engram +Fist of guthix token +Diviner's headwear +Diviner's robe +Diviner's legwear +Diviner's handwear +Diviner's footwear +Stealing creation token +Fletcher's gloves +Fletcher's boots +Fletcher's top +Fletcher's hat +Fletcher's legs +Inventors' helmet +Inventors' torso +Inventors' legs +Inventors' gloves +Inventors' boots +Inventors' backpack +Materials bag +A stylish hat (male, yellow) +Shirt (male, yellow) +Leggings (yellow) +A stylish hat (male, maroon) +Shirt (male, maroon) +Leggings (maroon) +A stylish hat (male, green) +Shirt (male, green) +Leggings (green) +A stylish hat (female, yellow) +Shirt (female, yellow) +Skirt (yellow) +A stylish hat (female, maroon) +Shirt (female, maroon) +Skirt (maroon) +A stylish hat (female, green) +Shirt (female, green) +Skirt (green) +Shoes (male, shoes) +Shoes (male, boots) +Shoes (female, straps) +Shoes (female, flats) +Giant's hand +Acrobat set +Clown set +Ringmaster set +Golden tench +Pearl fishing rod +Pearl fly fishing rod +Pearl barbarian rod +Heron +Rock golem +Beaver +Baby chinchompa +Giant squirrel +Rocky +Rift guardian +Herbi +Chompy chick +Barronite mace +Barronite head +Barronite handle +Barronite guard +Ancient globe +Ancient ledger +Ancient astroscope +Ancient treatise +Ancient carcanet +Imcando hammer +Chompy bird hat (ogre bowman) +Chompy bird hat (bowman) +Chompy bird hat (ogre yeoman) +Chompy bird hat (yeoman) +Chompy bird hat (ogre marksman) +Chompy bird hat (marksman) +Chompy bird hat (ogre woodsman) +Chompy bird hat (woodsman) +Chompy bird hat (ogre forester) +Chompy bird hat (forester) +Chompy bird hat (ogre bowmaster) +Chompy bird hat (bowmaster) +Chompy bird hat (ogre expert) +Chompy bird hat (expert) +Chompy bird hat (ogre dragon archer) +Chompy bird hat (dragon archer) +Chompy bird hat (expert ogre dragon archer) +Chompy bird hat (expert dragon archer) +Tea flask +Plain satchel +Green satchel +Red satchel +Black satchel +Gold satchel +Rune satchel +Bronze defender +Iron defender +Steel defender +Black defender +Mithril defender +Adamant defender +Rune defender +Dragon defender +Fox whistle +Golden pheasant egg +Forestry hat +Forestry top +Forestry legs +Forestry boots +Twitcher's gloves +Funky shaped log +Log basket +Log brace +Clothes pouch blueprint +Cape pouch +Felling axe handle +Pheasant hat +Pheasant legs +Pheasant boots +Pheasant cape +Petal garland +Sturdy beehive parts +Scribbled note +Partial note +Ancient note +Ancient writings +Experimental note +Paragraph of text +Musty smelling note +Hastily scrawled note +Old writing +Short note +Zenyte shard +Light frame +Heavy frame +Ballista limbs +Monkey tail +Ballista spring +Karamjan monkey +Kruk jr +Maniacal monkey +Princely monkey +Skeleton monkey +Zombie monkey +Coal bag +Gem bag +Prospector helmet +Prospector jacket +Prospector legs +Prospector boots +Mark of grace +Graceful hood +Graceful cape +Graceful top +Graceful legs +Graceful gloves +Graceful boots +Scruffy +Harry +Skipper +Celestial ring (uncharged) +Star fragment +Chaotic rapier +Chaotic longsword +Chaotic maul +Chaotic staff +Chaotic crossbow +Offhand Chaotic rapier +Offhand Chaotic longsword +Offhand Chaotic crossbow +Scroll of life +Scroll of efficiency +Scroll of cleansing +Scroll of dexterity +Scroll of teleportation +Scroll of farming +Scroll of proficiency +Scroll of mystery +Scroll of longevity +Scroll of the hunt +Farseer kiteshield +Chaotic remnant +Frostbite +Gorajan shards +Amulet of zealots +Herbicide +Gorajan warrior helmet +Gorajan warrior top +Gorajan warrior legs +Gorajan warrior gloves +Gorajan warrior boots +Gorajan archer helmet +Gorajan archer top +Gorajan archer legs +Gorajan archer gloves +Gorajan archer boots +Gorajan occult helmet +Gorajan occult top +Gorajan occult legs +Gorajan occult gloves +Gorajan occult boots +Arcane blast necklace +Farsight snapshot necklace +Brawler's hook necklace +Daemonheim agility pass +Dungeoneering dye +Gorajan bonecrusher (u) +Mining gloves +Superior mining gloves +Expert mining gloves +Golden nugget +Unidentified minerals +Big swordfish +Big shark +Big bass +Farmer's shirt +Pharaoh's sceptre +Kyatt hat +Kyatt top +Kyatt legs +Spotted cape +Spottier cape +Gloves of silence +Small pouch +Medium pouch +Large pouch +Giant pouch +Abyssal pouch +Elder pouch +Crystal pickaxe +Crystal axe +Crystal harpoon +Dwarven greataxe +Dwarven greathammer +Dwarven pickaxe +Dwarven knife +Dwarven gauntlets +Superior bonecrusher +Superior dwarf multicannon +Superior inferno adze +Silverhawk boots +Mecha mortar +Quick trap +Arcane harvester +Portable tanner +Drygore saw +Clue upgrader +Dwarven toolkit +Mecha rod +Master hammer and chisel +Abyssal amulet +RoboFlappy +Chincannon +Wisp-buster +Divine hand +Drygore axe +Moonlight mutator +Webshooter +Cogsworth +Cache portent +Graceful portent +Rogues portent +Dungeon portent +Lucky portent +Rebirth portent +Spiritual mining portent +Pacifist hunting portent +Pale energy +Boon of flickering energy +Flickering energy +Boon of bright energy +Bright energy +Boon of glowing energy +Glowing energy +Boon of sparkling energy +Sparkling energy +Boon of gleaming energy +Gleaming energy +Boon of vibrant energy +Vibrant energy +Boon of lustrous energy +Lustrous energy +Boon of elder energy +Elder energy +Boon of brilliant energy +Brilliant energy +Boon of radiant energy +Radiant energy +Boon of luminous energy +Luminous energy +Boon of incandescent energy +Incandescent energy +Boon of ancient energy +Ancient energy +Divine egg +Jar of memories +Doopy +Camo top +Camo bottoms +Camo helmet +Lederhosen top +Lederhosen shorts +Lederhosen hat +Zombie shirt +Zombie trousers +Zombie mask +Zombie gloves +Zombie boots +Mime mask +Mime top +Mime legs +Mime gloves +Mime boots +Frog token +Stale baguette +Beekeeper's hat +Beekeeper's top +Beekeeper's legs +Beekeeper's gloves +Beekeeper's boots +Shayzien gloves (1) +Shayzien boots (1) +Shayzien helm (1) +Shayzien greaves (1) +Shayzien platebody (1) +Shayzien gloves (2) +Shayzien boots (2) +Shayzien helm (2) +Shayzien greaves (2) +Shayzien platebody (2) +Shayzien gloves (3) +Shayzien boots (3) +Shayzien helm (3) +Shayzien greaves (3) +Shayzien platebody (3) +Shayzien gloves (4) +Shayzien boots (4) +Shayzien helm (4) +Shayzien greaves (4) +Shayzien platebody (4) +Shayzien gloves (5) +Shayzien boots (5) +Shayzien helm (5) +Shayzien greaves (5) +Shayzien body (5) +Dragon warhammer +Long bone +Curved bone +Ecumenical key +Dark totem base +Dark totem middle +Dark totem top +Chewed bones +Dragon full helm +Shield left half +Dragon metal slice +Dragon metal lump +Dragon spear +Amulet of eternal glory +Shaman mask +Evil chicken head +Evil chicken wings +Evil chicken legs +Evil chicken feet +Right skull half +Left skull half +Top of sceptre +Bottom of sceptre +Mossy key +Giant key +Hespori seed +Xeric's talisman (inert) +Mask of ranul +Elven signet +Crystal grail +Enhanced crystal teleport seed +Dragonstone full helm +Dragonstone platebody +Dragonstone platelegs +Dragonstone gauntlets +Dragonstone boots +Ivy seed +Merfolk trident +Orange egg sac +Dark Temple key +Elite black knight sword +Elite black knight kiteshield +Elite black knight helm +Elite black knight platebody +Elite black knight platelegs +Doug +Zippy +Shelldon +Remy +Lil Lamb +Klik +Zak +Hammy +Takon +Obis +Peky +Plopper +Wilvus +Ishi +Sandy +Steve +Baby duckling +Mr. E +Nexterminator +Brain lee +Balloon cat +Baby yaga house +Herbert +Fungo +Baby raven +Raven +Magic kitten +Magic cat +Zamorak egg +Baby zamorak hawk +Juvenile zamorak hawk +Zamorak hawk +Guthix egg +Baby guthix raptor +Juvenile guthix raptor +Guthix raptor +Saradomin egg +Baby saradomin owl +Juvenile saradomin owl +Saradomin owl +Grey and black kitten +Grey and black cat +White kitten +White cat +Brown kitten +Brown cat +Black kitten +Black cat +Grey and brown kitten +Grey and brown cat +Grey and blue kitten +Grey and blue cat +Fuzzy dice +Karambinana +Dragon igne armor +Barrows igne armor +Volcanic igne armor +Justiciar igne armor +Drygore igne armor +Dwarven igne armor +Gorajan igne armor +Runite igne claws +Dragon igne claws +Barrows igne claws +Volcanic igne claws +Drygore igne claws +Dwarven igne claws +Gorajan igne claws +Seamonkey staff (t1) +Seamonkey staff (t2) +Seamonkey staff (t3) +Impling locator +Divine ring +Abyssal jibwings (e) +Demonic jibwings (e) +3rd age jibwings (e) +Abyssal jibwings +Demonic jibwings +3rd age jibwings +Warpriest of Zamorak set +Warpriest of Saradomin set +Warpriest of Bandos set +Warpriest of Armadyl set diff --git a/allllllll_collection_log_items.txt b/allllllll_collection_log_items.txt new file mode 100644 index 0000000000..894a824c26 --- /dev/null +++ b/allllllll_collection_log_items.txt @@ -0,0 +1,4340 @@ +Abyssal orphan +Unsired +Abyssal head +Bludgeon spine +Bludgeon claw +Bludgeon axon +Jar of miasma +Abyssal dagger +Abyssal whip +Ikkle hydra +Hydra's claw +Hydra tail +Hydra leather +Hydra's fang +Hydra's eye +Hydra's heart +Dragon knife +Dragon thrownaxe +Jar of chemicals +Alchemical hydra heads +Karil's coif +Karil's leathertop +Karil's leatherskirt +Karil's crossbow +Ahrim's hood +Ahrim's robetop +Ahrim's robeskirt +Ahrim's staff +Dharok's helm +Dharok's platebody +Dharok's platelegs +Dharok's greataxe +Guthan's helm +Guthan's platebody +Guthan's chainskirt +Guthan's warspear +Torag's helm +Torag's platebody +Torag's platelegs +Torag's hammers +Verac's helm +Verac's brassard +Verac's plateskirt +Verac's flail +Bolt rack +Bryophyta's essence +Callisto cub +Tyrannical ring +Dragon pickaxe +Dragon 2h sword +Claws of callisto +Voidwaker hilt +Hellpuppy +Eternal crystal +Pegasian crystal +Primordial crystal +Jar of souls +Smouldering stone +Key master teleport +Pet chaos elemental +Odium shard 1 +Malediction shard 1 +Pet zilyana +Armadyl crossbow +Saradomin hilt +Saradomin sword +Saradomin's light +Godsword shard 1 +Godsword shard 2 +Godsword shard 3 +Steam battlestaff +Staff of the dead +Armadyl hilt +Bandos hilt +Zamorak hilt +Zamorakian spear +Armadyl helmet +Armadyl chestplate +Armadyl chainskirt +Bandos chestplate +Bandos tassets +Bandos boots +Pet kree'arra +Pet general graardor +Pet k'ril tsutsaroth +Pet dark core +Elysian sigil +Spectral sigil +Arcane sigil +Divine sigil +Holy elixir +Spirit shield +Jar of spirits +Odium shard 2 +Malediction shard 2 +Fedora +Pet dagannoth prime +Pet dagannoth supreme +Pet dagannoth rex +Berserker ring +Archers ring +Seers ring +Warrior ring +Dragon axe +Seercull +Mud battlestaff +Baron +Eye of the duke +Frozen tablet +Magus vestige +Chromium ingot +Awakener's orb +Ice quartz +Lil'viathan +Leviathan's lure +Venator vestige +Smoke quartz +Scarred tablet +Butch +Executioner's axe head +Ultor vestige +Blood quartz +Strangled tablet +Vampyre hunter boots +Vampyre hunter legs +Vampyre hunter top +Vampyre hunter cuffs +Vampyre hunter hat +Blightbrand +Vampyric plushie +Echo +Wisp +Siren's staff +Bellator vestige +Shadow quartz +Sirenic tablet +Tzrek-jad +Fire cape +Smol heredit +Dizana's quiver (uncharged) +Sunfire fanatic cuirass +Sunfire fanatic chausses +Sunfire fanatic helm +Echo crystal +Tonalztics of ralos (uncharged) +Sunfire splinters +Youngllef +Crystal armour seed +Crystal weapon seed +Enhanced crystal weapon seed +Gauntlet cape +Baby mole +Mole skin +Mole claw +Noon +Black tourmaline core +Granite gloves +Granite ring +Granite hammer +Jar of stone +Granite dust +Bottomless compost bucket +Iasor seed +Kronos seed +Attas seed +Jal-nib-rek +Infernal cape +Jal-MejJak +Head of TzKal Zuk +TzKal-Zuk's skin +Infernal core +Kalphite princess +Kq head +Jar of sand +Dragon chainbody +Prince black dragon +Kbd heads +Draconic visage +Pet kraken +Kraken tentacle +Trident of the seas (full) +Jar of dirt +Little nightmare +Inquisitor's mace +Inquisitor's great helm +Inquisitor's hauberk +Inquisitor's plateskirt +Nightmare staff +Volatile orb +Harmonised orb +Eldritch orb +Jar of dreams +Slepey tablet +Parasitic egg +Hill giant club +Muphin +Venator shard +Ancient icon +Charged ice +Frozen cache +Ancient essence +Sraracha +Jar of eyes +Giant egg sac(full) +Sarachnis cudgel +Scorpia's offspring +Odium shard 3 +Malediction shard 3 +Scurry +Scurrius' spine +Skotos +Jar of darkness +Dark claw +Dark totem +Uncut onyx +Ancient shard +Tiny tempor +Big harpoonfish +Spirit angler headband +Spirit angler top +Spirit angler waders +Spirit angler boots +Tome of water (empty) +Soaked page +Tackle box +Fish barrel +Dragon harpoon +Spirit flakes +Pet smoke devil +Occult necklace +Smoke battlestaff +Jar of smoke +Venenatis spiderling +Treasonous ring +Fangs of venenatis +Voidwaker gem +Vet'ion jr. +Ring of the gods +Skull of vet'ion +Voidwaker blade +Vorki +Vorkath's head +Skeletal visage +Jar of decay +Dragonbone necklace +Mini moktang +Volcanic dye +Igne gear frame +Volcanic shards +Dragonstone upgrade kit +Phoenix +Wintertoad +Tome of fire +Burnt page +Pyromancer garb +Pyromancer hood +Pyromancer robe +Pyromancer boots +Warm gloves +Bruma torch +Smolcano +Crystal tool seed +Zalcano shard +Pet snakeling +Brock +Tanzanite mutagen +Magma mutagen +Jar of swamp +Magic fang +Serpentine visage +Tanzanite fang +Zul-andra teleport +Zulrah's scales +Broken dwarven warhammer +Dwarven ore +Dwarven crate +Athelas seed +Abyssal thread +Abyssal cape +Dragon hunter lance +Ori +Nihil horn +Zaryte vambraces +Nihil shard +Nexling +Drygore mace +Offhand drygore mace +Drygore longsword +Offhand drygore longsword +Drygore rapier +Offhand drygore rapier +Perfect chitin +Baby kalphite king +Dark crystal +Abyssal gem +Tattered tome +Spellbound ring +Korulsi seed +Torva full helm (broken) +Torva platebody (broken) +Torva platelegs (broken) +Torva boots (broken) +Torva gloves (broken) +Pernix cowl (broken) +Pernix body (broken) +Pernix chaps (broken) +Pernix boots (broken) +Pernix gloves (broken) +Virtus mask (broken) +Virtus robe top (broken) +Virtus robe legs (broken) +Virtus boots (broken) +Virtus gloves (broken) +Virtus crystal +Zaryte bow +Frozen key +Bloodsoaked feather +Ancient emblem +Ancient hilt +Tattered robes of Vasa +Jar of magic +Voidling +Magus scroll +Magical artifact +Ignecarus scales +Ignecarus dragonclaw +Dragon egg +Ignis ring +Tangleroot +Crystal tangleroot +Dragonfruit tangleroot +Herb tangleroot +White lily tangleroot +Redwood tangleroot +Mysterious seed +Grand crystal acorn +Ent hide +Fish sack +Pufferfish +Squid dye +Queen black dragonling +Royal dragon kiteshield +Dragonbone upgrade kit +Royal torsion spring +Royal sight +Royal frame +Royal bolt stabiliser +Mini akumu +Nightmarish ashes +Cursed onyx +Demon statuette +Baby venatrix +Venatrix eggs +Venatrix webbing +Spiders leg bottom +Olmlet +Twisted bow +Elder maul +Kodai insignia +Dragon claws +Ancestral hat +Ancestral robe top +Ancestral robe bottom +Dinh's bulwark +Dexterous prayer scroll +Arcane prayer scroll +Dragon hunter crossbow +Twisted buckler +Torn prayer scroll +Dark relic +Onyx +Metamorphic dust +Twisted ancestral colour kit +Xeric's guard +Xeric's warrior +Xeric's sentinel +Xeric's general +Xeric's champion +Lil' zik +Scythe of vitur (uncharged) +Ghrazi rapier +Sanguinesti staff (uncharged) +Justiciar faceguard +Justiciar chestguard +Justiciar legguards +Avernic defender hilt +Vial of blood +Sinhaza shroud tier 1 +Sinhaza shroud tier 2 +Sinhaza shroud tier 3 +Sinhaza shroud tier 4 +Sinhaza shroud tier 5 +Sanguine dust +Holy ornament kit +Sanguine ornament kit +Lil' maiden +Lil' bloat +Lil' nylo +Lil' sot +Lil' xarp +Tumeken's guardian +Tumeken's shadow (uncharged) +Elidinis' ward +Masori mask +Masori body +Masori chaps +Lightbearer +Osmumten's fang +Thread of elidinis +Breach of the scarab +Eye of the corruptor +Jewel of the sun +Menaphite ornament kit +Cursed phalanx +Masori crafting kit +Cache of runes +Icthlarin's shroud (tier 1) +Icthlarin's shroud (tier 2) +Icthlarin's shroud (tier 3) +Icthlarin's shroud (tier 4) +Icthlarin's shroud (tier 5) +Remnant of akkha +Remnant of ba-ba +Remnant of kephri +Remnant of zebak +Ancient remnant +Shark jaw +Shark tooth +Oceanic relic +Aquifer aegis +Oceanic dye +Crush +Bruce +Pearl +Bluey +Oceanic shroud (tier 1) +Oceanic shroud (tier 2) +Oceanic shroud (tier 3) +Oceanic shroud (tier 4) +Oceanic shroud (tier 5) +Dragon claw +Offhand dragon claw +Ruined dragon armour slice +Ruined dragon armour lump +Ruined dragon armour shard +Dragon limbs +Earth warrior champion scroll +Ghoul champion scroll +Giant champion scroll +Goblin champion scroll +Hobgoblin champion scroll +Imp champion scroll +Jogre champion scroll +Lesser demon champion scroll +Skeleton champion scroll +Zombie champion scroll +Champion's cape +Elder chaos top +Elder chaos robe +Elder chaos hood +Viggora's chainmace (u) +Craw's bow (u) +Thammaron's sceptre (u) +Amulet of avarice +Bracelet of ethereum (uncharged) +Ancient crystal +Ancient relic +Ancient effigy +Ancient medallion +Ancient statuette +Ancient totem +Revenant cave teleport +Revenant ether +Mycelium leggings web +Mycelium poncho web +Mycelium visor web +Neem drupe +Polypore spore +Grifolic flake +Grifolic gloves +Grifolic orb +Gorajian mushroom +Tombshroom spore +Ganodermic flake +Ganodermic gloves +Ganodermic boots +Crawling hand +Cockatrice head +Basilisk head +Kurask head +Imbued heart +Eternal gem +Dust battlestaff +Mist battlestaff +Granite maul +Mudskipper hat +Flippers +Brine sabre +Leaf-bladed sword +Leaf-bladed battleaxe +Black mask (10) +Granite longsword +Granite boots +Wyvern visage +Granite legs +Granite helm +Bronze boots +Iron boots +Steel boots +Black boots +Mithril boots +Adamant boots +Rune boots +Dragon boots +Uncharged trident +Dark bow +Dragon sword +Broken dragon hasta +Drake's tooth +Drake's claw +Mystic hat (light) +Mystic robe top (light) +Mystic robe bottom (light) +Mystic gloves (light) +Mystic boots (light) +Mystic hat (dark) +Mystic robe top (dark) +Mystic robe bottom (dark) +Mystic gloves (dark) +Mystic boots (dark) +Mystic hat (dusk) +Mystic robe top (dusk) +Mystic robe bottom (dusk) +Mystic gloves (dusk) +Mystic boots (dusk) +Basilisk jaw +Dagon'hai hat +Dagon'hai robe top +Dagon'hai robe bottom +Blood shard +Copper stone spirit +Tin stone spirit +Iron stone spirit +Coal stone spirit +Silver stone spirit +Mithril stone spirit +Adamantite stone spirit +Gold stone spirit +Runite stone spirit +Brackish blade +Ancient ceremonial mask +Ancient ceremonial top +Ancient ceremonial legs +Ancient ceremonial gloves +Ancient ceremonial boots +Dagannoth slayer helm +Dagannoth mask +Jelly slayer helm +Jelly mask +Abyssal slayer helm +Abyssal mask +Black demonical slayer helm +Black demonical mask +Troll slayer helm +Troll mask +Ganodermic slayer helm +Ganodermic mask +Gargoyle slayer helm +Gargoyle mask +Dark beast slayer helm +Dark beast mask +Dust devil slayer helm +Dust devil mask +Crawling hand slayer helm +Crawling hand mask +Basilisk slayer helm +Basilisk mask +Bloodveld slayer helm +Bloodveld mask +Banshee slayer helm +Banshee's mask +Cockatrice slayer helm +Cockatrice mask +Aberrant slayer helm +Aberrant mask +Kurask slayer helm +Kurask mask +Obsidian cape +Toktz-ket-xil +Tzhaar-ket-om +Toktz-xil-ak +Toktz-xil-ek +Toktz-mej-tal +Toktz-xil-ul +Obsidian helmet +Obsidian platebody +Obsidian platelegs +Solite +Eagle egg +Sun-metal scraps +Lunite +Moonlight essence +Moondash charm +Noom +Mole slippers +Frog slippers +Bear feet +Demon feet +Jester cape +Shoulder parrot +Monk's robe top (t) +Monk's robe (t) +Amulet of defence (t) +Sandwich lady hat +Sandwich lady top +Sandwich lady bottom +Rune scimitar ornament kit (guthix) +Rune scimitar ornament kit (saradomin) +Rune scimitar ornament kit (zamorak) +Black pickaxe +Team cape zero +Team cape i +Team cape x +Cape of skulls +Golden chef's hat +Golden apron +Wooden shield (g) +Black full helm (t) +Black platebody (t) +Black platelegs (t) +Black plateskirt (t) +Black kiteshield (t) +Black full helm (g) +Black platebody (g) +Black platelegs (g) +Black plateskirt (g) +Black kiteshield (g) +Black shield (h1) +Black shield (h2) +Black shield (h3) +Black shield (h4) +Black shield (h5) +Black helm (h1) +Black helm (h2) +Black helm (h3) +Black helm (h4) +Black helm (h5) +Black platebody (h1) +Black platebody (h2) +Black platebody (h3) +Black platebody (h4) +Black platebody (h5) +Steel full helm (t) +Steel platebody (t) +Steel platelegs (t) +Steel plateskirt (t) +Steel kiteshield (t) +Steel full helm (g) +Steel platebody (g) +Steel platelegs (g) +Steel plateskirt (g) +Steel kiteshield (g) +Iron platebody (t) +Iron platelegs (t) +Iron plateskirt (t) +Iron kiteshield (t) +Iron full helm (t) +Iron platebody (g) +Iron platelegs (g) +Iron plateskirt (g) +Iron kiteshield (g) +Iron full helm (g) +Bronze platebody (t) +Bronze platelegs (t) +Bronze plateskirt (t) +Bronze kiteshield (t) +Bronze full helm (t) +Bronze platebody (g) +Bronze platelegs (g) +Bronze plateskirt (g) +Bronze kiteshield (g) +Bronze full helm (g) +Studded body (g) +Studded chaps (g) +Studded body (t) +Studded chaps (t) +Leather body (g) +Leather chaps (g) +Blue wizard hat (g) +Blue wizard robe (g) +Blue skirt (g) +Blue wizard hat (t) +Blue wizard robe (t) +Blue skirt (t) +Black wizard hat (g) +Black wizard robe (g) +Black skirt (g) +Black wizard hat (t) +Black wizard robe (t) +Black skirt (t) +Monk's robe top (g) +Monk's robe (g) +Saradomin robe top +Saradomin robe legs +Guthix robe top +Guthix robe legs +Zamorak robe top +Zamorak robe legs +Ancient robe top +Ancient robe legs +Armadyl robe top +Armadyl robe legs +Bandos robe top +Bandos robe legs +Bob's red shirt +Bob's green shirt +Bob's blue shirt +Bob's black shirt +Bob's purple shirt +Highwayman mask +Blue beret +Black beret +White beret +Red beret +A powdered wig +Beanie +Imp mask +Goblin mask +Sleeping cap +Flared trousers +Pantaloons +Black cane +Staff of bob the cat +Red elegant shirt +Red elegant blouse +Red elegant legs +Red elegant skirt +Green elegant shirt +Green elegant blouse +Green elegant legs +Green elegant skirt +Blue elegant shirt +Blue elegant blouse +Blue elegant legs +Blue elegant skirt +Amulet of magic (t) +Amulet of power (t) +Ham joint +Rain bow +Willow comp bow +Ranger boots +Wizard boots +Holy sandals +Climbing boots (g) +Spiked manacles +Adamant full helm (t) +Adamant platebody (t) +Adamant platelegs (t) +Adamant plateskirt (t) +Adamant kiteshield (t) +Adamant full helm (g) +Adamant platebody (g) +Adamant platelegs (g) +Adamant plateskirt (g) +Adamant kiteshield (g) +Adamant shield (h1) +Adamant shield (h2) +Adamant shield (h3) +Adamant shield (h4) +Adamant shield (h5) +Adamant helm (h1) +Adamant helm (h2) +Adamant helm (h3) +Adamant helm (h4) +Adamant helm (h5) +Adamant platebody (h1) +Adamant platebody (h2) +Adamant platebody (h3) +Adamant platebody (h4) +Adamant platebody (h5) +Mithril full helm (t) +Mithril platebody (t) +Mithril platelegs (t) +Mithril plateskirt (t) +Mithril kiteshield (t) +Mithril full helm (g) +Mithril platebody (g) +Mithril platelegs (g) +Mithril plateskirt (g) +Mithril kiteshield (g) +Green d'hide body (g) +Green d'hide body (t) +Green d'hide chaps (g) +Green d'hide chaps (t) +Saradomin mitre +Saradomin cloak +Guthix mitre +Guthix cloak +Zamorak mitre +Zamorak cloak +Ancient mitre +Ancient cloak +Ancient stole +Ancient crozier +Armadyl mitre +Armadyl cloak +Armadyl stole +Armadyl crozier +Bandos mitre +Bandos cloak +Bandos stole +Bandos crozier +Red boater +Green boater +Orange boater +Black boater +Blue boater +Pink boater +Purple boater +White boater +Red headband +Black headband +Brown headband +White headband +Blue headband +Gold headband +Pink headband +Green headband +Crier hat +Crier coat +Crier bell +Adamant cane +Arceuus banner +Piscarilius banner +Hosidius banner +Shayzien banner +Lovakengj banner +Cabbage round shield +Black unicorn mask +White unicorn mask +Cat mask +Penguin mask +Leprechaun hat +Black leprechaun hat +Wolf mask +Wolf cloak +Purple elegant shirt +Purple elegant blouse +Purple elegant legs +Purple elegant skirt +Black elegant shirt +White elegant blouse +Black elegant legs +White elegant skirt +Pink elegant shirt +Pink elegant blouse +Pink elegant legs +Pink elegant skirt +Gold elegant shirt +Gold elegant blouse +Gold elegant legs +Gold elegant skirt +Gnomish firelighter +Strength amulet (t) +Yew comp bow +Robin hood hat +Dragon boots ornament kit +Rune defender ornament kit +Tzhaar-ket-om ornament kit +Berserker necklace ornament kit +Rune full helm (t) +Rune platebody (t) +Rune platelegs (t) +Rune plateskirt (t) +Rune kiteshield (t) +Rune full helm (g) +Rune platebody (g) +Rune platelegs (g) +Rune plateskirt (g) +Rune kiteshield (g) +Zamorak full helm +Zamorak platebody +Zamorak platelegs +Zamorak plateskirt +Zamorak kiteshield +Guthix full helm +Guthix platebody +Guthix platelegs +Guthix plateskirt +Guthix kiteshield +Saradomin full helm +Saradomin platebody +Saradomin platelegs +Saradomin plateskirt +Saradomin kiteshield +Ancient full helm +Ancient platebody +Ancient platelegs +Ancient plateskirt +Ancient kiteshield +Armadyl full helm +Armadyl platebody +Armadyl platelegs +Armadyl plateskirt +Armadyl kiteshield +Bandos full helm +Bandos platebody +Bandos platelegs +Bandos plateskirt +Bandos kiteshield +Rune shield (h1) +Rune shield (h2) +Rune shield (h3) +Rune shield (h4) +Rune shield (h5) +Rune helm (h1) +Rune helm (h2) +Rune helm (h3) +Rune helm (h4) +Rune helm (h5) +Rune platebody (h1) +Rune platebody (h2) +Rune platebody (h3) +Rune platebody (h4) +Rune platebody (h5) +Saradomin coif +Saradomin d'hide body +Saradomin chaps +Saradomin bracers +Saradomin d'hide boots +Saradomin d'hide shield +Guthix coif +Guthix d'hide body +Guthix chaps +Guthix bracers +Guthix d'hide boots +Guthix d'hide shield +Zamorak coif +Zamorak d'hide body +Zamorak chaps +Zamorak bracers +Zamorak d'hide boots +Zamorak d'hide shield +Bandos coif +Bandos d'hide body +Bandos chaps +Bandos bracers +Bandos d'hide boots +Bandos d'hide shield +Armadyl coif +Armadyl d'hide body +Armadyl chaps +Armadyl bracers +Armadyl d'hide boots +Armadyl d'hide shield +Ancient coif +Ancient d'hide body +Ancient chaps +Ancient bracers +Ancient d'hide boots +Ancient d'hide shield +Red d'hide body (t) +Red d'hide chaps (t) +Red d'hide body (g) +Red d'hide chaps (g) +Blue d'hide body (t) +Blue d'hide chaps (t) +Blue d'hide body (g) +Blue d'hide chaps (g) +Enchanted hat +Enchanted top +Enchanted robe +Saradomin stole +Saradomin crozier +Guthix stole +Guthix crozier +Zamorak stole +Zamorak crozier +Zombie head +Cyclops head +Pirate's hat +Red cavalier +White cavalier +Navy cavalier +Tan cavalier +Dark cavalier +Black cavalier +Pith helmet +Explorer backpack +Thieving bag +Green dragon mask +Blue dragon mask +Red dragon mask +Black dragon mask +Nunchaku +Dual sai +Rune cane +Amulet of glory (t4) +Magic comp bow +Ring of 3rd age +Fury ornament kit +Dragon chainbody ornament kit +Dragon legs/skirt ornament kit +Dragon sq shield ornament kit +Dragon full helm ornament kit +Dragon scimitar ornament kit +Light infinity colour kit +Dark infinity colour kit +Holy wraps +Ranger gloves +Rangers' tunic +Rangers' tights +Black d'hide body (g) +Black d'hide chaps (g) +Black d'hide body (t) +Black d'hide chaps (t) +Royal crown +Royal sceptre +Royal gown top +Royal gown bottom +Musketeer hat +Musketeer tabard +Musketeer pants +Dark tuxedo jacket +Dark trousers +Dark tuxedo shoes +Dark tuxedo cuffs +Dark bow tie +Light tuxedo jacket +Light trousers +Light tuxedo shoes +Light tuxedo cuffs +Light bow tie +Arceuus scarf +Hosidius scarf +Piscarilius scarf +Shayzien scarf +Lovakengj scarf +Bronze dragon mask +Iron dragon mask +Steel dragon mask +Mithril dragon mask +Adamant dragon mask +Rune dragon mask +Katana +Dragon cane +Briefcase +Bucket helm +Blacksmith's helm +Deerstalker +Afro +Big pirate hat +Top hat +Monocle +Sagacious spectacles +Fremennik kilt +Giant boot +Uri's hat +Bloodhound +Armadyl godsword ornament kit +Bandos godsword ornament kit +Saradomin godsword ornament kit +Zamorak godsword ornament kit +Occult ornament kit +Torture ornament kit +Anguish ornament kit +Dragon defender ornament kit +Dragon kiteshield ornament kit +Dragon platebody ornament kit +Tormented ornament kit +Hood of darkness +Robe top of darkness +Robe bottom of darkness +Gloves of darkness +Boots of darkness +Samurai kasa +Samurai shirt +Samurai greaves +Samurai boots +Samurai gloves +Ankou mask +Ankou top +Ankou gloves +Ankou socks +Ankou's leggings +Mummy's head +Mummy's feet +Mummy's hands +Mummy's legs +Mummy's body +Shayzien hood +Hosidius hood +Arceuus hood +Piscarilius hood +Lovakengj hood +Lesser demon mask +Greater demon mask +Black demon mask +Jungle demon mask +Old demon mask +Left eye patch +Bowl wig +Ale of the gods +Obsidian cape (r) +Half moon spectacles +Fancy tiara +Lord marshal boots +Lord marshal gloves +Lord marshal trousers +Lord marshal top +Lord marshal cap +Akumu mask +Commander boots +Commander gloves +Commander trousers +Commander top +Commander cap +Apple parasol +Watermelon parasol +Lemon parasol +Strawberry parasol +Blueberry parasol +Grape parasol +Coconut parasol +Detective hat +Detective trenchcoat +Detective pants +2nd age range legs +2nd age range top +2nd age range coif +2nd age bow +2nd age mage top +2nd age mage bottom +2nd age mage mask +2nd age staff +First age robe top +First age robe bottom +Clue bag +Inventors tools +Elder knowledge +Octo +Dwarven blessing +Ring of luck +Holiday Mystery Box +Deathtouched dart +Ignecarus mask +Malygos mask +Blabberbeak +Helm of raedwald +Clue hunter garb +Clue hunter gloves +Clue hunter trousers +Clue hunter boots +Clue hunter cloak +First age tiara +First age amulet +First age cape +First age bracelet +First age ring +Blood dye +Third age dye +Ice dye +Shadow dye +3rd age range coif +3rd age range top +3rd age range legs +3rd age vambraces +3rd age robe top +3rd age robe +3rd age mage hat +3rd age amulet +3rd age plateskirt +3rd age platelegs +3rd age platebody +3rd age full helmet +3rd age kiteshield +Gilded platebody +Gilded platelegs +Gilded plateskirt +Gilded full helm +Gilded kiteshield +Gilded med helm +Gilded chainbody +Gilded sq shield +Gilded 2h sword +Gilded spear +Gilded hasta +3rd age longsword +3rd age wand +3rd age cloak +3rd age bow +Gilded scimitar +Gilded boots +Gilded coif +Gilded d'hide vambraces +Gilded d'hide body +Gilded d'hide chaps +Gilded pickaxe +Gilded axe +Gilded spade +Ring of nature +Lava dragon mask +3rd age pickaxe +3rd age axe +3rd age druidic robe bottoms +3rd age druidic robe top +3rd age druidic staff +3rd age druidic cloak +Bucket helm (g) +Ring of coins +Saradomin page 1 +Saradomin page 2 +Saradomin page 3 +Saradomin page 4 +Zamorak page 1 +Zamorak page 2 +Zamorak page 3 +Zamorak page 4 +Guthix page 1 +Guthix page 2 +Guthix page 3 +Guthix page 4 +Bandos page 1 +Bandos page 2 +Bandos page 3 +Bandos page 4 +Armadyl page 1 +Armadyl page 2 +Armadyl page 3 +Armadyl page 4 +Ancient page 1 +Ancient page 2 +Ancient page 3 +Ancient page 4 +Holy blessing +Unholy blessing +Peaceful blessing +War blessing +Honourable blessing +Ancient blessing +Nardah teleport +Mos le'harmless teleport +Mort'ton teleport +Feldip hills teleport +Lunar isle teleport +Digsite teleport +Piscatoris teleport +Pest control teleport +Tai bwo wannai teleport +Lumberyard teleport +Iorwerth camp teleport +Master scroll book (empty) +Red firelighter +Green firelighter +Blue firelighter +Purple firelighter +White firelighter +Charge dragonstone jewellery scroll +Purple sweets +Pet penance queen +Fighter hat +Ranger hat +Runner hat +Healer hat +Fighter torso +Penance skirt +Runner boots +Penance gloves +Granite body +Agility arena ticket +Pirate's hook +Brimhaven graceful hood +Brimhaven graceful top +Brimhaven graceful legs +Brimhaven graceful gloves +Brimhaven graceful boots +Brimhaven graceful cape +Red decorative full helm +Red decorative helm +Red decorative body +Red decorative legs +Red decorative skirt +Red decorative boots +Red decorative shield +Red decorative sword +White decorative full helm +White decorative helm +White decorative body +White decorative legs +White decorative skirt +White decorative boots +White decorative shield +White decorative sword +Gold decorative full helm +Gold decorative helm +Gold decorative body +Gold decorative legs +Gold decorative skirt +Gold decorative boots +Gold decorative shield +Gold decorative sword +Zamorak castlewars hood +Zamorak castlewars cloak +Saradomin castlewars hood +Saradomin castlewars cloak +Saradomin banner +Zamorak banner +Decorative magic hat +Decorative magic top +Decorative magic robe +Decorative ranged top +Decorative ranged legs +Decorative quiver +Saradomin halo +Zamorak halo +Guthix halo +Castle wars cape (beginner) +Castle wars cape (intermediate) +Castle wars cape (advanced) +Castle wars cape (expert) +Castle wars cape (legend) +Angler hat +Angler top +Angler waders +Angler boots +Smiths tunic +Smiths trousers +Smiths boots +Smiths gloves +Colossal blade +Double ammo mould +Kovac's grog +Smithing catalyst +Ore pack (Giant's Foundry) +Grand seed pod +Gnome scarf +Gnome goggles +Mint cake +Abyssal protector +Abyssal pearls +Catalytic talisman +Abyssal needle +Abyssal green dye +Abyssal blue dye +Abyssal red dye +Hat of the eye +Robe top of the eye +Robe bottoms of the eye +Boots of the eye +Ring of the elements +Abyssal lantern +Guardian's eye +Intricate pouch +Lost bag +Tarnished locket +Hallowed mark +Hallowed token +Hallowed grapple +Hallowed focus +Hallowed symbol +Hallowed hammer +Hallowed ring +Dark dye +Dark acorn +Strange old lockpick +Ring of endurance (uncharged) +Mysterious page 1 +Mysterious page 2 +Mysterious page 3 +Mysterious page 4 +Mysterious page 5 +Deadman's chest +Deadman's legs +Deadman's cape +Armadyl halo +Bandos halo +Seren halo +Ancient halo +Brassica halo +Golden armadyl special attack +Golden bandos special attack +Golden saradomin special attack +Golden zamorak special attack +Victor's cape (1) +Victor's cape (10) +Victor's cape (50) +Victor's cape (100) +Victor's cape (500) +Victor's cape (1000) +Granite clamp +Ornate maul handle +Steam staff upgrade kit +Lava staff upgrade kit +Dragon pickaxe upgrade kit +Ward upgrade kit +Green dark bow paint +Yellow dark bow paint +White dark bow paint +Blue dark bow paint +Volcanic whip mix +Frozen whip mix +Guthixian icon +Swift blade +Beginner wand +Apprentice wand +Teacher wand +Master wand +Infinity hat +Infinity top +Infinity bottoms +Infinity boots +Infinity gloves +Mage's book +Builders supply crate +Carpenter's helmet +Carpenter's shirt +Carpenter's trousers +Carpenter's boots +Amy's saw +Plank sack +Hosidius blueprints +Void knight mace +Void knight top +Void knight robe +Void knight gloves +Void mage helm +Void melee helm +Void ranger helm +Void seal(8) +Elite void top +Elite void robe +Rogue mask +Rogue top +Rogue trousers +Rogue boots +Rogue gloves +Amulet of the damned (full) +Flamtaer bag +Fine cloth +Bronze locks +Steel locks +Black locks +Silver locks +Gold locks +Zealot's helm +Zealot's robe top +Zealot's robe bottom +Zealot's boots +Tree wizards' journal +Bloody notes +Gary +Lil' creator +Red soul cape +Ectoplasmator +Lumberjack hat +Lumberjack top +Lumberjack legs +Lumberjack boots +Farmer's strawhat +Farmer's jacket +Farmer's boro trousers +Farmer's boots +Seed box +Gricoller's can +Herb sack +Blue naval shirt +Blue tricorn hat +Blue navy slacks +Green naval shirt +Green tricorn hat +Green navy slacks +Red naval shirt +Red tricorn hat +Red navy slacks +Brown naval shirt +Brown tricorn hat +Brown navy slacks +Black naval shirt +Black tricorn hat +Black navy slacks +Purple naval shirt +Purple tricorn hat +Purple navy slacks +Grey naval shirt +Grey tricorn hat +Grey navy slacks +Golden naval shirt +Golden tricorn hat +Golden navy slacks +Cutthroat flag +Gilded smile flag +Bronze fist flag +Lucky shot flag +Treasure flag +Phasmatys flag +The stuff +Red rum (trouble brewing) +Blue rum (trouble brewing) +Jolly roger cape +Ash covered tome +Large water container +Volcanic mine teleport +Dragon pickaxe (broken) +Master runecrafter hat +Master runecrafter robe +Master runecrafter skirt +Master runecrafter boots +Elder thread +Elder talisman +Magic crate +Lychee seed +Avocado seed +Mango seed +Monkey egg +Marimbo statue +Monkey dye +Banana enchantment scroll +Rumble token +Big banana +Monkey crate +Gorilla rumble greegree +Beginner rumble greegree +Intermediate rumble greegree +Ninja rumble greegree +Expert ninja rumble greegree +Elder rumble greegree +Fishing hat +Fishing jacket +Fishing waders +Fishing boots +Contest rod +Beginner's tackle box +Basic tackle box +Standard tackle box +Professional tackle box +Champion's tackle box +Golden fishing trophy +Inferno adze +Flame gloves +Ring of fire +Phoenix eggling +Rune spikeshield +Rune berserker shield +Adamant spikeshield +Adamant berserker shield +Guthix engram +Fist of guthix token +Diviner's headwear +Diviner's robe +Diviner's legwear +Diviner's handwear +Diviner's footwear +Stealing creation token +Fletcher's gloves +Fletcher's boots +Fletcher's top +Fletcher's hat +Fletcher's legs +Inventors' helmet +Inventors' torso +Inventors' legs +Inventors' gloves +Inventors' boots +Inventors' backpack +Materials bag +A stylish hat (male, yellow) +Shirt (male, yellow) +Leggings (yellow) +A stylish hat (male, maroon) +Shirt (male, maroon) +Leggings (maroon) +A stylish hat (male, green) +Shirt (male, green) +Leggings (green) +A stylish hat (female, yellow) +Shirt (female, yellow) +Skirt (yellow) +A stylish hat (female, maroon) +Shirt (female, maroon) +Skirt (maroon) +A stylish hat (female, green) +Shirt (female, green) +Skirt (green) +Shoes (male, shoes) +Shoes (male, boots) +Shoes (female, straps) +Shoes (female, flats) +Giant's hand +Acrobat set +Clown set +Ringmaster set +Golden tench +Pearl fishing rod +Pearl fly fishing rod +Pearl barbarian rod +Heron +Rock golem +Beaver +Baby chinchompa +Giant squirrel +Rocky +Rift guardian +Herbi +Chompy chick +Barronite mace +Barronite head +Barronite handle +Barronite guard +Ancient globe +Ancient ledger +Ancient astroscope +Ancient treatise +Ancient carcanet +Imcando hammer +Chompy bird hat (ogre bowman) +Chompy bird hat (bowman) +Chompy bird hat (ogre yeoman) +Chompy bird hat (yeoman) +Chompy bird hat (ogre marksman) +Chompy bird hat (marksman) +Chompy bird hat (ogre woodsman) +Chompy bird hat (woodsman) +Chompy bird hat (ogre forester) +Chompy bird hat (forester) +Chompy bird hat (ogre bowmaster) +Chompy bird hat (bowmaster) +Chompy bird hat (ogre expert) +Chompy bird hat (expert) +Chompy bird hat (ogre dragon archer) +Chompy bird hat (dragon archer) +Chompy bird hat (expert ogre dragon archer) +Chompy bird hat (expert dragon archer) +Tea flask +Plain satchel +Green satchel +Red satchel +Black satchel +Gold satchel +Rune satchel +Bronze defender +Iron defender +Steel defender +Black defender +Mithril defender +Adamant defender +Rune defender +Dragon defender +Fox whistle +Golden pheasant egg +Forestry hat +Forestry top +Forestry legs +Forestry boots +Twitcher's gloves +Funky shaped log +Log basket +Log brace +Clothes pouch blueprint +Cape pouch +Felling axe handle +Pheasant hat +Pheasant legs +Pheasant boots +Pheasant cape +Petal garland +Sturdy beehive parts +Scribbled note +Partial note +Ancient note +Ancient writings +Experimental note +Paragraph of text +Musty smelling note +Hastily scrawled note +Old writing +Short note +Zenyte shard +Light frame +Heavy frame +Ballista limbs +Monkey tail +Ballista spring +Karamjan monkey +Kruk jr +Maniacal monkey +Princely monkey +Skeleton monkey +Zombie monkey +Coal bag +Gem bag +Prospector helmet +Prospector jacket +Prospector legs +Prospector boots +Mark of grace +Graceful hood +Graceful cape +Graceful top +Graceful legs +Graceful gloves +Graceful boots +Scruffy +Harry +Skipper +Celestial ring (uncharged) +Star fragment +Chaotic rapier +Chaotic longsword +Chaotic maul +Chaotic staff +Chaotic crossbow +Offhand Chaotic rapier +Offhand Chaotic longsword +Offhand Chaotic crossbow +Scroll of life +Scroll of efficiency +Scroll of cleansing +Scroll of dexterity +Scroll of teleportation +Scroll of farming +Scroll of proficiency +Scroll of mystery +Scroll of longevity +Scroll of the hunt +Farseer kiteshield +Chaotic remnant +Frostbite +Gorajan shards +Amulet of zealots +Herbicide +Gorajan warrior helmet +Gorajan warrior top +Gorajan warrior legs +Gorajan warrior gloves +Gorajan warrior boots +Gorajan archer helmet +Gorajan archer top +Gorajan archer legs +Gorajan archer gloves +Gorajan archer boots +Gorajan occult helmet +Gorajan occult top +Gorajan occult legs +Gorajan occult gloves +Gorajan occult boots +Arcane blast necklace +Farsight snapshot necklace +Brawler's hook necklace +Daemonheim agility pass +Dungeoneering dye +Gorajan bonecrusher (u) +Grimy guam leaf +Guam seed +Grimy marrentill +Marrentill seed +Grimy tarromin +Tarromin seed +Grimy harralander +Harralander seed +Grimy ranarr weed +Ranarr seed +Grimy toadflax +Toadflax seed +Grimy irit leaf +Irit seed +Grimy avantoe +Avantoe seed +Grimy kwuarm +Kwuarm seed +Grimy snapdragon +Snapdragon seed +Grimy cadantine +Cadantine seed +Grimy lantadyme +Lantadyme seed +Grimy dwarf weed +Dwarf weed seed +Grimy torstol +Torstol seed +Athelas +Spirit weed +Spirit weed seed +Grimy korulsi +Acorn +Oak logs +Oak roots +Willow seed +Willow logs +Willow roots +Maple seed +Maple logs +Maple roots +Yew seed +Yew logs +Yew roots +Magic seed +Magic logs +Magic roots +Potato +Potato seed +Onion +Onion seed +Cabbage +Cabbage seed +Tomato +Tomato seed +Sweetcorn +Sweetcorn seed +Strawberry +Strawberry seed +Watermelon +Watermelon seed +Snape grass +Snape grass seed +Cooking apple +Apple tree seed +Banana +Banana tree seed +Orange +Orange tree seed +Curry leaf +Curry tree seed +Pineapple +Pineapple seed +Papaya fruit +Papaya tree seed +Coconut +Palm tree seed +Dragonfruit +Dragonfruit tree seed +Blood orange +Blood orange seed +Barley +Barley seed +Hammerstone hops +Hammerstone seed +Asgarnian hops +Asgarnian seed +Jute fibre +Jute seed +Yanillian hops +Yanillian seed +Krandorian hops +Krandorian seed +Wildblood hops +Wildblood seed +Marigolds +Marigold seed +Redberries +Redberry seed +Rosemary +Rosemary seed +Cadava berries +Cadavaberry seed +Giant seaweed +Seaweed spore +Nasturtiums +Nasturtium seed +Woad leaf +Woad seed +Limpwurt root +Limpwurt seed +Teak seed +Teak logs +Grapes +Saltpetre +Grape seed +Dwellberries +Dwellberry seed +Jangerberries +Jangerberry seed +Mushroom +Mushroom spore +Cactus spine +Cactus seed +Mahogany seed +Mahogany logs +White lily +White lily seed +White berries +Whiteberry seed +Cave nightshade +Belladonna seed +Potato cactus +Potato cactus seed +Hespori seed +Poison ivy berries +Poison ivy seed +Calquat fruit +Calquat tree seed +Crystal shard +Crystal acorn +Spirit seed +Celastrus bark +Celastrus seed +Redwood tree seed +Redwood logs +Tombshroom +Morchella mushroom +Morchella mushroom spore +Avocado +Mango +Lychee +Advax berry +Advax berry seed +Herbal zygomite spores +Barky zygomite spores +Fruity zygomite spores +Toxic zygomite spores +Master farmer hat +Master farmer jacket +Master farmer pants +Master farmer gloves +Master farmer boots +Plopper +Shiny mango +Fungo +Cooked meat +Sinew +Shrimps +Cooked chicken +Anchovies +Sardine +Herring +Mackerel +Trout +Cod +Pike +Salmon +Tuna +Cooked karambwan +Jug of wine +Cave eel +Lobster +Cooked jubbly +Bass +Swordfish +Monkfish +Wine of zamorak +Shark +Sea turtle +Anglerfish +Dark crab +Manta ray +Rocktail +Turkey +Christmas cake +Bird house +Oak bird house +Willow bird house +Teak bird house +Maple bird house +Mahogany bird house +Yew bird house +Magic bird house +Redwood bird house +Elder bird house +Serpentine helm (uncharged) +Toxic staff (uncharged) +Uncharged toxic trident +Green d'hide vambraces +Green d'hide chaps +Green d'hide shield +Green d'hide body +Blue d'hide vambraces +Blue d'hide chaps +Blue d'hide shield +Blue d'hide body +Red d'hide vambraces +Red d'hide chaps +Red d'hide shield +Red d'hide body +Black d'hide vambraces +Black d'hide chaps +Black d'hide shield +Black d'hide body +Opal +Jade +Red topaz +Sapphire +Emerald +Ruby +Diamond +Dragonstone +Zenyte +Molten glass +Beer glass +Empty candle lantern +Empty oil lamp +Vial +Fishbowl +Unpowered orb +Lantern lens +Empty light orb +Heat res. vial +Gold ring +Gold necklace +Gold bracelet +Gold amulet (u) +Gold amulet +Sapphire ring +Sapphire necklace +Sapphire bracelet +Sapphire amulet (u) +Sapphire amulet +Emerald ring +Emerald necklace +Emerald bracelet +Emerald amulet (u) +Emerald amulet +Ruby ring +Ruby necklace +Ruby bracelet +Ruby amulet (u) +Ruby amulet +Diamond ring +Diamond necklace +Diamond bracelet +Diamond amulet (u) +Diamond amulet +Dragonstone ring +Dragon necklace +Dragonstone bracelet +Dragonstone amulet (u) +Dragonstone amulet +Onyx ring +Onyx necklace +Onyx bracelet +Onyx amulet (u) +Onyx amulet +Zenyte ring +Zenyte necklace +Zenyte bracelet +Zenyte amulet (u) +Zenyte amulet +Leather gloves +Leather boots +Leather cowl +Leather vambraces +Leather body +Leather chaps +Hardleather body +Coif +Hard leather shield +Studded body +Studded chaps +Drift net +Snakeskin boots +Snakeskin vambraces +Snakeskin bandana +Snakeskin chaps +Snakeskin body +Snakeskin shield +Xerician hat +Xerician robe +Xerician top +Splitbark gauntlets +Splitbark boots +Splitbark helm +Splitbark legs +Splitbark body +Neitiznot shield +Water battlestaff +Earth battlestaff +Fire battlestaff +Air battlestaff +Ball of wool +Bow string +Crossbow string +Clockwork +Amethyst bolt tips +Amethyst arrowtips +Amethyst javelin heads +Amethyst dart tip +Strung rabbit foot +Obsidian javelin heads +Opal ring +Opal necklace +Opal bracelet +Opal amulet (u) +Opal amulet +Jade ring +Jade necklace +Jade bracelet +Jade amulet (u) +Jade amulet +Topaz ring +Topaz necklace +Topaz bracelet +Topaz amulet (u) +Topaz amulet +Unstrung symbol +Unblessed symbol +Unstrung emblem +Unpowered symbol +Silver sickle +Tiara +Leather +Hard leather +Green dragon leather +Blue dragon leather +Red dragon leather +Black dragon leather +Royal dragon leather +Infernal slayer helmet +Royal dragonhide body +Royal dragonhide boots +Royal dragonhide chaps +Royal dragonhide coif +Royal dragonhide vambraces +Carapace helm +Carapace torso +Carapace legs +Carapace boots +Carapace gloves +Carapace shield +Guam leaf +Marrentill +Tarromin +Harralander +Ranarr weed +Toadflax +Irit leaf +Avantoe +Kwuarm +Snapdragon +Cadantine +Lantadyme +Dwarf weed +Torstol +Korulsi +Guam potion (unf) +Marrentill potion (unf) +Tarromin potion (unf) +Harralander potion (unf) +Ranarr potion (unf) +Toadflax potion (unf) +Irit potion (unf) +Avantoe potion (unf) +Kwuarm potion (unf) +Snapdragon potion (unf) +Cadantine potion (unf) +Lantadyme potion (unf) +Dwarf weed potion (unf) +Torstol potion (unf) +Cadantine blood potion (unf) +Attack potion(3) +Antipoison(3) +Strength potion(3) +Serum 207 (3) +Guthix rest(3) +Compost potion(3) +Restore potion(3) +Guthix balance(3) +Energy potion(3) +Defence potion(3) +Agility potion(3) +Combat potion(3) +Prayer potion(3) +Super attack(3) +Superantipoison(3) +Fishing potion(3) +Super energy(3) +Hunter potion(3) +Super strength(3) +Weapon poison +Super restore(3) +Super defence(3) +Antidote+(4) +Antifire potion(3) +Ranging potion(3) +Weapon poison(+) +Magic potion(3) +Stamina potion(4) +Zamorak brew(3) +Antidote++(4) +Bastion potion(3) +Battlemage potion(3) +Saradomin brew(3) +Weapon poison(++) +Extended antifire(4) +Ancient brew(3) +Anti-venom(4) +Menaphite remedy(3) +Super combat potion(4) +Forgotten brew(4) +Super antifire potion(4) +Anti-venom+(4) +Extended super antifire(4) +Heat res. brew +Heat res. restore +Dragon's fury +Sanfew serum(4) +Enhanced saradomin brew +Enhanced super restore +Enhanced stamina potion +Enhanced divine water +Divination potion +Unicorn horn dust +Chocolate dust +Kebbit teeth dust +Crushed nest +Goat horn dust +Silver dust +Crushed superior dragon bones +Dragon scale dust +Lava scale shard +Athelas paste +Nihil dust +Guam tar +Marrentill tar +Tarromin tar +Harralander tar +Neem oil +Deathly toxic potion +Attack mix(2) +Antipoison mix(2) +Relicym's mix(2) +Strength mix(2) +Restore mix(2) +Energy mix(2) +Defence mix(2) +Agility mix(2) +Combat mix(2) +Prayer mix(2) +Superattack mix(2) +Anti-poison supermix(2) +Fishing mix(2) +Super energy mix(2) +Hunting mix(2) +Super str. mix(2) +Magic essence mix(2) +Super restore mix(2) +Super def. mix(2) +Antidote+ mix(2) +Antifire mix(2) +Ranging mix(2) +Magic mix(2) +Zamorak mix(2) +Stamina mix(2) +Extended antifire mix(2) +Ancient mix(2) +Super antifire mix(2) +Extended super antifire mix(2) +Bronze axe +Bronze dagger +Bronze mace +Bronze med helm +Bronze bolts (unf) +Bronze nails +Bronze sword +Bronze wire +Bronze dart tip +Bronze scimitar +Bronze hasta +Bronze arrowtips +Bronze spear +Bronze javelin heads +Bronze longsword +Bronze limbs +Bronze knife +Bronze full helm +Bronze sq shield +Bronze warhammer +Bronze battleaxe +Bronze chainbody +Bronze kiteshield +Bronze claws +Bronze 2h sword +Bronze platelegs +Bronze plateskirt +Bronze platebody +Iron dagger +Iron axe +Iron spit +Iron mace +Iron bolts (unf) +Iron med helm +Iron nails +Iron dart tip +Iron sword +Iron arrowtips +Iron scimitar +Iron hasta +Iron spear +Iron longsword +Iron javelin heads +Iron full helm +Iron knife +Iron limbs +Iron sq shield +Iron warhammer +Iron battleaxe +Oil lantern frame +Iron chainbody +Iron kiteshield +Iron claws +Iron 2h sword +Iron plateskirt +Iron platelegs +Iron platebody +Silver stake +Silver bolts (unf) +Steel dagger +Steel axe +Steel mace +Steel med helm +Steel bolts (unf) +Steel dart tip +Steel nails +Steel sword +Cannonball +Steel scimitar +Steel arrowtips +Steel hasta +Steel spear +Steel limbs +Steel studs +Steel longsword +Steel javelin heads +Steel knife +Steel full helm +Steel sq shield +Steel warhammer +Steel battleaxe +Steel chainbody +Steel kiteshield +Steel claws +Steel 2h sword +Steel platelegs +Steel plateskirt +Steel platebody +Bullseye lantern (unf) +Gold helmet +Gold bowl +Mithril dagger +Mithril axe +Mithril mace +Mithril med helm +Mithril bolts (unf) +Mithril sword +Mithril dart tip +Mithril nails +Mithril arrowtips +Mithril scimitar +Mithril hasta +Mithril spear +Mithril longsword +Mithril javelin heads +Mithril limbs +Mithril full helm +Mithril knife +Mithril sq shield +Mith grapple tip +Mithril warhammer +Mithril battleaxe +Mithril chainbody +Mithril kiteshield +Mithril claws +Mithril 2h sword +Mithril plateskirt +Mithril platelegs +Mithril platebody +Adamant dagger +Adamant axe +Adamant mace +Adamant bolts(unf) +Adamant med helm +Adamant dart tip +Adamant sword +Adamantite nails +Adamant arrowtips +Adamant scimitar +Adamant hasta +Adamant spear +Adamantite limbs +Adamant longsword +Adamant javelin heads +Adamant full helm +Adamant knife +Adamant sq shield +Adamant warhammer +Adamant battleaxe +Adamant chainbody +Adamant kiteshield +Adamant claws +Adamant 2h sword +Adamant plateskirt +Adamant platelegs +Adamant platebody +Rune dagger +Rune axe +Rune mace +Runite bolts (unf) +Rune med helm +Rune sword +Rune nails +Rune dart tip +Rune arrowtips +Rune scimitar +Rune hasta +Rune spear +Rune longsword +Rune javelin heads +Runite limbs +Rune knife +Rune full helm +Rune sq shield +Rune warhammer +Rune battleaxe +Rune chainbody +Rune kiteshield +Rune claws +Rune platebody +Rune plateskirt +Rune platelegs +Rune 2h sword +Dwarven greataxe +Dwarven knife +Dwarven gauntlets +Dwarven greathammer +Dwarven pickaxe +Dwarven warhammer +Dwarven full helm +Dwarven platebody +Dwarven platelegs +Dwarven gloves +Dwarven boots +Sun-god axe head +Simple kibble +Delicious kibble +Extraordinary kibble +Arceuus graceful hood +Piscarilius graceful hood +Lovakengj graceful hood +Shayzien graceful hood +Hosidius graceful hood +Kourend graceful hood +Dark graceful hood +Trailblazer graceful hood +Spooky graceful hood +Arceuus graceful top +Piscarilius graceful top +Lovakengj graceful top +Shayzien graceful top +Hosidius graceful top +Kourend graceful top +Dark graceful top +Trailblazer graceful top +Spooky graceful top +Arceuus graceful legs +Piscarilius graceful legs +Lovakengj graceful legs +Shayzien graceful legs +Hosidius graceful legs +Kourend graceful legs +Dark graceful legs +Trailblazer graceful legs +Spooky graceful legs +Arceuus graceful boots +Piscarilius graceful boots +Lovakengj graceful boots +Shayzien graceful boots +Hosidius graceful boots +Kourend graceful boots +Dark graceful boots +Trailblazer graceful boots +Silverhawk boots +Spooky graceful boots +Arceuus graceful gloves +Piscarilius graceful gloves +Lovakengj graceful gloves +Shayzien graceful gloves +Hosidius graceful gloves +Kourend graceful gloves +Dark graceful gloves +Trailblazer graceful gloves +Spooky graceful gloves +Arceuus graceful cape +Piscarilius graceful cape +Lovakengj graceful cape +Shayzien graceful cape +Hosidius graceful cape +Kourend graceful cape +Dark graceful cape +Trailblazer graceful cape +Spooky graceful cape +Shortbow (u) +Shortbow +Longbow (u) +Longbow +Oak shortbow (u) +Oak shortbow +Oak longbow (u) +Oak longbow +Willow shortbow (u) +Willow shortbow +Willow longbow (u) +Willow longbow +Maple shortbow (u) +Maple shortbow +Maple longbow (u) +Maple longbow +Yew shortbow (u) +Yew shortbow +Yew longbow (u) +Yew longbow +Magic shortbow (u) +Magic shortbow +Magic longbow (u) +Magic longbow +Elder bow(u) +Elder bow +Toxic blowpipe (empty) +Hellfire bow +Arrow shaft +Javelin shaft +Ogre arrow shaft +Battlestaff +Oak shield +Willow shield +Maple shield +Yew shield +Magic shield +Redwood shield +Headless arrow +Bronze arrow +Wolfbone arrowtips +Flighted ogre arrow +Ogre arrow +Iron arrow +Steel arrow +Mithril arrow +Adamant arrow +Rune arrow +Amethyst arrow +Dragon arrow +Hellfire arrow +Bronze bolts +Iron bolts +Silver bolts +Steel bolts +Mithril bolts +Adamant bolts +Runite bolts +Dragon bolts +Opal bolt tips +Jade bolt tips +Pearl bolt tips +Topaz bolt tips +Sapphire bolt tips +Emerald bolt tips +Ruby bolt tips +Diamond bolt tips +Dragonstone bolt tips +Onyx bolt tips +Opal dragon bolts +Jade dragon bolts +Pearl dragon bolts +Topaz dragon bolts +Sapphire dragon bolts +Emerald dragon bolts +Ruby dragon bolts +Diamond dragon bolts +Dragonstone dragon bolts +Onyx dragon bolts +Opal bolts +Pearl bolts +Topaz bolts +Sapphire bolts +Emerald bolts +Ruby bolts +Diamond bolts +Dragonstone bolts +Onyx bolts +Bronze javelin +Iron javelin +Steel javelin +Mithril javelin +Adamant javelin +Rune javelin +Amethyst javelin +Dragon javelin +Obsidian javelin +Bronze dart +Iron dart +Steel dart +Mithril dart +Adamant dart +Rune dart +Amethyst dart +Dragon dart +Wooden stock +Oak stock +Willow stock +Teak stock +Maple stock +Mahogany stock +Yew stock +Magic stock +Bronze crossbow (u) +Blurite crossbow (u) +Iron crossbow (u) +Steel crossbow (u) +Mithril crossbow (u) +Adamant crossbow (u) +Runite crossbow (u) +Dragon crossbow (u) +Bronze crossbow +Blurite crossbow +Iron crossbow +Steel crossbow +Mithril crossbow +Adamant crossbow +Rune crossbow +Dragon crossbow +Incomplete heavy ballista +Incomplete light ballista +Unstrung heavy ballista +Unstrung light ballista +Heavy ballista +Light ballista +Broad arrows +Broad bolts +Amethyst broad bolts +Mining gloves +Superior mining gloves +Expert mining gloves +Golden nugget +Unidentified minerals +Big swordfish +Big shark +Big bass +Farmer's shirt +Pharaoh's sceptre +Kyatt hat +Kyatt top +Kyatt legs +Spotted cape +Spottier cape +Gloves of silence +Small pouch +Medium pouch +Large pouch +Giant pouch +Abyssal pouch +Elder pouch +Crystal pickaxe +Crystal axe +Crystal harpoon +Superior bonecrusher +Superior dwarf multicannon +Superior inferno adze +Mecha mortar +Quick trap +Arcane harvester +Portable tanner +Drygore saw +Clue upgrader +Dwarven toolkit +Mecha rod +Master hammer and chisel +Abyssal amulet +RoboFlappy +Chincannon +Wisp-buster +Divine hand +Drygore axe +Moonlight mutator +Webshooter +Cogsworth +Cache portent +Graceful portent +Rogues portent +Dungeon portent +Lucky portent +Rebirth portent +Spiritual mining portent +Pacifist hunting portent +Pale energy +Boon of flickering energy +Flickering energy +Boon of bright energy +Bright energy +Boon of glowing energy +Glowing energy +Boon of sparkling energy +Sparkling energy +Boon of gleaming energy +Gleaming energy +Boon of vibrant energy +Vibrant energy +Boon of lustrous energy +Lustrous energy +Boon of elder energy +Elder energy +Boon of brilliant energy +Brilliant energy +Boon of radiant energy +Radiant energy +Boon of luminous energy +Luminous energy +Boon of incandescent energy +Incandescent energy +Boon of ancient energy +Ancient energy +Divine egg +Jar of memories +Doopy +Camo top +Camo bottoms +Camo helmet +Lederhosen top +Lederhosen shorts +Lederhosen hat +Zombie shirt +Zombie trousers +Zombie mask +Zombie gloves +Zombie boots +Mime mask +Mime top +Mime legs +Mime gloves +Mime boots +Frog token +Stale baguette +Beekeeper's hat +Beekeeper's top +Beekeeper's legs +Beekeeper's gloves +Beekeeper's boots +Shayzien gloves (1) +Shayzien boots (1) +Shayzien helm (1) +Shayzien greaves (1) +Shayzien platebody (1) +Shayzien gloves (2) +Shayzien boots (2) +Shayzien helm (2) +Shayzien greaves (2) +Shayzien platebody (2) +Shayzien gloves (3) +Shayzien boots (3) +Shayzien helm (3) +Shayzien greaves (3) +Shayzien platebody (3) +Shayzien gloves (4) +Shayzien boots (4) +Shayzien helm (4) +Shayzien greaves (4) +Shayzien platebody (4) +Shayzien gloves (5) +Shayzien boots (5) +Shayzien helm (5) +Shayzien greaves (5) +Shayzien body (5) +Dragon warhammer +Long bone +Curved bone +Ecumenical key +Dark totem base +Dark totem middle +Dark totem top +Chewed bones +Dragon full helm +Shield left half +Dragon metal slice +Dragon metal lump +Dragon spear +Amulet of eternal glory +Shaman mask +Evil chicken head +Evil chicken wings +Evil chicken legs +Evil chicken feet +Right skull half +Left skull half +Top of sceptre +Bottom of sceptre +Mossy key +Giant key +Xeric's talisman (inert) +Mask of ranul +Elven signet +Crystal grail +Enhanced crystal teleport seed +Dragonstone full helm +Dragonstone platebody +Dragonstone platelegs +Dragonstone gauntlets +Dragonstone boots +Ivy seed +Merfolk trident +Orange egg sac +Dark Temple key +Elite black knight sword +Elite black knight kiteshield +Elite black knight helm +Elite black knight platebody +Elite black knight platelegs +Karamja gloves 1 +Karamja gloves 2 +Karamja gloves 3 +Karamja gloves 4 +Ardougne cloak 1 +Ardougne cloak 2 +Ardougne cloak 3 +Ardougne cloak 4 +Falador shield 1 +Falador shield 2 +Falador shield 3 +Falador shield 4 +Fremennik sea boots 1 +Fremennik sea boots 2 +Fremennik sea boots 3 +Fremennik sea boots 4 +Kandarin headgear 1 +Kandarin headgear 2 +Kandarin headgear 3 +Kandarin headgear 4 +Desert amulet 1 +Desert amulet 2 +Desert amulet 3 +Desert amulet 4 +Explorer's ring 1 +Explorer's ring 2 +Explorer's ring 3 +Explorer's ring 4 +Morytania legs 1 +Morytania legs 2 +Morytania legs 3 +Morytania legs 4 +Varrock armour 1 +Varrock armour 2 +Varrock armour 3 +Varrock armour 4 +Wilderness sword 1 +Wilderness sword 2 +Wilderness sword 3 +Wilderness sword 4 +Western banner 1 +Western banner 2 +Western banner 3 +Western banner 4 +Rada's blessing 1 +Rada's blessing 2 +Rada's blessing 3 +Rada's blessing 4 +Goblin paint cannon +Green banner +Spinning plate +Brown toy horsey +White toy horsey +Black toy horsey +Grey toy horsey +Beach boxing gloves +Beach boxing gloves +Tiger toy +Lion toy +Snow leopard toy +Amur leopard toy +Holy handegg +Peaceful handegg +Chaotic handegg +Rainbow scarf +Diango's claws +Hornwood helm +Hand fan +Mask of balance +Druidic wreath +Disk of returning +Tradeable Mystery Box +Untradeable Mystery Box +Equippable mystery box +Pet Mystery Box +Mining hood +Mining cape(t) +Smithing hood +Smithing cape(t) +Woodcutting hood +Woodcut. cape(t) +Firemaking hood +Firemaking cape(t) +Fishing hood +Fishing cape(t) +Agility hood +Agility cape(t) +Cooking hood +Cooking cape(t) +Crafting hood +Crafting cape(t) +Prayer hood +Prayer cape(t) +Fletching hood +Fletching cape(t) +Runecraft hood +Runecraft cape(t) +Thieving hood +Thieving cape(t) +Farming hood +Farming cape(t) +Herblore hood +Herblore cape(t) +Hunter hood +Hunter cape(t) +Construct. hood +Construct. cape(t) +Magic hood +Magic cape(t) +Attack hood +Attack cape(t) +Strength hood +Strength cape(t) +Defence hood +Defence cape(t) +Hitpoints hood +Hitpoints cape(t) +Ranging hood +Ranging cape(t) +Slayer hood +Slayer cape(t) +Dungeoneering hood +Dungeoneering cape(t) +Divination hood +Divination cape(t) +Quest point hood +Quest point cape +Quest point cape (t) +Achievement diary hood +Achievement diary cape +Achievement diary cape (t) +Music hood +Music cape +Music cape(t) +Max hood +Max cape +Ardougne max hood +Ardougne max cape +Infernal max hood +Infernal max cape +Assembler max hood +Assembler max cape +Masori assembler max hood +Masori assembler max cape +Imbued guthix max hood +Imbued guthix max cape +Imbued saradomin max hood +Imbued saradomin max cape +Imbued zamorak max hood +Imbued zamorak max cape +Mythical max hood +Mythical max cape +Mining master cape +Smithing master cape +Woodcutting master cape +Firemaking master cape +Fishing master cape +Agility master cape +Cooking master cape +Crafting master cape +Prayer master cape +Fletching master cape +Runecraft master cape +Thieving master cape +Farming master cape +Herblore master cape +Hunter master cape +Construction master cape +Magic master cape +Attack master cape +Strength master cape +Defence master cape +Hitpoints master cape +Ranged master cape +Slayer master cape +Dungeoneering master cape +Invention master cape +Divination master cape +Master quest cape +Combatant's cape +Artisan's cape +Support cape +Gatherer's cape +Fire max hood +Fire max cape +Mythical cape +Imbued saradomin cape +Imbued guthix cape +Imbued zamorak cape +Saradomin cape +Guthix cape +Zamorak cape +Blue soul cape +Cape of legends +Icthlarin's hood (tier 5) +Helm of neitiznot +Anti-dragon shield +Goldsmith gauntlets +Cooking gauntlets +Magic secateurs +Barrows gloves +Dragon gloves +Rune gloves +Adamant gloves +Mithril gloves +Black gloves +Steel gloves +Iron gloves +Bronze gloves +Hardleather gloves +Doug +Zippy +Shelldon +Remy +Lil Lamb +Klik +Zak +Hammy +Takon +Obis +Peky +Wilvus +Ishi +Sandy +Steve +Baby duckling +Mr. E +Nexterminator +Brain lee +Balloon cat +Baby yaga house +Herbert +Baby impling jar +Young impling jar +Gourmet impling jar +Earth impling jar +Essence impling jar +Eclectic impling jar +Nature impling jar +Magpie impling jar +Ninja impling jar +Dragon impling jar +Lucky impling jar +Crystal impling jar +Chimpling jar +Infernal impling jar +Eternal impling jar +Mystery impling jar +Drygore rapier (blood) +Drygore rapier (ice) +Drygore rapier (shadow) +Drygore rapier (3a) +Offhand drygore rapier (blood) +Offhand drygore rapier (ice) +Offhand drygore rapier (shadow) +Offhand drygore rapier (3a) +Drygore mace (blood) +Drygore mace (ice) +Drygore mace (shadow) +Drygore mace (3a) +Offhand drygore mace (blood) +Offhand drygore mace (ice) +Offhand drygore mace (shadow) +Offhand drygore mace (3a) +Drygore longsword (blood) +Drygore longsword (ice) +Drygore longsword (shadow) +Drygore longsword (3a) +Offhand drygore longsword (blood) +Offhand drygore longsword (ice) +Offhand drygore longsword (shadow) +Offhand drygore longsword (3a) +Dwarven warhammer (blood) +Dwarven warhammer (ice) +Dwarven warhammer (shadow) +Dwarven warhammer (3a) +Dwarven warnana +Dwarven warhammer (volcanic) +Twisted bow (ice) +Twisted bow (shadow) +Twisted bow (blood) +Twisted bow (3a) +Twisted bownana +Zaryte bow (ice) +Zaryte bow (shadow) +Zaryte bow (blood) +Zaryte bow (3a) +Zaryte bownana +Hellfire bownana +Hellfire bow (ice) +Hellfire bow (Oceanic) +Vasa cloak (zamorak) +Vasa cloak (saradomin) +TzKal cape (Oceanic) +TzKal cape (Volcanic) +Gorajan warrior helmet (Primal) +Gorajan warrior helmet (Oceanic) +Gorajan warrior top (Primal) +Gorajan warrior top (Oceanic) +Gorajan warrior legs (Primal) +Gorajan warrior legs (Oceanic) +Gorajan warrior gloves (Primal) +Gorajan warrior gloves (Oceanic) +Gorajan warrior boots (Primal) +Gorajan warrior boots (Oceanic) +Gorajan occult helmet (Celestial) +Gorajan occult helmet (Oceanic) +Gorajan occult top (Celestial) +Gorajan occult top (Oceanic) +Gorajan occult legs (Celestial) +Gorajan occult legs (Oceanic) +Gorajan occult gloves (Celestial) +Gorajan occult gloves (Oceanic) +Gorajan occult boots (Celestial) +Gorajan occult boots (Oceanic) +Gorajan archer helmet (Sagittarian) +Gorajan archer helmet (Oceanic) +Gorajan archer top (Sagittarian) +Gorajan archer top (Oceanic) +Gorajan archer legs (Sagittarian) +Gorajan archer legs (Oceanic) +Gorajan archer gloves (Sagittarian) +Gorajan archer gloves (Oceanic) +Gorajan archer boots (Sagittarian) +Gorajan archer boots (Oceanic) +Dwarven full helm (Volcanic) +Dwarven platebody (Volcanic) +Dwarven platelegs (Volcanic) +Dwarven gloves (Volcanic) +Dwarven boots (Volcanic) +Infernal slayer helmet(i) (ice) +Swanky boots +Darkmeyer hood +Darkmeyer torso +Darkmeyer trousers +Darkmeyer boots +Gorilla mask +Thinker robes +Thinker trousers +Thinker gloves +Thinker boots +Prifddinian worker's robes +Prifddinian worker's trousers +Prifddinian worker's gloves +Prifddinian worker's boots +Prifddinian musician's robe top +Prifddinian musician's robe bottom +Prifddinian musician's gloves +Prifddinian musician's boots +Dervish head wrap (blue-gold) +Dervish hood (red) +Dervish robe (blue-gold) +Dervish robe (red) +Dervish trousers (blue-gold) +Dervish skirt (red) +Dervish shoes (blue-gold) +Dervish shoes (red) +Eastern knot +Eastern bun +Eastern robe (blue) +Eastern kimono (blue) +Eastern trousers (blue) +Eastern skirt (blue) +Eastern sandals (male) +Eastern sandals (blue, female) +Tribal ringlet (red, male) +Tribal ringlet (red, female) +Tribal shirt (red) +Tribal top (red) +Tribal trousers (red) +Tribal skirt (red) +Tribal shoes (red, male) +Samba headdress (blue, male) +Samba headdress (blue, female) +Samba top (blue, male) +Samba top (blue, female) +Samba loincloth (blue, male) +Samba loincloth (blue, female) +Samba sandals (blue, male) +Theatrical hat (blue) +Theatrical earrings (blue) +Theatrical tunic (blue, male) +Theatrical tunic (blue, female) +Theatrical trousers (blue) +Theatrical skirt (blue) +Theatrical shoes (blue, male) +Theatrical shoes (blue, female) +Pharaoh's nemes (green) +Pharaoh's bun (green) +Pharaoh's ankh (green) +Pharaoh's top (green) +Pharaoh's shendyt (green, male) +Pharaoh's shendyt (green, female) +Pharaoh's sandals (green, male) +Pharaoh's sandals (green, female) +Wushanko headdress (blue) +Wushanko hat (blue) +Wushanko top (blue) +Wushanko jacket (blue) +Wushanko skirt (blue) +Wushanko trousers (blue) +Wushanko shoes (blue, male) +Wushanko shoes (blue, female) +Silken turban (blue, male) +Silken turban (blue, female) +Silken top (blue, male) +Silken top (blue, female) +Silken trousers (blue) +Silken skirt (blue) +Silken boots (blue, male) +Silken boots (blue, female) +Colonist's hat (blue) +Colonist's bonnet (blue) +Colonist's coat (blue) +Colonist's dress top (blue) +Colonist's trousers (blue) +Colonist's skirt (blue) +Colonist's boots (blue) +Colonist's shoes (blue) +Feathered serpent headdress (blue, male) +Feathered serpent headdress (blue, female) +Feathered serpent body (blue, male) +Feathered serpent body (blue, female) +Feathered serpent skirt (blue, male) +Feathered serpent skirt (blue, female) +Feathered serpent boots (blue, female) +Highland war paint (blue, male) +Highland war paint (blue, female) +Highland shirt (blue) +Highland top (blue) +Highland kilt (blue, male) +Highland kilt (blue, female) +Highland boots (blue, male) +Highland boots (blue, female) +Musketeer's hat (blue, male) +Musketeer's hat (blue, female) +Musketeer's tabard (blue) +Musketeer's top (blue) +Musketeer's trousers (blue, male) +Musketeer's trousers (blue, female) +Musketeer's boots (blue, male) +Musketeer's boots (blue, female) +Elf-style wig (black, male) +Elf-style wig (black, female) +Elf-style coat (black) +Elf-style dress top (black) +Elf-style trousers (black) +Elf-style skirt (black) +Elf-style boots (black) +Elf-style shoes (black) +Werewolf mask (red, male) +Werewolf torso (red, male) +Werewolf legs (red, male) +Werewolf claws (red, male) +Werewolf paws (red, male) +Ikuchi orokami mask +Kodama orokami mask +Akkorokamui orokami mask +Karasu orokami mask +Akateko orokami mask +Nue orokami mask +Shinigami orokami mask +Oni orokami mask +Shaman's headdress +Shaman's leggings +Shaman's moccasins +Shaman's poncho +Shaman's hand wraps +Fang of Mohegan +Round glasses +Stylish glasses +Pyjama slippers +Pyjama top +Pyjama bottoms +Tuxedo jacket +Tuxedo trousers +Tuxedo shoes +Tuxedo gloves +Tuxedo cravat +Evening bolero +Evening dipped skirt +Evening gloves +Evening boots +Evening masquerade mask +Black afro +Brown afro +Burgundy afro +Dark blue afro +Dark brown afro +Dark green afro +Dark grey afro +Green afro +Indigo afro +Light blue afro +Light brown afro +Light grey afro +Military grey afro +Mint green afro +Orange afro +Peach afro +Pink afro +Purple afro +Red afro +Rainbow afro +Taupe afro +Turquoise afro +Vermilion afro +Violet afro +White afro +Yellow afro +Red top hat +Green top hat +Blue top hat +White top hat +Pink disco top +Pink disco legs +Pink disco gloves +Pink disco boots +Green disco top +Green disco legs +Green disco gloves +Green disco boots +Blue disco top +Blue disco legs +Blue disco gloves +Blue disco boots +Chicken head +Chicken wings +Chicken legs +Chicken feet +Scythe +Pumpkin +Red halloween mask +Blue halloween mask +Green halloween mask +Black h'ween mask +Skeleton mask +Skeleton shirt +Skeleton leggings +Skeleton gloves +Skeleton boots +Jack lantern mask +Yo-yo +Reindeer hat +Bunny ears +Easter egg +Wintumber tree +Santa hat +Bobble hat +Bobble scarf +Jester hat +Jester scarf +Tri-jester hat +Tri-jester scarf +Woolly hat +Woolly scarf +Red marionette +Green marionette +Blue marionette +Rubber chicken +Zombie head +Half full wine jug +Christmas cracker +War ship +Cow mask +Cow top +Cow trousers +Cow gloves +Cow shoes +Easter basket +Grim reaper hood +Santa mask +Santa jacket +Santa pantaloons +Santa gloves +Santa boots +Antisanta mask +Antisanta jacket +Antisanta pantaloons +Antisanta gloves +Antisanta boots +Bunny feet +Bunny top +Bunny legs +Bunny paws +Anti-panties +Gravedigger mask +Gravedigger top +Gravedigger leggings +Gravedigger gloves +Gravedigger boots +Black santa hat +Inverted santa hat +Gnome child hat +Cabbage cape +Cruciferous codex +Banshee mask +Banshee top +Banshee robe +Snow globe +Giant present +Sack of presents +4th birthday hat +Birthday balloons +Easter egg helm +Eggshell platebody +Eggshell platelegs +Jonas mask +Snow imp costume head +Snow imp costume body +Snow imp costume legs +Snow imp costume gloves +Snow imp costume feet +Snow imp costume tail +Star-face +Tree top +Tree skirt +Candy cane +Birthday cake +Giant easter egg +Bunnyman mask +Spooky hood +Spooky robe +Spooky skirt +Spooky gloves +Spooky boots +Spookier hood +Spookier robe +Spookier skirt +Spookier gloves +Spookier boots +Pumpkin lantern +Skeleton lantern +Blue gingerbread shield +Green gingerbread shield +Cat ears +Hell cat ears +Magic egg ball +Eek +Carrot sword +'24-carat' sword +Web cloak +Chocatrice cape +Warlock top +Warlock legs +Warlock cloak +Witch top +Witch skirt +Witch cloak +Ring of snow +Toy kite +Black partyhat +Pink partyhat +Rainbow partyhat +Red partyhat +Yellow partyhat +Blue partyhat +Purple partyhat +Green partyhat +White partyhat +Silver partyhat +Baby raven +Raven +Magic kitten +Magic cat +Zamorak egg +Baby zamorak hawk +Juvenile zamorak hawk +Zamorak hawk +Guthix egg +Baby guthix raptor +Juvenile guthix raptor +Guthix raptor +Saradomin egg +Baby saradomin owl +Juvenile saradomin owl +Saradomin owl +Grey and black kitten +Grey and black cat +White kitten +White cat +Brown kitten +Brown cat +Black kitten +Black cat +Grey and brown kitten +Grey and brown cat +Grey and blue kitten +Grey and blue cat +Godsword blade +Armadyl godsword +Bandos godsword +Saradomin godsword +Zamorak godsword +Ancient godsword +Infernal pickaxe +Malediction ward +Odium ward +Crystal key +Clue scroll (master) +Infernal axe +Infernal harpoon +Colossal pouch +Blessed spirit shield +Spectral spirit shield +Arcane spirit shield +Elysian spirit shield +Holy book +Book of balance +Unholy book +Book of law +Book of war +Book of darkness +Ava's accumulator +Ava's assembler +Dragon sq shield +Dragon kiteshield +Dragon platebody +Coconut milk +Coconut shell +Zamorakian hasta +Ultracompost +Tomatoes(5) +Apples(5) +Bananas(5) +Strawberries(5) +Oranges(5) +Potatoes(10) +Onions(10) +Cabbages(10) +Bucket of sand +Eldritch nightmare staff +Harmonised nightmare staff +Volatile nightmare staff +Zamorak's grapes +Toad's legs +Pegasian boots +Primordial boots +Eternal boots +Partyhat & specs +Ivandis flail +Blisterwood flail +Bottled dragonbreath +Ring of endurance +Fish sack barrel +Kodai wand +Salve amulet (e) +Salve amulet(ei) +Ring of wealth (i) +Strange hallowed tome +Daeyalt essence +Celestial signet +Eternal teleport crystal +Saturated heart +Trident of the swamp +Voidwaker +Accursed sceptre (u) +Ursine chainmace (u) +Webweaver bow (u) +Bone mace +Bone shortbow +Bone staff +Venator bow (uncharged) +Blessed dizana's quiver +Dizana's max cape +Dizana's max hood +Enhanced crystal key +Blade of saeldor (c) +Bow of faerdhinen (c) +Blade of saeldor (inactive) +Bow of faerdhinen (inactive) +Dragon defender (t) +Rune defender (t) +Dragon pickaxe (or) +Dragon sq shield (g) +Dragon full helm (g) +Dragon platebody (g) +Dragon kiteshield (g) +Dragon boots (g) +Dragon scimitar (or) +Dragon platelegs (g) +Dragon plateskirt (g) +Dragon chainbody (g) +Amulet of fury (or) +Zamorak godsword (or) +Bandos godsword (or) +Saradomin godsword (or) +Armadyl godsword (or) +Amulet of torture (or) +Necklace of anguish (or) +Tormented bracelet (or) +Occult necklace (or) +Rune scimitar (guthix) +Rune scimitar (saradomin) +Rune scimitar (zamorak) +Tzhaar-ket-om (t) +Berserker necklace (or) +Dark infinity hat +Dark infinity top +Dark infinity bottoms +Light infinity hat +Light infinity top +Light infinity bottoms +Polar camo top +Polar camo legs +Wood camo top +Wood camo legs +Jungle camo top +Jungle camo legs +Desert camo top +Desert camo legs +Larupia legs +Larupia top +Larupia hat +Graahk legs +Graahk top +Graahk headdress +Twisted ancestral hat +Twisted ancestral robe top +Twisted ancestral robe bottom +Puppadile +Tektiny +Vanguard +Vasa minirio +Vespina +Midnight +Baby mole-rat +Tzrek-zuk +Little parasite +Ziggy +Red +Great blue heron +Greatish guardian +Ferocious gloves +Uncut zenyte +Neitiznot faceguard +Arclight +Boots of brimstone +Devout boots +Bryophyta's staff +Abyssal tentacle +Brimstone ring +Guardian boots +Abyssal bludgeon +Black mask +Slayer ring (8) +Slayer ring (eternal) +Slayer helmet +Slayer helmet (i) +Black slayer helmet +Black slayer helmet (i) +Green slayer helmet +Green slayer helmet (i) +Red slayer helmet +Red slayer helmet (i) +Purple slayer helmet +Purple slayer helmet (i) +Turquoise slayer helmet +Turquoise slayer helmet (i) +Hydra slayer helmet +Hydra slayer helmet (i) +Twisted slayer helmet +Twisted slayer helmet (i) +Uncharged dragonfire shield +Uncharged dragonfire ward +Uncharged ancient wyvern shield +Dragonfire shield +Dragonfire ward +Ancient wyvern shield +Bracelet of ethereum +Viggora's chainmace +Ursine chainmace +Craw's bow +Webweaver bow +Thammaron's sceptre +Accursed sceptre +Bronze set (lg) +Bronze set (sk) +Bronze trimmed set (lg) +Bronze trimmed set (sk) +Bronze gold-trimmed set (lg) +Bronze gold-trimmed set (sk) +Iron set (lg) +Iron set (sk) +Iron trimmed set (lg) +Iron trimmed set (sk) +Iron gold-trimmed set (lg) +Iron gold-trimmed set (sk) +Steel set (lg) +Steel set (sk) +Steel trimmed set (lg) +Steel trimmed set (sk) +Steel gold-trimmed set (lg) +Steel gold-trimmed set (sk) +Black set (lg) +Black set (sk) +Black trimmed set (lg) +Black trimmed set (sk) +Black gold-trimmed set (lg) +Black gold-trimmed set (sk) +Mithril set (lg) +Mithril set (sk) +Mithril trimmed set (lg) +Mithril trimmed set (sk) +Mithril gold-trimmed set (lg) +Mithril gold-trimmed set (sk) +Adamant set (lg) +Adamant set (sk) +Adamant trimmed set (lg) +Adamant trimmed set (sk) +Adamant gold-trimmed set (lg) +Adamant gold-trimmed set (sk) +Rune armour set (lg) +Rune armour set (sk) +Rune trimmed set (lg) +Rune trimmed set (sk) +Rune gold-trimmed set (lg) +Rune gold-trimmed set (sk) +Gilded armour set (lg) +Gilded armour set (sk) +Guthix armour set (lg) +Guthix armour set (sk) +Saradomin armour set (lg) +Saradomin armour set (sk) +Zamorak armour set (lg) +Zamorak armour set (sk) +Ancient rune armour set (lg) +Ancient rune armour set (sk) +Armadyl rune armour set (lg) +Armadyl rune armour set (sk) +Bandos rune armour set (lg) +Bandos rune armour set (sk) +Dragon armour set (lg) +Dragon armour set (sk) +Verac's armour set +Dharok's armour set +Guthan's armour set +Ahrim's armour set +Torag's armour set +Karil's armour set +Inquisitor's armour set +Justiciar armour set +Obsidian armour set +Dragonstone armour set +Initiate harness m +Proselyte harness m +Proselyte harness f +Green dragonhide set +Blue dragonhide set +Red dragonhide set +Black dragonhide set +Gilded dragonhide set +Guthix dragonhide set +Saradomin dragonhide set +Zamorak dragonhide set +Ancient dragonhide set +Armadyl dragonhide set +Bandos dragonhide set +Mystic set (blue) +Mystic set (dark) +Mystic set (light) +Mystic set (dusk) +Ancestral robes set +Book of balance page set +Holy book page set +Unholy book page set +Book of darkness page set +Book of law page set +Book of war page set +Partyhat set +Halloween mask set +Combat potion set +Super potion set +Dwarf cannon set +Divine spirit shield +Heart crystal +Vasa cloak +Bryophyta's staff(i) +Ignis ring(i) +Breadcrumbs +Infernal bulwark +TzKal cape +Infernal slayer helmet(i) +Royal crossbow +Polypore staff +Crystal fishing rod +Void staff (u) +Void staff +Abyssal tome +Spellbound ring(i) +Seamonkey staff (t1) +Seamonkey staff (t2) +Seamonkey staff (t3) +Gorajan bonecrusher +Twisted relic hunter (t1) armour set +Twisted relic hunter (t2) armour set +Twisted relic hunter (t3) armour set +Trailblazer relic hunter (t1) armour set +Trailblazer relic hunter (t2) armour set +Trailblazer relic hunter (t3) armour set +Dagon'hai robes set +Ganodermic visor +Ganodermic poncho +Ganodermic leggings +Grifolic visor +Grifolic poncho +Grifolic leggings +Dragonbone boots +Dragonbone full helm +Dragonbone platebody +Dragonbone platelegs +Dragonbone gloves +Dragonbone mage boots +Dragonbone mage gloves +Dragonbone mage bottoms +Dragonbone mage hat +Dragonbone mage top +Royal dragon platebody +Frosty +Virtus wand +Virtus book +Pernix cowl +Pernix body +Pernix chaps +Pernix boots +Pernix gloves +Torva full helm +Torva platebody +Torva platelegs +Torva boots +Torva gloves +Virtus mask +Virtus robe top +Virtus robe legs +Virtus boots +Virtus gloves +Armadylean components +Bandosian components +Ancestral components +Masori components +Divine water +Crystal dust +Titan ballista +Piercing trident +Atlantean trident +Shark tooth necklace +Ring of piercing +Ring of piercing (i) +Tidal collector +Atomic energy +Sundial scimitar +Offhand spidergore rapier +Lumina +Clue scroll (elder) +Scythe of vitur +Sanguinesti staff +Holy sanguinesti staff +Holy sanguinesti staff (uncharged) +Holy scythe of vitur +Holy scythe of vitur (uncharged) +Sanguine scythe of vitur +Sanguine scythe of vitur (uncharged) +Avernic defender +Holy ghrazi rapier +Granite maul (or) +Granite maul (ornate handle) +Granite maul (or) (ornate handle) +Mystic steam staff (or) +Steam battlestaff (or) +Mystic lava staff (or) +Lava battlestaff (or) +Dragon pickaxe (upgraded) +Malediction ward (or) +Odium ward (or) +Dark bow (green) +Dark bow (blue) +Dark bow (yellow) +Dark bow (white) +Volcanic abyssal whip +Frozen abyssal whip +Staff of balance +Saradomin's blessed sword +Magic shortbow (i) +Looting bag +Rune pouch +Mystic air staff +Mystic water staff +Mystic earth staff +Mystic fire staff +Mystic dust staff +Mystic lava staff +Mystic mist staff +Mystic mud staff +Mystic smoke staff +Mystic steam staff +Zaryte crossbow +Golden prospector boots +Golden prospector helmet +Golden prospector jacket +Golden prospector legs +Dragon axe (or) +Dragon harpoon (or) +Infernal axe (or) +Infernal harpoon (or) +Infernal pickaxe (or) +Dragon pickaxe (or) (Trailblazer) +Abyssal tentacle (or) +Abyssal whip (or) +Book of balance (or) +Book of darkness (or) +Book of law (or) +Book of war (or) +Holy book (or) +Unholy book (or) +Rune crossbow (or) +Elite void robe (or) +Elite void top (or) +Void knight gloves (or) +Void knight top (or) +Void knight robe (or) +Void mage helm (or) +Void melee helm (or) +Void ranger helm (or) +Cannon barrels (or) +Cannon base (or) +Cannon furnace (or) +Cannon stand (or) +Mystic boots (or) +Mystic gloves (or) +Mystic hat (or) +Mystic robe bottom (or) +Mystic robe top (or) +Hat of the eye (red) +Robe top of the eye (red) +Robe bottoms of the eye (red) +Hat of the eye (green) +Robe top of the eye (green) +Robe bottoms of the eye (green) +Hat of the eye (blue) +Robe top of the eye (blue) +Robe bottoms of the eye (blue) +Runite igne claws +Dragon igne claws +Barrows igne claws +Volcanic igne claws +Drygore igne claws +Dwarven igne claws +Gorajan igne claws +Dragon igne armor +Barrows igne armor +Volcanic igne armor +Justiciar igne armor +Drygore igne armor +Dwarven igne armor +Gorajan igne armor +Demonic jibwings +Abyssal jibwings +3rd age jibwings +3rd age jibwings (e) +Demonic jibwings (e) +Abyssal jibwings (e) +Divine ring +Impling locator +Volcanic pickaxe +Offhand volcanic pickaxe +Moktang totem +Dragonstone full helm(u) +Dragonstone platebody(u) +Dragonstone platelegs(u) +Dragonstone boots(u) +Dragonstone gauntlets(u) +Bronze coffin +Steel coffin +Black coffin +Silver coffin +Gold coffin +Necromancer hood +Necromancer robe top +Necromancer robe bottom +Necromancer's air staff +Necromancer's earth staff +Necromancer's fire staff +Necromancer's lava staff +Necromancer's mud staff +Necromancer's steam staff +Necromancer's water staff +Skeletal battlestaff of air +Skeletal battlestaff of earth +Skeletal battlestaff of fire +Skeletal battlestaff of water +Skeletal lava battlestaff +Skeletal mud battlestaff +Skeletal steam battlestaff +Masori assembler +Osmumten's fang (or) +Elidinis' ward (f) +Elidinis' ward (or) +Divine rune pouch +Masori mask (f) +Masori body (f) +Masori chaps (f) +Armadylean plate +Keris partisan of breaching +Keris partisan of corruption +Keris partisan of the sun +Akkhito +Babi +Kephriti +Tumeken's damaged guardian +Elidinis' damaged guardian +Zebo +Bloodbark helm +Bloodbark body +Bloodbark legs +Bloodbark boots +Bloodbark gauntlets +Swampbark helm +Swampbark body +Swampbark legs +Swampbark boots +Swampbark gauntlets +Warrior icon +Bellator icon +Bellator ring +Berserker icon +Ultor icon +Ultor ring +Seers icon +Magus icon +Magus ring +Sanguine torva full helm +Sanguine torva platebody +Sanguine torva platelegs +Archer icon +Venator icon +Venator ring +Soulreaper axe +Tztok slayer helmet +Tztok slayer helmet (i) +Dragon hunter crossbow (t) +Dragon hunter crossbow (b) +Tzkal slayer helmet +Tzkal slayer helmet (i) +Vampyric slayer helmet +Vampyric slayer helmet (i) +Ghommal's avernic defender 5 +Ghommal's avernic defender 6 +Secateurs attachment +Nature offerings +Sturdy harness +Forestry basket +Clothes pouch +Bronze felling axe +Iron felling axe +Steel felling axe +Black felling axe +Mithril felling axe +Adamant felling axe +Rune felling axe +Dragon felling axe +Crystal felling axe +3rd age felling axe +Pheasant +Fox +Axe handle base +Axe handle +Axe of the high sungod (u) +Axe of the high sungod +Fuzzy dice +Karambinana +Warpriest of Zamorak set +Warpriest of Saradomin set +Warpriest of Bandos set +Warpriest of Armadyl set +Chickaxe +Leia +Decorative easter eggs +Cute magic egg +Fancy magic egg +Big mysterious egg +Fancy ancient egg +Sweet small egg +Mysterious magic egg +Big ancient egg +Fancy sweet egg +Eggy +Easter egg backpack +Floppy bunny ears +Egg coating +Chocolate pot +Raw turkey +Turkey drumstick +Burnt turkey +Cornucopia +Hoppy +Craig +Smokey +Flappy +Corgi +Snappy the Turtle +Cob +Gregoyle +Mini Pumpkinhead +Seer +Buzz +Kuro +Buggy +Casper +Mini mortimer +Rudolph +Cluckers +Human appendage +Sliced femur +Human blood +Human tooth +Candy teeth +Toffeet +Chocolified skull +Rotten sweets +Hairyfloss +Eyescream +Goblinfinger soup +Benny's brain brew +Roasted newt +Choc'rock +Haunted cloak +Pumpkinhead's headbringer +Haunted amulet +Haunted gloves +Haunted boots +Pumpkinhead's pumpkin head +Dirty hoe +Boo-balloon +Twinkly topper +Broomstick +Spooky box +Splooky fwizzle +Fool's ace +Pandora's box +Maledict codex +Maledict gloves +Maledict ring +Maledict amulet +Maledict boots +Maledict cape +Maledict legs +Maledict top +Maledict hat +Purple halloween mask +Cosmic dice +Spooky aura +Spooky sheet +Evil partyhat +Soul shield +Bag of tricks +Bat bat +Covenant of grimace +Smokey bbq sauce +Pumpkinhead pie +Roasted ham +Corn on the cob +Dougs' chocolate mud +Shepherd's pie +Flappy meal +Smokey painting +Yule log +Gr-egg-oyle special +Roast potatoes +Ratatouille +Fish n chips +Pretzel +Bacon +Prawns +Pavlova +Christmas tree hat +Christmas pudding +Christmas pudding amulet +Festive mistletoe +Christmas tree kite +Festive wrapping paper +Festive jumper (2021) +Frozen santa hat +Festive present +Honey +Honeycomb +Beehive +Christmas snowglobe +Festive jumper (2022) +Christmas cape +Christmas socks +Tinsel scarf +Frosted wreath +Edible yoyo +Reinbeer +Pork sausage +Pork crackling +Festive treats +Snowman plushie +Snowman top hat +Festive scarf +Ban hammer +The Interrogator +Acrylic set +Golden cape shard +Santa costume top (male) +Santa costume top (female) +Santa costume skirt +Santa costume pants +Santa costume gloves +Santa costume boots +Tinsel twirler +Grinch head +Grinch top +Grinch legs +Grinch feet +Grinch hands +Grinch santa hat +Drygore rapier (xmas) +Offhand drygore rapier (xmas) +Drygore mace (xmas) +Offhand drygore mace (xmas) +Drygore longsword (xmas) +Offhand drygore longsword (xmas) +Ho-ho hammer +Dwarven pumpkinsmasher +Mistleboe +Demonic piercer +Zaryte crossbow (xmas) +Vasa cloak (xmas) +Santa claws +TzKal cape (spooky) +Dwarven pickaxe (xmas) +Infernal slayer helmet(i) (xmas) +The Grim Reaper +Deathly collector +Golden cape +Golden shard +Golden partyhat +Supply crate (s1) +Supply crate key (s1) +OSB Jumper +BSO Jumper +Skipper's tie +Remy's chef hat +Paint box +Archon headdress +Archon tassets +Archon crest +Archon gloves +Archon boots +Coins +Chocolate bar +Baguette +Kebab +Spinach roll +Birthday crate (s2) +Birthday crate key (s2) +Cake partyhat +Rubber flappy +Shelldon shield +Koschei's toothpick +Imperial helmet +Imperial cuirass +Imperial legs +Imperial gloves +Imperial sabatons +Chocolate bomb +Cake +Chocolate cake +Peach +Beer +Spooky crate (s3) +Spooky crate key (s3) +Cob cap +Pumpkin peepers +Spooky sombrero +Demonic halloween mask +Spooky spider parasol +Spooky dye +Count Draynor torso +Count Draynor bottoms +Count Draynor cape +Count Draynor hands +Count Draynor shoes +Count Draynor fangs +Bones +Blood rune +Voodoo doll +Ghostweave +Festive crate (s4) +Festive crate key (s4) +Christmas cape (classic) +Christmas cape (rainbow) +Christmas cape (snowy tree) +Christmas cape (wintertodt blue) +Festive partyhat +Christmas jumper (green) +Christmas jumper (jolly red) +Christmas jumper (frosty) +Mistle-bow-tie +Frosty parasol +Frosty wings +Frosty cape +Frosty staff +Carrot +Easter crate (s5) +Easter crate key (s5) +Golden bunny ears +Cute bunny cape +Bunny plushie +Easter jumper +Easter-egg delight +Easter-egg salad +Easter tunic +Easter breeches +Easter shoes +Egg +Birthday crate (s6) +Birthday crate key (s6) +Ethereal partyhat +Swan hat +Swan scarf +BSO banner +Gambling skillcape +Monkey cape +BSO flowers +Dice plushie +Offhand dice plushie +Hoppy plushie +Plopper nose +Rose tinted glasses +Blabberbeak jumper +Ceremonial hat +Ceremonial cape +Ceremonial boots +Ceremonial legs +Ceremonial top +Raw plopper bacon +Blueberry birthday cake diff --git a/buyables.txt b/buyables.txt new file mode 100644 index 0000000000..c2fa8e6919 --- /dev/null +++ b/buyables.txt @@ -0,0 +1,660 @@ +Rope +Rope +Fishing Bait +Jug of Water +Feather +Shield right half +Dragon metal shard +Eye of newt +Vial of water +Vial +Bucket +Cup of hot water +Chocolate bar +Ball of wool +Empty bucket pack +Compost +Amylase pack +Dragon scimitar +Fishbowl pet +Potato with cheese +Torstol +Ogre bow +M'speak amulet +Salve amulet +Sandworms +Festive present +Granite Body +Raw shark +Bronze pickaxe +Iron pickaxe +Steel pickaxe +Mithril pickaxe +Adamant pickaxe +Rune pickaxe +Flower crown (bisexual) +Flower crown (asexual) +Flower crown (transgender) +Flower crown (pansexual) +Flower crown (non-binary) +Flower crown (genderqueer) +Flower crown (lesbian) +Flower crown (gay) +Flower crown +Mithril seeds +Brown apron +White apron +Pink skirt +Bull roarer +Rolling pin +Adamant halberd +Pirate bandana (white) +Stripy pirate shirt (white) +Pirate leggings (white) +Pirate bandana (blue) +Stripy pirate shirt (blue) +Pirate leggings (blue) +Pirate bandana (brown) +Stripy pirate shirt (brown) +Pirate leggings (brown) +Pirate bandana (red) +Stripy pirate shirt (red) +Pirate leggings (red) +Ghostly boots +Ghostly cloak +Ghostly gloves +Ghostly hood +Ghostly robe top +Ghostly robe bottom +Shadow sword +Menaphite purple outfit +Menaphite red outfit +Bone club +Bone spear +Bone dagger +Dorgeshuun crossbow +Crystal bow +Bronze axe +Iron axe +Steel axe +Broken coffin +Keris partisan +Mask of rebirth +Lockpick +Hallowed crystal shard +Hallowed token +Hallowed grapple +Hallowed focus +Hallowed symbol +Hallowed hammer +Hallowed sack +Hallowed ring +Dark dye +Dark acorn +Dark squirrel +Bolt of cloth +Limestone brick +Gold leaf +Marble block +Magic stone +Red dye +Skull +Fairy enchantment +Arceuus signet +Ancient signet +Lunar signet +Bucket of water +Butterfly jar +Magic box +Mystic hat +Mystic robe top +Mystic robe bottom +Mystic gloves +Mystic boots +Goldsmith gauntlets +Cooking gauntlets +Anti-dragon shield +Hardleather gloves +Bronze gloves +Iron gloves +Steel gloves +Black gloves +Mithril gloves +Adamant gloves +Rune gloves +Dragon gloves +Barrows gloves +Helm of neitiznot +Magic secateurs +Iban's staff +Barrelchest anchor +Mythical cape +Mind shield +Dwarven helmet +Amulet of accuracy +Cape of legends +Bearhead +Bonesack +Ram skull helm +Monkey +Rat pole +Silverlight +Darklight +Lunar Outfit +Moonclan Outfit +Jester Outfit +Ardougne Knight Outfit +Desert Outfit +Pirate boots +Vyrewatch outfit +Climbing boots +Warrior helm +Berserker helm +Archer helm +Farseer helm +Doctor's hat +Medical gown +Ring of charos +Nurse hat +Holy wrench +Initiate outfit +Proselyte outfit +Excalibur +Bomber jacket +Bomber cap +Pet rock +Dwarf multicannon +Cannon barrels +Cannon base +Cannon furnace +Cannon stand +Elemental shield +Royal seed pod +Ring of shadows +Beer +Vodka +Gin +Fremennik green cloak +Fremennik blue cloak +Fremennik brown cloak +Fremennik cyan cloak +Fremennik red cloak +Fremennik grey cloak +Fremennik yellow cloak +Fremennik teal cloak +Fremennik purple cloak +Fremennik pink cloak +Fremennik black cloak +Pink hat +Green hat +Blue hat +Cream hat +Turquoise hat +Pink robe top +Green robe top +Blue robe top +Cream robe top +Turquoise robe top +Pink robe bottoms +Green robe bottoms +Blue robe bottoms +Cream robe bottoms +Turquoise robe bottoms +Pink boots +Green boots +Blue boots +Cream boots +Turquoise boots +Grey gloves +Grey boots +Grey robe bottoms +Grey hat +Grey robe top +Red gloves +Red boots +Red robe bottoms +Red robe top +Red hat +Yellow gloves +Yellow boots +Yellow robe top +Yellow robe bottoms +Yellow hat +Teal gloves +Teal boots +Teal robe top +Teal robe bottoms +Teal hat +Purple gloves +Purple boots +Purple robe top +Purple robe bottoms +Purple hat +Red cape +Black cape +Blue cape +Yellow cape +Green cape +Red decorative full helm +Red decorative helm +Red decorative body +Red decorative legs +Red decorative skirt +Red decorative boots +Red decorative shield +Red decorative sword +White decorative full helm +White decorative helm +White decorative body +White decorative legs +White decorative skirt +White decorative boots +White decorative shield +White decorative sword +Gold decorative full helm +Gold decorative helm +Gold decorative body +Gold decorative legs +Gold decorative skirt +Gold decorative boots +Gold decorative shield +Gold decorative sword +Zamorak castlewars hood +Zamorak castlewars cloak +Saradomin castlewars hood +Saradomin castlewars cloak +Saradomin banner +Zamorak banner +Decorative magic hat +Decorative magic top +Decorative magic robe +Decorative ranged top +Decorative ranged legs +Decorative quiver +Saradomin halo +Zamorak halo +Guthix halo +Xeric's guard +Xeric's warrior +Xeric's sentinel +Xeric's general +Xeric's champion +Broad arrows +Broad arrowheads +Broad arrowhead pack +Unfinished broad bolts +Unfinished broad bolt pack +Enchanted gem +Leaf-bladed spear +Facemask +Earmuffs +Nose peg +Slayer's staff +Spiny helmet +Boots of stone +Antipoison(4) +Quest point cape +Quest point cape(t) +Master quest cape +Achievement diary cape +Achievement diary cape(t) +Music cape +Music cape(t) +Max cape +Support cape +Gatherer's cape +Combatant's cape +Artisan's cape +Prospector helmet +Prospector jacket +Prospector legs +Prospector boots +Coal bag +Gem bag +Mining gloves +Superior mining gloves +Expert mining gloves +Bag full of gems +Bag full of gems (minerals) +Air rune +Earth rune +Water rune +Fire rune +Body rune +Mind rune +Castle wars cape (beginner) +Castle wars cape (intermediate) +Castle wars cape (advanced) +Castle wars cape (expert) +Castle wars cape (legend) +Fishbowl helmet +Diving apparatus +Beginner's tackle box +Contest rod +Rainbow cape +Rune spikeshield +Rune berserker shield +Adamant spikeshield +Adamant berserker shield +Guthix engram +Fletcher's skilling outfit +Fletcher's gloves +Fletcher's boots +Fletcher's top +Fletcher's hat +Fletcher's legs +Ringmaster set +Clown set +Acrobat set +Supply crate key (s1) +Birthday crate key (s2) +Spooky crate key (s3) +Festive crate key (s4) +Easter crate key (s5) +Birthday crate key (s6) +Veteran cape (1 year) +Veteran cape (2 year) +Veteran cape (3 year) +Veteran cape (4 year) +Golden cape shard +Completionist cape +Completionist cape (t) +Wooden spoon +Prince outfit +Princess outfit +Frog mask +Sinhaza shroud tier 1 +Sinhaza shroud tier 2 +Sinhaza shroud tier 3 +Sinhaza shroud tier 4 +Sinhaza shroud tier 5 +Karamja gloves 1 +Karamja gloves 2 +Karamja gloves 3 +Karamja gloves 4 +Ardougne cloak 1 +Ardougne cloak 2 +Ardougne cloak 3 +Ardougne cloak 4 +Falador shield 1 +Falador shield 2 +Falador shield 3 +Falador shield 4 +Fremennik sea boots 1 +Fremennik sea boots 2 +Fremennik sea boots 3 +Fremennik sea boots 4 +Kandarin headgear 1 +Kandarin headgear 2 +Kandarin headgear 3 +Kandarin headgear 4 +Desert amulet 1 +Desert amulet 2 +Desert amulet 3 +Desert amulet 4 +Explorer's ring 1 +Explorer's ring 2 +Explorer's ring 3 +Explorer's ring 4 +Morytania legs 1 +Morytania legs 2 +Morytania legs 3 +Morytania legs 4 +Varrock armour 1 +Varrock armour 2 +Varrock armour 3 +Varrock armour 4 +Wilderness sword 1 +Wilderness sword 2 +Wilderness sword 3 +Wilderness sword 4 +Western banner 1 +Western banner 2 +Western banner 3 +Western banner 4 +Rada's blessing 1 +Rada's blessing 2 +Rada's blessing 3 +Rada's blessing 4 +Reclaim Holy book +Reclaim Unholy book +Reclaim Book of law +Reclaim Book of balance +Reclaim Book of war +Reclaim Book of darkness +Reclaim Guthix cape +Reclaim Saradomin cape +Reclaim Zamorak cape +Mining cape +Mining master cape +Smithing cape +Smithing master cape +Woodcutting cape +Woodcutting master cape +Firemaking cape +Firemaking master cape +Agility cape +Agility master cape +Fishing cape +Fishing master cape +Runecraft cape +Runecraft master cape +Cooking cape +Cooking master cape +Crafting cape +Crafting master cape +Prayer cape +Prayer master cape +Fletching cape +Fletching master cape +Thieving cape +Thieving master cape +Farming cape +Farming master cape +Herblore cape +Herblore master cape +Hunter cape +Hunter master cape +Construction cape +Construction master cape +Magic cape +Magic master cape +Attack cape +Attack master cape +Strength cape +Strength master cape +Defence cape +Defence master cape +Ranged cape +Ranged master cape +Hitpoints cape +Hitpoints master cape +Slayer cape +Slayer master cape +Dungeoneering cape +Dungeoneering master cape +Invention cape +Invention master cape +Divination cape +Divination master cape +Angler hat +Angler top +Angler waders +Angler boots +Pearl fishing rod +Pearl fly fishing rod +Pearl barbarian rod +Fish sack +Blue naval shirt +Blue tricorn hat +Blue navy slacks +Green naval shirt +Green tricorn hat +Green navy slacks +Red naval shirt +Red tricorn hat +Red navy slacks +Brown naval shirt +Brown tricorn hat +Brown navy slacks +Black naval shirt +Black tricorn hat +Black navy slacks +Golden naval shirt +Golden tricorn hat +Golden navy slacks +Grey naval shirt +Grey tricorn hat +Grey navy slacks +Purple naval shirt +Purple tricorn hat +Purple navy slacks +Cutthroat flag +Gilded smile flag +Bronze fist flag +Lucky shot flag +Treasure flag +Phasmatys flag +Jolly roger cape +The stuff +Red rum (trouble brewing) +Blue rum (trouble brewing) +Banana enchantment scroll +Monkey dye +Monkey crate +Gorilla rumble greegree +Beginner rumble greegree +Intermediate rumble greegree +Ninja rumble greegree +Expert ninja rumble greegree +Elder rumble greegree +Ironman helm +Ironman platebody +Ironman platelegs +Celestial ring (uncharged) +Star fragment +Bag full of gems (Stardust) +Soft clay pack +Hat of the eye +Robe top of the eye +Robe bottoms of the eye +Boots of the eye +Ring of the elements +Guardian's eye +Air talisman +Water talisman +Earth talisman +Fire talisman +Mind talisman +Body talisman +Chaos talisman +Cosmic talisman +Nature talisman +Law talisman +Death talisman +Blood talisman +Abyssal lantern +Icthlarin's shroud (tier 1) +Icthlarin's shroud (tier 2) +Icthlarin's shroud (tier 3) +Icthlarin's shroud (tier 4) +Icthlarin's shroud (tier 5) +Icthlarin's hood (tier 5) +Unidentified small fossil +Unidentified medium fossil +Unidentified large fossil +Merfolk trident +Seaweed spore +Bowl of fish +Oceanic shroud (tier 1) +Oceanic shroud (tier 2) +Oceanic shroud (tier 3) +Oceanic shroud (tier 4) +Oceanic shroud (tier 5) +Forestry kit +Secateurs blade +Ritual mulch +Log brace +Clothes pouch blueprint +Cape pouch +Log basket +Felling axe handle +Twitcher's gloves +Funky shaped log +Sawmill voucher +Lumberjack boots +Lumberjack hat +Lumberjack legs +Lumberjack top +Forestry boots +Forestry hat +Forestry legs +Forestry top +Chompy bird hat (ogre bowman) +Chompy bird hat (bowman) +Chompy bird hat (ogre yeoman) +Chompy bird hat (yeoman) +Chompy bird hat (ogre marksman) +Chompy bird hat (marksman) +Chompy bird hat (ogre woodsman) +Chompy bird hat (woodsman) +Chompy bird hat (ogre forester) +Chompy bird hat (forester) +Chompy bird hat (ogre bowmaster) +Chompy bird hat (bowmaster) +Chompy bird hat (ogre expert) +Chompy bird hat (expert) +Chompy bird hat (ogre dragon archer) +Chompy bird hat (dragon archer) +Chompy bird hat (expert ogre dragon archer) +Chompy bird hat (expert dragon archer) +Team-1 cape +Team-2 cape +Team-3 cape +Team-4 cape +Team-5 cape +Team-6 cape +Team-7 cape +Team-8 cape +Team-9 cape +Team-10 cape +Team-11 cape +Team-12 cape +Team-13 cape +Team-14 cape +Team-15 cape +Team-16 cape +Team-17 cape +Team-18 cape +Team-19 cape +Team-20 cape +Team-21 cape +Team-22 cape +Team-23 cape +Team-24 cape +Team-25 cape +Team-26 cape +Team-27 cape +Team-28 cape +Team-29 cape +Team-30 cape +Team-31 cape +Team-32 cape +Team-33 cape +Team-34 cape +Team-35 cape +Team-36 cape +Team-37 cape +Team-38 cape +Team-39 cape +Team-40 cape +Team-41 cape +Team-42 cape +Team-43 cape +Team-44 cape +Team-45 cape +Team-46 cape +Team-47 cape +Team-48 cape +Team-49 cape +Team-50 cape diff --git a/commands.txt b/commands.txt new file mode 100644 index 0000000000..ad5a2103c4 --- /dev/null +++ b/commands.txt @@ -0,0 +1,518 @@ +------ bs------ +bs search +bs format + + +------ build------ +build name +build quantity + + +------ buy------ +buy name +buy quantity + + +------ ca------ +ca view +ca claim + + +------ chop------ +chop name +chop quantity +chop powerchop +chop forestry_events +chop twitchers_gloves + + +------ cook------ +cook name +cook quantity + + +------ clue------ +clue tier +clue quantity + + +------ config------ +config server +config server channel +config server pet_messages +config server command +config user +config user toggle +config user combat_options +config user bank_sort +config user favorite_alchs +config user favorite_bh_seeds +config user favorite_food +config user favorite_items +config user slayer +config user toggle_invention + + +------ m------ + + +------ gp------ + + +------ craft------ +craft name +craft quantity + + +------ fish------ +fish name +fish quantity + + +------ farming------ +farming check_patches +farming plant +farming auto_farm +farming auto_farm_filter +farming default_compost +farming always_pay +farming harvest +farming tithe_farm +farming compost_bin +farming contract + + +------ drop------ +drop items +drop filter +drop search + + +------ create------ +create item +create quantity +create showall + + +------ activities------ +activities plank_make +activities chompy_hunt +activities champions_challenge +activities warriors_guild +activities camdozaal +activities collect +activities quest +activities decant +activities charge +activities fight_caves +activities inferno +activities birdhouses +activities aerial_fishing +activities enchant +activities bury +activities scatter +activities puro_puro +activities alch +activities cast +activities underwater +activities underwater agility_thieving +activities underwater drift_net_fishing +activities other + + +------ data------ +data name + + +------ fletch------ +fletch name +fletch quantity + + +------ gear------ +gear equip +gear unequip +gear stats +gear pet +gear view +gear swap +gear best_in_slot + + +------ hunt------ +hunt name +hunt quantity +hunt hunter_potion +hunt stamina_potions + + +------ k------ +k name +k quantity +k method +k wilderness + + +------ laps------ +laps name +laps quantity +laps alch + + +------ light------ +light name +light quantity + + +------ mine------ +mine name +mine quantity +mine powermine + + +------ minigames------ +minigames barb_assault +minigames barb_assault start +minigames barb_assault buy +minigames barb_assault level +minigames barb_assault gamble +minigames barb_assault stats +minigames castle_wars +minigames castle_wars stats +minigames castle_wars start +minigames lms +minigames lms stats +minigames lms start +minigames lms buy +minigames lms simulate +minigames pest_control +minigames pest_control stats +minigames pest_control start +minigames pest_control xp +minigames pest_control buy +minigames fishing_trawler +minigames fishing_trawler start +minigames mage_arena +minigames mage_arena start +minigames mage_arena_2 +minigames mage_arena_2 start +minigames gnome_restaurant +minigames gnome_restaurant start +minigames temple_trek +minigames temple_trek start +minigames temple_trek buy +minigames sepulchre +minigames sepulchre start +minigames gauntlet +minigames gauntlet start +minigames mage_training_arena +minigames mage_training_arena start +minigames mage_training_arena points +minigames mage_training_arena buy +minigames mahogany_homes +minigames mahogany_homes start +minigames mahogany_homes buy +minigames tears_of_guthix +minigames tears_of_guthix start +minigames pyramid_plunder +minigames pyramid_plunder start +minigames rogues_den +minigames rogues_den start +minigames soul_wars +minigames soul_wars start +minigames soul_wars tokens +minigames soul_wars buy +minigames soul_wars imbue +minigames volcanic_mine +minigames volcanic_mine start +minigames volcanic_mine buy +minigames volcanic_mine stats +minigames agility_arena +minigames agility_arena start +minigames agility_arena buy +minigames agility_arena recolor +minigames agility_arena xp +minigames trouble_brewing +minigames trouble_brewing start +minigames giants_foundry +minigames giants_foundry start +minigames giants_foundry buy +minigames giants_foundry stats +minigames gotr +minigames gotr start +minigames nmz +minigames nmz start +minigames nmz buy +minigames nmz stats +minigames nmz imbue +minigames shades_of_morton +minigames shades_of_morton start + + +------ minion------ +minion buy +minion status +minion stats +minion achievementdiary +minion cancel +minion set_icon +minion set_name +minion level +minion kc +minion charge +minion daily +minion train +minion pat +minion blowpipe +minion info +minion peak +minion feed_hammy +minion mastery + + +------ sell------ +sell items +sell filter +sell search + + +------ sacrifice------ +sacrifice items +sacrifice filter +sacrifice search + + +------ runecraft------ +runecraft rune +runecraft quantity +runecraft usestams +runecraft daeyalt_essence + + +------ raid------ +raid cox +raid cox start +raid cox stats +raid tob +raid tob start +raid tob stats +raid tob check +raid toa +raid toa start +raid toa help +raid doa +raid doa start +raid doa help + + +------ poh------ +poh view +poh wallkit +poh build +poh destroy +poh mount_item +poh items + + +------ open------ +open name +open quantity + + +------ offer------ +offer name +offer quantity + + +------ mix------ +mix name +mix quantity +mix wesley +mix zahur + + +------ smelt------ +smelt name +smelt quantity +smelt blast_furnace + + +------ slayer------ +slayer autoslay +slayer new_task +slayer manage +slayer rewards +slayer rewards unlock +slayer rewards unblock +slayer rewards buy +slayer rewards my_unlocks +slayer rewards show_all_rewards +slayer rewards disable +slayer status + + +------ smith------ +smith name +smith quantity + + +------ steal------ +steal name +steal quantity + + +------ tools------ +tools patron +tools patron sacrificed_bank +tools patron cl_bank +tools patron minion_stats +tools patron stats +tools user +tools user checkmasses +tools user fixbank +tools stash_units +tools stash_units view +tools stash_units build_all +tools stash_units fill_all +tools stash_units unfill + + +------ tokkulshop------ +tokkulshop buy +tokkulshop sell + + +------ use------ +use item +use secondary_item + + +------ bank------ +bank page +bank item +bank items +bank format +bank search +bank filter +bank sort +bank flag +bank flag_extra + + +------ bsominigames------ +bsominigames baxtorian_bathhouses +bsominigames baxtorian_bathhouses help +bsominigames baxtorian_bathhouses start +bsominigames monkey_rumble +bsominigames monkey_rumble start +bsominigames monkey_rumble stats +bsominigames ourania_delivery_service +bsominigames ourania_delivery_service start +bsominigames ourania_delivery_service stats +bsominigames ourania_delivery_service buy +bsominigames fishing_contest +bsominigames fishing_contest fish +bsominigames fishing_contest stats_info +bsominigames fist_of_guthix +bsominigames fist_of_guthix start +bsominigames stealing_creation +bsominigames stealing_creation start +bsominigames tinkering_workshop +bsominigames tinkering_workshop start +bsominigames balthazars_big_bonanza +bsominigames balthazars_big_bonanza start +bsominigames divine_dominion +bsominigames divine_dominion check +bsominigames divine_dominion sacrifice_god_item +bsominigames guthixian_cache +bsominigames guthixian_cache join +bsominigames guthixian_cache stats +bsominigames turaels_trials +bsominigames turaels_trials start + + +------ completion------ +completion view_all_tasks +completion check + + +------ dg------ +dg start +dg stats +dg buy + + +------ divination------ +divination harvest_memories +divination portent +divination charge_portent +divination toggle_portent + + +------ invention------ +invention disassemble +invention research +invention invent +invention tools +invention details +invention materials +invention group + + +------ kibble------ +kibble kibble +kibble quantity + + +------ nursery------ +nursery build +nursery fuel +nursery add_egg +nursery check + + +------ paint------ +paint paint +paint item + + +------ tames------ +tames status +tames list +tames set_name +tames cancel +tames merge +tames feed +tames kill +tames collect +tames select +tames view +tames equip +tames unequip +tames cast +tames activity +tames clue +tames set_custom_image + + +------ leagues------ +leagues check +leagues view_task +leagues claim +leagues view_all_tasks + + +------ randomizer------ +randomizer info +randomizer unlock_item_mapping +randomizer reset + + +------ gamble------ + + +------ relic------ +relic pick +relic view + + diff --git a/creatables.txt b/creatables.txt new file mode 100644 index 0000000000..985c6a6e17 --- /dev/null +++ b/creatables.txt @@ -0,0 +1,1438 @@ +Godsword blade +Armadyl godsword +Bandos godsword +Saradomin godsword +Zamorak godsword +Ancient godsword +Infernal pickaxe +Malediction ward +Odium ward +Crystal key +Master clue +Infernal axe +Infernal harpoon +Hell cat ears +Small pouch +Medium pouch +Large pouch +Giant pouch +Colossal pouch +Blessed spirit shield +Spectral spirit shield +Arcane spirit shield +Elysian spirit shield +Holy book +Book of balance +Unholy book +Book of law +Book of war +Book of darkness +Ava's accumulator +Ava's assembler +Dragon sq shield +Dragon kiteshield +Dragon platebody +Coconut milk +Zamorakian hasta +Ultracompost +Tomatoes(5) +Tomato +Apples(5) +Cooking apple +Bananas(5) +Banana +Strawberries(5) +Strawberry +Oranges(5) +Orange +Potatoes(10) +Potato +Onions(10) +Onion +Cabbages(10) +Cabbage +Bucket of sand (1kg) +Bucket of sand (2kg) +Bucket of sand (5kg) +Bucket of sand (10kg) +Eldritch nightmare staff +Harmonised nightmare staff +Volatile nightmare staff +Zamorak's grapes +Toad's legs +Pegasian boots +Primordial boots +Eternal boots +Partyhat & specs +Ivandis Flail +Blisterwood Flail +Spirit angler headband +Spirit angler top +Spirit angler waders +Spirit angler boots +Bottled dragonbreath +Ring of endurance +Fish sack barrel +Kodai wand +Salve amulet (e) +Salve amulet(ei) +Ring of wealth (i) +Strange hallowed tome +Frozen key +Ecumenical key +Daeyalt essence +Celestial signet +Eternal teleport crystal +Saturated heart +Trident of the swamp +Golden cape +Voidwaker +Accursed sceptre (u) +Ursine chainmace (u) +Webweaver bow (u) +Bone mace +Bone shortbow +Bone staff +Venator bow (uncharged) +Blessed dizana's quiver +Dizana's max cape +Revert tanzanite fang +Revert toxic blowpipe (empty) +Revert magic fang +Revert serpentine visage +Revert serpentine helm (uncharged) +Revert ancient icon +Revert venator shard +Revert volatile nightmare staff +Revert harmonised nightmare staff +Revert eldritch nightmare staff +Revert red decorative full helm +Revert red decorative helm +Revert red decorative body +Revert red decorative legs +Revert red decorative skirt +Revert red decorative boots +Revert red decorative shield +Revert red decorative sword +Revert white decorative full helm +Revert white decorative helm +Revert white decorative body +Revert white decorative legs +Revert white decorative skirt +Revert white decorative boots +Revert white decorative shield +Revert white decorative sword +Revert gold decorative full helm +Revert gold decorative helm +Revert gold decorative body +Revert gold decorative legs +Revert gold decorative skirt +Revert gold decorative boots +Revert gold decorative shield +Revert gold decorative sword +Revert zamorak castlewars hood +Revert zamorak castlewars cloak +Revert saradomin castlewars hood +Revert saradomin castlewars cloak +Revert saradomin banner +Revert zamorak banner +Revert decorative magic hat +Revert decorative magic top +Revert decorative magic robe +Revert decorative ranged top +Revert decorative ranged legs +Revert decorative quiver +Revert saradomin halo +Revert zamorak halo +Revert guthix halo +Revert partyhat & specs +Revert zamorakian hasta +Revert armadyl godsword +Revert bandos godsword +Revert saradomin godsword +Revert zamorak godsword +Revert Fish sack barrel +Revert midnight +Revert baby mole-rat +Revert tzrek-zuk +Revert little parasite +Revert ziggy +Revert red +Revert great blue heron +Revert greatish guardian +Revert xeric's talisman (inert) +Revert Dizana's quiver (uncharged) +Crystal pickaxe +Crystal harpoon +Crystal axe +Enhanced crystal key +Blade of saeldor (c) +Revert blade of saeldor (c) +Bow of faerdhinen (c) +Revert bow of faerdhinen (c) +Blade of saeldor (inactive) +Revert blade of saeldor (inactive) +Bow of faerdhinen (inactive) +Revert bow of faerdhinen (inactive) +Crystal halberd +Crystal bow +Crystal helm +Crystal legs +Crystal body +Dragon defender (t) +Revert dragon defender (t) +Rune defender (t) +Revert rune defender (t) +Dragon pickaxe (or) +Revert dragon pickaxe (or) +Dragon sq shield (g) +Revert dragon sq shield (g) +Dragon full helm (g) +Revert dragon full helm (g) +Dragon platebody (g) +Revert dragon platebody (g) +Dragon kiteshield (g) +Revert dragon kiteshield (g) +Dragon boots (g) +Revert dragon boots (g) +Dragon scimitar (or) +Revert dragon scimitar (or) +Dragon platelegs (g) +Revert dragon platelegs (g) +Dragon plateskirt (g) +Revert dragon plateskirt (g) +Dragon chainbody (g) +Revert dragon chainbody (g) +Amulet of fury (or) +Revert amulet of fury (or) +Zamorak godsword (or) +Revert zamorak godsword (or) +Bandos godsword (or) +Revert bandos godsword (or) +Saradomin godsword (or) +Revert saradomin godsword (or) +Armadyl godsword (or) +Revert armadyl godsword (or) +Amulet of torture (or) +Revert amulet of torture (or) +Necklace of anguish (or) +Revert necklace of anguish (or) +Tormented bracelet (or) +Revert tormented bracelet (or) +Occult necklace (or) +Revert occult necklace (or) +Rune scimitar (guthix) +Revert rune scimitar (guthix) +Rune scimitar (saradomin) +Revert rune scimitar (saradomin) +Rune scimitar (zamorak) +Revert rune scimitar (zamorak) +Tzhaar-ket-om (t) +Revert tzhaar-ket-om (t) +Berserker necklace (or) +Revert berserker necklace (or) +Dark infinity hat +Revert dark infinity hat +Dark infinity top +Revert dark infinity top +Dark infinity bottoms +Revert dark infinity bottoms +Light infinity hat +Revert light infinity hat +Light infinity top +Revert light infinity top +Light infinity bottoms +Revert light infinity bottoms +Polar camouflage gear +Woodland camouflage gear +Jungle camouflage gear +Desert camouflage gear +Larupia hunter gear +Graahk hunter gear +Kyatt hunter gear +Spotted cape +Spottier cape +Gloves of silence +Twisted ancestral hat +Twisted ancestral robe top +Twisted ancestral robe bottom +Revert twisted ancestral robe bottom +Revert twisted ancestral robe top +Revert twisted ancestral hat +Puppadile +Tektiny +Vanguard +Vasa minirio +Vespina +Lil' maiden +Lil' bloat +Lil' nylo +Lil' sot +Lil' xarp +Midnight +Baby mole-rat +Tzrek-zuk +Little parasite +Ziggy +Red +Great blue heron +Greatish guardian +Dark totem +Dragon hunter lance +Ferocious gloves +Revert ferocious gloves +Uncut zenyte +Neitiznot faceguard +Revert neitiznot faceguard +Arclight +Boots of brimstone +Devout boots +Uncharged toxic trident +Bryophyta's staff +Toxic staff (uncharged) +Abyssal tentacle +Brimstone ring +Guardian boots +Abyssal bludgeon +Uncharged black mask +Slayer ring (8) +Slayer ring (eternal) +Slayer helmet +Slayer helmet (i) +Revert slayer helmet +Revert slayer helmet (i) +Black slayer helmet +Black slayer helmet (i) +Revert black slayer helmet +Revert black slayer helmet (i) +Green slayer helmet +Green slayer helmet (i) +Revert green slayer helmet +Revert green slayer helmet (i) +Red slayer helmet +Red slayer helmet (i) +Revert red slayer helmet +Revert red slayer helmet (i) +Purple slayer helmet +Purple slayer helmet (i) +Revert purple slayer helmet +Revert purple slayer helmet (i) +Turquoise slayer helmet +Turquoise slayer helmet (i) +Revert turquoise slayer helmet +Revert turquoise slayer helmet (i) +Hydra slayer helmet +Hydra slayer helmet (i) +Revert hydra slayer helmet +Revert hydra slayer helmet (i) +Twisted slayer helmet +Twisted slayer helmet (i) +Revert twisted slayer helmet +Revert twisted slayer helmet (i) +Ardougne max cape +Infernal max cape +Assembler max cape +Imbued guthix max cape +Imbued saradomin max cape +Imbued zamorak max cape +Mythical max cape +Fire max cape +Masori assembler max cape +Uncharged dragonfire shield +Uncharged dragonfire ward +Uncharged ancient wyvern shield +Dragonfire shield +Dragonfire ward +Ancient wyvern shield +Bracelet of ethereum +Revenant ether +Viggora's chainmace +Revert viggora's chainmace +Ursine chainmace +Revert ursine chainmace +Revert viggora's chainmace (u) +Craw's bow +Revert craw's bow +Webweaver bow +Revert webweaver bow +Revert craw's bow (u) +Thammaron's sceptre +Revert thammaron's sceptre +Accursed sceptre +Revert accursed sceptre +Revert thammaron's sceptre (u) +Unpack bronze set (lg) +Bronze set (lg) +Unpack bronze set (sk) +Bronze set (sk) +Unpack bronze trimmed set (lg) +Bronze trimmed set (lg) +Unpack bronze trimmed set (sk) +Bronze trimmed set (sk) +Unpack bronze gold-trimmed set (lg) +Bronze gold-trimmed set (lg) +Unpack bronze gold-trimmed set (sk) +Bronze gold-trimmed set (sk) +Unpack iron set (lg) +Iron set (lg) +Unpack iron set (sk) +Iron set (sk) +Unpack iron trimmed set (lg) +Iron trimmed set (lg) +Unpack iron trimmed set (sk) +Iron trimmed set (sk) +Unpack iron gold-trimmed set (lg) +Iron gold-trimmed set (lg) +Unpack iron gold-trimmed set (sk) +Iron gold-trimmed set (sk) +Unpack steel set (lg) +Steel set (lg) +Unpack steel set (sk) +Steel set (sk) +Unpack steel trimmed set (lg) +Steel trimmed set (lg) +Unpack steel trimmed set (sk) +Steel trimmed set (sk) +Unpack steel gold-trimmed set (lg) +Steel gold-trimmed set (lg) +Unpack steel gold-trimmed set (sk) +Steel gold-trimmed set (sk) +Unpack black set (lg) +Black set (lg) +Unpack black set (sk) +Black set (sk) +Unpack black trimmed set (lg) +Black trimmed set (lg) +Unpack black trimmed set (sk) +Black trimmed set (sk) +Unpack black gold-trimmed set (lg) +Black gold-trimmed set (lg) +Unpack black gold-trimmed set (sk) +Black gold-trimmed set (sk) +Unpack mithril set (lg) +Mithril set (lg) +Unpack mithril set (sk) +Mithril set (sk) +Unpack mithril trimmed set (lg) +Mithril trimmed set (lg) +Unpack mithril trimmed set (sk) +Mithril trimmed set (sk) +Unpack mithril gold-trimmed set (lg) +Mithril gold-trimmed set (lg) +Unpack mithril gold-trimmed set (sk) +Mithril gold-trimmed set (sk) +Unpack adamant set (lg) +Adamant set (lg) +Unpack adamant set (sk) +Adamant set (sk) +Unpack adamant trimmed set (lg) +Adamant trimmed set (lg) +Unpack adamant trimmed set (sk) +Adamant trimmed set (sk) +Unpack adamant gold-trimmed set (lg) +Adamant gold-trimmed set (lg) +Unpack adamant gold-trimmed set (sk) +Adamant gold-trimmed set (sk) +Unpack rune armour set (lg) +Rune armour set (lg) +Unpack rune armour set (sk) +Rune armour set (sk) +Unpack rune trimmed set (lg) +Rune trimmed set (lg) +Unpack rune trimmed set (sk) +Rune trimmed set (sk) +Unpack rune gold-trimmed set (lg) +Rune gold-trimmed set (lg) +Unpack rune gold-trimmed set (sk) +Rune gold-trimmed set (sk) +Unpack gilded armour set (lg) +Gilded armour set (lg) +Unpack gilded armour set (sk) +Gilded armour set (sk) +Unpack guthix armour set (lg) +Guthix armour set (lg) +Unpack guthix armour set (sk) +Guthix armour set (sk) +Unpack saradomin armour set (lg) +Saradomin armour set (lg) +Unpack saradomin armour set (sk) +Saradomin armour set (sk) +Unpack zamorak armour set (lg) +Zamorak armour set (lg) +Unpack zamorak armour set (sk) +Zamorak armour set (sk) +Unpack ancient rune armour set (lg) +Ancient rune armour set (lg) +Unpack ancient rune armour set (sk) +Ancient rune armour set (sk) +Unpack armadyl rune armour set (lg) +Armadyl rune armour set (lg) +Unpack armadyl rune armour set (sk) +Armadyl rune armour set (sk) +Unpack bandos rune armour set (lg) +Bandos rune armour set (lg) +Unpack bandos rune armour set (sk) +Bandos rune armour set (sk) +Unpack dragon armour set (lg) +Dragon armour set (lg) +Unpack dragon armour set (sk) +Dragon armour set (sk) +Unpack verac's armour set +Verac's armour set +Unpack dharok's armour set +Dharok's armour set +Unpack guthan's armour set +Guthan's armour set +Unpack ahrim's armour set +Ahrim's armour set +Unpack torag's armour set +Torag's armour set +Unpack karil's armour set +Karil's armour set +Unpack inquisitor's armour set +Inquisitor's armour set +Unpack justiciar armour set +Justiciar armour set +Unpack obsidian armour set +Obsidian armour set +Unpack dragonstone armour set +Dragonstone armour set +Unpack initiate harness m +Initiate harness m +Unpack proselyte harness m +Proselyte harness m +Unpack proselyte harness f +Proselyte harness f +Unpack green dragonhide set +Green dragonhide set +Unpack blue dragonhide set +Blue dragonhide set +Unpack red dragonhide set +Red dragonhide set +Unpack black dragonhide set +Black dragonhide set +Unpack gilded dragonhide set +Gilded dragonhide set +Unpack guthix dragonhide set +Guthix dragonhide set +Unpack saradomin dragonhide set +Saradomin dragonhide set +Unpack zamorak dragonhide set +Zamorak dragonhide set +Unpack ancient dragonhide set +Ancient dragonhide set +Unpack armadyl dragonhide set +Armadyl dragonhide set +Unpack bandos dragonhide set +Bandos dragonhide set +Unpack mystic set (blue) +Mystic set (blue) +Unpack mystic set (dark) +Mystic set (dark) +Unpack mystic set (light) +Mystic set (light) +Unpack mystic set (dusk) +Mystic set (dusk) +Unpack ancestral robes set +Ancestral robes set +Unpack book of balance page set +Book of balance page set +Unpack holy book page set +Holy book page set +Unpack unholy book page set +Unholy book page set +Unpack book of darkness page set +Book of darkness page set +Unpack book of law page set +Book of law page set +Unpack book of war page set +Book of war page set +Unpack partyhat set +Partyhat set +Unpack halloween mask set +Halloween mask set +Unpack combat potion set +Combat potion set +Unpack super potion set +Super potion set +Unpack dwarf cannon set +Dwarf cannon set +Dagon'hai robes set +Unpack Dagon'hai robes set +Masori armour set (f) +Unpack Masori armour set (f) +Clown set +Unpack Clown set +Acrobat set +Unpack Acrobat set +Ringmaster set +Unpack Ringmaster set +Acrylic set +Unpack Acrylic set +Warpriest of Zamorak set +Unpack Warpriest of Zamorak set +Warpriest of Saradomin set +Unpack Warpriest of Saradomin set +Warpriest of Armadyl set +Unpack Warpriest of Armadyl set +Warpriest of Bandos set +Unpack Warpriest of Bandos set +Dwarven armour set +Unpack Dwarven armour set +Drygore longsword set +Unpack Drygore longsword set +Drygore rapier set +Unpack Drygore rapier set +Drygore mace set +Unpack Drygore mace set +Torva armour set +Unpack Torva armour set +Pernix armour set +Unpack Pernix armour set +Virtus armour set +Unpack Virtus armour set +Divine spirit shield +Heart crystal +Vasa cloak +Bryophyta's staff(i) +Ignis ring(i) +Abyssal pouch +Elder pouch +Bucket of sand +Breadcrumbs +Infernal bulwark +TzKal cape +Infernal slayer helmet +Infernal slayer helmet(i) +Royal crossbow +Polypore staff +Golden partyhat +Crystal fishing rod +Void staff (u) +Void staff +Revert void staff +Abyssal tome +Spellbound ring(i) +Black swan +Nexterminator +Seamonkey staff (t1) +Seamonkey staff (t2) +Seamonkey staff (t3) +Arcane blast necklace +Farsight snapshot necklace +Brawler's hook necklace +Gorajan bonecrusher +Revert gorajan bonecrusher +Fix fire cape +Fix fire max cape +Fix infernal cape +Fix infernal max cape +Fix assembler max cape +Fix imbued saradomin cape +Fix imbued saradomin max cape +Fix imbued guthix cape +Fix imbued guthix max cape +Fix imbued zamorak cape +Fix imbued zamorak max cape +Fix dragon defender +Fix avernic defender +Fix void knight top +Fix void knight robe +Fix elite void top +Fix elite void robe +Fix void knight gloves +Fix void mage helm +Fix void ranger helm +Fix void melee helm +Fix hellfire bow +Fix hellfire bownana +Unlock dragon defender +Unpack twisted relichunter (t1) armour set +Twisted relichunter (t1) armour set +Unpack twisted relichunter (t2) armour set +Twisted relichunter (t2) armour set +Unpack twisted relichunter (t3) armour set +Twisted relichunter (t3) armour set +Unpack trailblazer relichunter (t1) armour set +Trailblazer relichunter (t1) armour set +Unpack trailblazer relichunter (t2) armour set +Trailblazer relichunter (t2) armour set +Unpack trailblazer relichunter (t3) armour set +Trailblazer relichunter (t3) armour set +Unpack dagon'hai robes set +Dagon'hai robes set +Drygore rapier (blood) +Drygore rapier (ice) +Drygore rapier (shadow) +Drygore rapier (3a) +Drygore rapier (xmas) +Offhand drygore rapier (blood) +Offhand drygore rapier (ice) +Offhand drygore rapier (shadow) +Offhand drygore rapier (3a) +Offhand drygore rapier (xmas) +Drygore mace (blood) +Drygore mace (ice) +Drygore mace (shadow) +Drygore mace (3a) +Drygore mace (xmas) +Offhand drygore mace (blood) +Offhand drygore mace (ice) +Offhand drygore mace (shadow) +Offhand drygore mace (3a) +Offhand drygore mace (xmas) +Drygore longsword (blood) +Drygore longsword (ice) +Drygore longsword (shadow) +Drygore longsword (3a) +Drygore longsword (xmas) +Offhand drygore longsword (blood) +Offhand drygore longsword (ice) +Offhand drygore longsword (shadow) +Offhand drygore longsword (3a) +Offhand drygore longsword (xmas) +Dwarven warhammer (blood) +Dwarven warhammer (ice) +Dwarven warhammer (shadow) +Dwarven warhammer (3a) +Dwarven warnana +Dwarven warhammer (volcanic) +Ho-ho hammer +Dwarven pumpkinsmasher +Twisted bow (ice) +Twisted bow (shadow) +Twisted bow (blood) +Twisted bow (3a) +Twisted bownana +Zaryte bow (ice) +Zaryte bow (shadow) +Zaryte bow (blood) +Zaryte bow (3a) +Zaryte bownana +Hellfire bownana +Mistleboe +Hellfire bow (ice) +Hellfire bow (Oceanic) +Demonic piercer +Zaryte crossbow (xmas) +Vasa cloak (xmas) +Vasa cloak (zamorak) +Vasa cloak (saradomin) +Santa claws +TzKal cape (Oceanic) +TzKal cape (Volcanic) +TzKal cape (spooky) +Gorajan warrior helmet (Primal) +Gorajan warrior helmet (Oceanic) +Gorajan warrior top (Primal) +Gorajan warrior top (Oceanic) +Gorajan warrior legs (Primal) +Gorajan warrior legs (Oceanic) +Gorajan warrior gloves (Primal) +Gorajan warrior gloves (Oceanic) +Gorajan warrior boots (Primal) +Gorajan warrior boots (Oceanic) +Gorajan occult helmet (Celestial) +Gorajan occult helmet (Oceanic) +Gorajan occult top (Celestial) +Gorajan occult top (Oceanic) +Gorajan occult legs (Celestial) +Gorajan occult legs (Oceanic) +Gorajan occult gloves (Celestial) +Gorajan occult gloves (Oceanic) +Gorajan occult boots (Celestial) +Gorajan occult boots (Oceanic) +Gorajan archer helmet (Sagittarian) +Gorajan archer helmet (Oceanic) +Gorajan archer top (Sagittarian) +Gorajan archer top (Oceanic) +Gorajan archer legs (Sagittarian) +Gorajan archer legs (Oceanic) +Gorajan archer gloves (Sagittarian) +Gorajan archer gloves (Oceanic) +Gorajan archer boots (Sagittarian) +Gorajan archer boots (Oceanic) +Dwarven full helm (Volcanic) +Dwarven platebody (Volcanic) +Dwarven platelegs (Volcanic) +Dwarven gloves (Volcanic) +Dwarven boots (Volcanic) +Dwarven pickaxe (xmas) +Infernal slayer helmet(i) (ice) +Infernal slayer helmet(i) (xmas) +The Grim Reaper +Deathly collector +Ganodermic visor +Ganodermic poncho +Ganodermic leggings +Grifolic visor +Grifolic poncho +Grifolic leggings +Dragonbone boots +Dragonbone full helm +Dragonbone platebody +Dragonbone platelegs +Dragonbone gloves +Dragonbone mage boots +Dragonbone mage gloves +Dragonbone mage bottoms +Dragonbone mage hat +Dragonbone mage top +Royal dragon platebody +Frosty +Virtus wand +Virtus book +Pernix cowl +Pernix body +Pernix chaps +Pernix boots +Pernix gloves +Torva full helm +Torva platebody +Torva platelegs +Torva boots +Torva gloves +Virtus mask +Virtus robe top +Virtus robe legs +Virtus boots +Virtus gloves +Revert Pernix cowl (broken) +Revert Pernix body (broken) +Revert Pernix chaps (broken) +Revert Pernix boots (broken) +Revert Pernix gloves (broken) +Revert Torva full helm (broken) +Revert Torva platebody (broken) +Revert Torva platelegs (broken) +Revert Torva boots (broken) +Revert Torva gloves (broken) +Revert Virtus mask (broken) +Revert Virtus robe top (broken) +Revert Virtus robe legs (broken) +Revert Virtus boots (broken) +Revert Virtus gloves (broken) +Revert bandos chestplate +Revert bandos tassets +Revert armadyl helmet (components) +Revert armadyl chestplate (components) +Revert armadyl chainskirt (components) +Revert ancestral hat +Revert ancestral robe top +Revert ancestral robe bottom +Revert masori mask +Revert masori body +Revert masori chaps +Divine water +Divine water (Bones) +Divine water (Big bones) +Divine water (Jogre bones) +Divine water (Babydragon bones) +Divine water (Dragon bones) +Divine water (Wyrm bones) +Divine water (Wyvern bones) +Divine water (Drake bones) +Divine water (Lava dragon bones) +Divine water (Hydra bones) +Divine water (Dagannoth bones) +Divine water (Superior dragon bones) +Divine water (Abyssal dragon bones) +Divine water (Frost dragon bones) +Divine water (Royal dragon bones) +Crystal dust +Dagannoth slayer helm +Jelly slayer helm +Abyssal slayer helm +Black demonical slayer helm +Troll slayer helm +Ganodermic slayer helm +Gargoyle slayer helm +Dark beast slayer helm +Dust devil slayer helm +Crawling hand slayer helm +Basilisk slayer helm +Bloodveld slayer helm +Banshee slayer helm +Cockatrice slayer helm +Aberrant slayer helm +Kurask slayer helm +Titan ballista +Piercing trident +Atlantean trident +Shark tooth necklace +Ring of piercing +Ring of piercing (i) +Tidal collector +Revert completionist cape +Revert completionist cape (t) +Ghostly zombie gloves +Revert Ghostly zombie gloves +Ghostly zombie trousers +Revert Ghostly zombie trousers +Ghostly zombie shirt +Revert Ghostly zombie shirt +Ghostly zombie boots +Revert Ghostly zombie boots +Ghostly zombie mask +Revert Ghostly zombie mask +Ghostly lederhosen hat +Revert Ghostly lederhosen hat +Ghostly lederhosen shorts +Revert Ghostly lederhosen shorts +Ghostly lederhosen gloves +Revert Ghostly lederhosen gloves +Ghostly lederhosen top +Revert Ghostly lederhosen top +Ghostly lederhosen boots +Revert Ghostly lederhosen boots +Ghostly jester hat +Revert Ghostly jester hat +Ghostly jester gloves +Revert Ghostly jester gloves +Ghostly jester boots +Revert Ghostly jester boots +Ghostly jester top +Revert Ghostly jester top +Ghostly jester tights +Revert Ghostly jester tights +Ghostly ringmaster hat +Revert Ghostly ringmaster hat +Ghostly ringmaster pants +Revert Ghostly ringmaster pants +Ghostly ringmaster boots +Revert Ghostly ringmaster boots +Ghostly ringmaster shirt +Revert Ghostly ringmaster shirt +Ghostly ringmaster gloves +Revert Ghostly ringmaster gloves +Ghostly chicken feet +Revert Ghostly chicken feet +Ghostly chicken wings +Revert Ghostly chicken wings +Ghostly chicken gloves +Revert Ghostly chicken gloves +Ghostly chicken legs +Revert Ghostly chicken legs +Ghostly chicken head +Revert Ghostly chicken head +Revert Pale energy +Revert Flickering energy +Boon of flickering energy +Revert Bright energy +Boon of bright energy +Revert Glowing energy +Boon of glowing energy +Revert Sparkling energy +Boon of sparkling energy +Revert Gleaming energy +Boon of gleaming energy +Revert Vibrant energy +Boon of vibrant energy +Revert Lustrous energy +Boon of lustrous energy +Revert Elder energy +Boon of elder energy +Revert Brilliant energy +Boon of brilliant energy +Revert Radiant energy +Boon of radiant energy +Revert Luminous energy +Boon of luminous energy +Revert Incandescent energy +Boon of incandescent energy +Revert Ancient energy +Boon of ancient energy +Cache portent +Graceful portent +Rogues portent +Dungeon portent +Lucky portent +Rebirth portent +Spiritual mining portent +Pacifist hunting portent +Sundial scimitar +Offhand spidergore rapier +Lumina (Materials) +Clue scroll (elder) +Lumina (Elder logs) +Lumina (Redwood logs) +Lumina (Magic logs) +Lumina (Yew logs) +Revert Support cape +Revert Gatherer's cape +Revert Combatant's cape +Revert Artisan's cape +Graceful +Graceful hood +Graceful top +Graceful legs +Graceful gloves +Graceful boots +Graceful cape +Revert graceful +Revert graceful hood +Revert graceful top +Revert graceful legs +Revert graceful gloves +Revert graceful boots +Revert graceful cape +Dark Graceful +Dark Graceful hood +Dark graceful top +Dark graceful legs +Dark graceful gloves +Dark graceful boots +Dark graceful cape +Arceuus graceful +Arceuus graceful hood +Arceuus graceful top +Arceuus graceful legs +Arceuus graceful gloves +Arceuus graceful boots +Arceuus graceful cape +Revert arceuus graceful +Revert arceuus graceful hood +Revert arceuus graceful top +Revert arceuus graceful legs +Revert arceuus graceful gloves +Revert arceuus graceful boots +Revert arceuus graceful cape +Piscarilius graceful +Piscarilius graceful hood +Piscarilius graceful top +Piscarilius graceful legs +Piscarilius graceful gloves +Piscarilius graceful boots +Piscarilius graceful cape +Revert Piscarilius graceful +Revert Piscarilius graceful hood +Revert Piscarilius graceful top +Revert Piscarilius graceful legs +Revert Piscarilius graceful gloves +Revert Piscarilius graceful boots +Revert Piscarilius graceful cape +Lovakengj graceful +Lovakengj graceful hood +Lovakengj graceful top +Lovakengj graceful legs +Lovakengj graceful gloves +Lovakengj graceful boots +Lovakengj graceful cape +Revert Lovakengj graceful +Revert Lovakengj graceful hood +Revert Lovakengj graceful top +Revert Lovakengj graceful legs +Revert Lovakengj graceful gloves +Revert Lovakengj graceful boots +Revert Lovakengj graceful cape +Shayzien graceful +Shayzien graceful hood +Shayzien graceful top +Shayzien graceful legs +Shayzien graceful gloves +Shayzien graceful boots +Shayzien graceful cape +Revert Shayzien graceful +Revert Shayzien graceful hood +Revert Shayzien graceful top +Revert Shayzien graceful legs +Revert Shayzien graceful gloves +Revert Shayzien graceful boots +Revert Shayzien graceful cape +Hosidius graceful +Hosidius graceful hood +Hosidius graceful top +Hosidius graceful legs +Hosidius graceful gloves +Hosidius graceful boots +Hosidius graceful cape +Revert Hosidius graceful +Revert Hosidius graceful hood +Revert Hosidius graceful top +Revert Hosidius graceful legs +Revert Hosidius graceful gloves +Revert Hosidius graceful boots +Revert Hosidius graceful cape +Kourend graceful +Kourend graceful hood +Kourend graceful top +Kourend graceful legs +Kourend graceful gloves +Kourend graceful boots +Kourend graceful cape +Revert Kourend graceful +Revert Kourend graceful hood +Revert Kourend graceful top +Revert Kourend graceful legs +Revert Kourend graceful gloves +Revert Kourend graceful boots +Revert Kourend graceful cape +Scythe of vitur +Sanguinesti staff +Holy sanguinesti staff +Revert sanguinesti staff +Revert holy sanguinesti staff +Revert scythe of vitur +Holy scythe of vitur +Revert holy scythe of vitur +Revert holy scythe of vitur (uncharged) +Sanguine scythe of vitur +Revert sanguine scythe of vitur +Sanguine scythe of vitur (uncharged) +Revert sanguine scythe of vitur (uncharged) +Holy scythe of vitur (uncharged) +Holy sanguinesti staff (uncharged) +Revert holy sanguinesti staff (uncharged) +Avernic defender +Revert avernic defender +Holy ghrazi rapier +Revert holy ghrazi rapier +Granite maul (or) +Granite maul (ornate handle) +Granite maul (or) (ornate handle) +Mystic steam staff (or) (lms) +Revert mystic steam staff (or) +Steam battlestaff (or) +Revert steam battlestaff (or) +Mystic lava staff (or) (lms) +Revert mystic lava staff (or) +Lava battlestaff (or) +Revert lava battlestaff (or) +Dragon pickaxe (upgraded) +Revert dragon pickaxe (upgraded) +Malediction ward (or) +Revert malediction ward (or) +Odium ward (or) +Revert odium ward (or) +Dark bow (green) +Revert dark bow (green) +Dark bow (blue) +Revert dark bow (blue) +Dark bow (yellow) +Revert dark bow (yellow) +Dark bow (white) +Revert dark bow (white) +Volcanic abyssal whip +Revert volcanic abyssal whip +Frozen abyssal whip +Revert frozen abyssal whip +Staff of balance +Saradomin's blessed sword +Magic shortbow (i) +Looting bag +Rune pouch +Mystic air staff +Mystic water staff +Mystic earth staff +Mystic fire staff +Mystic dust staff +Mystic lava staff +Mystic lava staff (or) +Mystic mist staff +Mystic mud staff +Mystic smoke staff +Mystic steam staff +Mystic steam staff (or) +Ancient godsword +Zaryte crossbow +Revert crystal weapon seed +Revert crystal tool seed +Revert enhanced crystal teleport seed +Revert crystal armour seed +Revert enhanced crystal weapon seed +Golden prospector boots +Golden prospector helmet +Golden prospector jacket +Golden prospector legs +Dragon axe (or) +Revert Dragon axe (or) +Dragon harpoon (or) +Revert Dragon harpoon (or) +Infernal axe (or) +Revert Infernal axe (or) +Infernal harpoon (or) +Revert Infernal harpoon (or) +Infernal pickaxe (or) +Revert Infernal pickaxe (or) +Dragon pickaxe (or) (Trailblazer) +Revert Dragon pickaxe (or) (Trailblazer) +Abyssal tentacle (or) +Revert Abyssal tentacle (or) +Abyssal whip (or) +Revert Abyssal whip (or) +Book of balance (or) +Revert Book of balance (or) +Book of darkness (or) +Revert Book of darkness (or) +Book of law (or) +Revert Book of law (or) +Book of war (or) +Revert Book of war (or) +Holy book (or) +Revert Holy book (or) +Unholy book (or) +Revert Unholy book (or) +Rune crossbow (or) +Revert Rune crossbow (or) +Elite void robe (or) +Revert Elite void robe (or) +Elite void top (or) +Revert Elite void top (or)) +Void knight gloves (or) +Revert Void knight gloves (or) +Void knight top (or) +Revert Void knight top (or) +Void knight robe (or) +Revert Void knight robe (or) +Void mage helm (or) +Revert Void mage helm (or) +Void melee helm (or) +Revert Void melee helm (or) +Void ranger helm (or) +Revert Void ranger helm (or) +Cannon barrels (or) +Revert Cannon barrels (or) +Cannon base (or) +Revert Cannon base (or) +Cannon furnace (or) +Revert Cannon furnace (or) +Cannon stand (or) +Revert Cannon stand (or) +Mystic boots (or) +Revert Mystic boots (or) +Mystic gloves (or) +Revert Mystic gloves (or) +Mystic hat (or) +Revert Mystic hat (or) +Mystic robe bottom (or) +Revert Mystic robe bottom (or) +Mystic robe top (or) +Revert Mystic robe top (or) +Unpack Twisted relic hunter (t1) armour set +Unpack Twisted relic hunter (t2) armour set +Unpack Twisted relic hunter (t3) armour set +Unpack Trailblazer relic hunter (t1) armour set +Unpack Trailblazer relic hunter (t2) armour set +Unpack Trailblazer relic hunter (t3) armour set +Unpack Shattered relic hunter (t1) armour set +Unpack Shattered relic hunter (t2) armour set +Unpack Shattered relic hunter (t3) armour set +Trailblazer graceful hood +Revert Trailblazer graceful hood +Trailblazer graceful cape +Revert Trailblazer graceful cape +Trailblazer graceful top +Revert Trailblazer graceful top +Trailblazer graceful legs +Revert Trailblazer graceful legs +Trailblazer graceful gloves +Revert Trailblazer graceful gloves +Trailblazer graceful boots +Revert Trailblazer graceful boots +Hat of the eye (red) +Revert Hat of the eye (red) +Robe top of the eye (red) +Revert Robe top of the eye (red) +Robe bottoms of the eye (red) +Revert Robe bottoms of the eye (red) +Hat of the eye (green) +Revert Hat of the eye (green) +Robe top of the eye (green) +Revert Robe top of the eye (green) +Robe bottoms of the eye (green) +Revert Robe bottoms of the eye (green) +Hat of the eye (blue) +Revert Hat of the eye (blue) +Robe top of the eye (blue) +Revert Robe top of the eye (blue) +Robe bottoms of the eye (blue) +Revert Robe bottoms of the eye (blue) +Abyssal green dye (using Abyssal blue dye) +Abyssal green dye (using Abyssal red dye) +Abyssal blue dye (using Abyssal green dye) +Abyssal blue dye (using Abyssal red dye) +Abyssal red dye (using Abyssal green dye) +Abyssal red dye (using Abyssal blue dye) +Runite igne claws +Dragon igne claws +Barrows igne claws +Volcanic igne claws +Drygore igne claws +Dwarven igne claws +Gorajan igne claws +Dragon igne armor +Barrows igne armor +Volcanic igne armor +Justiciar igne armor +Drygore igne armor +Dwarven igne armor +Gorajan igne armor +Demonic jibwings +Abyssal jibwings +3rd age jibwings +3rd age jibwings (e) +Demonic jibwings (e) +Abyssal jibwings (e) +Divine ring +Impling locator +Revert Runite igne claws +Revert Dragon igne claws +Revert Barrows igne claws +Revert Volcanic igne claws +Revert Drygore igne claws +Revert Dwarven igne claws +Revert Gorajan igne claws +Revert Dragon igne armor +Revert Barrows igne armor +Revert Volcanic igne armor +Revert Justiciar igne armor +Revert Drygore igne armor +Revert Dwarven igne armor +Revert Gorajan igne armor +Volcanic pickaxe +Offhand volcanic pickaxe +Moktang totem +Dragonstone full helm(u) +Dragonstone platebody(u) +Dragonstone platelegs(u) +Dragonstone boots(u) +Dragonstone gauntlets(u) +Bronze coffin +Steel coffin +Black coffin +Silver coffin +Gold coffin +Necromancer outfit +Necromancer's air staff +Revert Necromancer's air staff +Necromancer's earth staff +Revert Necromancer's earth staff +Necromancer's fire staff +Revert Necromancer's fire staff +Necromancer's lava staff +Revert Necromancer's lava staff +Necromancer's mud staff +Revert Necromancer's mud staff +Necromancer's steam staff +Revert Necromancer's steam staff +Necromancer's water staff +Revert Necromancer's water staff +Skeletal battlestaff of air +Revert Skeletal battlestaff of air +Skeletal battlestaff of earth +Revert Skeletal battlestaff of earth +Skeletal battlestaff of fire +Revert Skeletal battlestaff of fire +Skeletal battlestaff of water +Revert Skeletal battlestaff of water +Skeletal lava battlestaff +Revert Skeletal lava battlestaff +Skeletal mud battlestaff +Revert Skeletal mud battlestaff +Skeletal steam battlestaff +Revert Skeletal steam battlestaff +Masori assembler +Osmumten's fang (or) +Elidinis' ward (f) +Revert Elidinis' ward (f) +Elidinis' ward (or) +Revert Elidinis' ward (or) +Divine rune pouch +Masori mask (f) +Masori body (f) +Masori chaps (f) +Revert Armadyl helmet (to plates) +Revert Armadyl chestplate (to plates) +Revert Armadyl chainskirt (to plates) +Keris partisan of breaching +Keris partisan of corruption +Keris partisan of the sun +Revert Masori assembler +Akkhito +Revert Akkhito +Babi +Revert Babi +Kephriti +Revert Kephriti +Tumeken's damaged guardian +Revert Tumeken's damaged guardian +Elidinis' damaged guardian +Revert Elidinis' damaged guardian +Zebo +Revert Zebo +Revert Arcane spirit shield +Bloodbark helm +Bloodbark body +Bloodbark legs +Bloodbark boots +Bloodbark gauntlets +Swampbark helm +Swampbark body +Swampbark legs +Swampbark boots +Swampbark gauntlets +Warrior icon +Bellator icon +Bellator ring +Berserker icon +Ultor icon +Ultor ring +Seers icon +Magus icon +Magus ring +Sanguine torva full helm +Sanguine torva platebody +Sanguine torva platelegs +Archer icon +Venator icon +Venator ring +Soulreaper axe +Tztok slayer helmet +Revert Tztok slayer helmet +Tztok slayer helmet (i) +Revert Tztok slayer helmet (i) +Dragon hunter crossbow (t) +Revert Dragon hunter crossbow (t) +Dragon hunter crossbow (b) +Revert Dragon hunter crossbow (b) +Tzkal slayer helmet +Revert Tzkal slayer helmet +Tzkal slayer helmet (i) +Revert Tzkal slayer helmet (i) +Vampyric slayer helmet +Revert Vampyric slayer helmet +Vampyric slayer helmet (i) +Revert Vampyric slayer helmet (i) +Ghommal's avernic defender 5 +Revert Ghommal's avernic defender 5 +Ghommal's avernic defender 6 +Revert Ghommal's avernic defender 6 +Secateurs attachment +Nature offerings +Sturdy harness +Forestry basket +Clothes pouch +Pheasant hat +Pheasant legs +Pheasant boots +Pheasant cape +Bronze felling axe +Iron felling axe +Steel felling axe +Black felling axe +Mithril felling axe +Adamant felling axe +Rune felling axe +Dragon felling axe +Crystal felling axe +3rd age felling axe +Pheasant +Revert Pheasant +Fox +Revert Fox +Barronite mace +Imcando hammer +Axe handle base +Axe handle +Axe of the high sungod (u) +Axe of the high sungod diff --git a/food.txt b/food.txt new file mode 100644 index 0000000000..eb498eae6d --- /dev/null +++ b/food.txt @@ -0,0 +1,241 @@ +Anchovies heals 1 +Shrimps heals 3 +Cooked chicken heals 3 +Cooked meat heals 3 +Sardine heals 4 +Bread heals 5 +Herring heals 5 +Mackerel heals 6 +Trout heals 7 +Cod heals 7 +Pike heals 8 +Salmon heals 9 +Tuna heals 10 +Jug of wine heals 11 +Stew heals 11 +Cake heals 12 +Lobster heals 12 +Bass heals 13 +Plain pizza heals 14 +Swordfish heals 14 +Potato with butter heals 14 +Chilli potato heals 14 +Chocolate cake heals 15 +Egg potato heals 16 +Potato with cheese heals 16 +Meat pizza heals 16 +Monkfish heals 16 +Anchovy pizza heals 18 +Cooked karambwan heals 18 +Blighted karambwan heals 18 +Curry heals 19 +Ugthanki kebab heals 19 +Mushroom potato heals 20 +Shark heals 20 +Sea turtle heals 21 +Pineapple pizza heals 22 +Summer pie heals 22 +Manta ray heals 22 +Blighted manta ray heals 22 +Tuna potato heals 22 +Dark crab heals 22 +Anglerfish heals e=>{let t=e.skillLevel("hitpoints"),a=2;return t>10&&(a=2),t>25&&(a=4),t>50&&(a=6),t>75&&(a=8),t>93&&(a=13),t*(1/10)+a} +Blighted anglerfish heals e=>{let t=e.skillLevel("hitpoints"),a=2;return t>10&&(a=2),t>25&&(a=4),t>50&&(a=6),t>75&&(a=8),t>93&&(a=13),t*(1/10)+a} +Rocktail heals 26 +Raw yeti meat heals 26 +Cup of tea heals 3 +Bruised banana heals 0 +Jangerberries heals 2 +Giant carp heals 6 +Edible seaweed heals 4 +Karamjan rum heals 5 +Ugthanki meat heals 3 +Chopped tomato heals 2 +Chopped onion heals 1 +Onion & tomato heals 3 +Asgarnian ale heals 1 +Wizard's mind bomb heals 1 +Greenman's ale heals 1 +Dragon bitter heals 1 +Dwarven stout heals 1 +Grog heals 3 +Beer heals 1 +Potato heals 1 +Onion heals 1 +Pumpkin heals 14 +Easter egg heals 14 +Banana heals 2 +Cabbage heals 1 +Spinach roll heals 2 +Chocolate bar heals 3 +Chocolatey milk heals 4 +Tomato heals 2 +Cheese heals 2 +Half full wine jug heals 7 +Vodka heals 5 +Whisky heals 5 +Gin heals 5 +Brandy heals 5 +Premade blurb' sp. heals 7 +Premade choc s'dy heals 5 +Premade dr' dragon heals 5 +Premade fr' blast heals 9 +Premade p' punch heals 9 +Premade sgg heals 5 +Premade wiz blz'd heals 5 +Pineapple punch heals 9 +Wizard blizzard heals 5 +Blurberry special heals 7 +Choc saturday heals 5 +Short green guy heals 5 +Fruit blast heals 9 +Drunk dragon heals 5 +Lemon heals 2 +Lemon chunks heals 2 +Lemon slices heals 2 +Orange heals 2 +Orange chunks heals 2 +Orange slices heals 2 +Pineapple chunks heals 2 +Pineapple ring heals 2 +Lime heals 2 +Lime chunks heals 2 +Lime slices heals 2 +Dwellberries heals 2 +Equa leaves heals 1 +Pot of cream heals 1 +Lava eel heals 11 +Toad's legs heals 3 +King worm heals 2 +Chocolate bomb heals 15 +Tangled toad's legs heals 15 +Worm hole heals 12 +Veg ball heals 12 +Worm crunchies heals 8 +Chocchip crunchies heals 7 +Spicy crunchies heals 7 +Toad crunchies heals 8 +Premade w'm batta heals 11 +Premade t'd batta heals 11 +Premade c+t batta heals 11 +Premade fr't batta heals 11 +Premade veg batta heals 11 +Premade choc bomb heals 15 +Premade ttl heals 15 +Premade worm hole heals 12 +Premade veg ball heals 12 +Premade w'm crun' heals 8 +Premade ch' crunch heals 8 +Premade s'y crunch heals 7 +Premade t'd crunch heals 8 +Worm batta heals 11 +Toad batta heals 11 +Cheese+tom batta heals 11 +Fruit batta heals 11 +Vegetable batta heals 11 +Cooked oomlie wrap heals 14 +Cooked chompy heals 11 +Moonlight mead heals 4 +Sliced banana heals 2 +Cooked rabbit heals 5 +Keg of beer heals 15 +Beer tankard heals 4 +Monkey nuts heals 4 +Monkey bar heals 5 +Banana stew heals 11 +Nettle-water heals 1 +Nettle tea heals 3 +White pearl heals 2 +Giant frog legs heals 6 +Bandit's brew heals 1 +Asgarnian ale(m) heals 2 +Mature wmb heals 2 +Greenman's ale(m) heals 2 +Dragon bitter(m) heals 2 +Dwarven stout(m) heals 2 +Moonlight mead(m) heals 6 +Axeman's folly heals 1 +Axeman's folly(m) heals 2 +Chef's delight heals 1 +Chef's delight(m) heals 2 +Slayer's respite heals 1 +Slayer's respite(m) heals 2 +Cider heals 1 +Mature cider heals 2 +Papaya fruit heals 8 +Gout tuber heals 12 +White tree fruit heals 3 +Baked potato heals 4 +Choc-ice heals 7 +Peach heals 8 +Baguette heals 6 +Triangle sandwich heals 6 +Roll heals 6 +Square sandwich heals 6 +Chilli con carne heals 5 +Egg and tomato heals 8 +Mushroom & onion heals 11 +Tuna and corn heals 13 +Minced meat heals 13 +Spicy sauce heals 2 +Scrambled egg heals 2 +Fried mushrooms heals 5 +Fried onions heals 5 +Chopped tuna heals 10 +Braindeath 'rum' heals 14 +Roast rabbit heals 7 +Spicy stew heals 0 +Cooked fishcake heals 11 +Cooked jubbly heals 15 +Red banana heals 5 +Tchiki monkey nuts heals 5 +Sliced red banana heals 5 +Stuffed snake heals 20 +Bottle of wine heals 14 +Field ration heals 10 +Fresh monkfish heals 1 +Locust meat heals 3 +Roast bird meat heals 6 +Roast beast meat heals 8 +Spicy tomato heals 2 +Spicy minced meat heals 3 +Rainbow fish heals 11 +Green gloop soup heals 2 +Frogspawn gumbo heals 2 +Frogburger heals 2 +Coated frogs' legs heals 2 +Bat shish heals 2 +Fingers heals 2 +Grubs à la mode heals 2 +Roast frog heals 2 +Mushrooms heals 2 +Fillets heals 2 +Loach heals 3 +Eel sushi heals 10 +Roe heals 3 +Caviar heals 5 +Pysk fish (0) heals 5 +Suphi fish (1) heals 8 +Leckish fish (2) heals 11 +Brawk fish (3) heals 14 +Mycil fish (4) heals 17 +Roqed fish (5) heals 20 +Kyren fish (6) heals 23 +Guanic bat (0) heals 5 +Prael bat (1) heals 8 +Giral bat (2) heals 11 +Phluxia bat (3) heals 14 +Kryket bat (4) heals 17 +Murng bat (5) heals 20 +Psykk bat (6) heals 23 +Honey locust heals 20 +Silk dressing (2) heals 100 +Bloody bracer heals 2 +Dragonfruit heals 10 +Paddlefish heals 20 +Elven dawn heals 1 +Cooked mystery meat heals 5 +Steak sandwich heals 6 +Corrupted paddlefish heals 16 +Crystal paddlefish heals 16 +Kovac's grog heals 1 diff --git a/package.json b/package.json index 2cd90d2506..bd73628fdc 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "start": "yarn build && node --enable-source-maps dist/", "gen": "concurrently --raw \"prisma generate --no-hints\" \"prisma generate --no-hints --schema prisma/robochimp.prisma\" && echo \"Generated Prisma Client\"", "prettify": "prettier --use-tabs \"./**/*.{md,yml}\" --write", - "lint": "concurrently --raw --kill-others-on-fail \"biome check --write --unsafe --diagnostic-level=error\" \"yarn prettify\" \"prisma format --schema ./prisma/robochimp.prisma\" \"prisma format --schema ./prisma/schema.prisma\"", + "lint": "concurrently --raw --kill-others-on-fail \"biome check --write --unsafe --diagnostic-level=error\" \"yarn prettify\" \"prisma format --schema ./prisma/schema.prisma\"", "build:tsc": "tsc -p src", "watch:tsc": "tsc -w -p src", "wipedist": "node -e \"try { require('fs').rmSync('dist', { recursive: true }) } catch(_){}\"", @@ -17,9 +17,8 @@ "test:docker": "docker-compose up --build --abort-on-container-exit --remove-orphans && docker-compose down --volumes --remove-orphans", "test:watch": "vitest --config vitest.unit.config.mts --coverage", "buildandrun": "yarn build:esbuild && node --enable-source-maps dist", - "build:esbuild": "concurrently --raw \"yarn build:main\" \"yarn build:workers\"", - "build:main": "esbuild src/index.ts src/lib/workers/index.ts --sourcemap=inline --minify --legal-comments=none --outdir=./dist --log-level=error --bundle --platform=node --loader:.node=file --external:@napi-rs/canvas --external:@prisma/robochimp --external:@prisma/client --external:zlib-sync --external:bufferutil --external:oldschooljs --external:discord.js --external:node-fetch --external:piscina", - "build:workers": "esbuild src/lib/workers/kill.worker.ts src/lib/workers/finish.worker.ts src/lib/workers/casket.worker.ts --sourcemap=inline --log-level=error --bundle --minify --legal-comments=none --outdir=./dist/lib/workers --platform=node --loader:.node=file --external:@napi-rs/canvas --external:@prisma/robochimp --external:@prisma/client --external:zlib-sync --external:bufferutil --external:oldschooljs --external:discord.js --external:node-fetch --external:piscina", + "build:esbuild": "yarn build:main", + "build:main": "esbuild src/index.ts --sourcemap=inline --minify --legal-comments=none --outdir=./dist --log-level=error --bundle --platform=node --loader:.node=file --external:@napi-rs/canvas --external:@prisma/robochimp --external:@prisma/client --external:zlib-sync --external:bufferutil --external:oldschooljs --external:discord.js --external:node-fetch --external:piscina", "test:circular": "dpdm --exit-code circular:1 --progress=false --warning=false --tree=false ./dist/index.js", "test:ci:unit": "concurrently --raw --kill-others-on-fail \"yarn test:unit\" \"yarn test:lint\" \"tsc -p tests/integration\" \"tsc -p tests/unit\" \"yarn test:circular\"" }, @@ -28,34 +27,25 @@ "@oldschoolgg/toolkit": "git+https://github.com/oldschoolgg/toolkit.git#3550582efbdf04929f0a2c5114ed069a61551807", "@prisma/client": "^5.17.0", "@sapphire/ratelimits": "^2.4.9", - "@sapphire/snowflake": "^3.5.3", - "@sapphire/time-utilities": "^1.6.0", - "@sentry/node": "^8.15.0", + "@sapphire/utilities": "^3.16.2", "ascii-table3": "^0.9.0", - "bufferutil": "^4.0.8", "discord.js": "^14.15.3", "dotenv": "^16.4.5", "e": "0.2.33", - "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21", "lru-cache": "^10.3.0", "murmurhash": "^2.0.1", - "node-cron": "^3.0.3", "node-fetch": "^2.6.7", "oldschooljs": "^2.5.10", "p-queue": "^6.6.2", - "piscina": "^4.6.1", "random-js": "^2.1.0", - "simple-statistics": "^7.8.3", "sonic-boom": "^4.0.1", - "zlib-sync": "^0.1.9", "zod": "^3.23.8" }, "devDependencies": { "@biomejs/biome": "^1.8.3", "@types/lodash": "^4.14.195", "@types/node": "^20.14.9", - "@types/node-cron": "^3.0.7", "@types/node-fetch": "^2.6.1", "@vitest/coverage-v8": "^2.0.3", "concurrently": "^8.2.2", diff --git a/pool1-cl.txt b/pool1-cl.txt new file mode 100644 index 0000000000..dcf8179414 --- /dev/null +++ b/pool1-cl.txt @@ -0,0 +1,4076 @@ +1st Item Pool (Overall+ CL Items) +Abyssal orphan +Unsired +Abyssal head +Bludgeon spine +Bludgeon claw +Bludgeon axon +Jar of miasma +Abyssal dagger +Abyssal whip +Ikkle hydra +Hydra's claw +Hydra tail +Hydra leather +Hydra's fang +Hydra's eye +Hydra's heart +Dragon knife +Dragon thrownaxe +Jar of chemicals +Alchemical hydra heads +Karil's coif +Karil's leathertop +Karil's leatherskirt +Karil's crossbow +Ahrim's hood +Ahrim's robetop +Ahrim's robeskirt +Ahrim's staff +Dharok's helm +Dharok's platebody +Dharok's platelegs +Dharok's greataxe +Guthan's helm +Guthan's platebody +Guthan's chainskirt +Guthan's warspear +Torag's helm +Torag's platebody +Torag's platelegs +Torag's hammers +Verac's helm +Verac's brassard +Verac's plateskirt +Verac's flail +Bolt rack +Bryophyta's essence +Callisto cub +Tyrannical ring +Dragon pickaxe +Dragon 2h sword +Claws of callisto +Voidwaker hilt +Hellpuppy +Eternal crystal +Pegasian crystal +Primordial crystal +Jar of souls +Smouldering stone +Key master teleport +Pet chaos elemental +Odium shard 1 +Malediction shard 1 +Pet zilyana +Armadyl crossbow +Saradomin hilt +Saradomin sword +Saradomin's light +Godsword shard 1 +Godsword shard 2 +Godsword shard 3 +Steam battlestaff +Staff of the dead +Armadyl hilt +Bandos hilt +Zamorak hilt +Zamorakian spear +Armadyl helmet +Armadyl chestplate +Armadyl chainskirt +Bandos chestplate +Bandos tassets +Bandos boots +Pet kree'arra +Pet general graardor +Pet k'ril tsutsaroth +Pet dark core +Elysian sigil +Spectral sigil +Arcane sigil +Divine sigil +Holy elixir +Spirit shield +Jar of spirits +Odium shard 2 +Malediction shard 2 +Fedora +Pet dagannoth prime +Pet dagannoth supreme +Pet dagannoth rex +Berserker ring +Archers ring +Seers ring +Warrior ring +Dragon axe +Seercull +Mud battlestaff +Baron +Eye of the duke +Frozen tablet +Magus vestige +Chromium ingot +Awakener's orb +Ice quartz +Lil'viathan +Leviathan's lure +Venator vestige +Smoke quartz +Scarred tablet +Butch +Executioner's axe head +Ultor vestige +Blood quartz +Strangled tablet +Vampyre hunter boots +Vampyre hunter legs +Vampyre hunter top +Vampyre hunter cuffs +Vampyre hunter hat +Blightbrand +Vampyric plushie +Echo +Wisp +Siren's staff +Bellator vestige +Shadow quartz +Sirenic tablet +Tzrek-jad +Fire cape +Smol heredit +Dizana's quiver (uncharged) +Sunfire fanatic cuirass +Sunfire fanatic chausses +Sunfire fanatic helm +Echo crystal +Tonalztics of ralos (uncharged) +Sunfire splinters +Youngllef +Crystal armour seed +Crystal weapon seed +Enhanced crystal weapon seed +Gauntlet cape +Baby mole +Mole skin +Mole claw +Noon +Black tourmaline core +Granite gloves +Granite ring +Granite hammer +Jar of stone +Granite dust +Bottomless compost bucket +Iasor seed +Kronos seed +Attas seed +Jal-nib-rek +Infernal cape +Jal-MejJak +Head of TzKal Zuk +TzKal-Zuk's skin +Infernal core +Kalphite princess +Kq head +Jar of sand +Dragon chainbody +Prince black dragon +Kbd heads +Draconic visage +Pet kraken +Kraken tentacle +Trident of the seas (full) +Jar of dirt +Little nightmare +Inquisitor's mace +Inquisitor's great helm +Inquisitor's hauberk +Inquisitor's plateskirt +Nightmare staff +Volatile orb +Harmonised orb +Eldritch orb +Jar of dreams +Slepey tablet +Parasitic egg +Hill giant club +Muphin +Venator shard +Ancient icon +Charged ice +Frozen cache +Ancient essence +Sraracha +Jar of eyes +Giant egg sac(full) +Sarachnis cudgel +Scorpia's offspring +Odium shard 3 +Malediction shard 3 +Scurry +Scurrius' spine +Skotos +Jar of darkness +Dark claw +Dark totem +Uncut onyx +Ancient shard +Tiny tempor +Big harpoonfish +Spirit angler headband +Spirit angler top +Spirit angler waders +Spirit angler boots +Tome of water (empty) +Soaked page +Tackle box +Fish barrel +Dragon harpoon +Spirit flakes +Pet smoke devil +Occult necklace +Smoke battlestaff +Jar of smoke +Venenatis spiderling +Treasonous ring +Fangs of venenatis +Voidwaker gem +Vet'ion jr. +Ring of the gods +Skull of vet'ion +Voidwaker blade +Vorki +Vorkath's head +Skeletal visage +Jar of decay +Dragonbone necklace +Mini moktang +Volcanic dye +Igne gear frame +Volcanic shards +Dragonstone upgrade kit +Phoenix +Wintertoad +Tome of fire +Burnt page +Pyromancer garb +Pyromancer hood +Pyromancer robe +Pyromancer boots +Warm gloves +Bruma torch +Smolcano +Crystal tool seed +Zalcano shard +Pet snakeling +Brock +Tanzanite mutagen +Magma mutagen +Jar of swamp +Magic fang +Serpentine visage +Tanzanite fang +Zul-andra teleport +Zulrah's scales +Broken dwarven warhammer +Dwarven ore +Dwarven crate +Athelas seed +Abyssal thread +Abyssal cape +Dragon hunter lance +Ori +Nihil horn +Zaryte vambraces +Nihil shard +Nexling +Drygore mace +Offhand drygore mace +Drygore longsword +Offhand drygore longsword +Drygore rapier +Offhand drygore rapier +Perfect chitin +Baby kalphite king +Dark crystal +Abyssal gem +Tattered tome +Spellbound ring +Korulsi seed +Torva full helm (broken) +Torva platebody (broken) +Torva platelegs (broken) +Torva boots (broken) +Torva gloves (broken) +Pernix cowl (broken) +Pernix body (broken) +Pernix chaps (broken) +Pernix boots (broken) +Pernix gloves (broken) +Virtus mask (broken) +Virtus robe top (broken) +Virtus robe legs (broken) +Virtus boots (broken) +Virtus gloves (broken) +Virtus crystal +Zaryte bow +Frozen key +Bloodsoaked feather +Ancient emblem +Ancient hilt +Tattered robes of Vasa +Jar of magic +Voidling +Magus scroll +Magical artifact +Ignecarus scales +Ignecarus dragonclaw +Dragon egg +Ignis ring +Tangleroot +Crystal tangleroot +Dragonfruit tangleroot +Herb tangleroot +White lily tangleroot +Redwood tangleroot +Mysterious seed +Grand crystal acorn +Ent hide +Fish sack +Pufferfish +Squid dye +Queen black dragonling +Royal dragon kiteshield +Dragonbone upgrade kit +Royal torsion spring +Royal sight +Royal frame +Royal bolt stabiliser +Mini akumu +Nightmarish ashes +Cursed onyx +Demon statuette +Baby venatrix +Venatrix eggs +Venatrix webbing +Spiders leg bottom +Olmlet +Twisted bow +Elder maul +Kodai insignia +Dragon claws +Ancestral hat +Ancestral robe top +Ancestral robe bottom +Dinh's bulwark +Dexterous prayer scroll +Arcane prayer scroll +Dragon hunter crossbow +Twisted buckler +Torn prayer scroll +Dark relic +Onyx +Metamorphic dust +Twisted ancestral colour kit +Xeric's guard +Xeric's warrior +Xeric's sentinel +Xeric's general +Xeric's champion +Lil' zik +Scythe of vitur (uncharged) +Ghrazi rapier +Sanguinesti staff (uncharged) +Justiciar faceguard +Justiciar chestguard +Justiciar legguards +Avernic defender hilt +Vial of blood +Sinhaza shroud tier 1 +Sinhaza shroud tier 2 +Sinhaza shroud tier 3 +Sinhaza shroud tier 4 +Sinhaza shroud tier 5 +Sanguine dust +Holy ornament kit +Sanguine ornament kit +Lil' maiden +Lil' bloat +Lil' nylo +Lil' sot +Lil' xarp +Tumeken's guardian +Tumeken's shadow (uncharged) +Elidinis' ward +Masori mask +Masori body +Masori chaps +Lightbearer +Osmumten's fang +Thread of elidinis +Breach of the scarab +Eye of the corruptor +Jewel of the sun +Menaphite ornament kit +Cursed phalanx +Masori crafting kit +Cache of runes +Icthlarin's shroud (tier 1) +Icthlarin's shroud (tier 2) +Icthlarin's shroud (tier 3) +Icthlarin's shroud (tier 4) +Icthlarin's shroud (tier 5) +Remnant of akkha +Remnant of ba-ba +Remnant of kephri +Remnant of zebak +Ancient remnant +Shark jaw +Shark tooth +Oceanic relic +Aquifer aegis +Oceanic dye +Crush +Bruce +Pearl +Bluey +Oceanic shroud (tier 1) +Oceanic shroud (tier 2) +Oceanic shroud (tier 3) +Oceanic shroud (tier 4) +Oceanic shroud (tier 5) +Dragon claw +Offhand dragon claw +Ruined dragon armour slice +Ruined dragon armour lump +Ruined dragon armour shard +Dragon limbs +Earth warrior champion scroll +Ghoul champion scroll +Giant champion scroll +Goblin champion scroll +Hobgoblin champion scroll +Imp champion scroll +Jogre champion scroll +Lesser demon champion scroll +Skeleton champion scroll +Zombie champion scroll +Champion's cape +Elder chaos top +Elder chaos robe +Elder chaos hood +Viggora's chainmace (u) +Craw's bow (u) +Thammaron's sceptre (u) +Amulet of avarice +Bracelet of ethereum (uncharged) +Ancient crystal +Ancient relic +Ancient effigy +Ancient medallion +Ancient statuette +Ancient totem +Revenant cave teleport +Revenant ether +Mycelium leggings web +Mycelium poncho web +Mycelium visor web +Neem drupe +Polypore spore +Grifolic flake +Grifolic gloves +Grifolic orb +Gorajian mushroom +Tombshroom spore +Ganodermic flake +Ganodermic gloves +Ganodermic boots +Crawling hand +Cockatrice head +Basilisk head +Kurask head +Imbued heart +Eternal gem +Dust battlestaff +Mist battlestaff +Granite maul +Mudskipper hat +Flippers +Brine sabre +Leaf-bladed sword +Leaf-bladed battleaxe +Black mask (10) +Granite longsword +Granite boots +Wyvern visage +Granite legs +Granite helm +Bronze boots +Iron boots +Steel boots +Black boots +Mithril boots +Adamant boots +Rune boots +Dragon boots +Uncharged trident +Dark bow +Dragon sword +Broken dragon hasta +Drake's tooth +Drake's claw +Mystic hat (light) +Mystic robe top (light) +Mystic robe bottom (light) +Mystic gloves (light) +Mystic boots (light) +Mystic hat (dark) +Mystic robe top (dark) +Mystic robe bottom (dark) +Mystic gloves (dark) +Mystic boots (dark) +Mystic hat (dusk) +Mystic robe top (dusk) +Mystic robe bottom (dusk) +Mystic gloves (dusk) +Mystic boots (dusk) +Basilisk jaw +Dagon'hai hat +Dagon'hai robe top +Dagon'hai robe bottom +Blood shard +Copper stone spirit +Tin stone spirit +Iron stone spirit +Coal stone spirit +Silver stone spirit +Mithril stone spirit +Adamantite stone spirit +Gold stone spirit +Runite stone spirit +Brackish blade +Ancient ceremonial mask +Ancient ceremonial top +Ancient ceremonial legs +Ancient ceremonial gloves +Ancient ceremonial boots +Dagannoth slayer helm +Dagannoth mask +Jelly slayer helm +Jelly mask +Abyssal slayer helm +Abyssal mask +Black demonical slayer helm +Black demonical mask +Troll slayer helm +Troll mask +Ganodermic slayer helm +Ganodermic mask +Gargoyle slayer helm +Gargoyle mask +Dark beast slayer helm +Dark beast mask +Dust devil slayer helm +Dust devil mask +Crawling hand slayer helm +Crawling hand mask +Basilisk slayer helm +Basilisk mask +Bloodveld slayer helm +Bloodveld mask +Banshee slayer helm +Banshee's mask +Cockatrice slayer helm +Cockatrice mask +Aberrant slayer helm +Aberrant mask +Kurask slayer helm +Kurask mask +Obsidian cape +Toktz-ket-xil +Tzhaar-ket-om +Toktz-xil-ak +Toktz-xil-ek +Toktz-mej-tal +Toktz-xil-ul +Obsidian helmet +Obsidian platebody +Obsidian platelegs +Solite +Eagle egg +Sun-metal scraps +Lunite +Moonlight essence +Moondash charm +Noom +Mole slippers +Frog slippers +Bear feet +Demon feet +Jester cape +Shoulder parrot +Monk's robe top (t) +Monk's robe (t) +Amulet of defence (t) +Sandwich lady hat +Sandwich lady top +Sandwich lady bottom +Rune scimitar ornament kit (guthix) +Rune scimitar ornament kit (saradomin) +Rune scimitar ornament kit (zamorak) +Black pickaxe +Team cape zero +Team cape i +Team cape x +Cape of skulls +Golden chef's hat +Golden apron +Wooden shield (g) +Black full helm (t) +Black platebody (t) +Black platelegs (t) +Black plateskirt (t) +Black kiteshield (t) +Black full helm (g) +Black platebody (g) +Black platelegs (g) +Black plateskirt (g) +Black kiteshield (g) +Black shield (h1) +Black shield (h2) +Black shield (h3) +Black shield (h4) +Black shield (h5) +Black helm (h1) +Black helm (h2) +Black helm (h3) +Black helm (h4) +Black helm (h5) +Black platebody (h1) +Black platebody (h2) +Black platebody (h3) +Black platebody (h4) +Black platebody (h5) +Steel full helm (t) +Steel platebody (t) +Steel platelegs (t) +Steel plateskirt (t) +Steel kiteshield (t) +Steel full helm (g) +Steel platebody (g) +Steel platelegs (g) +Steel plateskirt (g) +Steel kiteshield (g) +Iron platebody (t) +Iron platelegs (t) +Iron plateskirt (t) +Iron kiteshield (t) +Iron full helm (t) +Iron platebody (g) +Iron platelegs (g) +Iron plateskirt (g) +Iron kiteshield (g) +Iron full helm (g) +Bronze platebody (t) +Bronze platelegs (t) +Bronze plateskirt (t) +Bronze kiteshield (t) +Bronze full helm (t) +Bronze platebody (g) +Bronze platelegs (g) +Bronze plateskirt (g) +Bronze kiteshield (g) +Bronze full helm (g) +Studded body (g) +Studded chaps (g) +Studded body (t) +Studded chaps (t) +Leather body (g) +Leather chaps (g) +Blue wizard hat (g) +Blue wizard robe (g) +Blue skirt (g) +Blue wizard hat (t) +Blue wizard robe (t) +Blue skirt (t) +Black wizard hat (g) +Black wizard robe (g) +Black skirt (g) +Black wizard hat (t) +Black wizard robe (t) +Black skirt (t) +Monk's robe top (g) +Monk's robe (g) +Saradomin robe top +Saradomin robe legs +Guthix robe top +Guthix robe legs +Zamorak robe top +Zamorak robe legs +Ancient robe top +Ancient robe legs +Armadyl robe top +Armadyl robe legs +Bandos robe top +Bandos robe legs +Bob's red shirt +Bob's green shirt +Bob's blue shirt +Bob's black shirt +Bob's purple shirt +Highwayman mask +Blue beret +Black beret +White beret +Red beret +A powdered wig +Beanie +Imp mask +Goblin mask +Sleeping cap +Flared trousers +Pantaloons +Black cane +Staff of bob the cat +Red elegant shirt +Red elegant blouse +Red elegant legs +Red elegant skirt +Green elegant shirt +Green elegant blouse +Green elegant legs +Green elegant skirt +Blue elegant shirt +Blue elegant blouse +Blue elegant legs +Blue elegant skirt +Amulet of magic (t) +Amulet of power (t) +Ham joint +Rain bow +Willow comp bow +Ranger boots +Wizard boots +Holy sandals +Climbing boots (g) +Spiked manacles +Adamant full helm (t) +Adamant platebody (t) +Adamant platelegs (t) +Adamant plateskirt (t) +Adamant kiteshield (t) +Adamant full helm (g) +Adamant platebody (g) +Adamant platelegs (g) +Adamant plateskirt (g) +Adamant kiteshield (g) +Adamant shield (h1) +Adamant shield (h2) +Adamant shield (h3) +Adamant shield (h4) +Adamant shield (h5) +Adamant helm (h1) +Adamant helm (h2) +Adamant helm (h3) +Adamant helm (h4) +Adamant helm (h5) +Adamant platebody (h1) +Adamant platebody (h2) +Adamant platebody (h3) +Adamant platebody (h4) +Adamant platebody (h5) +Mithril full helm (t) +Mithril platebody (t) +Mithril platelegs (t) +Mithril plateskirt (t) +Mithril kiteshield (t) +Mithril full helm (g) +Mithril platebody (g) +Mithril platelegs (g) +Mithril plateskirt (g) +Mithril kiteshield (g) +Green d'hide body (g) +Green d'hide body (t) +Green d'hide chaps (g) +Green d'hide chaps (t) +Saradomin mitre +Saradomin cloak +Guthix mitre +Guthix cloak +Zamorak mitre +Zamorak cloak +Ancient mitre +Ancient cloak +Ancient stole +Ancient crozier +Armadyl mitre +Armadyl cloak +Armadyl stole +Armadyl crozier +Bandos mitre +Bandos cloak +Bandos stole +Bandos crozier +Red boater +Green boater +Orange boater +Black boater +Blue boater +Pink boater +Purple boater +White boater +Red headband +Black headband +Brown headband +White headband +Blue headband +Gold headband +Pink headband +Green headband +Crier hat +Crier coat +Crier bell +Adamant cane +Arceuus banner +Piscarilius banner +Hosidius banner +Shayzien banner +Lovakengj banner +Cabbage round shield +Black unicorn mask +White unicorn mask +Cat mask +Penguin mask +Leprechaun hat +Black leprechaun hat +Wolf mask +Wolf cloak +Purple elegant shirt +Purple elegant blouse +Purple elegant legs +Purple elegant skirt +Black elegant shirt +White elegant blouse +Black elegant legs +White elegant skirt +Pink elegant shirt +Pink elegant blouse +Pink elegant legs +Pink elegant skirt +Gold elegant shirt +Gold elegant blouse +Gold elegant legs +Gold elegant skirt +Gnomish firelighter +Strength amulet (t) +Yew comp bow +Robin hood hat +Dragon boots ornament kit +Rune defender ornament kit +Tzhaar-ket-om ornament kit +Berserker necklace ornament kit +Rune full helm (t) +Rune platebody (t) +Rune platelegs (t) +Rune plateskirt (t) +Rune kiteshield (t) +Rune full helm (g) +Rune platebody (g) +Rune platelegs (g) +Rune plateskirt (g) +Rune kiteshield (g) +Zamorak full helm +Zamorak platebody +Zamorak platelegs +Zamorak plateskirt +Zamorak kiteshield +Guthix full helm +Guthix platebody +Guthix platelegs +Guthix plateskirt +Guthix kiteshield +Saradomin full helm +Saradomin platebody +Saradomin platelegs +Saradomin plateskirt +Saradomin kiteshield +Ancient full helm +Ancient platebody +Ancient platelegs +Ancient plateskirt +Ancient kiteshield +Armadyl full helm +Armadyl platebody +Armadyl platelegs +Armadyl plateskirt +Armadyl kiteshield +Bandos full helm +Bandos platebody +Bandos platelegs +Bandos plateskirt +Bandos kiteshield +Rune shield (h1) +Rune shield (h2) +Rune shield (h3) +Rune shield (h4) +Rune shield (h5) +Rune helm (h1) +Rune helm (h2) +Rune helm (h3) +Rune helm (h4) +Rune helm (h5) +Rune platebody (h1) +Rune platebody (h2) +Rune platebody (h3) +Rune platebody (h4) +Rune platebody (h5) +Saradomin coif +Saradomin d'hide body +Saradomin chaps +Saradomin bracers +Saradomin d'hide boots +Saradomin d'hide shield +Guthix coif +Guthix d'hide body +Guthix chaps +Guthix bracers +Guthix d'hide boots +Guthix d'hide shield +Zamorak coif +Zamorak d'hide body +Zamorak chaps +Zamorak bracers +Zamorak d'hide boots +Zamorak d'hide shield +Bandos coif +Bandos d'hide body +Bandos chaps +Bandos bracers +Bandos d'hide boots +Bandos d'hide shield +Armadyl coif +Armadyl d'hide body +Armadyl chaps +Armadyl bracers +Armadyl d'hide boots +Armadyl d'hide shield +Ancient coif +Ancient d'hide body +Ancient chaps +Ancient bracers +Ancient d'hide boots +Ancient d'hide shield +Red d'hide body (t) +Red d'hide chaps (t) +Red d'hide body (g) +Red d'hide chaps (g) +Blue d'hide body (t) +Blue d'hide chaps (t) +Blue d'hide body (g) +Blue d'hide chaps (g) +Enchanted hat +Enchanted top +Enchanted robe +Saradomin stole +Saradomin crozier +Guthix stole +Guthix crozier +Zamorak stole +Zamorak crozier +Zombie head +Cyclops head +Pirate's hat +Red cavalier +White cavalier +Navy cavalier +Tan cavalier +Dark cavalier +Black cavalier +Pith helmet +Explorer backpack +Thieving bag +Green dragon mask +Blue dragon mask +Red dragon mask +Black dragon mask +Nunchaku +Dual sai +Rune cane +Amulet of glory (t4) +Magic comp bow +Ring of 3rd age +Fury ornament kit +Dragon chainbody ornament kit +Dragon legs/skirt ornament kit +Dragon sq shield ornament kit +Dragon full helm ornament kit +Dragon scimitar ornament kit +Light infinity colour kit +Dark infinity colour kit +Holy wraps +Ranger gloves +Rangers' tunic +Rangers' tights +Black d'hide body (g) +Black d'hide chaps (g) +Black d'hide body (t) +Black d'hide chaps (t) +Royal crown +Royal sceptre +Royal gown top +Royal gown bottom +Musketeer hat +Musketeer tabard +Musketeer pants +Dark tuxedo jacket +Dark trousers +Dark tuxedo shoes +Dark tuxedo cuffs +Dark bow tie +Light tuxedo jacket +Light trousers +Light tuxedo shoes +Light tuxedo cuffs +Light bow tie +Arceuus scarf +Hosidius scarf +Piscarilius scarf +Shayzien scarf +Lovakengj scarf +Bronze dragon mask +Iron dragon mask +Steel dragon mask +Mithril dragon mask +Adamant dragon mask +Rune dragon mask +Katana +Dragon cane +Briefcase +Bucket helm +Blacksmith's helm +Deerstalker +Afro +Big pirate hat +Top hat +Monocle +Sagacious spectacles +Fremennik kilt +Giant boot +Uri's hat +Bloodhound +Armadyl godsword ornament kit +Bandos godsword ornament kit +Saradomin godsword ornament kit +Zamorak godsword ornament kit +Occult ornament kit +Torture ornament kit +Anguish ornament kit +Dragon defender ornament kit +Dragon kiteshield ornament kit +Dragon platebody ornament kit +Tormented ornament kit +Hood of darkness +Robe top of darkness +Robe bottom of darkness +Gloves of darkness +Boots of darkness +Samurai kasa +Samurai shirt +Samurai greaves +Samurai boots +Samurai gloves +Ankou mask +Ankou top +Ankou gloves +Ankou socks +Ankou's leggings +Mummy's head +Mummy's feet +Mummy's hands +Mummy's legs +Mummy's body +Shayzien hood +Hosidius hood +Arceuus hood +Piscarilius hood +Lovakengj hood +Lesser demon mask +Greater demon mask +Black demon mask +Jungle demon mask +Old demon mask +Left eye patch +Bowl wig +Ale of the gods +Obsidian cape (r) +Half moon spectacles +Fancy tiara +Lord marshal boots +Lord marshal gloves +Lord marshal trousers +Lord marshal top +Lord marshal cap +Akumu mask +Commander boots +Commander gloves +Commander trousers +Commander top +Commander cap +Apple parasol +Watermelon parasol +Lemon parasol +Strawberry parasol +Blueberry parasol +Grape parasol +Coconut parasol +Detective hat +Detective trenchcoat +Detective pants +2nd age range legs +2nd age range top +2nd age range coif +2nd age bow +2nd age mage top +2nd age mage bottom +2nd age mage mask +2nd age staff +First age robe top +First age robe bottom +Clue bag +Inventors tools +Elder knowledge +Octo +Dwarven blessing +Ring of luck +Holiday Mystery Box +Deathtouched dart +Ignecarus mask +Malygos mask +Blabberbeak +Helm of raedwald +Clue hunter garb +Clue hunter gloves +Clue hunter trousers +Clue hunter boots +Clue hunter cloak +First age tiara +First age amulet +First age cape +First age bracelet +First age ring +Blood dye +Third age dye +Ice dye +Shadow dye +3rd age range coif +3rd age range top +3rd age range legs +3rd age vambraces +3rd age robe top +3rd age robe +3rd age mage hat +3rd age amulet +3rd age plateskirt +3rd age platelegs +3rd age platebody +3rd age full helmet +3rd age kiteshield +Gilded platebody +Gilded platelegs +Gilded plateskirt +Gilded full helm +Gilded kiteshield +Gilded med helm +Gilded chainbody +Gilded sq shield +Gilded 2h sword +Gilded spear +Gilded hasta +3rd age longsword +3rd age wand +3rd age cloak +3rd age bow +Gilded scimitar +Gilded boots +Gilded coif +Gilded d'hide vambraces +Gilded d'hide body +Gilded d'hide chaps +Gilded pickaxe +Gilded axe +Gilded spade +Ring of nature +Lava dragon mask +3rd age pickaxe +3rd age axe +3rd age druidic robe bottoms +3rd age druidic robe top +3rd age druidic staff +3rd age druidic cloak +Bucket helm (g) +Ring of coins +Saradomin page 1 +Saradomin page 2 +Saradomin page 3 +Saradomin page 4 +Zamorak page 1 +Zamorak page 2 +Zamorak page 3 +Zamorak page 4 +Guthix page 1 +Guthix page 2 +Guthix page 3 +Guthix page 4 +Bandos page 1 +Bandos page 2 +Bandos page 3 +Bandos page 4 +Armadyl page 1 +Armadyl page 2 +Armadyl page 3 +Armadyl page 4 +Ancient page 1 +Ancient page 2 +Ancient page 3 +Ancient page 4 +Holy blessing +Unholy blessing +Peaceful blessing +War blessing +Honourable blessing +Ancient blessing +Nardah teleport +Mos le'harmless teleport +Mort'ton teleport +Feldip hills teleport +Lunar isle teleport +Digsite teleport +Piscatoris teleport +Pest control teleport +Tai bwo wannai teleport +Lumberyard teleport +Iorwerth camp teleport +Master scroll book (empty) +Red firelighter +Green firelighter +Blue firelighter +Purple firelighter +White firelighter +Charge dragonstone jewellery scroll +Purple sweets +Pet penance queen +Fighter hat +Ranger hat +Runner hat +Healer hat +Fighter torso +Penance skirt +Runner boots +Penance gloves +Granite body +Agility arena ticket +Pirate's hook +Brimhaven graceful hood +Brimhaven graceful top +Brimhaven graceful legs +Brimhaven graceful gloves +Brimhaven graceful boots +Brimhaven graceful cape +Red decorative full helm +Red decorative helm +Red decorative body +Red decorative legs +Red decorative skirt +Red decorative boots +Red decorative shield +Red decorative sword +White decorative full helm +White decorative helm +White decorative body +White decorative legs +White decorative skirt +White decorative boots +White decorative shield +White decorative sword +Gold decorative full helm +Gold decorative helm +Gold decorative body +Gold decorative legs +Gold decorative skirt +Gold decorative boots +Gold decorative shield +Gold decorative sword +Zamorak castlewars hood +Zamorak castlewars cloak +Saradomin castlewars hood +Saradomin castlewars cloak +Saradomin banner +Zamorak banner +Decorative magic hat +Decorative magic top +Decorative magic robe +Decorative ranged top +Decorative ranged legs +Decorative quiver +Saradomin halo +Zamorak halo +Guthix halo +Castle wars cape (beginner) +Castle wars cape (intermediate) +Castle wars cape (advanced) +Castle wars cape (expert) +Castle wars cape (legend) +Angler hat +Angler top +Angler waders +Angler boots +Smiths tunic +Smiths trousers +Smiths boots +Smiths gloves +Colossal blade +Double ammo mould +Kovac's grog +Smithing catalyst +Ore pack (Giant's Foundry) +Grand seed pod +Gnome scarf +Gnome goggles +Mint cake +Abyssal protector +Abyssal pearls +Catalytic talisman +Abyssal needle +Abyssal green dye +Abyssal blue dye +Abyssal red dye +Hat of the eye +Robe top of the eye +Robe bottoms of the eye +Boots of the eye +Ring of the elements +Abyssal lantern +Guardian's eye +Intricate pouch +Lost bag +Tarnished locket +Hallowed mark +Hallowed token +Hallowed grapple +Hallowed focus +Hallowed symbol +Hallowed hammer +Hallowed ring +Dark dye +Dark acorn +Strange old lockpick +Ring of endurance (uncharged) +Mysterious page 1 +Mysterious page 2 +Mysterious page 3 +Mysterious page 4 +Mysterious page 5 +Deadman's chest +Deadman's legs +Deadman's cape +Armadyl halo +Bandos halo +Seren halo +Ancient halo +Brassica halo +Golden armadyl special attack +Golden bandos special attack +Golden saradomin special attack +Golden zamorak special attack +Victor's cape (1) +Victor's cape (10) +Victor's cape (50) +Victor's cape (100) +Victor's cape (500) +Victor's cape (1000) +Granite clamp +Ornate maul handle +Steam staff upgrade kit +Lava staff upgrade kit +Dragon pickaxe upgrade kit +Ward upgrade kit +Green dark bow paint +Yellow dark bow paint +White dark bow paint +Blue dark bow paint +Volcanic whip mix +Frozen whip mix +Guthixian icon +Swift blade +Beginner wand +Apprentice wand +Teacher wand +Master wand +Infinity hat +Infinity top +Infinity bottoms +Infinity boots +Infinity gloves +Mage's book +Builders supply crate +Carpenter's helmet +Carpenter's shirt +Carpenter's trousers +Carpenter's boots +Amy's saw +Plank sack +Hosidius blueprints +Void knight mace +Void knight top +Void knight robe +Void knight gloves +Void mage helm +Void melee helm +Void ranger helm +Void seal(8) +Elite void top +Elite void robe +Rogue mask +Rogue top +Rogue trousers +Rogue boots +Rogue gloves +Amulet of the damned (full) +Flamtaer bag +Fine cloth +Bronze locks +Steel locks +Black locks +Silver locks +Gold locks +Zealot's helm +Zealot's robe top +Zealot's robe bottom +Zealot's boots +Tree wizards' journal +Bloody notes +Gary +Lil' creator +Red soul cape +Ectoplasmator +Lumberjack hat +Lumberjack top +Lumberjack legs +Lumberjack boots +Farmer's strawhat +Farmer's jacket +Farmer's boro trousers +Farmer's boots +Seed box +Gricoller's can +Herb sack +Blue naval shirt +Blue tricorn hat +Blue navy slacks +Green naval shirt +Green tricorn hat +Green navy slacks +Red naval shirt +Red tricorn hat +Red navy slacks +Brown naval shirt +Brown tricorn hat +Brown navy slacks +Black naval shirt +Black tricorn hat +Black navy slacks +Purple naval shirt +Purple tricorn hat +Purple navy slacks +Grey naval shirt +Grey tricorn hat +Grey navy slacks +Golden naval shirt +Golden tricorn hat +Golden navy slacks +Cutthroat flag +Gilded smile flag +Bronze fist flag +Lucky shot flag +Treasure flag +Phasmatys flag +The stuff +Red rum (trouble brewing) +Blue rum (trouble brewing) +Jolly roger cape +Ash covered tome +Large water container +Volcanic mine teleport +Dragon pickaxe (broken) +Master runecrafter hat +Master runecrafter robe +Master runecrafter skirt +Master runecrafter boots +Elder thread +Elder talisman +Magic crate +Lychee seed +Avocado seed +Mango seed +Monkey egg +Marimbo statue +Monkey dye +Banana enchantment scroll +Rumble token +Big banana +Monkey crate +Gorilla rumble greegree +Beginner rumble greegree +Intermediate rumble greegree +Ninja rumble greegree +Expert ninja rumble greegree +Elder rumble greegree +Fishing hat +Fishing jacket +Fishing waders +Fishing boots +Contest rod +Beginner's tackle box +Basic tackle box +Standard tackle box +Professional tackle box +Champion's tackle box +Golden fishing trophy +Inferno adze +Flame gloves +Ring of fire +Phoenix eggling +Rune spikeshield +Rune berserker shield +Adamant spikeshield +Adamant berserker shield +Guthix engram +Fist of guthix token +Diviner's headwear +Diviner's robe +Diviner's legwear +Diviner's handwear +Diviner's footwear +Stealing creation token +Fletcher's gloves +Fletcher's boots +Fletcher's top +Fletcher's hat +Fletcher's legs +Inventors' helmet +Inventors' torso +Inventors' legs +Inventors' gloves +Inventors' boots +Inventors' backpack +Materials bag +A stylish hat (male, yellow) +Shirt (male, yellow) +Leggings (yellow) +A stylish hat (male, maroon) +Shirt (male, maroon) +Leggings (maroon) +A stylish hat (male, green) +Shirt (male, green) +Leggings (green) +A stylish hat (female, yellow) +Shirt (female, yellow) +Skirt (yellow) +A stylish hat (female, maroon) +Shirt (female, maroon) +Skirt (maroon) +A stylish hat (female, green) +Shirt (female, green) +Skirt (green) +Shoes (male, shoes) +Shoes (male, boots) +Shoes (female, straps) +Shoes (female, flats) +Giant's hand +Acrobat set +Clown set +Ringmaster set +Golden tench +Pearl fishing rod +Pearl fly fishing rod +Pearl barbarian rod +Heron +Rock golem +Beaver +Baby chinchompa +Giant squirrel +Rocky +Rift guardian +Herbi +Chompy chick +Barronite mace +Barronite head +Barronite handle +Barronite guard +Ancient globe +Ancient ledger +Ancient astroscope +Ancient treatise +Ancient carcanet +Imcando hammer +Chompy bird hat (ogre bowman) +Chompy bird hat (bowman) +Chompy bird hat (ogre yeoman) +Chompy bird hat (yeoman) +Chompy bird hat (ogre marksman) +Chompy bird hat (marksman) +Chompy bird hat (ogre woodsman) +Chompy bird hat (woodsman) +Chompy bird hat (ogre forester) +Chompy bird hat (forester) +Chompy bird hat (ogre bowmaster) +Chompy bird hat (bowmaster) +Chompy bird hat (ogre expert) +Chompy bird hat (expert) +Chompy bird hat (ogre dragon archer) +Chompy bird hat (dragon archer) +Chompy bird hat (expert ogre dragon archer) +Chompy bird hat (expert dragon archer) +Tea flask +Plain satchel +Green satchel +Red satchel +Black satchel +Gold satchel +Rune satchel +Bronze defender +Iron defender +Steel defender +Black defender +Mithril defender +Adamant defender +Rune defender +Dragon defender +Fox whistle +Golden pheasant egg +Forestry hat +Forestry top +Forestry legs +Forestry boots +Twitcher's gloves +Funky shaped log +Log basket +Log brace +Clothes pouch blueprint +Cape pouch +Felling axe handle +Pheasant hat +Pheasant legs +Pheasant boots +Pheasant cape +Petal garland +Sturdy beehive parts +Scribbled note +Partial note +Ancient note +Ancient writings +Experimental note +Paragraph of text +Musty smelling note +Hastily scrawled note +Old writing +Short note +Zenyte shard +Light frame +Heavy frame +Ballista limbs +Monkey tail +Ballista spring +Karamjan monkey +Kruk jr +Maniacal monkey +Princely monkey +Skeleton monkey +Zombie monkey +Coal bag +Gem bag +Prospector helmet +Prospector jacket +Prospector legs +Prospector boots +Mark of grace +Graceful hood +Graceful cape +Graceful top +Graceful legs +Graceful gloves +Graceful boots +Scruffy +Harry +Skipper +Celestial ring (uncharged) +Star fragment +Chaotic rapier +Chaotic longsword +Chaotic maul +Chaotic staff +Chaotic crossbow +Offhand Chaotic rapier +Offhand Chaotic longsword +Offhand Chaotic crossbow +Scroll of life +Scroll of efficiency +Scroll of cleansing +Scroll of dexterity +Scroll of teleportation +Scroll of farming +Scroll of proficiency +Scroll of mystery +Scroll of longevity +Scroll of the hunt +Farseer kiteshield +Chaotic remnant +Frostbite +Gorajan shards +Amulet of zealots +Herbicide +Gorajan warrior helmet +Gorajan warrior top +Gorajan warrior legs +Gorajan warrior gloves +Gorajan warrior boots +Gorajan archer helmet +Gorajan archer top +Gorajan archer legs +Gorajan archer gloves +Gorajan archer boots +Gorajan occult helmet +Gorajan occult top +Gorajan occult legs +Gorajan occult gloves +Gorajan occult boots +Arcane blast necklace +Farsight snapshot necklace +Brawler's hook necklace +Daemonheim agility pass +Dungeoneering dye +Gorajan bonecrusher (u) +Grimy guam leaf +Guam seed +Grimy marrentill +Marrentill seed +Grimy tarromin +Tarromin seed +Grimy harralander +Harralander seed +Grimy ranarr weed +Ranarr seed +Grimy toadflax +Toadflax seed +Grimy irit leaf +Irit seed +Grimy avantoe +Avantoe seed +Grimy kwuarm +Kwuarm seed +Grimy snapdragon +Snapdragon seed +Grimy cadantine +Cadantine seed +Grimy lantadyme +Lantadyme seed +Grimy dwarf weed +Dwarf weed seed +Grimy torstol +Torstol seed +Athelas +Spirit weed +Spirit weed seed +Grimy korulsi +Acorn +Oak logs +Oak roots +Willow seed +Willow logs +Willow roots +Maple seed +Maple logs +Maple roots +Yew seed +Yew logs +Yew roots +Magic seed +Magic logs +Magic roots +Potato +Potato seed +Onion +Onion seed +Cabbage +Cabbage seed +Tomato +Tomato seed +Sweetcorn +Sweetcorn seed +Strawberry +Strawberry seed +Watermelon +Watermelon seed +Snape grass +Snape grass seed +Cooking apple +Apple tree seed +Banana +Banana tree seed +Orange +Orange tree seed +Curry leaf +Curry tree seed +Pineapple +Pineapple seed +Papaya fruit +Papaya tree seed +Coconut +Palm tree seed +Dragonfruit +Dragonfruit tree seed +Blood orange +Blood orange seed +Barley +Barley seed +Hammerstone hops +Hammerstone seed +Asgarnian hops +Asgarnian seed +Jute fibre +Jute seed +Yanillian hops +Yanillian seed +Krandorian hops +Krandorian seed +Wildblood hops +Wildblood seed +Marigolds +Marigold seed +Redberries +Redberry seed +Rosemary +Rosemary seed +Cadava berries +Cadavaberry seed +Giant seaweed +Seaweed spore +Nasturtiums +Nasturtium seed +Woad leaf +Woad seed +Limpwurt root +Limpwurt seed +Teak seed +Teak logs +Grapes +Saltpetre +Grape seed +Dwellberries +Dwellberry seed +Jangerberries +Jangerberry seed +Mushroom +Mushroom spore +Cactus spine +Cactus seed +Mahogany seed +Mahogany logs +White lily +White lily seed +White berries +Whiteberry seed +Cave nightshade +Belladonna seed +Potato cactus +Potato cactus seed +Hespori seed +Poison ivy berries +Poison ivy seed +Calquat fruit +Calquat tree seed +Crystal shard +Crystal acorn +Spirit seed +Celastrus bark +Celastrus seed +Redwood tree seed +Redwood logs +Tombshroom +Morchella mushroom +Morchella mushroom spore +Avocado +Mango +Lychee +Advax berry +Advax berry seed +Herbal zygomite spores +Barky zygomite spores +Fruity zygomite spores +Toxic zygomite spores +Master farmer hat +Master farmer jacket +Master farmer pants +Master farmer gloves +Master farmer boots +Plopper +Shiny mango +Fungo +Cooked meat +Sinew +Shrimps +Cooked chicken +Anchovies +Sardine +Herring +Mackerel +Trout +Cod +Pike +Salmon +Tuna +Cooked karambwan +Jug of wine +Cave eel +Lobster +Cooked jubbly +Bass +Swordfish +Monkfish +Wine of zamorak +Shark +Sea turtle +Anglerfish +Dark crab +Manta ray +Rocktail +Turkey +Christmas cake +Bird house +Oak bird house +Willow bird house +Teak bird house +Maple bird house +Mahogany bird house +Yew bird house +Magic bird house +Redwood bird house +Elder bird house +Serpentine helm (uncharged) +Toxic staff (uncharged) +Uncharged toxic trident +Green d'hide vambraces +Green d'hide chaps +Green d'hide shield +Green d'hide body +Blue d'hide vambraces +Blue d'hide chaps +Blue d'hide shield +Blue d'hide body +Red d'hide vambraces +Red d'hide chaps +Red d'hide shield +Red d'hide body +Black d'hide vambraces +Black d'hide chaps +Black d'hide shield +Black d'hide body +Opal +Jade +Red topaz +Sapphire +Emerald +Ruby +Diamond +Dragonstone +Zenyte +Molten glass +Beer glass +Empty candle lantern +Empty oil lamp +Vial +Fishbowl +Unpowered orb +Lantern lens +Empty light orb +Heat res. vial +Gold ring +Gold necklace +Gold bracelet +Gold amulet (u) +Gold amulet +Sapphire ring +Sapphire necklace +Sapphire bracelet +Sapphire amulet (u) +Sapphire amulet +Emerald ring +Emerald necklace +Emerald bracelet +Emerald amulet (u) +Emerald amulet +Ruby ring +Ruby necklace +Ruby bracelet +Ruby amulet (u) +Ruby amulet +Diamond ring +Diamond necklace +Diamond bracelet +Diamond amulet (u) +Diamond amulet +Dragonstone ring +Dragon necklace +Dragonstone bracelet +Dragonstone amulet (u) +Dragonstone amulet +Onyx ring +Onyx necklace +Onyx bracelet +Onyx amulet (u) +Onyx amulet +Zenyte ring +Zenyte necklace +Zenyte bracelet +Zenyte amulet (u) +Zenyte amulet +Leather gloves +Leather boots +Leather cowl +Leather vambraces +Leather body +Leather chaps +Hardleather body +Coif +Hard leather shield +Studded body +Studded chaps +Drift net +Snakeskin boots +Snakeskin vambraces +Snakeskin bandana +Snakeskin chaps +Snakeskin body +Snakeskin shield +Xerician hat +Xerician robe +Xerician top +Splitbark gauntlets +Splitbark boots +Splitbark helm +Splitbark legs +Splitbark body +Neitiznot shield +Water battlestaff +Earth battlestaff +Fire battlestaff +Air battlestaff +Ball of wool +Bow string +Crossbow string +Clockwork +Amethyst bolt tips +Amethyst arrowtips +Amethyst javelin heads +Amethyst dart tip +Strung rabbit foot +Obsidian javelin heads +Opal ring +Opal necklace +Opal bracelet +Opal amulet (u) +Opal amulet +Jade ring +Jade necklace +Jade bracelet +Jade amulet (u) +Jade amulet +Topaz ring +Topaz necklace +Topaz bracelet +Topaz amulet (u) +Topaz amulet +Unstrung symbol +Unblessed symbol +Unstrung emblem +Unpowered symbol +Silver sickle +Tiara +Leather +Hard leather +Green dragon leather +Blue dragon leather +Red dragon leather +Black dragon leather +Royal dragon leather +Infernal slayer helmet +Royal dragonhide body +Royal dragonhide boots +Royal dragonhide chaps +Royal dragonhide coif +Royal dragonhide vambraces +Carapace helm +Carapace torso +Carapace legs +Carapace boots +Carapace gloves +Carapace shield +Guam leaf +Marrentill +Tarromin +Harralander +Ranarr weed +Toadflax +Irit leaf +Avantoe +Kwuarm +Snapdragon +Cadantine +Lantadyme +Dwarf weed +Torstol +Korulsi +Guam potion (unf) +Marrentill potion (unf) +Tarromin potion (unf) +Harralander potion (unf) +Ranarr potion (unf) +Toadflax potion (unf) +Irit potion (unf) +Avantoe potion (unf) +Kwuarm potion (unf) +Snapdragon potion (unf) +Cadantine potion (unf) +Lantadyme potion (unf) +Dwarf weed potion (unf) +Torstol potion (unf) +Cadantine blood potion (unf) +Attack potion(3) +Antipoison(3) +Strength potion(3) +Serum 207 (3) +Guthix rest(3) +Compost potion(3) +Restore potion(3) +Guthix balance(3) +Energy potion(3) +Defence potion(3) +Agility potion(3) +Combat potion(3) +Prayer potion(3) +Super attack(3) +Superantipoison(3) +Fishing potion(3) +Super energy(3) +Hunter potion(3) +Super strength(3) +Weapon poison +Super restore(3) +Super defence(3) +Antidote+(4) +Antifire potion(3) +Ranging potion(3) +Weapon poison(+) +Magic potion(3) +Stamina potion(4) +Zamorak brew(3) +Antidote++(4) +Bastion potion(3) +Battlemage potion(3) +Saradomin brew(3) +Weapon poison(++) +Extended antifire(4) +Ancient brew(3) +Anti-venom(4) +Menaphite remedy(3) +Super combat potion(4) +Forgotten brew(4) +Super antifire potion(4) +Anti-venom+(4) +Extended super antifire(4) +Heat res. brew +Heat res. restore +Dragon's fury +Sanfew serum(4) +Enhanced saradomin brew +Enhanced super restore +Enhanced stamina potion +Enhanced divine water +Divination potion +Unicorn horn dust +Chocolate dust +Kebbit teeth dust +Crushed nest +Goat horn dust +Silver dust +Crushed superior dragon bones +Dragon scale dust +Lava scale shard +Athelas paste +Nihil dust +Guam tar +Marrentill tar +Tarromin tar +Harralander tar +Neem oil +Deathly toxic potion +Attack mix(2) +Antipoison mix(2) +Relicym's mix(2) +Strength mix(2) +Restore mix(2) +Energy mix(2) +Defence mix(2) +Agility mix(2) +Combat mix(2) +Prayer mix(2) +Superattack mix(2) +Anti-poison supermix(2) +Fishing mix(2) +Super energy mix(2) +Hunting mix(2) +Super str. mix(2) +Magic essence mix(2) +Super restore mix(2) +Super def. mix(2) +Antidote+ mix(2) +Antifire mix(2) +Ranging mix(2) +Magic mix(2) +Zamorak mix(2) +Stamina mix(2) +Extended antifire mix(2) +Ancient mix(2) +Super antifire mix(2) +Extended super antifire mix(2) +Bronze axe +Bronze dagger +Bronze mace +Bronze med helm +Bronze bolts (unf) +Bronze nails +Bronze sword +Bronze wire +Bronze dart tip +Bronze scimitar +Bronze hasta +Bronze arrowtips +Bronze spear +Bronze javelin heads +Bronze longsword +Bronze limbs +Bronze knife +Bronze full helm +Bronze sq shield +Bronze warhammer +Bronze battleaxe +Bronze chainbody +Bronze kiteshield +Bronze claws +Bronze 2h sword +Bronze platelegs +Bronze plateskirt +Bronze platebody +Iron dagger +Iron axe +Iron spit +Iron mace +Iron bolts (unf) +Iron med helm +Iron nails +Iron dart tip +Iron sword +Iron arrowtips +Iron scimitar +Iron hasta +Iron spear +Iron longsword +Iron javelin heads +Iron full helm +Iron knife +Iron limbs +Iron sq shield +Iron warhammer +Iron battleaxe +Oil lantern frame +Iron chainbody +Iron kiteshield +Iron claws +Iron 2h sword +Iron plateskirt +Iron platelegs +Iron platebody +Silver stake +Silver bolts (unf) +Steel dagger +Steel axe +Steel mace +Steel med helm +Steel bolts (unf) +Steel dart tip +Steel nails +Steel sword +Cannonball +Steel scimitar +Steel arrowtips +Steel hasta +Steel spear +Steel limbs +Steel studs +Steel longsword +Steel javelin heads +Steel knife +Steel full helm +Steel sq shield +Steel warhammer +Steel battleaxe +Steel chainbody +Steel kiteshield +Steel claws +Steel 2h sword +Steel platelegs +Steel plateskirt +Steel platebody +Bullseye lantern (unf) +Gold helmet +Gold bowl +Mithril dagger +Mithril axe +Mithril mace +Mithril med helm +Mithril bolts (unf) +Mithril sword +Mithril dart tip +Mithril nails +Mithril arrowtips +Mithril scimitar +Mithril hasta +Mithril spear +Mithril longsword +Mithril javelin heads +Mithril limbs +Mithril full helm +Mithril knife +Mithril sq shield +Mith grapple tip +Mithril warhammer +Mithril battleaxe +Mithril chainbody +Mithril kiteshield +Mithril claws +Mithril 2h sword +Mithril plateskirt +Mithril platelegs +Mithril platebody +Adamant dagger +Adamant axe +Adamant mace +Adamant bolts(unf) +Adamant med helm +Adamant dart tip +Adamant sword +Adamantite nails +Adamant arrowtips +Adamant scimitar +Adamant hasta +Adamant spear +Adamantite limbs +Adamant longsword +Adamant javelin heads +Adamant full helm +Adamant knife +Adamant sq shield +Adamant warhammer +Adamant battleaxe +Adamant chainbody +Adamant kiteshield +Adamant claws +Adamant 2h sword +Adamant plateskirt +Adamant platelegs +Adamant platebody +Rune dagger +Rune axe +Rune mace +Runite bolts (unf) +Rune med helm +Rune sword +Rune nails +Rune dart tip +Rune arrowtips +Rune scimitar +Rune hasta +Rune spear +Rune longsword +Rune javelin heads +Runite limbs +Rune knife +Rune full helm +Rune sq shield +Rune warhammer +Rune battleaxe +Rune chainbody +Rune kiteshield +Rune claws +Rune platebody +Rune plateskirt +Rune platelegs +Rune 2h sword +Dwarven greataxe +Dwarven knife +Dwarven gauntlets +Dwarven greathammer +Dwarven pickaxe +Dwarven warhammer +Dwarven full helm +Dwarven platebody +Dwarven platelegs +Dwarven gloves +Dwarven boots +Sun-god axe head +Simple kibble +Delicious kibble +Extraordinary kibble +Arceuus graceful hood +Piscarilius graceful hood +Lovakengj graceful hood +Shayzien graceful hood +Hosidius graceful hood +Kourend graceful hood +Dark graceful hood +Trailblazer graceful hood +Spooky graceful hood +Arceuus graceful top +Piscarilius graceful top +Lovakengj graceful top +Shayzien graceful top +Hosidius graceful top +Kourend graceful top +Dark graceful top +Trailblazer graceful top +Spooky graceful top +Arceuus graceful legs +Piscarilius graceful legs +Lovakengj graceful legs +Shayzien graceful legs +Hosidius graceful legs +Kourend graceful legs +Dark graceful legs +Trailblazer graceful legs +Spooky graceful legs +Arceuus graceful boots +Piscarilius graceful boots +Lovakengj graceful boots +Shayzien graceful boots +Hosidius graceful boots +Kourend graceful boots +Dark graceful boots +Trailblazer graceful boots +Silverhawk boots +Spooky graceful boots +Arceuus graceful gloves +Piscarilius graceful gloves +Lovakengj graceful gloves +Shayzien graceful gloves +Hosidius graceful gloves +Kourend graceful gloves +Dark graceful gloves +Trailblazer graceful gloves +Spooky graceful gloves +Arceuus graceful cape +Piscarilius graceful cape +Lovakengj graceful cape +Shayzien graceful cape +Hosidius graceful cape +Kourend graceful cape +Dark graceful cape +Trailblazer graceful cape +Spooky graceful cape +Shortbow (u) +Shortbow +Longbow (u) +Longbow +Oak shortbow (u) +Oak shortbow +Oak longbow (u) +Oak longbow +Willow shortbow (u) +Willow shortbow +Willow longbow (u) +Willow longbow +Maple shortbow (u) +Maple shortbow +Maple longbow (u) +Maple longbow +Yew shortbow (u) +Yew shortbow +Yew longbow (u) +Yew longbow +Magic shortbow (u) +Magic shortbow +Magic longbow (u) +Magic longbow +Elder bow(u) +Elder bow +Toxic blowpipe (empty) +Hellfire bow +Arrow shaft +Javelin shaft +Ogre arrow shaft +Battlestaff +Oak shield +Willow shield +Maple shield +Yew shield +Magic shield +Redwood shield +Headless arrow +Bronze arrow +Wolfbone arrowtips +Flighted ogre arrow +Ogre arrow +Iron arrow +Steel arrow +Mithril arrow +Adamant arrow +Rune arrow +Amethyst arrow +Dragon arrow +Hellfire arrow +Bronze bolts +Iron bolts +Silver bolts +Steel bolts +Mithril bolts +Adamant bolts +Runite bolts +Dragon bolts +Opal bolt tips +Jade bolt tips +Pearl bolt tips +Topaz bolt tips +Sapphire bolt tips +Emerald bolt tips +Ruby bolt tips +Diamond bolt tips +Dragonstone bolt tips +Onyx bolt tips +Opal dragon bolts +Jade dragon bolts +Pearl dragon bolts +Topaz dragon bolts +Sapphire dragon bolts +Emerald dragon bolts +Ruby dragon bolts +Diamond dragon bolts +Dragonstone dragon bolts +Onyx dragon bolts +Opal bolts +Pearl bolts +Topaz bolts +Sapphire bolts +Emerald bolts +Ruby bolts +Diamond bolts +Dragonstone bolts +Onyx bolts +Bronze javelin +Iron javelin +Steel javelin +Mithril javelin +Adamant javelin +Rune javelin +Amethyst javelin +Dragon javelin +Obsidian javelin +Bronze dart +Iron dart +Steel dart +Mithril dart +Adamant dart +Rune dart +Amethyst dart +Dragon dart +Wooden stock +Oak stock +Willow stock +Teak stock +Maple stock +Mahogany stock +Yew stock +Magic stock +Bronze crossbow (u) +Blurite crossbow (u) +Iron crossbow (u) +Steel crossbow (u) +Mithril crossbow (u) +Adamant crossbow (u) +Runite crossbow (u) +Dragon crossbow (u) +Bronze crossbow +Blurite crossbow +Iron crossbow +Steel crossbow +Mithril crossbow +Adamant crossbow +Rune crossbow +Dragon crossbow +Incomplete heavy ballista +Incomplete light ballista +Unstrung heavy ballista +Unstrung light ballista +Heavy ballista +Light ballista +Broad arrows +Broad bolts +Amethyst broad bolts +Mining gloves +Superior mining gloves +Expert mining gloves +Golden nugget +Unidentified minerals +Big swordfish +Big shark +Big bass +Farmer's shirt +Pharaoh's sceptre +Kyatt hat +Kyatt top +Kyatt legs +Spotted cape +Spottier cape +Gloves of silence +Small pouch +Medium pouch +Large pouch +Giant pouch +Abyssal pouch +Elder pouch +Crystal pickaxe +Crystal axe +Crystal harpoon +Superior bonecrusher +Superior dwarf multicannon +Superior inferno adze +Mecha mortar +Quick trap +Arcane harvester +Portable tanner +Drygore saw +Clue upgrader +Dwarven toolkit +Mecha rod +Master hammer and chisel +Abyssal amulet +RoboFlappy +Chincannon +Wisp-buster +Divine hand +Drygore axe +Moonlight mutator +Webshooter +Cogsworth +Cache portent +Graceful portent +Rogues portent +Dungeon portent +Lucky portent +Rebirth portent +Spiritual mining portent +Pacifist hunting portent +Pale energy +Boon of flickering energy +Flickering energy +Boon of bright energy +Bright energy +Boon of glowing energy +Glowing energy +Boon of sparkling energy +Sparkling energy +Boon of gleaming energy +Gleaming energy +Boon of vibrant energy +Vibrant energy +Boon of lustrous energy +Lustrous energy +Boon of elder energy +Elder energy +Boon of brilliant energy +Brilliant energy +Boon of radiant energy +Radiant energy +Boon of luminous energy +Luminous energy +Boon of incandescent energy +Incandescent energy +Boon of ancient energy +Ancient energy +Divine egg +Jar of memories +Doopy +Camo top +Camo bottoms +Camo helmet +Lederhosen top +Lederhosen shorts +Lederhosen hat +Zombie shirt +Zombie trousers +Zombie mask +Zombie gloves +Zombie boots +Mime mask +Mime top +Mime legs +Mime gloves +Mime boots +Frog token +Stale baguette +Beekeeper's hat +Beekeeper's top +Beekeeper's legs +Beekeeper's gloves +Beekeeper's boots +Shayzien gloves (1) +Shayzien boots (1) +Shayzien helm (1) +Shayzien greaves (1) +Shayzien platebody (1) +Shayzien gloves (2) +Shayzien boots (2) +Shayzien helm (2) +Shayzien greaves (2) +Shayzien platebody (2) +Shayzien gloves (3) +Shayzien boots (3) +Shayzien helm (3) +Shayzien greaves (3) +Shayzien platebody (3) +Shayzien gloves (4) +Shayzien boots (4) +Shayzien helm (4) +Shayzien greaves (4) +Shayzien platebody (4) +Shayzien gloves (5) +Shayzien boots (5) +Shayzien helm (5) +Shayzien greaves (5) +Shayzien body (5) +Dragon warhammer +Long bone +Curved bone +Ecumenical key +Dark totem base +Dark totem middle +Dark totem top +Chewed bones +Dragon full helm +Shield left half +Dragon metal slice +Dragon metal lump +Dragon spear +Amulet of eternal glory +Shaman mask +Evil chicken head +Evil chicken wings +Evil chicken legs +Evil chicken feet +Right skull half +Left skull half +Top of sceptre +Bottom of sceptre +Mossy key +Giant key +Xeric's talisman (inert) +Mask of ranul +Elven signet +Crystal grail +Enhanced crystal teleport seed +Dragonstone full helm +Dragonstone platebody +Dragonstone platelegs +Dragonstone gauntlets +Dragonstone boots +Ivy seed +Merfolk trident +Orange egg sac +Dark Temple key +Elite black knight sword +Elite black knight kiteshield +Elite black knight helm +Elite black knight platebody +Elite black knight platelegs +Karamja gloves 1 +Karamja gloves 2 +Karamja gloves 3 +Karamja gloves 4 +Ardougne cloak 1 +Ardougne cloak 2 +Ardougne cloak 3 +Ardougne cloak 4 +Falador shield 1 +Falador shield 2 +Falador shield 3 +Falador shield 4 +Fremennik sea boots 1 +Fremennik sea boots 2 +Fremennik sea boots 3 +Fremennik sea boots 4 +Kandarin headgear 1 +Kandarin headgear 2 +Kandarin headgear 3 +Kandarin headgear 4 +Desert amulet 1 +Desert amulet 2 +Desert amulet 3 +Desert amulet 4 +Explorer's ring 1 +Explorer's ring 2 +Explorer's ring 3 +Explorer's ring 4 +Morytania legs 1 +Morytania legs 2 +Morytania legs 3 +Morytania legs 4 +Varrock armour 1 +Varrock armour 2 +Varrock armour 3 +Varrock armour 4 +Wilderness sword 1 +Wilderness sword 2 +Wilderness sword 3 +Wilderness sword 4 +Western banner 1 +Western banner 2 +Western banner 3 +Western banner 4 +Rada's blessing 1 +Rada's blessing 2 +Rada's blessing 3 +Rada's blessing 4 +Goblin paint cannon +Green banner +Spinning plate +Brown toy horsey +White toy horsey +Black toy horsey +Grey toy horsey +Beach boxing gloves +Beach boxing gloves +Tiger toy +Lion toy +Snow leopard toy +Amur leopard toy +Holy handegg +Peaceful handegg +Chaotic handegg +Rainbow scarf +Diango's claws +Hornwood helm +Hand fan +Mask of balance +Druidic wreath +Disk of returning +Tradeable Mystery Box +Untradeable Mystery Box +Equippable mystery box +Pet Mystery Box +Mining hood +Mining cape(t) +Smithing hood +Smithing cape(t) +Woodcutting hood +Woodcut. cape(t) +Firemaking hood +Firemaking cape(t) +Fishing hood +Fishing cape(t) +Agility hood +Agility cape(t) +Cooking hood +Cooking cape(t) +Crafting hood +Crafting cape(t) +Prayer hood +Prayer cape(t) +Fletching hood +Fletching cape(t) +Runecraft hood +Runecraft cape(t) +Thieving hood +Thieving cape(t) +Farming hood +Farming cape(t) +Herblore hood +Herblore cape(t) +Hunter hood +Hunter cape(t) +Construct. hood +Construct. cape(t) +Magic hood +Magic cape(t) +Attack hood +Attack cape(t) +Strength hood +Strength cape(t) +Defence hood +Defence cape(t) +Hitpoints hood +Hitpoints cape(t) +Ranging hood +Ranging cape(t) +Slayer hood +Slayer cape(t) +Dungeoneering hood +Dungeoneering cape(t) +Divination hood +Divination cape(t) +Quest point hood +Quest point cape +Quest point cape (t) +Achievement diary hood +Achievement diary cape +Achievement diary cape (t) +Music hood +Music cape +Music cape(t) +Max hood +Max cape +Ardougne max hood +Ardougne max cape +Infernal max hood +Infernal max cape +Assembler max hood +Assembler max cape +Masori assembler max hood +Masori assembler max cape +Imbued guthix max hood +Imbued guthix max cape +Imbued saradomin max hood +Imbued saradomin max cape +Imbued zamorak max hood +Imbued zamorak max cape +Mythical max hood +Mythical max cape +Mining master cape +Smithing master cape +Woodcutting master cape +Firemaking master cape +Fishing master cape +Agility master cape +Cooking master cape +Crafting master cape +Prayer master cape +Fletching master cape +Runecraft master cape +Thieving master cape +Farming master cape +Herblore master cape +Hunter master cape +Construction master cape +Magic master cape +Attack master cape +Strength master cape +Defence master cape +Hitpoints master cape +Ranged master cape +Slayer master cape +Dungeoneering master cape +Invention master cape +Divination master cape +Master quest cape +Combatant's cape +Artisan's cape +Support cape +Gatherer's cape +Fire max hood +Fire max cape +Mythical cape +Imbued saradomin cape +Imbued guthix cape +Imbued zamorak cape +Saradomin cape +Guthix cape +Zamorak cape +Blue soul cape +Cape of legends +Icthlarin's hood (tier 5) +Helm of neitiznot +Anti-dragon shield +Goldsmith gauntlets +Cooking gauntlets +Magic secateurs +Barrows gloves +Dragon gloves +Rune gloves +Adamant gloves +Mithril gloves +Black gloves +Steel gloves +Iron gloves +Bronze gloves +Hardleather gloves +Doug +Zippy +Shelldon +Remy +Lil Lamb +Klik +Zak +Hammy +Takon +Obis +Peky +Wilvus +Ishi +Sandy +Steve +Baby duckling +Mr. E +Nexterminator +Brain lee +Balloon cat +Baby yaga house +Herbert +Baby impling jar +Young impling jar +Gourmet impling jar +Earth impling jar +Essence impling jar +Eclectic impling jar +Nature impling jar +Magpie impling jar +Ninja impling jar +Dragon impling jar +Lucky impling jar +Crystal impling jar +Chimpling jar +Infernal impling jar +Eternal impling jar +Mystery impling jar +Drygore rapier (blood) +Drygore rapier (ice) +Drygore rapier (shadow) +Drygore rapier (3a) +Offhand drygore rapier (blood) +Offhand drygore rapier (ice) +Offhand drygore rapier (shadow) +Offhand drygore rapier (3a) +Drygore mace (blood) +Drygore mace (ice) +Drygore mace (shadow) +Drygore mace (3a) +Offhand drygore mace (blood) +Offhand drygore mace (ice) +Offhand drygore mace (shadow) +Offhand drygore mace (3a) +Drygore longsword (blood) +Drygore longsword (ice) +Drygore longsword (shadow) +Drygore longsword (3a) +Offhand drygore longsword (blood) +Offhand drygore longsword (ice) +Offhand drygore longsword (shadow) +Offhand drygore longsword (3a) +Dwarven warhammer (blood) +Dwarven warhammer (ice) +Dwarven warhammer (shadow) +Dwarven warhammer (3a) +Dwarven warnana +Dwarven warhammer (volcanic) +Twisted bow (ice) +Twisted bow (shadow) +Twisted bow (blood) +Twisted bow (3a) +Twisted bownana +Zaryte bow (ice) +Zaryte bow (shadow) +Zaryte bow (blood) +Zaryte bow (3a) +Zaryte bownana +Hellfire bownana +Hellfire bow (ice) +Hellfire bow (Oceanic) +Vasa cloak (zamorak) +Vasa cloak (saradomin) +TzKal cape (Oceanic) +TzKal cape (Volcanic) +Gorajan warrior helmet (Primal) +Gorajan warrior helmet (Oceanic) +Gorajan warrior top (Primal) +Gorajan warrior top (Oceanic) +Gorajan warrior legs (Primal) +Gorajan warrior legs (Oceanic) +Gorajan warrior gloves (Primal) +Gorajan warrior gloves (Oceanic) +Gorajan warrior boots (Primal) +Gorajan warrior boots (Oceanic) +Gorajan occult helmet (Celestial) +Gorajan occult helmet (Oceanic) +Gorajan occult top (Celestial) +Gorajan occult top (Oceanic) +Gorajan occult legs (Celestial) +Gorajan occult legs (Oceanic) +Gorajan occult gloves (Celestial) +Gorajan occult gloves (Oceanic) +Gorajan occult boots (Celestial) +Gorajan occult boots (Oceanic) +Gorajan archer helmet (Sagittarian) +Gorajan archer helmet (Oceanic) +Gorajan archer top (Sagittarian) +Gorajan archer top (Oceanic) +Gorajan archer legs (Sagittarian) +Gorajan archer legs (Oceanic) +Gorajan archer gloves (Sagittarian) +Gorajan archer gloves (Oceanic) +Gorajan archer boots (Sagittarian) +Gorajan archer boots (Oceanic) +Dwarven full helm (Volcanic) +Dwarven platebody (Volcanic) +Dwarven platelegs (Volcanic) +Dwarven gloves (Volcanic) +Dwarven boots (Volcanic) +Infernal slayer helmet(i) (ice) +Swanky boots +Darkmeyer hood +Darkmeyer torso +Darkmeyer trousers +Darkmeyer boots +Gorilla mask +Thinker robes +Thinker trousers +Thinker gloves +Thinker boots +Prifddinian worker's robes +Prifddinian worker's trousers +Prifddinian worker's gloves +Prifddinian worker's boots +Prifddinian musician's robe top +Prifddinian musician's robe bottom +Prifddinian musician's gloves +Prifddinian musician's boots +Dervish head wrap (blue-gold) +Dervish hood (red) +Dervish robe (blue-gold) +Dervish robe (red) +Dervish trousers (blue-gold) +Dervish skirt (red) +Dervish shoes (blue-gold) +Dervish shoes (red) +Eastern knot +Eastern bun +Eastern robe (blue) +Eastern kimono (blue) +Eastern trousers (blue) +Eastern skirt (blue) +Eastern sandals (male) +Eastern sandals (blue, female) +Tribal ringlet (red, male) +Tribal ringlet (red, female) +Tribal shirt (red) +Tribal top (red) +Tribal trousers (red) +Tribal skirt (red) +Tribal shoes (red, male) +Samba headdress (blue, male) +Samba headdress (blue, female) +Samba top (blue, male) +Samba top (blue, female) +Samba loincloth (blue, male) +Samba loincloth (blue, female) +Samba sandals (blue, male) +Theatrical hat (blue) +Theatrical earrings (blue) +Theatrical tunic (blue, male) +Theatrical tunic (blue, female) +Theatrical trousers (blue) +Theatrical skirt (blue) +Theatrical shoes (blue, male) +Theatrical shoes (blue, female) +Pharaoh's nemes (green) +Pharaoh's bun (green) +Pharaoh's ankh (green) +Pharaoh's top (green) +Pharaoh's shendyt (green, male) +Pharaoh's shendyt (green, female) +Pharaoh's sandals (green, male) +Pharaoh's sandals (green, female) +Wushanko headdress (blue) +Wushanko hat (blue) +Wushanko top (blue) +Wushanko jacket (blue) +Wushanko skirt (blue) +Wushanko trousers (blue) +Wushanko shoes (blue, male) +Wushanko shoes (blue, female) +Silken turban (blue, male) +Silken turban (blue, female) +Silken top (blue, male) +Silken top (blue, female) +Silken trousers (blue) +Silken skirt (blue) +Silken boots (blue, male) +Silken boots (blue, female) +Colonist's hat (blue) +Colonist's bonnet (blue) +Colonist's coat (blue) +Colonist's dress top (blue) +Colonist's trousers (blue) +Colonist's skirt (blue) +Colonist's boots (blue) +Colonist's shoes (blue) +Feathered serpent headdress (blue, male) +Feathered serpent headdress (blue, female) +Feathered serpent body (blue, male) +Feathered serpent body (blue, female) +Feathered serpent skirt (blue, male) +Feathered serpent skirt (blue, female) +Feathered serpent boots (blue, female) +Highland war paint (blue, male) +Highland war paint (blue, female) +Highland shirt (blue) +Highland top (blue) +Highland kilt (blue, male) +Highland kilt (blue, female) +Highland boots (blue, male) +Highland boots (blue, female) +Musketeer's hat (blue, male) +Musketeer's hat (blue, female) +Musketeer's tabard (blue) +Musketeer's top (blue) +Musketeer's trousers (blue, male) +Musketeer's trousers (blue, female) +Musketeer's boots (blue, male) +Musketeer's boots (blue, female) +Elf-style wig (black, male) +Elf-style wig (black, female) +Elf-style coat (black) +Elf-style dress top (black) +Elf-style trousers (black) +Elf-style skirt (black) +Elf-style boots (black) +Elf-style shoes (black) +Werewolf mask (red, male) +Werewolf torso (red, male) +Werewolf legs (red, male) +Werewolf claws (red, male) +Werewolf paws (red, male) +Ikuchi orokami mask +Kodama orokami mask +Akkorokamui orokami mask +Karasu orokami mask +Akateko orokami mask +Nue orokami mask +Shinigami orokami mask +Oni orokami mask +Shaman's headdress +Shaman's leggings +Shaman's moccasins +Shaman's poncho +Shaman's hand wraps +Fang of Mohegan +Round glasses +Stylish glasses +Pyjama slippers +Pyjama top +Pyjama bottoms +Tuxedo jacket +Tuxedo trousers +Tuxedo shoes +Tuxedo gloves +Tuxedo cravat +Evening bolero +Evening dipped skirt +Evening gloves +Evening boots +Evening masquerade mask +Black afro +Brown afro +Burgundy afro +Dark blue afro +Dark brown afro +Dark green afro +Dark grey afro +Green afro +Indigo afro +Light blue afro +Light brown afro +Light grey afro +Military grey afro +Mint green afro +Orange afro +Peach afro +Pink afro +Purple afro +Red afro +Rainbow afro +Taupe afro +Turquoise afro +Vermilion afro +Violet afro +White afro +Yellow afro +Red top hat +Green top hat +Blue top hat +White top hat +Pink disco top +Pink disco legs +Pink disco gloves +Pink disco boots +Green disco top +Green disco legs +Green disco gloves +Green disco boots +Blue disco top +Blue disco legs +Blue disco gloves +Blue disco boots +Chicken head +Chicken wings +Chicken legs +Chicken feet +Scythe +Pumpkin +Red halloween mask +Blue halloween mask +Green halloween mask +Black h'ween mask +Skeleton mask +Skeleton shirt +Skeleton leggings +Skeleton gloves +Skeleton boots +Jack lantern mask +Yo-yo +Reindeer hat +Bunny ears +Easter egg +Wintumber tree +Santa hat +Bobble hat +Bobble scarf +Jester hat +Jester scarf +Tri-jester hat +Tri-jester scarf +Woolly hat +Woolly scarf +Red marionette +Green marionette +Blue marionette +Rubber chicken +Zombie head +Half full wine jug +Christmas cracker +War ship +Cow mask +Cow top +Cow trousers +Cow gloves +Cow shoes +Easter basket +Grim reaper hood +Santa mask +Santa jacket +Santa pantaloons +Santa gloves +Santa boots +Antisanta mask +Antisanta jacket +Antisanta pantaloons +Antisanta gloves +Antisanta boots +Bunny feet +Bunny top +Bunny legs +Bunny paws +Anti-panties +Gravedigger mask +Gravedigger top +Gravedigger leggings +Gravedigger gloves +Gravedigger boots +Black santa hat +Inverted santa hat +Gnome child hat +Cabbage cape +Cruciferous codex +Banshee mask +Banshee top +Banshee robe +Snow globe +Giant present +Sack of presents +4th birthday hat +Birthday balloons +Easter egg helm +Eggshell platebody +Eggshell platelegs +Jonas mask +Snow imp costume head +Snow imp costume body +Snow imp costume legs +Snow imp costume gloves +Snow imp costume feet +Snow imp costume tail +Star-face +Tree top +Tree skirt +Candy cane +Birthday cake +Giant easter egg +Bunnyman mask +Spooky hood +Spooky robe +Spooky skirt +Spooky gloves +Spooky boots +Spookier hood +Spookier robe +Spookier skirt +Spookier gloves +Spookier boots +Pumpkin lantern +Skeleton lantern +Blue gingerbread shield +Green gingerbread shield +Cat ears +Hell cat ears +Magic egg ball +Eek +Carrot sword +'24-carat' sword +Web cloak +Chocatrice cape +Warlock top +Warlock legs +Warlock cloak +Witch top +Witch skirt +Witch cloak +Ring of snow +Toy kite +Black partyhat +Pink partyhat +Rainbow partyhat +Red partyhat +Yellow partyhat +Blue partyhat +Purple partyhat +Green partyhat +White partyhat +Silver partyhat +Baby raven +Raven +Magic kitten +Magic cat +Zamorak egg +Baby zamorak hawk +Juvenile zamorak hawk +Zamorak hawk +Guthix egg +Baby guthix raptor +Juvenile guthix raptor +Guthix raptor +Saradomin egg +Baby saradomin owl +Juvenile saradomin owl +Saradomin owl +Grey and black kitten +Grey and black cat +White kitten +White cat +Brown kitten +Brown cat +Black kitten +Black cat +Grey and brown kitten +Grey and brown cat +Grey and blue kitten +Grey and blue cat +Godsword blade +Armadyl godsword +Bandos godsword +Saradomin godsword +Zamorak godsword +Ancient godsword +Infernal pickaxe +Malediction ward +Odium ward +Crystal key +Clue scroll (master) +Infernal axe +Infernal harpoon +Colossal pouch +Blessed spirit shield +Spectral spirit shield +Arcane spirit shield +Elysian spirit shield +Holy book +Book of balance +Unholy book +Book of law +Book of war +Book of darkness +Ava's accumulator +Ava's assembler +Dragon sq shield +Dragon kiteshield +Dragon platebody +Coconut milk +Coconut shell +Zamorakian hasta +Ultracompost +Tomatoes(5) +Apples(5) +Bananas(5) +Strawberries(5) +Oranges(5) +Potatoes(10) +Onions(10) +Cabbages(10) +Bucket of sand +Eldritch nightmare staff +Harmonised nightmare staff +Volatile nightmare staff +Zamorak's grapes +Toad's legs +Pegasian boots +Primordial boots +Eternal boots +Partyhat & specs +Ivandis flail +Blisterwood flail +Bottled dragonbreath +Ring of endurance +Fish sack barrel +Kodai wand +Salve amulet (e) +Salve amulet(ei) +Ring of wealth (i) +Strange hallowed tome +Daeyalt essence +Celestial signet +Eternal teleport crystal +Saturated heart +Trident of the swamp +Voidwaker +Accursed sceptre (u) +Ursine chainmace (u) +Webweaver bow (u) +Bone mace +Bone shortbow +Bone staff +Venator bow (uncharged) +Blessed dizana's quiver +Dizana's max cape +Dizana's max hood +Enhanced crystal key +Blade of saeldor (c) +Bow of faerdhinen (c) +Blade of saeldor (inactive) +Bow of faerdhinen (inactive) +Dragon defender (t) +Rune defender (t) +Dragon pickaxe (or) +Dragon sq shield (g) +Dragon full helm (g) +Dragon platebody (g) +Dragon kiteshield (g) +Dragon boots (g) +Dragon scimitar (or) +Dragon platelegs (g) +Dragon plateskirt (g) +Dragon chainbody (g) +Amulet of fury (or) +Zamorak godsword (or) +Bandos godsword (or) +Saradomin godsword (or) +Armadyl godsword (or) +Amulet of torture (or) +Necklace of anguish (or) +Tormented bracelet (or) +Occult necklace (or) +Rune scimitar (guthix) +Rune scimitar (saradomin) +Rune scimitar (zamorak) +Tzhaar-ket-om (t) +Berserker necklace (or) +Dark infinity hat +Dark infinity top +Dark infinity bottoms +Light infinity hat +Light infinity top +Light infinity bottoms +Polar camo top +Polar camo legs +Wood camo top +Wood camo legs +Jungle camo top +Jungle camo legs +Desert camo top +Desert camo legs +Larupia legs +Larupia top +Larupia hat +Graahk legs +Graahk top +Graahk headdress +Twisted ancestral hat +Twisted ancestral robe top +Twisted ancestral robe bottom +Puppadile +Tektiny +Vanguard +Vasa minirio +Vespina +Midnight +Baby mole-rat +Tzrek-zuk +Little parasite +Ziggy +Red +Great blue heron +Greatish guardian +Ferocious gloves +Uncut zenyte +Neitiznot faceguard +Arclight +Boots of brimstone +Devout boots +Bryophyta's staff +Abyssal tentacle +Brimstone ring +Guardian boots +Abyssal bludgeon +Black mask +Slayer ring (8) +Slayer ring (eternal) +Slayer helmet +Slayer helmet (i) +Black slayer helmet +Black slayer helmet (i) +Green slayer helmet +Green slayer helmet (i) +Red slayer helmet +Red slayer helmet (i) +Purple slayer helmet +Purple slayer helmet (i) +Turquoise slayer helmet +Turquoise slayer helmet (i) +Hydra slayer helmet +Hydra slayer helmet (i) +Twisted slayer helmet +Twisted slayer helmet (i) +Uncharged dragonfire shield +Uncharged dragonfire ward +Uncharged ancient wyvern shield +Dragonfire shield +Dragonfire ward +Ancient wyvern shield +Bracelet of ethereum +Viggora's chainmace +Ursine chainmace +Craw's bow +Webweaver bow +Thammaron's sceptre +Accursed sceptre +Bronze set (lg) +Bronze set (sk) +Bronze trimmed set (lg) +Bronze trimmed set (sk) +Bronze gold-trimmed set (lg) +Bronze gold-trimmed set (sk) +Iron set (lg) +Iron set (sk) +Iron trimmed set (lg) +Iron trimmed set (sk) +Iron gold-trimmed set (lg) +Iron gold-trimmed set (sk) +Steel set (lg) +Steel set (sk) +Steel trimmed set (lg) +Steel trimmed set (sk) +Steel gold-trimmed set (lg) +Steel gold-trimmed set (sk) +Black set (lg) +Black set (sk) +Black trimmed set (lg) +Black trimmed set (sk) +Black gold-trimmed set (lg) +Black gold-trimmed set (sk) +Mithril set (lg) +Mithril set (sk) +Mithril trimmed set (lg) +Mithril trimmed set (sk) +Mithril gold-trimmed set (lg) +Mithril gold-trimmed set (sk) +Adamant set (lg) +Adamant set (sk) +Adamant trimmed set (lg) +Adamant trimmed set (sk) +Adamant gold-trimmed set (lg) +Adamant gold-trimmed set (sk) +Rune armour set (lg) +Rune armour set (sk) +Rune trimmed set (lg) +Rune trimmed set (sk) +Rune gold-trimmed set (lg) +Rune gold-trimmed set (sk) +Gilded armour set (lg) +Gilded armour set (sk) +Guthix armour set (lg) +Guthix armour set (sk) +Saradomin armour set (lg) +Saradomin armour set (sk) +Zamorak armour set (lg) +Zamorak armour set (sk) +Ancient rune armour set (lg) +Ancient rune armour set (sk) +Armadyl rune armour set (lg) +Armadyl rune armour set (sk) +Bandos rune armour set (lg) +Bandos rune armour set (sk) +Dragon armour set (lg) +Dragon armour set (sk) +Verac's armour set +Dharok's armour set +Guthan's armour set +Ahrim's armour set +Torag's armour set +Karil's armour set +Inquisitor's armour set +Justiciar armour set +Obsidian armour set +Dragonstone armour set +Initiate harness m +Proselyte harness m +Proselyte harness f +Green dragonhide set +Blue dragonhide set +Red dragonhide set +Black dragonhide set +Gilded dragonhide set +Guthix dragonhide set +Saradomin dragonhide set +Zamorak dragonhide set +Ancient dragonhide set +Armadyl dragonhide set +Bandos dragonhide set +Mystic set (blue) +Mystic set (dark) +Mystic set (light) +Mystic set (dusk) +Ancestral robes set +Book of balance page set +Holy book page set +Unholy book page set +Book of darkness page set +Book of law page set +Book of war page set +Partyhat set +Halloween mask set +Combat potion set +Super potion set +Dwarf cannon set +Divine spirit shield +Heart crystal +Vasa cloak +Bryophyta's staff(i) +Ignis ring(i) +Breadcrumbs +Infernal bulwark +TzKal cape +Infernal slayer helmet(i) +Royal crossbow +Polypore staff +Crystal fishing rod +Void staff (u) +Void staff +Abyssal tome +Spellbound ring(i) +Seamonkey staff (t1) +Seamonkey staff (t2) +Seamonkey staff (t3) +Gorajan bonecrusher +Twisted relic hunter (t1) armour set +Twisted relic hunter (t2) armour set +Twisted relic hunter (t3) armour set +Trailblazer relic hunter (t1) armour set +Trailblazer relic hunter (t2) armour set +Trailblazer relic hunter (t3) armour set +Dagon'hai robes set +Ganodermic visor +Ganodermic poncho +Ganodermic leggings +Grifolic visor +Grifolic poncho +Grifolic leggings +Dragonbone boots +Dragonbone full helm +Dragonbone platebody +Dragonbone platelegs +Dragonbone gloves +Dragonbone mage boots +Dragonbone mage gloves +Dragonbone mage bottoms +Dragonbone mage hat +Dragonbone mage top +Royal dragon platebody +Frosty +Virtus wand +Virtus book +Pernix cowl +Pernix body +Pernix chaps +Pernix boots +Pernix gloves +Torva full helm +Torva platebody +Torva platelegs +Torva boots +Torva gloves +Virtus mask +Virtus robe top +Virtus robe legs +Virtus boots +Virtus gloves +Armadylean components +Bandosian components +Ancestral components +Masori components +Divine water +Crystal dust +Titan ballista +Piercing trident +Atlantean trident +Shark tooth necklace +Ring of piercing +Ring of piercing (i) +Tidal collector +Atomic energy +Sundial scimitar +Offhand spidergore rapier +Lumina +Clue scroll (elder) +Scythe of vitur +Sanguinesti staff +Holy sanguinesti staff +Holy sanguinesti staff (uncharged) +Holy scythe of vitur +Holy scythe of vitur (uncharged) +Sanguine scythe of vitur +Sanguine scythe of vitur (uncharged) +Avernic defender +Holy ghrazi rapier +Granite maul (or) +Granite maul (ornate handle) +Granite maul (or) (ornate handle) +Mystic steam staff (or) +Steam battlestaff (or) +Mystic lava staff (or) +Lava battlestaff (or) +Dragon pickaxe (upgraded) +Malediction ward (or) +Odium ward (or) +Dark bow (green) +Dark bow (blue) +Dark bow (yellow) +Dark bow (white) +Volcanic abyssal whip +Frozen abyssal whip +Staff of balance +Saradomin's blessed sword +Magic shortbow (i) +Looting bag +Rune pouch +Mystic air staff +Mystic water staff +Mystic earth staff +Mystic fire staff +Mystic dust staff +Mystic lava staff +Mystic mist staff +Mystic mud staff +Mystic smoke staff +Mystic steam staff +Zaryte crossbow +Golden prospector boots +Golden prospector helmet +Golden prospector jacket +Golden prospector legs +Dragon axe (or) +Dragon harpoon (or) +Infernal axe (or) +Infernal harpoon (or) +Infernal pickaxe (or) +Dragon pickaxe (or) (Trailblazer) +Abyssal tentacle (or) +Abyssal whip (or) +Book of balance (or) +Book of darkness (or) +Book of law (or) +Book of war (or) +Holy book (or) +Unholy book (or) +Rune crossbow (or) +Elite void robe (or) +Elite void top (or) +Void knight gloves (or) +Void knight top (or) +Void knight robe (or) +Void mage helm (or) +Void melee helm (or) +Void ranger helm (or) +Cannon barrels (or) +Cannon base (or) +Cannon furnace (or) +Cannon stand (or) +Mystic boots (or) +Mystic gloves (or) +Mystic hat (or) +Mystic robe bottom (or) +Mystic robe top (or) +Hat of the eye (red) +Robe top of the eye (red) +Robe bottoms of the eye (red) +Hat of the eye (green) +Robe top of the eye (green) +Robe bottoms of the eye (green) +Hat of the eye (blue) +Robe top of the eye (blue) +Robe bottoms of the eye (blue) +Runite igne claws +Dragon igne claws +Barrows igne claws +Volcanic igne claws +Drygore igne claws +Dwarven igne claws +Gorajan igne claws +Dragon igne armor +Barrows igne armor +Volcanic igne armor +Justiciar igne armor +Drygore igne armor +Dwarven igne armor +Gorajan igne armor +Demonic jibwings +Abyssal jibwings +3rd age jibwings +3rd age jibwings (e) +Demonic jibwings (e) +Abyssal jibwings (e) +Divine ring +Impling locator +Volcanic pickaxe +Offhand volcanic pickaxe +Moktang totem +Dragonstone full helm(u) +Dragonstone platebody(u) +Dragonstone platelegs(u) +Dragonstone boots(u) +Dragonstone gauntlets(u) +Bronze coffin +Steel coffin +Black coffin +Silver coffin +Gold coffin +Necromancer hood +Necromancer robe top +Necromancer robe bottom +Necromancer's air staff +Necromancer's earth staff +Necromancer's fire staff +Necromancer's lava staff +Necromancer's mud staff +Necromancer's steam staff +Necromancer's water staff +Skeletal battlestaff of air +Skeletal battlestaff of earth +Skeletal battlestaff of fire +Skeletal battlestaff of water +Skeletal lava battlestaff +Skeletal mud battlestaff +Skeletal steam battlestaff +Masori assembler +Osmumten's fang (or) +Elidinis' ward (f) +Elidinis' ward (or) +Divine rune pouch +Masori mask (f) +Masori body (f) +Masori chaps (f) +Armadylean plate +Keris partisan of breaching +Keris partisan of corruption +Keris partisan of the sun +Akkhito +Babi +Kephriti +Tumeken's damaged guardian +Elidinis' damaged guardian +Zebo +Bloodbark helm +Bloodbark body +Bloodbark legs +Bloodbark boots +Bloodbark gauntlets +Swampbark helm +Swampbark body +Swampbark legs +Swampbark boots +Swampbark gauntlets +Warrior icon +Bellator icon +Bellator ring +Berserker icon +Ultor icon +Ultor ring +Seers icon +Magus icon +Magus ring +Sanguine torva full helm +Sanguine torva platebody +Sanguine torva platelegs +Archer icon +Venator icon +Venator ring +Soulreaper axe +Tztok slayer helmet +Tztok slayer helmet (i) +Dragon hunter crossbow (t) +Dragon hunter crossbow (b) +Tzkal slayer helmet +Tzkal slayer helmet (i) +Vampyric slayer helmet +Vampyric slayer helmet (i) +Ghommal's avernic defender 5 +Ghommal's avernic defender 6 +Secateurs attachment +Nature offerings +Sturdy harness +Forestry basket +Clothes pouch +Bronze felling axe +Iron felling axe +Steel felling axe +Black felling axe +Mithril felling axe +Adamant felling axe +Rune felling axe +Dragon felling axe +Crystal felling axe +3rd age felling axe +Pheasant +Fox +Axe handle base +Axe handle +Axe of the high sungod (u) +Axe of the high sungod +Fuzzy dice +Karambinana +Warpriest of Zamorak set +Warpriest of Saradomin set +Warpriest of Bandos set +Warpriest of Armadyl set \ No newline at end of file diff --git a/pool2-cl.txt b/pool2-cl.txt new file mode 100644 index 0000000000..3801be4650 --- /dev/null +++ b/pool2-cl.txt @@ -0,0 +1,3834 @@ +2nd Item Pool (Not CL and not UMB) +Tormented skull +Bingo ticket +Olof's gold +Lit celebratory cake +Celebratory cake with candle +Burnt celebratory cake +Bucket of dung +Offhand dice plushie +Dice plushie +Hoppy plushie +Plopper nose +Ceremonial hat +Ceremonial cape +Ceremonial boots +Ceremonial legs +Ceremonial top +BSO flowers +Monkey cape +Cooked plopper bacon +Raw plopper bacon +Gambling skillcape +Blueberry birthday cake +BSO banner +Blabberbeak jumper +Rose tinted glasses +Swan scarf +Swan hat +Ethereal partyhat +Birthday crate key (s6) +Birthday crate (s6) +Zak plushie +Chilli chocolate +Dunce shoes +Dunce legs +Dunce top +Dunce hat +Nex plushie +Frost mask +Large egg +Dwarfqueen tiara +Offhand dwarven spatula +Dwarven frying pan +Queen goldemar beard +Queen goldemar skirt +Queen goldemar blouse +Cluckers +Lime parasol +Elder scroll piece +Reward casket (elder) +Easter crate key (s5) +Easter crate (s5) +Easter shoes +Easter breeches +Easter tunic +Bunny plushie +Easter jumper +Cute bunny cape +Easter-egg salad +Easter-egg delight +Golden bunny ears +Sun-metal bar +Celestial cape +Celestial boots +Celestial gloves +Celestial platelegs +Celestial platebody +Celestial helm +Lunite boots +Lunite cape +Lunite chestplate +Lunite helm +Lunite gloves +Lunite platelegs +Sunlight sprouter +Solervus cape +Solervus boots +Solervus gloves +Solervus platelegs +Solervus platebody +Solervus helm +Solite blade +Solite shield +Solite boots +Solite cape +Solite chestplate +Solite helm +Solite gloves +Solite platelegs +Guthixian cache boost +Grimy spirit weed +Divination cape +Inversion paint can +Drakan Dark paint can +Silver Light paint can +Ruby Red paint can +Amethyst Purple paint can +Abyssal Purple paint can +BSO Blurple paint can +Zamorak Red paint can +Pretty Pink paint can +Sapphire Blue paint can +Vorkath Blue paint can +Gilded Gold paint can +TzHaar Orange paint can +Guthix Green paint can +Metallic chocolate dust +Tinsel twirler +Frosty staff +Frosty cape +Frosty wings +Frosty parasol +Note from pets +Rudolph +Mistle-bow-tie +Burnt christmas cake +Gingerbread +Dodgy bread +Ginger root +Christmas cake recipe +Hairy banana-butter +Smokey egg +Banana-butter +Milk with spoon +Grimy salt +Snail oil +Ashy flour +Chomped chocolate bits +Cocoa bean +Pristine chocolate bar +Fresh rat milk +Scorched rat milk +Raw rat milk +Santa costume boots +Santa costume gloves +Santa costume pants +Santa costume skirt +Santa costume top (female) +Santa costume top (male) +Festive crate key (s4) +Festive crate (s4) +Infernal slayer helmet(i) (xmas) +Christmas jumper (green) +Christmas jumper (jolly red) +Christmas cape (classic) +Christmas cape (rainbow) +Christmas cape (snowy tree) +Christmas cape (jolly red) +Christmas cape (wintertodt blue) +Christmas jumper (frosty) +Festive partyhat +Grinch santa hat +Grinch hands +Grinch feet +Grinch legs +Grinch top +Grinch head +Completionist hood (t) +Drakan fangs +Bloodstone obelisk +Hemoglyphs +Blood chalice +Purple halloween mask +Cosmic dice +Spooky aura +Spooky sheet +Evil partyhat +Soul shield +TzKal cape (spooky) +Casper +Bat bat +Demonic piercer +Mini mortimer +Splooky fwizzle +Pandora's box +Fool's ace +Bag of tricks +Covenant of grimace +Maledict codex +Maledict gloves +Maledict ring +Maledict amulet +Maledict boots +Maledict cape +Maledict legs +Maledict top +Maledict hat +Dwarven armour set +Drygore longsword set +Drygore mace set +Drygore rapier set +Virtus armour set +Pernix armour set +Torva armour set +Deathly collector +Dwarven pumpkinsmasher +The Grim Reaper +Spooky dye +Demonic halloween mask +Spooky sombrero +Ghostly zombie gloves +Ghostly zombie trousers +Ghostly zombie shirt +Ghostly zombie boots +Ghostly zombie mask +Pumpkin peepers +Cob cap +Ghostly lederhosen gloves +Ghostly jester hat +Ghostly ringmaster hat +Ghostly lederhosen hat +Ghostly lederhosen shorts +Ghostly lederhosen top +Ghostly lederhosen boots +Ghostly chicken feet +Ghostly chicken wings +Ghostly chicken legs +Ghostly chicken gloves +Ghostly chicken head +Ghostly jester gloves +Ghostly jester boots +Ghostly jester top +Ghostly jester tights +Ghostly ringmaster pants +Ghostly ringmaster boots +Ghostly ringmaster shirt +Ghostly ringmaster gloves +Ghostweave +Count Draynor fangs +Count Draynor shoes +Count Draynor hands +Count Draynor cape +Count Draynor bottoms +Count Draynor torso +Voodoo doll +Spooky spider parasol +Spooky crate key (s3) +Spooky crate (s3) +Raw yeti meat +Yeti hide +Cold heart +Frostclaw cape +Skip +Penguin egg +Mysterious clue (6) +Mysterious clue (5) +Mysterious clue (4) +Mysterious clue (3) +Mysterious clue (2) +Mysterious clue (1) +Bloodsoaked children's book +Torn fur +Bloodsoaked fur +Bloodsoaked cowhide +Dungsoaked message +Zamorakian codex +Carapace +Warpriest of Bandos cape +Warpriest of Bandos gauntlets +Warpriest of Bandos boots +Warpriest of Bandos greaves +Warpriest of Bandos cuirass +Warpriest of Bandos helm +Warpriest of Armadyl cape +Warpriest of Armadyl gauntlets +Warpriest of Armadyl boots +Warpriest of Armadyl greaves +Warpriest of Armadyl cuirass +Warpriest of Armadyl helm +Warpriest of Saradomin cape +Warpriest of Saradomin gauntlets +Warpriest of Saradomin boots +Warpriest of Saradomin greaves +Warpriest of Saradomin cuirass +Warpriest of Saradomin helm +Warpriest of Zamorak cape +Warpriest of Zamorak gauntlets +Warpriest of Zamorak boots +Warpriest of Zamorak greaves +Warpriest of Zamorak cuirass +Warpriest of Zamorak helm +Completionist hood +Acrylic set +Full bug jar +Perfect egg +Perfect bucket of milk +Perfect pot of flour +Bug jar +Shelldon shield +Buggy +Delicious birthday cake +Cake partyhat +Birthday crate key (s2) +Birthday crate (s2) +Rubber flappy +Imperial sabatons +Imperial gloves +Imperial legs +Imperial cuirass +Imperial helmet +Koschei's toothpick +Veteran hood (4 year) +Veteran hood (3 year) +Veteran hood (2 year) +Veteran hood (1 year) +Golden cape shard +Bunch of flowers +Veteran cape (4 year) +Veteran cape (3 year) +Veteran cape (2 year) +Veteran cape (1 year) +Birthday love note +The Interrogator +Ban hammer +Golden cape +Acrylic boots +Acrylic bottom +Acrylic top +Acrylic hood +Archon boots +Archon gloves +Archon crest +Archon tassets +Archon headdress +Supply crate key (s1) +Supply crate (s1) +Remy's chef hat +Skipper's tie +Paint box +BSO Jumper +OSB Jumper +Message in a bottle +Eggy +Seamonkey staff (t4) +Chocolate rabbit +Easter egg backpack +Floppy bunny ears +Fancy sweet egg +Big ancient egg +Mysterious magic egg +Sweet small egg +Fancy ancient egg +Big mysterious egg +Fancy magic egg +Cute magic egg +Egg coating +Chocolate pot +Prisoner legs +Prisoner top +Fox tail +Fox ears +Sack of mystery boxes +Queen's guard staff +Queen's guard shoes +Queen's guard trousers +Queen's guard shirt +Queen's guard hat +Ring of cabbage +White mask +Ringmaster hat +Ringmaster boots +Tambourine +Ringmaster shirt +Ringmaster pants +Clown feet +Clown shirt +Clown leggings +Clown hat +Circus ticket +Acrobat shoes +Acrobat shirt +Acrobat pants +Acrobat hood +Shade skull +Necromancer kit +Santa claws +Vasa cloak (xmas) +Pork crackling +Zaryte crossbow (xmas) +Dwarven pickaxe (xmas) +Christmas cape +Festive jumper (2022) +Drygore longsword (xmas) +Offhand drygore longsword (xmas) +Drygore mace (xmas) +Offhand drygore mace (xmas) +Drygore rapier (xmas) +Offhand drygore rapier (xmas) +Christmas dye +Frosted wreath +Ho-ho hammer +Candy partyhat +Mistleboe +Edible yoyo +Snowman plushie +Festive scarf +Snowman top hat +Tinsel scarf +Christmas box +Christmas socks +Reinbeer +Pork sausage +Festive treats +Moktang mint +Craig creme +Cob cup +Seer sweet +Takon truffle +Pumpkinhead praline +Christmas snowglobe +Offhand rubber turkey +Rubber turkey +Turkey recipes +Grim sweeper +Pumpkin seed +Dirty hoe +Handled candle +Orange halloween mask +Necronomicon +Boo-balloon +Gloom and doom potion +Spooky box +Twinkly topper +M'eye hat +Back pain +Pumpkinpole +Gastly ghost cape +Spooky cat ears +Spooky gear frame unlock +Oriental fan +Leprechaun top hat +Sombrero +Map hat +Potion hat +Turkey hat +Fish mask +Craftman's monocle +Monkey hat +Penguin gloves +Penguin boots +Penguin legs +Penguin torso +Penguin head +Spooky partyhat +Mooshroom +Kuro +Spooky mask +Witch hat +Broomstick +Rainbow cape +Dark animica +Mangobeak +Invention hood +Invention cape(t) +Invention cape +Chimpchompa +Magical mango +White strawberry +Sweet potato +Rainbow sweetcorn +Cannonball cabbage +Golden partyhat +Black swan +Seer +Smokey painting +Golden shard +Festive wrapping paper +Festive present +Festive jumper (2021) +Pavlova +Prawns +Bacon +Pretzel +Fish n chips +Ratatouille +Roast potatoes +Christmas tree hat +Gr-egg-oyle special +Christmas tree kite +Festive mistletoe +Christmas pudding amulet +Christmas pudding +Yule log +Flappy meal +Shepherd's pie +Dougs' chocolate mud +Corn on the cob +Roasted ham +Pumpkinhead pie +Smokey bbq sauce +Frozen santa hat +Obsidian shards +Elder table +Buzz +Honey +Honeycomb +Beehive +Celebratory cake +Purple flower crown +Lava flower crown +Double loot token +Clothing Mystery Box +Eastern ferret +Royal dragon bones +Royal bolts +Royal dragonhide +Polypore stick +Grifolic wand +Grifolic shield +Frost dragon bones +Magic banana +Elder rune +Elder plank +Elder logs +Blacksmith boots +Blacksmith gloves +Blacksmith apron +Blacksmith top +Blacksmith helmet +Raw rocktail +Chickaxe +Leia +Decorative easter eggs +Easter egg crate +Cornucopia +Burnt turkey +Turkey drumstick +Raw turkey +Blacksmith crate +Shiny cat +Mysterious token +Mini Pumpkinhead +Haunted boots +Haunted gloves +Haunted amulet +Pumpkinhead's pumpkin head +Pumpkinhead's headbringer +Haunted cloak +Choc'rock +Gregoyle +Roasted newt +Benny's brain brew +Goblinfinger soup +Hairyfloss +Candy teeth +Toffeet +Rotten sweets +Eyescream +Chocolified skull +Human tooth +Human blood +Sliced femur +Human appendage +Hellfire arrowtips +Hellfire bow (broken) +Sam's hat +Liber tea +Fireworks +Independence box +Crab hat +Ice cream +Water balloon +Beach ball +Snappy the Turtle +Beach mystery box +Craig +Hoppy +Royal mystery box +Diamond crown +Diamond sceptre +Corgi +Flappy +100 sided die +20 sided die +12 sided die +10 sided die +8 sided die +6 sided die +4 sided die +Gamblers bag +Birthday pack +Party music box +Sparkler +Cake hat +Party cake +Party popper +Party horn +Glass of bubbly +Hellfire bownana (broken) +Completionist cape (t) +Completionist cape +Dungeoneering cape +Cursed amulet of magic +Adamant seeds +Teleport anchoring scroll +Zombie pirate key +Sunfire fanatic armour set +Sun-shine +Moon-lite +Eclipse wine +Steamforge brew +Sunbeam ale +Hunter spear tips +Quetzal feed +Hunter's spear +Mixed hide base +Mixed hide cape +Mixed hide boots +Mixed hide legs +Mixed hide top +Trapper's tipple +Perfected quetzal whistle blueprint +Enhanced quetzal whistle blueprint +Jaguar fur +Moonlight moth mix (1) +Sunlight moth mix (1) +Black warlock mix (1) +Ruby harvest mix (1) +Snowy knight mix (1) +Sapphire glacialis mix (1) +Moonlight moth mix (2) +Sunlight moth mix (2) +Black warlock mix (2) +Ruby harvest mix (2) +Snowy knight mix (2) +Sapphire glacialis mix (2) +Sunlight antelope fur +Moonlight antelope fur +Moonlight antelope antler +Sunlight antelope antler +Jerboa tail +Fox fur +Burnt fox meat +Burnt antelope +Burnt large beast +Burnt kebbit +Cooked kyatt +Cooked graahk +Cooked larupia +Cooked moonlight antelope +Cooked sunlight antelope +Cooked pyre fox +Cooked dashing kebbit +Cooked barb-tailed kebbit +Cooked wild kebbit +Raw kyatt +Raw larupia +Raw graahk +Raw sunlight antelope +Raw moonlight antelope +Raw pyre fox +Raw dashing kebbit +Raw wild kebbit +Raw barb-tailed kebbit +Not meat +Calcified moth +Sulphur blades +Blood moon helm (broken) +Blood moon tassets (broken) +Blood moon chestplate (broken) +Blue moon helm (broken) +Blue moon tassets (broken) +Blue moon chestplate (broken) +Eclipse moon helm (broken) +Eclipse moon tassets (broken) +Eclipse moon chestplate (broken) +Blood moon helm +Blood moon tassets +Blood moon chestplate +Blue moon helm +Blue moon tassets +Blue moon chestplate +Eclipse moon helm +Eclipse moon tassets +Eclipse moon chestplate +Eclipse atlatl +Dual macuahuitl +Atlatl dart +Blue moon spear +Searing page +Sunfire rune +Wyrmling bones +Rum +Moonlight moth +Sunlight moth +Moonlight antler bolts +Sunlight antler bolts +Hunters' sunlight crossbow +Irit tar +Tecu salamander +Immature tecu salamander +Civitas illa fortis teleport +Broken zombie axe +Zombie axe +Kourend castle teleport +Trailblazer reloaded relic hunter (t3) armour set +Trailblazer reloaded relic hunter (t2) armour set +Trailblazer reloaded relic hunter (t1) armour set +Trailblazer reloaded bronze trophy +Trailblazer reloaded iron trophy +Trailblazer reloaded steel trophy +Trailblazer reloaded mithril trophy +Trailblazer reloaded adamant trophy +Trailblazer reloaded rune trophy +Trailblazer reloaded dragon trophy +Trailblazer reloaded torch +Trailblazer reloaded boots (t3) +Trailblazer reloaded trousers (t3) +Trailblazer reloaded top (t3) +Trailblazer reloaded headband (t3) +Trailblazer reloaded boots (t2) +Trailblazer reloaded trousers (t2) +Trailblazer reloaded top (t2) +Trailblazer reloaded headband (t2) +Trailblazer reloaded boots (t1) +Trailblazer reloaded trousers (t1) +Trailblazer reloaded top (t1) +Trailblazer reloaded headband (t1) +Trailblazer reloaded rejuvenation pool scroll +Trailblazer reloaded home teleport scroll +Trailblazer reloaded banner +Trailblazer reloaded death scroll +Trailblazer reloaded vengeance scroll +Trailblazer reloaded alchemy scroll +Trailblazer reloaded blowpipe ornament kit +Trailblazer reloaded bulwark ornament kit +Trap disarmer blueprint +Trap disarmer +Egg cushion +Petal circlet +Crystal charm +Smoker canister +Smoker fuel +Sawmill voucher +Warped sceptre (uncharged) +Trinket of undead +Trinket of advanced weaponry +Trinket of fairies +Trinket of vengeance +Corrupted tumeken's shadow (uncharged) +Corrupted scythe of vitur (uncharged) +Corrupted twisted bow +Corrupted armadyl godsword +Corrupted dragon claws +Corrupted voidwaker +Sigil of woodcraft +Sigil of woodcraft +Sigil of the ninja +Sigil of the ninja +Sigil of titanium +Sigil of titanium +Sigil of faith +Sigil of faith +Sigil of the augmented thrall +Sigil of the augmented thrall +Sigil of precision +Sigil of precision +Sigil of the bloodhound +Sigil of the bloodhound +Sigil of the lightbearer +Sigil of the lightbearer +Sigil of the infernal smith +Sigil of the infernal smith +Sigil of the infernal chef +Sigil of the infernal chef +Sigil of the well-fed +Sigil of the well-fed +Sigil of the food master +Sigil of the food master +Sigil of agile fortune +Sigil of agile fortune +Sigil of resistance +Sigil of resistance +Sigil of the hunter +Sigil of the hunter +Sigil of the alchemaniac +Sigil of the alchemaniac +Sigil of hoarding +Sigil of hoarding +Sigil of sustenance +Sigil of sustenance +Bellator ring +Magus ring +Venator ring +Ultor ring +Unfired cup +Powdered pollen +Bee on a stick +Clover insignia +Leprechaun charm +Secateurs blade +Forester's ration +Ritual mulch +Scaly blue dragonhide +Nightshade +Thammaron's sceptre (a) +Thammaron's sceptre (au) +Diamond speedrun trophy +Crystal helm (inactive) +Crystal helm +Crystal legs (inactive) +Crystal legs +Crystal body (inactive) +Crystal body +Crystal helm (inactive) +Crystal helm +Crystal legs (inactive) +Crystal legs +Crystal body (inactive) +Crystal body +Crystal helm (inactive) +Crystal helm +Crystal legs (inactive) +Crystal legs +Crystal body (inactive) +Crystal body +Crystal helm (inactive) +Crystal helm +Crystal legs (inactive) +Crystal legs +Crystal body (inactive) +Crystal body +Crystal helm (inactive) +Crystal helm +Crystal legs (inactive) +Crystal legs +Crystal body (inactive) +Crystal body +Crystal helm (inactive) +Crystal helm +Crystal legs (inactive) +Crystal legs +Crystal body (inactive) +Crystal body +Crystal helm (inactive) +Crystal helm +Crystal legs (inactive) +Crystal legs +Crystal body (inactive) +Crystal body +Ore pack (Volcanic Mine) +Accursed sceptre (a) +Accursed sceptre (au) +Vet'ion jr. +Vet'ion jr. +Callisto cub +Venenatis spiderling +Mystic cards +Forgotten brew(1) +Forgotten brew(2) +Forgotten brew(3) +Ancient sceptre (l) +Ancient sceptre +Venator bow +Icy key +Ancient jewel +Jewel shard +Jewel shard +Icy chest +Lever handle +Settlements note +Numbers note +Duke note +Strange list +Strange cipher +Ancient map +Tullia's letter +Dusty scroll +Muphin +Muphin +Festive games crown +Sweet nutcracker staff +Sweet nutcracker boots +Sweet nutcracker hat +Sweet nutcracker trousers +Sweet nutcracker top +Festive nutcracker staff +Festive nutcracker boots +Festive nutcracker hat +Festive nutcracker trousers +Festive nutcracker top +Sack of coal +Snow goggles & hat +Christmas jumper +Santa's list +Eggnog +Mulled pine +Light beer +Golden snowball +Sack of coal +Shattered gingerbread +Very broken gingerbread +Broken gingerbread +Perfect gingerbread +Ghommal's avernic defender 6 (l) +Ghommal's avernic defender 5 (l) +Anim offhand +Anim offhand +Ghommal's lucky penny +Antique lamp +Wood carving +Wood carving +Wood carving +Wood carving +Wood carving +Compass +Wood carving +Wood carving +Wood carving +Wood carving +Wood carving +Wood carving +Wood carving +Wood carving +Wood carving +Stone tablet +Stone tablet +Stone tablet +Stone tablet +Warning note +Dirty note +Dirty note +Dirty note +Word translations +Kasonde's journal +Divine rune pouch (l) +Halloween wig +Halloween wig +Halloween wig +Halloween wig +Halloween wig +Old wool +Spooky egg +Smelly sock +Bruised banana +Graceful boots +Graceful gloves +Graceful legs +Graceful top +Graceful cape +Graceful hood +Adventurer's cape +Infinite money bag +Cloak of ruin +Socks of ruin +Gloves of ruin +Robe bottom of ruin +Robe top of ruin +Hood of ruin +Clue scroll (special) +Dynamite(p) +Platinum speedrun trophy +Gold speedrun trophy +Silver speedrun trophy +Bronze speedrun trophy +Speedy teleport scroll +Giant stopwatch +Adventurer's vambraces +Adventurer's boots (t3) +Adventurer's hood (t3) +Adventurer's trousers (t3) +Adventurer's top (t3) +Adventurer's boots (t2) +Adventurer's hood (t2) +Adventurer's trousers (t2) +Adventurer's top (t2) +Adventurer's boots (t1) +Adventurer's hood (t1) +Adventurer's trousers (t1) +Adventurer's top (t1) +Masori assembler (l) +Mask of rebirth +Ancient key +Dawn scarab egg +Masori assembler max cape (l) +Masori assembler max cape (broken) +Masori assembler (broken) +Tome of fire +Masori armour set (f) +Elidinis' guardian +Honey locust +Ambrosia (1) +Ambrosia (2) +Smelling salts (1) +Smelling salts (2) +Liquid adrenaline (1) +Liquid adrenaline (2) +Blessed crystal scarab (1) +Blessed crystal scarab (2) +Tears of elidinis (1) +Tears of elidinis (2) +Tears of elidinis (3) +Tears of elidinis (4) +Silk dressing (1) +Silk dressing (2) +Nectar (1) +Nectar (2) +Nectar (3) +Nectar (4) +Supplies +The jackal's torch +The wardens +Crondis' capture +Scabaras' capture +Apmeken's capture +Het's capture +Akila's journal +Antique lamp +Maisa's message +Neutralising potion +Mirror +Water container +Tumeken's shadow +Lily of the sands +Grain +Eldritch ashes +Big banana +Fang +Fossilised dung +Scarab dung +Menaphite remedy(1) +Menaphite remedy(2) +Menaphite remedy(4) +Bow of faerdhinen +Ensouled hellhound head +Ensouled hellhound head +Lost bag +Lost bag +Lost bag +Elemental tiara +Catalytic tiara +Gold tiara +Pot (cookout) +Osman's report +Shattered relic hunter (t3) armour set +Shattered relic hunter (t2) armour set +Shattered relic hunter (t1) armour set +Shattered relics mystic ornament kit +Shattered cannon ornament kit +Shattered cane +Placeholder dragon trophy +Placeholder rune trophy +Placeholder adamant trophy +Placeholder mithril trophy +Placeholder steel trophy +Placeholder iron trophy +Placeholder bronze trophy +Shattered teleport scroll +Shattered relics void ornament kit +Shattered boots (t3) +Shattered trousers (t3) +Shattered top (t3) +Shattered hood (t3) +Shattered boots (t2) +Shattered trousers (t2) +Shattered top (t2) +Shattered hood (t2) +Shattered boots (t1) +Shattered trousers (t1) +Shattered top (t1) +Shattered hood (t1) +Shattered banner +Shattered relics variety ornament kit +Blood essence +Ancient mix(1) +Ancient brew(1) +Ancient brew(2) +Ancient brew(4) +Condensed gold +Sigil of the guardian angel +Sigil of last recall +Sigil of remote storage +Sigil of the skiller +Sigil of rampage +Sigil of aggression +Sigil of pious protection +Sigil of finality +Sigil of preservation +Sigil of supreme stamina +Sigil of the serpent +Sigil of versatility +Sigil of the fortune farmer +Sigil of slaughter +Sigil of garments +Sigil of the forager +Sigil of devotion +Sigil of nature +Sigil of the gnomes +Sigil of the barbarians +Sigil of the elves +Sigil of the dwarves +Sigil of prosperity +Sigil of the menacing mage +Sigil of the feral fighter +Sigil of the ruthless ranger +Sigil of escaping +Sigil of binding +Sigil of the porcupine +Sigil of specialised strikes +Sigil of exaggeration +Sigil of mobility +Sigil of the treasure hunter +Sigil of the eternal jeweller +Sigil of the potion master +Sigil of stamina +Sigil of the abyss +Sigil of the craftsman +Sigil of the chef +Sigil of the fletcher +Sigil of the alchemist +Sigil of the smith +Sigil of storage +Sigil of enhanced harvest +Sigil of freedom +Sigil of deft strikes +Sigil of barrows +Sigil of fortification +Sigil of the meticulous mage +Sigil of the rigorous ranger +Sigil of the formidable fighter +Sigil of consistency +Sigil of resilience +Amethyst dart(p++) +Amethyst dart(p+) +Amethyst dart(p) +Raw boar meat +Lizardkicker +Infernal ashes +Abyssal ashes +Malicious ashes +Vile ashes +Fiendish ashes +Antipoison (+)(4) +Antipoison (+)(3) +Antipoison (+)(2) +Antipoison (+)(1) +Antipoison potion (4) +Antipoison potion (3) +Antipoison potion (2) +Antipoison potion (1) +Antipoison (-)(4) +Antipoison (-)(3) +Antipoison (-)(2) +Antipoison (-)(1) +Damp egg +Shadow ancient sceptre +Smoke ancient sceptre +Ice ancient sceptre +Blood ancient sceptre +Runescroll of bloodbark +Runescroll of swampbark +Urium remains +Barricade +Barricade +Bone fragments +Trailblazer graceful ornament kit +Trailblazer rug +Trailblazer globe +Trailblazer tool ornament kit +Trailblazer teleport scroll +Trailblazer banner +Comp. bronze trophy +Comp. iron trophy +Comp. steel trophy +Comp. mithril trophy +Comp. adamant trophy +Comp. rune trophy +Comp. dragon trophy +Trailblazer boots (t1) +Trailblazer trousers (t1) +Trailblazer top (t1) +Trailblazer hood (t1) +Trailblazer boots (t2) +Trailblazer trousers (t2) +Trailblazer top (t2) +Trailblazer hood (t2) +Trailblazer cane +Trailblazer boots (t3) +Trailblazer trousers (t3) +Trailblazer top (t3) +Trailblazer hood (t3) +Ice plateau teleport +Catherby teleport +Fishing guild teleport +Khazard teleport +Barbarian teleport +Waterbirth teleport +Ourania teleport +Moonclan teleport +Cooked mystery meat +Raw mystery meat +Blood pint +Strange old lockpick (full) +Dark squirrel +Raw shrimps +Divine bastion potion(1) +Divine bastion potion(2) +Divine bastion potion(3) +Divine bastion potion(4) +Divine battlemage potion(1) +Divine battlemage potion(2) +Divine battlemage potion(3) +Divine battlemage potion(4) +Blighted vengeance sack +Blighted teleport spell sack +Blighted entangle sack +Blighted ancient ice sack +Blighted super restore(4) +Blighted karambwan +Blighted anglerfish +Blighted manta ray +Rune pouch note +Looting bag note +Twisted horns +Twisted blueprints +Twisted teleport scroll +Twisted banner +Twisted boots (t1) +Twisted trousers (t1) +Twisted coat (t1) +Twisted hat (t1) +Twisted boots (t2) +Twisted trousers (t2) +Twisted coat (t2) +Twisted hat (t2) +Twisted cane +Twisted boots (t3) +Twisted trousers (t3) +Twisted coat (t3) +Twisted hat (t3) +BSO bronze trophy +BSO iron trophy +BSO steel trophy +BSO mithril trophy +BSO adamant trophy +BSO rune trophy +BSO dragon trophy +Target teleport +Birthday cake +Mysterious emblem (tier 5) +Mysterious emblem (tier 4) +Mysterious emblem (tier 3) +Mysterious emblem (tier 2) +Mysterious emblem (tier 1) +Wilderness crabs teleport +Abyssal dragon bones +Trouver parchment +Elven top +Elven legwear +Elven top +Elven skirt +Elven top +Elven skirt +Elven top +Elven gloves +Elven boots +Elven dawn +Divine magic potion(1) +Divine magic potion(2) +Divine magic potion(3) +Divine magic potion(4) +Divine ranging potion(1) +Divine ranging potion(2) +Divine ranging potion(3) +Divine ranging potion(4) +Divine super defence potion(1) +Divine super defence potion(2) +Divine super defence potion(3) +Divine super defence potion(4) +Divine super strength potion(1) +Divine super strength potion(2) +Divine super strength potion(3) +Divine super strength potion(4) +Divine super attack potion(1) +Divine super attack potion(2) +Divine super attack potion(3) +Divine super attack potion(4) +Divine super combat potion(1) +Divine super combat potion(2) +Divine super combat potion(3) +Divine super combat potion(4) +Flyer +Karil's leathertop (LMS) +Imbued saradomin cape +Imbued zamorak cape +Average lamp +Grubby key +Larran's key +Watson teleport +Boots of stone +Bottled dragonbreath (unpowered) +Seed pack +Dragonfruit sapling +Redwood sapling +Celastrus sapling +Greater siren +Mottled eel +Common tench +Bluegill +Fish chunks +Dragon knife(p++) +Dragon knife(p+) +Dragon knife(p) +Dragonfruit pie +Half a dragonfruit pie +Uncooked dragonfruit pie +Hydra bones +Drake bones +Wyrm bones +Dragon hasta(p++) +Dragon hasta(p+) +Dragon hasta(p) +Dragon hasta +Empty bucket pack +Zuriel's robe bottom +Zuriel's robe top +Zuriel's hood +Zuriel's staff +Morrigan's leather chaps +Morrigan's leather body +Morrigan's coif +Morrigan's javelin +Morrigan's throwing axe +Statius's platelegs +Statius's platebody +Statius's full helm +Statius's warhammer +Vesta's plateskirt +Vesta's chainbody +Vesta's longsword +Vesta's spear +Basalt +Stony basalt +Icy basalt +Urt salt +Efh salt +Te salt +Dawnbringer +Bastion potion(1) +Bastion potion(2) +Bastion potion(4) +Battlemage potion(1) +Battlemage potion(2) +Battlemage potion(4) +Bloody bracer +Bryophyta's staff (uncharged) +Large lamp +Staff of light +Uncharged toxic trident (e) +Uncharged trident (e) +Extended super antifire mix(1) +Extended super antifire(1) +Extended super antifire(2) +Extended super antifire(3) +Superior dragon bones +Wrath tiara +Wrath talisman +Dragon metal shard +Super antifire mix(1) +Super antifire potion(1) +Super antifire potion(2) +Super antifire potion(3) +Onyx dragon bolts (e) +Dragonstone dragon bolts (e) +Diamond dragon bolts (e) +Ruby dragon bolts (e) +Emerald dragon bolts (e) +Sapphire dragon bolts (e) +Topaz dragon bolts (e) +Pearl dragon bolts (e) +Jade dragon bolts (e) +Opal dragon bolts (e) +Dragon bolts (unf) +Dragon bolts (p++) +Dragon bolts (p+) +Dragon bolts (p) +Wrath rune +Rock thrownhammer +Tzhaar fire rune pack +Tzhaar earth rune pack +Tzhaar water rune pack +Tzhaar air rune pack +Mushroom pie +Half a mushroom pie +Uncooked mushroom pie +Small lamp +Sulliuscep cap +Volcanic ash +Numulite +Pyrophosphite +Calcite +Mahogany sapling +Teak sapling +Amethyst +Amethyst arrow(p++) +Amethyst arrow(p+) +Amethyst arrow(p) +Amethyst fire arrow (lit) +Amethyst fire arrow +Amethyst javelin(p++) +Amethyst javelin(p+) +Amethyst javelin(p) +Slayer's enchantment +Bracelet of slaughter +Flamtaer bracelet +Expeditious bracelet +Burning amulet(1) +Burning amulet(2) +Burning amulet(3) +Burning amulet(4) +Burning amulet(5) +Amulet of chemistry +Amulet of bounty +Necklace of faith +Necklace of passage(1) +Necklace of passage(2) +Necklace of passage(3) +Necklace of passage(4) +Necklace of passage(5) +Dodgy necklace +Efaritay's aid +Ring of returning(1) +Ring of returning(2) +Ring of returning(3) +Ring of returning(4) +Ring of returning(5) +Ring of pursuit +Mallignum root plank +Overload (+)(4) +Overload (+)(3) +Overload (+)(2) +Overload (+)(1) +Overload (4) +Overload (3) +Overload (2) +Overload (1) +Overload (-)(4) +Overload (-)(3) +Overload (-)(2) +Overload (-)(1) +Xeric's aid (+)(4) +Xeric's aid (+)(3) +Xeric's aid (+)(2) +Xeric's aid (+)(1) +Xeric's aid (4) +Xeric's aid (3) +Xeric's aid (2) +Xeric's aid (1) +Xeric's aid (-)(4) +Xeric's aid (-)(3) +Xeric's aid (-)(2) +Xeric's aid (-)(1) +Prayer enhance (+)(4) +Prayer enhance (+)(3) +Prayer enhance (+)(2) +Prayer enhance (+)(1) +Prayer enhance (4) +Prayer enhance (3) +Prayer enhance (2) +Prayer enhance (1) +Prayer enhance (-)(4) +Prayer enhance (-)(3) +Prayer enhance (-)(2) +Prayer enhance (-)(1) +Revitalisation (+)(4) +Revitalisation (+)(3) +Revitalisation (+)(2) +Revitalisation (+)(1) +Revitalisation potion (4) +Revitalisation potion (3) +Revitalisation potion (2) +Revitalisation potion (1) +Revitalisation (-)(4) +Revitalisation (-)(3) +Revitalisation (-)(2) +Revitalisation (-)(1) +Kodai (+)(4) +Kodai (+)(3) +Kodai (+)(2) +Kodai (+)(1) +Kodai potion (4) +Kodai potion (3) +Kodai potion (2) +Kodai potion (1) +Kodai (-)(4) +Kodai (-)(3) +Kodai (-)(2) +Kodai (-)(1) +Twisted (+)(4) +Twisted (+)(3) +Twisted (+)(2) +Twisted (+)(1) +Twisted potion (4) +Twisted potion (3) +Twisted potion (2) +Twisted potion (1) +Twisted (-)(4) +Twisted (-)(3) +Twisted (-)(2) +Twisted (-)(1) +Elder (+)(4) +Elder (+)(3) +Elder (+)(2) +Elder (+)(1) +Elder potion (4) +Elder potion (3) +Elder potion (2) +Elder potion (1) +Elder (-)(4) +Elder (-)(3) +Elder (-)(2) +Elder (-)(1) +Cicely +Endarkened juice +Stinkhorn mushroom +Buchu seed +Buchu leaf +Grimy buchu leaf +Golpar seed +Golpar +Grimy golpar +Noxifer seed +Noxifer +Grimy noxifer +Medivaemia blossom +Cavern grubs +Keystone crystal +Psykk bat (6) +Raw psykk bat (6) +Murng bat (5) +Raw murng bat (5) +Kryket bat (4) +Raw kryket bat (4) +Phluxia bat (3) +Raw phluxia bat (3) +Giral bat (2) +Raw giral bat (2) +Prael bat (1) +Raw prael bat (1) +Guanic bat (0) +Raw guanic bat (0) +Burnt bat +Kyren fish (6) +Raw kyren fish (6) +Roqed fish (5) +Raw roqed fish (5) +Mycil fish (4) +Raw mycil fish (4) +Brawk fish (3) +Raw brawk fish (3) +Leckish fish (2) +Raw leckish fish (2) +Suphi fish (1) +Raw suphi fish (1) +Pysk fish (0) +Raw pysk fish (0) +Burnt fish +Cave worms +Water-filled gourd vial +Empty gourd vial +Kindling +Empty jug pack +Tome of fire (empty) +Bloodier key +Rune arrow pack +Survival token +Bloody key +Adamant arrow pack +Elemental rune pack +Catalytic rune pack +Ancient magicks tablet +Spear +Reward casket (grandmaster) +Clue scroll (grandmaster) +Double eye patch +Compost pack +Redwood pyre logs +Damaged monkey tail +Botanical pie +Half a botanical pie +Uncooked botanical pie +Golovanova fruit top +Ape atoll teleport +Barrows teleport +Cemetery teleport +Harmony island teleport +West ardougne teleport +Fenkenstrain's castle teleport +Salve graveyard teleport +Mind altar teleport +Draynor manor teleport +Arceuus library teleport +Dragon javelin heads +Cob +Amulet of torture +Ring of suffering +Necklace of anguish +Tormented bracelet +Dragon javelin(p++) +Dragon javelin(p+) +Dragon javelin(p) +Teleport card +Dynamite +Shayzien supply crate +Ensouled dragon head +Ensouled dragon head +Ensouled abyssal head +Ensouled abyssal head +Ensouled aviansie head +Ensouled aviansie head +Ensouled demon head +Ensouled demon head +Ensouled tzhaar head +Ensouled tzhaar head +Ensouled bloodveld head +Ensouled bloodveld head +Ensouled dagannoth head +Ensouled dagannoth head +Ensouled kalphite head +Ensouled kalphite head +Ensouled horror head +Ensouled horror head +Ensouled troll head +Ensouled troll head +Ensouled elf head +Ensouled elf head +Ensouled ogre head +Ensouled ogre head +Ensouled giant head +Ensouled giant head +Ensouled chaos druid head +Ensouled chaos druid head +Ensouled dog head +Ensouled dog head +Ensouled unicorn head +Ensouled unicorn head +Ensouled bear head +Ensouled bear head +Ensouled scorpion head +Ensouled scorpion head +Ensouled minotaur head +Ensouled minotaur head +Ensouled imp head +Ensouled monkey head +Ensouled monkey head +Ensouled goblin head +Ensouled goblin head +Burnt anglerfish +Raw anglerfish +Sandworms pack +Sandworms +Lizardman fang +Xerician fabric +Tester Gift Box +Abyssal dagger (p++) +Abyssal dagger (p+) +Abyssal dagger (p) +Basket pack +Sack pack +Plant pot pack +Platinum token +Bone bolt pack +Old school bond +Anti-venom+(1) +Anti-venom+(2) +Anti-venom+(3) +Anti-venom(1) +Anti-venom(2) +Anti-venom(3) +Thanksgiving dinner +Eye of newt pack +Olive oil pack +Target teleport scroll +Saradomin's tear +Clue box +Magic shortbow scroll +Ring of wealth scroll +Senntisten teleport +Paddewwa teleport +Lassar teleport +Kharyrll teleport +Ghorrock teleport +Dareeyak teleport +Carrallanger teleport +Annakarl teleport +Magic imp box pack +Box trap pack +Bird snare pack +Chaos rune pack +Mind rune pack +Fire rune pack +Earth rune pack +Water rune pack +Air rune pack +Super combat potion(1) +Super combat potion(2) +Super combat potion(3) +Amylase crystal +Stamina mix(1) +Stamina potion(1) +Stamina potion(2) +Stamina potion(3) +Top hat & monocle +Pirate hat & patch +Lava scale +Ring of wealth (1) +Ring of wealth (2) +Ring of wealth (3) +Ring of wealth (4) +Ring of wealth (5) +Amulet of glory(6) +Amulet of glory(5) +Combat bracelet(5) +Combat bracelet(6) +Skills necklace(5) +Skills necklace(6) +Amulet of glory (t5) +Amulet of glory (t6) +Extended antifire mix(1) +Black chinchompa +Extended antifire(1) +Extended antifire(2) +Extended antifire(3) +Lava dragon bones +Dark fishing bait +Burnt dark crab +Raw dark crab +Broken pickaxe +Box of chocolate strawberries +Box of chocolate strawberries +Chocolate strawberry +Unfinished broad bolt pack +Broad arrowhead pack +Bait pack +Feather pack +Water-filled vial pack +Empty vial pack +Unfinished broad bolts +Broad arrowheads +Godsword shards 2 & 3 +Godsword shards 1 & 3 +Godsword shards 1 & 2 +Crystal shield 1/10 (i) +Crystal shield 2/10 (i) +Crystal shield 3/10 (i) +Crystal shield 4/10 (i) +Crystal shield 5/10 (i) +Crystal shield 6/10 (i) +Crystal shield 7/10 (i) +Crystal shield 8/10 (i) +Crystal shield 9/10 (i) +Crystal shield full (i) +New crystal shield (i) +Glassblowing book +Feather +Zamorak mix(1) +Hunting mix(1) +Magic mix(1) +Ranging mix(1) +Antifire mix(1) +Antidote+ mix(1) +Super def. mix(1) +Super restore mix(1) +Magic essence mix(1) +Super str. mix(1) +Super energy mix(1) +Fishing mix(1) +Anti-poison supermix(1) +Superattack mix(1) +Prayer mix(1) +Agility mix(1) +Defence mix(1) +Energy mix(1) +Restore mix(1) +Combat mix(1) +Strength mix(1) +Relicym's mix(1) +Antipoison mix(1) +Attack mix(1) +Rune hasta(p++) +Rune hasta(p+) +Rune hasta(p) +Adamant hasta(p++) +Adamant hasta(p+) +Adamant hasta(p) +Mithril hasta(p++) +Mithril hasta(p+) +Mithril hasta(p) +Steel hasta(p++) +Steel hasta(p+) +Steel hasta(p) +Iron hasta(p++) +Iron hasta(p+) +Iron hasta(p) +Bronze hasta(p++) +Bronze hasta(p+) +Bronze hasta(p) +Fish offcuts +Leaping sturgeon +Leaping salmon +Leaping trout +Caviar +Roe +Cavalier mask +Anchovy paste +Anchovy oil +Imp repellent +Impling jar +Dragon arrowtips +Dragon dart(p++) +Dragon dart(p+) +Dragon dart tip +Dragon dart(p) +Dragon arrow(p++) +Dragon arrow(p+) +Dragon arrow(p) +Dragon fire arrow (lit) +Dragon fire arrow +Shrunk ogleroot +Dwarven helmet +Half certificate +Half certificate +Newspaper +Newspaper +Phoenix crossbow +Phoenix crossbow +Huge lamp +Regen bracelet +Berserker necklace +Combat bracelet +Combat bracelet(1) +Combat bracelet(2) +Combat bracelet(3) +Combat bracelet(4) +Skills necklace +Skills necklace(1) +Skills necklace(2) +Skills necklace(3) +Skills necklace(4) +Abyssal bracelet(1) +Abyssal bracelet(2) +Abyssal bracelet(3) +Abyssal bracelet(4) +Abyssal bracelet(5) +Phoenix necklace +Inoculation bracelet +Castle wars bracelet(1) +Castle wars bracelet(2) +Castle wars bracelet(3) +Bracelet of clay +Bracelet mould +Ancient mace +Goblin book +Cave goblin wire +Swamp weed +Light orb +Frog-leather boots +Frog-leather chaps +Frog-leather body +Slayer bell +Nail beast nails +Sanfew serum(1) +Sanfew serum(2) +Sanfew serum(3) +Mixture - step 2(1) +Mixture - step 2(2) +Mixture - step 2(3) +Mixture - step 2(4) +Mixture - step 1(1) +Mixture - step 1(2) +Mixture - step 1(3) +Mixture - step 1(4) +Wooden cat +Apricot cream pie +Yak-hide armour +Yak-hide armour +Cured yak-hide +Yak-hide +Raw yak meat +Hair +Split log +Arctic pine logs +Arctic pyre logs +Polished buttons +Amulet of glory (t) +Amulet of glory (t1) +Amulet of glory (t2) +Amulet of glory (t3) +Long kebbit bolts +Kebbit bolts +Hunters' crossbow +Noose wand +Swamp lizard +Black salamander +Red salamander +Orange salamander +Burnt rainbow fish +Raw rainbow fish +Rainbow fish +Rabbit foot +Barb-tail harpoon +Dashing kebbit fur +Spotted kebbit fur +Desert devil fur +Common kebbit fur +Feldip weasel fur +Polar kebbit fur +Dark kebbit fur +Kebbit claws +Kebbit teeth +Long kebbit spike +Kebbit spike +Kyatt fur +Tatty kyatt fur +Graahk fur +Tatty graahk fur +Larupia fur +Tatty larupia fur +Orange feather +Yellow feather +Blue feather +Red feather +Stripy feather +Black spiky vambraces +Red spiky vambraces +Blue spiky vambraces +Green spiky vambraces +Spiky vambraces +Red chinchompa +Chinchompa +Rabbit snare +Teasing stick +Magic box +Ruby harvest +Sapphire glacialis +Snowy knight +Black warlock +Butterfly jar +Butterfly net +Box trap +Bird snare +Hunter potion(1) +Hunter potion(2) +Hunter potion(4) +Spicy minced meat +Spicy tomato +Skewered beast +Burnt beast meat +Roast beast meat +Raw beast meat +Skewered bird meat +Burnt bird meat +Roast bird meat +Raw bird meat +Mahogany fancy dress box +Teak fancy dress box +Oak fancy dress box +M. treasure chest +Teak treasure chest +Oak treasure chest +Mahogany armour case +Teak armour case +Oak armour case +Marble magic wardrobe +Gilded magic wardrobe +Mahogany magic wardrobe +Carved teak magic wardrobe +Teak magic wardrobe +Carved oak magic wardrobe +Oak magic wardrobe +Mahogany toy box +Teak toy box +Oak toy box +Magic cape rack +Marble cape rack +Gilded cape rack +Mahogany cape rack +Teak cape rack +Oak cape rack +Combat potion(1) +Combat potion(2) +Combat potion(4) +Desert goat horn +Mind helmet +Mind shield +Elemental helmet +Proselyte tasset +Proselyte cuisse +Proselyte hauberk +Proselyte sallet +Torch +Citizen shoes +Citizen trousers +Citizen top +Vyrewatch shoes +Vyrewatch legs +Vyrewatch top +Tyras helm +Bolt mould +Mith grapple +Mith grapple +Silver bolts (p++) +Runite bolts (p++) +Adamant bolts (p++) +Mithril bolts (p++) +Steel bolts (p++) +Iron bolts (p++) +Silver bolts (p+) +Runite bolts (p+) +Adamant bolts (p+) +Mithril bolts (p+) +Steel bolts (p+) +Iron bolts (p+) +Silver bolts (p) +Runite bolts (p) +Adamant bolts (p) +Mithril bolts (p) +Steel bolts (p) +Iron bolts (p) +Onyx bolts (e) +Dragonstone bolts (e) +Diamond bolts (e) +Ruby bolts (e) +Emerald bolts (e) +Sapphire bolts (e) +Topaz bolts (e) +Pearl bolts (e) +Opal bolts (e) +Astral rune +Locust meat +Stone seal +Gold seal +Stone statuette +Pottery statuette +Golden statuette +Pottery scarab +Stone scarab +Golden scarab +Ivory comb +Stronghold notes +Security book +Sweetgrubs +Bridge section +Scrapey bark +Bowl of blue water +Bowl of red water +Red flowers +Blue flowers +Scrapey tree logs +Lumber patch +Pipe section +Hat eyepatch +Bandana eyepatch +Bandana eyepatch +Bandana eyepatch +Bandana eyepatch +Black mask (1) +Black mask (2) +Black mask (3) +Black mask (4) +Black mask (5) +Black mask (6) +Black mask (7) +Black mask (8) +Black mask (9) +Bone bolts +Dorgeshuun crossbow +Bone dagger (p++) +Bone dagger (p+) +Bone dagger (p) +Bone dagger +Timber beam +Saw +Bolt of cloth +Magic stone +Marble block +Gold leaf +Mahogany plank +Teak plank +Oak plank +Mahogany telescope +Teak telescope +Oak telescope +Large orrery +Small orrery +Armillary sphere +Celestial globe +Lunar globe +Ornamental globe +Globe +Crystal of power +Elemental sphere +Crystal ball +Gilded wardrobe +Mahogany wardrobe +Teak wardrobe +Teak drawers +Oak wardrobe +Oak drawers +Shoe box +Gilded dresser +Mahogany dresser +Fancy teak dresser +Teak dresser +Oak dresser +Oak shaving stand +Shaving stand +Gilded clock +Teak clock +Oak clock +Gilded four-poster +Four-poster bed +Large teak bed +Teak bed +Large oak bed +Oak bed +Wooden bed +Gilded bench +Mahogany bench +Carved teak bench +Teak dining bench +Carved oak bench +Oak bench +Wooden bench +Opulent table +Mahogany table +Carved teak table +Teak table +Carved oak table +Oak dining table +Wood dining table +Mahogany demon +Mahogany eagle +Teak demon lectern +Teak eagle lectern +Demon lectern +Eagle lectern +Oak lectern +Teak kitchen table +Oak kitchen table +Kitchen table +Chef's delight +Dragon bitter +Greenman's ale +Asgarnian ale +Cider barrel +Beer barrel +Mahogany bookcase +Oak bookcase +Bookcase +Mahogany armchair +Teak armchair +Oak armchair +Oak chair +Rocking chair +Wooden chair +Crude chair +Bagged roses +Bagged marigolds +Bagged sunflower +Bagged bluebells +Bagged daffodils +Bagged flower +Tall box hedge +Tall fancy hedge +Fancy hedge +Topiary hedge +Nice hedge +Thorny hedge +Bagged plant 3 +Bagged plant 2 +Bagged plant 1 +Bagged magic tree +Bagged yew tree +Bagged maple tree +Bagged willow tree +Bagged oak tree +Bagged nice tree +Bagged dead tree +Enchant onyx +Enchant dragonstone +Enchant diamond +Enchant ruby or topaz +Enchant emerald or jade +Enchant sapphire or opal +Bones to peaches +Bones to bananas +Teleport to house +Watchtower teleport +Ardougne teleport +Camelot teleport +Falador teleport +Lumbridge teleport +Varrock teleport +Burnt shrimp +Axe handle +Burnt monkfish +Raw monkfish +Tortoise shell +Pure essence +Field ration +Bottle of wine +Ancient mjolnir +Snake hide +Toy cat +Toy mouse (wound) +Toy mouse +Toy doll (wound) +Toy doll +Toy soldier (wound) +Toy soldier +Chef's delight +Cider +Moonlight mead +Dragon bitter +Greenman's ale +Asgarnian ale +Beer glass +Beer +Tea leaves +Teapot +Teapot with leaves +Teapot +Teapot with leaves +Pot of tea (1) +Pot of tea (2) +Pot of tea (3) +Pot of tea (4) +Teapot +Teapot with leaves +Kettle +Pugel +Gadderhammer +Guthix balance(1) +Guthix balance(2) +Guthix balance(4) +Guthix balance (unf) +Guthix balance (unf) +Guthix balance (unf) +Guthix balance (unf) +Crate +Bucket of rubble +Bucket of rubble +Bucket of rubble +Burnt jubbly +Raw jubbly +Cooked crab meat +Pot of cornflour +Cornflour +Cleaver +Meat tenderiser +Kitchen knife +Rolling pin +Skewer +Frying pan +Spatula +Spork +Egg whisk +Wooden spoon +Paddle +Skewered chompy +Cooked chompy +Burnt chompy +Skewered rabbit +Roast rabbit +Burnt rabbit +Half a summer pie +Summer pie +Raw summer pie +Part summer pie +Part summer pie +Half a wild pie +Wild pie +Raw wild pie +Part wild pie +Part wild pie +Half an admiral pie +Admiral pie +Raw admiral pie +Part admiral pie +Part admiral pie +Half a fish pie +Fish pie +Raw fish pie +Part fish pie +Part fish pie +Half a garden pie +Garden pie +Raw garden pie +Part garden pie +Part garden pie +Mud pie +Raw mud pie +Part mud pie +Part mud pie +Pie recipe book +Insulated boots +Pirate leggings (brown) +Pirate bandana (brown) +Stripy pirate shirt (blue) +Pirate leggings (blue) +Pirate bandana (blue) +Stripy pirate shirt (brown) +Pirate leggings (red) +Pirate bandana (red) +Stripy pirate shirt (red) +Pirate leggings (white) +Pirate boots +Pirate bandana (white) +Stripy pirate shirt (white) +Burnt mushroom +Burnt onion +Burnt egg +Sweetcorn +Chopped tuna +Fried onions +Fried mushrooms +Sliced mushrooms +Scrambled egg +Uncooked egg +Chopped garlic +Spicy sauce +Minced meat +Tuna and corn +Mushroom & onion +Egg and tomato +Chilli con carne +Tuna potato +Mushroom potato +Egg potato +Chilli potato +Unlit bug lantern +Granite (5kg) +Granite (2kg) +Granite (500g) +Sandstone (10kg) +Sandstone (5kg) +Sandstone (2kg) +Sandstone (1kg) +Triangle sandwich +Pink cape +Arena book +Fur +Wyvern bones +Tiny lamp +Choc-ice +Zamorak mjolnir +Saradomin mjolnir +Guthix mjolnir +Black desert robe +Black desert shirt +Dragon axe head +Dagannoth bones +Potato with cheese +Potato with butter +Baked potato +Burnt potato +Pat of butter +Saradomin brew(1) +Saradomin brew(2) +Saradomin brew(4) +Ground seaweed +Ground guam +An empty box +Empty fishbowl +White kiteshield +White sq shield +White gloves +White plateskirt +White platelegs +White full helm +White med helm +White boots +White platebody +White chainbody +White warhammer +White scimitar +White 2h sword +White longsword +White sword +White magic staff +White mace +White halberd +White dagger(p++) +White dagger(p+) +White dagger(p) +White dagger +White battleaxe +White claws +Amulet of fury +Ring of stone +Tzhaar-ket-em +Compost potion(1) +Compost potion(2) +Compost potion(4) +Maple blackjack(d) +Maple blackjack(o) +Maple blackjack +Willow blackjack(d) +Willow blackjack(o) +Oak blackjack(d) +Oak blackjack(o) +Menaphite red kilt +Menaphite red robe +Menaphite red top +Menaphite red hat +Menaphite purple kilt +Menaphite purple robe +Menaphite purple top +Menaphite purple hat +Desert legs +Desert top +Desert robes +Desert top +Fez +Villager armband +Villager sandals +Villager hat +Villager robe +Tribal top +Villager armband +Villager sandals +Villager hat +Villager robe +Tribal top +Villager armband +Villager sandals +Villager hat +Villager robe +Tribal top +Villager sandals +Villager armband +Villager hat +Villager robe +Tribal top +Tribal mask +Tribal mask +Tribal mask +Proboscis +Red topaz machete +Jade machete +Opal machete +Gout tuber +Trading sticks +Skewer stick +Spider on shaft +Burnt spider +Spider on shaft +Spider on stick +Spider on shaft +Spider on stick +Spider carcass +Snakeskin +Snake hide +Thatch spar dense +Thatch spar med +Thatch spar light +Broodoo shield +Broodoo shield (1) +Broodoo shield (2) +Broodoo shield (3) +Broodoo shield (4) +Broodoo shield (5) +Broodoo shield (6) +Broodoo shield (7) +Broodoo shield (8) +Broodoo shield (9) +Broodoo shield (10) +Broodoo shield +Broodoo shield (1) +Broodoo shield (2) +Broodoo shield (3) +Broodoo shield (4) +Broodoo shield (5) +Broodoo shield (6) +Broodoo shield (7) +Broodoo shield (8) +Broodoo shield (9) +Broodoo shield (10) +Broodoo shield +Broodoo shield (1) +Broodoo shield (2) +Broodoo shield (3) +Broodoo shield (4) +Broodoo shield (5) +Broodoo shield (6) +Broodoo shield (7) +Broodoo shield (8) +Broodoo shield (9) +Broodoo shield (10) +Mahogany pyre logs +Teak pyre logs +Stretched hide +Flattened hide +Circular hide +Fibula piece +Ribcage piece +Skull piece +Rock-shell splinter +Rock-shell shard +Rock-shell chunk +Dagannoth hide +Skeletal gloves +Rock-shell gloves +Spined gloves +Skeletal boots +Rock-shell boots +Spined boots +Skeletal bottoms +Skeletal top +Skeletal helm +Spined chaps +Spined body +Spined helm +Rock-shell legs +Rock-shell plate +Rock-shell helm +The great divide +Eastern settlement +Eastern discovery +Prifddinas' history +Bronze bolts (p++) +Bronze bolts (p+) +Weeds +Pre-nature amulet +Magic string +Plant cure +Supercompost +Compost +Magic leaves +Maple leaves +Yew leaves +Willow leaves +Oak leaves +Leaves +Barley malt +Apple mush +Burnt sweetcorn +Cooked sweetcorn +Watermelon slice +Half coconut +Tomatoes(4) +Tomatoes(3) +Tomatoes(2) +Tomatoes(1) +Antidote++(1) +Antidote++(2) +Antidote++(3) +Antidote+(1) +Antidote+(2) +Antidote+(3) +Willow branch +Cider(m4) +Cider(m3) +Cider(m2) +Cider(m1) +Slayer's respite(m4) +Slayer's respite(m3) +Slayer's respite(m2) +Slayer's respite(m1) +Chef's delight(m4) +Chef's delight(m3) +Chef's delight(m2) +Chef's delight(m1) +Axeman's folly(m4) +Axeman's folly(m3) +Axeman's folly(m2) +Axeman's folly(m1) +Moonlight mead(m4) +Moonlight mead(m3) +Moonlight mead(m2) +Moonlight mead(m1) +Dragon bitter(m4) +Dragon bitter(m3) +Dragon bitter(m2) +Dragon bitter(m1) +Mind bomb(m4) +Mind bomb(m3) +Mind bomb(m2) +Mind bomb(m1) +Greenmans ale(m4) +Greenmans ale(m3) +Greenmans ale(m2) +Greenmans ale(m1) +Asgarnian ale(m4) +Asgarnian ale(m3) +Asgarnian ale(m2) +Asgarnian ale(m1) +Dwarven stout(m4) +Dwarven stout(m3) +Dwarven stout(m2) +Dwarven stout(m1) +Cider(4) +Cider(3) +Cider(2) +Cider(1) +Slayer's respite(4) +Slayer's respite(3) +Slayer's respite(2) +Slayer's respite(1) +Chef's delight(4) +Chef's delight(3) +Chef's delight(2) +Chef's delight(1) +Axeman's folly(4) +Axeman's folly(3) +Axeman's folly(2) +Axeman's folly(1) +Moonlight mead(4) +Moonlight mead(3) +Moonlight mead(2) +Moonlight mead(1) +Dragon bitter(4) +Dragon bitter(3) +Dragon bitter(2) +Dragon bitter(1) +Mind bomb(4) +Mind bomb(3) +Mind bomb(2) +Mind bomb(1) +Greenmans ale(4) +Greenmans ale(3) +Greenmans ale(2) +Greenmans ale(1) +Asgarnian ale(4) +Asgarnian ale(3) +Asgarnian ale(2) +Asgarnian ale(1) +Dwarven stout(4) +Dwarven stout(3) +Dwarven stout(2) +Dwarven stout(1) +Calquat keg +Ale yeast +Mature cider +Cider +Slayer's respite(m) +Slayer's respite +Chef's delight(m) +Chef's delight +Axeman's folly(m) +Axeman's folly +Moonlight mead(m) +Dwarven stout(m) +Dragon bitter(m) +Greenman's ale(m) +Mature wmb +Asgarnian ale(m) +Black spear(p++) +Black spear(p+) +Stool +Dragon spear(p++) +Rune spear(p++) +Adamant spear(p++) +Mithril spear(p++) +Steel spear(p++) +Iron spear(p++) +Bronze spear(p++) +Dragon spear(p+) +Rune spear(p+) +Adamant spear(p+) +Mithril spear(p+) +Steel spear(p+) +Iron spear(p+) +Bronze spear(p+) +Black dagger(p++) +Dragon dagger(p++) +Rune dagger(p++) +Adamant dagger(p++) +Mithril dagger(p++) +Steel dagger(p++) +Bronze dagger(p++) +Iron dagger(p++) +Black dagger(p+) +Dragon dagger(p+) +Rune dagger(p+) +Adamant dagger(p+) +Mithril dagger(p+) +Steel dagger(p+) +Bronze dagger(p+) +Iron dagger(p+) +Rune knife(p++) +Adamant knife(p++) +Black knife(p++) +Mithril knife(p++) +Steel knife(p++) +Iron knife(p++) +Bronze knife(p++) +Rune knife(p+) +Adamant knife(p+) +Black knife(p+) +Mithril knife(p+) +Steel knife(p+) +Iron knife(p+) +Bronze knife(p+) +Rune javelin(p++) +Adamant javelin(p++) +Mithril javelin(p++) +Steel javelin(p++) +Iron javelin(p++) +Bronze javelin(p++) +Rune javelin(p+) +Adamant javelin(p+) +Mithril javelin(p+) +Steel javelin(p+) +Iron javelin(p+) +Bronze javelin(p+) +Rune dart(p++) +Adamant dart(p++) +Mithril dart(p++) +Black dart(p++) +Steel dart(p++) +Iron dart(p++) +Bronze dart(p++) +Rune dart(p+) +Adamant dart(p+) +Mithril dart(p+) +Black dart(p+) +Steel dart(p+) +Iron dart(p+) +Bronze dart(p+) +Rune arrow(p++) +Adamant arrow(p++) +Mithril arrow(p++) +Steel arrow(p++) +Iron arrow(p++) +Bronze arrow(p++) +Rune arrow(p+) +Adamant arrow(p+) +Mithril arrow(p+) +Steel arrow(p+) +Iron arrow(p+) +Bronze arrow(p+) +Initiate cuisse +Initiate hauberk +Initiate sallet +Blood tiara +Death tiara +Chaos tiara +Nature tiara +Cosmic tiara +Fire tiara +Earth tiara +Body tiara +Water tiara +Mind tiara +Air tiara +Tiara mould +Binding necklace +Elemental talisman +Calquat sapling +Palm sapling +Papaya sapling +Pineapple sapling +Curry sapling +Orange sapling +Banana sapling +Apple sapling +Cabbages(9) +Cabbages(8) +Cabbages(7) +Cabbages(6) +Cabbages(5) +Cabbages(4) +Cabbages(3) +Cabbages(2) +Cabbages(1) +Onions(9) +Onions(8) +Onions(7) +Onions(6) +Onions(5) +Onions(4) +Onions(3) +Onions(2) +Onions(1) +Potatoes(9) +Potatoes(8) +Potatoes(7) +Potatoes(6) +Potatoes(5) +Potatoes(4) +Potatoes(3) +Potatoes(2) +Potatoes(1) +Empty sack +Bananas(4) +Bananas(3) +Bananas(2) +Bananas(1) +Strawberries(4) +Strawberries(3) +Strawberries(2) +Strawberries(1) +Oranges(4) +Oranges(3) +Oranges(2) +Oranges(1) +Apples(4) +Apples(3) +Apples(2) +Apples(1) +Basket +Magic sapling +Yew sapling +Maple sapling +Willow sapling +Oak sapling +Plant pot +Filled plant pot +Unfired plant pot +Empty plant pot +Gardening boots +Seed dibber +Rake +Watering can +Secateurs +Gardening trowel +Bird nest +Dwarf +Skirt +Skirt +Skirt +Shorts +Shorts +Shorts +Trousers +Trousers +Trousers +Shirt +Shirt +Shirt +Woven top +Woven top +Woven top +Bank lottery ticket +Lottery ticket +Bone club +Bone spear +Mining helmet +Burnt cave eel +Raw cave eel +Verac's plateskirt 0 +Verac's brassard 0 +Verac's flail 0 +Verac's helm 0 +Torag's platelegs 0 +Torag's platebody 0 +Torag's hammers 0 +Torag's helm 0 +Karil's leatherskirt 0 +Karil's leathertop 0 +Karil's crossbow 0 +Karil's coif 0 +Guthan's chainskirt 0 +Guthan's platebody 0 +Guthan's warspear 0 +Guthan's helm 0 +Dharok's platelegs 0 +Dharok's platebody 0 +Dharok's greataxe 0 +Dharok's helm 0 +Ahrim's robeskirt 0 +Ahrim's robetop 0 +Ahrim's staff 0 +Ahrim's hood 0 +Ogre coffin key +Relicym's balm(1) +Relicym's balm(2) +Relicym's balm(3) +Relicym's balm(4) +Unfinished potion +Ourg bones +Raurg bones +Fayrg bones +Comp ogre bow +Unstrung comp bow +Black nails +Zogre bones +Rune brutal +Adamant brutal +Mithril brutal +Black brutal +Steel brutal +Iron brutal +Bronze brutal +Lava rune +Mud rune +Smoke rune +Dust rune +Mist rune +Steam rune +Pile of salt +Bucket of sap +Linen +Ancient staff +Garlic powder +Bandit's brew +Broken plate +Super kebab +Willow blackjack +Karidian disguise +Fake beard +Kharidian headpiece +Dragon scimitar +Dragon plateskirt +Black spear(p) +Black spear +Spiny helmet +Bullseye lantern +Bullseye lantern (empty) +Oil lantern +Empty oil lantern +Candle lantern +Candle lantern +Oil lamp +Giant frog legs +Pole +Rope +Broken pole +Muddy rock +Mud +Herb tea mix +Herb tea mix +Herb tea mix +Herb tea mix +Herb tea mix +Herb tea mix +Herb tea mix +Herb tea mix +Herb tea mix +Herb tea mix +Ruined herb tea +Cup of hot water +Cup of water +Bowl of hot water +Pot lid +Unfired pot lid +Airtight pot +Guthix rest(1) +Guthix rest(2) +Guthix rest(4) +Team-50 cape +Team-49 cape +Team-48 cape +Team-47 cape +Team-46 cape +Team-45 cape +Team-44 cape +Team-43 cape +Team-42 cape +Team-41 cape +Team-40 cape +Team-39 cape +Team-38 cape +Team-37 cape +Team-36 cape +Team-35 cape +Team-34 cape +Team-33 cape +Team-32 cape +Team-31 cape +Team-30 cape +Team-29 cape +Team-28 cape +Team-27 cape +Team-26 cape +Team-25 cape +Team-24 cape +Team-23 cape +Team-22 cape +Team-21 cape +Team-20 cape +Team-19 cape +Team-18 cape +Team-17 cape +Team-16 cape +Team-15 cape +Team-14 cape +Team-13 cape +Team-12 cape +Team-11 cape +Team-10 cape +Team-9 cape +Team-8 cape +Team-7 cape +Team-6 cape +Team-5 cape +Team-4 cape +Team-3 cape +Team-2 cape +Team-1 cape +Crystal singing for beginners +Ham boots +Ham gloves +Ham logo +Ham cloak +Ham hood +Ham robe +Ham shirt +Cooked meat +Cooked chicken +Raw chicken +Raw beef +New crystal shield +New crystal bow +Cadarn lineage +Slayer's staff +Nose peg +Earmuffs +Facemask +Rock hammer +Bag of salt +Mirror shield +Mystic boots +Mystic gloves +Mystic robe bottom +Mystic robe top +Mystic hat +Dragon platelegs +Damp tinderbox +Barricade +Toolkit +Bandages +Climbing rope +Explosive potion +Rock +Banana stew +Monkey bar +Monkey nuts +Spare controls +Iron sickle +Games necklace(1) +Games necklace(2) +Games necklace(3) +Games necklace(4) +Games necklace(5) +Games necklace(6) +Games necklace(7) +Games necklace(8) +Tankard +Beer tankard +Keg of beer +Fremennik gloves +Fremennik hat +Fremennik skirt +Fremennik robe +Fremennik boots +Fremennik black cloak +Fremennik pink cloak +Fremennik purple cloak +Fremennik teal cloak +Fremennik yellow cloak +Fremennik grey cloak +Fremennik red cloak +Fremennik blue shirt +Fremennik red shirt +Fremennik beige shirt +Fremennik grey shirt +Fremennik brown shirt +Fremennik green cloak +Fremennik blue cloak +Fremennik brown cloak +Fremennik cyan cloak +Farseer helm +Warrior helm +Berserker helm +Archer helm +Sticky red goop +Flamtaer hammer +Magic pyre logs +Yew pyre logs +Maple pyre logs +Willow pyre logs +Oak pyre logs +Pyre logs +Sacred oil(1) +Sacred oil(2) +Sacred oil(3) +Sacred oil(4) +Olive oil(1) +Olive oil(2) +Olive oil(3) +Olive oil(4) +Limestone brick +Serum 207 (1) +Serum 207 (2) +Serum 207 (4) +Unfinished potion +Fiyr remains +Asyn remains +Riyl remains +Phrin remains +Loar remains +Burnt eel +Cooked slimy eel +Raw slimy eel +Sample bottle +Burnt snail +Fat snail meat +Lean snail meat +Thin snail meat +Fat snail +Lean snail +Thin snail +Blamish blue shell +Blamish ochre shell +Blamish red shell +Blamish myre shell +Blamish bark shell +Blamish blue shell +Blamish ochre shell +Blamish red shell +Blamish myre shell +Bruise blue snelm (pointed) +Ochre snelm (pointed) +Blood'n'tar snelm (pointed) +Myre snelm (pointed) +Broken bark snelm +Bruise blue snelm +Ochre snelm +Blood'n'tar snelm +Myre snelm +Vampyre dust +Bark +Cooked rabbit +Raw rabbit +Barrel +Limestone +Sulphur +Dragon halberd +Rune halberd +Adamant halberd +Mithril halberd +Black halberd +Steel halberd +Iron halberd +Bronze halberd +Cleaning cloth +Monkey bones +Sliced banana +Karambwan vessel +Karambwan vessel +Burnt karambwan +Raw karambwan +Jogre bones +Shaikahan bones +Granite shield +Spiked boots +Climbing boots +Black claws +Black dart(p) +Black dart +Lava battlestaff +Magic potion(1) +Magic potion(2) +Magic potion(4) +Agility potion(1) +Agility potion(2) +Agility potion(4) +Super restore(1) +Super restore(2) +Super restore(4) +Super energy(1) +Super energy(2) +Super energy(4) +Energy potion(1) +Energy potion(2) +Energy potion(4) +Sickle mould +Mort myre pear +Mort myre stem +Mort myre fungus +Washing bowl +Rotten food +Moonlight mead +Purple gloves +Purple hat +Purple robe bottoms +Purple robe top +Purple boots +Teal gloves +Teal hat +Teal robe bottoms +Teal robe top +Teal boots +Yellow gloves +Yellow hat +Yellow robe bottoms +Yellow robe top +Yellow boots +Red gloves +Red hat +Red robe bottoms +Red robe top +Red boots +Grey gloves +Grey hat +Grey robe bottoms +Grey robe top +Grey boots +Elemental shield +Ruined chompy +Cooked chompy +Raw chompy +Achey tree logs +Wolf bones +Ring of wealth +Ring of life +Ring of forging +Ring of dueling(1) +Ring of dueling(2) +Ring of dueling(3) +Ring of dueling(4) +Ring of dueling(5) +Ring of dueling(6) +Ring of dueling(7) +Ring of dueling(8) +Ring of recoil +Rune fire arrow (lit) +Rune fire arrow +Adamant fire arrow (lit) +Adamant fire arrow +Mithril fire arrow (lit) +Mithril fire arrow +Steel fire arrow (lit) +Steel fire arrow +Iron fire arrow (lit) +Iron fire arrow +Bones +Rotten tomato +Pot of flour +Raw shrimps +Black flowers +White flowers +Mixed flowers +Orange flowers +Purple flowers +Yellow flowers +Blue flowers +Red flowers +Assorted flowers +Antifire potion(1) +Antifire potion(2) +Antifire potion(4) +Zamorak brew(4) +Superantipoison(4) +Antipoison(4) +Ranging potion(4) +Super defence(4) +Super strength(4) +Fishing potion(4) +Super attack(4) +Prayer potion(4) +Defence potion(4) +Restore potion(4) +Attack potion(4) +Burnt oomlie +Ground bat bones +Shield right half +Runite bar +Adamantite bar +Mithril bar +Gold bar +Silver bar +Steel bar +Iron bar +Bronze bar +Hammer +Burnt oomlie wrap +Cooked oomlie wrap +Wrapped oomlie +Raw oomlie +Half an apple pie +Half a redberry pie +Half a meat pie +Burnt pie +Meat pie +Redberry pie +Apple pie +Uncooked berry pie +Uncooked meat pie +Uncooked apple pie +Pie shell +Pie dish +Burnt bread +Bread +Bread dough +Burnt pizza +1/2 pineapple pizza +Pineapple pizza +1/2 anchovy pizza +Anchovy pizza +1/2 meat pizza +Meat pizza +1/2 plain pizza +Plain pizza +Uncooked pizza +Incomplete pizza +Pizza base +Vegetable batta +Fruit batta +Cheese+tom batta +Toad batta +Worm batta +Burnt batta +Premade t'd crunch +Premade s'y crunch +Premade ch' crunch +Premade w'm crun' +Premade veg ball +Premade worm hole +Premade ttl +Premade choc bomb +Premade veg batta +Premade fr't batta +Premade c+t batta +Premade t'd batta +Premade w'm batta +Toad crunchies +Spicy crunchies +Chocchip crunchies +Worm crunchies +Rock-climbing boots +Raw crunchies +Burnt crunchies +Veg ball +Worm hole +Tangled toad's legs +Chocolate bomb +Burnt gnomebowl +Gianne dough +Gnome spice +Gianne's cook book +Gnomebowl mould +Crunchy tray +Batta tin +King worm +Swamp toad +Burnt meat +Burnt chicken +Raw chicken +Raw bear meat +Raw rat meat +Raw beef +Pot of cream +Equa leaves +Lime slices +Lime chunks +Lime +Pineapple ring +Pineapple chunks +Orange slices +Orange chunks +Lemon slices +Lemon chunks +Lemon +Odd cocktail +Odd cocktail +Odd cocktail +Odd cocktail +Drunk dragon +Fruit blast +Short green guy +Choc saturday +Blurberry special +Wizard blizzard +Pineapple punch +Premade wiz blz'd +Premade sgg +Premade p' punch +Premade fr' blast +Premade dr' dragon +Premade choc s'dy +Premade blurb' sp. +Cocktail glass +Cocktail shaker +Cocktail guide +Brandy +Gin +Whisky +Vodka +Burnt curry +Curry +Uncooked curry +Spice +Burnt stew +Stew +Uncooked stew +Incomplete stew +Incomplete stew +Cheese +Empty cup +Cup of tea +Chocolate bar +Kebab +Spinach roll +Cabbage +Pastry dough +Chef's hat +Grain +Egg +Swamp paste +Raw swamp paste +Swamp tar +Jug of water +Jug +Pot of flour +Pot +Bucket of water +Bucket of milk +Bucket +Bowl +Bowl of water +Beer +Grog +Dwarven stout +Dragon bitter +Greenman's ale +Wizard's mind bomb +Asgarnian ale +Burnt cake +Chocolate slice +2/3 chocolate cake +Chocolate cake +Slice of cake +2/3 cake +Cake +Uncooked cake +Cake tin +Ugthanki kebab +Ugthanki kebab +Kebab mix +Ugthanki & tomato +Ugthanki & onion +Onion & tomato +Chopped ugthanki +Chopped onion +Chopped tomato +Burnt pitta bread +Pitta bread +Pitta dough +Ugthanki meat +Raw ugthanki meat +Shantay pass +Desert boots +Desert robe +Desert shirt +Waterskin(0) +Waterskin(1) +Waterskin(2) +Waterskin(3) +Waterskin(4) +Unfired bowl +Unfired pie dish +Unfired pot +Glassblowing pipe +Soda ash +Flax +Purple dye +Green dye +Orange dye +Blue dye +Yellow dye +Red dye +Soft clay +Brown apron +Chisel +Green dragonhide +Blue dragonhide +Red dragonhide +Black dragonhide +Cowhide +Wool +Shears +Thread +Needle +Amulet of power +Amulet of defence +Amulet of magic +Amulet of strength +Unholy symbol +Holy symbol +Amulet of glory(4) +Amulet of glory(3) +Amulet of glory(2) +Amulet of glory(1) +Amulet of glory +Crushed gem +Uncut dragonstone +Uncut red topaz +Uncut jade +Uncut opal +Uncut sapphire +Uncut emerald +Uncut ruby +Uncut diamond +Holy mould +Necklace mould +Amulet mould +Ring mould +Miscellaneous key +Pete's candlestick +Cat training medal +Doogle leaves +Seasoned sardine +Garlic +Lockpick +Logs +Amulet of accuracy +White bead +Black bead +Yellow bead +Red bead +Archery ticket +Nature talisman +Death talisman +Cosmic talisman +Chaos talisman +Blood talisman +Mind talisman +Body talisman +Water talisman +Fire talisman +Earth talisman +Air talisman +Rune essence +Dragon mace +Black mace +Magic staff +Staff of fire +Staff of earth +Staff of water +Staff of air +Staff +Dragon battleaxe +Black battleaxe +Black axe +Black warhammer +Black scimitar +Black 2h sword +Dragon longsword +Black longsword +Black sword +Rune pickaxe +Mithril pickaxe +Adamant pickaxe +Steel pickaxe +Iron pickaxe +Bronze pickaxe +Dragon spear(p) +Rune spear(p) +Adamant spear(p) +Mithril spear(p) +Steel spear(p) +Iron spear(p) +Bronze spear(p) +Black dagger(p) +Dragon dagger(p) +Rune dagger(p) +Adamant dagger(p) +Mithril dagger(p) +Steel dagger(p) +Bronze dagger(p) +Iron dagger(p) +Black dagger +Dragon dagger +Black kiteshield +Black sq shield +Wooden shield +Black full helm +Black med helm +Dragon med helm +Black platebody +Black chainbody +Black plateskirt +Black platelegs +Zamorak monk top +Zamorak monk bottom +Orange cape +Purple cape +Green cape +Right eye patch +Yellow cape +Blue cape +Black cape +Wizard hat +Black skirt +Pink skirt +Blue skirt +Brass necklace +Red cape +White apron +Sinister key +Muddy key +Loop half of key +Tooth half of key +Machete +Charcoal +Papyrus +Rock +Tile +Plank +Grey wolf fur +Flyer +Rope +Spade +Silk +Bear fur +Knife +Bronze fire arrow (lit) +Rune arrow(p) +Adamant arrow(p) +Mithril arrow(p) +Steel arrow(p) +Iron arrow(p) +Bronze arrow(p) +Barbed bolts +Bronze bolts (p) +Rune knife(p) +Adamant knife(p) +Black knife(p) +Mithril knife(p) +Steel knife(p) +Iron knife(p) +Bronze knife(p) +Black knife +Crossbow +Rune javelin(p) +Adamant javelin(p) +Mithril javelin(p) +Steel javelin(p) +Iron javelin(p) +Bronze javelin(p) +Rune dart(p) +Adamant dart(p) +Mithril dart(p) +Steel dart(p) +Iron dart (p) +Bronze dart(p) +Rune thrownaxe +Adamant thrownaxe +Mithril thrownaxe +Steel thrownaxe +Iron thrownaxe +Bronze thrownaxe +Phoenix crossbow +Gnomeball +Smokey +Smashed glass +Turquoise hat +Cream hat +Blue hat +Green hat +Pink hat +Turquoise robe bottoms +Cream robe bottoms +Blue robe bottoms +Green robe bottoms +Pink robe bottoms +Turquoise robe top +Cream robe top +Blue robe top +Green robe top +Pink robe top +Turquoise boots +Cream boots +Blue boots +Green boots +Pink boots +Ship ticket +Astronomy book +Bronze fire arrow +Unlit torch +Ashes +Tinderbox +Bailing bucket +Bailing bucket +Black robe +Blue wizard hat +Blue wizard robe +Earth orb +Air orb +Water orb +Fire orb +Soul rune +Blood rune +Cosmic rune +Law rune +Chaos rune +Nature rune +Death rune +Body rune +Mind rune +Earth rune +Air rune +Water rune +Fire rune +Newcomer map +Shade robe +Shade robe top +Monk's robe top +Monk's robe +Druid's robe top +Druid's robe +Dragon bones +Babydragon bones +Big bones +Bat bones +Burnt bones +Bones +Rune axe head +Adamant axe head +Nuts of monkey +Black axe head +Steel axe head +Iron axe head +Dwarven bar +Broken axe +Broken axe +Broken axe +Broken axe +Broken axe +Axe handle +Rune pick head +Adamant pick head +Mithril pick head +Steel pick head +Iron pick head +Bronze pick head +Broken pickaxe +Broken pickaxe +Pickaxe handle +Strange fruit +Coal +Runite ore +Adamantite ore +Mithril ore +Gold ore +Silver ore +Iron ore +Tin ore +Copper ore +Clay +Priest gown +Priest gown +Oyster pearls +Oyster pearl +Empty oyster +Oyster +Casket +Edible seaweed +Seaweed +Burnt sea turtle +Raw sea turtle +Burnt manta ray +Raw manta ray +Burnt shark +Raw shark +Burnt lobster +Raw lobster +Burnt swordfish +Raw swordfish +Burnt fish +Burnt fish +Raw bass +Raw tuna +Burnt fish +Raw mackerel +Raw pike +Raw herring +Burnt fish +Raw cod +Raw trout +Raw salmon +Raw sardine +Burnt fish +Raw anchovies +Raw shrimps +Feather +Fishing bait +Harpoon +Fly fishing rod +Fishing rod +Big fishing net +Small fishing net +Lobster pot +Mithril seeds +Goblin mail +Poison +Fish food +Blue dragon scale +Unicorn horn +Pestle and mortar +Vial of water +Red spiders' eggs +Eye of newt +Poison chalice +Potion +Zamorak brew(1) +Zamorak brew(2) +Superantipoison(1) +Superantipoison(2) +Antipoison(1) +Antipoison(2) +Ranging potion(1) +Ranging potion(2) +Super defence(1) +Super defence(2) +Super strength(1) +Super strength(2) +Fishing potion(1) +Fishing potion(2) +Super attack(1) +Super attack(2) +Prayer potion(1) +Prayer potion(2) +Defence potion(1) +Defence potion(2) +Restore potion(1) +Restore potion(2) +Attack potion(1) +Attack potion(2) +Strength potion(1) +Strength potion(2) +Strength potion(4) +Barb bolttips +Candle +Bucket of wax +Insect repellent +Cannon furnace +Cannon barrels +Cannon stand +Cannon base \ No newline at end of file diff --git a/pool3-cl.txt b/pool3-cl.txt new file mode 100644 index 0000000000..c46d5207d9 --- /dev/null +++ b/pool3-cl.txt @@ -0,0 +1,5989 @@ +3rd Item Pool (Not CL and in UMB) +Dunce gloves +Graceful recolour +Agility xp +Brimhaven voucher +Agility arena ticket +Large fur pouch (open) +Medium fur pouch (open) +Small fur pouch (open) +Large meat pouch (open) +Small meat pouch (open) +Wilderness agility ticket +Imbued mage arena cape +Egg priest mitre +Egg priest necklace +Egg priest robe top +Egg priest robe +Book of egg +Chef's notes +Herbalist's notes +A nice key +Token +Jug of blessed wine +Jug of blessed sunfire wine +Jug of sunfire wine +Blessed bone shards +Sun-kissed bones +Dagannoth bones +Blessed ourg bones +Blessed raurg bones +Blessed fayrg bones +Blessed hydra bones +Blessed drake bones +Blessed wyrm bones +Blessed superior dragon bones +Blessed wyvern bones +Blessed lava dragon bones +Blessed dragon bones +Blessed babywyrm bones +Blessed babydragon bones +Blessed zogre bones +Blessed big bones +Blessed bat bones +Blessed bones +Blessed bone statuette +Blessed bone statuette +Blessed bone statuette +Valuables +House keys +Apatura's key +Apatura's note +Guild history excerpt +Tattered request note +Huntsman's kit +Large fur pouch +Medium fur pouch +Small fur pouch +Large meat pouch +Small meat pouch +Perfected quetzal whistle +Enhanced quetzal whistle +Basic quetzal whistle +Guild hunter boots +Guild hunter legs +Guild hunter top +Guild hunter headwear +Torn perfected quetzal whistle blueprint +Torn enhanced quetzal whistle blueprint +Basic quetzal whistle blueprint +Hunters' loot sack +Hunters' loot sack (master) +Hunters' loot sack (expert) +Hunters' loot sack (adept) +Hunters' loot sack (basic) +Antelope hoof shard +Moonlight moth wing +Herby tuft +Salamander claw +Sunlight moth wing +Antelope hoof shard +Red chinchompa tuft +Red salamander claw +Fox fluff +Kyatt tooth chip +Orange salamander claw +Black butterfly wing +Graahk horn spur +Large jerboa tail +White butterfly wing +Larupia ear +Swamp lizard claw +Blue butterfly wing +Kebbity tuft +Tailfeathers +Chinchompa tuft +Cooked bream +Raw bream +Calcified deposit +Sulphurous essence +Moonlight potion(1) +Moonlight potion(2) +Moonlight potion(3) +Moonlight potion(4) +Moonlight grub paste +Moonlight grub +Cooked moss lizard +Raw moss lizard +Blood moon helm +Blood moon tassets +Blood moon chestplate +Blue moon helm +Blue moon tassets +Blue moon chestplate +Eclipse moon helm +Eclipse moon tassets +Eclipse moon chestplate +Plushy +Love letter +Baby dragon bonemeal +Fox's report +Trimmed fur +Fur sample +Makeshift poultice +Sticky leaf +Smooth leaf +Knight of varlamore +Stolen amulet +Quetzal feed +Incriminating letter +Varlamore crest +Varlamore invitation +Bream scales +Moss lizard tail +Building supplies +Infused earth talisman +Infused water talisman +Enchanted earth talisman +Enchanted water talisman +Quetzin +Scrawled poem +Blessed dizana's quiver (l) +Dizana's quiver (l) +Dizana's quiver +Dizana's quiver (uncharged) (l) +Echo boots +Tonalztics of ralos +Dizana's max cape (l) +Moonlight antler +Sunlight antler +Moonlight antelope +Sunlight antelope +Embertailed jerboa +Pyre fox +Moonlight moth +Sunlight moth +Dizana's max cape (broken) +Blessed dizana's quiver (broken) +Dizana's quiver (broken) +Lesser nagua +Coin pouch +Wealthy citizen +Antique lamp +Shale tablet +Slate tablet +Granite tablet +Stone tablet +Elias white +List of elders +Shield of arrav +Imbued barronite +Bottle of mist +Bottle +Grubby key +Antique lamp +Snowglobe helmet +Icy jumper +Ruinous powers +Sage's axe +Sage's greaves +Guardian horn +Banker's note +Globetrotter pendant +Trailblazer reloaded rejuvenation pool +Blazing blowpipe +Blazing blowpipe (empty) +Dinh's blazing bulwark +Beehive (style 2) +Beehive (style 1) +Fancier boots +Web cloak +Pheasant egg +Padded spoon +Packed mulch +Mulch +Pheasant tail feathers +Spider hat +Spider hat +Spider hat +Spider hat +Spider hat +Cobweb cape +Scarred extract +Mangled extract +Twisted extract +Warped extract +Tainted essence chunk +Magic lamp (magic) +Magic lamp (thieving) +Magic lamp (slayer) +Magic lamp (strength) +Warped sceptre +Yewnock's notes +Crystal chime +Crystal chime seed +Strongroom key +Chest key +Quest lamp +Corrupted tumeken's shadow +Corrupted scythe of vitur +Shadow ancient sceptre (l) +Smoke ancient sceptre (l) +Ice ancient sceptre (l) +Blood ancient sceptre (l) +Stink bomb +Gooey note +Gooey note +Gooey note +Slimy tablet +Slimy tablet +Slimy tablet +Illuminating lure +Radiant fibre +Crimson fibre +Abyssal observations +Lava nerve +Steam nerve +Dust nerve +Wrath nerve +Astral nerve +Cosmic nerve +Law nerve +Blood nerve +Smoke nerve +Nature nerve +Soul nerve +Mind nerve +Air nerve +Fire nerve +Water nerve +Earth nerve +Withered note +Scarred scraps +Gunpowder +Slimy key +Damp tablet +Damp tablet +Old tablet +Evacuation note +Protest note +Thank you note +Prayer note +Request note +Refugees note +Orders note +Odd key +Warning letter +Library note +Strange slider +Magic lantern +Code converter +Grid note +Requisition note +Rations +Onyx key +Dragonstone key +Diamond key +Ruby key +Emerald key +Sapphire key +Lockpick +Chisel +Knife +Prisoner's letter +Dr banikan +Ancient lamp +Hair clip +Whisperer's medallion +Sucellus' medallion +Perseriya's medallion +Vardorvis' medallion +Charged cell +Uncharged cell +Mucky note +Tatty page +Tatty page +Tatty page +Tatty page +Tatty page +Tatty page +Tatty page +Detonator +Satchel +Barricade +Temple key +Strangler serum +Unfinished serum +Unfinished serum +Argian berries +Korbal herb +Strange potion +Potion note +Perfected shadow torch schematic +Superior shadow torch schematic +Basic shadow torch schematic +Shadow blocker schematic +Revitalising idol schematic +Anima portal schematic +Shadow key +Shadow key +Shadow key +Shadow key +Shadow key +Anima portal +Revitalising idol +Shadow blocker +Perfected shadow torch +Superior shadow torch +Basic shadow torch +Very long rope +Icon segment +Icon segment +Strange icon +Blackstone fragment +Blackstone fragment +Arder-resper poison +Resper-holos poison +Holos-arder poison +Musca-resper poison +Arder-musca poison +Musca-holos poison +Salax salt +Resper powder +Resper mushroom +Arder powder +Arder mushroom +Holos powder +Holos mushroom +Musca powder +Musca mushroom +Ancient blood ornament kit +Ring of shadows +Ring of shadows +Shadow ancient sceptre (broken) +Ice ancient sceptre (broken) +Smoke ancient sceptre (broken) +Blood ancient sceptre (broken) +Beaver +Beaver +Beaver +Beaver +Beaver +Beaver +Beaver +Beaver +Beaver +Crystal felling axe (inactive) +Strange pollen +Mulch +Open forestry basket +Open log basket +Forestry kit +Anima-infused bark +Crypt map +Dusty lamp +Strange icon +Love crossbow +Poet's jacket +Colourful jumper +Colourful jumper +Colourful jumper +Colourful jumper +Colourful jumper +Colourful jumper +Colourful jumper +Colourful jumper +Rainbow jumper +Colourful scarf +Colourful scarf +Colourful scarf +Colourful scarf +Colourful scarf +Colourful scarf +Colourful scarf +Colourful scarf +Bounty crate (tier 9) +Bounty crate (tier 8) +Bounty crate (tier 7) +Bounty crate (tier 6) +Bounty crate (tier 5) +Bounty crate (tier 4) +Bounty crate (tier 3) +Bounty crate (tier 2) +Bounty crate (tier 1) +Abyssal dagger imbue scroll +Dragon longsword imbue scroll +Dragon mace imbue scroll +Barrelchest anchor imbue scroll +Dark bow imbue scroll +Helm of neitiznot (or) +Fighter torso (l)(or) +Fighter torso (or) +Dragon chainbody (cr) +Dragon plateskirt (cr) +Dragon platelegs (cr) +Dragon sq shield (cr) +Dragon med helm (cr) +Dragon boots (cr) +Dragon crossbow (cr) +Dragon 2h sword (cr) +Dragon halberd (cr) +Dragon spear (p++)(cr) +Dragon spear (p+)(cr) +Dragon spear (p)(cr) +Dragon spear (cr) +Dragon claws (cr) +Dragon battleaxe (cr) +Dragon warhammer (cr) +Dragon longsword (cr) +Dragon scimitar (cr) +Dragon sword (cr) +Dragon mace (cr) +Dragon dagger (p++)(cr) +Dragon dagger (p+)(cr) +Dragon dagger (p)(cr) +Dragon dagger (cr) +Bounty hunter ornament kit +Esoteric emblem (tier 10) +Esoteric emblem (tier 9) +Esoteric emblem (tier 8) +Esoteric emblem (tier 7) +Esoteric emblem (tier 6) +Esoteric emblem (tier 5) +Esoteric emblem (tier 4) +Esoteric emblem (tier 3) +Esoteric emblem (tier 2) +Esoteric emblem (tier 1) +Corrupted zuriel's robe bottom (bh)(inactive) +Corrupted zuriel's robe top (bh)(inactive) +Corrupted zuriel's hood (bh)(inactive) +Corrupted morrigan's leather chaps (bh)(inactive) +Corrupted morrigan's leather body (bh)(inactive) +Corrupted morrigan's coif (bh)(inactive) +Corrupted statius's platelegs (bh)(inactive) +Corrupted statius's platebody (bh)(inactive) +Corrupted statius's full helm (bh)(inactive) +Corrupted vesta's plateskirt (bh)(inactive) +Corrupted vesta's chainbody (bh)(inactive) +Prayer xp +Ranged xp +Magic xp +Hitpoints xp +Defence xp +Strength xp +Attack xp +Zuriel's robe bottom (bh)(inactive) +Zuriel's robe top (bh)(inactive) +Zuriel's hood (bh)(inactive) +Morrigan's leather chaps (bh)(inactive) +Morrigan's leather body (bh)(inactive) +Morrigan's coif (bh)(inactive) +Statius's platelegs (bh)(inactive) +Statius's platebody (bh)(inactive) +Statius's full helm (bh)(inactive) +Vesta's plateskirt (bh)(inactive) +Vesta's chainbody (bh)(inactive) +Zuriel's staff (bh)(inactive) +Zuriel's staff (bh) +Morrigan's javelin (bh)(inactive) +Morrigan's javelin (bh) +Morrigan's throwing axe (bh)(inactive) +Morrigan's throwing axe (bh) +Statius's warhammer (bh)(inactive) +Statius's warhammer (bh) +Vesta's longsword (bh)(inactive) +Vesta's longsword (bh) +Vesta's spear (bh)(inactive) +Vesta's spear (bh) +Nest hat +Nest hat +Eastfloor spade +Giant bronze dagger +Lightbearer +Voidwaker +Abyssal dagger (bh)(p++) +Abyssal dagger (bh)(p+) +Abyssal dagger (bh)(p) +Abyssal dagger (bh) +Dragon longsword (bh) +Dragon mace (bh) +Barrelchest anchor (bh) +Dark bow (bh) +Corrupted zuriel's robe bottom (bh) +Corrupted zuriel's robe top (bh) +Corrupted zuriel's hood (bh) +Corrupted morrigan's leather chaps (bh) +Corrupted morrigan's leather body (bh) +Corrupted morrigan's coif (bh) +Corrupted statius's platelegs (bh) +Corrupted statius's platebody (bh) +Corrupted statius's full helm (bh) +Corrupted vesta's plateskirt (bh) +Corrupted vesta's chainbody (bh) +Zuriel's robe bottom (bh) +Zuriel's robe top (bh) +Zuriel's hood (bh) +Morrigan's leather chaps (bh) +Morrigan's leather body (bh) +Morrigan's coif (bh) +Statius's platelegs (bh) +Statius's platebody (bh) +Statius's full helm (bh) +Vesta's plateskirt (bh) +Vesta's chainbody (bh) +Oldschool jumper +10th birthday balloons +Gnome child plush +Stray dog plush +Jad plush +10th birthday cape +Dragon candle dagger +Jad slippers +Bob the cat slippers +Cake hat +Gnome child backpack +Snowball +Halloween wig +Terrifying charm +Witch cape +Witch boots +Witch robes +Witch top +Witch hat +Treat cauldron +Treat cauldron +Treat cauldron +Treat cauldron +Treat cauldron +Fresh start helper +Verac's brassard +Verac's flail +Light ballista +Flower crown (gay) +Flower crown (lesbian) +Flower crown (genderqueer) +Flower crown (non-binary) +Flower crown (pansexual) +Flower crown (transgender) +Flower crown (asexual) +Flower crown (bisexual) +Dagon'hai robe bottom (or) +Dagon'hai robe top (or) +Dagon'hai hat (or) +Dagon'hai robes ornament kit +Elder chaos hood (or) +Elder chaos robe (or) +Elder chaos top (or) +Elder chaos robes ornament kit +Barrows gloves (wrapped) +Rune gloves (wrapped) +Mithril gloves (wrapped) +Elder maul (or) +Elder maul ornament kit +Rune pouch +Group ironman platebody (unranked) +Group ironman platebody (unranked) +Group ironman platebody (unranked) +Group ironman platebody (unranked) +Group ironman bracers (unranked) +Group ironman platelegs (unranked) +Group ironman helm (unranked) +Quality violet tulip seed +Tenacious indigo iris seed +Beautiful yellow pansy seed +Gorgeous orange lily seed +Legendary red rose seed +Flower crown +Smiths gloves (i) +Preform +Rune defender (l)(t) +Dragon defender (l)(t) +Void melee helm (l)(or) +Void ranger helm (l)(or) +Void mage helm (l)(or) +Elite void robe (l)(or) +Elite void top (l)(or) +Void knight gloves (l)(or) +Void knight robe (l)(or) +Void knight top (l)(or) +Amulet of the eye +Amulet of the eye +Amulet of the eye +Circlet of water +Circlet of water (uncharged) +Bottle of 'tonic' +Odd spectacles +Cure crate +Lily of the elid +Rusty key +Crocodile emblem +Baboon emblem +Human emblem +Scarab emblem +Chest +Stone tablet +Scarab emblem +Scarab mould +Message +Polyelemental guardian stone +Crate ring +Easter hat +Frozen churning machine +Churning machine +Wooden pole +Vat (cleaned) +Vat (dirty) +Tanning wheel +Washing line +Blunt scimitars +Ice cream easter egg +Melted easter egg +Easter egg +Cooler +Magical cleaning potion +Big bucket of frozen camel milk +Big bucket of camel milk +Big bucket +Secret report +Special super hot kebab +Special hot sauce +Amulet of the eye +Colossal pouch +Abyssal incantation +Strong cup of tea +Eye amulet +Portal talisman (law) +Portal talisman (nature) +Portal talisman (cosmic) +Portal talisman (body) +Portal talisman (blood) +Portal talisman (death) +Portal talisman (chaos) +Portal talisman (mind) +Portal talisman (fire) +Portal talisman (earth) +Portal talisman (water) +Portal talisman (air) +Overcharged cell +Strong cell +Medium cell +Weak cell +Uncharged cell +Elemental guardian stone +Catalytic guardian stone +Guardian essence +Guardian fragments +Atlax's diary +Abyssal lantern (redwood logs) +Abyssal lantern (magic logs) +Abyssal lantern (blisterwood logs) +Abyssal lantern (yew logs) +Abyssal lantern (maple logs) +Abyssal lantern (willow logs) +Abyssal lantern (oak logs) +Abyssal lantern (green logs) +Abyssal lantern (purple logs) +Abyssal lantern (white logs) +Abyssal lantern (red logs) +Abyssal lantern (blue logs) +Abyssal lantern (normal logs) +Ring of the elements +Colossal pouch +Salve amulet(ei) +Berserker ring (i) +Warrior ring (i) +Archers ring (i) +Seers ring (i) +Treasonous ring (i) +Tyrannical ring (i) +Ring of the gods (i) +Salve amulet(i) +Granite ring (i) +Tzkal slayer helmet (i) +Vampyric slayer helmet (i) +Tztok slayer helmet (i) +Twisted slayer helmet (i) +Hydra slayer helmet (i) +Turquoise slayer helmet (i) +Purple slayer helmet (i) +Red slayer helmet (i) +Green slayer helmet (i) +Black slayer helmet (i) +Slayer helmet (i) +Shoe +Shoe +Shoe +Shoe +Shoe +Shoe +Shoe. +Shoe +Shoe +Shoe +Shoe +Shoe +Shoe. +Shoe +Shoe +Shoe +Shoe +Shoe +Loot key +Skis +Burnt banana pizza (cookout) +Banana pizza (cookout) +Burnt bread (cookout) +Bread (cookout) +Bread dough (cookout) +Burnt pizza (cookout) +Plain pizza (cookout) +Uncooked pizza (cookout) +Incomplete pizza (cookout) +Pizza base (cookout) +Banana (cookout) +Cheese (cookout) +Tomato (cookout) +Pot of flour (cookout) +Bucket of water (cookout) +Bucket (cookout) +Capt' arnav's chest +Zeke's challenge scroll +Treasure clue three +Treasure clue two +Treasure clue one +Tiny fish +Desert bait +Notes +Zanik +Grubfoot +Strongbones' bone +Redeyes' bone +Mosschin's bone +Snailfeet's bone +Snothead's bone +Goblin potion(1) +Goblin potion(2) +Goblin potion(3) +Goblin potion(4) +Whitefish +Plain of mud sphere +Yurkolgokh key +Horogothgar key +Saragorgak key +Huzamogaarb key +Narogoshuun key +Ekeleshuun key +Pharmakos berries +White goblin mail +Arcane grimoire +Portable waystone +Unidentified fragment (misc) +Unidentified fragment (combat) +Unidentified fragment (skilling) +Unidentified fragment (production) +Unidentified fragment (harvesting) +Cabbage +Blood essence (active) +Ecumenical key shard +Important letter +Frozen key piece (saradomin) +Frozen key piece (zamorak) +Frozen key piece (bandos) +Frozen key piece (armadyl) +Humongous snowball +Huge snowball +Large snowball +Big snowball +Normal snowball +Small snowball +Little snowball +Secret santa present (gold) +Secret santa present (black) +Secret santa present (green) +Secret santa present (blue) +Secret santa present (red) +Snowman ring +Festive elf hat +Festive elf slippers +A big present +Chocolate chips +Chocolate chips +Secret santa present +Secret santa present +Secret santa present +Snow +Festive mulled wine +Festive gingerbread gnomes +Clean full helm +Clean platelegs +Clean platebody +Pink stained full helm +Pink stained platelegs +Pink stained platebody +Magical cleaning potion +Notes +Festive white wine +Festive holly +Festive cinnamon +Gold sink +Studded body +Rune scimitar +Haunted wine bottle +Ugly halloween jumper (black) +Ugly halloween jumper (orange) +Saucepan +Ad coupon +Jered's empty wine bottle +Pumpkin pie +Hardcore group ironman bracers +Hardcore group ironman platelegs +Hardcore group ironman platebody +Hardcore group ironman platebody +Hardcore group ironman platebody +Hardcore group ironman platebody +Hardcore group ironman helm +Group ironman bracers +Group ironman platelegs +Group ironman platebody +Group ironman platebody +Group ironman platebody +Group ironman platebody +Group ironman helm +Mount karuulm diary +Combat potion(1) +Combat potion(2) +Combat potion(3) +Combat potion(4) +Tuna +Sigil of the guardian angel +Sigil of last recall +Sigil of remote storage +Sigil of the skiller +Sigil of rampage +Sigil of aggression +Sigil of pious protection +Sigil of finality +Sigil of preservation +Sigil of supreme stamina +Sigil of the serpent +Sigil of versatility +Sigil of the fortune farmer +Sigil of slaughter +Sigil of garments +Sigil of the forager +Sigil of devotion +Sigil of nature +Sigil of the gnomes +Sigil of the barbarians +Sigil of the elves +Sigil of the dwarves +Sigil of prosperity +Sigil of the menacing mage +Sigil of the feral fighter +Sigil of the ruthless ranger +Sigil of escaping +Sigil of binding +Sigil of the porcupine +Sigil of specialised strikes +Sigil of exaggeration +Sigil of mobility +Sigil of the treasure hunter +Sigil of the eternal jeweller +Sigil of the potion master +Sigil of stamina +Sigil of the abyss +Sigil of the craftsman +Sigil of the chef +Sigil of the fletcher +Sigil of the alchemist +Sigil of the smith +Sigil of storage +Sigil of enhanced harvest +Sigil of freedom +Sigil of deft strikes +Sigil of barrows +Sigil of fortification +Sigil of the meticulous mage +Sigil of the rigorous ranger +Sigil of the formidable fighter +Sigil of consistency +Sigil of resilience +Keris partisan +Hespori bark +Sticky note +Sulphuric acid +Strange spider eggs +Ranis' head +Crypt key +Escape crystal +Crystal paddlefish +Corrupted escape crystal +Corrupted paddlefish +Combat achievements +Anim offhand +Anim offhand +Anim offhand +Anim offhand +Anim offhand +Anim offhand +Ghommal's hilt 6 +Ghommal's hilt 5 +Ghommal's hilt 4 +Ghommal's hilt 3 +Ghommal's hilt 2 +Ghommal's hilt 1 +Antique lamp (grandmaster ca) +Antique lamp (master ca) +Antique lamp (elite ca) +Antique lamp (hard ca) +Antique lamp (medium ca) +Antique lamp (easy ca) +Tzkal slayer helmet (i) +Vampyric slayer helmet (i) +Tztok slayer helmet (i) +Bow of faerdhinen (c) +Bow of faerdhinen (c) +Bow of faerdhinen (c) +Bow of faerdhinen (c) +Bow of faerdhinen (c) +Bow of faerdhinen (c) +Bow of faerdhinen (c) +Blade of saeldor (c) +Blade of saeldor (c) +Blade of saeldor (c) +Blade of saeldor (c) +Blade of saeldor (c) +Blade of saeldor (c) +Blade of saeldor (c) +Bow of faerdhinen +Blue egg sac +Blue sraracha +Orange sraracha +Banana hat +Shayzia military orders +Old note +Research notes +Protest banner +Antique lamp +Book of the dead +Royal accord of twill +Shayzien journal +Dark nullifier +Declaration +Shielding potion +Sulphur potion +Broken redirector +Damp key +Lizardman egg +Rose's note +Rose's note +Rose's note +Rose's note +Cold key +Bluish key +Rose's diary +Kourend map +Cultist robe +Bloody knife +Demonic incantations +Order form +Delivery confirmation +Rose +Bone +Receipt +Ash sanctifier +Antique lamp +Bandages +Clan vexillum +Clan vexillum +Clan vexillum +Clan vexillum +Clan vexillum +Clan vexillum +Clan vexillum +Clan vexillum +Clan cloak +Clan cloak +Clan cloak +Clan cloak +Clan cloak +Clan cloak +Clan cloak +Clan cloak +Stool +Stool +Dusty note +Lithkren vault notes +Ungael lab notes +Chaos core +Body core +Mind core +Barronite deposit +Barronite shards +Ruined catfish +Catfish +Raw catfish +Ruined tetra +Tetra +Raw tetra +Ruined cavefish +Cavefish +Raw cavefish +Ruined guppy +Guppy +Raw guppy +Ornate lockbox +Ornate lockbox +Elaborate lockbox +Elaborate lockbox +Simple lockbox +Simple lockbox +Barronite mace (l) +Imcando hammer (broken) +Steak sandwich +Barronite mace (broken) +S.t.a.s.h blueprint +S.t.a.s.h chart +Thick dye +Pastel flowers +Gregg's iou +Propeller hat +Gregg's eastdoor +Casket +Open fish sack barrel +Open fish barrel +Tome of water +Spirit anglers research notes +The desert trout - ship's log +Crystallised harpoonfish +Harpoonfish +Raw harpoonfish +Mounted harpoonfish +Stuffed big harpoonfish +Dark flippers +Celestial signet (uncharged) +Celestial ring +Bag full of gems +Essence pack +Soft clay pack +Stardust +Jalrek-jad +Ancestral hat (LMS) +Volatile nightmare staff (LMS) +Dharok's greataxe (LMS) +Dharok's platebody (LMS) +Banana cape +Cursed banana +Open gold coffin +Open silver coffin +Open black coffin +Open steel coffin +Open bronze coffin +Broken coffin +Gold key purple +Gold key black +Gold key crimson +Gold key brown +Gold key red +Bleached bones +Infernal axe (uncharged) +Infernal pickaxe (uncharged) +Infernal harpoon (uncharged) +League hall +League accomplishments scroll +Ornate league statue +League statue +Mahogany outfit stand +Oak outfit stand +Ornate banner stand +Banner stand +Mahogany trophy case +Oak trophy case +Ornate trophy pedestal +Trophy pedestal +Lil' destructor +Spoils of war +Gnome child mask +20th anniversary cape +20th anniversary necklace +20th anniversary gloves +20th anniversary boots +20th anniversary bottom +20th anniversary top +20th anniversary hat +Gnome child icon +Goblin decorations +Giant boulder +Goblin gifts +Goblin stew +Stale bread +Rotten meat +Mouldy sawdust +Stick +Green fireflies +Red fireflies +Sled +Essence pack +Salve amulet(ei) +Berserker ring (i) +Warrior ring (i) +Archers ring (i) +Seers ring (i) +Treasonous ring (i) +Tyrannical ring (i) +Ring of the gods (i) +Salve amulet(i) +Dark key +Red icon +Red icon +Red icon +Red icon +Red icon +Red icon +Red icon +Red icon +Red icon +Red icon +Red icon +Red icon +Red icon +Red icon +Red icon +Red icon +Blue icon +Blue icon +Blue icon +Blue icon +Blue icon +Blue icon +Blue icon +Blue icon +Blue icon +Blue icon +Blue icon +Blue icon +Blue icon +Blue icon +Blue icon +Blue icon +Explosive potion +Blue cape +Red cape +Potion of power(1) +Potion of power(2) +Potion of power(3) +Potion of power(4) +Bandages +Soul fragment +Bones +Soul wars guide +Soul fragment +Blue cape +Granite ring (i) +Twisted slayer helmet (i) +Hydra slayer helmet (i) +Turquoise slayer helmet (i) +Purple slayer helmet (i) +Red slayer helmet (i) +Green slayer helmet (i) +Black slayer helmet (i) +Slayer helmet (i) +Decorative full helm (l) +Decorative boots (l) +Castlewars brew(1) +Castlewars brew(2) +Castlewars brew(3) +Castlewars brew(4) +Decorative full helm (broken) +Decorative boots (broken) +Ornate undead combat dummy +Neilan's journal +Bloody head +Fur head +Clay head +League tomato +Trailblazer harpoon +Trailblazer pickaxe +Trailblazer axe +Extradimensional bag +Extradimensional bag +Crystal of memories +Fairy mushroom +Cabbage +Cabbage +Incantation +Spider snack +Witch's brew +Rick's head +Rick's box +Pink candy +Orange candy +Black candy +Green candy +Red candy +Purple candy +White candy +Blue candy +Brown candy +Magical pumpkin +Headless head +Cabbage +Cabbage +Hallowed sack +Sourhog foot +Reinforced goggles +Bp obj +Marlo's crate +Waxwood plank +Waxwood log +Mahogany cupboard +Teak cupboard +Oak cupboard +Wooden cupboard +Mahogany chair +Teak chair +Mahogany drawer +Teak drawer +Oak drawer +Wooden drawer +Mahogany bed +Mahogany shelves +Teak shelves +Oak shelves +Wooden shelves +Wooden dresser +Wooden wardrobe +Teak bookcase +Mahogany cabinet +Teak cabinet +Oak cabinet +Wooden cabinet +Oak table +Ranger path starter kit +Wizard path starter kit +Warrior path starter kit +Bag full of gems +Soft clay pack +A taste of hope +Vyre noble dress bottom +Vyre noble dress top +Vyre noble skirt +Vyre noble corset +Vyre noble pants +Vyre noble vest +Vyre noble coat tails +Vyre noble blazer +Vyre noble dress bottom +Vyre noble dress top +Vyre noble skirt +Vyre noble corset +Vyre noble pants +Vyre noble vest +Vyre noble coat tails +Vyre noble blazer +Vyre noble dress bottom +Vyre noble dress top +Vyre noble skirt +Vyre noble corset +Vyre noble pants +Vyre noble vest +Vyre noble coat tails +Vyre noble blazer +Severed leg +Long rope +Pat of not garlic butter +Amulet of blood fury +Hallowed crystal shard +Vampyre +Daeyalt shard +Coin pouch +Vyre +Blisterwood sickle +Enchanted ruby sickle (b) +Ruby sickle (b) +Blisterwood logs +Tome of experience +Ancient armour +Journal page +Tatty note +Old note +Vyre noble shoes +Vyre noble legs +Vyre noble top +Vyre noble shoes (unscented) +Vyre noble legs (unscented) +Vyre noble top (unscented) +Haemalchemy volume 2 +Massive storage unit +Flying vespina +Enraged tektiny +Bones +Logs +Vesta's longsword (inactive) +Vesta's blighted longsword +Blighted super restore(1) +Blighted super restore(2) +Blighted super restore(3) +Antique emblem (tier 10) +Antique emblem (tier 9) +Antique emblem (tier 8) +Antique emblem (tier 7) +Antique emblem (tier 6) +Antique emblem (tier 5) +Antique emblem (tier 4) +Antique emblem (tier 3) +Antique emblem (tier 2) +Antique emblem (tier 1) +Pyromancer set +Cake +Broken goat horn +Carrot +Dummy portal +Broken egg +Conch shell +Unpainted fake magic egg +Painted fake magic egg +Mithril seeds +Runner hat (l) +Runner hat (broken) +Harmony +Lamp of the gatherer +Gravestone +Death's coffer +Purple phoenix +White phoenix +Blue phoenix +Green phoenix +Open seed box +Open gem bag +Open coal bag +Spice rack +Open herb sack +Gingerbread gnome +Bakery storage key +Scaperune teleport +Iced gingerbread shield +Iced gingerbread shield +Iced gingerbread shield +Gingerbread shield +Festive flour +Festive pot +Festive egg +Festive ginger powder +Festive cinnamon stick +Red gingerbread shield +Cabbage +Gravestone +Rune pouch (l) +Cabbage +Scroll box (master) +Scroll box (elite) +Scroll box (hard) +Scroll box (medium) +Scroll box (easy) +Scroll box (beginner) +Bounty hunter hat (tier 6) +Bounty hunter hat (tier 5) +Bounty hunter hat (tier 4) +Bounty hunter hat (tier 3) +Bounty hunter hat (tier 2) +Bounty hunter hat (tier 1) +Bounty crate +Spooky boots +Spooky gloves +Spooky skirt +Spooky robe +Spooky hood +Shiny glass +Smoke powder +White bed sheets +Decorative emblem +Basilisk knight +V's shield +V's shield +Ballad of the basilisk +Polishing rock +Lunar glass +Molten glass (i) +V sigil (e) +V sigil +Unsealed letter +Unsealed letter +Venom gland +Fang +Imbued zamorak cape (l) +Imbued guthix cape (l) +Imbued saradomin cape (l) +Imbued zamorak max cape (broken) +Imbued zamorak cape (broken) +Imbued guthix max cape (broken) +Imbued guthix cape (broken) +Imbued saradomin max cape (broken) +Imbued saradomin cape (broken) +House advertisement +Imbued guthix max cape (l) +Imbued zamorak max cape (l) +Imbued saradomin max cape (l) +Infernal cape (l) +Fire cape (l) +Ava's assembler (l) +Brassica halo (l) +Ancient halo (l) +Seren halo (l) +Bandos halo (l) +Armadyl halo (l) +Deadman's cape +Deadman's legs +Deadman's chest +Avernic defender (l) +Void melee helm (l) +Void ranger helm (l) +Void mage helm (l) +Void knight gloves (l) +Void knight mace (l) +Elite void robe (l) +Void knight robe (l) +Elite void top (l) +Void knight top (l) +Penance skirt (l) +Fighter torso (l) +Ranger hat (l) +Fighter hat (l) +Healer hat (l) +Guthix halo (l) +Zamorak halo (l) +Saradomin halo (l) +Decorative armour (l) +Decorative armour (l) +Decorative armour (l) +Decorative armour (l) +Decorative armour (l) +Decorative armour (l) +Decorative armour (l) +Decorative shield (l) +Decorative helm (l) +Decorative armour (l) +Decorative armour (l) +Decorative sword (l) +Brassica halo (broken) +Ancient halo (broken) +Seren halo (broken) +Bandos halo (broken) +Armadyl halo (broken) +Dragon defender (l) +Rune defender (l) +Adamant defender (l) +Mithril defender (l) +Black defender (l) +Steel defender (l) +Iron defender (l) +Bronze defender (l) +Assembler max cape (l) +Fire max cape (l) +Infernal max cape (l) +Marble lectern +Combat path voucher +Combat path starter kit +Crystal shield +Crystal halberd +Crystal bow +Legends of the mountain +The spurned demon +The living statues +The truth behind the myth (excerpt) +Stained journal +Ebrill's journal +Soggy journal +Niff & harry +Gollwyn's final statement +The eight clans +Bloody diary +On leprechauns +A dear friend +Crazed scribbles +Memoriam crystal (4) +Memoriam crystal (3) +Memoriam crystal (2) +Memoriam crystal (1) +Blade of saeldor +Crystal shield (inactive) +Crystal shield +Crystal halberd (inactive) +Crystal halberd +Crystal bow (inactive) +Crystal bow +Crystal legs (inactive) +Crystal legs +Crystal body (inactive) +Crystal body +Crystal helm (inactive) +Crystal helm +Crystal dust +Crystal of amlodd +Crystal of hefin +Crystal of meilyr +Crystal of crwys +Crystal of cadarn +Crystal of trahaearn +Crystal of iorwerth +Crystal of ithell +Crystal crown +Crystal crown +Crystal crown +Crystal crown +Crystal crown +Crystal crown +Crystal crown +Crystal crown +Imbued tephra +Refined tephra +Tephra +Teleport crystal +Crystal bow (perfected) +Crystal bow (attuned) +Crystal bow (basic) +Crystal staff (perfected) +Crystal staff (attuned) +Crystal staff (basic) +Crystal halberd (perfected) +Crystal halberd (attuned) +Crystal halberd (basic) +Crystal legs (perfected) +Crystal legs (attuned) +Crystal legs (basic) +Crystal body (perfected) +Crystal body (attuned) +Crystal body (basic) +Crystal helm (perfected) +Crystal helm (attuned) +Crystal helm (basic) +Egniol potion (4) +Egniol potion (3) +Egniol potion (2) +Egniol potion (1) +Grym potion (unf) +Water-filled vial +Vial +Phren bark +Crystal ore +Linum tirinum +Grym leaf +Paddlefish +Burnt fish +Raw paddlefish +Weapon frame +Crystal orb +Crystalline bowstring +Crystal spike +Crystal dust +Crystal shards +Pestle and mortar +Crystal harpoon +Crystal pickaxe +Crystal axe +Crystal sceptre +Corrupted teleport crystal +Corrupted bow (perfected) +Corrupted bow (attuned) +Corrupted bow (basic) +Corrupted staff (perfected) +Corrupted staff (attuned) +Corrupted staff (basic) +Corrupted halberd (perfected) +Corrupted halberd (attuned) +Corrupted halberd (basic) +Corrupted legs (perfected) +Corrupted legs (attuned) +Corrupted legs (basic) +Corrupted body (perfected) +Corrupted body (attuned) +Corrupted body (basic) +Corrupted helm (perfected) +Corrupted helm (attuned) +Corrupted helm (basic) +Vial +Phren bark +Corrupted ore +Linum tirinum +Grym leaf +Weapon frame +Corrupted orb +Corrupted bowstring +Corrupted spike +Corrupted dust +Corrupted shards +Corrupted harpoon +Corrupted pickaxe +Corrupted axe +Corrupted sceptre +Explosive potion +Orb of light +Crystal seed +Crystal seed +Inversion potion +Crystal +Elder cadantine potion (unf) +Elder cadantine +Ode to eternity +Red powder +Clear liquid +Green powder +Blue liquid +Ardougne knight tabard +Ardougne knight platelegs +Ardougne knight platebody +Ardougne knight helm +Fractured crystal +Green crystal +Black crystal +Magenta crystal +Blue crystal +Cyan crystal +Green crystal +Yellow crystal +Red crystal +Hand mirror +Scrawled notes +Prifddinas teleport +Crystal harpoon (inactive) +Corrupted youngllef +Crystal pickaxe (inactive) +Crystal axe (inactive) +Crystal sapling +Crystal seedling (w) +Crystal seedling +Occult necklace (LMS) +Ahrim's staff +Mage's book (LMS) +Rune pouch +Diamond bolts (e) +Dragon javelin (LMS) +Bandos tassets (LMS) +Eternal boots (LMS) +Blessed spirit shield (LMS) +Amulet of fury (LMS) +Dharok's helm (LMS) +Guthan's helm (LMS) +Torag's helm (LMS) +Verac's helm (LMS) +Verac's plateskirt (LMS) +Torag's platelegs (LMS) +Dharok's platelegs (LMS) +Heavy ballista (LMS) +Ghrazi rapier (LMS) +Kodai wand (LMS) +Seers ring (i) (LMS) +Infernal cape (LMS) +Statius's warhammer (LMS) +Morrigan's javelin (LMS) +Zuriel's staff (LMS) +Vesta's longsword (LMS) +Staff of the dead (LMS) +Armadyl crossbow (LMS) +Imbued guthix cape +Rune crossbow +Spirit shield +Dragon defender +Berserker ring +Barrows gloves +Helm of neitiznot +Stamina potion(1) +Stamina potion(2) +Stamina potion(3) +Stamina potion(4) +Saradomin brew(1) +Saradomin brew(2) +Saradomin brew(3) +Saradomin brew(4) +Super restore(1) +Super restore(2) +Super restore(3) +Super restore(4) +Sanfew serum(1) +Sanfew serum(2) +Sanfew serum(3) +Sanfew serum(4) +Ranging potion(1) +Ranging potion(2) +Ranging potion(3) +Ranging potion(4) +Super combat potion(1) +Super combat potion(2) +Super combat potion(3) +Super combat potion(4) +Cooked karambwan +Giant egg sac +Tattered temple page +Tattered sun page +Tattered moon page +Tome of the temple +Tome of the sun +Tome of the moon +Temple key +Temple coin +Wine of zamorak +Archaic emblem (tier 10) +Healer icon +Healer icon +Healer icon +Healer icon +Healer icon +Healer icon +Healer icon +Healer icon +Healer icon +Collector icon +Collector icon +Collector icon +Collector icon +Collector icon +Collector icon +Collector icon +Defender icon +Defender icon +Defender icon +Defender icon +Defender icon +Attacker icon +Attacker icon +Attacker icon +Attacker icon +Attacker icon +Attacker icon +Enchanted lyre(i) +Clue geode (beginner) +Puzzle box (master) +Stash units (beginner) +Reward casket (beginner) +Mimic +Strange device +Clue scroll (beginner) +Clue bottle (beginner) +Clue nest (beginner) +Oily pearl fishing rod +Ornate helm +Ornate cape +Ornate top +Ornate legs +Ornate boots +Ornate gloves +Brimstone key +Antique lamp +Alchemical hydra head +Stuffed hydra heads +Antique lamp +Ancient casket +Treasure scroll +Mysterious orb +Treasure scroll +Treasure scroll +Nest box (seeds) +Sulphur lizard +Hydra +Drake +Wyrm +Old notes +Old notes +Old notes +Old notes +Old notes +Old notes +Old notes +Gielinor's flora - fruit +Gielinor's flora - trees +Gielinor's flora - herbs +Gielinor's flora - allotments +Gielinor's flora - hops +Gielinor's flora - bushes +Gielinor's flora - flowers +Tatty note +Bottomless compost bucket +Stone tablet +Bonecrusher necklace +Dragonfruit seedling (w) +Dragonfruit seedling +Redwood seedling (w) +Celastrus seedling (w) +Redwood seedling +Celastrus seedling +Molch pearl +Cormorant's glove +Cormorant's glove +Dragon knife +Dragon knife +Rada's blessing +Bird nest +Bird nest +Certificate +Ancient letter +Old notes +Unknown fluid 5 +Unknown fluid 4 +Unknown fluid 3 +Unknown fluid 2 +Unknown fluid 1 +Energy disk (level 1) +Energy disk (level 2) +Energy disk (level 3) +Energy disk (level 4) +5-gallon jug +8-gallon jug +Generator crank +Dinh's hammer +Certificate +Hydra bonemeal +Drake bonemeal +Wyrm bonemeal +Ikkle hydra +Ikkle hydra +Ikkle hydra +Fake dragon hasta(kp) +Dragon hasta(kp) +Attacker icon +Attacker icon +Defender icon +Defender icon +Defender icon +Defender icon +Collector icon +Attacker icon +Attacker icon +Attacker icon +Collection log +Curator's medallion +Mounted digsite pendant +Mounted xeric's talisman +Crystalline portal nexus +Gilded portal nexus +Marble portal nexus +Portal nexus +Clown shoes +Clown trousers +Clown gown +Clown bow tie +Clown mask +Scroll sack +Stuffed kq head (tattered) +Kq head (tattered) +Rubber chicken +Armadyl godsword +Scythe of vitur (JMod) +Pet smoke devil +Fire of unseasonal warmth +Fire of nourishment +Fire of dehumidification +Fire of eternal light +Weiss fire notes +Goat dung +Reduced cadava potion +Old man's coffin +Looting bag +Rotten strawberry +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Coin pouch +Cabbage +Cabbage +Escape crystal +Verzik vitur - patient record +The wild hunt +The shadow realm +Arachnids of vampyrium +The butcher +Serafina's diary +Message +Avernic defender (broken) +Rotten carrot +Enchanted emerald sickle (b) +Emerald sickle (b) +Old key +Deed +Buried alive +Elixir of everlasting +Bloody grimoire +Explosive discovery +The turncloak +Tome of experience +Chain +Flaygian's notes +Old diary +Old notes +Potion +Unfinished potion +Blood potion +Unfinished blood potion +Mysterious crushed meat +Mysterious meat +Mysterious herb +Drakan's medallion +Certificate +Certificate +Oculus orb +Attacker icon +Attacker icon +Attacker icon +Attacker icon +Defender icon +Defender icon +Defender icon +Defender icon +Defender icon +Defender icon +Collector icon +Collector icon +Collector icon +Starter staff +Starter bow +Starter sword +Deadman starter pack +Rotten cabbage +Pet corporeal critter +Prop sword +Collector icon +Collector icon +Collector icon +Collector icon +Healer icon +Healer icon +Healer icon +Healer icon +Trident of the swamp (e) +Trident of the seas (e) +Leather shields flyer +Barbed arrow +Blunt arrow +Field arrow +Bullet arrow +Wrath rune +Glistening tear +Useful rock +Adamant heraldic helm +Adamant heraldic helm +Adamant heraldic helm +Adamant heraldic helm +Adamant heraldic helm +Adamant heraldic helm +Adamant heraldic helm +Adamant heraldic helm +Adamant heraldic helm +Adamant heraldic helm +Adamant heraldic helm +Adamant heraldic helm +Adamant heraldic helm +Adamant heraldic helm +Adamant heraldic helm +Adamant heraldic helm +Adamant kiteshield +Adamant kiteshield +Adamant kiteshield +Adamant kiteshield +Adamant kiteshield +Adamant kiteshield +Adamant kiteshield +Adamant kiteshield +Adamant kiteshield +Adamant kiteshield +Adamant kiteshield +Adamant kiteshield +Adamant kiteshield +Adamant kiteshield +Adamant kiteshield +Adamant kiteshield +Superior dragon bonemeal +Revitalisation potion +Swamp paste +Water container +Ancient key +Dragon key +Dragon key piece +Dragon key piece +Dragon key piece +Dragon key piece +Dragon key +Aivas bust +Tristan bust +Camorra bust +Robert bust +Locator orb +Inert locator orb +Old notes +Old notes +Old notes +Old notes +Old notes +Old notes +Old notes +Old notes +Old notes +Old notes +Old notes +Old notes +Old notes +Old notes +The weeping +Serafina +Lutwidge and the moonfly +Imafore's betrayal +Imcandoria's fall +Ablenkian's escape +Malumac's journal +Varrock census records +Aivas' diary +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Map piece +Rift guardian +Assembler max cape (broken) +Ava's assembler (broken) +Rune dragon +Vorkath's stuffed head +Kristmas kebab +Bulging sack +Empty sack +Vault key +Santa's seal +Promissory note +Logs and kindling +Santa suit (dry) +Santa suit (wet) +Santa suit +Fine mesh net +Snow sprite +Wise old man's teleport tablet +Enchanted snowy curtains +Enchanted curtains +Wise old man's santa hat +Snow imp costume feet +Snow imp costume gloves +Snow imp costume tail +Snow imp costume legs +Snow imp costume body +Snow imp costume head +Ogre artefact +Enchanted symbol +Demon's heart +Ent's roots +Justiciar's hand +Obelisk +Certificate +Letter +Secret page +A dark disposition +Jewellery of jubilation +History and hearsay +The fisher's flute +Lunch by the lancalliums +Kharedst's memoirs +Certificate +Royal accord of twill +Varlamore envoy +Granite ring (i) +Granite cannonball +Brittle key +Diving apparatus +Diving helmet +Jonas mask +Traiborn note +Time bubble +Carved gem +Note +Tattered book +Tomberries +Spectral potion +Murky potion +Death note +Runefest shield +Bowl of fish +Fossil island note book +Mermaid's tear +Antique lamp +Antique lamp +Ancient diary +Archaeologist's diary +Hoop snake +Rare fossilised tusk +Rare fossilised skull +Rare fossilised pelvis +Rare fossilised ribs +Rare fossilised spine +Rare fossilised limbs +Large fossilised skull +Large fossilised pelvis +Large fossilised ribs +Large fossilised spine +Large fossilised limbs +Fossilised mushroom +Fossilised leaf +Fossilised branch +Fossilised stump +Fossilised roots +Medium fossilised skull +Medium fossilised pelvis +Medium fossilised ribs +Medium fossilised spine +Medium fossilised limbs +Small fossilised skull +Small fossilised pelvis +Small fossilised ribs +Small fossilised spine +Small fossilised limbs +Unidentified rare fossil +Unidentified large fossil +Unidentified medium fossil +Unidentified small fossil +Rare enriched bone +Large enriched bone +Medium enriched bone +Small enriched bone +Large rock +Heat-proof vessel +Runite ore fragment +Adamantite ore fragment +Mithril ore fragment +Gold ore fragment +Coal fragment +Silver ore fragment +Iron ore fragment +Potion of sealegs +Bone charm +Sawmill agreement +Sawmill proposal +Herbiboar +Fossil island wyvern +Mahogany seedling (w) +Teak seedling (w) +Mahogany seedling +Teak seedling +Wilderness cape +Wilderness cape +Wilderness cape +Wilderness cape +Wilderness cape +Wilderness champion amulet +Wilderness cape +Wilderness cape +Wilderness cape +Wilderness cape +Wilderness cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Karambwanji +Brutal black dragon +Master scroll book +Rock golem +Rock golem +Rock golem +Minnow +Rock golem +Blue rainbow strand +Green rainbow strand +Yellow rainbow strand +Orange rainbow strand +Red rainbow strand +Rogue's equipment crate +Infernal cape +Infernal eel +Infernal max cape (broken) +Infernal cape (broken) +Tzhaar-hur +Skull sceptre (i) +Copper's crimson collar +Antique lamp +Mysterious orb +Enchanted quill +Enchanted scroll +Slayer's staff (e) +Farmer's strawhat +Farmer's strawhat +Egg mould +Wester chocolate +Rock +Wester fish +Fluffy feathers +Gold fragment +Sea salt +Beef fillet +Wester spices +Bucket of wester sand +Wester lemon +Wester papaya +Wester banana +Crunchy chocolate mix +Fishy chocolate mix +Smoked chocolate mix +Fluffy chocolate mix +Rich chocolate mix +Salted chocolate mix +Meaty chocolate mix +Spicy chocolate mix +Earthy chocolate mix +Bitter chocolate mix +Fresh chocolate mix +Fruity chocolate mix +Crunchy easter egg +Fishy easter egg +Smoked easter egg +Fluffy easter egg +Rich easter egg +Salted easter egg +Meaty easter egg +Spicy easter egg +Earthy easter egg +Bitter easter egg +Fresh easter egg +Fruity easter egg +Servant's money bag +Invitation list +Elder maul +Rock golem +Rock golem +Rock golem +Rock golem +Rock golem +Rock golem +Rock golem +Rock golem +Rock golem +Rock golem +Rock golem +Fire max cape +Bandos godsword +Killer's knife +Notes +Notes +Notes +Sapphire key +Emerald key +Ruby key +Manor key +Ancient tablet +Large storage unit +Medium storage unit +Small storage unit +Infernal harpoon (uncharged) +Dark journal +Houndmaster's diary +Vanguard judgement +Transdimensional notes +Tekton's journal +Nistirio's manifesto +Creature keeper's journal +Corrupted kiteshield +Corrupted plateskirt +Corrupted platelegs +Corrupted platebody +Corrupted helm +Healer icon +Smelly journal +Hardcore ironman platelegs +Hardcore ironman platebody +Hardcore ironman helm +Extra supply crate +Ring of wealth (i1) +Ring of wealth (i2) +Ring of wealth (i3) +Ring of wealth (i4) +Ring of wealth (i5) +Dragon claws (LMS) +Killer's knife +Hunting knife +Notes +Notes +Notes +Sapphire key +Emerald key +Ruby key +Manor key +Zamorak's unfermented wine +Bologa's blessing +Undead combat dummy +Combat dummy +Emerald lantern +Supply crate +Rejuvenation potion (1) +Rejuvenation potion (2) +Rejuvenation potion (3) +Rejuvenation potion (4) +Bruma herb +Rejuvenation potion (unf) +Bruma kindling +Bruma root +Rift guardian +Rift guardian +Rift guardian +Rift guardian +Rift guardian +Rift guardian +Rift guardian +Rift guardian +Rift guardian +Rift guardian +Rift guardian +Rift guardian +Rift guardian +Ring of suffering (ri) +Ring of suffering (r) +Achievement gallery +Superior garden +Obsidian decorative bench +Marble decorative bench +Gnome bench +Teak garden bench +Obsidian fence +Redwood fence +Volcanic theme +Otherworldly theme +Zen theme +Ornate rejuvenation pool +Fancy rejuvenation pool +Rejuvenation pool +Revitalisation pool +Restoration pool +Topiary bush +Spirit tree & fairy ring +Fairy ring +Tip jar +Quest list +Cape hanger +Mounted coins +Mounted emblem +Boss lair display +Ornate jewellery box +Fancy jewellery box +Basic jewellery box +Marble adventure log +Gilded adventure log +Mahogany adventure log +Occult altar +Dark altar +Lunar altar +Ancient altar +Arceuus signet +Lunar signet +Ancient signet +Fairy enchantment +Wooden stool +Carpet +Waxwood bed +Wooden table +Ahrim's robeskirt (LMS) +Ahrim's robetop (LMS) +Bank filler +Armadyl godsword (LMS) +Rope +Amulet of glory +Amulet of power +Adamant gloves +Climbing boots +Granite maul (LMS) +Super energy(1) +Super energy(2) +Super energy(3) +Super energy(4) +Reward casket (easy) +Reward casket (medium) +Reward casket (hard) +Reward casket (elite) +Guthix halo (broken) +Zamorak halo (broken) +Saradomin halo (broken) +Stash units (master) +Stash units (elite) +Stash units (hard) +Stash units (medium) +Stash units (easy) +Penance skirt (broken) +Fighter torso (broken) +Healer hat (broken) +Ranger hat (broken) +Fighter hat (broken) +Decorative armour (broken) +Decorative armour (broken) +Decorative armour (broken) +Decorative armour (broken) +Decorative armour (broken) +Decorative armour (broken) +Decorative armour (broken) +Decorative shield (broken) +Decorative helm (broken) +Decorative armour (broken) +Decorative armour (broken) +Decorative sword (broken) +Void melee helm (broken) +Void ranger helm (broken) +Void mage helm (broken) +Void knight gloves (broken) +Void knight mace (broken) +Elite void robe (broken) +Void knight robe (broken) +Elite void top (broken) +Void knight top (broken) +Dragon defender (broken) +Rune defender (broken) +Adamant defender (broken) +Mithril defender (broken) +Black defender (broken) +Steel defender (broken) +Iron defender (broken) +Bronze defender (broken) +Fire max cape (broken) +Fire cape (broken) +Mystic robe bottom +Mystic robe top +Black d'hide body +Rune platelegs +Dark bow (LMS) +Dragon dagger +Abyssal whip +Prayer potion(1) +Prayer potion(2) +Prayer potion(3) +Prayer potion(4) +Shark +Dragon arrow (LMS) +Clue geode (elite) +Clue geode (hard) +Clue geode (medium) +Clue geode (easy) +Light box +Puzzle box (master) +Puzzle box (master) +Puzzle box (master) +Gnomish firelighter +Clueless scroll +Large spade +Heavy casket +Reward casket (master) +Key (elite) +Clue nest (elite) +Clue nest (hard) +Clue nest (medium) +Clue nest (easy) +Ring of suffering (i) +Minecart control scroll +Hosidius teleport +Soul journey +Damaged soul bearer +Soul bearer +Combat damaged key +Combat scratched key +Bronze key +Royal seed pod +Deconstructed onyx +Charged onyx +Elysian spirit shield dust +Nieve +Monkey +Satchel +Satchel +Kruk monkey greegree +Kruk's paw +Handkerchief +Juice-coated brush +Brush +Book of spyology +Translated note +Scrawled note +Mysterious note +Mysterious note +Mysterious note +Bag full of gems +Useless key +Cabbage rune +Deadman teleport tablet +Chronicle +Present +Nest box (seeds) +Bird nest +Clue bottle (elite) +Clue bottle (hard) +Clue bottle (medium) +Clue bottle (easy) +Farmer's strawhat +Farmer's boots +Farmer's boro trousers +Blasted ore +Dynamite pot +Volcanic sulphur +Juniper charcoal +Shayzien supply set (5) +Shayzien supply set (4) +Shayzien supply set (3) +Shayzien supply set (2) +Shayzien supply set (1) +Shayzien supply platebody (5) +Shayzien supply greaves (5) +Shayzien supply helm (5) +Shayzien supply boots (5) +Shayzien supply gloves (5) +Shayzien supply platebody (4) +Shayzien supply greaves (4) +Shayzien supply helm (4) +Shayzien supply boots (4) +Shayzien supply gloves (4) +Shayzien supply platebody (3) +Shayzien supply greaves (3) +Shayzien supply helm (3) +Shayzien supply boots (3) +Shayzien supply gloves (3) +Shayzien supply platebody (2) +Shayzien supply greaves (2) +Shayzien supply helm (2) +Shayzien supply boots (2) +Shayzien supply gloves (2) +Shayzien supply platebody (1) +Shayzien supply greaves (1) +Shayzien supply helm (1) +Shayzien supply boots (1) +Shayzien supply gloves (1) +Transportation incantations +Treachery of royalty +Tristessa's tragedy +Transvergence theory +Rada's journey +Ideology of darkness +Byrne's coronation speech +Twill accord +Wintertodt parable +Hosidius letter +Killing of a king +Eathram & rada extract +Ricktor's diary (7) +Rada's census +Dark manuscript +Dark manuscript +Dark manuscript +Dark manuscript +Dark manuscript +Dark manuscript +Dark manuscript +Dark manuscript +Dark manuscript +Dark manuscript +Book of arcane knowledge +Ensouled imp head +Dark essence block +Dense essence block +Stolen jewelry box +Stolen family heirloom +Stolen circlet +Stolen garnet ring +Stolen pendant +Bucket of sandworms +Fresh fish +Logavano fruit +Bologano fruit +Golovanova fruit +Logavano seed +Bologano seed +Golovanova seed +Gricoller's fertiliser +Sulphurous fertiliser +Servery stew +Servery uncooked stew +Servery incomplete stew +Servery incomplete stew +Servery potato +Servery cooked meat +Servery pineapple pizza +Servery pineapple chunks +Servery pineapple +Servery plain pizza +Servery uncooked pizza +Servery cheese +Servery incomplete pizza +Servery tomato +Servery pizza base +Servery meat pie +Servery uncooked pie +Servery pie shell +Servery dish +Servery raw meat +Servery pastry dough +Servery flour +Training manual +Intelligence +Gang meeting info +Xeric's talisman +Shayzien medpack +Lovakite ore +Juniper logs +Lovakite bar +Vial of sorrow +Vial of tears (full) +Vial of tears (3) +Vial of tears (2) +Vial of tears (1) +Vial of tears (empty) +Present +Sacred eel +Accumulator max hood +Accumulator max cape +Guthix max hood +Guthix max cape +Zamorak max hood +Zamorak max cape +Saradomin max hood +Saradomin max cape +Rotten onion +Gold baby chinchompa +Black baby chinchompa +Grey baby chinchompa +Blood money +Bank key +Bank key +Bank key +Bank key +Bank key +Overseer's book +Key master's key +Infernal pickaxe (uncharged) +Infernal axe (uncharged) +Rotten egg +Ring of the gods (i) +Magma helm +Magma helm (uncharged) +Tanzanite helm +Tanzanite helm (uncharged) +Oddskull +Old school bond (untradeable) +Package +Volatile mineral +Easter blaster +Incomplete blaster +Empty blaster +Vet'ion jr. +Antique lamp +Antique lamp +Antique lamp +Antique lamp +Bonecrusher +Teleport crystal (5) +Crystal halberd 1/10 +Crystal halberd 2/10 +Crystal halberd 3/10 +Crystal halberd 4/10 +Crystal halberd 5/10 +Crystal halberd 6/10 +Crystal halberd 7/10 +Crystal halberd 8/10 +Crystal halberd 9/10 +Crystal halberd full +New crystal halberd full +Crystal halberd 1/10 (i) +Crystal halberd 2/10 (i) +Crystal halberd 3/10 (i) +Crystal halberd 4/10 (i) +Crystal halberd 5/10 (i) +Crystal halberd 6/10 (i) +Crystal halberd 7/10 (i) +Crystal halberd 8/10 (i) +Crystal halberd 9/10 (i) +Crystal halberd full (i) +New crystal halberd full (i) +Enchanted lyre(5) +Free to play starter pack +Tanzanite pet snakeling +Magma pet snakeling +Ohn's diary +Serpentine helm +Toxic blowpipe +Toxic staff of the dead +Antisanta's coal box (full) +Antisanta's coal box +Rogue's revenge +Hunter's honour +Amulet of the damned +Grim reaper hood +Voice potion +Human eye +Scythe sharpener +Hourglass +Servant's skull +Human bones +Will and testament +Grim robe +Grim reaper's diary +Community pumpkin +Ultimate ironman platelegs +Ultimate ironman platebody +Ultimate ironman helm +Ironman platelegs +Ironman platebody +Ironman helm +Sara's blessed sword (full) +Nest box (ring) +Nest box (seeds) +Nest box (empty) +Archaic emblem (tier 10) +Archaic emblem (tier 9) +Archaic emblem (tier 8) +Archaic emblem (tier 7) +Archaic emblem (tier 6) +Archaic emblem (tier 5) +Archaic emblem (tier 4) +Archaic emblem (tier 3) +Archaic emblem (tier 2) +Archaic emblem (tier 1) +Archaic emblem (tier 1) +Menagerie +Mahogany feeder +Teak feeder +Oak feeder +Pet list +Glorious arena +Advanced arena +Simple arena +Mahogany scratching post +Teak scratching post +Oak scratching post +Volcanic habitat +Polar habitat +Desert habitat +Forest habitat +Grassland habitat +Nature house +Desecrated house +Consecrated house +Mahogany house +Teak house +Oak house +Chaos elemental +Kree'arra +Treasonous ring (i) +Tyrannical ring (i) +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Clan wars cape +Iban's staff (u) +Junk +Junk +Airborne kalphite princess +Amylase pack +Damaged book +Damaged book +Damaged book +Puzzle box (elite) +Challenge scroll (elite) +Casket (elite) +Clue scroll (elite) +Salve amulet(i) +Pay-dirt +Soft clay pack +Soft clay pack +Easter +Holiday tool +Lava dragon bonemeal +Birthday present +Slice of birthday cake +Slice of birthday cake +Trident of the seas +Entomologist's diary +Zamorak banner +Saradomin banner +Slayer ring (1) +Slayer ring (2) +Slayer ring (3) +Slayer ring (4) +Slayer ring (5) +Slayer ring (6) +Slayer ring (7) +Rancid turkey +Knight's notes +Knight's notes +Black mask (i) +Black mask (1) (i) +Black mask (2) (i) +Black mask (3) (i) +Black mask (4) (i) +Black mask (5) (i) +Black mask (6) (i) +Black mask (7) (i) +Black mask (8) (i) +Black mask (9) (i) +Black mask (10) (i) +Berserker ring (i) +Warrior ring (i) +Archers ring (i) +Seers ring (i) +Crystal bow 1/10 (i) +Crystal bow 2/10 (i) +Crystal bow 3/10 (i) +Crystal bow 4/10 (i) +Crystal bow 5/10 (i) +Crystal bow 6/10 (i) +Crystal bow 7/10 (i) +Crystal bow 8/10 (i) +Crystal bow 9/10 (i) +Crystal bow full (i) +New crystal bow (i) +Trollheim teleport +Yanille teleport +Brimhaven teleport +Rellekka teleport +Pollnivneach teleport +Taverley teleport +Rimmington teleport +Scroll of redirection +Open herb box +Herb box +Absorption (1) +Absorption (2) +Absorption (3) +Absorption (4) +Overload (1) +Overload (2) +Overload (3) +Overload (4) +Super magic potion (1) +Super magic potion (2) +Super magic potion (3) +Super magic potion (4) +Super ranging (1) +Super ranging (2) +Super ranging (3) +Super ranging (4) +Iron pickaxe (nz) +Mithril pickaxe (nz) +Rune pickaxe (nz) +Fire rune (nz) +Earth rune (nz) +Water rune (nz) +Air rune (nz) +Blood rune (nz) +Death rune (nz) +Chaos rune (nz) +Magic secateurs (nz) +Anti-dragon shield (nz) +Cursed goblin staff +Cursed goblin bow +Cursed goblin hammer +Raw pheasant +Mithril arrow +Steel arrow +Iron arrow +Bronze arrow +Astral rune +Soul rune +Blood rune +Cosmic rune +Law rune +Chaos rune +Nature rune +Death rune +Body rune +Mind rune +Earth rune +Air rune +Water rune +Fire rune +Hair clip +Scrap paper +Address form +Antique lamp +Black knight helm +Explorer's notes +Void seal(1) +Void seal(2) +Void seal(3) +Void seal(4) +Void seal(5) +Void seal(6) +Void seal(7) +Book of knowledge +Ava's device +Fish vial +Fish vial +Rune hasta(kp) +Adamant hasta(kp) +Mithril hasta(kp) +Steel hasta(kp) +Iron hasta(kp) +Bronze hasta(kp) +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Ancient page +Barbarian skills +My notes +Mangled bones +Barbarian rod +Beret mask +Elvarg's head +Impling scroll +Magic butterfly net +Jar generator +Magic beans +Golden goblin +Shrink-me-quick +To-do list +Shrinking recipe +Rupert's helmet +Music sheet +Miazrqa's pendant +Clean necklace +Digsite pendant (5) +Digsite pendant (4) +Digsite pendant (3) +Digsite pendant (2) +Digsite pendant (1) +Antique lamp +Antique lamp +Antique lamp +Antique lamp +Antique lamp 4 +Museum map +Old chipped vase +Old symbol +Ancient symbol +Ancient coin +Old coin +Pottery +Jewellery +Arrowheads +Uncleaned find +Hunter kit +Cyrisus's chest +Astral rune shards +Ground astral rune +Dream potion +Dream vial (herb) +Dream vial (water) +Dream vial (empty) +Antique lamp 3 +Antique lamp 2 +Antique lamp 1 +Dragon bracelet +Zanik (slice) +Goblin village sphere +Artefact +Mace +Artefact +Sword fragment +Artefact +Shield fragment +Artefact +Helmet fragment +Artefact +Axe head +Artefact +Armour shard +Brine rat +Rope +Rotten barrel +Rotten barrel +Key +Key +Key +Key +Key +Parchment +Windswept logs +Sven's last map +Cruder carving +Crude carving +Damp planks +Easter egg +Easter egg +Easter egg +Easter egg +Chocolate kebbit +Chocolate chunks +Rabbit mould +Magic egg +Beacon ring +Infused wand +Wand +Zaff's instructions +Surok's letter +Letter to surok +Rat's paper +Full folder +Used folder +An empty folder +Sin'keth's diary +Dagon'hai history +Cave goblin +Molanisk +Perfect snail shell +Perfect shell +Powerbox +Powerbox +Lever +Lever +Capacitor +Capacitor +Meter +Meter +Fuse +Fuse +Cog +Cog +Spanner +Dorgesh-kaan sphere +Eel sushi +Loach +Fillets +Mushrooms +Roast frog +Grubs à la mode +Fingers +Bat shish +Coated frogs' legs +Frogburger +Frogspawn gumbo +Green gloop soup +Skull staples +Skull staples +Starjump +Situp +Run +Pushup +Reward token +Reward token +Reward token +Reward token +Reward token +Reward token +Skull staple +Crate part +Keg +Shipping order +Wolf whistle +Bell jar +Brain tongs +Cranial clamp +Prayer book +Blessed lamp +Barrelchest anchor +Barrelchest anchor +Prayer book +Keg +Fuse +Metal bar +Valve wheel +Coloured ball +Metal sheet +Pipe ring +Pipe +Binding fluid +Rivets +Builder's boots +Builder's trousers +Builder's shirt +Hard hat +Shadow sword +Severed leg +Sin seer's note +Winter garden +Autumn garden +Spring garden +Summer garden +Winter sq'irkjuice +Autumn sq'irkjuice +Summer sq'irkjuice +Spring sq'irkjuice +Winter sq'irk +Autumn sq'irk +Summer sq'irk +Spring sq'irk +Decapitated head +A jester stick +Silly jester boots +Silly jester tights +Silly jester top +Silly jester hat +Bulging taxbag +Hefty tax bag +Normal tax bag +Light tax bag +Empty tax bag +Royal decree +Documents +Kgp id card +Mission report +Mission report +Mission report +Clockwork suit +Clockwork suit +Clockwork book +Cowbells +Penguin bongos +Terror dog +Tarn's diary +Combat lamp +Parchment +Keris(p++) +Keris(p+) +Keris(p) +Keris +Healer icon +Fire cape +No eggs +Queen help book +Spikes +Collector horn +Healer icon +Defender icon +Collector icon +Attacker icon +Healing vial +Healing vial(1) +Healing vial(2) +Healing vial(3) +Healing vial(4) +Poisoned meat +Poisoned worms +Poisoned tofu +Defender horn +Omega egg +Spiked/pois. egg +Poisoned egg +Yellow egg +Blue egg +Red egg +Green egg +Healer horn +Healer horn +Healer horn +Healer horn +Healer horn +Collection bag +Collection bag +Collection bag +Collection bag +Collection bag +Attacker horn +Attacker horn +Attacker horn +Attacker horn +Attacker horn +Worms +Tofu +Crackers +Scroll +Fremennik sea boots +Gublinch shards +Snowball +Crone-made amulet +Ava's attractor +A container +A pattern +Translated notes +Research notes +Blessed axe +Undead twigs +Bar magnet +Selected iron +Undead chicken +Empty sack +Scroll +White logs +Feathered journal +Odd bird seed +Bronze feather +Silver feather +Golden feather +Metal feather +Bird book +Fake beak +Eagle cape +Eagle feather +Imp-in-a-box(1) +Imp-in-a-box(2) +Falconer's glove +Falconer's glove +Rabbit +Giant eagle +Butterfly +Tropical wagtail +Golden warbler +Cerulean twitch +Copper longtail +Crimson swift +Kebbit +Kebbit +Kebbit +Kebbit +Kebbit +Kebbit +Kebbit +Kebbit +Kebbit +Kebbit +Kebbit +Kebbit +Imp +Footprint +Hunter cape +Old red disk +Cap and goggles +Bomber cap +Bomber jacket +Sandbag +Black balloon +Pink balloon +Purple balloon +Green balloon +Orange balloon +Red balloon +Blue balloon +Yellow balloon +Origami balloon +Balloon structure +Auguste's sapling +Evil root +Black destabiliser +Yellow destabiliser +Green destabiliser +Blue destabiliser +Red destabiliser +White destabiliser +Ghost buster 500 +Ghost buster 500 +Ghost buster 500 +Ghost buster 500 +Ghost buster 500 +Ghost buster 500 +Ghost buster 500 +Sailing book +Farming manual +Hardy gout tubers +Goutweedy lump +Costume room +Mahogany treasure chest +Teak treasure chest +Oak treasure chest +Mahogany toy box +Teak toy box +Oak toy box +Marble wardrobe +Carved teak wardrobe +Carved oak wardrobe +Mahogany armour case +Teak armour case +Oak armour case +Mahogany costume box +Teak costume box +Oak costume box +Magical cape rack +Marble cape rack +Gilded cape rack +Mahogany cape rack +Teak cape rack +Oak cape rack +Farming cape +Woodcutting cape +Firemaking cape +Cooking cape +Fishing cape +Smithing cape +Mining cape +Construct. cape +Slayer cape +Fletching cape +Crafting cape +Thieving cape +Herblore cape +Agility cape +Hitpoints cape +Runecraft cape +Magic cape +Prayer cape +Ranging cape +Defence cape +Strength cape +Attack cape +Primed mind bar +Primed bar +Small cog +Medium cog +Large cog +Pipe +Key +Scroll +Crane claw +Lever schematic +Crane schematic +Beaten book +Rock +Slashed book +Training arrows +Training bow +Training shield +Training sword +Stick +Fire rune +Blank fire rune +Mind rune +Blank mind rune +Earth rune +Blank earth rune +Air rune +Blank air rune +Water rune +Blank water rune +Fragment 3 +Fragment 2 +Fragment 1 +Page 3 +Page 2 +Page 1 +Dead sea slug +Door transcription +Commorb v2 +Sea slug glue +Useless key +Bucket +Bucket of water +Tome of experience (1) +Tome of experience (2) +Tome of experience (3) +Ladder top +Sealed message +Haemalchemy volume 1 +Large ornate key +Blood tithe pouch +Message +Castle sketch 3 +Castle sketch 2 +Castle sketch 1 +Message +Daeyalt ore +A handwritten book +Crystal saw seed +Crystal saw +Violet pentagon +Violet square +Violet triangle +Violet circle +Indigo triangle +Indigo circle +Blue pentagon +Blue square +Blue triangle +Blue circle +Green pentagon +Green square +Green triangle +Green circle +Yellow pentagon +Yellow square +Yellow triangle +Yellow circle +Orange pentagon +Orange square +Orange triangle +Orange circle +Red pentagon +Red square +Red triangle +Red circle +Hazelmere's book +Ground mud runes +Weird gloop +Magic glue +Broken cauldron +Dossier +Dossier +Unfinished crunchy +Half made crunchy +Unfinished crunchy +Half made crunchy +Unfinished crunchy +Half made crunchy +Unfinished crunchy +Half made crunchy +Mixed dragon +Mixed dragon +Mixed dragon +Mixed saturday +Mixed saturday +Mixed saturday +Mixed special +Mixed punch +Mixed blast +Mixed sgg +Mixed blizzard +Unfinished bowl +Half made bowl +Unfinished bowl +Half made bowl +Unfinished bowl +Half made bowl +Half made bowl +Tangled toads' legs +Unfinished batta +Half made batta +Unfinished batta +Half made batta +Half made batta +Unfinished batta +Half made batta +Unfinished batta +Half made batta +Aluft aloft box +Reward token +Sawdust +Blurite bar +Bolt pouch +Blurite limbs +Grapple +Blurite bolts (unf) +Jade bolts +Blurite bolts (p++) +Blurite bolts (p+) +Blurite bolts (p) +Jade bolts (e) +Blurite bolts +Astral tiara +Suqah monster +Lunar ring +A special tiara +Lunar amulet +Lunar cape +Lunar boots +Lunar gloves +Lunar legs +Lunar torso +Lunar helm +Soaked kindling +Kindling +Lunar staff - pt3 +Lunar staff - pt2 +Lunar staff - pt1 +Guam-marr vial +Marr vial +Guam vial +Waking sleep vial +Vial of water +Empty vial +Lunar staff +Seal of passage +Ground tooth +Suqah leather +Suqah hide +Suqah tooth +Moonclan manual +Lunar bar +Lunar ore +Moonclan cape +Moonclan boots +Moonclan gloves +Moonclan skirt +Moonclan armour +Moonclan hat +Moonclan helm +Dream log +Emerald lens +Emerald lantern +Emerald lantern +Pink goblin mail +Yellow goblin mail +Black goblin mail +Red goblin mail +Nuff's certificate +Magic essence(1) +Magic essence(2) +Magic essence(3) +Magic essence(4) +Queen's secateurs +Magic essence (unf) +Gorak claw powder +Star flower +Gorak claws +Skull sceptre +Runed sceptre +Strange skull +Fighting boots +Fancy boots +Brewin' guide +Brewin' guide +Torch +Bucket +Bitternut +Pieces of eight +Pirate hat +Pirate bandana +Red monkey +Red monkey +Red monkey +Blue monkey +Blue monkey +Blue monkey +Monkey +Crabclaw hook +Witchwood icon +Cave horror +Zanik (showdown) +Zanik (ham) +Zanik +Zanik +Iron key +Silver key +Bronze key +Steel key +Ground ashes +Five barrels +Four barrels +Three barrels +Two barrels +One barrel +22lb shot +18lb shot +Shot +Defensive shield +Warrior guild token +Steel kiteshield +Steel kiteshield +Steel kiteshield +Steel kiteshield +Steel kiteshield +Steel kiteshield +Steel kiteshield +Steel kiteshield +Steel kiteshield +Steel kiteshield +Steel kiteshield +Steel kiteshield +Steel kiteshield +Steel kiteshield +Steel kiteshield +Steel kiteshield +Rune kiteshield +Rune kiteshield +Rune kiteshield +Rune kiteshield +Rune kiteshield +Rune kiteshield +Rune kiteshield +Rune kiteshield +Rune kiteshield +Rune kiteshield +Rune kiteshield +Rune kiteshield +Rune kiteshield +Rune kiteshield +Rune kiteshield +Rune kiteshield +Steel heraldic helm +Steel heraldic helm +Steel heraldic helm +Steel heraldic helm +Steel heraldic helm +Steel heraldic helm +Steel heraldic helm +Steel heraldic helm +Steel heraldic helm +Steel heraldic helm +Steel heraldic helm +Steel heraldic helm +Steel heraldic helm +Steel heraldic helm +Steel heraldic helm +Steel heraldic helm +Banner +Banner +Banner +Banner +Banner +Banner +Banner +Banner +Banner +Banner +Banner +Banner +Banner +Banner +Banner +Banner +Rune heraldic helm +Rune heraldic helm +Rune heraldic helm +Rune heraldic helm +Rune heraldic helm +Rune heraldic helm +Rune heraldic helm +Rune heraldic helm +Rune heraldic helm +Rune heraldic helm +Rune heraldic helm +Rune heraldic helm +Rune heraldic helm +Rune heraldic helm +Rune heraldic helm +Rune heraldic helm +Construction guide +Formal garden +Garden +Treasure room +Dungeon stairs +Dungeon cross +Dungeon corridor +Oubliette +Throne room +Portal chamber +Study +Workshop +Chapel +Hall +Combat room +Games room +Bedroom +Dining room +Kitchen +Parlour +Banner easel +Shield easel +Pluming stand +Armour stand +Whetstone +Repair bench +Tool store 5 +Tool store 4 +Tool store 3 +Tool store 2 +Tool store 1 +Crafting table 4 +Crafting table 3 +Crafting table 2 +Crafting table 1 +Bench with lathe +Bench with vice +Steel framed bench +Oak workbench +Wooden workbench +Greater magic cage +Lesser magic cage +Floor decoration +Trapdoor +Mahogany lever +Teak lever +Oak lever +Demonic throne +Crystal throne +Skeleton throne +Gilded throne +Mahogany throne +Teak throne +Oak throne +Infernal chart +Astronomical chart +Alchemical chart +Crystal of power +Elemental sphere +Crystal ball +Mahogany telescope +Teak telescope +Oak telescope +Large orrery +Small orrery +Armillary sphere +Celestial globe +Lunar globe +Ornamental globe +Globe +Mahogany demon +Mahogany eagle +Teak demon lectern +Teak eagle lectern +Demon lectern +Eagle lectern +Oak lectern +Scrying pool +Greater focus +Teleport focus +Marble portal +Mahogany portal +Teak portal +Marble fireplace +Stone fireplace +Clay fireplace +Opulent curtains +Curtains +Torn curtains +Mahogany bookcase +Oak bookcase +Wooden bookcase +Opulent rug +Rug +Brown rug +Mahogany armchair +Teak armchair +Oak armchair +Oak chair +Rocking chair +Wooden chair +Crude wooden chair +Mahogany ladder +Teak ladder +Oak ladder +Rocnar +Flame pit +Tentacle pool +Spikes +Bone cage +Spiked cage +Steel cage +Oak and steel cage +Oak cage +Isafdar +Karamja +Morytania +The desert +Lumbridge +Miscellanians +Giant dwarf +Elena +King arthur +Rune case 3 +Rune case 2 +Rune case 1 +Cw armour 3 +Cw armour 2 +Cw armour 1 +Runite armour +Adamantite armour +Mithril armour +Mounted shark +Mounted swordfish +Mounted bass +Marble spiral +Spiral staircase +Marble staircase +Teak staircase +Oak staircase +Teak kitchen table +Oak kitchen table +Wood kitchen table +Cider barrel +Beer barrel +Cushioned basket +Cat basket +Cat blanket +Teak larder +Oak larder +Wooden larder +Sink +Pump and tub +Pump and drain +Teak shelves 2 +Teak shelves 1 +Oak shelves 2 +Oak shelves 1 +Wooden shelves 3 +Wooden shelves 2 +Wooden shelves 1 +Fancy range +Steel range +Large oven +Small oven +Firepit with pot +Firepit with hook +Firepit +Roses +Sunflower +Bluebells +Daffodils +Tall box hedge +Tall fancy hedge +Fancy hedge +Topiary hedge +Small box hedge +Nice hedge +Thorny hedge +Marble wall +Garden fence +Picket fence +Iron railings +Stone wall +Wooden fence +Boundary stones +Posh fountain +Large fountain +Small fountain +Gazebo +Huge plant +Large-leaf plant +Short plant +Tall plant +Bush +Reeds +Thistle +Dock leaf +Fern +Small fern +Plant +Magic tree +Yew tree +Maple tree +Willow tree +Oak tree +Nice tree +Tree +Dungeon entrance +Imp statue +Pond +Decorative rock +Exit portal +Teak prize chest +Oak prize chest +Archery target +Dartboard +Hoop and stick +Hangman game +Treasure hunt +Jester +Magical balance 3 +Magical balance 2 +Magical balance 1 +Marble att. stone +Attack stone +Clay attack stone +Magic chest +Mahogany chest +Teak chest +Oak chest +Wooden crate +Teleport trap +Marble trap +Tangle vine +Man trap +Spike trap +Steel dragon +Dagannoth +Tok-xil +Kalphite soldier +Demon +Hellhound +Troll guard +Huge spider +Baby red dragon +Hobgoblin guard +Guard dog +Skeleton guard +Skull torches +Torches +Candles +Hanging skeleton +Decorative pipe +Decorative blood +Marble door +Steel-plated door +Oak door +Opulent table +Mahogany table +Carved teak table +Teak table +Carved oak table +Oak dining table +Wood dining table +Gilded bench +Mahogany bench +Carved teak bench +Teak dining bench +Carved oak bench +Oak bench +Wooden bench +Kite shield +Square shield +Round shield +Gilded decoration +Teak decoration +Oak decoration +Posh bell-pull +Bell-pull +Rope bell-pull +High-level plants +Mid-level plants +Low-level plants +Rune display case +Large landscape +Large portrait +Small landscape +Mounted sword +Major head +Medium head +Minor head +Small portrait +Suit of armour +Large statues +Medium statues +Small statues +Organ +Bells +Windchimes +Stained glass +Decorative window +Shuttered window +Marble burners +Mahogany burners +Incense burners +Gold candlesticks +Steel candlesticks +Steel torches +Wooden torches +Gilded altar +Marble altar +Limestone altar +Mahogany altar +Cloth-covered altar +Teak altar +Oak altar +Bob icon +Guthix icon +Zamorak icon +Saradomin icon +Guthix symbol +Zamorak symbol +Saradomin symbol +Gilded clock +Teak clock +Oak clock +Gilded dresser +Mahogany dresser +Fancy teak dresser +Teak dresser +Oak dresser +Oak shaving stand +Shaving stand +Gilded wardrobe +Mahogany wardrobe +Teak wardrobe +Teak drawers +Oak wardrobe +Oak drawers +Shoe box +Gilded 4-poster +4-poster +Large teak bed +Teak bed +Large oak bed +Oak bed +Wooden bed +Extra weapons rack +Weapons rack +Glove rack +Balance beam +Ranging pedestals +Combat ring +Fencing ring +Boxing ring +Telekinetic grab +Large map +Medium map +Small map +Morytania painting +Lumbridge painting +Karamja painting +Isafdar painting +Desert painting +Misc. portrait +Keldagrim portrait +Elena portrait +Arthur portrait +Stuffed big shark +Stuffed big swordfish +Stuffed big bass +Stuffed kq head +Stuffed kbd heads +Stuffed abyssal head +Stuffed kurask head +Stuffed basilisk head +Stuffed cockatrice head +Stuffed crawling hand +Servant bell +Beam +Lift manual +Longer pulley beam +Long pulley beam +Pulley beam +Scroll +Engine +Letter +Burnt diary +Burnt diary +Burnt diary +Burnt diary +Burnt diary +Empty box +Heavy box +Mining prop +White apron +Casket +Herman's book +Bone seeds +Fresh monkfish +Fresh monkfish +Iron sheet +Dark essence fragments +Easter egg +Easter egg +Easter egg +Easter egg +Easter egg +Easter egg +Easter ring +Al kharid flyer +Empty wine bottle +Bonesack +Ram skull helm +Jackal bone +Bone in vinegar +Jackal bone +Vulture wing +Bone in vinegar +Vulture wing +Big frog leg +Bone in vinegar +Big frog leg +Cave goblin skull +Bone in vinegar +Cave goblin skull +Desert lizard bone +Bone in vinegar +Desert lizard bone +Basilisk bone +Bone in vinegar +Basilisk bone +Rabbit bone +Bone in vinegar +Rabbit bone +Experiment bone +Bone in vinegar +Experiment bone +Undead cow ribs +Bone in vinegar +Undead cow ribs +Seagull wing +Bone in vinegar +Seagull wing +Troll bone +Bone in vinegar +Troll bone +Ghoul bone +Bone in vinegar +Ghoul bone +Terrorbird wing +Bone in vinegar +Terrorbird wing +Ice giant ribs +Bone in vinegar +Ice giant ribs +Fire giant bone +Bone in vinegar +Fire giant bone +Moss giant bone +Bone in vinegar +Moss giant bone +Werewolf bone +Bone in vinegar +Werewolf bone +Zombie bone +Bone in vinegar +Zombie bone +Snake spine +Bone in vinegar +Snake spine +Dagannoth ribs +Bone in vinegar +Dagannoth ribs +Monkey paw +Bone in vinegar +Monkey paw +Mogre bone +Bone in vinegar +Mogre bone +Zogre bone +Bone in vinegar +Zogre bone +Jogre bone +Bone in vinegar +Jogre bone +Ogre ribs +Bone in vinegar +Ogre ribs +Baby dragon bone +Bone in vinegar +Baby dragon bone +Rat bone +Bone in vinegar +Rat bone +Bat wing +Bone in vinegar +Bat wing +Wolf bone +Bone in vinegar +Wolf bone +Giant bat wing +Bone in vinegar +Giant bat wing +Giant rat bone +Bone in vinegar +Giant rat bone +Unicorn bone +Bone in vinegar +Unicorn bone +Ram skull +Bone in vinegar +Ram skull +Bear ribs +Bone in vinegar +Bear ribs +Goblin skull +Bone in vinegar +Goblin skull +Pot of vinegar +Jug of vinegar +Anger spear +Anger mace +Anger battleaxe +Anger sword +Yin yang amulet +Snail shell +Woodcutting tome +Woodcutting tome +Woodcutting tome +Firemaking tome +Firemaking tome +Firemaking tome +Mining tome +Mining tome +Mining tome +Slayer tome +Slayer tome +Slayer tome +Thieving tome +Thieving tome +Thieving tome +Agility tome +Agility tome +Agility tome +Fishing tome +Fishing tome +Fishing tome +Short vine +Long vine +Reward token +Reward token +Reward token +Branch +Paintbrush +Cup of tea +Cup of tea +Porcelain cup +Cup of tea +Cup of tea +Porcelain cup +Cup of tea +Cup of tea +Empty cup +Pot of tea (1) +Pot of tea (2) +Pot of tea (3) +Pot of tea (4) +Pot of tea (1) +Pot of tea (2) +Pot of tea (3) +Pot of tea (4) +Hot kettle +Full kettle +Bow and arrow +Dart +Hoop +Game book +Prize key +Treasure stone +Wooden shield +Wooden sword +Boxing gloves +Boxing gloves +Rod mould +Rod of ivandis (1) +Rod of ivandis (2) +Rod of ivandis (3) +Rod of ivandis (4) +Rod of ivandis (5) +Rod of ivandis (6) +Rod of ivandis (7) +Rod of ivandis (8) +Rod of ivandis (9) +Rod of ivandis (10) +Silvthrill rod +Silvthrill rod +Rod dust +Modern day morytania +Histories of the hallowland +The sleeping seven +Temple library key +Dusty scroll +Plaster fragment +Item +Coffin +Coffin +Coffin +Coffin +Coffin +Dummy +Wily hellcat +Lazy hell cat +Hell-kitten +Hell cat +Overgrown hellcat +Snake over-cooked +Stuffed snake +Odd stuffed snake +Raw stuffed snake +Snake corpse +Tchiki nut paste +Sliced red banana +Tchiki monkey nuts +Red banana +Balloon toad +Balloon toad +Enchanted flour +Enchanted milk +Enchanted egg +Raw guide cake +Cake of guidance +Broken crab shell +Broken crab claw +Crab helmet +Fresh crab shell +Crab claw +Fresh crab claw +Diving apparatus +Fishbowl helmet +Rock +Mudskipper hide +Burnt fishcake +Cooked fishcake +Raw fishcake +Ground cod +Ground crab meat +Cooked crab meat +Cooked crab meat +Cooked crab meat +Cooked crab meat +Burnt crab meat +Crab meat +Crab meat +Ground kelp +Kelp +Dyed orange +Spicy maggots +Soggy bread +Slop of compromise +Dwarven rock cake +Dwarven rock cake +Asgoldian ale +Antique lamp +Dirty blast +Empty spice shaker +Yellow spice (1) +Yellow spice (2) +Yellow spice (3) +Yellow spice (4) +Brown spice (1) +Brown spice (2) +Brown spice (3) +Brown spice (4) +Orange spice (1) +Orange spice (2) +Orange spice (3) +Orange spice (4) +Red spice (1) +Red spice (2) +Red spice (3) +Red spice (4) +Spicy stew +Dragon token +Evil chicken's egg +Brulee supreme +Brulee +Brulee +Brulee +Cinnamon +Milky mixture +Cornflour mixture +Vanilla pod +Book on chickens +Cornflour +Fungicide +Fungicide spray 0 +Fungicide spray 1 +Fungicide spray 2 +Fungicide spray 3 +Fungicide spray 4 +Fungicide spray 5 +Fungicide spray 6 +Fungicide spray 7 +Fungicide spray 8 +Fungicide spray 9 +Fungicide spray 10 +Mutated zygomite +Bird nest +Symptoms list +Queen's secateurs +Draynor skull +Blue logs +Green logs +Red logs +Challenge scroll (hard) +Killerwatt +Braindeath 'rum' +Tinderbox +Rope +Tacks +Canister +Repair plank +Cannon balls +Broken cannon +Cannon barrel +Book o' piracy +Plunder +Rapier +Harry's cutlass +Lucky cutlass +Repair plank +Ramrod +Cannon ball +Canister +Fuse +Gunpowder +Lit bug lantern +Swarm +Camel mask +Stone head +Camel mould (p) +Stone right leg +Stone left leg +Stone right arm +Stone left arm +K sigil +R sigil +M sigil +Z sigil +Stone head +Stone head +Stone head +Stone head +Sandstone base +Sandstone body +Sandstone (32kg) +Sandstone (20kg) +Pyramid top +Shark +Dragon med helm +Prison key +Square sandwich +Roll +Baguette +Sand +Wizard's head +Rose tinted lens +Pink dye +Redberry juice +Bottled water +Truth serum +Magical orb (a) +Magical orb +A magic scroll +Sandy's rota +Bert's rota +Beer soaked hand +Sandy hand +Bones to peaches +Animals' bones +Animals' bones +Animals' bones +Animals' bones +Dragonstone +Orb +Pentamid +Icosahedron +Cube +Cylinder +Rune longsword +Emerald +Adamant med helm +Adamant kiteshield +Leather boots +Progress hat +Progress hat +Progress hat +Peach +Green marionette +Green marionette +Green marionette +Green marionette +Blue marionette +Blue marionette +Blue marionette +Blue marionette +Red marionette +Red marionette +Red marionette +Red marionette +Red marionette +Green marionette +Blue marionette +Marionette handle +Bauble box +Puppet box +Bauble box +Puppet box +Bell bauble +Bell bauble +Bell bauble +Bell bauble +Bell bauble +Bell bauble +Tree bauble +Tree bauble +Tree bauble +Tree bauble +Tree bauble +Tree bauble +Diamond bauble +Diamond bauble +Diamond bauble +Diamond bauble +Diamond bauble +Diamond bauble +Box bauble +Box bauble +Box bauble +Box bauble +Box bauble +Box bauble +Star bauble +Star bauble +Star bauble +Star bauble +Star bauble +Star bauble +Orb +Relic +Large pouch +Bow-sword +Slender blade +Skeletal wyvern +Wyvern bonemeal +Leon's champion scroll +Ballad +Ancestral key +Sole +Shoes +Torn robe +Torn robe +Robe of elidinis +Robe of elidinis +Statuette +Menaphite thug +Rat pole +Rat pole +Rat pole +Rat pole +Rat pole +Rat pole +Rat pole +Smouldering pot +Pot of weeds +Directions +Music scroll +Poisoned cheese +Book +Cat antipoison +Chest +Scroll +Letter +Letter +Journal +Enchanted key +Demonic tome +Demonic sigil +Demonic sigil mould +Darklight +Silverlight +Dagannoth-king bonemeal +Rusty scimitar +Unsanitary swill +Fever spider body +Karamthulhu +Karamthulhu +Sluglings +Holy wrench +Wrench +Bucket of water +Blindweed +Blindweed seed +Fever spider +Slayer gloves +Camulet +Ice cooler +Desert lizard +Seaweed in a box? +Seaweed in a box +Guam in a box? +Guam in a box +Tiny net +Fishbowl and net +Fishbowl pet +Fishbowl +Fishbowl +Fishbowl +Forlorn boot +Broken fishing rod +Mogre +Fishing explosive +Camo helmet +Camo bottoms +Camo top +Crystal trinket +Newly made crystal +Newly made crystal +Blackened crystal +Edern's journal +Item list +Fractured crystal +Fractured crystal +Magenta crystal +Blue crystal +Cyan crystal +Green crystal +Yellow crystal +Red crystal +Hand mirror +Colour wheel +Dark beast +Solus's hat +Commorb +Ahab's beer +Wily cat +Wily cat +Wily cat +Wily cat +Wily cat +Wily cat +Lazy cat +Lazy cat +Lazy cat +Lazy cat +Lazy cat +Lazy cat +Nurse hat +Doctor's hat +Recipe +Chores +Catspeak amulet(e) +Antique lamp +Present +Mouse toy +Tokkul +Agility climb +Agility contortion +Agility balance +Agility jump +List +Trolley +White tree fruit +Plant cure +Rune dust +Rune shards +Ring of charos(a) +White tree sapling +White tree shoot (w) +White tree shoot +White tree shoot +Snowdrop seed +Orchid seed +Orchid seed +Delphinium seed +Vine seed +Pink rose seed +Red rose seed +White rose seed +Mage arena cape +Kandarin headgear +Spadeful of coke +Small fishing net +Fishlike thing +Raw fishlike thing +Fishlike thing +Raw fishlike thing +Frog mask +Princess skirt +Princess blouse +Prince leggings +Prince tunic +Raw pheasant +Raw pheasant +Enchanted lyre(4) +Enchanted lyre(3) +Enchanted lyre(2) +Beer glass +A chair +Letter +Square stone +Square stone +Kelda stout +Kelda hops +Kelda seed +Ghostly cloak +Ghostly gloves +Ghostly hood +Ghostly robe bottom +Ghostly robe top +Ghostly boots +Elf +New key +Crystal teleport seed +Teleport crystal (1) +Teleport crystal (2) +Teleport crystal (3) +Teleport crystal (4) +Toxic powder +Sieve +Toxic naphtha +Naphtha apple mix +Apple barrel +Rotten apples +Green toad +Yellow toad +Red toad +Blue toad +Green dye bellows +Yellow dye bellows +Blue dye bellows +Red dye bellows +Worn key +Tarnished key +Fixed device +Broken device +Tegid's soap +Mourner letter +Mourner cloak +Mourner boots +Mourner gloves +Mourner trousers +Ripped mourner trousers +Mourner top +Bloody mourner top +Spirit tree +Scarecrow +Hay sack +Hay sack +Spirit roots +Amulet of nature +Antidote++ (unf) +Antidote+ (unf) +Weapon poison++ (unf) +Weapon poison+ (unf) +Rotten potato +Poison dagger(p++) +Poison dagger(p+) +Shaikahan bonemeal +Magic carpet +Hourglass +Chicken +Fox +Grain +Knife +Magnet +Shears +Bronze wire +Chisel +Tin +Tin +Tin +Tin +Tin +Tin +Tin +Tin +Tin +??? mixture +??? mixture +??? mixture +Alchemical notes +Metal spade +Metal spade +Bronze key +Cupric ore powder +Tin ore powder +Vial of liquid +Nitrous oxide +Sodium chloride +Gypsum +Acetic acid +Cupric sulfate +Desert amulet +Tiles +Tiles +Tiles +Tile +Gear +Gear +Gear +Gear +Gear +Gear +Mystic jewel +Stethoscope +Flash powder +Rogue kit +Law tiara +Abyssal book +Scrying orb +Scrying orb +Giant pouch +Large pouch +Medium pouch +Book of folklore +Old man's message +Calquat seedling (w) +Palm seedling (w) +Papaya seedling (w) +Pineapple seedling (w) +Curry seedling (w) +Orange seedling (w) +Banana seedling (w) +Apple seedling (w) +Calquat seedling +Palm seedling +Papaya seedling +Pineapple seedling +Curry seedling +Orange seedling +Banana seedling +Apple seedling +Spirit sapling +Spirit seedling (w) +Magic seedling (w) +Yew seedling (w) +Maple seedling (w) +Willow seedling (w) +Oak seedling (w) +Spirit seedling +Magic seedling +Yew seedling +Maple seedling +Willow seedling +Oak seedling +Smoke devil +Rake head +Rake handle +Watering can(8) +Watering can(7) +Watering can(6) +Watering can(5) +Watering can(4) +Watering can(3) +Watering can(2) +Watering can(1) +Spade head +Spade handle +Seeds +Explorer's ring +Morytania legs +Sea snake +Varrock armour +Green bird egg +Blue bird egg +Red bird egg +Bird nest +Bird nest +Bird nest +Bird nest +Bird nest +Master farmer +Exquisite clothes +Meeting notes +Book on costumes +Exquisite boots +Right boot +Left boot +Dwarven battleaxe +Dwarven battleaxe +Dwarven battleaxe +Dwarven battleaxe +Dwarven battleaxe +Dwarven battleaxe +Minecart ticket +Minecart ticket +Mining helmet +Peace treaty +Silverware +Key +Goblin symbol book +Brooch +Frog spawn +Verac's plateskirt 25 +Verac's plateskirt 50 +Verac's plateskirt 75 +Verac's plateskirt 100 +Verac's brassard 25 +Verac's brassard 50 +Verac's brassard 75 +Verac's brassard 100 +Verac's flail 25 +Verac's flail 50 +Verac's flail 75 +Verac's flail 100 +Verac's helm 25 +Verac's helm 50 +Verac's helm 75 +Verac's helm 100 +Torag's platelegs 25 +Torag's platelegs 50 +Torag's platelegs 75 +Torag's platelegs 100 +Torag's platebody 25 +Torag's platebody 50 +Torag's platebody 75 +Torag's platebody 100 +Torag's hammers 25 +Torag's hammers 50 +Torag's hammers 75 +Torag's hammers 100 +Torag's helm 25 +Torag's helm 50 +Torag's helm 75 +Torag's helm 100 +Karil's leatherskirt 25 +Karil's leatherskirt 50 +Karil's leatherskirt 75 +Karil's leatherskirt 100 +Karil's leathertop 25 +Karil's leathertop 50 +Karil's leathertop 75 +Karil's leathertop 100 +Karil's crossbow 25 +Karil's crossbow 50 +Karil's crossbow 75 +Karil's crossbow 100 +Karil's coif 25 +Karil's coif 50 +Karil's coif 75 +Karil's coif 100 +Guthan's chainskirt 25 +Guthan's chainskirt 50 +Guthan's chainskirt 75 +Guthan's chainskirt 100 +Guthan's platebody 25 +Guthan's platebody 50 +Guthan's platebody 75 +Guthan's platebody 100 +Guthan's warspear 25 +Guthan's warspear 50 +Guthan's warspear 75 +Guthan's warspear 100 +Guthan's helm 25 +Guthan's helm 50 +Guthan's helm 75 +Guthan's helm 100 +Dharok's platelegs 25 +Dharok's platelegs 50 +Dharok's platelegs 75 +Dharok's platelegs 100 +Dharok's platebody 25 +Dharok's platebody 50 +Dharok's platebody 75 +Dharok's platebody 100 +Dharok's greataxe 25 +Dharok's greataxe 50 +Dharok's greataxe 75 +Dharok's greataxe 100 +Dharok's helm 25 +Dharok's helm 50 +Dharok's helm 75 +Dharok's helm 100 +Ahrim's robeskirt 25 +Ahrim's robeskirt 50 +Ahrim's robeskirt 75 +Ahrim's robeskirt 100 +Ahrim's robetop 25 +Ahrim's robetop 50 +Ahrim's robetop 75 +Ahrim's robetop 100 +Ahrim's staff 25 +Ahrim's staff 50 +Ahrim's staff 75 +Ahrim's staff 100 +Ahrim's hood 25 +Ahrim's hood 50 +Ahrim's hood 75 +Ahrim's hood 100 +Ourg bonemeal +Raurg bonemeal +Fayrg bonemeal +Zogre bonemeal +Ogre gate key +Cup of tea +Necromancy book +Strange potion +Book of 'h.a.m' +Ogre artefact +Book of portraiture +Signed portrait +Sithik portrait +Sithik portrait +Dragon inn tankard +Ruined backpack +Torn page +Black prism +Crumbling tome +Stone bowl +Sapphire lantern +Sapphire lantern +Sapphire lantern +Bucket of saltwater +Sphinx's token +Embalming manual +Unholy symbol +Holy symbol +Canopic jar +Canopic jar +Canopic jar +Canopic jar +Catspeak amulet +Gilded cross +Shadow diamond +Smoke diamond +Ice diamond +Blood diamond +Blessed pot +Silver pot +Blessed pot +Silver pot +Blessed pot +Silver pot +Blessed pot +Silver pot +Blessed pot +Silver pot +Ring of visibility +Warm key +Translation +Etchings +Fire +Bandit +Golem program +Phoenix quill pen +Black dye +Phoenix feather +Black mushroom +Strange implement +Statuette +Display cabinet key +Varmen's notes +Letter +Desert disguise +Red hot sauce +Snake basket full +Snake basket +Snake charm +Hag's poison +Receipt +Ugthanki dung +Ugthanki dung +Oak blackjack +Note +Note +Jewels +Keys +Black spear(kp) +Cannon ball +Schematic +Schematics +Schematics +Schematic +Base schematics +Pages +Pages +Book page 3 +Book page 2 +Book page 1 +Dwarven lore +Pink sweets +Green sweets +Red sweets +White sweets +Deep blue sweets +Blue sweets +Bullseye lantern +Oil lantern +Candle lantern +Candle lantern +Oil lamp +Swamp cave bug +Swamp cave slime +Swamp wallbeast +Bearhead +Pole +Asleif's necklace +Corpse of woman +Half a rock +White pearl seed +White pearl +Safety guarantee +Antique lamp +Steel key ring +Red mahogany log +Sharpened axe +Chicken cage +Breathing salts +Weather report +Weathervane pillar +Broken vane part +Ornament +Broken vane part +Directionals +Broken vane part +Animate rock scroll +Iron oxide +Comfy mattress +Stodgy mattress +Herbal tincture +Blunt axe +Male h.a.m. +Female h.a.m. +Bucket of slime +Bedsheet +Bedsheet +Petition form +Ecto-token +Treasure map +Map scrap +Map scrap +Map scrap +Jogre bonemeal +Skeleton bonemeal +Large zombie monkey bonemeal +Small zombie monkey bonemeal +Monkey bonemeal +Bearded gorilla bonemeal +Gorilla bonemeal +Medium ninja bonemeal +Small ninja bonemeal +Wolf bonemeal +Dragon bonemeal +Baby dragon bonemeal +Burnt jogre bonemeal +Burnt bonemeal +Big bonemeal +Bat bonemeal +Bonemeal +Model ship +Model ship +Ectophial +Ectophial +Ghostspeak amulet +Translation manual +Book of haricanto +Mystical robes +Cup of tea +Cup of tea +Porcelain cup +Cup of tea +Cup of tea +Nettles +Nettle tea +Nettle tea +Puddle of slime +Nettle-water +Signed oak bow +New crystal shield +Crystal shield 1/10 +Crystal shield 2/10 +Crystal shield 3/10 +Crystal shield 4/10 +Crystal shield 5/10 +Crystal shield 6/10 +Crystal shield 7/10 +Crystal shield 8/10 +Crystal shield 9/10 +Crystal shield full +Crystal bow 1/10 +Crystal bow 2/10 +Crystal bow 3/10 +Crystal bow 4/10 +Crystal bow 5/10 +Crystal bow 6/10 +Crystal bow 7/10 +Crystal bow 8/10 +Crystal bow 9/10 +Crystal bow full +New crystal bow +Elf crystal +Consecration seed +Consecration seed +Letter +Journal +Ring of charos +Conductor +Conductor mould +Pickled brain +Decapitated head +Decapitated head +Legs +Arms +Torso +Extended brush +Extended brush +Extended brush +Garden brush +Garden cane +Obsidian amulet +Marble amulet +Shed key +Tower key +Cavern key +Star amulet +Goutweed +Mouth grip +Dragon platelegs +Stick +Abyssal whip +Leaf-bladed spear +Leaf-bladed spear +Enchanted gem +Nechryael +Gargoyle +Kurask +Dust devil +Aberrant spectre +Turoth +Jelly +Bloodveld +Infernal mage +Basilisk +Pyrefiend +Cockatrice +Rockslug +Banshee +Cave crawler +Crawling hand +Trollweiss +Wax +Sled +Sled +Salve shard +Salve amulet +Zealot's key +Crystal-mine key +Glowing fungus +Castle wars ticket +Castlewars manual +Hooded cloak +Hooded cloak +10th squad sigil +Monkey skull +Monkey +Karamjan monkey greegree +Zombie monkey greegree +Zombie monkey greegree +Ancient gorilla greegree +Bearded gorilla greegree +Gorilla greegree +Ninja monkey greegree +Ninja monkey greegree +Monkey talisman +M'speak amulet +M'speak amulet +M'amulet mould +Monkey wrench +Monkey magic +Eye of gnome +Eye of gnome +Enchanted bar +Monkey dentures +Narnode's orders +Gnome royal seal +Hardy gout tuber +Western banner +Wilderness sword +Ghrim's book +Giant pen +Giant nib +Treaty +Good anthem +Awful anthem +Stool +Board game piece +Rusty casket +Lighthouse key +Manual +Diary +Journal +Damaged book +Damaged book +Damaged book +Fremennik shield +Fremennik blade +Fremennik helm +Seer's key +Wooden disk +Red herring +Sealed vase +Sealed vase +Sealed vase +Vase lid +Frozen vase +Vase of water +Vase +Frozen jug +Empty jug +1/3rds full jug +2/3rds full jug +Full jug +Frozen bucket +Empty bucket +1/5ths full bucket +2/5ths full bucket +3/5ths full bucket +4/5ths full bucket +Full bucket +Toy ship +Small pick +Blue thread +Magnet +Red disk +Low alcohol keg +Keg of beer +Warriors' contract +Promissory note +Fiscal statement +Legendary cocktail +Champions token +Weather forecast +Sea fishing map +Unusual fish +Custom bow string +Tracking map +Sturdy boots +Fremennik ballad +Exotic flower +Hunters' talisman +Hunters' talisman +Pet rock +Golden wool +Golden fleece +Branch +Enchanted lyre(1) +Enchanted lyre +Lyre +Unstrung lyre +Fremennik +Shoe +Shoe +Shoe +Shoe +Shoe +Shoe +Boss helper tool +Puzzle box (hard) +Puzzle box (hard) +Silver key black +Silver key crimson +Silver key brown +Silver key red +Black key purple +Black key black +Black key crimson +Black key brown +Black key red +Steel key purple +Steel key black +Steel key crimson +Steel key brown +Steel key red +Bronze key black +Bronze key crimson +Bronze key brown +Bronze key red +Serum 208 (1) +Serum 208 (2) +Serum 208 (3) +Serum 208 (4) +Diary +Cave kraken +Compost bin +Alco-chunks +Storeroom key +Fake man +Dirty robe +Drunk parrot +Troll potion +Ground thistle +Dried thistle +Troll thistle +Goutweed +Hero +Gnome +Paladin +Watchman +Knight +Guard +Rogue +Warrior woman +Farmer +Man +Symbol +Symbol +Symbol +Symbol +Big book of bangs +Strip of cloth +Naphtha mix +Naphtha mix +Barrel of naphtha +Barrel of coal tar +Barrel bomb +Barrel bomb +Ground sulphur +Pot of quicklime +Quicklime +Crystal pendant +Iorwerth's message +King's message +Bones +Large zombie monkey bones +Small zombie monkey bones +Bearded gorilla bones +Gorilla bones +Medium ninja monkey bones +Small ninja monkey bones +Left-handed banana +Dragon spear(kp) +Rune spear(kp) +Adamant spear(kp) +Mithril spear(kp) +Steel spear(kp) +Iron spear(kp) +Bronze spear(kp) +Stuffed monkey +Seaweed sandwich +Monkey skin +Monkey corpse +Karamjan rum +Karamjan rum +Crafting manual +Karambwanji paste +Karambwanji paste +Karambwan paste +Karambwan paste +Karambwan paste +Raw karambwanji +Cooked karambwan +Poison karambwan +Cell key 2 +Cell key 1 +Prison key +Marinated j' bones +Pasty jogre bones +Pasty jogre bones +Marinated j' bones +Pasty jogre bones +Pasty jogre bones +Burnt jogre bones +Certificate +Stone ball +Stone ball +Stone ball +Stone ball +Stone ball +Secret way map +Iou +Combination +Firework +A used spell +Druidic spell +Journal +Mirror +Silver sickle (b) +Druid pouch +Druid pouch +Blessed water +Murky water +Wolfbane +Golden needle +Golden feather +Golden hammer +Golden pot +Golden candle +Golden tinderbox +Iron key +Golden key +Elemental metal +Elemental ore +A stone bowl +A stone bowl +Battered key +Battered book +Ogre bow +Seasoned chompy +Bloated toad +Ogre bellows (1) +Ogre bellows (2) +Ogre bellows (3) +Ogre bellows +Challenge scroll (medium) +Casket (medium) +Clue scroll (medium) +Puzzle box (hard) +Casket (hard) +Clue scroll (hard) +Casket (easy) +Casket (easy) +Casket (easy) +Casket (easy) +Clue scroll (easy) +Chart +Watch +Sextant +Dead orb +Genie lamp +Logs +Paste +Wise old man's partyhat +Wig +Wig +Bronze key +Zamorak staff +Guthix staff +Saradomin staff +Key +Magnet +Diary +Ball +Hazeel's mark +Carnillean armour +Hazeel scroll +Silverlight +Silverlight key +Silverlight key +Silverlight key +Shaman robe +Spell scroll +Magic ogre potion +Potion +Toban's gold +Vial +Vial +Tattered eye patch +Damaged dagger +Unusual armour +Old robe +Fingernails +Crystal +Crystal +Crystal +Crystal +Rock cake +Toban's key +Ogre tooth +Skavid map +Relic part 3 +Relic part 2 +Relic part 1 +Ogre relic +'perfect' gold bar +Palm leaf +Palm leaf +Unfinished batta +Unfinished batta +Unfinished batta +Unfinished batta +Unfinished batta +Unfinished batta +Unfinished batta +Unfinished batta +Unfinished batta +Unfinished batta +Unfinished batta +Raw batta +Half baked batta +Odd batta +Unfinished crunchy +Unfinished crunchy +Unfinished crunchy +Half baked crunchy +Odd crunchies +Unfinished bowl +Unfinished bowl +Unfinished bowl +Unfinished bowl +Unfinished bowl +Raw gnomebowl +Half baked bowl +Odd gnomebowl +Spicy worm +Seasoned legs +Spicy toad's legs +Equa toad's legs +Lava eel +Raw lava eel +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfinished cocktail +Unfermented wine +Unfermented wine +Jug of bad wine +Jug of bad wine +Rotten apple +Chocolatey milk +Flour +Address label +Totem +Guide book +Rock +Prototype dart tip +Bedabin key +Tenti pineapple +Technical plans +Prototype dart +Shantay disclaimer +Scrumpled paper +Slave boots +Slave robe +Slave shirt +Wrought iron key +Ana in a barrel +Barrel +Cell door key +Metal key +Unknown print +Frank's print +Elizabeth's print +David's print +Carol's print +Bob's print +Anna's print +Killer's print +Criminal's dagger +Criminal's dagger +Pungent pot +Flypaper +Silver pot +Silver pot +Silver needle +Silver needle +Silver book +Silver book +Silver bottle +Silver bottle +Silver cup +Silver cup +Silver necklace +Silver necklace +Karamja gloves +Unholy mould +Jail key +Dusty key +Grip's keyring +Oily fishing rod +Id papers +Fire feather +Blamish oil +Blamish snail slime +Ice gloves +Thieves' armband +Pet cat +Pet cat +Pet cat +Pet cat +Pet cat +Pet cat +Fluffs' kitten +Stake +Key +Key +Key +Key +Key +Key +Maze key +Crandor map +Map part +Map part +Map part +Rogue's purse +Grimy rogue's purse +Volencia moss +Grimy volencia moss +Sito foil +Grimy sito foil +Ardrigal +Grimy ardrigal +Snake weed +Grimy snake weed +Picture +Book +A scruffy note +A small key +Gas mask +Ardougne teleport scroll +Hangover cure +Warrant +Iban's ashes +Dwarf brew +Iban's shadow +Amulet of holthion +Amulet of doomion +Amulet of othanian +Iban's dove +Klank's gauntlets +History of iban +Old journal +Doll of iban +Witch's cat +Paladin's badge +Paladin's badge +Paladin's badge +Unicorn horn +Piece of railing +Oily cloth +Orb of light +Orb of light +Orb of light +Orb of light +Rock +Broken glass +Dry sticks +Damp sticks +Sea slug +Weapon poison +Law talisman +Javelin +Warhammer +Halberd +Farmer's fork +Iban's staff +Iban's staff +Poisoned dagger(p) +Cooking pot +Papyrus +Skull +Skull +Throwing rope +Worm +Poisoned dart(p) +Invasion plans +Daconia rock +Twigs +Twigs +Twigs +Twigs +Lumber order +Hazelmere's scroll +Glough's journal +Translation book +Bark sample +Family crest +Crest part +Crest part +Crest part +Steel gauntlets +Chaos gauntlets +'perfect' necklace +'perfect' ring +Dramen staff +Dramen branch +Ardougne cloak +Certificate +Broken shield +Broken shield +Falador shield +Intel report +Book +Cadava potion +Message +Gilded totem +Yommi totem +Holy force +Glowing dagger +Dark dagger +Heart crystal +Lump of crystal +Hunk of crystal +Chunk of crystal +Blue hat +Bravery potion +Ardrigal mixture +Yommi tree seeds +Yommi tree seeds +Holy water +Enchanted vial +Binding book +Shaman's tome +Hollow reed +Hollow reed +Golden bowl +Golden bowl +Golden bowl +Golden bowl +Blessed gold bowl +Sketch +Scrumpled note +A scribbled note +Scrawled note +Bullroarer +Radimus notes +Radimus notes +Clue scroll +Cup of tea +Book on chemicals +Vase +Arcenia root +Chemical compound +Mixed chemicals +Mixed chemicals +Ground charcoal +Nitroglycerin +Unidentified liquid +Ammonium nitrate +Chemical powder +Stone tablet +Broken armour +Damaged armour +Invitation letter +Old tooth +Ceramic remains +Level 3 certificate +Level 2 certificate +Level 1 certificate +Broken glass +Broken staff +Buttons +Broken arrow +Rusty sword +Old boot +Belt buckle +Sealed letter +Unstamped letter +Ancient talisman +Nuggets +Panning tray +Panning tray +Panning tray +Trowel +Rock pick +Cracked sample +Teddy +Special cup +Animal skull +Specimen brush +Specimen jar +Blurite ore +Blurite sword +Portrait +Wampum belt +Bervirius notes +Sword pommel +Paramaya ticket +Paramaya ticket +Bone beads +Beads of the dead +Locating crystal +Locating crystal +Locating crystal +Locating crystal +Locating crystal +Zadimus corpse +Rashiliyia corpse +Crumpled scroll +Tattered scroll +Stone-plaque +Bone shard +Observatory lens +Lens mould +Torch +Lit torch +Gnome amulet +Orbs of protection +Orb of protection +Ghost's skull +Ghostspeak amulet +Enchanted chicken +Enchanted bear +Enchanted rat +Enchanted beef +Scorpion cage +Scorpion cage +Scorpion cage +Scorpion cage +Scorpion cage +Scorpion cage +Scorpion cage +Scorpion cage +Barcrawl card +'perfect' gold ore +Pirate message +Karamjan rum +Medical gown +Pigeon cage +Pigeon cage +Key +Bird feed +Lathas' amulet +Distillator +Touch paper +Plague sample +Sulphuric broline +Liquid honey +Ethenea +Raw giant carp +Giant carp +Rat's tail +Key +Glarial's urn (empty) +Glarial's urn +Glarial's amulet +Glarial's pebble +Key +Book on baxtorian +Research notes +Research package +Blue goblin mail +Orange goblin mail +Plague trousers +Plague jacket +Sheep bones (4) +Sheep bones (3) +Sheep bones (2) +Sheep bones (1) +Sheep feed +Cattleprod +Oil can +Rubber tube +Key +Poisoned fish food +Pressure gauge +Child's blanket +Boots of lightness +Armadyl pendant +Pendant of lucien +Shiny key +Staff of armadyl +Lever +Ice arrows +Khali brew +Khazard armour +Khazard helmet +Black candle +Excalibur +Lit candle +Lit black candle +Fishing pass +Fishing trophy +Red vine worm +Rat poison +Red cog +Blue cog +Black cog +White cog +Holy grail +Magic gold feather +Grail bell +Magic whistle +Holy table napkin +Railing +Instruction manual +Ammo mould +Nulodion's notes +Toolkit +Dwarf remains \ No newline at end of file diff --git a/prisma/robochimp.prisma b/prisma/robochimp.prisma deleted file mode 100644 index 25d6275b83..0000000000 --- a/prisma/robochimp.prisma +++ /dev/null @@ -1,109 +0,0 @@ -generator client { - provider = "prisma-client-js" - previewFeatures = ["fullTextSearch"] - output = "../node_modules/@prisma/robochimp" -} - -datasource db { - provider = "postgresql" - url = env("ROBOCHIMP_DATABASE_URL") -} - -model TriviaQuestion { - id Int @id @unique @default(autoincrement()) - question String @db.VarChar() - answers String[] @db.VarChar() - - @@map("trivia_question") -} - -enum BlacklistedEntityType { - guild - user -} - -model BlacklistedEntity { - id BigInt @id @unique - type BlacklistedEntityType - reason String? - date DateTime @default(now()) @db.Timestamp(6) - - @@map("blacklisted_entity") -} - -model User { - id BigInt @id @unique - bits Int[] - github_id Int? - patreon_id String? - - migrated_user_id BigInt? - - leagues_completed_tasks_ids Int[] - leagues_points_balance_osb Int @default(0) - leagues_points_balance_bso Int @default(0) - leagues_points_total Int @default(0) - - react_emoji_id String? - - osb_total_level Int? - bso_total_level Int? - osb_total_xp BigInt? - bso_total_xp BigInt? - osb_cl_percent Float? - bso_cl_percent Float? - osb_mastery Float? - bso_mastery Float? - - store_bitfield Int[] - - testing_points Float @default(0) - testing_points_balance Float @default(0) - - perk_tier Int @default(0) - premium_balance_tier Int? - premium_balance_expiry_date BigInt? - - user_group_id String? @db.Uuid - userGroup UserGroup? @relation(fields: [user_group_id], references: [id]) - - tag Tag[] - - @@map("user") -} - -model PingableRole { - role_id String @id - name String @unique @db.VarChar(32) - - @@map("pingable_role") -} - -model Tag { - id Int @id @unique @default(autoincrement()) - name String @unique @db.VarChar(32) - content String @db.VarChar(2000) - - user_id BigInt - creator User @relation(fields: [user_id], references: [id]) - - @@map("tag") -} - -model StoreCode { - product_id Int - code String @id @unique - - redeemed_at DateTime? - redeemed_by_user_id String? @db.VarChar(19) - - @@map("store_code") -} - -model UserGroup { - id String @id @default(uuid()) @db.Uuid - - users User[] - - @@map("user_group") -} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index dad6547002..625985c93a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -18,8 +18,7 @@ model Activity { group_activity Boolean type activity_type_enum channel_id BigInt - data Json @db.JsonB - pinnedTrip PinnedTrip[] + data Json @db.Json @@index([user_id, finish_date]) @@index([group_activity]) @@ -29,247 +28,22 @@ model Activity { @@map("activity") } -model Analytic { - timestamp BigInt @id(map: "PK_03b59d5e1bdd2e4c466198d1fd3") - guildsCount BigInt? - membersCount BigInt? - clueTasksCount Int? - minigameTasksCount Int? - monsterTasksCount Int? - skillingTasksCount Int? - minionsCount Int? - ironMinionsCount Int? - totalSacrificed BigInt? - totalGP BigInt? - totalGeGp BigInt? - totalBigAlchGp BigInt? - dicingBank BigInt? - duelTaxBank BigInt? - dailiesAmount BigInt? - gpSellingItems BigInt? - gpPvm BigInt? - gpAlching BigInt? - gpPickpocket BigInt? - gpDice BigInt? - gpOpen BigInt? - gpDaily BigInt? - gpItemContracts BigInt? - gpLuckyPick BigInt? - gpSlots BigInt? - gpHotCold BigInt? - - @@map("analytics_table") -} - model ClientStorage { - id String @id @db.VarChar(19) - totalCommandsUsed Int @default(0) - prices Json @default("{}") @db.Json - sold_items_bank Json @default("{}") @db.Json - herblore_cost_bank Json @default("{}") @db.Json - construction_cost_bank Json @default("{}") @db.Json - farming_cost_bank Json @default("{}") @db.Json - farming_loot_bank Json @default("{}") @db.Json - buy_cost_bank Json @default("{}") @db.Json - buy_loot_bank Json @default("{}") @db.Json - magic_cost_bank Json @default("{}") @db.Json - crafting_cost Json @default("{}") @db.Json - gnome_res_cost Json @default("{}") @db.Json - gnome_res_loot Json @default("{}") @db.Json - rogues_den_cost Json @default("{}") @db.Json - gauntlet_loot Json @default("{}") @db.Json - cox_cost Json @default("{}") @db.Json - cox_loot Json @default("{}") @db.Json - collecting_cost Json @default("{}") @db.Json - collecting_loot Json @default("{}") @db.Json - mta_cost Json @default("{}") @db.Json - bf_cost Json @default("{}") @db.Json - mage_arena_cost Json @default("{}") @db.Json - hunter_cost Json @default("{}") @db.Json - hunter_loot Json @default("{}") @db.Json - revs_cost Json @default("{}") @db.Json - revs_loot Json @default("{}") @db.Json - inferno_cost Json @default("{}") @db.Json - dropped_items Json @default("{}") @db.Json - runecraft_cost Json @default("{}") @db.Json - smithing_cost Json @default("{}") @db.Json - economyStats_dicingBank Int @default(0) @map("economyStats.dicingBank") - economyStats_duelTaxBank Int @default(0) @map("economyStats.duelTaxBank") - economyStats_dailiesAmount Int @default(0) @map("economyStats.dailiesAmount") - economyStats_itemSellTaxBank Int @default(0) @map("economyStats.itemSellTaxBank") - economyStats_bankBgCostBank Json @default("{}") @map("economyStats.bankBgCostBank") @db.Json - economyStats_sacrificedBank Json @default("{}") @map("economyStats.sacrificedBank") @db.Json - economyStats_wintertodtCost Json @default("{}") @map("economyStats.wintertodtCost") @db.Json - economyStats_wintertodtLoot Json @default("{}") @map("economyStats.wintertodtLoot") @db.Json - economyStats_fightCavesCost Json @default("{}") @map("economyStats.fightCavesCost") @db.Json - economyStats_PVMCost Json @default("{}") @map("economyStats.PVMCost") @db.Json - economyStats_thievingCost Json @default("{}") @map("economyStats.thievingCost") @db.Json - gp_sell BigInt @default(0) - gp_pvm BigInt @default(0) - gp_alch BigInt @default(0) - gp_pickpocket BigInt @default(0) - gp_dice BigInt @default(0) - gp_open BigInt @default(0) - gp_daily BigInt @default(0) - gp_luckypick BigInt @default(0) - gp_slots BigInt @default(0) - gp_hotcold BigInt @default(0) - custom_prices Json @default("{}") @db.Json - nightmare_cost Json @default("{}") @db.Json - create_cost Json @default("{}") @db.Json - create_loot Json @default("{}") @db.Json - tob_cost Json @default("{}") @db.Json - tob_loot Json @default("{}") @db.Json - degraded_items_cost Json @default("{}") @db.Json - tks_cost Json @default("{}") @db.Json - tks_loot Json @default("{}") @db.Json - disabled_commands String[] @default([]) @db.VarChar(32) - gp_tax_balance BigInt @default(0) - gotr_cost Json @default("{}") @db.Json - gotr_loot Json @default("{}") @db.Json - gf_cost Json @default("{}") @db.Json - gf_loot Json @default("{}") @db.Json - nmz_cost Json @default("{}") @db.Json - toa_cost Json @default("{}") @db.Json - toa_loot Json @default("{}") @db.Json - colo_cost Json @default("{}") @db.Json - colo_loot Json @default("{}") @db.Json - - grand_exchange_is_locked Boolean @default(false) - grand_exchange_total_tax BigInt @default(0) - grand_exchange_tax_bank BigInt @default(0) - - // BSO - gp_ic BigInt @default(0) - double_loot_finish_time BigInt @default(0) - gp_pet BigInt @default(0) - ignecarus_cost Json @default("{}") @db.Json - ignecarus_loot Json @default("{}") @db.Json - kibble_cost Json @default("{}") @db.Json - mr_cost Json @default("{}") @db.Json - mr_loot Json @default("{}") @db.Json - item_contract_cost Json @default("{}") @db.Json - item_contract_loot Json @default("{}") @db.Json - kg_cost Json @default("{}") @db.Json - kg_loot Json @default("{}") @db.Json - nex_cost Json @default("{}") @db.Json - nex_loot Json @default("{}") @db.Json - kk_cost Json @default("{}") @db.Json - kk_loot Json @default("{}") @db.Json - vasa_cost Json @default("{}") @db.Json - vasa_loot Json @default("{}") @db.Json - ods_cost Json @default("{}") @db.Json - ods_loot Json @default("{}") @db.Json - naxxus_loot Json @default("{}") @db.Json - naxxus_cost Json @default("{}") @db.Json - tame_merging_cost Json @default("{}") @db.Json - trip_doubling_loot Json @default("{}") @db.Json - fc_cost Json @default("{}") @db.Json - fc_loot Json @default("{}") @db.Json - zippy_loot Json @default("{}") @db.Json - market_prices Json @default("{}") @db.Json - bb_cost Json @default("{}") @db.Json - bb_loot Json @default("{}") @db.Json - moktang_cost Json @default("{}") @db.Json - moktang_loot Json @default("{}") @db.Json - doa_cost Json @default("{}") @db.Json - doa_loot Json @default("{}") @db.Json - - lottery_is_active Boolean @default(false) - - items_disassembled_cost Json @default("{}") @db.Json - invention_materials_cost Json @default("{}") @db.Json - invention_prizes_remaining Json @default("{}") @db.Json - clue_upgrader_loot Json @default("{}") @db.Json - portable_tanner_loot Json @default("{}") @db.Json - - last_bso_giveaways_ping DateTime? @db.Timestamp(6) - last_giveaway_doubleloot DateTime? @db.Timestamp(6) - - xmas_ironman_food_bank Json @default("{}") @db.Json - - turaels_trials_cost_bank Json @default("{}") @db.Json - turaels_trials_loot_bank Json @default("{}") @db.Json + id String @id @db.VarChar(19) + userBlacklist String[] @default([]) @db.VarChar(19) + guildBlacklist String[] @default([]) @db.VarChar(19) + prices Json @default("{}") @db.Json + sold_items_bank Json @default("{}") @db.Json + disabled_commands String[] @db.VarChar(32) + double_loot_finish_time BigInt @default(0) + custom_prices Json @default("{}") @db.Json @@map("clientStorage") } -model BuyCommandTransaction { - id String @id @default(uuid()) @db.Uuid - date DateTime @default(now()) @db.Timestamp(6) - - user_id BigInt - - cost_gp BigInt - cost_bank_excluding_gp Json? @db.Json - - loot_bank Json? @db.Json - - @@map("buy_command_transaction") -} - -enum GearSetupType { - melee - range - mage - misc - skilling - wildy - fashion - other -} - -model GearPreset { - user_id String @db.VarChar(19) - name String @db.VarChar(24) - two_handed Int? - body Int? - cape Int? - feet Int? - hands Int? - head Int? - legs Int? - neck Int? - ring Int? - shield Int? - weapon Int? - ammo Int? - ammo_qty Int? - - emoji_id String? @db.VarChar(19) - times_equipped Int @default(0) - pinned_setup GearSetupType? - - @@id([user_id, name]) - @@map("gear_presets") -} - -model Giveaway { - id Int @id - user_id String @db.VarChar(19) - start_date DateTime @db.Timestamp(6) - finish_date DateTime @db.Timestamp(6) - duration Int - completed Boolean - channel_id String @db.VarChar(19) - loot Json @db.Json - message_id String @db.VarChar(19) - reaction_id String? @db.VarChar(19) - - users_entered String[] @default([]) - - @@index([completed, finish_date]) - @@map("giveaway") -} - model Guild { - id String @id @db.VarChar(19) - disabledCommands String[] @default([]) - petchannel String? @db.VarChar(19) - staffOnlyChannels String[] @default([]) @db.VarChar(19) - - // BSO - mega_duck_location Json @default("{\"x\":1356,\"y\":209,\"usersParticipated\":{}}") @db.Json + id String @id @db.VarChar(19) + disabledCommands String[] @@map("guilds") } @@ -287,14 +61,6 @@ model NewUser { @@map("new_users") } -model PingableRole { - id Int @id @default(autoincrement()) - role_id String @unique @db.VarChar(19) - name String @db.VarChar(32) - - @@map("pingable_roles") -} - model PlayerOwnedHouse { user_id String @id @db.VarChar(19) background_id Int @default(1) @@ -342,41 +108,41 @@ enum bank_sort_method_enum { } model User { - id String @id @db.VarChar(19) - last_command_date DateTime? @db.Timestamp(6) - minion_bought_date DateTime? @db.Timestamp(6) - minion_hasBought Boolean @default(false) @map("minion.hasBought") - minion_ironman Boolean @default(false) @map("minion.ironman") - premium_balance_tier Int? - premium_balance_expiry_date BigInt? - - RSN String? - pets Json @default("{}") @db.Json + randomize_method Int @default(-1) + relics Int[] + skill_map Json? @db.JsonB + item_map_key String? + item_map Json? @db.Json + unlocked_item_map Json @default("{}") @db.JsonB + reverse_item_map Json? @db.Json + leagues_completed_tasks_ids Int[] + leagues_completed_tasks_count Int @default(0) + cl_percent Float @default(0) + total_xp BigInt @default(0) + total_level Int? + mastery Int @default(0) + + id String @id @db.VarChar(19) + last_command_date DateTime? @db.Timestamp(6) + minion_bought_date DateTime? @db.Timestamp(6) + minion_hasBought Boolean @default(false) @map("minion.hasBought") sacrificedValue BigInt @default(0) - GP BigInt @default(0) - QP Int @default(0) - bank Json @default("{}") @db.Json - collectionLogBank Json @default("{}") @db.JsonB - blowpipe Json @default("{\"scales\":0,\"dartID\":null,\"dartQuantity\":0}") @db.Json - slayer_unlocks Int[] @default([]) @map("slayer.unlocks") - slayer_blocked_ids Int[] @default([]) @map("slayer.blocked_ids") - slayer_last_task Int @default(0) @map("slayer.last_task") - badges Int[] @default([]) - bitfield Int[] @default([]) - temp_cl Json @default("{}") @db.Json - last_temp_cl_reset DateTime? @db.Timestamp(6) - minion_equippedPet Int? @map("minion.equippedPet") - minion_farmingContract Json? @map("minion.farmingContract") @db.Json - minion_birdhouseTraps Json? @map("minion.birdhouseTraps") @db.Json - finished_quest_ids Int[] @default([]) - - // Relations - farmedCrops FarmedCrop[] - botItemSell BotItemSell[] - pinnedTrip PinnedTrip[] - historicalData HistoricalData[] + GP BigInt @default(0) + QP Int @default(0) + bank Json @default("{}") @db.Json + collectionLogBank Json @default("{}") @db.JsonB + blowpipe Json @default("{\"scales\":0,\"dartID\":null,\"dartQuantity\":0}") @db.Json + slayer_unlocks Int[] @map("slayer.unlocks") + slayer_blocked_ids Int[] @map("slayer.blocked_ids") + slayer_last_task Int @default(0) @map("slayer.last_task") + badges Int[] + bitfield Int[] + minion_equippedPet Int? @map("minion.equippedPet") + minion_farmingContract Json? @map("minion.farmingContract") @db.Json + minion_birdhouseTraps Json? @map("minion.birdhouseTraps") @db.Json + finished_quest_ids Int[] // Configs/Settings minion_defaultCompostToUse CropUpgradeType @default(compost) @map("minion.defaultCompostToUse") @@ -396,8 +162,6 @@ model User { slayer_autoslay_options Int[] @default([]) @map("slayer.autoslay_options") bank_sort_method String? @db.VarChar(16) bank_sort_weightings Json @default("{}") @db.Json - gambling_lockout_expiry DateTime? - icon_pack_id String? // Skills skills_agility BigInt @default(0) @map("skills.agility") @@ -465,22 +229,11 @@ model User { emerged_inferno_attempts Int @default(0) skills_dungeoneering BigInt @default(0) @map("skills.dungeoneering") ourania_tokens Int @default(0) - total_item_contracts Int @default(0) - last_item_contract_date BigInt @default(0) - current_item_contract Int? - lastSpawnLamp BigInt @default(1) - lastGivenBoxx BigInt @default(1) dungeoneering_tokens Int @default(0) - item_contract_streak Int @default(0) - item_contract_bank Json @default("{}") @db.Json void_staff_charges Int @default(0) - last_patron_double_time_trigger DateTime? @db.Timestamp(6) - lottery_input Json @default("{}") @db.Json - gear_template Int @default(0) @db.SmallInt - unlocked_gear_templates Int[] @default([]) - last_bonanza_date DateTime? @db.Timestamp(6) - painted_items_tuple Json? @db.Json + last_bonanza_date DateTime? @db.Timestamp(6) + painted_items_tuple Json? @db.Json // Invention skills_invention BigInt @default(0) @map("skills.invention") @@ -490,10 +243,6 @@ model User { unlocked_blueprints Int[] @default([]) disabled_inventions Int[] @default([]) - bso_mystery_trail_current_step_id Int? - - store_bitfield Int[] - // Migrate farmingPatches_herb Json? @map("farmingPatches.herb") @db.Json farmingPatches_fruit_tree Json? @map("farmingPatches.fruit tree") @db.Json @@ -515,57 +264,17 @@ model User { farmingPatches_mushroom Json? @map("farmingPatches.mushroom") @db.Json farmingPatches_belladonna Json? @map("farmingPatches.belladonna") @db.Json - cached_networth_value BigInt? - username String? @db.VarChar(32) - geListings GEListing[] - bingo_participant BingoParticipant[] - bingo Bingo[] - - gift_boxes_owned GiftBox[] @relation("gift_boxes_owned") - gift_boxes_created GiftBox[] @relation("gift_boxes_created") - skills_divination BigInt @default(0) @map("skills.divination") guthixian_cache_boosts_available Int @default(0) disabled_portent_ids Int[] portent Portent[] - smokey_lottery_tickets Json? @db.Json - grinchions_caught Int @default(0) - last_giveaway_ticket_given_date DateTime? @db.Timestamp(6) - @@index([id, last_command_date]) @@map("users") } -model MortimerTricks { - id Int @id @default(autoincrement()) - - trickster_id String @db.VarChar(19) - target_id String @db.VarChar(19) - completed Boolean @default(false) - - date DateTime @default(now()) @db.Timestamp(6) - - @@map("mortimer_trick") -} - -model ReclaimableItem { - user_id String @db.VarChar(19) - key String - - name String - description String - date DateTime @default(now()) @db.Timestamp(6) - item_id Int - quantity Int - - @@id([user_id, key]) - @@unique([user_id, key]) - @@map("reclaimable_item") -} - model Webhook { channel_id String @id @db.VarChar(19) webhook_id String @db.VarChar(19) @@ -574,79 +283,6 @@ model Webhook { @@map("webhook_table") } -enum XpGainSource { - TombsOfAmascut - BalthazarsBigBonanza - UnderwaterAgilityThieving - ChambersOfXeric - TheatreOfBlood - DepthsOfAtlantis - NightmareZone - AshSanctifier - OfferingBones - TempleTrekking - DarkAltar - MotherlodeMine - Birdhouses - GuardiansOfTheRift - BuryingBones - ScatteringAshes - Zalcano - Wintertodt - FishingTrawler - Tempoross - TearsOfGuthix - ShadesOfMorton - PuroPuro - MahoganyHomes - AerialFishing - CleaningHerbsWhileFarming - CamdozaalMining - CamdozaalSmithing - CamdozaalFishing - MemoryHarvest - GuthixianCache - ForestryEvents - TuraelsTrials -} - -model XPGain { - id Int @id @default(autoincrement()) - user_id BigInt - date DateTime @default(now()) @db.Timestamp(6) - skill xp_gains_skill_enum - xp Int - artificial Boolean? - post_max Boolean @default(false) - source XpGainSource? - - @@index([date, skill]) - @@map("xp_gains") -} - -model Metric { - timestamp BigInt @id(map: "PK_dc6f197424b326d462eb953eca6") - eventLoopDelayMin Float @db.Real - eventLoopDelayMax Float @db.Real - eventLoopDelayMean Float @db.Real - memorySizeTotal BigInt - memorySizeUsed BigInt - memorySizeExternal BigInt - memorySizeRSS BigInt - cpuUser Float @db.Real - cpuSystem Float @db.Real - cpuPercent Float @db.Real - qps Float? @db.Real - - prisma_query_total_queries Int? - prisma_pool_active_connections Int? - prisma_pool_idle_connections Int? - prisma_pool_wait_count Int? - prisma_query_active_transactions Int? - - @@map("metrics") -} - model Minigame { id Int @id @default(autoincrement()) user_id String @unique @db.VarChar(19) @@ -708,17 +344,6 @@ model Minigame { @@map("minigames") } -model BossEvent { - id Int @id @default(autoincrement()) - start_date DateTime @db.Timestamp(6) - boss_id Int - completed Boolean - data Json @db.Json - - @@index([completed]) - @@map("boss_event") -} - model TameActivity { id Int @id(map: "PK_ff3610ef0ab0a39a22496e130e9") @default(autoincrement()) user_id String @db.VarChar(19) @@ -779,12 +404,13 @@ model Tame { } model CommandUsage { - id Int @id @default(autoincrement()) - date DateTime @default(now()) @db.Timestamp(6) + id Int @id @default(autoincrement()) + date DateTime @default(now()) @db.Timestamp(6) user_id BigInt - command_name command_name_enum - is_continue Boolean @default(false) - inhibited Boolean? @default(false) + command_name String @db.VarChar(32) + is_continue Boolean @default(false) + flags Json? + inhibited Boolean? @default(false) is_mention_command Boolean @default(false) @@ -807,37 +433,6 @@ model FishingContestCatch { @@map("fishing_contest_catch") } -enum loot_track_type { - Monster - Minigame - Skilling -} - -model LootTrack { - id String @id @default(uuid()) @db.Uuid - - key String @db.VarChar(32) - type loot_track_type - // In minutes - total_duration Int - total_kc Int - loot Json @default("{}") @db.Json - cost Json @default("{}") @db.Json - user_id BigInt? - - @@index([key, user_id]) - @@map("loot_track") -} - -model EconomyItem { - item_id Int - quantity BigInt - date DateTime @default(now()) - - @@id([item_id, date]) - @@map("economy_item") -} - model LastManStandingGame { id Int @id @default(autoincrement()) user_id BigInt @@ -848,29 +443,6 @@ model LastManStandingGame { @@map("lms_games") } -enum economy_transaction_type { - trade - giveaway - duel - gri - gift -} - -model EconomyTransaction { - id String @id @default(uuid()) @db.Uuid - date DateTime @default(now()) - sender BigInt - recipient BigInt - guild_id BigInt? - type economy_transaction_type - - // From the perspective of the sender - items_sent Json? @default("{}") @db.Json - items_received Json? @default("{}") @db.Json - - @@map("economy_transaction") -} - model StashUnit { stash_id Int user_id BigInt @@ -911,8 +483,6 @@ model UserStats { bars_from_klik_bank Json @default("{}") @db.Json portable_tanner_bank Json @default("{}") @db.Json clue_upgrader_bank Json @default("{}") @db.Json - ic_cost_bank Json @default("{}") @db.Json - ic_loot_bank Json @default("{}") @db.Json loot_from_zippy_bank Json @default("{}") @db.Json peky_loot_bank Json @default("{}") @db.Json @@ -927,11 +497,6 @@ model UserStats { silverhawk_boots_passive_xp BigInt @default(0) bonecrusher_prayer_xp BigInt @default(0) - ic_donations_given_bank Json @default("{}") @db.Json - ic_donations_received_bank Json @default("{}") @db.Json - - lamped_xp Json @default("{}") @db.Json - tame_cl_bank Json @default("{}") @db.JsonB tinker_workshop_mats_bank Json @default("{}") @db.Json @@ -963,10 +528,6 @@ model UserStats { on_task_with_mask_monster_scores Json @default("{}") @db.Json deaths Int @default(0) pk_evasion_exp Int @default(0) - dice_wins Int @default(0) - dice_losses Int @default(0) - duel_losses Int @default(0) - duel_wins Int @default(0) fight_caves_attempts Int @default(0) firecapes_sacrificed Int @default(0) tithe_farms_completed Int @default(0) @@ -988,21 +549,12 @@ model UserStats { slayer_unsired_offered Int @default(0) slayer_chewed_offered Int @default(0) - tob_cost Json @default("{}") - tob_loot Json @default("{}") creature_scores Json @default("{}") monster_scores Json @default("{}") laps_scores Json @default("{}") sacrificed_bank Json @default("{}") openable_scores Json @default("{}") - gp_luckypick BigInt @default(0) - gp_dice BigInt @default(0) - gp_slots BigInt @default(0) - gp_hotcold BigInt @default(0) - - total_gp_traded BigInt @default(0) - last_daily_timestamp BigInt @default(0) last_tears_of_guthix_timestamp BigInt @default(0) @@ -1010,8 +562,6 @@ model UserStats { forestry_event_completions_bank Json @default("{}") @db.Json - main_server_challenges_won Int @default(0) - doa_attempts Int @default(0) doa_cost Json @default("{}") doa_loot Json @default("{}") @@ -1025,9 +575,6 @@ model UserStats { god_favour_bank Json? @db.Json god_items_sacrificed_bank Json @default("{}") @db.Json - steal_cost_bank Json @default("{}") @db.Json - steal_loot_bank Json @default("{}") @db.Json - xp_from_graceful_portent BigInt @default(0) xp_from_dungeon_portent BigInt @default(0) xp_from_mining_portent BigInt @default(0) @@ -1058,80 +605,6 @@ enum CropUpgradeType { ultracompost } -// FarmedCrop -// - Not a source of truth, just used for tracking/stats. -// - One is made each time you plant a crop, and then updated when you harvest that crop. -// - Deleted when you become an iron. -model FarmedCrop { - id Int @id @default(autoincrement()) - - user_id String @db.VarChar(19) - user User @relation(fields: [user_id], references: [id]) - - date_planted DateTime - date_harvested DateTime? - item_id Int - quantity_planted Int - upgrade_type CropUpgradeType? - was_autofarmed Boolean - paid_for_protection Boolean - - @@map("farmed_crop") -} - -model BotItemSell { - id String @id @default(uuid()) @db.Uuid - - date DateTime @default(now()) @db.Timestamp(6) - - item_id Int - quantity Int - gp_received BigInt - - user_id String @db.VarChar(19) - user User @relation(fields: [user_id], references: [id]) - - @@map("bot_item_sell") -} - -model PinnedTrip { - id String @id - - user_id String @db.VarChar(19) - activity_id Int - emoji_id String? @db.VarChar(19) - activity_type activity_type_enum - data Json? @db.Json - custom_name String? @db.VarChar(32) - - activity Activity @relation(fields: [activity_id], references: [id]) - user User @relation(fields: [user_id], references: [id]) - - @@map("pinned_trip") -} - -model HistoricalData { - user_id String @db.VarChar(19) - date DateTime @default(now()) - - cl_global_rank Int @db.SmallInt - cl_completion_percentage Decimal @db.Decimal(5, 2) - cl_completion_count Int @db.SmallInt - GP Decimal @db.Decimal(13, 0) - total_xp Decimal @db.Decimal(13, 0) - - mastery_percentage Decimal? @db.Decimal(5, 2) - - comp_cape_percent Decimal? @db.Decimal(5, 2) - - comp_cape_percent_untrimmed Decimal? @db.Decimal(5, 2) - - user User @relation(fields: [user_id], references: [id]) - - @@id([user_id, date]) - @@map("historical_data") -} - enum activity_type_enum { Agility Cooking @@ -1296,149 +769,6 @@ enum AutoFarmFilterEnum { Replant } -enum GEListingType { - Buy - Sell -} - -model GEListing { - id Int @id @unique @default(autoincrement()) - type GEListingType - - userfacing_id String - - user_id String? @db.VarChar(19) - user User? @relation(fields: [user_id], references: [id]) - - created_at DateTime @default(now()) - cancelled_at DateTime? - fulfilled_at DateTime? - - gp_refunded BigInt @default(0) - item_id Int - asking_price_per_item BigInt - total_quantity Int - quantity_remaining Int - - buyTransactions GETransaction[] @relation("buy_transactions") - sellTransactions GETransaction[] @relation("sell_transactions") - - @@map("ge_listing") -} - -model GETransaction { - id Int @id @unique @default(autoincrement()) - - created_at DateTime @default(now()) - - quantity_bought Int - price_per_item_before_tax BigInt - price_per_item_after_tax BigInt - tax_rate_percent Int @db.SmallInt - - total_tax_paid BigInt - - buy_listing GEListing @relation("buy_transactions", fields: [buy_listing_id], references: [id], onDelete: Cascade) - buy_listing_id Int - - sell_listing GEListing @relation("sell_transactions", fields: [sell_listing_id], references: [id], onDelete: Cascade) - sell_listing_id Int - - @@index([sell_listing_id]) - @@map("ge_transaction") -} - -model GEBank { - item_id Int @id - quantity BigInt - - @@map("ge_bank") -} - -model Bingo { - id Int @id @unique @default(autoincrement()) - - creator User @relation(fields: [creator_id], references: [id]) - creator_id String @db.VarChar(19) - - is_global Boolean @default(false) - - organizers String[] @default([]) - start_date DateTime @default(now()) @db.Timestamp(6) - duration_days Int - team_size Int - title String - notifications_channel_id String @db.VarChar(19) - ticket_price BigInt - bingo_tiles Json[] @default([]) - was_finalized Boolean @default(false) - - guild_id String @db.VarChar(19) - - extra_gp BigInt @default(0) - - trophies_apply Boolean @default(false) - - bingo_participant BingoParticipant[] - bingo_team BingoTeam[] - - @@map("bingo") -} - -model BingoTeam { - id Int @id @unique @default(autoincrement()) - - bingo Bingo @relation(fields: [bingo_id], references: [id]) - bingo_id Int - - users BingoParticipant[] - - @@map("bingo_team") -} - -model BingoParticipant { - tickets_bought Int - - bingo Bingo @relation(fields: [bingo_id], references: [id], onDelete: Cascade) - bingo_id Int - - user User @relation(fields: [user_id], references: [id]) - user_id String @db.VarChar(19) - - team BingoTeam @relation(fields: [bingo_team_id], references: [id], onDelete: Cascade) - bingo_team_id Int - - cl Json @default("{}") @db.Json - - @@unique([user_id, bingo_id]) - @@map("bingo_participant") -} - -enum GiftBoxStatus { - Created - Sent - Opened -} - -model GiftBox { - id String @id - created_at DateTime @default(now()) @db.Timestamp(6) - status GiftBoxStatus - name String? - - creator_id String? @db.VarChar(19) - creator User? @relation("gift_boxes_created", fields: [creator_id], references: [id]) - - owner_id String? @db.VarChar(19) - owner User? @relation("gift_boxes_owned", fields: [owner_id], references: [id]) - - items Json @default("{}") @db.Json - - is_grinch_box Boolean @default(false) - - @@map("gift_box") -} - model Portent { item_id Int @@ -1481,309 +811,10 @@ model UserEvent { @@map("user_event") } -enum command_name_enum { - testpotato - achievementdiary - activities - admin - aerialfish - agilityarena - alch - amrod - ash - ask - autoequip - autoslay - bal - bank - bankbg - barbassault - bgcolor - bingo - birdhouse - blastfurnace - blowpipe - bossrecords - botleagues - botstats - bs - bso - build - bury - buy - ca - cancel - capegamble - cash - casket - cast - castlewars - cd - championchallenge - channel - chargeglories - chargewealth - checkmasses - checkpatch - chompyhunt - choose - chop - christmas - cl - claim - clbank - clue - clues - cmd - collect - collectionlog - combat - combatoptions - compostbin - config - cook - cox - cracker - craft - create - daily - darkaltar - data - decant - defaultfarming - defender - diary - dice - dicebank - disable - dmm - driftnet - drop - drycalc - drystreak - duel - easter - economybank - emotes - enable - enchant - equip - eval - fake - fakearma - fakebandos - fakeely - fakepm - fakesara - fakescythe - fakezammy - faq - farm - farming - farmingcontract - favalch - favfood - favorite - favour - fightcaves - finish - fish - fishingtrawler - fletch - gamble - gauntlet - ge - gear - gearpresets - gearstats - gift - github - giveaway - gnomerestaurant - gp - groupkill - halloween - hans - harvest - hcim - hcimdeaths - help - hiscores - hunt - inbank - inferno - info - invite - ironman - is - itemtrivia - jmodcomments - jmodtweets - k - kc - kcgains - kill - lamp - lapcount - laps - lastmanstanding - lb - leaderboard - leagues - light - lms - loot - love - luckyimp - luckypick - lvl - m - magearena - magearena2 - mahoganyhomes - mass - mclue - mine - minigames - minion - minionstats - mix - monster - mostdrops - mta - mygiveaways - mypets - news - nightmare - offer - open - osrskc - patreon - pay - pestcontrol - pet - petmessages - petrate - petroll - pickpocket - ping - players - plunder - poh - poll - polls - prefix - price - pvp - quest - raid - randomevents - randquote - ranks - rc - redeem - reload - resetrng - revs - roguesden - roles - roll - rp - runecraft - runelite - s - sacrifice - sacrificedbank - sacrificegp - sacrificelog - sawmill - seedpack - sell - sellto - sendtoabutton - sepulchre - server - setrsn - shutdownlock - simulate - skillcape - slayer - slayershop - slayertask - smelt - smith - soulwars - stats - steal - streamertweets - support - tag - tearsofguthix - tempoross - tithefarm - tithefarmshop - tob - tokkulshop - tools - trade - train - trek - trekshop - trickortreat - trivia - tweets - uim - unequip - unequipall - use - user - virtualstats - volcanicmine - warriorsguild - wiki - wintertodt - world - wt - wyson - xp - xpgains - xpto99 - zalcano - completion - tames - birthday - bsominigames - dg - divination - droprate - dung - fishingcontest - givebox - giverandomitem - hammy - ic - igne - invention - itemcontract - kibble - kinggoldemar - kk - lottery - lotterybank - megaduck - mmmr - nex - nursery - ods - paint - pingmass - rates - slots - smokeylottery - sotw - spawn - spawnbox - spawnlamp - testershop - vasa +model FullReset { + id String @id @default(uuid()) @db.Uuid + discord_id String + date DateTime @default(now()) @db.Timestamp(6) + + @@map("full_reset") } diff --git a/src/config.example.ts b/src/config.example.ts deleted file mode 100644 index 7c3d2c498f..0000000000 --- a/src/config.example.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { IDiscordSettings } from './lib/types'; - -export const production = false; -export const SENTRY_DSN: string | null = null; -export const DISCORD_SETTINGS: Partial = {}; -export const OWNER_IDS = ['157797566833098752']; -export const ADMIN_IDS = ['425134194436341760']; -export const MAXING_MESSAGE = 'Congratulations on maxing!'; -export const SupportServer = '940758552425955348'; diff --git a/src/index.ts b/src/index.ts index 9cff6b0db4..54c8208675 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,24 +2,19 @@ import './lib/safeglobals'; import './lib/globals'; import './lib/MUser'; import './lib/util/transactItemsFromBank'; -import './lib/geImage'; -import { MahojiClient } from '@oldschoolgg/toolkit'; -import { init } from '@sentry/node'; +import { MahojiClient, bulkUpdateCommands } from '@oldschoolgg/toolkit'; import type { TextChannel } from 'discord.js'; import { GatewayIntentBits, Options, Partials } from 'discord.js'; import { isObject } from 'e'; -import { SENTRY_DSN, SupportServer } from './config'; import { BLACKLISTED_GUILDS, BLACKLISTED_USERS } from './lib/blacklists'; -import { Channel, Events, gitHash, globalConfig } from './lib/constants'; -import { economyLog } from './lib/economyLogs'; +import { Events, globalConfig } from './lib/constants'; import { onMessage } from './lib/events'; import { modalInteractionHook } from './lib/modals'; import { preStartup } from './lib/preStartup'; import { OldSchoolBotClient } from './lib/structures/OldSchoolBotClient'; import { runTimedLoggedFn } from './lib/util'; -import { CACHED_ACTIVE_USER_IDS } from './lib/util/cachedUserIDs'; import { interactionHook } from './lib/util/globalInteractions'; import { handleInteractionError, interactionReply } from './lib/util/interactionReply'; import { logError } from './lib/util/logError'; @@ -29,16 +24,6 @@ import { postCommand } from './mahoji/lib/postCommand'; import { preCommand } from './mahoji/lib/preCommand'; import { convertMahojiCommandToAbstractCommand } from './mahoji/lib/util'; -if (SENTRY_DSN) { - init({ - dsn: SENTRY_DSN, - enableTracing: false, - defaultIntegrations: false, - integrations: [], - release: gitHash - }); -} - const client = new OldSchoolBotClient({ shards: 'auto', intents: [ @@ -58,16 +43,13 @@ const client = new OldSchoolBotClient({ maxSize: 0 }, UserManager: { - maxSize: 1000, - keepOverLimit: user => CACHED_ACTIVE_USER_IDS.has(user.id) + maxSize: 1000 }, GuildMemberManager: { - maxSize: 200, - keepOverLimit: member => CACHED_ACTIVE_USER_IDS.has(member.user.id) + maxSize: 200 }, GuildEmojiManager: { - maxSize: 1, - keepOverLimit: i => [globalConfig.testingServerID, SupportServer].includes(i.guild.id) + maxSize: 1 }, GuildStickerManager: { maxSize: 0 }, PresenceManager: { maxSize: 0 }, @@ -75,21 +57,11 @@ const client = new OldSchoolBotClient({ GuildInviteManager: { maxSize: 0 }, ThreadManager: { maxSize: 0 }, ThreadMemberManager: { maxSize: 0 } - }), - sweepers: { - guildMembers: { - interval: 60 * 60, - filter: () => member => !CACHED_ACTIVE_USER_IDS.has(member.user.id) - }, - users: { - interval: 60 * 60, - filter: () => user => !CACHED_ACTIVE_USER_IDS.has(user.id) - } - } + }) }); export const mahojiClient = new MahojiClient({ - developmentServerID: globalConfig.testingServerID, + developmentServerID: globalConfig.mainServerID, applicationID: globalConfig.clientID, commands: allCommands, handlers: { @@ -136,7 +108,7 @@ client.on('interactionCreate', async interaction => { if (interaction.isRepliable()) { await interactionReply(interaction, { content: - 'BSO is currently shutting down for maintenance/updates, please try again in a couple minutes! Thank you <3', + 'Randomizer is currently shutting down for maintenance/updates, please try again in a couple minutes! Thank you <3', ephemeral: true }); } @@ -174,15 +146,10 @@ client.on('interactionCreate', async interaction => { }); client.on(Events.ServerNotification, (message: string) => { - const channel = globalClient.channels.cache.get(Channel.Notifications); - if (channel) { - (channel as TextChannel).send({ content: message, allowedMentions: { parse: [], users: [], roles: [] } }); - } + const channel = globalClient.channels.cache.get(globalConfig.announcementsChannelID); + if (channel) (channel as TextChannel).send(message); }); -client.on(Events.EconomyLog, async (message: string) => { - economyLog(message); -}); client.on('guildCreate', guild => { if (!guild.available) return; if (BLACKLISTED_GUILDS.has(guild.id) || BLACKLISTED_USERS.has(guild.ownerId)) { @@ -191,13 +158,28 @@ client.on('guildCreate', guild => { }); client.on('shardError', err => debugLog('Shard Error', { error: err.message })); -client.once('ready', () => onStartup()); async function main() { if (process.env.TEST) return; await preStartup(); await runTimedLoggedFn('Log In', () => client.login(globalConfig.botToken)); console.log(`Logged in as ${globalClient.user.username}`); + const totalCommands = Array.from(globalClient.mahojiClient.commands.values()); + const globalCommands = totalCommands.filter(i => !i.guildID); + const guildCommands = totalCommands.filter(i => Boolean(i.guildID) && !['testpotato'].includes(i.name)); + if (globalConfig.isProduction) { + await bulkUpdateCommands({ + client: globalClient.mahojiClient, + commands: globalCommands, + guildID: null + }); + } + await bulkUpdateCommands({ + client: globalClient.mahojiClient, + commands: guildCommands, + guildID: globalConfig.isProduction ? '342983479501389826' : '940758552425955348' + }); + await onStartup(); } process.on('uncaughtException', err => { diff --git a/src/lib/DynamicButtons.ts b/src/lib/DynamicButtons.ts deleted file mode 100644 index 5d892ba99e..0000000000 --- a/src/lib/DynamicButtons.ts +++ /dev/null @@ -1,152 +0,0 @@ -import type { - BaseMessageOptions, - ButtonInteraction, - DMChannel, - Message, - MessageComponentInteraction, - NewsChannel, - TextChannel, - ThreadChannel -} from 'discord.js'; -import { ButtonBuilder, ButtonStyle } from 'discord.js'; -import { Time, isFunction, noOp } from 'e'; -import murmurhash from 'murmurhash'; - -import { BLACKLISTED_USERS } from './blacklists'; -import { awaitMessageComponentInteraction, makeComponents } from './util'; -import { silentButtonAck } from './util/handleMahojiConfirmation'; -import { minionIsBusy } from './util/minionIsBusy'; - -type DynamicButtonFn = (opts: { message: Message; interaction: MessageComponentInteraction }) => unknown; - -export class DynamicButtons { - buttons: { - name: string; - id: string; - fn?: DynamicButtonFn; - emoji: string | undefined; - cantBeBusy: boolean; - style?: ButtonStyle; - }[] = []; - - channel: TextChannel; - timer: number | undefined; - usersWhoCanInteract: string[]; - message: Message | null = null; - contentAfterFinish: string | null = null; - deleteAfterConfirm: boolean | undefined; - - constructor({ - channel, - timer, - usersWhoCanInteract, - contentAfterFinish, - deleteAfterConfirm - }: { - channel: TextChannel | DMChannel | NewsChannel | ThreadChannel; - timer?: number; - usersWhoCanInteract: string[]; - contentAfterFinish?: string | null; - deleteAfterConfirm?: boolean; - }) { - this.channel = channel as TextChannel; - this.timer = timer; - this.usersWhoCanInteract = usersWhoCanInteract; - this.contentAfterFinish = contentAfterFinish ?? null; - this.deleteAfterConfirm = deleteAfterConfirm; - } - - async render({ - isBusy, - messageOptions, - extraButtons = [] - }: { - isBusy: boolean; - messageOptions: BaseMessageOptions; - extraButtons?: ButtonBuilder[]; - }) { - const buttons = this.buttons - .filter(b => { - if (isBusy && b.cantBeBusy) return false; - return true; - }) - .map( - b => - new ButtonBuilder({ - label: b.name, - customId: b.id, - style: (b.style ?? ButtonStyle.Secondary) as ButtonStyle.Secondary, - emoji: b.emoji - }) - ) - .concat(extraButtons); - - this.message = await this.channel.send({ - ...messageOptions, - components: makeComponents(buttons) - }); - const collectedInteraction: ButtonInteraction = (await awaitMessageComponentInteraction({ - message: this.message, - filter: async i => { - if (!i.isButton()) return false; - await silentButtonAck(i); - if (BLACKLISTED_USERS.has(i.user.id)) return false; - if (this.usersWhoCanInteract.includes(i.user.id)) { - return true; - } - i.reply({ ephemeral: true, content: 'This is not your message.' }); - return false; - }, - time: this.timer ?? Time.Second * 20 - }).catch(noOp)) as ButtonInteraction; - if (this.deleteAfterConfirm === true) { - await this.message.delete().catch(noOp); - } else { - await this.message.edit({ components: [], content: this.contentAfterFinish ?? undefined }); - } - - if (collectedInteraction) { - for (const button of this.buttons) { - if (collectedInteraction.customId === button.id) { - if (minionIsBusy(collectedInteraction.user.id) && button.cantBeBusy) { - await collectedInteraction.reply({ - content: "Your action couldn't be performed, because your minion is busy.", - ephemeral: true - }); - return null; - } - if ('fn' in button && isFunction(button.fn)) { - await button.fn({ message: this.message!, interaction: collectedInteraction }); - } - return button; - } - } - } - - return null; - } - - add({ - name, - fn, - emoji, - cantBeBusy, - style - }: { - name: string; - fn?: DynamicButtonFn; - emoji?: string; - cantBeBusy?: boolean; - style?: ButtonStyle; - }) { - const id = murmurhash(name).toString(); - this.buttons.push({ - name, - id: `DYN_${id}`, - fn, - emoji, - cantBeBusy: cantBeBusy ?? false, - style - }); - } -} diff --git a/src/lib/MUser.ts b/src/lib/MUser.ts index ef7711b922..34f498801f 100644 --- a/src/lib/MUser.ts +++ b/src/lib/MUser.ts @@ -1,11 +1,10 @@ -import { PerkTier, cleanUsername, mentionCommand, seedShuffle } from '@oldschoolgg/toolkit'; +import { type PerkTier, cleanUsername, mentionCommand } from '@oldschoolgg/toolkit'; import { UserError } from '@oldschoolgg/toolkit'; -import type { GearSetupType, Prisma, TameActivity, User, UserStats, xp_gains_skill_enum } from '@prisma/client'; +import type { Prisma, TameActivity, User, UserStats, xp_gains_skill_enum } from '@prisma/client'; import { userMention } from 'discord.js'; -import { Time, calcWhatPercent, objectEntries, percentChance, randArrItem, sumArr, uniqueArr } from 'e'; +import { Time, calcWhatPercent, objectEntries, percentChance, sumArr, uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; -import { EquipmentSlot } from 'oldschooljs/dist/meta/types'; import { resolveItems } from 'oldschooljs/dist/util/util'; import { timePerAlch } from '../mahoji/lib/abstracted_commands/alchCommand'; @@ -16,27 +15,23 @@ import type { GodFavourBank, GodName } from './bso/divineDominion'; import { userIsBusy } from './busyCounterCache'; import { ClueTiers } from './clues/clueTiers'; import { type CATier, CombatAchievements } from './combat_achievements/combatAchievements'; -import { BitField, projectiles } from './constants'; -import { bossCLItems } from './data/Collections'; +import { type BitField, perkTierUnlocks, projectiles } from './constants'; +import { allCLItemsFiltered, bossCLItems } from './data/Collections'; import { allPetIDs } from './data/CollectionsExport'; import { getSimilarItems } from './data/similarItems'; import { degradeableItems } from './degradeableItems'; import { type GearSetup, type UserFullGearSetup, defaultGear } from './gear'; -import { gearImages } from './gear/functions/generateGearImage'; import { handleNewCLItems } from './handleNewCLItems'; import type { IMaterialBank } from './invention'; import { MaterialBank } from './invention/MaterialBank'; -import { marketPriceOfBank } from './marketPrices'; -import backgroundImages from './minions/data/bankBackgrounds'; import type { CombatOptionsEnum } from './minions/data/combatConstants'; import { defaultFarmingContract } from './minions/farming'; import type { FarmingContract } from './minions/farming/types'; import type { AttackStyles } from './minions/functions'; import { blowpipeDarts, validateBlowpipeData } from './minions/functions/blowpipeCommand'; import type { AddXpParams, BlowpipeData, ClueBank } from './minions/types'; -import { mysteriousStepData, mysteriousTrailTracks } from './mysteryTrail'; -import { getUsersPerkTier } from './perkTiers'; -import { roboChimpUserFetch } from './roboChimp'; +import { randomizationMethods } from './randomizer'; +import type { RelicID } from './relics'; import type { MinigameScore } from './settings/minigames'; import { Minigames, getMinigameEntity } from './settings/minigames'; import { getFarmingInfoFromUser } from './skilling/functions/getFarmingInfo'; @@ -49,7 +44,6 @@ import { MTame } from './structures/MTame'; import type { Skills } from './types'; import { addItemToBank, convertXPtoLVL, itemNameFromID } from './util'; import { determineRunes } from './util/determineRunes'; -import { findGroupOfUser } from './util/findGroupOfUser'; import { getKCByName } from './util/getKCByName'; import getOSItem, { getItem } from './util/getOSItem'; import itemID from './util/itemID'; @@ -107,6 +101,7 @@ export class MUserClass { paintedItems!: Map; badgesString!: string; bitfield!: readonly BitField[]; + cachedPerkTier!: PerkTier; constructor(user: User) { this.user = user; @@ -143,13 +138,18 @@ export class MUserClass { this.skillsAsLevels = this.getSkills(true); this.paintedItems = this.buildPaintedItems(); - this.badgesString = makeBadgeString(this.user.badges, this.isIronman); + this.badgesString = makeBadgeString(this, this.user.badges); this.bitfield = this.user.bitfield as readonly BitField[]; - } - get gearTemplate() { - return gearImages.find(i => i.id === this.user.gear_template)!; + const clSlots = allCLItemsFiltered.filter(i => this.cl.has(i)).length; + const clPercentage = calcWhatPercent(clSlots, allCLItemsFiltered.length); + for (const a of perkTierUnlocks) { + if (clPercentage >= a.clPercent) { + this.cachedPerkTier = a.perk; + break; + } + } } countSkillsAtleast99() { @@ -205,15 +205,15 @@ export class MUserClass { } get isIronman() { - return this.user.minion_ironman; + return true; } get GP() { return Number(this.user.GP); } - perkTier() { - return getUsersPerkTier(this); + perkTier(): 1 | 2 | 3 | 4 | 5 { + return 1; } skillLevel(skill: xp_gains_skill_enum) { @@ -355,7 +355,7 @@ GROUP BY data->>'ci';`); dontAddToTempCL = false, neverUpdateHistory = false }: { - items: ItemBank | Bank; + items: Bank; collectionLog?: boolean; filterLoot?: boolean; dontAddToTempCL?: boolean; @@ -363,7 +363,7 @@ GROUP BY data->>'ci';`); }) { const res = await transactItems({ collectionLog, - itemsToAdd: new Bank(items), + itemsToAdd: items, filterLoot, dontAddToTempCL, userID: this.id, @@ -548,6 +548,10 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` return percentBossCLFinished; } + hasRelic(id: RelicID) { + return this.user.relics.includes(id); + } + async addItemsToCollectionLog(itemsToAdd: Bank) { const previousCL = new Bank(this.cl.bank); const updates = this.calculateAddItemsToCLUpdates({ @@ -682,9 +686,12 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` return (stats.creature_scores as ItemBank)[creatureID] ?? 0; } + getRandomizeMethod() { + return randomizationMethods.find(i => i.id === this.user.randomize_method); + } + calculateAddItemsToCLUpdates({ - items, - dontAddToTempCL = false + items }: { items: Bank; dontAddToTempCL?: boolean; @@ -692,10 +699,6 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` const updates: Prisma.UserUpdateArgs['data'] = { collectionLogBank: new Bank(this.user.collectionLogBank as ItemBank).add(items).bank }; - - if (!dontAddToTempCL) { - updates.temp_cl = new Bank(this.user.temp_cl as ItemBank).add(items).bank; - } return updates; } @@ -837,31 +840,6 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` await this.sync(); } - getMysteriousTrailData() { - const currentStepID = this.user.bso_mystery_trail_current_step_id as 1 | 2 | 3 | 4 | 5 | 6 | 7 | null; - if (currentStepID === null) { - return { step: null, track: null }; - } - const [trackID] = seedShuffle( - mysteriousTrailTracks.map(i => i.id), - this.id - ); - const track = mysteriousTrailTracks.find(i => i.id === trackID)!; - const step = track.steps[currentStepID - 1]; - if (!step) { - throw new Error(`No step for ${currentStepID} ${this.id}`); - } - return { - step, - nextStep: track.steps[currentStepID as any] ?? null, - track, - stepData: mysteriousStepData[currentStepID], - nextStepData: mysteriousStepData[(currentStepID + 1) as keyof typeof mysteriousStepData], - previousStepData: mysteriousStepData[(currentStepID - 1) as keyof typeof mysteriousStepData], - minionMessage: randArrItem(mysteriousStepData[currentStepID].messages).replace('{minion}', this.minionName) - }; - } - caPoints(): number { const keys = Object.keys(CombatAchievements) as CATier[]; return keys @@ -877,149 +855,11 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` return this.caPoints() >= CombatAchievements[tier].rewardThreshold; } - buildTertiaryItemChanges(hasRingOfWealthI = false, inWildy = false, onTask = false) { - const changes = new Map(); - - const tiers = Object.keys(CombatAchievements) as Array; - for (const tier of tiers) { - let change = hasRingOfWealthI ? 50 : 0; - if (this.hasCompletedCATier(tier)) { - change += 5; - } - changes.set(`Clue scroll (${tier})`, change); - } - - if (inWildy) changes.set('Giant key', 50); - - if (inWildy && !onTask) { - changes.set('Mossy key', 60); - } else if (!inWildy && onTask) { - changes.set('Mossy key', 66.67); - } else if (inWildy && onTask) { - changes.set('Mossy key', 77.6); - } - - return changes; - } - - async checkBankBackground() { - if (this.bitfield.includes(BitField.isModerator)) { - return; - } - const resetBackground = async () => { - await this.update({ bankBackground: 1 }); - }; - const background = backgroundImages.find(i => i.id === this.user.bankBackground); - if (!background) { - return resetBackground(); - } - if (background.id === 1) return; - if (background.storeBitField && this.user.store_bitfield.includes(background.storeBitField)) { - return; - } - if (background.perkTierNeeded && this.perkTier() >= background.perkTierNeeded) { - return; - } - if (background.bitfield && this.bitfield.includes(background.bitfield)) { - return; - } - if (!background.storeBitField && !background.perkTierNeeded && !background.bitfield && !background.owners) { - return; - } - if (background.owners) { - const userIDs = await findGroupOfUser(this.id); - if (background.owners.some(owner => userIDs.includes(owner))) { - return; - } - } - return resetBackground(); - } - - async fetchRobochimpUser() { - return roboChimpUserFetch(this.id); - } - - async forceUnequip(setup: GearSetupType, slot: EquipmentSlot, reason: string) { - const gear = this.gear[setup].raw(); - const equippedInSlot = gear[slot]; - if (!equippedInSlot) { - return { refundBank: new Bank() }; - } - gear[slot] = null; - - const actualItem = getItem(equippedInSlot.item); - const refundBank = new Bank(); - if (actualItem) { - refundBank.add(actualItem.id, equippedInSlot.quantity); - } - - await this.update({ - [`gear_${setup}`]: gear as any as Prisma.InputJsonObject - }); - if (refundBank.length > 0) { - await this.addItemsToBank({ - items: refundBank, - collectionLog: false - }); - } - - debugLog( - `ForceUnequip User[${this.id}] in ${setup} slot[${slot}] ${JSON.stringify(equippedInSlot)}: ${reason}` - ); - - return { refundBank }; - } - async fetchStashUnits() { const units = await getParsedStashUnits(this.id); return units; } - async validateEquippedGear() { - const itemsUnequippedAndRefunded = new Bank(); - for (const [gearSetupName, gearSetup] of Object.entries(this.gear) as [GearSetupType, GearSetup][]) { - if (gearSetup['2h'] !== null) { - if (gearSetup.weapon?.item) { - const { refundBank } = await this.forceUnequip( - gearSetupName, - EquipmentSlot.Weapon, - '2h Already equipped' - ); - itemsUnequippedAndRefunded.add(refundBank); - } - if (gearSetup.shield?.item) { - const { refundBank } = await this.forceUnequip( - gearSetupName, - EquipmentSlot.Shield, - '2h Already equipped' - ); - itemsUnequippedAndRefunded.add(refundBank); - } - } - for (const slot of Object.values(EquipmentSlot)) { - const item = gearSetup[slot]; - if (!item) continue; - const osItem = getItem(item.item); - if (!osItem) { - const { refundBank } = await this.forceUnequip(gearSetupName, slot, 'Invalid item'); - itemsUnequippedAndRefunded.add(refundBank); - continue; - } - if (osItem.equipment?.slot !== slot) { - const { refundBank } = await this.forceUnequip(gearSetupName, slot, 'Wrong slot'); - itemsUnequippedAndRefunded.add(refundBank); - } - if (osItem.equipment?.requirements && !this.hasSkillReqs(osItem.equipment.requirements)) { - const { refundBank } = await this.forceUnequip(gearSetupName, slot, 'Doesnt meet requirements'); - itemsUnequippedAndRefunded.add(refundBank); - } - } - } - return { - itemsUnequippedAndRefunded - }; - } - async fetchTames() { const rawTames = await prisma.tame.findMany({ where: { @@ -1065,52 +905,6 @@ Charge your items using ${mentionCommand(globalClient, 'minion', 'charge')}.` }); return { activity, tame: new MTame(tame) }; } - - async calculateNetWorth() { - const bank = this.allItemsOwned.clone(); - const activeListings = await prisma.gEListing.findMany({ - where: { - user_id: this.id, - quantity_remaining: { - gt: 0 - }, - fulfilled_at: null, - cancelled_at: null - }, - include: { - buyTransactions: true, - sellTransactions: true - } - }); - for (const listing of activeListings) { - if (listing.type === 'Sell') { - bank.add(listing.item_id, listing.quantity_remaining); - } else { - bank.add('Coins', Number(listing.asking_price_per_item) * listing.quantity_remaining); - } - } - const activeGiveaways = await prisma.giveaway.findMany({ - where: { - completed: false, - user_id: this.id - } - }); - for (const giveaway of activeGiveaways) { - bank.add(giveaway.loot as ItemBank); - } - const gifts = await prisma.giftBox.findMany({ - where: { - owner_id: this.id - } - }); - for (const gift of gifts) { - bank.add(gift.items as ItemBank); - } - return { - bank, - value: marketPriceOfBank(bank) - }; - } } declare global { export type MUser = MUserClass; @@ -1134,22 +928,6 @@ async function srcMUserFetch(userID: string, updates: Prisma.UserUpdateInput = { global.mUserFetch = srcMUserFetch; export const dailyResetTime = Time.Hour * 4; -export const spawnLampResetTime = (user: MUser) => { - const bf = user.bitfield; - const perkTier = user.perkTier(); - - const hasPerm = bf.includes(BitField.HasPermanentSpawnLamp); - const hasTier5 = perkTier >= PerkTier.Five; - const hasTier4 = !hasTier5 && perkTier === PerkTier.Four; - - let cooldown = ([PerkTier.Six, PerkTier.Five] as number[]).includes(perkTier) ? Time.Hour * 12 : Time.Hour * 24; - - if (!hasTier5 && !hasTier4 && hasPerm) { - cooldown = Time.Hour * 48; - } - - return cooldown - Time.Minute * 15; -}; export const itemContractResetTime = Time.Hour * 7.8; export const giveBoxResetTime = Time.Hour * 23.5; global.GlobalMUserClass = MUserClass; diff --git a/src/lib/Task.ts b/src/lib/Task.ts index 4d5d8b7304..cbab839814 100644 --- a/src/lib/Task.ts +++ b/src/lib/Task.ts @@ -3,7 +3,7 @@ import { activity_type_enum } from '@prisma/client'; import type { ZodSchema } from 'zod'; import { z } from 'zod'; -import { production } from '../config'; +import { writeFileSync } from 'node:fs'; import { aerialFishingTask } from '../tasks/minions/HunterActivity/aerialFishingActivity'; import { birdHouseTask } from '../tasks/minions/HunterActivity/birdhouseActivity'; import { driftNetTask } from '../tasks/minions/HunterActivity/driftNetActivity'; @@ -13,7 +13,6 @@ import { offeringTask } from '../tasks/minions/PrayerActivity/offeringActivity'; import { scatteringTask } from '../tasks/minions/PrayerActivity/scatteringActivity'; import { agilityTask } from '../tasks/minions/agilityActivity'; import { alchingTask } from '../tasks/minions/alchingActivity'; -import { bossEventTask } from '../tasks/minions/bossEventActivity'; import { bathhouseTask } from '../tasks/minions/bso/bathhousesActivity'; import { bonanzaTask } from '../tasks/minions/bso/bonanzaActivity'; import { disassemblingTask } from '../tasks/minions/bso/disassemblingActivity'; @@ -116,7 +115,7 @@ import { guardiansOfTheRiftTask } from './../tasks/minions/minigames/guardiansOf import { nightmareZoneTask } from './../tasks/minions/minigames/nightmareZoneActivity'; import { underwaterAgilityThievingTask } from './../tasks/minions/underwaterActivity'; import { modifyBusyCounter } from './busyCounterCache'; -import { minionActivityCache } from './constants'; +import { globalConfig, minionActivityCache } from './constants'; import { convertStoredActivityToFlatActivity } from './settings/prisma'; import { activitySync, minionActivityCacheDelete } from './settings/settings'; import { logWrapFn } from './util'; @@ -218,7 +217,6 @@ const tasks: MinionTask[] = [ dungeoneeringTask, fogTask, scTask, - bossEventTask, twTask, cutLeapingFishTask, toaTask, @@ -241,7 +239,7 @@ export async function processPendingActivities() { const activities: Activity[] = await prisma.activity.findMany({ where: { completed: false, - finish_date: production + finish_date: globalConfig.isProduction ? { lt: new Date() } @@ -345,3 +343,10 @@ for (const a of Object.values(activity_type_enum)) { console.log(`Missing ${a} task`); } } + +let str2 = ''; +for (const a of tasks) { + if (ignored.includes(a.type)) continue; + str2 += `${a.type}\n`; +} +writeFileSync('activities.txt', str2); diff --git a/src/lib/addXP.ts b/src/lib/addXP.ts index 202d9cced3..b804980006 100644 --- a/src/lib/addXP.ts +++ b/src/lib/addXP.ts @@ -1,11 +1,10 @@ import { formatOrdinal, toTitleCase } from '@oldschoolgg/toolkit'; import { UserEventType } from '@prisma/client'; import { bold } from 'discord.js'; -import { Time, increaseNumByPercent, noOp, notEmpty, objectValues } from 'e'; +import { Time, increaseNumByPercent, noOp, notEmpty, objectEntries, objectValues } from 'e'; import type { Item } from 'oldschooljs/dist/meta/types'; -import { MAXING_MESSAGE } from '../config'; -import { Channel, Events, GLOBAL_BSO_XP_MULTIPLIER, LEVEL_120_XP, MAX_TOTAL_LEVEL, MAX_XP } from './constants'; +import { Events, GLOBAL_BSO_XP_MULTIPLIER, LEVEL_120_XP, MAX_TOTAL_LEVEL, MAX_XP, globalConfig } from './constants'; import { divinersOutfit, gorajanArcherOutfit, @@ -17,9 +16,10 @@ import { skillEmoji } from './data/emojis'; import { getSimilarItems } from './data/similarItems'; import type { AddXpParams } from './minions/types'; +import { RelicID } from './relics'; import Skillcapes from './skilling/skillcapes'; import Skills from './skilling/skills'; -import { SkillsEnum } from './skilling/types'; +import { type SkillNameType, SkillsArray, SkillsEnum } from './skilling/types'; import { convertLVLtoXP, convertXPtoLVL, itemNameFromID, toKMB } from './util'; import getOSItem from './util/getOSItem'; import resolveItems from './util/resolveItems'; @@ -28,37 +28,25 @@ import { sendToChannelID } from './util/webhook'; const skillsVals = Object.values(Skills); const maxFilter = skillsVals.map(s => `"skills.${s.id}" >= ${LEVEL_120_XP}`).join(' AND '); -const makeQuery = (ironman: boolean) => `SELECT count(id)::int +const makeQuery = () => `SELECT count(id)::int FROM users -WHERE ${maxFilter} -${ironman ? 'AND "minion.ironman" = true' : ''};`; +WHERE ${maxFilter};`; async function howManyMaxed() { - const [normies, irons] = ( - (await Promise.all([prisma.$queryRawUnsafe(makeQuery(false)), prisma.$queryRawUnsafe(makeQuery(true))])) as any - ) - .map((i: any) => i[0].count) - .map((i: any) => Number.parseInt(i)); - return { - normies, - irons + irons: Number.parseInt((await prisma.$queryRawUnsafe(makeQuery()))[0].count) }; } async function onMax(user: MUser) { - const { normies, irons } = await howManyMaxed(); + const { irons } = await howManyMaxed(); const str = `🎉 ${ user.usernameOrMention - }'s minion just achieved level 120 in every skill, they are the **${formatOrdinal(normies)}** minion to be maxed${ - user.isIronman ? `, and the **${formatOrdinal(irons)}** ironman to max.` : '.' - } 🎉`; + }'s minion just achieved level 120 in every skill, they are the **${formatOrdinal(irons)}** minion to be maxed 🎉`; globalClient.emit(Events.ServerNotification, str); - sendToChannelID(Channel.BSOGeneral, { content: str }).catch(noOp); - const djsUser = await globalClient.users.fetch(user.id); - djsUser.send(MAXING_MESSAGE).catch(noOp); + sendToChannelID(globalConfig.generalChannelID, { content: str }).catch(noOp); } interface StaticXPBoost { @@ -119,6 +107,10 @@ function getEquippedCapes(user: MUser) { } export async function addXP(user: MUser, params: AddXpParams): Promise { + params.skillName = (user.user.skill_map as any)[params.skillName as any] as SkillsEnum; + if (!params.skillName || !SkillsArray.includes(params.skillName)) { + throw new Error(`${user.id} has skill mapping: ${params.skillName}`); + } const currentXP = Number(user.user[`skills_${params.skillName}`]); const currentLevel = user.skillLevel(params.skillName); const currentTotalLevel = user.totalLevel; @@ -231,29 +223,6 @@ export async function addXP(user: MUser, params: AddXpParams): Promise { let preMax = -1; if (totalXPAdded > 0) { preMax = totalXPAdded; - await prisma.xPGain.create({ - data: { - user_id: BigInt(user.id), - skill: params.skillName, - xp: Math.floor(totalXPAdded), - artificial: params.artificial ? true : null, - source: params.source - } - }); - } - - // Post-MAX_XP - if (params.amount - totalXPAdded > 0) { - await prisma.xPGain.create({ - data: { - user_id: BigInt(user.id), - skill: params.skillName, - xp: Math.floor(params.amount - totalXPAdded), - artificial: params.artificial ? true : null, - post_max: true, - source: params.source - } - }); } // If they reached a XP milestone, send a server notification. @@ -324,18 +293,14 @@ export async function addXP(user: MUser, params: AddXpParams): Promise { queryValue = convertLVLtoXP(value); resultStr += `${skill.emoji} **${user.usernameOrMention}'s** minion, ${ user.minionName - }, just achieved level ${value} in ${skillNameCased}! They are the {nthUser} to get level ${value} in ${skillNameCased}.${ - !user.isIronman ? '' : ` They are the {nthIron} Ironman to get level ${value} in ${skillNameCased}` - }`; + }, just achieved level ${value} in ${skillNameCased}! They are the {nthUser} to get level ${value} in ${skillNameCased}.`; } else { queryValue = value; resultStr += `${skill.emoji} **${user.usernameOrMention}'s** minion, ${ user.minionName }, just achieved ${toKMB(value)} XP in ${skillNameCased}! They are the {nthUser} to get ${toKMB( value - )} in ${skillNameCased}.${ - !user.isIronman ? '' : ` They are the {nthIron} Ironman to get ${toKMB(value)} XP in ${skillNameCased}` - }`; + )} in ${skillNameCased}.`; } // Query nthUser and nthIronman const [nthUser] = await prisma.$queryRawUnsafe< @@ -344,16 +309,6 @@ export async function addXP(user: MUser, params: AddXpParams): Promise { }[] >(`SELECT COUNT(*)::int FROM users WHERE "skills.${params.skillName}" >= ${queryValue};`); resultStr = resultStr.replace('{nthUser}', formatOrdinal(Number(nthUser.count) + 1)); - if (user.isIronman) { - const [nthIron] = await prisma.$queryRawUnsafe< - { - count: string; - }[] - >( - `SELECT COUNT(*)::int FROM users WHERE "minion.ironman" = true AND "skills.${params.skillName}" >= ${queryValue};` - ); - resultStr = resultStr.replace('{nthIron}', formatOrdinal(Number(nthIron.count) + 1)); - } globalClient.emit(Events.ServerNotification, resultStr); } @@ -413,5 +368,30 @@ export async function addXP(user: MUser, params: AddXpParams): Promise { : `\n**Congratulations! Your ${name} level is now ${newLevel}** 🎉`; } } + + if (user.hasRelic(RelicID.XP)) { + let lowestSkill: SkillNameType = 'attack'; + let lowestSkillXP = 100000000; + for (const [skill, xp] of objectEntries(user.skillsAsXP)) { + if (xp < lowestSkillXP) { + lowestSkill = skill; + lowestSkillXP = xp; + } + } + + const bonusXPForThisSkill = Math.ceil(params.amount * 0.1); + const xpForLowestSkill = Math.ceil(params.amount * 0.01); + await user.update({ + [`skills_${lowestSkill}`]: { + increment: xpForLowestSkill + }, + [`skills_${params.skillName}`]: { + increment: bonusXPForThisSkill + } + }); + + str += ` **Relic of XP granted you:** ${toKMB(bonusXPForThisSkill)} ${params.skillName} XP, ${toKMB(xpForLowestSkill)} ${lowestSkill} XP.`; + } + return str; } diff --git a/src/lib/analytics.ts b/src/lib/analytics.ts deleted file mode 100644 index 8381a15c8f..0000000000 --- a/src/lib/analytics.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { ActivityGroup, globalConfig } from '../lib/constants'; - -import type { GroupMonsterActivityTaskOptions } from '../lib/types/minions'; -import { taskGroupFromActivity } from '../lib/util/taskGroupFromActivity'; -import { getItem } from './util/getOSItem'; - -async function calculateMinionTaskCounts() { - const minionTaskCounts: Record = { - [ActivityGroup.Clue]: 0, - [ActivityGroup.Minigame]: 0, - [ActivityGroup.Monster]: 0, - [ActivityGroup.Skilling]: 0 - }; - - const currentTasks = await prisma.activity.findMany({ - where: { - completed: false, - finish_date: { - gt: new Date() - } - } - }); - - for (const task of currentTasks) { - const group = taskGroupFromActivity(task.type); - - if (task.group_activity) { - minionTaskCounts[group] += (task.data as unknown as GroupMonsterActivityTaskOptions).users.length; - } else { - minionTaskCounts[group] += 1; - } - } - return minionTaskCounts; -} - -export async function analyticsTick() { - const [numberOfMinions, totalSacrificed, numberOfIronmen, totalGP] = ( - await Promise.all( - [ - 'SELECT COUNT(*)::int FROM users WHERE "minion.hasBought" = true;', - 'SELECT SUM("sacrificedValue") AS count FROM users;', - 'SELECT COUNT(*)::int FROM users WHERE "minion.ironman" = true;', - 'SELECT SUM("GP") AS count FROM users;' - ].map(query => prisma.$queryRawUnsafe(query)) - ) - ).map((result: any) => Number.parseInt(result[0].count)) as number[]; - - const artifact = getItem('Magical artifact')!; - const statuette = getItem('Demon statuette')!; - - const [totalGeGp, totalArtifactGp, totalDemonStatuetteGp] = ( - await Promise.all( - [ - 'SELECT quantity AS val FROM ge_bank WHERE item_id = 995', - `SELECT COALESCE(SUM((bank->>'${artifact.id}')::bigint) * ${artifact.highalch}, 0) as val FROM users WHERE bank->>'${artifact.id}' IS NOT NULL`, - `SELECT COALESCE(SUM((bank->>'${statuette.id}')::bigint) * ${statuette.highalch}, 0) as val FROM users WHERE bank->>'${artifact.id}' IS NOT NULL` - ].map(q => prisma.$queryRawUnsafe<{ val: bigint }[]>(q)) - ) - ).map((v: { val: bigint }[]) => BigInt(v[0].val)); - - const taskCounts = await calculateMinionTaskCounts(); - const currentClientSettings = await prisma.clientStorage.findFirst({ - where: { - id: globalConfig.clientID - }, - select: { - economyStats_dicingBank: true, - economyStats_duelTaxBank: true, - gp_daily: true, - gp_alch: true, - gp_dice: true, - gp_hotcold: true, - gp_luckypick: true, - gp_open: true, - gp_pickpocket: true, - gp_pvm: true, - gp_sell: true, - gp_slots: true, - gp_tax_balance: true, - economyStats_dailiesAmount: true, - gp_ic: true - } - }); - if (!currentClientSettings) throw new Error('No client settings found'); - await prisma.analytic.create({ - data: { - guildsCount: globalClient.guilds.cache.size, - membersCount: globalClient.guilds.cache.reduce((acc, curr) => (acc += curr.memberCount || 0), 0), - timestamp: Math.floor(Date.now() / 1000), - clueTasksCount: taskCounts.Clue, - minigameTasksCount: taskCounts.Minigame, - monsterTasksCount: taskCounts.Monster, - skillingTasksCount: taskCounts.Skilling, - ironMinionsCount: numberOfIronmen, - minionsCount: numberOfMinions, - totalSacrificed, - totalGP, - totalGeGp, - totalBigAlchGp: totalDemonStatuetteGp + totalArtifactGp, - dicingBank: currentClientSettings.economyStats_dicingBank, - duelTaxBank: currentClientSettings.economyStats_duelTaxBank, - dailiesAmount: currentClientSettings.economyStats_dailiesAmount, - gpAlching: currentClientSettings.gp_alch, - gpPvm: currentClientSettings.gp_pvm, - gpSellingItems: currentClientSettings.gp_sell, - gpPickpocket: currentClientSettings.gp_pickpocket, - gpOpen: currentClientSettings.gp_open, - gpDice: currentClientSettings.gp_dice, - gpDaily: currentClientSettings.gp_daily, - gpLuckyPick: currentClientSettings.gp_luckypick, - gpSlots: currentClientSettings.gp_slots, - gpHotCold: currentClientSettings.gp_hotcold, - gpItemContracts: currentClientSettings.gp_ic - } - }); -} diff --git a/src/lib/badges.ts b/src/lib/badges.ts deleted file mode 100644 index bcf839069a..0000000000 --- a/src/lib/badges.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { BadgesEnum, badges } from '../lib/constants'; - -export async function cacheBadges() { - const newCache = new Map(); - const usersWithBadges = await prisma.user.findMany({ - where: { - badges: { - hasSome: Object.values(BadgesEnum) - }, - RSN: { - not: null - } - }, - select: { - badges: true, - id: true, - RSN: true - } - }); - - for (const user of usersWithBadges) { - if (!user.RSN) continue; - const userBadges = user.badges.map((badge: number) => badges[badge]); - newCache.set(user.RSN.toLowerCase(), userBadges.join(' ')); - } - - globalClient._badgeCache?.clear(); - globalClient._badgeCache = newCache; -} diff --git a/src/lib/bankImage.ts b/src/lib/bankImage.ts index f2609f6394..7a19f4dfb1 100644 --- a/src/lib/bankImage.ts +++ b/src/lib/bankImage.ts @@ -3,14 +3,14 @@ import * as fs from 'node:fs/promises'; import * as path from 'node:path'; import type { SKRSContext2D } from '@napi-rs/canvas'; import { Canvas, GlobalFonts, Image, loadImage } from '@napi-rs/canvas'; -import { PerkTier, cleanString, formatItemStackQuantity, generateHexColorForCashStack } from '@oldschoolgg/toolkit'; +import { cleanString, formatItemStackQuantity, generateHexColorForCashStack } from '@oldschoolgg/toolkit'; import { UserError } from '@oldschoolgg/toolkit'; import { AttachmentBuilder } from 'discord.js'; -import { chunk, randInt, sumArr } from 'e'; +import { chunk, randInt } from 'e'; import fetch from 'node-fetch'; import { Bank } from 'oldschooljs'; import type { Item } from 'oldschooljs/dist/meta/types'; -import { resolveItems, toKMB } from 'oldschooljs/dist/util/util'; +import { getItemOrThrow, resolveItems, toKMB } from 'oldschooljs/dist/util/util'; import { allCLItems } from '../lib/data/Collections'; import { filterableTypes } from '../lib/data/filterables'; @@ -23,11 +23,9 @@ import type { ItemBank } from '../lib/types'; import { drawImageWithOutline, fillTextXTimesInCtx, getClippedRegionImage } from '../lib/util/canvasUtil'; import itemID from '../lib/util/itemID'; import { logError } from '../lib/util/logError'; -import { XPLamps } from '../mahoji/lib/abstracted_commands/lampCommand'; import { divinationEnergies } from './bso/divination'; -import { BOT_TYPE, BitField, ItemIconPacks, doaPurples, toaPurpleItems } from './constants'; +import { doaPurples, toaPurpleItems } from './constants'; import { TOBUniques } from './data/tob'; -import { marketPriceOfBank, marketPriceOrBotPrice } from './marketPrices'; import { SkillsEnum } from './skilling/types'; import { applyCustomItemEffects } from './util/customItemEffects'; import { allSlayerMaskHelmsAndMasks, slayerMaskLeaderboardCache } from './util/slayerMaskLeaderboard'; @@ -227,7 +225,6 @@ const forcedShortNameMap = new Map([ [i('Yew logs'), 'Yew'], [i('Magic logs'), 'Magic'], [i('Redwood logs'), 'Redwood'], - ...XPLamps.map(lamp => [lamp.itemID, toKMB(lamp.amount)] as const), // Uncharged [i('Holy sanguinesti staff (uncharged)'), 'Unch.'], @@ -408,32 +405,9 @@ export class BankImageTask { for (const fileName of filesInDir) { this.itemIconsList.add(Number.parseInt(path.parse(fileName).name)); } - - for (const pack of ItemIconPacks) { - const directories = BOT_TYPE === 'OSB' ? ['osb'] : ['osb', 'bso']; - - for (const dir of directories) { - const filesInThisDir = await fs.readdir(`./src/lib/resources/images/icon_packs/${pack.id}_${dir}`); - for (const fileName of filesInThisDir) { - const themedItemID = Number.parseInt(path.parse(fileName).name); - const image = await loadImage( - `./src/lib/resources/images/icon_packs/${pack.id}_${dir}/${fileName}` - ); - pack.icons.set(themedItemID, image); - } - } - } } async getItemImage(itemID: number, user?: MUser): Promise { - if (user && user.user.icon_pack_id !== null) { - for (const pack of ItemIconPacks) { - if (pack.id === user.user.icon_pack_id) { - return pack.icons.get(itemID) ?? this.getItemImage(itemID, undefined); - } - } - } - const cachedImage = this.itemIconImagesCache.get(itemID); if (cachedImage) return cachedImage; @@ -454,17 +428,23 @@ export class BankImageTask { } } - async fetchAndCacheImage(itemID: number) { + async fetchAndCacheImage(itemID: number): Promise { const imageBuffer = await fetch(`https://static.runelite.net/cache/item/icon/${itemID}.png`).then(result => result.buffer() ); await fs.writeFile(path.join(CACHE_DIR, `${itemID}.png`), imageBuffer); - const image = await loadImage(imageBuffer); + try { + const image = await loadImage(imageBuffer); - this.itemIconsList.add(itemID); - this.itemIconImagesCache.set(itemID, image); + this.itemIconsList.add(itemID); + this.itemIconImagesCache.set(itemID, image); + return image; + } catch (err) { + console.error(`${itemID} image failed to load`); + return this.fetchAndCacheImage(getItemOrThrow('Bones').id); + } } drawBorder(ctx: SKRSContext2D, sprite: IBgSprite, titleLine = true) { @@ -655,10 +635,7 @@ export class BankImageTask { } } else if (mahojiFlags?.includes('show_weights') && weightings && weightings[item.id]) { bottomItemText = weightings[item.id]; - } else if (mahojiFlags?.includes('show_market_price')) { - bottomItemText = marketPriceOrBotPrice(item.id) * quantity; } - const forcedShortName = forcedShortNameMap.get(item.id); if (forcedShortName && !bottomItemText) { ctx.font = '10px Smallest Pixel-7'; @@ -686,7 +663,7 @@ export class BankImageTask { collectionLog?: Bank; mahojiFlags?: BankFlag[]; }): Promise { - let { user, collectionLog, title = '', showValue = true } = opts; + let { user, collectionLog, title = '' } = opts; const bank = opts.bank.clone(); const flags = new Map(Object.entries(opts.flags ?? {})); let compact = flags.has('compact'); @@ -720,8 +697,7 @@ export class BankImageTask { // Sorting const favorites = user?.user.favoriteItems; const weightings = user?.user.bank_sort_weightings as ItemBank; - const perkTier = user ? user.perkTier() : 0; - const defaultSort: BankSortMethod = perkTier < PerkTier.Two ? 'value' : user?.bankSortMethod ?? 'value'; + const defaultSort: BankSortMethod = user?.bankSortMethod ?? 'value'; const sortInput = flags.get('sort'); const sort = sortInput ? BankSortMethods.find(s => s === sortInput) ?? defaultSort : defaultSort; @@ -738,7 +714,7 @@ export class BankImageTask { }); } - if (perkTier >= PerkTier.Two && weightings && Object.keys(weightings).length > 0) { + if (user && user.perkTier() >= 2 && weightings && Object.keys(weightings).length > 0) { items.sort((a, b) => { const aWeight = weightings[a[0].id]; const bWeight = weightings[b[0].id]; @@ -750,8 +726,6 @@ export class BankImageTask { }); } - const totalValue = sumArr(items.map(([i, q]) => i.price * q)); - const chunkSize = compact ? 140 : 56; const chunked = chunk(items, chunkSize); @@ -803,14 +777,10 @@ export class BankImageTask { const hexColor = user?.user.bank_bg_hex; - const useSmallBank = - bgImage.id !== 100 && - (user ? (hasBgSprite ? true : user.bitfield.includes(BitField.AlwaysSmallBank)) : true); - - const canvas = new Canvas(width, useSmallBank ? canvasHeight : Math.max(331, canvasHeight)); + const canvas = new Canvas(width, Math.max(331, canvasHeight)); let resizeBg = -1; - if (!wide && !useSmallBank && !isTransparent && actualBackground && canvasHeight > 331) { + if (!wide && !isTransparent && actualBackground && canvasHeight > 331) { resizeBg = Math.min(1440, canvasHeight) / actualBackground.height; } @@ -840,10 +810,6 @@ export class BankImageTask { ); } - if (showValue) { - title += ` (V: ${toKMB(totalValue)} / MV: ${toKMB(marketPriceOfBank(bank))}) `; - } - drawTitle(ctx, title, canvas); // Skips border if noBorder is set diff --git a/src/lib/baxtorianBathhouses.ts b/src/lib/baxtorianBathhouses.ts index 86b6f0d0b7..40015a82fd 100644 --- a/src/lib/baxtorianBathhouses.ts +++ b/src/lib/baxtorianBathhouses.ts @@ -19,7 +19,6 @@ import getOSItem from './util/getOSItem'; import { handleTripFinish } from './util/handleTripFinish'; import { makeBankImage } from './util/makeBankImage'; import resolveItems from './util/resolveItems'; -import { updateBankSetting } from './util/updateBankSetting'; export const bathhouseTierNames = ['Warm', 'Hot', 'Fiery'] as const; export type BathhouseTierName = (typeof bathhouseTierNames)[number]; @@ -359,7 +358,6 @@ export async function baxtorianBathhousesStartCommand({ if (!user.owns(cost)) { return `You don't have enough supplies to do a trip, for ${quantity}x ${bathHouseTier.name} baths, you need: ${cost}.`; } - await updateBankSetting('bb_cost', cost); await user.removeItemsFromBank(cost); await addSubTaskToActivityTask({ @@ -489,7 +487,6 @@ export async function baxtorianBathhousesActivity(data: BathhouseTaskOptions) { }); const uniqSpecies = uniqueArr(speciesServed); - await updateBankSetting('bb_loot', loot); const bankImage = await makeBankImage({ bank: itemsAdded, diff --git a/src/lib/blacklists.ts b/src/lib/blacklists.ts index 7ae966f68d..9565a49f8d 100644 --- a/src/lib/blacklists.ts +++ b/src/lib/blacklists.ts @@ -1,20 +1,14 @@ import { Time } from 'e'; -import { production } from '../config'; +import { mahojiClientSettingsFetch } from './util/clientSettings'; export const BLACKLISTED_USERS = new Set(); export const BLACKLISTED_GUILDS = new Set(); export async function syncBlacklists() { - const blacklistedEntities = await roboChimpClient.blacklistedEntity.findMany(); - BLACKLISTED_USERS.clear(); - BLACKLISTED_GUILDS.clear(); - for (const entity of blacklistedEntities) { - const set = entity.type === 'guild' ? BLACKLISTED_GUILDS : BLACKLISTED_USERS; - set.add(entity.id.toString()); - } + const a = await mahojiClientSettingsFetch(); + for (const g of a.guildBlacklist) BLACKLISTED_GUILDS.add(g); + for (const u of a.userBlacklist) BLACKLISTED_USERS.add(u); } -if (production) { - setInterval(syncBlacklists, Time.Minute * 10); -} +setInterval(syncBlacklists, Time.Minute * 10); diff --git a/src/lib/bossEvents.ts b/src/lib/bossEvents.ts deleted file mode 100644 index ca290e16d4..0000000000 --- a/src/lib/bossEvents.ts +++ /dev/null @@ -1,344 +0,0 @@ -import { EmbedBuilder, type TextChannel } from 'discord.js'; -import { - Time, - calcPercentOfNum, - calcWhatPercent, - chunk, - percentChance, - randArrItem, - reduceNumByPercent, - shuffleArr -} from 'e'; -import { Bank, LootTable } from 'oldschooljs'; - -import { OWNER_IDS, production } from '../config'; - -import { - PUMPKINHEAD_HEALING_NEEDED, - PUMPKINHEAD_ID, - getPHeadDescriptor, - numberOfPHeadItemsInCL, - pumpkinHeadNonUniqueTable, - pumpkinHeadUniqueTable -} from './simulation/pumpkinHead'; -import { BossInstance, type BossOptions, type BossUser } from './structures/Boss'; -import { Gear } from './structures/Gear'; -import type { NewBossOptions } from './types/minions'; -import { formatDuration, roll } from './util'; -import getOSItem from './util/getOSItem'; -import { logError } from './util/logError'; -import { sendToChannelID } from './util/webhook'; -import { LampTable } from './xpLamps'; - -interface BossEvent { - id: number; - name: string; - bossOptions: Omit; - handleFinish: (options: NewBossOptions, bossUsers: BossUser[]) => Promise; -} - -export const bossEventChannelID = production ? '897170239333220432' : '1023760501957722163'; - -export const scaryEatables = [ - { - item: getOSItem('Candy teeth'), - healAmount: 3 - }, - { - item: getOSItem('Toffeet'), - healAmount: 5 - }, - { - item: getOSItem('Chocolified skull'), - healAmount: 8 - }, - { - item: getOSItem('Rotten sweets'), - healAmount: 9 - }, - { - item: getOSItem('Hairyfloss'), - healAmount: 12 - }, - { - item: getOSItem('Eyescream'), - healAmount: 13 - }, - { - item: getOSItem('Goblinfinger soup'), - healAmount: 20 - }, - { - item: getOSItem("Benny's brain brew"), - healAmount: 50 - }, - { - item: getOSItem('Roasted newt'), - healAmount: 120 - } -]; - -function getScaryFoodFromBank(user: MUser, totalHealingNeeded: number, _userBank?: Bank): false | Bank { - if (OWNER_IDS.includes(user.id)) return new Bank(); - let totalHealingCalc = totalHealingNeeded; - const foodToRemove = new Bank(); - const userBank = _userBank ?? user.bank; - - const sorted = [...scaryEatables] - .filter(i => !user.user.favoriteItems.includes(i.item.id)) - .sort((i, j) => (i.healAmount > j.healAmount ? 1 : -1)) - .sort((a, b) => { - if (!userBank.has(a.item.id!)) return 1; - if (!userBank.has(b.item.id!)) return -1; - return 0; - }); - - // Gets all the eatables in the user bank - for (const eatable of sorted) { - const { id } = eatable.item; - const { healAmount } = eatable; - const amountOwned = userBank.amount(id!); - const toRemove = Math.ceil(totalHealingCalc / healAmount); - if (!amountOwned) continue; - if (amountOwned >= toRemove) { - totalHealingCalc -= Math.ceil(healAmount * toRemove); - foodToRemove.add(id!, toRemove); - break; - } else { - totalHealingCalc -= Math.ceil(healAmount * amountOwned); - foodToRemove.add(id!, amountOwned); - } - } - // Check if qty is still above 0. If it is, it means the user doesn't have enough food. - if (totalHealingCalc > 0) return false; - return foodToRemove; -} - -export const bossEvents: BossEvent[] = [ - { - id: PUMPKINHEAD_ID, - name: 'Pumpkinhead', - handleFinish: async (data, bossUsers) => { - const lootElligible = shuffleArr(bossUsers.filter(i => !percentChance(i.deathChance))); - const userLoot: Record = {}; - for (const i of lootElligible) { - userLoot[i.user.id] = new Bank(); - userLoot[i.user.id].add(pumpkinHeadNonUniqueTable.roll(5)); - if (roll(25)) { - userLoot[i.user.id].add("Choc'rock"); - } - await i.user.incrementKC(PUMPKINHEAD_ID, 1); - } - - const lootGroups = chunk(lootElligible, 4).filter(i => i.length === 4); - const uniqueItemRecipients = lootGroups.map(groupArr => randArrItem(groupArr)); - const uniqueLootStr = []; - const rerolledUsersStr = []; - - const secondChancePeople = []; - for (const lootElliPerson of lootElligible) { - if ( - !uniqueItemRecipients.includes(lootElliPerson) && - numberOfPHeadItemsInCL(lootElliPerson.user) < 2 && - lootElliPerson.user.hasEquipped('Haunted amulet') && - roll(2) - ) { - uniqueItemRecipients.push(lootElliPerson); - secondChancePeople.push(lootElliPerson); - } - } - - const failoverEmojis = ['🙈', '🙉', '🙊']; - const randomFailEmoji = () => randArrItem(failoverEmojis); - for (const recip of uniqueItemRecipients) { - const { cl } = recip.user; - const items = pumpkinHeadUniqueTable.roll(); - const numPetsInCL = cl.amount('Mini Pumpkinhead'); - let pheadDropRate = 40 * (numPetsInCL + 1); - const userPhKc = await recip.user.getKC(PUMPKINHEAD_ID); - if (numPetsInCL === 0) { - // 140 kc gets 60% - const reductionPercent = calcPercentOfNum(calcWhatPercent(Math.min(140, userPhKc), 140), 60); - pheadDropRate = Math.floor(reduceNumByPercent(pheadDropRate, reductionPercent)); - } - if (roll(pheadDropRate)) { - items.add('Mini Pumpkinhead'); - } - - let rerolled = false; - - // If no pet, and they already have 2 of this item in CL - if (items.length === 1 && cl.amount(items.items()[0][0].id) >= 2) { - // Roll them new loot - const newRoll = pumpkinHeadUniqueTable.roll(); - newRoll.remove('Mini Pumpkinhead'); - // If the new loot has no pet, and they also have 2 of this item in CL, - // they get nothing, and someone who didn't originally get a drop, now gets one, - // however, the new recipient is subject to the same rerolling happening to them. - if (newRoll.length === 1 && cl.amount(newRoll.items()[0][0].id) >= 2) { - const newRecipient = randArrItem(lootElligible.filter(u => !uniqueItemRecipients.includes(u))); - if (newRecipient && roll(2)) { - uniqueItemRecipients.push(newRecipient); - rerolledUsersStr.push(`${recip.user}'s loot got rerolled to ${newRecipient.user}!`); - } - continue; - } - - items.bank = newRoll.bank; - rerolled = true; - } - const hasPet = items.has('Mini Pumpkinhead'); - let str = `${rerolled ? '♻️ ' : ''}${recip.user} got ${items}`; - if (hasPet) str = `<:Mini_pumpkinhead:904028863724675072>**${str}**`; - if (secondChancePeople.includes(recip)) str = `<:Haunted_amulet:898407574527942677>${str}`; - uniqueLootStr.push(str); - userLoot[recip.user.id].add(items); - } - - const specialLootRecipient = randArrItem(lootElligible); - const specialLoot = new Bank() - .add('Holiday mystery box') - .add(LampTable.roll()) - .add( - new LootTable() - .add('Clue scroll (hard)', 1, 20) - .add('Clue scroll (elite)', 1, 10) - .add('Clue scroll (master)', 1, 5) - .add('Clue scroll (grandmaster)', 1, 2) - .roll() - ); - userLoot[specialLootRecipient.user.id].add(specialLoot); - - for (const [id, bank] of Object.entries(userLoot)) { - const user = bossUsers.find(u => u.user.id === id)!; - await user.user.addItemsToBank({ items: bank, collectionLog: true }); - } - - sendToChannelID(data.channelID, { - content: `<@&896845245873025067> **Your Group Finished Fighting Pumpkinhead the Pumpkinheaded Horror!** - -*Everyone* received some Halloween candy! -${specialLootRecipient.user.usernameOrMention} received ${specialLoot}. -**Unique Loot:** -${uniqueLootStr.length > 0 ? uniqueLootStr.join('\n') : 'Nobody received any unique items!'} - -**${randomFailEmoji()} Rerolled players:** -${rerolledUsersStr.length > 0 ? rerolledUsersStr.join('\n') : 'Nobody was rerolled!'} - -**Key:** These Emoji by your name mean: -<:Haunted_amulet:898407574527942677> - Your Haunted amulet activated to give you a second chance! -♻ - You already had 2+ of the selected item and got a second chance. -<:Mini_pumpkinhead:904028863724675072> - You got the pet! Congratulations 🙂`, - allowedMentions: { roles: ['896845245873025067'] } - }); - }, - bossOptions: { - id: PUMPKINHEAD_ID, - baseDuration: Time.Hour, - skillRequirements: {}, - itemBoosts: [], - customDenier: async user => { - const foodRequired = getScaryFoodFromBank(user, PUMPKINHEAD_HEALING_NEEDED); - if (!foodRequired) { - return [ - true, - `Not enough food! You need special spooky food to fight Pumpkinhead: ${scaryEatables - .map(i => `${i.item.name} (${i.healAmount} HP)`) - .join(', ')}` - ]; - } - return [false]; - }, - bisGear: new Gear(), - gearSetup: 'melee', - itemCost: async data => { - const foodRequired = getScaryFoodFromBank(data.user, PUMPKINHEAD_HEALING_NEEDED); - if (!foodRequired) { - const fakeBank = new Bank(); - for (const { item } of scaryEatables) fakeBank.add(item.id, 100); - return getScaryFoodFromBank(data.user, PUMPKINHEAD_HEALING_NEEDED, fakeBank) as Bank; - } - - return foodRequired; - }, - mostImportantStat: 'attack_crush', - food: () => new Bank(), - activity: 'BossEvent', - minSize: production ? 5 : 1, - solo: false, - canDie: true, - customDeathChance: () => { - return 5; - }, - quantity: 1, - allowMoreThan1Solo: false, - allowMoreThan1Group: false, - automaticStartTime: production ? Time.Minute * 3 : Time.Second * 30, - maxSize: 500, - skipInvalidUsers: true, - speedMaxReduction: 50 - } - } -]; - -export async function bossActiveIsActiveOrSoonActive(id?: BossEvent['id']) { - const results = await prisma.activity.findMany({ - where: { - completed: false, - type: 'BossEvent' - } - }); - - const otherResults = await prisma.bossEvent.findMany({ - where: { - completed: false, - start_date: { - lt: new Date(Date.now() + Time.Minute * 20) - }, - id: - id === undefined - ? undefined - : { - not: id - } - } - }); - - return results.length > 0 || otherResults.length > 0; -} - -export async function startBossEvent({ boss, id }: { boss: BossEvent; id?: BossEvent['id'] }) { - if (await bossActiveIsActiveOrSoonActive(id)) { - throw new Error('There is already a boss event activity going on.'); - } - const channel = globalClient.channels.cache.get(bossEventChannelID) as TextChannel; - const instance = new BossInstance({ - ...boss.bossOptions, - channel, - massText: `<@&896845245873025067> Pumpkinhead the Pumpkinheaded ${getPHeadDescriptor()} ${getPHeadDescriptor()} Horror has spawned! Who will fight him?!`, - allowedMentions: { roles: ['896845245873025067'] }, - quantity: 1, - leader: await mUserFetch(OWNER_IDS[0]) - }); - try { - const { bossUsers } = await instance.start(); - const embed = new EmbedBuilder() - .setDescription( - `A group of ${bossUsers.length} users is off to fight ${ - boss.name - }, good luck! The total trip will take ${formatDuration(instance.duration)}.` - ) - .setImage('https://cdn.discordapp.com/attachments/357422607982919680/896527691849826374/PHEAD.png') - .setColor('#ff9500'); - - return channel.send({ - embeds: [embed], - content: instance.boosts.length > 0 ? `**Boosts:** ${instance.boosts.join(', ')}.` : 'No boosts.', - allowedMentions: { - roles: ['896845245873025067'] - } - }); - } catch (err: unknown) { - logError(err); - } -} diff --git a/src/lib/boxSpawns.ts b/src/lib/boxSpawns.ts deleted file mode 100644 index 8822503d86..0000000000 --- a/src/lib/boxSpawns.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { formatOrdinal } from '@oldschoolgg/toolkit'; -import { EmbedBuilder, type Message, type User } from 'discord.js'; -import { Time, isFunction, randArrItem, shuffleArr } from 'e'; -import fetch from 'node-fetch'; -import { Bank, Items, LootTable, Monsters } from 'oldschooljs'; - -import { production } from '../config'; -import { itemNameFromID, roll, stringMatches } from '../lib/util'; -import { userStatsUpdate } from '../mahoji/mahojiSettings'; -import { MysteryBoxes } from './bsoOpenables'; -import { allCollectionLogsFlat } from './data/Collections'; -import Createables from './data/createables'; -import killableMonsters from './minions/data/killableMonsters'; -import { BSOMonsters } from './minions/data/killableMonsters/custom/customMonsters'; -import { LampTable } from './xpLamps'; - -const triviaChallenge: Challenge = async (msg: Message): Promise => { - const { question, correct_answer, incorrect_answers } = await fetch( - 'https://opentdb.com/api.php?amount=1&category=9&difficulty=medium&type=multiple' - ) - .then(res => res.json()) - .then(res => res.results[0]); - - const allAnswers = [correct_answer, ...incorrect_answers].sort(() => 0.5 - Math.random()); - - const embed = new EmbedBuilder() - .setTitle('Reply with the answer for a reward!') - .setDescription(`${question}\n\nPossible answers: ${allAnswers.join(', ')}`) - .setThumbnail( - 'https://cdn.discordapp.com/attachments/357422607982919680/1100378550189707314/534px-Mystery_box_detail.png' - ); - - await msg.channel.send({ embeds: [embed] }); - - try { - const collected = await msg.channel.awaitMessages({ - max: 1, - time: Time.Second * 30, - errors: ['time'], - filter: _msg => stringMatches(_msg.content, correct_answer) - }); - - const winner = collected.first()?.author; - return winner ?? null; - } catch (err) { - await msg.channel.send('Nobody answered in time, sorry!'); - return null; - } -}; - -const itemChallenge: Challenge = async (msg: Message): Promise => { - const randomItem = Items.random(); - const scrambed = randomItem.name - .split(' ') - .map(part => shuffleArr([...part]).join('')) - .join(' '); - - const embed = new EmbedBuilder() - .setTitle('Reply with the answer for a reward!') - .setDescription(`Unscramble this item name for a reward: ${scrambed}`) - .setThumbnail( - 'https://cdn.discordapp.com/attachments/357422607982919680/1100378550189707314/534px-Mystery_box_detail.png' - ); - - await msg.channel.send({ embeds: [embed] }); - - try { - const collected = await msg.channel.awaitMessages({ - max: 1, - time: Time.Second * 30, - errors: ['time'], - filter: _msg => stringMatches(_msg.content, randomItem.name) - }); - - const winner = collected.first()?.author; - return winner ?? null; - } catch (err) { - await msg.channel.send('Nobody answered in time, sorry!'); - return null; - } -}; - -const createdChallenge: Challenge = async (msg: Message): Promise => { - const all = Createables.filter( - i => - ['revert', 'fix', '(', 'unlock', 'unpack', 'pouch', ' set', 'graceful'].every( - o => !i.name.toLowerCase().includes(o) - ) && - Object.keys(i.outputItems).length === 1 && - !isFunction(i.inputItems) - ); - const randomCreatable = randArrItem(all); - - const embed = new EmbedBuilder() - .setTitle('Reply with the answer for a reward!') - .setDescription( - `What item is created using these? ${ - isFunction(randomCreatable.inputItems) - ? "This shouldn't be possible..." - : randomCreatable.inputItems instanceof Bank - ? randomCreatable.inputItems - : new Bank(randomCreatable.inputItems) - }` - ) - .setThumbnail( - 'https://cdn.discordapp.com/attachments/357422607982919680/1100378550189707314/534px-Mystery_box_detail.png' - ); - - await msg.channel.send({ embeds: [embed] }); - - try { - const collected = await msg.channel.awaitMessages({ - max: 1, - time: Time.Second * 30, - errors: ['time'], - filter: _msg => stringMatches(_msg.content, randomCreatable.name) - }); - - const winner = collected.first()?.author; - return winner ?? null; - } catch (err) { - await msg.channel.send(`Nobody answered in time, sorry! The correct answer was: ${randomCreatable.name}`); - return null; - } -}; -const monsters = [...Object.values(BSOMonsters), ...killableMonsters] - .map(i => { - let allItems = []; - if (i.table instanceof LootTable) { - allItems = i.table.allItems; - } else { - allItems = Monsters.get(i.id)!.allItems; - } - - return { - name: i.name, - allItems - }; - }) - .filter(m => m.allItems.length >= 3); - -const monsterDropChallenge: Challenge = async (msg: Message): Promise => { - const monster = randArrItem(monsters); - - const items = shuffleArr(monster.allItems).slice(0, 3); - const validMonsters = monsters.filter(mon => items.every(t => mon.allItems.includes(t))); - - const embed = new EmbedBuilder() - .setTitle('Reply with the answer for a reward!') - .setDescription(`Name a monster that drops these 3 items: ${items.map(itemNameFromID).join(', ')}`) - .setThumbnail( - 'https://cdn.discordapp.com/attachments/357422607982919680/1100378550189707314/534px-Mystery_box_detail.png' - ); - - await msg.channel.send({ embeds: [embed] }); - - try { - const collected = await msg.channel.awaitMessages({ - max: 1, - time: Time.Second * 30, - errors: ['time'], - filter: _msg => validMonsters.some(m => stringMatches(_msg.content, m.name)) - }); - - const winner = collected.first()?.author; - return winner ?? null; - } catch (err) { - await msg.channel.send(`Nobody answered in time, sorry! The correct answer was: ${monster.name}`); - return null; - } -}; - -const collectionLogChallenge: Challenge = async (msg: Message): Promise => { - const cl = randArrItem(allCollectionLogsFlat); - - const embed = new EmbedBuilder() - .setTitle('Reply with the answer for a reward!') - .setDescription(`Name any item from this collection log: ${cl.name}`) - .setThumbnail( - 'https://cdn.discordapp.com/attachments/357422607982919680/1100378550189707314/534px-Mystery_box_detail.png' - ); - - await msg.channel.send({ embeds: [embed] }); - - try { - const collected = await msg.channel.awaitMessages({ - max: 1, - time: Time.Second * 30, - errors: ['time'], - filter: _msg => cl.items.some(c => stringMatches(_msg.content, itemNameFromID(c))) - }); - - const winner = collected.first()?.author; - return winner ?? null; - } catch (err) { - await msg.channel.send('Nobody answered in time, sorry!'); - return null; - } -}; -// export async function reactChallenge(msg: Message): Promise { -// const embed = new EmbedBuilder() -// .setTitle('Answer this for a reward!') -// .setDescription('React to this message with any emoji for a reward!') -// .setThumbnail( -// 'https://cdn.discordapp.com/attachments/357422607982919680/1100378550189707314/534px-Mystery_box_detail.png' -// ); - -// const message = await msg.channel.send({ embeds: [embed] }); -// try { -// let winner = null; -// const collected = await message.createReactionCollector({ -// time: Time.Second * 10 -// }); -// collected.on('collect', a => { -// console.log(a); -// winner = a.users.cache.first(); -// }); -// await sleep(Time.Second * 10); -// return winner; -// } catch (err) { -// await msg.channel.send('Nobody reacted in time, sorry!'); -// return null; -// } -// } - -type Challenge = (msg: Message) => Promise; - -let lastDrop = 0; - -const channelID = production ? '792691343284764693' : '944924763405574174'; - -export async function boxSpawnHandler(msg: Message) { - if (!production) return; - if (msg.channel.id !== channelID || msg.author.bot) { - return; - } - if (Date.now() - lastDrop < Time.Minute * 5) return; - if (!roll(20)) return; - lastDrop = Date.now(); - - const item: Challenge = randArrItem([ - itemChallenge, - triviaChallenge, - createdChallenge, - collectionLogChallenge, - collectionLogChallenge, - monsterDropChallenge, - monsterDropChallenge - ]); - const winner = await item(msg); - if (!winner) return; - const winnerUser = await mUserFetch(winner.id); - const newStats = await userStatsUpdate( - winnerUser.id, - { - main_server_challenges_won: { - increment: 1 - } - }, - { main_server_challenges_won: true } - ); - const wonStr = `This is your ${formatOrdinal(newStats.main_server_challenges_won)} challenge win!`; - const loot = roll(20) ? LampTable.roll() : MysteryBoxes.roll(); - - await winnerUser.addItemsToBank({ items: loot, collectionLog: true }); - return msg.channel.send(`Congratulations, ${winner}! You received: **${loot}**. ${wonStr}`); -} diff --git a/src/lib/bso/bsoUtil.ts b/src/lib/bso/bsoUtil.ts deleted file mode 100644 index 5f39786b9b..0000000000 --- a/src/lib/bso/bsoUtil.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { discontinuedItems } from '../constants'; - -export function removeDiscontinuedItems(arr: number[]) { - return arr.filter(i => !discontinuedItems.includes(i)); -} diff --git a/src/lib/bso/doa/doaStartCommand.ts b/src/lib/bso/doa/doaStartCommand.ts index fbc219aaeb..814e5a0cb9 100644 --- a/src/lib/bso/doa/doaStartCommand.ts +++ b/src/lib/bso/doa/doaStartCommand.ts @@ -12,7 +12,6 @@ import { checkDOAUser, createDOATeam } from '../../depthsOfAtlantis'; -import { trackLoot } from '../../lootTrack'; import { setupParty } from '../../party'; import type { MakePartyOptions } from '../../types'; import type { DOAOptions } from '../../types/minions'; @@ -20,7 +19,6 @@ import { bankToStrShortNames } from '../../util'; import addSubTaskToActivityTask from '../../util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../util/calcMaxTripLength'; import getOSItem from '../../util/getOSItem'; -import { updateBankSetting } from '../../util/updateBankSetting'; export async function doaStartCommand( user: MUser, @@ -114,7 +112,7 @@ export async function doaStartCommand( const totalCost = new Bank(); - const costResult = await Promise.all( + await Promise.all( users.map(async u => { const { cost, sangCharges, voidStaffCharges, tumShadowCharges } = await calcDOAInput({ user: u, @@ -163,18 +161,6 @@ export async function doaStartCommand( }) ); - await updateBankSetting('doa_cost', totalCost); - await trackLoot({ - totalCost, - id: 'depths_of_atlantis', - type: 'Minigame', - changeType: 'cost', - users: costResult.map(i => ({ - id: i.userID, - cost: i.effectiveCost, - duration: createdDOATeam.realDuration - })) - }); await addSubTaskToActivityTask({ userID: user.id, channelID: channelID.toString(), diff --git a/src/lib/bso/turaelsTrials.ts b/src/lib/bso/turaelsTrials.ts index ce23b183cf..8421201b1f 100644 --- a/src/lib/bso/turaelsTrials.ts +++ b/src/lib/bso/turaelsTrials.ts @@ -2,7 +2,6 @@ import { Time, increaseNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; import { formatDuration } from '@oldschoolgg/toolkit'; -import { trackClientBankStats, userStatsBankUpdate } from '../../mahoji/mahojiSettings'; import { degradeChargeBank } from '../degradeableItems'; import { ChargeBank } from '../structures/Banks'; import type { TuraelsTrialsOptions } from '../types/minions'; @@ -108,8 +107,6 @@ export async function turaelsTrialsStartCommand(user: MUser, channelID: string, const degradeResults = await degradeChargeBank(user, chargeBank); await user.removeItemsFromBank(cost); - await trackClientBankStats('turaels_trials_cost_bank', cost); - await userStatsBankUpdate(user.id, 'turaels_trials_cost_bank', cost); messages.push(degradeResults.map(i => i.userMessage).join(', ')); messages.push(`Removed ${cost}`); diff --git a/src/lib/bsoOpenables.ts b/src/lib/bsoOpenables.ts index 02c522a66d..6f4054e3bc 100644 --- a/src/lib/bsoOpenables.ts +++ b/src/lib/bsoOpenables.ts @@ -1,9 +1,7 @@ import { randArrItem, roll } from 'e'; import { Bank, Items, LootTable } from 'oldschooljs'; -import TreeHerbSeedTable from 'oldschooljs/dist/simulation/subtables/TreeHerbSeedTable'; import { divinationEnergies } from './bso/divination'; -import { Emoji, OSB_VIRTUS_IDS } from './constants'; import { allPetIDs, chambersOfXericCL, @@ -16,29 +14,27 @@ import { import { PartyhatTable, baseHolidayItems } from './data/holidayItems'; import { allTrophyItems } from './data/itemAliases'; import { keyCrates } from './keyCrates'; -import { FishTable } from './minions/data/killableMonsters/custom/SeaKraken'; import type { UnifiedOpenable } from './openables'; import { PaintBoxTable } from './paintColors'; +import { RelicID } from './relics'; import { ChimplingImpling, EternalImpling, InfernalImpling, MysteryImpling } from './simulation/customImplings'; +import { LampTable } from './simulation/grandmasterClue'; import { RuneTable } from './simulation/seedTable'; import { ExoticSeedsTable } from './simulation/sharedTables'; import { clAdjustedDroprate } from './util'; import getOSItem from './util/getOSItem'; import itemID from './util/itemID'; import resolveItems from './util/resolveItems'; -import { LampTable } from './xpLamps'; const MR_E_DROPRATE_FROM_UMB_AND_TMB = 5000; const MR_E_DROPRATE_FROM_PMB = 200; -const MR_E_DROPRATE_FROM_EMB = 500; export const MysteryBoxes = new LootTable() .oneIn(55, 'Pet Mystery Box') .oneIn(165, 'Holiday Mystery Box') .oneIn(35, 'Equippable mystery box') .oneIn(35, 'Clothing Mystery Box') - .add('Tradeable Mystery Box') - .add('Untradeable Mystery Box'); + .add('Tradeable Mystery Box'); export const magicCreateCrate = new LootTable() .add('Pure essence', [500, 1000], 4) @@ -177,45 +173,6 @@ export const NestBoxes = new LootTable() .add('Nest box (ring)', 1, 5) .add('Nest box (empty)', 1, 3); -const baseTGBTable = new LootTable() - .add('Tradeable mystery box', [1, 3]) - .add('Reward casket (master)', [3, 6]) - .add('Reward casket (beginner)', [3, 9]) - .add('Reward casket (hard)', [3, 7]) - .add('Dwarven crate', 2) - .add(NestBoxes, 100) - .add('Holiday Mystery box') - .add('Pet Mystery box') - .add('Untradeable Mystery box') - .add('Abyssal dragon bones', [100, 500], 2) - .add('Coins', [20_000_000, 100_000_000], 2) - .add(LampTable, [1, 3]) - .add('Clue scroll (beginner)', [5, 10], 2) - .add('Clue scroll (easy)', [4, 9], 2) - .add('Clue scroll (medium)', [4, 9], 2) - .add('Clue scroll (hard)', [3, 6], 2) - .add('Clue scroll (elite)', [4, 9], 2) - .add('Clue scroll (master)', [2, 5], 2) - .add('Manta ray', [100, 600], 2) - .add(FishTable, [1, 15]) - .add(TreeHerbSeedTable, [1, 15]) - .add('Prayer potion(4)', [5, 40]) - .add('Saradomin brew(4)', [5, 40]) - .add('Super restore(4)', [5, 20]) - .add('Monkey nuts', 2) - .add('Shark', [100, 200], 2) - .add('Beer', [500, 5000]) - .add('Tchiki monkey nuts') - .add('Magic seed', [20, 50]); - -const testerGiftTable = new LootTable() - .every(baseTGBTable, [3, 7]) - .every('Clue scroll (grandmaster)', [1, 3]) - .every(LampTable, [1, 2]) - .add('Rocktail', [30, 60]) - .add('Tradeable mystery box', [1, 3]) - .add(baseTGBTable); - export const IronmanPMBTable = new LootTable() .add(PMBTable, 1, PMBTable.length) .add('Smokey') @@ -673,7 +630,6 @@ const cantBeDropped = resolveItems([ 27_785, 27_788, 27_790, - ...OSB_VIRTUS_IDS, 'Scurry', 'Trailblazer reloaded dragon trophy', 'Trailblazer reloaded rune trophy', @@ -722,14 +678,6 @@ export const itemSearchMbTable = [ ]) ]; -function makeOutputFromArrayOfItemIDs(fn: () => number, quantity: number) { - const loot = new Bank(); - for (let i = 0; i < quantity; i++) { - loot.add(fn()); - } - return { bank: loot }; -} - const christmasPetFoodTable = new LootTable() .add('Pumpkinhead praline') .add('Takon truffle') @@ -786,80 +734,18 @@ for (const energy of divinationEnergies) { const VenatrixEggTable = new LootTable().tertiary(1000, 'Baby venatrix'); export const bsoOpenables: UnifiedOpenable[] = [ - { - name: 'Tradeables Mystery box', - id: 6199, - openedItem: getOSItem(6199), - aliases: ['mystery', 'mystery box', 'tradeables mystery box', 'tmb'], - - output: async ({ user, quantity, totalLeaguesPoints }) => ({ - bank: getMysteryBoxItem(user, totalLeaguesPoints, true, quantity) - }), - emoji: Emoji.MysteryBox, - allItems: [], - isMysteryBox: true, - smokeyApplies: true - }, { name: 'Untradeables Mystery box', id: 19_939, openedItem: getOSItem(19_939), aliases: ['untradeables mystery box', 'umb'], - output: async ({ user, quantity, totalLeaguesPoints }) => ({ - bank: getMysteryBoxItem(user, totalLeaguesPoints, false, quantity) + output: async ({ user, quantity }) => ({ + ...getMysteryBoxItem(user, false, quantity) }), allItems: [], isMysteryBox: true, smokeyApplies: true }, - { - name: 'Equippable mystery box', - id: itemID('Equippable mystery box'), - openedItem: getOSItem('Equippable mystery box'), - aliases: ['equippable mystery box', 'emb'], - output: async ({ quantity }) => makeOutputFromArrayOfItemIDs(randomEquippable, quantity), - allItems: [], - isMysteryBox: true, - smokeyApplies: true - }, - { - name: 'Clothing Mystery Box', - id: 50_421, - openedItem: getOSItem(50_421), - aliases: ['cmb', 'clothing mystery box'], - output: ClothingMysteryBoxTable, - allItems: ClothingMysteryBoxTable.allItems, - smokeyApplies: true - }, - { - name: 'Holiday Mystery box', - id: 3713, - openedItem: getOSItem(3713), - aliases: ['holiday mystery box', 'hmb', 'holiday', 'holiday item mystery box', 'himb'], - output: baseHolidayItems, - allItems: baseHolidayItems.allItems, - smokeyApplies: true - }, - { - name: 'Pet Mystery box', - id: 3062, - openedItem: getOSItem(3062), - aliases: ['pet mystery box', 'pmb'], - output: async ({ user, quantity }) => ({ - bank: user.isIronman ? IronmanPMBTable.roll(quantity) : PMBTable.roll(quantity) - }), - allItems: PMBTable.allItems, - smokeyApplies: true - }, - { - name: 'Tester Gift box', - id: itemID('Tester gift box'), - openedItem: getOSItem('Tester Gift box'), - aliases: ['tester gift box', 'tgb'], - output: testerGiftTable, - allItems: testerGiftTable.allItems, - excludeFromOpenAll: true - }, { name: 'Dwarven crate', id: itemID('Dwarven crate'), @@ -1054,16 +940,6 @@ for (const crate of keyCrates) { }); } -function randomEquippable(): number { - const res = randArrItem(embTable); - if (cantBeDropped.includes(res)) return randomEquippable(); - if (res >= 40_000 && res <= 50_000) return randomEquippable(); - if (roll(MR_E_DROPRATE_FROM_EMB)) { - return itemID('Mr. E'); - } - return res; -} - function findMysteryBoxItem(table: number[]): number { const result = randArrItem(table); if (cantBeDropped.includes(result)) return findMysteryBoxItem(table); @@ -1071,44 +947,23 @@ function findMysteryBoxItem(table: number[]): number { return result; } -const leaguesUnlockedMysteryBoxItems = [ - { - item: getOSItem('Fuzzy dice'), - unlockedAt: 5000 - }, - { - item: getOSItem('Karambinana'), - unlockedAt: 10_000 - } -]; - -export function getMysteryBoxItem( - user: MUser, - totalLeaguesPoints: number, - tradeables: boolean, - quantity: number -): Bank { +export function getMysteryBoxItem(user: MUser, tradeables: boolean, quantity: number) { const mrEDroprate = clAdjustedDroprate(user, 'Mr. E', MR_E_DROPRATE_FROM_UMB_AND_TMB, 1.2); const table = tradeables ? tmbTable : umbTable; const loot = new Bank(); - - const elligibleLeaguesRewards = leaguesUnlockedMysteryBoxItems - .filter(i => totalLeaguesPoints >= i.unlockedAt) - .map(i => ({ ...i, dropRate: clAdjustedDroprate(user, i.item.id, 500, 1.5) })); - - outer: for (let i = 0; i < quantity; i++) { + let message = ''; + for (let i = 0; i < quantity; i++) { if (roll(mrEDroprate)) { loot.add('Mr. E'); continue; } - for (const leagueReward of elligibleLeaguesRewards) { - if (roll(leagueReward.dropRate)) { - loot.add(leagueReward.item.id); - continue outer; - } + let item = findMysteryBoxItem(table); + if (user.hasRelic(RelicID.Randomness) && user.cl.has(item)) { + item = findMysteryBoxItem(table); + message = 'Your Relic of Randomness rerolled a duplicate item.'; } - loot.add(findMysteryBoxItem(table)); + loot.add(item); } - return loot; + return { bank: loot, message }; } diff --git a/src/lib/collectionLogTask.ts b/src/lib/collectionLogTask.ts index 23c18c65ef..8eead679dc 100644 --- a/src/lib/collectionLogTask.ts +++ b/src/lib/collectionLogTask.ts @@ -17,7 +17,6 @@ export const collectionLogTypes = [ { name: 'collection', description: 'Normal Collection Log' }, { name: 'sacrifice', description: 'Sacrificed Items Log' }, { name: 'bank', description: 'Owned Items Log' }, - { name: 'temp', description: 'Temporary Log' }, { name: 'tame', description: 'Tames Collection Log' }, { name: 'disassembly', description: 'Disassembly Collection Log' } ] as const; @@ -106,10 +105,6 @@ class CollectionLogTask { collectionLog?: IToReturnCollection; }): Promise { const { sprite } = bankImageGenerator.getBgAndSprite(options.user.user.bankBackground, options.user); - - if (options.flags.temp) { - options.type = 'temp'; - } if (options.flags.tame) { options.type = 'tame'; } diff --git a/src/lib/colosseum.ts b/src/lib/colosseum.ts index f7db53b9d0..2659eec597 100644 --- a/src/lib/colosseum.ts +++ b/src/lib/colosseum.ts @@ -17,10 +17,8 @@ import { import { Bank, LootTable } from 'oldschooljs'; import type { EquipmentSlot } from 'oldschooljs/dist/meta/types'; -import { userStatsBankUpdate } from '../mahoji/mahojiSettings'; import { degradeChargeBank } from './degradeableItems'; import type { GearSetupType } from './gear/types'; -import { trackLoot } from './lootTrack'; import { QuestID } from './minions/data/quests'; import { ChargeBank } from './structures/Bank'; import type { ItemBank, Skills } from './types'; @@ -28,7 +26,6 @@ import type { ColoTaskOptions } from './types/minions'; import addSubTaskToActivityTask from './util/addSubTaskToActivityTask'; import resolveItems from './util/resolveItems'; import { formatSkillRequirements, itemNameFromID } from './util/smallUtils'; -import { updateBankSetting } from './util/updateBankSetting'; function combinedChance(percentages: number[]): number { const failureProbabilities = percentages.map(p => (100 - p) / 100); @@ -619,21 +616,6 @@ export async function colosseumCommand(user: MUser, channelID: string) { } messages.push(`Removed ${realCost}`); - await updateBankSetting('colo_cost', realCost); - await userStatsBankUpdate(user, 'colo_cost', realCost); - await trackLoot({ - totalCost: realCost, - id: 'colo', - type: 'Minigame', - changeType: 'cost', - users: [ - { - id: user.id, - cost: realCost - } - ] - }); - if (chargeBank.length() > 0) { const hasChargesResult = user.hasCharges(chargeBank); if (!hasChargesResult.hasCharges) { diff --git a/src/lib/compCape.ts b/src/lib/compCape.ts index 159cb0b6c7..81516f2c40 100644 --- a/src/lib/compCape.ts +++ b/src/lib/compCape.ts @@ -517,9 +517,9 @@ miscRequirements }) .add({ name: 'Unlock all Slayer unlocks', - has: ({ user, roboChimpUser }) => { + has: ({ user }) => { const hasAll = - roboChimpUser.leagues_completed_tasks_ids.includes(4103) || + user.user.leagues_completed_tasks_ids.includes(4103) || user.user.slayer_unlocks.length >= slayerUnlockableRewards.length || user.bitfield.includes(BitField.HadAllSlayerUnlocks); if (!hasAll) { @@ -600,10 +600,6 @@ const unlockablesRequirements = new Requirements() name: 'Use a Guthix Engram', bitfieldRequirement: BitField.HasGuthixEngram }) - .add({ - name: 'Unlock the Hosidius wallkit', - bitfieldRequirement: BitField.HasHosidiusWallkit - }) .add({ name: 'Use a Dexterous Prayer Scroll', bitfieldRequirement: BitField.HasDexScroll @@ -640,10 +636,6 @@ const unlockablesRequirements = new Requirements() name: "Use a Saradomin's light", bitfieldRequirement: BitField.HasSaradominsLight }) - .add({ - name: 'Unlock the Leagues max trip length boost', - bitfieldRequirement: BitField.HasLeaguesOneMinuteLengthBoost - }) .add({ name: 'Unlock the sacrifice max trip length boost', has: ({ user }) => { @@ -770,10 +762,10 @@ export const compCapeTrimmedRequirements = new Requirements() }) .add({ name: 'Complete all Leagues tasks', - has: ({ roboChimpUser }) => { + has: ({ user }) => { const hasAll = - roboChimpUser.leagues_completed_tasks_ids.length === allLeagueTasks.length && - allLeagueTasks.every(t => roboChimpUser.leagues_completed_tasks_ids.includes(t.id)); + user.user.leagues_completed_tasks_ids.length === allLeagueTasks.length && + allLeagueTasks.every(t => user.user.leagues_completed_tasks_ids.includes(t.id)); if (!hasAll) { return 'You need to complete all Leagues tasks.'; } @@ -792,8 +784,8 @@ export const compCapeTrimmedRequirements = new Requirements() for (const group of leagueTasks) { compCapeTrimmedRequirements.add({ name: `Complete all ${group.name} Leagues tasks`, - has: ({ roboChimpUser }) => { - return group.tasks.every(t => roboChimpUser.leagues_completed_tasks_ids.includes(t.id)); + has: ({ user }) => { + return group.tasks.every(t => user.user.leagues_completed_tasks_ids.includes(t.id)); } }); } diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 233973f4ae..a1c4563668 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,19 +1,16 @@ import { execSync } from 'node:child_process'; import path from 'node:path'; -import type { Image } from '@napi-rs/canvas'; -import { PerkTier, SimpleTable, StoreBitfield, dateFm } from '@oldschoolgg/toolkit'; +import { PerkTier, SimpleTable, dateFm } from '@oldschoolgg/toolkit'; import type { CommandOptions } from '@oldschoolgg/toolkit'; import type { Prisma } from '@prisma/client'; -import type { APIButtonComponent, APIInteractionDataResolvedChannel, APIRole } from 'discord.js'; -import { ButtonBuilder, ButtonStyle, ComponentType } from 'discord.js'; +import type { APIInteractionDataResolvedChannel, APIRole } from 'discord.js'; import * as dotenv from 'dotenv'; import { Time } from 'e'; import { Items } from 'oldschooljs'; import { convertLVLtoXP, getItemOrThrow } from 'oldschooljs/dist/util/util'; import { z } from 'zod'; -import { DISCORD_SETTINGS, production } from '../config'; import type { AbstractCommand } from '../mahoji/lib/inhibitors'; import { customItems } from './customItems/util'; import { SkillsEnum } from './skilling/types'; @@ -24,69 +21,9 @@ import '../lib/data/itemAliases'; export { PerkTier }; -const TestingMainChannelID = DISCORD_SETTINGS.Channels?.TestingMain ?? '944924763405574174'; - export const BOT_TYPE: 'BSO' | 'OSB' = 'BSO' as 'BSO' | 'OSB'; export const BOT_TYPE_LOWERCASE: 'bso' | 'osb' = BOT_TYPE.toLowerCase() as 'bso' | 'osb'; -export const Channel = { - General: DISCORD_SETTINGS.Channels?.General ?? '342983479501389826', - Notifications: production ? '811589869314899980' : '1042760447830536212', - ErrorLogs: DISCORD_SETTINGS.Channels?.ErrorLogs ?? '665678499578904596', - GrandExchange: DISCORD_SETTINGS.Channels?.GrandExchange ?? '738780181946171493', - Developers: DISCORD_SETTINGS.Channels?.Developers ?? '648196527294251020', - BlacklistLogs: DISCORD_SETTINGS.Channels?.BlacklistLogs ?? '782459317218967602', - EconomyLogs: DISCORD_SETTINGS.Channels?.EconomyLogs ?? '802029843712573510', - PatronLogs: '806744016309714966', - NewSponsors: DISCORD_SETTINGS.Channels?.NewSponsors ?? '806744016309714966', - HelpAndSupport: '970752140324790384', - TestingMain: DISCORD_SETTINGS.Channels?.TestingMain ?? '680770361893322761', - ChambersOfXeric: DISCORD_SETTINGS.Channels?.ChambersOfXeric ?? '991383631337635971', - BotLogs: production ? '1051725977320964197' : TestingMainChannelID, - GeneralChannel: - BOT_TYPE === 'OSB' - ? production - ? '346304390858145792' - : '1154056119019393035' - : production - ? '792691343284764693' - : '1154056119019393035', - // BSO Channels - BSOGeneral: DISCORD_SETTINGS.Channels?.BSOGeneral ?? '792691343284764693', - BSOChannel: DISCORD_SETTINGS.Channels?.BSOChannel ?? '732207379818479756', - BSOGambling: DISCORD_SETTINGS.Channels?.BSOChannel ?? '792692390778896424', - BSOGrandExchange: DISCORD_SETTINGS.Channels?.BSOChannel ?? '738780181946171493' -}; - -export const Roles = { - Booster: DISCORD_SETTINGS.Roles?.Booster ?? '665908237152813057', - Contributor: DISCORD_SETTINGS.Roles?.Contributor ?? '456181501437018112', - Moderator: DISCORD_SETTINGS.Roles?.Moderator ?? '622806157563527178', - PatronTier1: DISCORD_SETTINGS.Roles?.PatronTier1 ?? '678970545789730826', - PatronTier2: DISCORD_SETTINGS.Roles?.PatronTier2 ?? '678967943979204608', - PatronTier3: DISCORD_SETTINGS.Roles?.PatronTier3 ?? '687408140832342043', - Patron: DISCORD_SETTINGS.Roles?.Patron ?? '679620175838183424', - MassHoster: DISCORD_SETTINGS.Roles?.MassHoster ?? '734055552933429280', - BSOMassHoster: DISCORD_SETTINGS.Roles?.BSOMassHoster ?? '759572886364225558', - TopSkiller: DISCORD_SETTINGS.Roles?.TopSkiller ?? '848966830617788427', - TopCollector: DISCORD_SETTINGS.Roles?.TopCollector ?? '848966773885763586', - TopSacrificer: DISCORD_SETTINGS.Roles?.TopSacrificer ?? '848966732265160775', - TopMinigamer: DISCORD_SETTINGS.Roles?.TopMinigamer ?? '867967884515770419', - TopClueHunter: DISCORD_SETTINGS.Roles?.TopClueHunter ?? '848967350120218636', - TopSlayer: DISCORD_SETTINGS.Roles?.TopSlayer ?? '867967551819358219', - TopInventor: '992799099801833582', - TopLeagues: '1005417171112972349', - EventOrganizer: '1149907536749801542' -}; - -export enum DefaultPingableRoles { - // Tester roles: - Tester = '682052620809928718', - BSOTester = '829368646182371419', - // Mass roles: - BSOMass = '759573020464906242' -} - export enum Emoji { MoneyBag = '<:MoneyBag:493286312854683654>', OSBot = '<:OSBot:601768469905801226>', @@ -163,7 +100,6 @@ export enum Emoji { CombatAchievements = '<:combatAchievements:1145015804040065184>', Stopwatch = '⏱️', Smokey = '<:Smokey:886284971914969149>', - ItemContract = '<:Item_contract:988422348434718812>', // Badges, BigOrangeGem = '<:bigOrangeGem:778418736188489770>', GreenGem = '<:greenGem:778418736495067166>', @@ -221,32 +157,16 @@ export enum Events { export const COINS_ID = 995; export enum BitField { - IsPatronTier1 = 2, - IsPatronTier2 = 3, - IsPatronTier3 = 4, - IsPatronTier4 = 5, - IsPatronTier5 = 6, - isModerator = 7, - isContributor = 8, - BypassAgeRestriction = 9, - HasHosidiusWallkit = 10, - HasPermanentEventBackgrounds = 11, - HasPermanentTierOne = 12, + isModerator = 1, DisabledRandomEvents = 13, - PermanentIronman = 14, - AlwaysSmallBank = 15, HasDexScroll = 16, HasArcaneScroll = 17, HasTornPrayerScroll = 18, - IsWikiContributor = 19, HasSlepeyTablet = 20, - IsPatronTier6 = 21, DisableBirdhouseRunButton = 22, DisableAshSanctifier = 23, - BothBotsMaxedFreeTierOnePerks = 24, HasBloodbarkScroll = 25, DisableAutoFarmContractButton = 26, - DisableGrandExchangeDMs = 27, HadAllSlayerUnlocks = 28, HasSwampbarkScroll = 29, HasSaradominsLight = 30, @@ -256,25 +176,20 @@ export enum BitField { UsedFrozenTablet = 34, CleanHerbsFarming = 35, SelfGamblingLocked = 36, - DisabledFarmingReminders = 37, DisableClueButtons = 38, DisableAutoSlayButton = 39, DisableHighPeakTimeWarning = 40, DisableOpenableNames = 41, - HasGivenBirthdayPack = 200, - HasPermanentSpawnLamp = 201, HasScrollOfFarming = 202, HasScrollOfLongevity = 203, HasScrollOfTheHunt = 204, HasBananaEnchantmentScroll = 205, HasDaemonheimAgilityPass = 206, DisabledGorajanBoneCrusher = 207, - HasLeaguesOneMinuteLengthBoost = 208, HasPlantedIvy = 209, HasGuthixEngram = 210, ScrollOfLongevityDisabled = 211, - HasUnlockedYeti = 212, NoItemContractDonations = 213, HasFlickeringBoon = 214, @@ -295,7 +210,9 @@ export enum BitField { HasUnlockedVenatrix = 229, GrewFiveSpiritTrees = 230, UseSuperRestoresForDwarvenBlessing = 231, - DisableSizeMatters = 232 + DisableSizeMatters = 232, + + DisableRelicOfRepitition = 800 } interface BitFieldData { @@ -308,20 +225,8 @@ interface BitFieldData { } export const BitFieldData: Record = { - [BitField.IsWikiContributor]: { name: 'Wiki Contributor', protected: true, userConfigurable: false }, [BitField.isModerator]: { name: 'Moderator', protected: true, userConfigurable: false }, - [BitField.isContributor]: { name: 'Contributor', protected: true, userConfigurable: false }, - - [BitField.HasPermanentTierOne]: { name: 'Permanent Tier 1', protected: false, userConfigurable: false }, - [BitField.HasPermanentSpawnLamp]: { name: 'Permanent Spawn Lamp', protected: false, userConfigurable: false }, - [BitField.IsPatronTier1]: { name: 'Tier 1 Patron', protected: false, userConfigurable: false }, - [BitField.IsPatronTier2]: { name: 'Tier 2 Patron', protected: false, userConfigurable: false }, - [BitField.IsPatronTier3]: { name: 'Tier 3 Patron', protected: false, userConfigurable: false }, - [BitField.IsPatronTier4]: { name: 'Tier 4 Patron', protected: false, userConfigurable: false }, - [BitField.IsPatronTier5]: { name: 'Tier 5 Patron', protected: false, userConfigurable: false }, - [BitField.IsPatronTier6]: { name: 'Tier 6 Patron', protected: false, userConfigurable: false }, - - [BitField.HasHosidiusWallkit]: { name: 'Hosidius Wall Kit Unlocked', protected: false, userConfigurable: false }, + [BitField.HasDexScroll]: { name: 'Dexterous Scroll Used', protected: false, userConfigurable: false }, [BitField.HasArcaneScroll]: { name: 'Arcane Scroll Used', protected: false, userConfigurable: false }, [BitField.HasTornPrayerScroll]: { name: 'Torn Prayer Scroll Used', protected: false, userConfigurable: false }, @@ -331,7 +236,6 @@ export const BitFieldData: Record = { [BitField.HasScrollOfTheHunt]: { name: 'Scroll of the Hunt Used', protected: false, userConfigurable: false }, [BitField.HasPlantedIvy]: { name: 'Has Planted Ivy Seed', protected: false, userConfigurable: false }, [BitField.HasGuthixEngram]: { name: 'Has Guthix Engram', protected: false, userConfigurable: false }, - [BitField.HasUnlockedYeti]: { name: 'Yeti Unlocked', protected: false, userConfigurable: false }, [BitField.HasBananaEnchantmentScroll]: { name: 'Banana Enchantment Scroll Used', protected: false, @@ -352,24 +256,6 @@ export const BitFieldData: Record = { [BitField.UsedStrangledTablet]: { name: 'Used Strangled Tablet', protected: false, userConfigurable: false }, [BitField.SelfGamblingLocked]: { name: 'Self Gambling Lock', protected: false, userConfigurable: false }, - [BitField.HasGivenBirthdayPack]: { name: 'Has Given Birthday Pack', protected: false, userConfigurable: false }, - [BitField.BypassAgeRestriction]: { name: 'Bypassed Age Restriction', protected: false, userConfigurable: false }, - [BitField.HasPermanentEventBackgrounds]: { - name: 'Permanent Event Backgrounds', - protected: false, - userConfigurable: false - }, - [BitField.PermanentIronman]: { name: 'Permanent Ironman', protected: false, userConfigurable: false }, - [BitField.HasLeaguesOneMinuteLengthBoost]: { - name: 'Leagues One Minute Trip Length Boost', - protected: false, - userConfigurable: false - }, - [BitField.BothBotsMaxedFreeTierOnePerks]: { - name: 'Free T1 Perks for Maxed in OSB/BSO', - protected: false, - userConfigurable: false - }, [BitField.HasFlickeringBoon]: { name: 'Has Flickering Boon', protected: false, @@ -436,7 +322,6 @@ export const BitFieldData: Record = { userConfigurable: false }, - [BitField.AlwaysSmallBank]: { name: 'Always Use Small Banks', protected: false, userConfigurable: true }, [BitField.DisabledRandomEvents]: { name: 'Disabled Random Events', protected: false, userConfigurable: true }, [BitField.DisabledGorajanBoneCrusher]: { name: 'Disabled Gorajan Bonecrusher', @@ -454,11 +339,6 @@ export const BitFieldData: Record = { protected: false, userConfigurable: true }, - [BitField.DisableGrandExchangeDMs]: { - name: 'Disable Grand Exchange DMs', - protected: false, - userConfigurable: true - }, [BitField.ScrollOfLongevityDisabled]: { name: 'Disable Scroll of Longevity', protected: false, @@ -469,11 +349,6 @@ export const BitFieldData: Record = { protected: false, userConfigurable: true }, - [BitField.DisabledFarmingReminders]: { - name: 'Disable Farming Reminders', - protected: false, - userConfigurable: true - }, [BitField.DisableClueButtons]: { name: 'Disable Clue Buttons', protected: false, @@ -528,6 +403,11 @@ export const BitFieldData: Record = { name: 'Disable Size Matters unlock', protected: false, userConfigurable: true + }, + [BitField.DisableRelicOfRepitition]: { + name: 'Disable Relic of repitition', + protected: false, + userConfigurable: true } } as const; @@ -585,43 +465,6 @@ export const MAX_LEVEL = 120; export const MAX_TOTAL_LEVEL = Object.values(SkillsEnum).length * MAX_LEVEL; export const SILENT_ERROR = 'SILENT_ERROR'; -const buttonSource = [ - { - label: 'Wiki', - emoji: '802136964027121684', - url: 'https://bso-wiki.oldschool.gg/' - }, - { - label: 'Patreon', - emoji: '679334888792391703', - url: 'https://www.patreon.com/oldschoolbot' - }, - { - label: 'Support Server', - emoji: '778418736180494347', - url: 'https://www.discord.gg/ob' - }, - { - label: 'Bot Invite', - emoji: '778418736180494347', - url: 'http://www.oldschool.gg/invite/bso' - } -]; - -export const informationalButtons = buttonSource.map(i => - new ButtonBuilder().setLabel(i.label).setEmoji(i.emoji).setURL(i.url).setStyle(ButtonStyle.Link) -); -export const mahojiInformationalButtons: APIButtonComponent[] = buttonSource.map(i => ({ - type: ComponentType.Button, - label: i.label, - emoji: { id: i.emoji }, - style: ButtonStyle.Link, - url: i.url -})); - -export const PATRON_ONLY_GEAR_SETUP = - 'Sorry - but the `other` gear setup is only available for Tier 3 Patrons (and higher) to use.'; - export const projectiles = { arrow: { items: resolveItems(['Adamant arrow', 'Rune arrow', 'Amethyst arrow', 'Dragon arrow', 'Hellfire arrow']), @@ -723,10 +566,7 @@ export const TWITCHERS_GLOVES = ['egg', 'ring', 'seed', 'clue'] as const; export type TwitcherGloves = (typeof TWITCHERS_GLOVES)[number]; export const busyImmuneCommands = ['admin', 'rp']; -export const minionBuyButton = new ButtonBuilder() - .setCustomId('BUY_MINION') - .setLabel('Buy Minion') - .setStyle(ButtonStyle.Success); + export const FormattedCustomEmoji = //; export const IVY_MAX_TRIP_LENGTH_BOOST = Time.Minute * 25; @@ -789,8 +629,10 @@ const globalConfigSchema = z.object({ redisPort: z.coerce.number().int().optional(), botToken: z.string().min(1), isCI: z.coerce.boolean().default(false), - isProduction: z.coerce.boolean().default(production), - testingServerID: z.string(), + isProduction: z.coerce.boolean(), + mainServerID: z.string(), + generalChannelID: z.string(), + announcementsChannelID: z.string(), timeZone: z.literal('UTC') }); dotenv.config({ path: path.resolve(process.cwd(), process.env.TEST ? '.env.test' : '.env') }); @@ -801,7 +643,6 @@ if (!process.env.BOT_TOKEN && !process.env.CI) { ); } -const OLDSCHOOLGG_TESTING_SERVER_ID = '940758552425955348'; const isProduction = process.env.NODE_ENV === 'production'; export const globalConfig = globalConfigSchema.parse({ @@ -810,15 +651,13 @@ export const globalConfig = globalConfigSchema.parse({ redisPort: process.env.REDIS_PORT, botToken: process.env.BOT_TOKEN, isCI: process.env.CI, - isProduction, - testingServerID: process.env.TESTING_SERVER_ID ?? OLDSCHOOLGG_TESTING_SERVER_ID, + isProduction: process.env.NODE_ENV === 'production', + mainServerID: process.env.MAIN_SERVER, + generalChannelID: process.env.GENERAL_CHANNEL, + announcementsChannelID: process.env.ANNOUNCEMENTS_CHANNEL, timeZone: process.env.TZ }); -if ((process.env.NODE_ENV === 'production') !== globalConfig.isProduction || production !== globalConfig.isProduction) { - throw new Error('The NODE_ENV and isProduction variables must match'); -} - export const ONE_TRILLION = 1_000_000_000_000; export const gloriesInventorySize = 26; export const gloriesInventoryTime = Time.Minute * 2.2; @@ -847,9 +686,8 @@ export const YETI_ID = 129_521; export const KING_GOLDEMAR_GUARD_ID = 30_913; export const gitHash = execSync('git rev-parse HEAD').toString().trim(); -const gitRemote = BOT_TYPE === 'BSO' ? 'gc/oldschoolbot-secret' : 'oldschoolgg/oldschoolbot'; - -const GIT_BRANCH = BOT_TYPE === 'BSO' ? 'bso' : 'master'; +const gitRemote = 'oldschoolgg/oldschoolbot'; +const GIT_BRANCH = 'randomizer2'; export const META_CONSTANTS = { GIT_HASH: gitHash, @@ -870,33 +708,6 @@ export const CHINCANNON_MESSAGES = [ 'Your Chincannon turned the loot into dust.' ]; -export const masteryKey = BOT_TYPE === 'OSB' ? 'osb_mastery' : 'bso_mastery'; - -export const ItemIconPacks = [ - { - name: 'Halloween', - storeBitfield: StoreBitfield.HalloweenItemIconPack, - id: 'halloween', - icons: new Map() - } -]; - -export const patronFeatures = { - ShowEnteredInGiveawayList: { - tier: PerkTier.Four - } -}; - -export const christmasCakeIngredients = resolveItems([ - 'Gingerbread', - 'Grimy salt', - 'Snail oil', - 'Ashy flour', - 'Banana-butter', - 'Fresh rat milk', - 'Pristine chocolate bar', - 'Smokey egg' -]); export const gearValidationChecks = new Set(); export const BSO_MAX_TOTAL_LEVEL = 3120; @@ -929,3 +740,36 @@ if (!process.env.TEST) { `Starting... Git[${gitHash}] ClientID[${globalConfig.clientID}] Production[${globalConfig.isProduction}]` ); } + +export const perkTierUnlocks = [ + { + tier: 5, + clPercent: 90, + perk: PerkTier.Six + }, + { + tier: 4, + clPercent: 80, + perk: PerkTier.Five + }, + { + tier: 3, + clPercent: 60, + perk: PerkTier.Four + }, + { + tier: 2, + clPercent: 40, + perk: PerkTier.Three + }, + { + tier: 1, + clPercent: 20, + perk: PerkTier.Two + }, + { + tier: 0, + clPercent: 0, + perk: PerkTier.One + } +]; diff --git a/src/lib/crons.ts b/src/lib/crons.ts deleted file mode 100644 index 8b2b6595f7..0000000000 --- a/src/lib/crons.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { schedule } from 'node-cron'; - -import { analyticsTick } from './analytics'; -import { syncPrescence } from './doubleLoot'; -import { cacheGEPrices } from './marketPrices'; -import { cacheCleanup } from './util/cachedUserIDs'; -import { syncSlayerMaskLeaderboardCache } from './util/slayerMaskLeaderboard'; - -export function initCrons() { - /** - * Capture economy item data - */ - schedule('0 */6 * * *', async () => { - await prisma.$queryRawUnsafe(`INSERT INTO economy_item -SELECT item_id::integer, SUM(qty)::bigint FROM -( - SELECT id, (jdata).key AS item_id, (jdata).value::text::bigint AS qty FROM (select id, json_each(bank) AS jdata FROM users) AS banks -) -AS DATA -GROUP BY item_id;`); - }); - - /** - * Analytics - */ - schedule('*/5 * * * *', () => { - debugLog('Analytics cronjob starting'); - return analyticsTick(); - }); - - /** - * prescence - */ - schedule('0 * * * *', () => { - syncPrescence(); - }); - - /** - * Delete all voice channels - */ - schedule('0 0 */1 * *', async () => { - debugLog('Cache cleanup cronjob starting'); - cacheCleanup(); - }); - - schedule('0 0 * * *', async () => { - syncSlayerMaskLeaderboardCache(); - }); - - schedule('35 */48 * * *', async () => { - debugLog('cacheGEPrices cronjob starting'); - await cacheGEPrices(); - }); -} diff --git a/src/lib/data/Collections.ts b/src/lib/data/Collections.ts index 2f95d3888a..f23d6ce613 100644 --- a/src/lib/data/Collections.ts +++ b/src/lib/data/Collections.ts @@ -4,6 +4,7 @@ import type { Item } from 'oldschooljs/dist/meta/types'; import { ChambersOfXeric } from 'oldschooljs/dist/simulation/misc/ChambersOfXeric'; import type Monster from 'oldschooljs/dist/structures/Monster'; +import { writeFileSync } from 'node:fs'; import { divinationEnergies, portents } from '../bso/divination'; import { type ClueTier, ClueTiers } from '../clues/clueTiers'; import type { CollectionLogType } from '../collectionLogTask'; @@ -50,7 +51,7 @@ import type { ItemBank } from '../types'; import { stringMatches } from '../util'; import getOSItem from '../util/getOSItem'; import resolveItems from '../util/resolveItems'; -import { shuffleRandom } from '../util/smallUtils'; +import { itemNameFromID, shuffleRandom } from '../util/smallUtils'; import type { FormatProgressFunction, ICollection, ILeftListStatus, IToReturnCollection } from './CollectionsExport'; import { abyssalDragonCL, @@ -1884,6 +1885,11 @@ export const allCLItemsFiltered = [ .flat(100) ) ]; +let str = ''; +for (const a of allCLItemsFiltered) { + str += `${itemNameFromID(a)}\n`; +} +writeFileSync('all_collection_log_items.txt', str); export const overallPlusItems = [ ...new Set( @@ -1893,6 +1899,11 @@ export const overallPlusItems = [ .flat(100) ) ]; +let str2 = ''; +for (const a of allCLItems) { + str2 += `${itemNameFromID(a)}\n`; +} +writeFileSync('allllllll_collection_log_items.txt', str2); export function calcCLDetails(user: MUser | Bank) { const clItems = (user instanceof Bank ? user : user.cl).filter(i => allCLItemsFiltered.includes(i.id)); @@ -1949,12 +1960,11 @@ export function getBank(user: MUser, type: CLType, userStats: MUserStats | null) if (!userStats) return new Bank(); return new Bank(userStats.userStats.tame_cl_bank as ItemBank); } - case 'temp': - return new Bank(user.user.temp_cl as ItemBank); case 'disassembly': { return new Bank(user.user.disassembled_items_bank as ItemBank); } } + return new Bank(); } export async function getTotalCl(user: MUser, logType: CLType, userStats: MUserStats | null) { diff --git a/src/lib/data/CollectionsExport.ts b/src/lib/data/CollectionsExport.ts index 9ddeef7087..54dc40c68d 100644 --- a/src/lib/data/CollectionsExport.ts +++ b/src/lib/data/CollectionsExport.ts @@ -9,7 +9,6 @@ import type { MinigameScore } from '../settings/minigames'; import type { MUserStats } from '../structures/MUserStats'; import getOSItem from '../util/getOSItem'; import { assert } from '../util/logError'; -import { LampTable } from '../xpLamps'; import { gracefulCapes, gracefulFeet, @@ -1094,7 +1093,6 @@ export const cluesGrandmasterCL = resolveItems([ 'Ignecarus mask', 'Malygos mask', 'Blabberbeak', - ...LampTable.allItems, 'Helm of raedwald', 'Clue hunter garb', 'Clue hunter gloves', diff --git a/src/lib/data/bso.commands.json b/src/lib/data/bso.commands.json index d523c7b5df..6857095b09 100644 --- a/src/lib/data/bso.commands.json +++ b/src/lib/data/bso.commands.json @@ -26,26 +26,11 @@ "other" ] }, - { - "name": "ask", - "desc": "Ask a yes/no question to the bot and receive an answer.", - "subOptions": [] - }, { "name": "bank", "desc": "See your minions bank.", "subOptions": [] }, - { - "name": "bingo", - "desc": "Bingo!", - "subOptions": ["make_team", "leave_team", "view", "leaderboard", "create_bingo", "manage_bingo", "items"] - }, - { - "name": "bossrecords", - "desc": "Shows your OSRS boss records.", - "subOptions": [] - }, { "name": "bs", "desc": "Search your minions bank.", @@ -84,17 +69,6 @@ "desc": "Combat Achievements", "subOptions": ["view", "claim"] }, - { - "name": "casket", - "desc": "Simulate opening lots of clues caskets.", - "subOptions": [] - }, - { - "name": "choose", - "desc": "Have the bot make a choice from a list of things.", - "examples": ["/choose list:First option, second option, third option"], - "subOptions": [] - }, { "name": "chop", "desc": "Chop logs using the Woodcutting skill.", @@ -107,11 +81,6 @@ "examples": ["/cl name:Boss"], "subOptions": [] }, - { - "name": "claim", - "desc": "Claim prizes, rewards and other things.", - "subOptions": [] - }, { "name": "clue", "desc": "Send your minion to complete clue scrolls.", @@ -167,21 +136,6 @@ "examples": ["/drop items:10 trout, 5 coal"], "subOptions": [] }, - { - "name": "droprate", - "desc": "Check the droprate of something.", - "subOptions": [] - }, - { - "name": "fake", - "desc": "Generate fake images of getting loot.", - "subOptions": [] - }, - { - "name": "fakepm", - "desc": "Generate fake images of PMs.", - "subOptions": [] - }, { "name": "farming", "desc": "Allows you to do Farming related things.", @@ -198,11 +152,6 @@ "contract" ] }, - { - "name": "finish", - "desc": "Simulate finishing a CL.", - "subOptions": [] - }, { "name": "fish", "desc": "Send your minion to fish fish.", @@ -215,91 +164,39 @@ "examples": ["/craft name:Onyx necklace"], "subOptions": [] }, - { - "name": "gamble", - "desc": "Partake in various gambling activities.", - "subOptions": ["item", "dice", "duel", "lucky_pick", "slots", "hot_cold", "give_random_item"] - }, - { - "name": "ge", - "desc": "Exchange grandly with other players on the bot!", - "subOptions": ["buy", "sell", "my_listings", "cancel", "stats", "price", "view"] - }, { "name": "gear", "desc": "Manage, equip, unequip your gear.", "subOptions": ["equip", "unequip", "stats", "pet", "view", "swap", "best_in_slot"] }, - { - "name": "gearpresets", - "desc": "Manage, equip, unequip your gear presets.", - "subOptions": ["view", "equip", "create", "edit", "delete"] - }, - { - "name": "gift", - "desc": "Create gifts for other users, or open one you received.", - "subOptions": ["open", "list", "create", "send"] - }, - { - "name": "giveaway", - "desc": "Giveaway items from your ban to other players.", - "examples": ["/giveaway items:10 trout, 5 coal time:1h"], - "subOptions": ["start", "list"] - }, { "name": "gp", "desc": "See your current GP balance.", "subOptions": [] }, - { - "name": "help", - "desc": "Get information and help with the bot.", - "subOptions": [] - }, { "name": "hunt", "desc": "Hunt creatures with the Hunter skill.", "examples": ["/hunt name:Ferret"], "subOptions": [] }, - { - "name": "ic", - "desc": "Hand in random items for rewards.", - "subOptions": ["info", "send", "skip"] - }, { "name": "invention", "desc": "The invention skill.", "subOptions": ["disassemble", "research", "invent", "tools", "details", "materials", "group"] }, - { - "name": "invite", - "desc": "Shows the invite link for the bot.", - "subOptions": [] - }, { "name": "k", "desc": "Send your minion to kill things.", "examples": ["/k name:zulrah"], "subOptions": [] }, - { - "name": "kc", - "desc": "See your OSRS kc for a monster/boss.", - "examples": ["/kc name:General Graardor"], - "subOptions": [] - }, { "name": "kibble", "desc": "Make kibble from herbs and crops.", "examples": ["/kibble name:Simple kibble quantity:100"], "subOptions": [] }, - { - "name": "kill", - "desc": "Simulate killing monsters.", - "subOptions": [] - }, { "name": "laps", "desc": "Do laps on Agility courses to train Agility.", @@ -322,11 +219,8 @@ "skills", "opens", "cl", - "item_contract_streak", "leagues", "clues", - "movers", - "global", "completion", "combat_achievements", "mastery" @@ -343,33 +237,11 @@ "examples": ["/light name:Logs"], "subOptions": [] }, - { - "name": "loot", - "desc": "View your loot tracker data.", - "examples": ["/loot view name:Nex"], - "subOptions": ["view", "reset"] - }, - { - "name": "lottery", - "desc": "Win big!", - "subOptions": ["buy_tickets", "deposit_items", "info", "prices"] - }, { "name": "m", "desc": "See your current minion status and helpful buttons.", "subOptions": [] }, - { - "name": "mass", - "desc": "Arrange to mass bosses, killing them as a group.", - "examples": ["/mass name:General graardor"], - "subOptions": [] - }, - { - "name": "megaduck", - "desc": "Mega duck!.", - "subOptions": [] - }, { "name": "mine", "desc": "Send your minion to mine things.", @@ -412,17 +284,13 @@ "subOptions": [ "buy", "status", - "cracker", "stats", "achievementdiary", - "bankbg", - "lamp", "cancel", "set_icon", "set_name", "level", "kc", - "ironman", "charge", "daily", "train", @@ -461,51 +329,21 @@ "desc": "Paint an item.", "subOptions": [] }, - { - "name": "patreon", - "desc": "Shows the patreon link for the bot, where you can donate.", - "subOptions": [] - }, - { - "name": "pay", - "desc": "Send GP to another user.", - "subOptions": [] - }, { "name": "poh", "desc": "Allows you to access and build in your POH.", "examples": ["/poh build:Demonic throne"], "subOptions": ["view", "wallkit", "build", "destroy", "mount_item", "items"] }, - { - "name": "poll", - "desc": "Create a reaction poll.", - "subOptions": [] - }, - { - "name": "price", - "desc": "Looks up the price of an item.", - "subOptions": [] - }, { "name": "raid", "desc": "Send your minion to do raids - CoX or ToB.", "subOptions": ["cox", "tob", "toa", "doa"] }, { - "name": "rates", - "desc": "Check rates of various skills/activities.", - "subOptions": ["minigames", "tames", "xphr", "monster", "misc"] - }, - { - "name": "redeem", - "desc": "Redeem a code you received.", - "subOptions": [] - }, - { - "name": "roll", - "desc": "Roll a random number from 1, up to a limit.", - "subOptions": [] + "name": "randomizer", + "desc": "randomizer.", + "subOptions": ["info", "test_mapping"] }, { "name": "rp", @@ -533,12 +371,6 @@ "flags": ["minion"], "subOptions": [] }, - { - "name": "simulate", - "desc": "Simulate various OSRS related things.", - "examples": ["/simulate cox quantity:1"], - "subOptions": ["cox", "petroll", "colosseum"] - }, { "name": "slayer", "desc": "Slayer skill commands", @@ -585,11 +417,6 @@ "set_custom_image" ] }, - { - "name": "testershop", - "desc": "Buy things using your testing points.", - "subOptions": [] - }, { "name": "tokkulshop", "desc": "Buy or sell items from the Tzhaar shops.", @@ -602,17 +429,6 @@ "desc": "Various tools and miscellaneous commands.", "subOptions": ["patron", "user", "stash_units"] }, - { - "name": "trade", - "desc": "Allows you to trade items with other players.", - "subOptions": [] - }, - { - "name": "trivia", - "desc": "Try to answer a random trivia question!", - "examples": ["/trivia", "/trivia duel:@Magnaboy"], - "subOptions": [] - }, { "name": "use", "desc": "Use items/things.", diff --git a/src/lib/data/buyables/keyCrateBuyables.ts b/src/lib/data/buyables/keyCrateBuyables.ts index 7d07ac77ca..dae9608831 100644 --- a/src/lib/data/buyables/keyCrateBuyables.ts +++ b/src/lib/data/buyables/keyCrateBuyables.ts @@ -6,7 +6,6 @@ export const keyCrateBuyables: Buyable[] = []; for (const crate of keyCrates) { keyCrateBuyables.push({ name: crate.key.name, - gpCost: crate.keyCostGP, - ironmanPrice: Math.floor(crate.keyCostGP / 10) + gpCost: crate.keyCostGP }); } diff --git a/src/lib/data/buyables/trekBuyables.ts b/src/lib/data/buyables/trekBuyables.ts index 9eecc44eaa..ab14c96a72 100644 --- a/src/lib/data/buyables/trekBuyables.ts +++ b/src/lib/data/buyables/trekBuyables.ts @@ -55,13 +55,6 @@ const TrekShopItems: TrekShopItem[] = [ medRange: [30, 65], hardRange: [60, 80], aliases: ['lobster', 'raw lobsters'] - }, - { - name: 'Experience', - easyRange: [1100, 1650], - medRange: [2035, 3025], - hardRange: [4015, 5005], - aliases: ['xp'] } ]; diff --git a/src/lib/data/createables.ts b/src/lib/data/createables.ts index f9fd0ce089..21953ec173 100644 --- a/src/lib/data/createables.ts +++ b/src/lib/data/createables.ts @@ -7,10 +7,10 @@ import type { MaterialBank } from '../invention/MaterialBank'; import { blisterwoodRequirements, ivandisRequirements } from '../minions/data/templeTrekking'; import type { SlayerTaskUnlocksEnum } from '../slayer/slayerUnlocks'; import type { ItemBank, Skills } from '../types'; +import { resolveItems } from '../util'; import getOSItem from '../util/getOSItem'; import itemID from '../util/itemID'; import { itemNameFromID } from '../util/smallUtils'; -import { chambersOfXericMetamorphPets, tobMetamorphPets } from './CollectionsExport'; import { amrodCreatables } from './creatables/amrod'; import { armorAndItemPacks } from './creatables/armorPacks'; import { BsoCreateables } from './creatables/bsoItems'; @@ -34,6 +34,8 @@ import { toaCreatables } from './creatables/toa'; import { tobCreatables } from './creatables/tob'; import { tameCreatables } from './tameCreatables'; +const chambersOfXericMetamorphPets = resolveItems(['Puppadile', 'Tektiny', 'Vanguard', 'Vasa minirio', 'Vespina']); +const tobMetamorphPets = resolveItems(["Lil' Maiden", "Lil' Bloat", "Lil' Nylo", "Lil' Sot", "Lil' Xarp"]); export interface Createable { name: string; outputItems: ItemBank | Bank | ((user: MUser) => Bank); diff --git a/src/lib/data/eatables.ts b/src/lib/data/eatables.ts index d77bd3de8f..d8cde239fd 100644 --- a/src/lib/data/eatables.ts +++ b/src/lib/data/eatables.ts @@ -1,3 +1,5 @@ +import { writeFileSync } from 'node:fs'; +import { getItem } from '../util'; import itemID from '../util/itemID'; export interface Eatable { @@ -8,8 +10,7 @@ export interface Eatable { pvmBoost?: number; wildyOnly?: boolean; } - -export const Eatables: readonly Eatable[] = [ +export const Eatables: Eatable[] = [ { name: 'Anchovies', id: itemID('Anchovies'), @@ -315,3 +316,222 @@ export const Eatables: readonly Eatable[] = [ pvmBoost: 8 } ]; + +const others = [ + ['Cup of tea', 3], + ['Bruised banana', 0], + ['Jangerberries', 2], + ['Giant carp', 6], + ['Edible seaweed', 4], + ['Karamjan rum', 5], + ['Cup of tea (nettle)', 3], + ['Ugthanki meat', 3], + ['Chopped tomato', 2], + ['Chopped onion', 1], + ['Onion & tomato', 3], + ['Ugthanki kebab (smelling)', 0], + ['Asgarnian ale', 1], + ["Wizard's mind bomb", 1], + ["Greenman's ale", 1], + ['Dragon bitter', 1], + ['Dwarven stout', 1], + ['Grog', 3], + ['Beer', 1], + ['Potato', 1], + ['Onion', 1], + ['Pumpkin', 14], + ['Easter egg', 14], + ['Banana', 2], + ['Cabbage', 1], + ['Spinach roll', 2], + ['Chocolate bar', 3], + ['Chocolatey milk', 4], + ['Tomato', 2], + ['Cheese', 2], + ['Half full wine jug', 7], + ['Vodka', 5], + ['Whisky', 5], + ['Gin', 5], + ['Brandy', 5], + ["Premade blurb' sp.", 7], + ["Premade choc s'dy", 5], + ["Premade dr' dragon", 5], + ["Premade fr' blast", 9], + ["Premade p' punch", 9], + ['Premade sgg', 5], + ["Premade wiz blz'd", 5], + ['Pineapple punch', 9], + ['Wizard blizzard', 5], + ['Blurberry special', 7], + ['Choc saturday', 5], + ['Short green guy', 5], + ['Fruit blast', 9], + ['Drunk dragon', 5], + ['Lemon', 2], + ['Lemon chunks', 2], + ['Lemon slices', 2], + ['Orange', 2], + ['Orange chunks', 2], + ['Orange slices', 2], + ['Pineapple chunks', 2], + ['Pineapple ring', 2], + ['Lime', 2], + ['Lime chunks', 2], + ['Lime slices', 2], + ['Dwellberries', 2], + ['Equa leaves', 1], + ['Pot of cream', 1], + ['Lava eel', 11], + ["Toad's legs", 3], + ['King worm', 2], + ['Chocolate bomb', 15], + ["Tangled toad's legs", 15], + ['Worm hole', 12], + ['Veg ball', 12], + ['Worm crunchies', 8], + ['Chocchip crunchies', 7], + ['Spicy crunchies', 7], + ['Toad crunchies', 8], + ["Premade w'm batta", 11], + ["Premade t'd batta", 11], + ['Premade c+t batta', 11], + ["Premade fr't batta", 11], + ['Premade veg batta', 11], + ['Premade choc bomb', 15], + ['Premade ttl', 15], + ['Premade worm hole', 12], + ['Premade veg ball', 12], + ["Premade w'm crun'", 8], + ["Premade ch' crunch", 8], + ["Premade s'y crunch", 7], + ["Premade t'd crunch", 8], + ['Worm batta', 11], + ['Toad batta', 11], + ['Cheese+tom batta', 11], + ['Fruit batta', 11], + ['Vegetable batta', 11], + ['Cooked oomlie wrap', 14], + ['Cooked chompy', 11], + ['Moonlight mead', 4], + ['Sliced banana', 2], + ['Cooked rabbit', 5], + ['Keg of beer', 15], + ['Beer tankard', 4], + ['Monkey nuts', 4], + ['Monkey bar', 5], + ['Banana stew', 11], + ['Nettle-water', 1], + ['Nettle tea', 3], + ['White pearl', 2], + ['Giant frog legs', 6], + ["Bandit's brew", 1], + ['Asgarnian ale(m)', 2], + ['Mature wmb', 2], + ["Greenman's ale(m)", 2], + ['Dragon bitter(m)', 2], + ['Dwarven stout(m)', 2], + ['Moonlight mead(m)', 6], + ["Axeman's folly", 1], + ["Axeman's folly(m)", 2], + ["Chef's delight", 1], + ["Chef's delight(m)", 2], + ["Slayer's respite", 1], + ["Slayer's respite(m)", 2], + ['Cider', 1], + ['Mature cider', 2], + ['Dwarven stout', 1], + ['Papaya fruit', 8], + ['Gout tuber', 12], + ['White tree fruit', 3], + ['Baked potato', 4], + ['Choc-ice', 7], + ['Peach', 8], + ['Baguette', 6], + ['Triangle sandwich', 6], + ['Roll', 6], + ['Square sandwich', 6], + ['Chilli con carne', 5], + ['Egg and tomato', 8], + ['Mushroom & onion', 11], + ['Tuna and corn', 13], + ['Minced meat', 13], + ['Spicy sauce', 2], + ['Scrambled egg', 2], + ['Fried mushrooms', 5], + ['Fried onions', 5], + ['Chopped tuna', 10], + ["Braindeath 'rum'", 14], + ['Roast rabbit', 7], + ['Spicy stew', 0], + ['Cooked fishcake', 11], + ['Cooked jubbly', 15], + ['Red banana', 5], + ['Tchiki monkey nuts', 5], + ['Sliced red banana', 5], + ['Stuffed snake', 20], + ['Bottle of wine', 14], + ['Field ration', 10], + ['Fresh monkfish', 1], + ['Locust meat', 3], + ['Roast bird meat', 6], + ['Roast beast meat', 8], + ['Spicy tomato', 2], + ['Spicy minced meat', 3], + ['Rainbow fish', 11], + ['Green gloop soup', 2], + ['Frogspawn gumbo', 2], + ['Frogburger', 2], + ["Coated frogs' legs", 2], + ['Bat shish', 2], + ['Fingers', 2], + ['Grubs à la mode', 2], + ['Roast frog', 2], + ['Mushrooms', 2], + ['Fillets', 2], + ['Loach', 3], + ['Eel sushi', 10], + ['Roe', 3], + ['Caviar', 5], + ['Pysk fish (0)', 5], + ['Suphi fish (1)', 8], + ['Leckish fish (2)', 11], + ['Brawk fish (3)', 14], + ['Mycil fish (4)', 17], + ['Roqed fish (5)', 20], + ['Kyren fish (6)', 23], + ['Guanic bat (0)', 5], + ['Prael bat (1)', 8], + ['Giral bat (2)', 11], + ['Phluxia bat (3)', 14], + ['Kryket bat (4)', 17], + ['Murng bat (5)', 20], + ['Psykk bat (6)', 23], + ['Honey locust', 20], + ['Silk dressing (2)', 100], + ['Bloody bracer', 2], + ['Dragonfruit', 10], + ['Paddlefish', 20], + ['Elven dawn', 1], + ['Cooked mystery meat', 5], + ['Steak sandwich', 6], + ['Corrupted paddlefish', 16], + ['Crystal paddlefish', 16], + ["Kovac's grog", 1] +]; + +for (const [name, healAmount] of others) { + if (Eatables.some(i => i.name === name)) continue; + if (!getItem(name)) continue; + Eatables.push({ + name: name as string, + id: itemID(name as string), + healAmount: healAmount as number, + raw: null + }); +} + +let str = ''; +for (const a of Eatables) { + str += `${a.name} heals ${a.healAmount}\n`; +} +writeFileSync('food.txt', str); diff --git a/src/lib/data/filterables.ts b/src/lib/data/filterables.ts index 56bfadc159..367efd7558 100644 --- a/src/lib/data/filterables.ts +++ b/src/lib/data/filterables.ts @@ -5,7 +5,6 @@ import { HardClueTable } from 'oldschooljs/dist/simulation/clues/Hard'; import { MasterClueTable } from 'oldschooljs/dist/simulation/clues/Master'; import { MediumClueTable } from 'oldschooljs/dist/simulation/clues/Medium'; -import { tmbTable, umbTable } from '../bsoOpenables'; import { customItems } from '../customItems/util'; import { type DisassembleFlag, disassembleFlagMaterials, materialTypes } from '../invention'; import { DisassemblySourceGroups } from '../invention/groups'; @@ -21,7 +20,6 @@ import PotionsMixable from '../skilling/skills/herblore/mixables/potions'; import unfinishedPotions from '../skilling/skills/herblore/mixables/unfinishedPotions'; import itemID from '../util/itemID'; import resolveItems from '../util/resolveItems'; -import { XPLamps } from '../xpLamps'; import { allCollectionLogs } from './Collections'; import { allClueItems, @@ -949,16 +947,6 @@ export const baseFilters: Filterable[] = [ aliases: ['clues rares', 'clues rare', 'rare clues', 'clue rare', 'rare clue'], items: () => [...new Set([...cluesHardRareCL, ...cluesEliteRareCL, ...cluesMasterRareCL])] }, - { - name: 'umb', - aliases: ['umb'], - items: () => umbTable - }, - { - name: 'tmb', - aliases: ['tmb'], - items: () => tmbTable - }, { name: 'Pets', aliases: ['pets', 'pmb'], @@ -1014,11 +1002,6 @@ export const baseFilters: Filterable[] = [ aliases: ['fruit'], items: () => monkeyEatables.map(i => i.item.id) }, - { - name: 'Lamps', - aliases: ['lamps'], - items: () => XPLamps.map(i => i.itemID) - }, { name: 'Favourite Alchs', aliases: ['favourite alchs', 'favalchs'], diff --git a/src/lib/degradeableItems.ts b/src/lib/degradeableItems.ts index 951569d290..220552e84e 100644 --- a/src/lib/degradeableItems.ts +++ b/src/lib/degradeableItems.ts @@ -9,7 +9,6 @@ import type { ChargeBank } from './structures/Banks'; import { assert } from './util'; import getOSItem from './util/getOSItem'; import itemID from './util/itemID'; -import { updateBankSetting } from './util/updateBankSetting'; export interface DegradeableItem { item: Item; @@ -375,9 +374,6 @@ export async function degradeItem({ await user.update({ [degItem.settingsKey]: 0 }); - const itemsDeleted = new Bank().add(item.id); - - updateBankSetting('degraded_items_cost', itemsDeleted); if (hasEquipped) { // Get the users equipped item. diff --git a/src/lib/doubleLoot.ts b/src/lib/doubleLoot.ts index 5b24472b96..1dff139555 100644 --- a/src/lib/doubleLoot.ts +++ b/src/lib/doubleLoot.ts @@ -1,7 +1,6 @@ import type { TextChannel } from 'discord.js'; -import { Time } from 'e'; -import { Channel } from './constants'; +import { globalConfig } from './constants'; import { formatDuration } from './util'; import { mahojiClientSettingsFetch, mahojiClientSettingsUpdate } from './util/clientSettings'; @@ -12,9 +11,7 @@ export function isDoubleLootActive(duration = 0) { } export async function addToDoubleLootTimer(amount: number, reason: string) { - const clientSettings = await mahojiClientSettingsFetch({ - double_loot_finish_time: true - }); + const clientSettings = await mahojiClientSettingsFetch(); let current = Number(clientSettings.double_loot_finish_time); if (current < Date.now()) { current = Date.now(); @@ -24,7 +21,7 @@ export async function addToDoubleLootTimer(amount: number, reason: string) { double_loot_finish_time: newDoubleLootTimer }); DOUBLE_LOOT_FINISH_TIME_CACHE = newDoubleLootTimer; - (globalClient.channels.cache.get(Channel.BSOGeneral)! as TextChannel).send({ + (globalClient.channels.cache.get(globalConfig.generalChannelID)! as TextChannel).send({ content: `<@&923768318442229792> 🎉 ${formatDuration( amount )} added to the Double Loot timer because: ${reason}. 🎉`, @@ -34,25 +31,8 @@ export async function addToDoubleLootTimer(amount: number, reason: string) { syncPrescence(); } -export async function addPatronLootTime(_tier: number, user: MUser | null) { - const map: Record = { - 1: 3, - 2: 6, - 3: 15, - 4: 25, - 5: 60 - }; - const tier = _tier - 1; - if (!map[tier]) return; - const minutes = map[tier]; - const timeAdded = Math.floor(Time.Minute * minutes); - addToDoubleLootTimer(timeAdded, `${user ?? 'Someone'} became a Tier ${tier} sponsor`); -} - export async function syncDoubleLoot() { - const clientSettings = await mahojiClientSettingsFetch({ - double_loot_finish_time: true - }); + const clientSettings = await mahojiClientSettingsFetch(); DOUBLE_LOOT_FINISH_TIME_CACHE = Number(clientSettings.double_loot_finish_time); } diff --git a/src/lib/economyLogs.ts b/src/lib/economyLogs.ts deleted file mode 100644 index b364fc0d21..0000000000 --- a/src/lib/economyLogs.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Channel } from './constants'; -import { sendToChannelID } from './util/webhook'; - -let economyLogBuffer: string[] = []; - -export async function economyLog(message: string, flush = false) { - economyLogBuffer.push(message); - if ((flush && economyLogBuffer.length !== 1) || economyLogBuffer.length === 10) { - await sendToChannelID(Channel.EconomyLogs, { - content: economyLogBuffer.join('\n---------------------------------\n'), - allowedMentions: { parse: [], users: [], roles: [] } - }); - economyLogBuffer = []; - } -} diff --git a/src/lib/events.ts b/src/lib/events.ts index 7434c77e62..9f4405c2d6 100644 --- a/src/lib/events.ts +++ b/src/lib/events.ts @@ -1,24 +1,13 @@ import { channelIsSendable, mentionCommand } from '@oldschoolgg/toolkit'; import { UserError } from '@oldschoolgg/toolkit'; -import type { BaseMessageOptions, Message } from 'discord.js'; -import { ButtonBuilder, ButtonStyle, EmbedBuilder, bold, time } from 'discord.js'; +import { type BaseMessageOptions, type Message, bold, time } from 'discord.js'; import { Time, isFunction } from 'e'; -import { Items } from 'oldschooljs'; -import { Cooldowns } from '../mahoji/lib/Cooldowns'; - -import { command_name_enum } from '@prisma/client'; import { minionStatusCommand } from '../mahoji/lib/abstracted_commands/minionStatusCommand'; -import { giveBoxResetTime, itemContractResetTime, spawnLampResetTime } from './MUser'; -import { boxSpawnHandler } from './boxSpawns'; import { getGuthixianCacheInterval, userHasDoneCurrentGuthixianCache } from './bso/guthixianCache'; -import { IronmanPMBTable, itemSearchMbTable } from './bsoOpenables'; -import { BitField, Emoji, globalConfig } from './constants'; -import { customItems } from './customItems/util'; +import { Emoji, globalConfig } from './constants'; import { DOUBLE_LOOT_FINISH_TIME_CACHE, isDoubleLootActive } from './doubleLoot'; -import { PATRON_DOUBLE_LOOT_COOLDOWN } from '../mahoji/commands/tools'; -import type { ItemBank } from './types'; -import { formatDuration, makeComponents, toKMB } from './util'; +import { formatDuration, toKMB } from './util'; import { logError } from './util/logError'; import { makeBankImage } from './util/makeBankImage'; import { minionStatsEmbed } from './util/minionStatsEmbed'; @@ -44,36 +33,6 @@ const cooldownTimers: { cd: Time.Hour * 12, command: ['minion', 'daily'] }, - { - name: 'Spawn Lamp', - timeStamp: (user: MUser) => Number(user.user.lastSpawnLamp), - cd: (user: MUser) => spawnLampResetTime(user), - command: ['tools', 'patron', 'spawnlamp'] - }, - { - name: 'Spawn Box', - timeStamp: (user: MUser) => Cooldowns.cooldownMap.get(user.id)?.get('SPAWN_BOX') ?? 0, - cd: Time.Minute * 45, - command: ['tools', 'patron', 'spawnbox'] - }, - { - name: 'Give Box', - timeStamp: (user: MUser) => Number(user.user.lastGivenBoxx), - cd: giveBoxResetTime, - command: ['tools', 'patron', 'give_box'] - }, - { - name: 'Item Contract', - timeStamp: (user: MUser) => Number(user.user.last_item_contract_date), - cd: itemContractResetTime, - command: ['ic', 'info'] - }, - { - name: 'Monthly Double Loot', - timeStamp: (user: MUser) => Number(user.user.last_patron_double_time_trigger), - cd: PATRON_DOUBLE_LOOT_COOLDOWN, - command: ['tools', 'patron', 'doubleloot'] - }, { name: 'Balthazars Big Bonanza', timeStamp: (user: MUser) => Number(user.user.last_bonanza_date), @@ -89,7 +48,7 @@ interface MentionCommandOptions { content: string; } interface MentionCommand { - name: command_name_enum; + name: string; aliases: string[]; description: string; run: (options: MentionCommandOptions) => Promise; @@ -97,7 +56,7 @@ interface MentionCommand { const mentionCommands: MentionCommand[] = [ { - name: command_name_enum.bs, + name: 'bs', aliases: ['bs'], description: 'Searches your bank.', run: async ({ msg, user, components, content }: MentionCommandOptions) => { @@ -116,7 +75,7 @@ const mentionCommands: MentionCommand[] = [ } }, { - name: command_name_enum.bal, + name: 'bal', aliases: ['bal', 'gp'], description: 'Shows how much GP you have.', run: async ({ msg, user, components }: MentionCommandOptions) => { @@ -127,57 +86,7 @@ const mentionCommands: MentionCommand[] = [ } }, { - name: command_name_enum.is, - aliases: ['is'], - description: 'Searches for items.', - run: async ({ msg, components, user, content }: MentionCommandOptions) => { - const items = Items.filter( - i => - [i.id.toString(), i.name.toLowerCase()].includes(content.toLowerCase()) && - !i.customItemData?.isSecret - ).array(); - if (items.length === 0) return msg.reply('No results for that item.'); - - const gettedItem = items[0]; - const { sacrificed_bank: sacrificedBank } = await user.fetchStats({ sacrificed_bank: true }); - - let str = `Found ${items.length} items:\n${items - .slice(0, 5) - .map((item, index) => { - const icons = []; - - if (user.cl.has(item.id)) icons.push(Emoji.CollectionLog); - if (user.bank.has(item.id)) icons.push(Emoji.Bank); - const isCustom = customItems.includes(item.id); - if (isCustom) icons.push(Emoji.BSO); - if (((sacrificedBank as ItemBank)[item.id] ?? 0) > 0) icons.push(Emoji.Incinerator); - - if (user.isIronman) { - itemSearchMbTable.push(...IronmanPMBTable.allItems); - } - - const price = toKMB(Math.floor(item.price)); - const wikiURL = isCustom ? '' : `[Wiki Page](${item.wiki_url}) `; - let str = `${index + 1}. ${item.name} ID[${item.id}] Price[${price}] ${ - itemSearchMbTable.includes(item.id) ? Emoji.MysteryBox : '' - } ${wikiURL}${icons.join(' ')}`; - if (gettedItem.id === item.id) { - str = bold(str); - } - - return str; - }) - .join('\n')}`; - - if (items.length > 5) { - str += `\n...and ${items.length - 5} others`; - } - - return msg.reply({ embeds: [new EmbedBuilder().setDescription(str)], components }); - } - }, - { - name: command_name_enum.bank, + name: 'bank', aliases: ['b', 'bank'], description: 'Shows your bank.', run: async ({ msg, user, components }: MentionCommandOptions) => { @@ -199,7 +108,7 @@ const mentionCommands: MentionCommand[] = [ } }, { - name: command_name_enum.cd, + name: 'cd', aliases: ['cd'], description: 'Shows your cooldowns.', run: async ({ msg, user, components }: MentionCommandOptions) => { @@ -240,32 +149,7 @@ const mentionCommands: MentionCommand[] = [ } }, { - name: command_name_enum.sendtoabutton, - aliases: ['sendtoabutton'], - description: 'Shows your stats.', - run: async ({ msg, user }: MentionCommandOptions) => { - if ([BitField.isModerator].every(bit => !user.bitfield.includes(bit))) { - return; - } - return msg.reply({ - content: `Click this button to find out if you're ready to do Tombs of Amascut! You can also use the ${mentionCommand( - globalClient, - 'raid', - 'toa', - 'help' - )} command.`, - components: makeComponents([ - new ButtonBuilder() - .setStyle(ButtonStyle.Primary) - .setCustomId('TOA_CHECK') - .setLabel('Check TOA Requirements') - .setEmoji('1069174271894638652') - ]) - }); - } - }, - { - name: command_name_enum.stats, + name: 's', aliases: ['s', 'stats'], description: 'Shows your stats.', run: async ({ msg, user, components }: MentionCommandOptions) => { @@ -278,12 +162,11 @@ const mentionCommands: MentionCommand[] = [ ]; export async function onMessage(msg: Message) { - boxSpawnHandler(msg); if (!msg.content || msg.author.bot || !channelIsSendable(msg.channel)) return; const content = msg.content.trim(); if (!content.includes(mentionText)) return; const user = await mUserFetch(msg.author.id); - const result = await minionStatusCommand(user, msg.channelId); + const result = await minionStatusCommand(user); const { components } = result; const command = mentionCommands.find(i => diff --git a/src/lib/finishables.ts b/src/lib/finishables.ts deleted file mode 100644 index 3e729cca57..0000000000 --- a/src/lib/finishables.ts +++ /dev/null @@ -1,350 +0,0 @@ -import { stringMatches } from '@oldschoolgg/toolkit'; -import { notEmpty, randArrItem, roll } from 'e'; -import { Bank, Monsters } from 'oldschooljs'; -import BeginnerClueTable from 'oldschooljs/dist/simulation/clues/Beginner'; -import EasyClueTable from 'oldschooljs/dist/simulation/clues/Easy'; -import EliteClueTable from 'oldschooljs/dist/simulation/clues/Elite'; -import HardClueTable from 'oldschooljs/dist/simulation/clues/Hard'; -import MasterCasket from 'oldschooljs/dist/simulation/clues/Master'; -import MediumClueTable from 'oldschooljs/dist/simulation/clues/Medium'; -import { ChambersOfXeric, Nightmare } from 'oldschooljs/dist/simulation/misc'; -import { EliteMimicTable, MasterMimicTable } from 'oldschooljs/dist/simulation/misc/Mimic'; - -import { resolveItems } from 'oldschooljs/dist/util/util'; -import { rollNaxxusLoot } from './bso/naxxus/rollNaxxusLoot'; -import { allCollectionLogsFlat } from './data/Collections'; -import { - chambersOfXericCL, - chambersOfXericNormalCL, - cluesBeginnerCL, - cluesEasyCL, - cluesEliteCL, - cluesHardCL, - cluesMasterCL, - cluesMediumCL, - evilChickenOutfit, - moktangCL, - naxxusCL, - nexCL, - nexUniqueDrops, - temporossCL, - theGauntletCL, - theNightmareCL, - theNightmareNormalCL, - theatreOfBloodCapes, - theatreOfBloodHardUniques, - theatreOfBloodNormalUniques, - wintertodtCL -} from './data/CollectionsExport'; -import pets from './data/pets'; -import killableMonsters from './minions/data/killableMonsters'; -import { MoktangLootTable } from './minions/data/killableMonsters/custom/bosses/Moktang'; -import { Naxxus } from './minions/data/killableMonsters/custom/bosses/Naxxus'; -import { BSOMonsters } from './minions/data/killableMonsters/custom/customMonsters'; -import { NEX_UNIQUE_DROPRATE, NexMonster } from './nex'; -import { openShadeChest } from './shadesKeys'; -import { birdsNestID, treeSeedsNest } from './simulation/birdsNest'; -import { gauntlet } from './simulation/gauntlet'; -import { getTemporossLoot } from './simulation/tempoross'; -import { TheatreOfBlood } from './simulation/tob'; -import { WintertodtCrate } from './simulation/wintertodt'; -import { calculateTripConsumableCost } from './util/calculateTripConsumableCost'; -import getOSItem from './util/getOSItem'; -import itemID from './util/itemID'; - -interface KillArgs { - accumulatedLoot: Bank; - totalRuns: number; -} - -interface Finishable { - name: string; - aliases?: string[]; - cl: number[]; - kill: (args: KillArgs) => Bank | { cost: Bank; loot: Bank }; - customResponse?: (kc: number) => string; - maxAttempts?: number; - tertiaryDrops?: { itemId: number; kcNeeded: number }[]; -} - -export const finishables: Finishable[] = [ - { - name: 'Chambers of Xeric (Solo, Non-CM)', - aliases: ['cox', 'raid1', 'raids1', 'chambers', 'xeric'], - cl: chambersOfXericNormalCL, - kill: () => ChambersOfXeric.complete({ team: [{ id: '1', personalPoints: 25_000 }] })['1'] - }, - { - name: 'Chambers of Xeric (Solo, CM)', - aliases: ['coxcm', 'raid1cm', 'raids1cm', 'chamberscm', 'xericcm'], - cl: chambersOfXericCL, - kill: () => - ChambersOfXeric.complete({ - challengeMode: true, - team: [{ id: '1', personalPoints: 25_000, canReceiveAncientTablet: true, canReceiveDust: true }], - timeToComplete: 1 - })['1'], - tertiaryDrops: [ - { itemId: itemID("Xeric's guard"), kcNeeded: 100 }, - { itemId: itemID("Xeric's warrior"), kcNeeded: 500 }, - { itemId: itemID("Xeric's sentinel"), kcNeeded: 1000 }, - { itemId: itemID("Xeric's general"), kcNeeded: 1500 }, - { itemId: itemID("Xeric's champion"), kcNeeded: 2000 } - ] - }, - { - name: 'Theatre of Blood (Solo, Non-HM)', - aliases: ['tob', 'theatre of blood'], - cl: [...theatreOfBloodNormalUniques, ...theatreOfBloodCapes], - kill: () => new Bank(TheatreOfBlood.complete({ hardMode: false, team: [{ id: '1', deaths: [] }] }).loot['1']), - tertiaryDrops: [ - { itemId: itemID('Sinhaza shroud tier 1'), kcNeeded: 100 }, - { itemId: itemID('Sinhaza shroud tier 2'), kcNeeded: 500 }, - { itemId: itemID('Sinhaza shroud tier 3'), kcNeeded: 1000 }, - { itemId: itemID('Sinhaza shroud tier 4'), kcNeeded: 1500 }, - { itemId: itemID('Sinhaza shroud tier 5'), kcNeeded: 2000 } - ] - }, - { - name: 'Theatre of Blood (Solo, HM)', - aliases: ['tob hard', 'tob hard mode', 'tobhm'], - cl: [...theatreOfBloodNormalUniques, ...theatreOfBloodCapes, ...theatreOfBloodHardUniques], - kill: () => new Bank(TheatreOfBlood.complete({ hardMode: true, team: [{ id: '1', deaths: [] }] }).loot['1']), - tertiaryDrops: [ - { itemId: itemID('Sinhaza shroud tier 1'), kcNeeded: 100 }, - { itemId: itemID('Sinhaza shroud tier 2'), kcNeeded: 500 }, - { itemId: itemID('Sinhaza shroud tier 3'), kcNeeded: 1000 }, - { itemId: itemID('Sinhaza shroud tier 4'), kcNeeded: 1500 }, - { itemId: itemID('Sinhaza shroud tier 5'), kcNeeded: 2000 } - ] - }, - { - name: 'The Nightmare', - aliases: ['nightmare'], - cl: theNightmareNormalCL, - kill: () => new Bank(Nightmare.kill({ isPhosani: false, team: [{ id: '1', damageDone: 2400 }] })['1']) - }, - { - name: "Phosani's Nightmare", - aliases: ['phosani'], - cl: theNightmareCL, - kill: () => new Bank(Nightmare.kill({ isPhosani: true, team: [{ id: '1', damageDone: 2400 }] })['1']) - }, - { - name: 'Gauntlet', - aliases: ['gauntlet', 'gaunt', 'ng'], - cl: theGauntletCL.filter(i => i !== itemID('Gauntlet cape')), - kill: () => gauntlet({ died: false, type: 'normal' }) - }, - { - name: 'Corrupted Gauntlet', - aliases: ['cgauntlet', 'corruptedg', 'corruptg', 'cg'], - cl: theGauntletCL, - kill: () => gauntlet({ died: false, type: 'corrupted' }), - tertiaryDrops: [{ itemId: itemID('Gauntlet cape'), kcNeeded: 1 }] - }, - { - name: 'Wintertodt (500pt crates, Max stats)', - cl: wintertodtCL, - aliases: ['todt', 'wintertodt', 'wt'], - kill: ({ accumulatedLoot }) => { - const loot = new Bank( - WintertodtCrate.open({ - points: 500, - itemsOwned: accumulatedLoot.bank, - skills: { - herblore: 99, - firemaking: 99, - woodcutting: 99, - fishing: 99, - mining: 99, - crafting: 99, - farming: 99 - }, - firemakingXP: 1000 - }) - ); - // Wintertoad: Assume 1 game per 5 minutes, Wintertoad rate is 1 in 3k minutes - if (roll(600)) loot.add('Wintertoad'); - return loot; - } - }, - { - name: Naxxus.name, - cl: naxxusCL, - aliases: Naxxus.aliases, - kill: ({ accumulatedLoot }) => rollNaxxusLoot(1, accumulatedLoot) - }, - { - name: 'Nex', - cl: nexCL.filter(i => i !== itemID('Frozen key')), - kill: () => { - const loot = new Bank(NexMonster.table.kill(1, {})); - if (roll(NEX_UNIQUE_DROPRATE(1))) { - loot.add(randArrItem(nexUniqueDrops), 1); - } - return loot; - } - }, - { - name: 'Moktang', - cl: moktangCL, - kill: () => MoktangLootTable.roll() - }, - { - name: 'Tempoross', - cl: temporossCL, - aliases: ['temp', 'ross', 'tempo', 'watertodt'], - kill: () => getTemporossLoot(1, 99, new Bank()), - tertiaryDrops: [ - { itemId: itemID('Spirit angler top'), kcNeeded: 65 }, - { itemId: itemID('Spirit angler waders'), kcNeeded: 65 }, - { itemId: itemID('Spirit angler headband'), kcNeeded: 65 }, - { itemId: itemID('Spirit angler boots'), kcNeeded: 65 } - ] - }, - { - name: 'Beginner Clue Scolls', - cl: cluesBeginnerCL, - aliases: ['beginner clues', 'beginner clue', 'beginner clue scroll', 'beginner clue scrolls'], - kill: () => BeginnerClueTable.open() - }, - { - name: 'Easy Clue Scolls', - cl: cluesEasyCL, - aliases: ['easy clues', 'easy clue', 'easy clue scroll', 'easy clue scrolls'], - kill: () => EasyClueTable.open() - }, - { - name: 'Medium Clue Scolls', - cl: cluesMediumCL, - aliases: ['medium clues', 'medium clue', 'medium clue scroll', 'medium clue scrolls'], - kill: () => MediumClueTable.open() - }, - { - name: 'Hard Clue Scolls', - cl: cluesHardCL, - aliases: ['hard clues', 'hard clue', 'hard clue scroll', 'hard clue scrolls'], - kill: () => HardClueTable.open() - }, - { - name: 'Elite Clue Scolls', - cl: cluesEliteCL, - aliases: ['elite clues', 'elite clue', 'elite clue scroll', 'elite clue scrolls'], - kill: () => { - if (roll(35)) { - return EliteMimicTable.roll().add(EliteClueTable.open()); - } - return EliteClueTable.open(); - } - }, - { - name: 'Master Clue Scolls', - cl: cluesMasterCL, - aliases: ['master clues', 'master clue', 'master clue scroll', 'master clue scrolls'], - kill: () => { - if (roll(15)) { - return MasterMimicTable.roll().add(MasterCasket.open()); - } - return MasterCasket.open(); - } - }, - { - name: 'Evil Chicken Outfit', - cl: evilChickenOutfit, - aliases: ['evil chicken outfit'], - kill: () => { - const loot = new Bank(); - if (roll(300)) { - loot.add(randArrItem(evilChickenOutfit)); - } else { - loot.add(birdsNestID); - loot.add(treeSeedsNest.roll()); - } - return loot; - } - }, - { - name: 'Shades of Morton (Gold Keys)', - cl: resolveItems([ - 'Amulet of the damned (full)', - 'Flamtaer bag', - 'Fine cloth', - 'Bronze locks', - 'Steel locks', - 'Black locks', - 'Silver locks', - 'Gold locks', - "Zealot's helm", - "Zealot's robe top", - "Zealot's robe bottom", - "Zealot's boots", - "Tree wizards' journal", - 'Bloody notes' - ]), - aliases: ['shades of morton'], - kill: ({ accumulatedLoot, totalRuns }) => { - for (const tier of ['Bronze', 'Steel', 'Black', 'Silver', 'Gold'] as const) { - const key = getOSItem(`${tier} key red`); - const lock = getOSItem(`${tier} locks`); - if (accumulatedLoot.has(lock.id) && tier !== 'Gold') continue; - return openShadeChest({ item: key, allItemsOwned: accumulatedLoot, qty: totalRuns }).bank; - } - throw new Error('Not possible!'); - } - } -]; - -const monsterPairedCLs = [...Monsters.values(), ...Object.values(BSOMonsters)] - .map(mon => { - const cl = allCollectionLogsFlat.find(c => stringMatches(c.name, mon.name)); - if (!cl) return null; - if (!('allItems' in mon) || !mon.allItems) return; - if (!cl.items.every(id => mon.allItems!.includes(id))) return null; - if (finishables.some(f => stringMatches(f.name, mon.name))) return null; - return { - name: mon.name, - aliases: mon.aliases, - cl: cl.items, - mon - }; - }) - .filter(notEmpty); - -for (const mon of monsterPairedCLs) { - const killableMonster = killableMonsters.find(m => m.id === mon.mon.id); - finishables.push({ - name: mon.name, - aliases: mon.aliases, - cl: mon.cl, - kill: ({ accumulatedLoot }) => { - const cost = new Bank(); - if (killableMonster?.healAmountNeeded) { - cost.add('Swordfish', Math.ceil(killableMonster.healAmountNeeded / 14)); - } - if (killableMonster?.itemCost) { - cost.add(calculateTripConsumableCost(killableMonster.itemCost, 1, killableMonster.timeToFinish)); - } - - const loot = 'kill' in mon.mon ? mon.mon.kill(1, {}) : mon.mon.table.roll(); - if (killableMonster?.specialLoot) { - killableMonster.specialLoot({ ownedItems: accumulatedLoot, loot, quantity: 1, cl: accumulatedLoot }); - } - return { loot, cost }; - } - }); -} - -for (const pet of pets) { - finishables.push({ - name: `${pet.name} Pet`, - cl: [itemID(pet.name)], - maxAttempts: 1_000_000, - kill: () => { - const bank = new Bank(); - if (roll(pet.chance)) bank.add(itemID(pet.name)); - return bank; - }, - customResponse: kc => pet.formatFinish(kc) - }); -} diff --git a/src/lib/geImage.ts b/src/lib/geImage.ts deleted file mode 100644 index fb0e5224bd..0000000000 --- a/src/lib/geImage.ts +++ /dev/null @@ -1,239 +0,0 @@ -import * as fs from 'node:fs/promises'; -import type { Image, SKRSContext2D } from '@napi-rs/canvas'; -import { Canvas, loadImage } from '@napi-rs/canvas'; -import { formatItemStackQuantity, generateHexColorForCashStack, toTitleCase } from '@oldschoolgg/toolkit'; - -import type { GEListing, GETransaction } from '@prisma/client'; -import type { GEListingWithTransactions } from './../mahoji/commands/ge'; -import { GrandExchange } from './grandExchange'; -import { fillTextXTimesInCtx } from './util/canvasUtil'; -import getOSItem from './util/getOSItem'; - -function drawTitle(ctx: SKRSContext2D, title: string, canvas: Canvas) { - // Draw Page Title - ctx.font = '16px RuneScape Bold 12'; - const titleWidthPx = ctx.measureText(title); - const titleX = Math.floor(Math.floor(canvas.width) * 0.95 - titleWidthPx.width); - ctx.fillStyle = '#000000'; - fillTextXTimesInCtx(ctx, title, titleX + 1, 22); - - ctx.fillStyle = '#ff981f'; - fillTextXTimesInCtx(ctx, title, titleX, 21); -} - -class GeImageTask { - public geInterface: Image | null = null; - public geSlotLocked: Image | null = null; - public geSlotOpen: Image | null = null; - public geSlotActive: Image | null = null; - public geProgressShadow: Image | null = null; - public geIconBuy: Image | null = null; - public geIconSell: Image | null = null; - - async init() { - await this.prepare(); - } - - async prepare() { - this.geInterface = await loadImage( - await fs.readFile('./src/lib/resources/images/grandexchange/ge_interface.png') - ); - this.geSlotLocked = await loadImage( - await fs.readFile('./src/lib/resources/images/grandexchange/ge_slot_locked.png') - ); - this.geSlotOpen = await loadImage( - await fs.readFile('./src/lib/resources/images/grandexchange/ge_slot_open.png') - ); - this.geSlotActive = await loadImage( - await fs.readFile('./src/lib/resources/images/grandexchange/ge_slot_active.png') - ); - this.geProgressShadow = await loadImage( - await fs.readFile('./src/lib/resources/images/grandexchange/ge_progress_shadow.png') - ); - this.geIconBuy = await loadImage( - await fs.readFile('./src/lib/resources/images/grandexchange/ge_buy_mini_icon.png') - ); - this.geIconSell = await loadImage( - await fs.readFile('./src/lib/resources/images/grandexchange/ge_sell_mini_icon.png') - ); - } - - drawText(ctx: SKRSContext2D, text: string, x: number, y: number, maxWidth: number | undefined, lineHeight: number) { - // If max width is set, we have to line break the text - const textLines = []; - const measuredText = ctx.measureText(text); - if (maxWidth && measuredText.width > maxWidth) { - const explodedText = text.split(' '); - let newTextLine = ''; - for (const word of explodedText) { - if (ctx.measureText(`${newTextLine} ${word}`).width >= maxWidth) { - textLines.push(newTextLine); - newTextLine = word; - } else { - newTextLine += ` ${word}`; - } - } - textLines.push(newTextLine); - } - for (const [index, textLine] of (textLines.length > 0 ? textLines : [text]).entries()) { - const textColor = ctx.fillStyle === '#000000' ? '#ff981f' : ctx.fillStyle; - ctx.fillStyle = '#000000'; - fillTextXTimesInCtx(ctx, textLine.trim(), x + 1, y + lineHeight * index + 1); - ctx.fillStyle = textColor; - fillTextXTimesInCtx(ctx, textLine.trim(), x, y + lineHeight * index); - } - } - - async getSlotImage( - ctx: SKRSContext2D, - slot: number, - locked: boolean, - listing: GEListingWithTransactions | undefined - ) { - const slotImage = listing ? this.geSlotActive! : locked ? this.geSlotLocked! : this.geSlotOpen!; - ctx.drawImage(slotImage, 0, 0, slotImage.width, slotImage.height); - - // Draw Bank Title - ctx.textAlign = 'center'; - ctx.font = '16px RuneScape Bold 12'; - const type = listing ? ` - ${toTitleCase(listing.type.toString())}` : ' - Empty'; - this.drawText( - ctx, - locked ? 'Locked' : `Slot ${slot}${type}`, - Math.floor(slotImage.width / 2), - 17, - undefined, - 10 - ); - - if (listing) { - // Get item - const itemImage = await bankImageGenerator.getItemImage(listing.item_id); - - // Draw item - ctx.textAlign = 'left'; - ctx.font = '16px OSRSFontCompact'; - ctx.save(); - - ctx.translate(8, 34); - ctx.drawImage( - itemImage, - Math.floor((32 - itemImage?.width) / 2) + 2, - Math.floor((32 - itemImage?.height) / 2), - itemImage?.width, - itemImage?.height - ); - if (listing.total_quantity > 1) { - const formattedQuantity = formatItemStackQuantity(listing.total_quantity); - ctx.fillStyle = generateHexColorForCashStack(listing.total_quantity); - this.drawText(ctx, formattedQuantity, 0, 9, undefined, 10); - } - // Draw item name - ctx.translate(39, 11); - const itemName = getOSItem(listing.item_id).name; - ctx.fillStyle = '#FFB83F'; - ctx.font = '16px OSRSFontCompact'; - this.drawText(ctx, itemName, 0, 0, ctx.measureText('Elysian spirit').width, 10); - ctx.restore(); - - ctx.save(); - // Draw item value of the transaction - ctx.translate(0, 87); - ctx.font = '16px OSRSFontCompact'; - ctx.textAlign = 'center'; - - ctx.fillStyle = '#ff981f'; - this.drawText( - ctx, - `${Number(listing.asking_price_per_item).toLocaleString()} coins`, - Math.floor(this.geSlotOpen!.width / 2) + 1, - 17, - undefined, - 10 - ); - ctx.restore(); - // Draw progress bar - ctx.save(); - - const progressShadowImage = this.geProgressShadow!; - - ctx.translate(5, 75); - - const maxWidth = progressShadowImage.width; - ctx.fillStyle = '#ff981f'; - let progressWidth = 0; - if (listing.quantity_remaining > 0) { - progressWidth = Math.floor( - (maxWidth * (listing.total_quantity - listing.quantity_remaining)) / listing.total_quantity - ); - if (progressWidth === maxWidth) { - ctx.fillStyle = '#005F00'; - } - } else { - ctx.fillStyle = '#8F0000'; - progressWidth = maxWidth; - } - - ctx.fillRect(0, 0, progressWidth, progressShadowImage.height); - ctx.drawImage(progressShadowImage, 0, 0, progressShadowImage.width, progressShadowImage.height); - - ctx.restore(); - } - } - - async createInterface(opts: { - user: MUser; - page: number; - activeListings: (GEListing & { - buyTransactions: GETransaction[]; - sellTransactions: GETransaction[]; - })[]; - }): Promise { - let { user, page, activeListings } = opts; - const { slots, maxPossible } = await GrandExchange.calculateSlotsOfUser(user); - const canvasImage = this.geInterface!; - const canvas = new Canvas(canvasImage.width, canvasImage.height); - const ctx = canvas.getContext('2d'); - ctx.font = '16px OSRSFontCompact'; - ctx.imageSmoothingEnabled = false; - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(canvasImage, 0, 0, canvas.width, canvas.height); - - // Pages - const chunkSize = 8; - const totalChunks = Math.ceil(maxPossible / chunkSize); - if (page > totalChunks) page = totalChunks; - if (page >= 0) { - const pageTitle = `Page ${page} of ${totalChunks}`; - drawTitle(ctx, pageTitle, canvas); - } - - ctx.translate(9, 64); - let y = 0; - let x = 0; - for (let i = (page - 1) * chunkSize; i < maxPossible; i++) { - const listing: GEListingWithTransactions = activeListings[i]; - if (i > (page - 1) * chunkSize && i % 4 === 0) { - y += this.geSlotOpen!.height + 10; - x = 0; - } - ctx.save(); - ctx.translate(x * (this.geSlotOpen!.width + 2), y); - await this.getSlotImage(ctx, i + 1, i >= slots, listing); - ctx.restore(); - x++; - if (i > (page - 1) * chunkSize + 8) break; - } - const image = await canvas.encode('png'); - - return image; - } -} - -declare global { - var geImageGenerator: GeImageTask; -} - -global.geImageGenerator = new GeImageTask(); - -geImageGenerator.init(); diff --git a/src/lib/gear/functions/generateGearImage.ts b/src/lib/gear/functions/generateGearImage.ts index bec8624b40..2f2538fdc6 100644 --- a/src/lib/gear/functions/generateGearImage.ts +++ b/src/lib/gear/functions/generateGearImage.ts @@ -254,7 +254,7 @@ export async function generateGearImage( const hexColor = user.user.bank_bg_hex; const gearStats = gearSetup instanceof Gear ? gearSetup.stats : new Gear(gearSetup).stats; - const gearTemplateImage = await loadImage(user.gearTemplate.template); + const gearTemplateImage = await loadImage(gearImages[0].template); const canvas = new Canvas(gearTemplateImage.width, gearTemplateImage.height); const ctx = canvas.getContext('2d'); ctx.imageSmoothingEnabled = false; @@ -368,7 +368,7 @@ export async function generateAllGearImage(user: MUser) { } = bankImageGenerator.getBgAndSprite(user.user.bankBackground ?? 1, user); const hexColor = user.user.bank_bg_hex; - const gearTemplateImage = await loadImage(user.gearTemplate.templateCompact); + const gearTemplateImage = await loadImage(gearImages[0].templateCompact); const canvas = new Canvas((gearTemplateImage.width + 10) * 4 + 20, Number(gearTemplateImage.height) * 2 + 70); const ctx = canvas.getContext('2d'); ctx.imageSmoothingEnabled = false; diff --git a/src/lib/gear/index.ts b/src/lib/gear/index.ts index 606083b4d1..b083b686b1 100644 --- a/src/lib/gear/index.ts +++ b/src/lib/gear/index.ts @@ -1,7 +1,5 @@ -import type { GearPreset } from '@prisma/client'; import { EquipmentSlot } from 'oldschooljs/dist/meta/types'; -import itemID from '../util/itemID'; import { type DefenceGearStat, type GearSetup, GearStat, type OffenceGearStat, type OtherGearStat } from './types'; export * from './types'; @@ -45,232 +43,3 @@ export const defaultGear: GearSetup = { [EquipmentSlot.Weapon]: null }; Object.freeze(defaultGear); - -export const globalPresets: GearPreset[] = [ - { - name: 'graceful', - user_id: '123', - head: itemID('Graceful hood'), - neck: null, - body: itemID('Graceful top'), - legs: itemID('Graceful legs'), - cape: itemID('Graceful cape'), - two_handed: null, - hands: itemID('Graceful gloves'), - feet: itemID('Graceful boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - pinned_setup: null - }, - { - name: 'carpenter', - user_id: '123', - head: itemID("Carpenter's helmet"), - neck: null, - body: itemID("Carpenter's shirt"), - legs: itemID("Carpenter's trousers"), - cape: null, - two_handed: null, - hands: null, - feet: itemID("Carpenter's boots"), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - pinned_setup: null - }, - { - name: 'rogue', - user_id: '123', - head: itemID('Rogue mask'), - neck: null, - body: itemID('Rogue top'), - legs: itemID('Rogue trousers'), - cape: null, - two_handed: null, - hands: itemID('Rogue gloves'), - feet: itemID('Rogue boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - pinned_setup: null - }, - { - name: 'clue_hunter', - user_id: '123', - head: itemID('Helm of raedwald'), - neck: null, - body: itemID('Clue hunter garb'), - legs: itemID('Clue hunter trousers'), - cape: itemID('Clue hunter cloak'), - two_handed: null, - hands: itemID('Clue hunter gloves'), - feet: itemID('Clue hunter boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - pinned_setup: null - }, - { - name: 'angler', - user_id: '123', - head: itemID('Angler hat'), - neck: null, - body: itemID('Angler top'), - legs: itemID('Angler waders'), - cape: null, - two_handed: null, - hands: null, - feet: itemID('Angler boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - pinned_setup: null - }, - { - name: 'spirit_angler', - user_id: '123', - head: itemID('Spirit angler headband'), - neck: null, - body: itemID('Spirit angler top'), - legs: itemID('Spirit angler waders'), - cape: null, - two_handed: null, - hands: null, - feet: itemID('Spirit angler boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - pinned_setup: null - }, - { - name: 'pyromancer', - user_id: '123', - head: itemID('Pyromancer hood'), - neck: null, - body: itemID('Pyromancer garb'), - legs: itemID('Pyromancer robe'), - cape: null, - two_handed: null, - hands: null, - feet: itemID('Pyromancer boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - pinned_setup: null - }, - { - name: 'prospector', - user_id: '123', - head: itemID('Prospector helmet'), - neck: null, - body: itemID('Prospector jacket'), - legs: itemID('Prospector legs'), - cape: null, - two_handed: null, - hands: null, - feet: itemID('Prospector boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - pinned_setup: null - }, - { - name: 'lumberjack', - user_id: '123', - head: itemID('Lumberjack hat'), - neck: null, - body: itemID('Lumberjack top'), - legs: itemID('Lumberjack legs'), - cape: null, - two_handed: null, - hands: null, - feet: itemID('Lumberjack boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - pinned_setup: null - }, - { - name: 'farmer', - user_id: '123', - head: itemID("Farmer's strawhat"), - neck: null, - body: itemID("Farmer's jacket"), - legs: itemID("Farmer's boro trousers"), - cape: null, - two_handed: null, - hands: null, - feet: itemID("Farmer's boots"), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - pinned_setup: null - } -]; - -const bsoPresets: GearPreset[] = [ - { - name: 'fletcher', - user_id: '123', - head: itemID("Fletcher's hat"), - neck: null, - body: itemID("Fletcher's top"), - legs: itemID("Fletcher's legs"), - cape: null, - two_handed: null, - hands: itemID("Fletcher's gloves"), - feet: itemID("Fletcher's boots"), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - pinned_setup: null - } -]; -for (const preset of bsoPresets) { - globalPresets.push(preset); -} diff --git a/src/lib/gear/util.ts b/src/lib/gear/util.ts index e466900650..324edc63cd 100644 --- a/src/lib/gear/util.ts +++ b/src/lib/gear/util.ts @@ -1,5 +1,4 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; -import type { GearPreset } from '@prisma/client'; import type { EquipmentSlot, Item } from 'oldschooljs/dist/meta/types'; import type { GearSetup } from '.'; @@ -36,15 +35,3 @@ export function constructGearSetup(setup: PartialGearSetup): Gear { weapon: setup.weapon ? { item: itemID(setup.weapon), quantity: 1 } : null }); } - -export function gearPresetToGear(gearPreset: GearPreset) { - const gear: GearSetup = {} as GearSetup; - for (const key of ['cape', 'feet', 'hands', 'head', 'legs', 'neck', 'ring', 'shield', 'weapon', 'body'] as const) { - const val = gearPreset[key]; - gear[key] = val !== null ? { item: val, quantity: 1 } : null; - } - - gear.ammo = gearPreset.ammo ? { item: gearPreset.ammo, quantity: gearPreset.ammo_qty ?? 1 } : null; - gear['2h'] = gearPreset.two_handed ? { item: gearPreset.two_handed, quantity: 1 } : null; - return new Gear(gear); -} diff --git a/src/lib/globals.ts b/src/lib/globals.ts index f75e85a675..44e076ade6 100644 --- a/src/lib/globals.ts +++ b/src/lib/globals.ts @@ -1,20 +1,14 @@ import { isMainThread } from 'node:worker_threads'; -import { TSRedis } from '@oldschoolgg/toolkit/TSRedis'; import { PrismaClient } from '@prisma/client'; -import { PrismaClient as RobochimpPrismaClient } from '@prisma/robochimp'; -import { production } from '../config'; import { globalConfig } from './constants'; -import { handleDeletedPatron, handleEditPatron } from './patreonUtils'; declare global { var prisma: PrismaClient; - var redis: TSRedis; - var roboChimpClient: RobochimpPrismaClient; } function makePrismaClient(): PrismaClient { - if (!production && !process.env.TEST) console.log('Making prisma client...'); + if (!globalConfig.isProduction && !process.env.TEST) console.log('Making prisma client...'); if (!isMainThread && !process.env.TEST) { throw new Error('Prisma client should only be created on the main thread.'); } @@ -24,35 +18,3 @@ function makePrismaClient(): PrismaClient { }); } global.prisma = global.prisma || makePrismaClient(); - -function makeRobochimpPrismaClient(): RobochimpPrismaClient { - if (!production && !process.env.TEST) console.log('Making robochimp client...'); - if (!isMainThread && !process.env.TEST) { - throw new Error('Robochimp client should only be created on the main thread.'); - } - - return new RobochimpPrismaClient({ - log: ['info', 'warn', 'error'] - }); -} -global.roboChimpClient = global.roboChimpClient || makeRobochimpPrismaClient(); - -function makeRedisClient(): TSRedis { - if (!production && !process.env.TEST) console.log('Making Redis client...'); - if (!isMainThread && !process.env.TEST) { - throw new Error('Redis client should only be created on the main thread.'); - } - return new TSRedis({ mocked: !globalConfig.redisPort, port: globalConfig.redisPort }); -} -global.redis = global.redis || makeRedisClient(); - -global.redis.subscribe(message => { - debugLog(`Received message from Redis: ${JSON.stringify(message)}`); - if (message.type === 'patron_tier_change') { - if (message.new_tier === 0) { - return handleDeletedPatron(message.discord_ids); - } else { - return handleEditPatron(message.discord_ids); - } - } -}); diff --git a/src/lib/grandExchange.ts b/src/lib/grandExchange.ts deleted file mode 100644 index 556a979e91..0000000000 --- a/src/lib/grandExchange.ts +++ /dev/null @@ -1,1008 +0,0 @@ -import type { GEListing, GETransaction } from '@prisma/client'; -import { GEListingType } from '@prisma/client'; -import { ButtonBuilder, ButtonStyle, bold, userMention } from 'discord.js'; -import { Time, calcPercentOfNum, clamp, noOp, sumArr, uniqueArr } from 'e'; -import { LRUCache } from 'lru-cache'; -import { Bank } from 'oldschooljs'; -import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; -import PQueue from 'p-queue'; - -import { BLACKLISTED_USERS } from './blacklists'; -import { BitField, ONE_TRILLION, globalConfig } from './constants'; -import { isCustomItem } from './customItems/util'; -import { marketPricemap } from './marketPrices'; -import type { RobochimpUser } from './roboChimp'; -import { roboChimpUserFetch } from './roboChimp'; - -import { ADMIN_IDS, OWNER_IDS } from '../config'; -import { fetchTableBank, makeTransactFromTableBankQueries } from './tableBank'; -import { - assert, - PerkTier, - generateGrandExchangeID, - getInterval, - isGEUntradeable, - itemNameFromID, - makeComponents, - toKMB -} from './util'; -import { mahojiClientSettingsFetch, mahojiClientSettingsUpdate } from './util/clientSettings'; -import getOSItem, { getItem } from './util/getOSItem'; -import { logError } from './util/logError'; -import { sendToChannelID } from './util/webhook'; - -interface CreateListingArgs { - user: MUser; - itemName: string; - quantity: number; - price: number; - type: GEListingType; -} - -function validateNumber(num: number) { - if (num < 0 || Number.isNaN(num) || !Number.isInteger(num) || num >= Number.MAX_SAFE_INTEGER) { - throw new Error(`Invalid number: ${num}.`); - } -} - -function sanityCheckListing(listing: GEListing) { - if (listing.fulfilled_at && listing.quantity_remaining !== 0) { - throw new Error(`Listing ${listing.id} is fulfilled but quantity remaining is not 0.`); - } - if (listing.quantity_remaining < 0) { - throw new Error(`Listing ${listing.id} has negative quantity remaining.`); - } - if (listing.quantity_remaining > listing.total_quantity) { - throw new Error(`Listing ${listing.id} has quantity remaining greater than total quantity.`); - } - if (listing.quantity_remaining === 0 && !listing.fulfilled_at) { - throw new Error(`Listing ${listing.id} has quantity remaining of 0 but is not fulfilled.`); - } - if (listing.quantity_remaining !== 0 && listing.fulfilled_at) { - throw new Error(`Listing ${listing.id} has quantity remaining but is fulfilled.`); - } - const item = getItem(listing.item_id); - if (!item) { - throw new Error(`Listing ${listing.id} has invalid item ID ${listing.item_id}.`); - } - if (Number.isNaN(listing.asking_price_per_item) || Number.isNaN(listing.total_quantity)) { - throw new Error(`Listing ${listing.id} has NaN price or quantity.`); - } - if (listing.asking_price_per_item < 0 || listing.total_quantity < 0) { - throw new Error(`Listing ${listing.id} has negative price or quantity.`); - } - if (listing.cancelled_at && listing.fulfilled_at) { - throw new Error(`Listing ${listing.id} has both fulfilled and cancelled.`); - } - validateNumber(Number(listing.asking_price_per_item)); - validateNumber(Number(listing.total_quantity)); - validateNumber(Number(listing.gp_refunded)); -} - -function sanityCheckTransaction({ - id, - total_tax_paid, - tax_rate_percent, - price_per_item_after_tax, - price_per_item_before_tax, - quantity_bought -}: GETransaction) { - if (price_per_item_before_tax < price_per_item_after_tax) { - throw new Error(`Transaction ${id} has price before tax less than price after tax.`); - } - - validateNumber(Number(price_per_item_before_tax)); - validateNumber(Number(price_per_item_before_tax)); - validateNumber(Number(total_tax_paid)); - - assert( - Number(total_tax_paid) === - (Number(price_per_item_before_tax) - Number(price_per_item_after_tax)) * quantity_bought, - `Transaction ${id} total_tax_paid should equal price_per_item_before_tax - price_per_item_after_tax. Transaction ${id} has ${total_tax_paid} total_tax_paid, ${price_per_item_before_tax} price_per_item_before_tax, and ${price_per_item_after_tax} price_per_item_after_tax.` - ); - - validateNumber(tax_rate_percent); -} - -export function createGECancelButton(listing: GEListing) { - const button = new ButtonBuilder() - .setCustomId(`ge_cancel_${listing.userfacing_id}`) - .setLabel( - `Cancel ${listing.type} ${toKMB(listing.total_quantity)} ${itemNameFromID(listing.item_id)}`.slice(0, 79) - ) - .setStyle(ButtonStyle.Secondary); - - return button; -} - -class GrandExchangeSingleton { - public queue = new PQueue({ concurrency: 1 }); - public locked = false; - public isTicking = false; - public ready = false; - public loggingEnabled = false; - - log(message: string, context?: any) { - if (this.loggingEnabled) { - debugLog(message, context); - } - } - - public config = { - maxPricePerItem: ONE_TRILLION, - maxTotalPrice: ONE_TRILLION, - buyLimit: { - interval: Time.Hour * 4, - fallbackBuyLimit: (item: Item) => (item.price > 1_000_000 ? 1 : 1000) - }, - tax: { - // Tax per item - rate: () => { - return 1; - }, - // Max tax per item - cap: () => { - return 5_000_000; - } - }, - slots: { - slotBoosts: [ - { - has: () => true, - name: 'Base', - amount: 3 - }, - ...[100, 250, 1000, 2000, 2500].map(num => ({ - has: (user: MUser) => user.totalLevel >= num, - name: `${num} Total Level`, - amount: 1 - })), - ...[30, 60, 90, 95].map(num => ({ - has: (_: MUser, robochimpUser: RobochimpUser) => - robochimpUser.bso_cl_percent && robochimpUser.bso_cl_percent >= num, - name: `${num}% CL Completion`, - amount: 1 - })), - ...[10_000, 20_000, 30_000].map(num => ({ - has: (_: MUser, robochimpUser: RobochimpUser) => robochimpUser.leagues_points_total >= num, - name: `${num.toLocaleString()} Leagues Points`, - amount: 1 - })), - { - has: (user: MUser) => user.perkTier() >= PerkTier.Four, - name: 'Tier 3 Patron', - amount: 10 - } - ] - } - }; - - async init() { - try { - await this.fetchOwnedBank(); - await this.extensiveVerification(); - } catch (err: any) { - await this.lockGE(err.message); - } finally { - this.ready = true; - } - } - - private slotsCache = new LRUCache>>({ - ttl: Time.Hour, - max: 100 - }); - async calculateSlotsOfUser( - user: MUser - ): Promise<{ slots: number; doesntHaveNames: string[]; possibleExtra: number; maxPossible: number }> { - const cached = this.slotsCache.get(user.id); - if (cached) return cached; - const robochimpUser = await roboChimpUserFetch(user.id); - let slots = 0; - const doesntHaveNames = []; - let possibleExtra = 0; - let maxPossible = 0; - for (const boost of this.config.slots.slotBoosts) { - maxPossible += boost.amount; - if (boost.has(user, robochimpUser)) { - slots += boost.amount; - } else { - doesntHaveNames.push(boost.name); - possibleExtra += boost.amount; - } - } - const result = { slots, doesntHaveNames, possibleExtra, maxPossible }; - this.slotsCache.set(user.id, result); - return result; - } - - getInterval() { - return getInterval(4); - } - - async fetchOwnedBank() { - const geBank = await fetchTableBank(); - return geBank; - } - - calculateTaxForTransaction({ pricePerItem }: { pricePerItem: number }) { - const rate = this.config.tax.rate(); - let amount = calcPercentOfNum(rate, pricePerItem); - amount = Math.floor(amount); - amount = clamp(amount, 0, this.config.tax.cap()); - validateNumber(amount); - - const newPrice = pricePerItem - amount; - validateNumber(newPrice); - - return { - taxedAmount: amount, - rate, - newPrice - }; - } - - async lockGE(reason: string) { - if (this.locked) return; - debugLog(`The Grand Exchange has encountered an error and has been locked. Reason: ${reason}`); - const idsToNotify = [...ADMIN_IDS, ...OWNER_IDS]; - await sendToChannelID(globalConfig.geAdminChannelID, { - content: `The Grand Exchange has encountered an error and has been locked. Reason: ${reason}. ${idsToNotify - .map(i => userMention(i)) - .join(', ')}`, - allowedMentions: globalConfig.isProduction ? { users: idsToNotify } : undefined - }).catch(noOp); - await mahojiClientSettingsUpdate({ - grand_exchange_is_locked: true - }); - this.locked = true; - } - - getItemBuyLimit(item: Item) { - return item.buy_limit ?? this.config.buyLimit.fallbackBuyLimit(item); - } - - async checkBuyLimitForListing(geListing: GEListing) { - const interval = this.getInterval(); - - const allActiveListingsInTimePeriod = await prisma.gETransaction.findMany({ - where: { - buy_listing: { - user_id: geListing.user_id, - item_id: geListing.item_id - }, - created_at: { - gte: interval.start, - lt: interval.end - } - } - }); - - const item = getOSItem(geListing.item_id); - let buyLimit = item.buy_limit ?? this.config.buyLimit.fallbackBuyLimit(item); - if (!isCustomItem(item.id)) { - buyLimit *= 5; - } - - const totalSold = sumArr(allActiveListingsInTimePeriod.map(listing => listing.quantity_bought)); - const remainingItemsCanBuy = Math.max(0, buyLimit - totalSold); - - validateNumber(buyLimit); - validateNumber(totalSold); - validateNumber(remainingItemsCanBuy); - - return { - buyLimit, - totalSold, - item, - remainingItemsCanBuy - }; - } - - async preCreateListing({ - user, - itemName, - price, - quantity, - type - }: CreateListingArgs): Promise< - { error: string } | { confirmationStr: string; cost: Bank; price: number; quantity: number; item: Item } - > { - if (this.locked || !this.ready) { - return { error: 'The Grand Exchange is temporarily closed! Sorry, please try again later.' }; - } - if (user.isIronman) return { error: "You're an ironman." }; - const item = getItem(itemName); - if (!item || ['Coins'].includes(item.name)) { - return { error: 'Invalid item.' }; - } - - if (isGEUntradeable(item.id)) { - return { error: 'Invalid item.' }; - } - - if ( - !price || - price <= 0 || - Number.isNaN(price) || - !Number.isInteger(price) || - price > this.config.maxPricePerItem - ) { - return { - error: `Invalid price, the price must be a number between 1 and ${toKMB(this.config.maxPricePerItem)}.` - }; - } - if ( - !quantity || - quantity <= 0 || - Number.isNaN(quantity) || - !Number.isInteger(quantity) || - quantity > 5_000_000 - ) { - return { error: 'Invalid quantity, the quantity must be a number between 1 and 5m.' }; - } - - if (price * quantity > this.config.maxTotalPrice) { - return { - error: `You cannot make a listing with a total price of more than ${toKMB(this.config.maxTotalPrice)}.` - }; - } - - const theirCurrentListings = await prisma.gEListing.findMany({ - where: { - user_id: user.id, - cancelled_at: null, - fulfilled_at: null - } - }); - const { slots, doesntHaveNames, possibleExtra } = await this.calculateSlotsOfUser(user); - if (theirCurrentListings.length >= slots) { - let str = `You can't make this listing, because all your slots are full. You have ${theirCurrentListings.length} active listings, and ${slots} slots. One of your slots needs to be cancelled or fulfilled before you can make another.`; - if (possibleExtra > 0) { - str += `\n\nYou can get ${possibleExtra} extra slots through: ${doesntHaveNames.join(', ')}.`; - } - - return { error: str }; - } - - await user.sync(); - - const cost = new Bank(); - - if (type === 'Buy') { - cost.add('Coins', price * quantity); - } else { - cost.add(item.id, quantity); - } - - if (!user.owns(cost)) { - return { error: `You don't own ${cost}.` }; - } - - const applicableTax = this.calculateTaxForTransaction({ pricePerItem: price }); - - const total = price * quantity; - const totalAfterTax = applicableTax.newPrice * quantity; - - let confirmationStr = `Are you sure you want to create this listing? - -${type} ${toKMB(quantity)} ${item.name} for ${toKMB(price)} each, for a total of ${toKMB(total)}.${ - type === 'Buy' - ? '' - : applicableTax.taxedAmount > 0 - ? ` At this price, you will receive ${toKMB(totalAfterTax)} after taxes.` - : ' No tax will be charged on these items.' - }`; - - const guidePrice = marketPricemap.get(item.id); - if (guidePrice) { - confirmationStr += bold( - `\n\n💰 The current estimated market value for this item is ${toKMB(guidePrice.guidePrice)} GP.` - ); - } - - return { - confirmationStr, - cost, - item, - price, - quantity - }; - } - - async createListing({ - user, - itemName, - price, - quantity, - type - }: CreateListingArgs): Promise<{ error: string } | { createdListing: GEListing; error: null }> { - return this.queue.add(async () => { - const result = await this.preCreateListing({ user, itemName, price, quantity, type }); - if ('error' in result) return result; - - await user.removeItemsFromBank(result.cost); - - const [listing] = await prisma.$transaction([ - prisma.gEListing.create({ - data: { - user_id: user.id, - item_id: result.item.id, - asking_price_per_item: price, - total_quantity: quantity, - type, - quantity_remaining: quantity, - userfacing_id: generateGrandExchangeID() - } - }), - ...makeTransactFromTableBankQueries({ bankToAdd: result.cost }) - ]); - - sanityCheckListing(listing); - this.log(`${user.id} created ${type} listing, removing ${result.cost}, adding it to the g.e bank.`); - - return { - createdListing: listing, - error: null - }; - }); - } - - private async createTransaction( - buyerListing: GEListing, - sellerListing: GEListing, - remainingItemsInBuyLimit: number - ) { - const logContext: Record = { - buyerListingID: buyerListing.id.toString(), - sellerListingID: sellerListing.id.toString(), - type: 'GE_TRANSACTION' - }; - assert(buyerListing.type !== sellerListing.type, 'Buyer and seller listings are the same type.'); - assert(sellerListing.type === 'Sell' && buyerListing.type === 'Buy', 'Wrong listing types'); - assert(buyerListing.item_id === sellerListing.item_id, 'Buyer and seller listings are not for the same item.'); - assert(buyerListing.quantity_remaining > 0, 'Buyer listing has 0 quantity remaining.'); - assert(sellerListing.quantity_remaining > 0, 'Seller listing has 0 quantity remaining.'); - assert(buyerListing.user_id !== sellerListing.user_id, 'Buyer and seller are the same user.'); - assert(remainingItemsInBuyLimit !== 0, 'Buyer has 0 remaining items in buy limit.'); - assert(sellerListing.user_id !== null, 'null seller listing user id'); - assert(buyerListing.user_id !== null, 'null buyer listing user id'); - if (buyerListing.user_id === null || sellerListing.user_id === null) { - throw new Error('null user id'); - } - - const quantityToBuy = Math.min( - remainingItemsInBuyLimit, - buyerListing.quantity_remaining, - sellerListing.quantity_remaining - ); - validateNumber(quantityToBuy); - - if ( - quantityToBuy <= 0 || - quantityToBuy > buyerListing.quantity_remaining || - quantityToBuy > sellerListing.quantity_remaining - ) { - throw new Error( - `Tried to buy ${quantityToBuy} but buyer has ${buyerListing.quantity_remaining} remaining and seller has ${sellerListing.quantity_remaining} remaining` - ); - } - - let priceWinner: 'buyer' | 'seller' = 'buyer'; - let pricePerItemBeforeTax = -1; - if (buyerListing.created_at < sellerListing.created_at) { - pricePerItemBeforeTax = Number(buyerListing.asking_price_per_item); - priceWinner = 'buyer'; - } else { - pricePerItemBeforeTax = Number(sellerListing.asking_price_per_item); - priceWinner = 'seller'; - } - const totalPriceBeforeTax = quantityToBuy * pricePerItemBeforeTax; - validateNumber(pricePerItemBeforeTax); - validateNumber(totalPriceBeforeTax); - - const { taxedAmount, rate } = this.calculateTaxForTransaction({ pricePerItem: pricePerItemBeforeTax }); - assert(taxedAmount >= 0, 'Taxed amount is less than 0.'); - assert(taxedAmount <= pricePerItemBeforeTax, 'Taxed amount is greater than price per item.'); - validateNumber(taxedAmount); - validateNumber(rate); - - const pricePerItemAfterTax = pricePerItemBeforeTax - taxedAmount; - validateNumber(pricePerItemAfterTax); - - const totalPriceAfterTax = pricePerItemAfterTax * quantityToBuy; - validateNumber(totalPriceAfterTax); - - assert( - pricePerItemAfterTax <= pricePerItemBeforeTax, - `Price per item after tax (${pricePerItemAfterTax}) is greater than price per item before tax (${pricePerItemBeforeTax})` - ); - assert( - totalPriceAfterTax <= totalPriceBeforeTax, - `Total price after tax (${totalPriceAfterTax}) is greater than total price before tax (${totalPriceBeforeTax})` - ); - - const newBuyerListingQuantityRemaining = buyerListing.quantity_remaining - quantityToBuy; - const newSellerListingQuantityRemaining = sellerListing.quantity_remaining - quantityToBuy; - validateNumber(newBuyerListingQuantityRemaining); - validateNumber(newSellerListingQuantityRemaining); - - assert( - totalPriceBeforeTax >= totalPriceAfterTax, - `Price before tax (${totalPriceBeforeTax}) is not greater than price after tax (${totalPriceAfterTax})` - ); - const totalTaxPaid = totalPriceBeforeTax - totalPriceAfterTax; - validateNumber(totalTaxPaid); - - const buyerLoot = new Bank().add(buyerListing.item_id, quantityToBuy); - const sellerLoot = new Bank().add('Coins', totalPriceAfterTax); - const bankToRemoveFromGeBank = new Bank().add(buyerLoot).add(sellerLoot); - bankToRemoveFromGeBank.add('Coins', totalTaxPaid); - - let buyerRefund = 0; - if (buyerListing.asking_price_per_item > sellerListing.asking_price_per_item) { - const buyerPricePerItemBeforeTax = buyerListing.asking_price_per_item; - const totalAmountToRemove = Number(buyerPricePerItemBeforeTax) * quantityToBuy; - buyerRefund = totalAmountToRemove - totalPriceBeforeTax; - - validateNumber(buyerRefund); - buyerLoot.add('Coins', buyerRefund); - bankToRemoveFromGeBank.add('Coins', buyerRefund); - - this.log( - `Buyer got refunded ${buyerRefund} GP due to price difference. Buyer was asking ${buyerListing.asking_price_per_item}GP for each of the ${quantityToBuy}x items, seller was asking ${sellerListing.asking_price_per_item}GP, and the post-tax price per item was ${pricePerItemAfterTax}`, - logContext - ); - } - - const geBank = await this.fetchOwnedBank(); - const bankGEShouldHave = bankToRemoveFromGeBank.clone(); - - const debug = `PriceWinner[${priceWinner}] PricePerItemBeforeTax[${pricePerItemBeforeTax}] PricePerItemAfterTax[${pricePerItemAfterTax}] BuyerPrice[${ - buyerListing.asking_price_per_item - }] SellerPrice[${ - sellerListing.asking_price_per_item - }] TotalPriceBeforeTax[${totalPriceBeforeTax}] QuantityToBuy[${quantityToBuy}] TotalTaxPaid[${totalTaxPaid}] BuyerRefund[${buyerRefund}] BuyerLoot[${JSON.stringify(buyerLoot)}] SellerLoot[${sellerLoot}] CurrentGEBank[${JSON.stringify(geBank)}] BankToRemoveFromGeBank[${JSON.stringify(bankToRemoveFromGeBank.bank)}] ExpectedAfterBank[${ - geBank.clone().remove(bankToRemoveFromGeBank).bank - }]`; - - assert( - bankToRemoveFromGeBank.amount('Coins') === Number(buyerListing.asking_price_per_item) * quantityToBuy, - `The G.E Must be removing the full amount the buyer put in, otherwise there's extra/notenough GP leftover in their buyerListing. Expected to be removing ${ - Number(buyerListing.asking_price_per_item) * quantityToBuy - }, but we're removing ${bankToRemoveFromGeBank.amount('Coins')} ${debug}` - ); - - if (!geBank.has(bankGEShouldHave)) { - const missingItems = bankGEShouldHave.clone().remove(geBank); - const str = `The GE did not have enough items to cover this transaction! We tried to remove ${bankGEShouldHave} missing: ${missingItems}. ${debug}`; - logError(str, logContext); - this.log(str, logContext); - throw new Error(str); - } - - this.log( - `Completing a transaction, removing ${JSON.stringify(bankToRemoveFromGeBank.bank)} from the GE bank, ${totalTaxPaid} in taxed gp. The current GE bank is ${JSON.stringify(geBank.bank)}. ${debug}`, - { - totalPriceAfterTax, - totalTaxPaid, - totalPriceBeforeTax, - bankToRemoveFromGeBank: bankToRemoveFromGeBank.toString(), - currentGEBank: JSON.stringify(geBank.bank) - } - ); - - const [newTx] = await prisma.$transaction([ - prisma.gETransaction.create({ - data: { - buy_listing_id: buyerListing.id, - sell_listing_id: sellerListing.id, - quantity_bought: quantityToBuy, - price_per_item_before_tax: pricePerItemBeforeTax, - price_per_item_after_tax: pricePerItemAfterTax, - total_tax_paid: totalTaxPaid, - tax_rate_percent: rate - } - }), - prisma.gEListing.update({ - where: { - id: buyerListing.id - }, - data: { - quantity_remaining: newBuyerListingQuantityRemaining, - fulfilled_at: newBuyerListingQuantityRemaining === 0 ? new Date() : null, - gp_refunded: buyerRefund - } - }), - prisma.gEListing.update({ - where: { - id: sellerListing.id - }, - data: { - quantity_remaining: newSellerListingQuantityRemaining, - fulfilled_at: newSellerListingQuantityRemaining === 0 ? new Date() : null - } - }), - prisma.clientStorage.update({ - where: { - id: globalConfig.clientID - }, - data: { - grand_exchange_tax_bank: { - increment: totalTaxPaid - }, - grand_exchange_total_tax: { - increment: totalTaxPaid - } - }, - select: { - id: true - } - }), - ...makeTransactFromTableBankQueries({ bankToRemove: bankToRemoveFromGeBank }) - ]); - - sanityCheckTransaction(newTx); - - this.log(`Transaction completed, the new G.E bank is ${JSON.stringify((await this.fetchOwnedBank()).bank)}.`); - - const buyerUser = await mUserFetch(buyerListing.user_id); - const sellerUser = await mUserFetch(sellerListing.user_id); - - await buyerUser.addItemsToBank({ - items: buyerLoot, - collectionLog: false, - dontAddToTempCL: true, - filterLoot: false, - neverUpdateHistory: true - }); - await sellerUser.addItemsToBank({ - items: sellerLoot, - collectionLog: false, - dontAddToTempCL: true, - filterLoot: false, - neverUpdateHistory: true - }); - - const itemName = itemNameFromID(buyerListing.item_id)!; - - const disableDMsButton = new ButtonBuilder() - .setCustomId('ge_cancel_dms') - .setLabel('Disable These DMs') - .setStyle(ButtonStyle.Secondary); - - const buyerDJSUser = await globalClient.fetchUser(buyerListing.user_id).catch(noOp); - if (buyerDJSUser && !buyerUser.bitfield.includes(BitField.DisableGrandExchangeDMs)) { - let str = `You bought ${quantityToBuy.toLocaleString()}x ${itemName} for ${toKMB( - pricePerItemAfterTax - )} GP each, for a total of ${toKMB(totalPriceAfterTax)} GP${totalTaxPaid > 0 ? ', after tax' : ''}.`; - if (totalTaxPaid > 0) { - str += ` ${toKMB(totalTaxPaid)} GP in tax was paid.`; - } else { - str += ' No tax was paid.'; - } - if (buyerRefund) { - str += ` ${toKMB(buyerRefund)} GP was refunded to you, because you offered more than the seller.`; - } - - str += ` You received ${buyerLoot}.`; - - if (newBuyerListingQuantityRemaining === 0) { - str += ` ${bold('This listing has now been fully fulfilled.')}`; - } else { - str += ` There are ${newBuyerListingQuantityRemaining}x remaining to buy in your listing.`; - } - - const remainingBuyLimit = remainingItemsInBuyLimit - quantityToBuy; - if (remainingBuyLimit <= 0) { - str += ` You have reached your buy limit for this item, your buy limit will reset at ${ - this.getInterval().nextResetStr - }.`; - } - - const components = [disableDMsButton]; - if (newBuyerListingQuantityRemaining > 0) { - components.push(createGECancelButton(buyerListing)); - } - - await buyerDJSUser.send({ content: str, components: makeComponents(components) }).catch(noOp); - } - - const sellerDJSUser = await globalClient.fetchUser(sellerListing.user_id).catch(noOp); - if (sellerDJSUser && !sellerUser.bitfield.includes(BitField.DisableGrandExchangeDMs)) { - let str = `You sold ${quantityToBuy.toLocaleString()}x ${itemName} for ${toKMB( - pricePerItemAfterTax - )} GP each and received ${sellerLoot}.`; - if (totalTaxPaid > 0) { - str += ` ${toKMB(totalTaxPaid)} GP in tax was paid.`; - } else { - str += ' No tax was paid.'; - } - - const components = [disableDMsButton]; - if (newSellerListingQuantityRemaining > 0) { - components.push(createGECancelButton(sellerListing)); - str += `\n\nYou have ${newSellerListingQuantityRemaining}x remaining to sell in your listing.`; - } else { - str += '\n\nThis listing has now been fully fulfilled.'; - } - - await sellerDJSUser.send({ content: str, components: makeComponents(components) }).catch(noOp); - } - } - - async fetchActiveListings() { - const buyListings = await prisma.gEListing.findMany({ - where: { - type: GEListingType.Buy, - fulfilled_at: null, - cancelled_at: null, - user_id: { - not: null - } - }, - orderBy: [ - { - asking_price_per_item: 'desc' - }, - { - created_at: 'asc' - } - ] - }); - const [sellListings, clientStorage, currentBankRaw] = await prisma.$transaction([ - prisma.gEListing.findMany({ - where: { - type: GEListingType.Sell, - fulfilled_at: null, - cancelled_at: null, - user_id: { - not: null - }, - item_id: { - in: uniqueArr(buyListings.map(i => i.item_id)) - } - }, - orderBy: [ - { - asking_price_per_item: 'asc' - }, - { - created_at: 'asc' - } - ], - // Take the last purchase transaction for each sell listing - include: { - sellTransactions: { - orderBy: { - created_at: 'desc' - }, - take: 1 - } - } - }), - prisma.clientStorage.findFirst({ - where: { id: globalConfig.clientID }, - select: { - grand_exchange_is_locked: true - } - }), - prisma.$queryRawUnsafe<{ bank: ItemBank }[]>( - 'SELECT json_object_agg(item_id, quantity) as bank FROM ge_bank WHERE quantity != 0;' - ) - ]); - - if (clientStorage?.grand_exchange_is_locked) { - this.locked = true; - } else if (clientStorage?.grand_exchange_is_locked === false) { - this.locked = false; - } - return { buyListings, sellListings, currentBank: new Bank(currentBankRaw[0].bank) }; - } - - async extensiveVerification() { - await this.checkGECanFullFilAllListings(); - return true; - } - - async checkGECanFullFilAllListings() { - const shouldHave = new Bank(); - const [buyListings, sellListings, currentBankRaw] = await prisma.$transaction([ - prisma.gEListing.findMany({ - where: { - type: GEListingType.Buy, - fulfilled_at: null, - cancelled_at: null, - user_id: { - not: null - } - }, - orderBy: [ - { - asking_price_per_item: 'desc' - }, - { - created_at: 'asc' - } - ] - }), - prisma.gEListing.findMany({ - where: { - type: GEListingType.Sell, - fulfilled_at: null, - cancelled_at: null, - user_id: { - not: null - } - }, - orderBy: [ - { - asking_price_per_item: 'asc' - }, - { - created_at: 'asc' - } - ], - // Take the last purchase transaction for each sell listing - include: { - sellTransactions: { - orderBy: { - created_at: 'desc' - }, - take: 1 - } - } - }), - prisma.$queryRawUnsafe<{ bank: ItemBank }[]>( - 'SELECT json_object_agg(item_id, quantity) as bank FROM ge_bank WHERE quantity != 0;' - ) - ]); - const currentBank = new Bank(currentBankRaw[0].bank); - // How much GP the g.e still has from this listing - for (const listing of buyListings) { - shouldHave.add('Coins', Number(listing.asking_price_per_item) * listing.quantity_remaining); - } - - for (const listing of sellListings) { - shouldHave.add(listing.item_id, listing.quantity_remaining); - } - - this.log(`Expected G.E Bank: ${JSON.stringify(shouldHave.bank)}`); - if (!currentBank.equals(shouldHave)) { - if (!currentBank.has(shouldHave)) { - throw new Error( - `GE is MISSING items to cover the ${[...buyListings, ...sellListings].length}x active listings. -G.E Bank Has: ${currentBank} -G.E Bank Should Have: ${shouldHave} -Difference: ${shouldHave.difference(currentBank)}` - ); - } - throw new Error(`GE has EXTRA items. -G.E Bank Has: ${currentBank} -G.E Bank Should Have: ${shouldHave} -Difference: ${shouldHave.difference(currentBank)}`); - } else { - this.log('GE has enough to cover the listings.'); - return true; - } - } - - async tick() { - return new Promise(async (resolve, reject) => { - await this.queue.add(async () => { - if (this.isTicking) return reject('Already ticking.'); - try { - await this._tick(); - } catch (err: any) { - logError(err.message); - debugLog(err.message); - reject(err); - } finally { - this.isTicking = false; - resolve(); - } - }); - }); - } - - async fetchData() { - const settings = await mahojiClientSettingsFetch({ - grand_exchange_is_locked: true, - grand_exchange_tax_bank: true, - grand_exchange_total_tax: true - }); - - const taxBank = Number(settings.grand_exchange_tax_bank); - validateNumber(taxBank); - - const totalTax = Number(settings.grand_exchange_total_tax); - validateNumber(totalTax); - - return { - isLocked: settings.grand_exchange_is_locked, - taxBank, - totalTax - }; - } - - private async _tick() { - if (!this.ready) return; - if (this.locked) return; - const { buyListings, sellListings } = await this.fetchActiveListings(); - - const minimumSellPricePerItem = new Map(); - for (const sellListing of sellListings) { - const currentPrice = minimumSellPricePerItem.get(sellListing.item_id); - if (currentPrice === undefined || sellListing.asking_price_per_item < currentPrice) { - minimumSellPricePerItem.set(sellListing.item_id, Number(sellListing.asking_price_per_item)); - } - } - - for (const buyListing of buyListings) { - const minPrice = minimumSellPricePerItem.get(buyListing.item_id); - if (!buyListing.user_id || minPrice === undefined || buyListing.asking_price_per_item < minPrice) { - continue; - } - - if (BLACKLISTED_USERS.has(buyListing.user_id)) { - continue; - } - - // These are all valid, matching sell listings we can match with this buy listing. - const matchingSellListings = sellListings.filter( - sellListing => - sellListing.item_id === buyListing.item_id && - // "Trades succeed when one player's buy offer is greater than or equal to another player's sell offer." - buyListing.asking_price_per_item >= sellListing.asking_price_per_item && - buyListing.user_id !== sellListing.user_id && - sellListing.user_id !== null && - !BLACKLISTED_USERS.has(sellListing.user_id) - ); - - /** - * If we have multiple matching sell listings, sort them so we buy from the *least* - * active one. To prevent buying over and over from the same person. - */ - matchingSellListings.sort((a, b) => { - const aPrice = a.asking_price_per_item; - const bPrice = b.asking_price_per_item; - if (aPrice === bPrice) { - const aLastSale = a.sellTransactions[0]?.created_at ?? a.created_at; - const bLastSale = b.sellTransactions[0]?.created_at ?? b.created_at; - return aLastSale.getTime() - bLastSale.getTime(); - } - return Number(aPrice - bPrice); - }); - - const matchingSellListing = matchingSellListings[0]; - if (!matchingSellListing) continue; - try { - const { remainingItemsCanBuy } = await this.checkBuyLimitForListing(buyListing); - if (remainingItemsCanBuy === 0) continue; - await this.createTransaction(buyListing, matchingSellListing, remainingItemsCanBuy); - } catch (err: any) { - await this.lockGE(err.message); - logError(err); - debugLog(err); - break; - } - - // Process only one transaction per tick - break; - } - } - - async totalReset() { - if (globalConfig.isProduction) throw new Error("You can't reset the GE in production."); - await mahojiClientSettingsUpdate({ - grand_exchange_is_locked: false, - grand_exchange_tax_bank: 0, - grand_exchange_total_tax: 0 - }); - await prisma.gEBank.deleteMany(); - await prisma.gETransaction.deleteMany(); - await prisma.gEListing.deleteMany(); - } -} - -export const GrandExchange = new GrandExchangeSingleton(); diff --git a/src/lib/handleNewCLItems.ts b/src/lib/handleNewCLItems.ts index 9da92ab0ec..891ca79563 100644 --- a/src/lib/handleNewCLItems.ts +++ b/src/lib/handleNewCLItems.ts @@ -1,34 +1,39 @@ -import { formatOrdinal, roboChimpCLRankQuery } from '@oldschoolgg/toolkit'; -import type { Prisma } from '@prisma/client'; +import { formatOrdinal } from '@oldschoolgg/toolkit'; import { UserEventType } from '@prisma/client'; -import { roll, sumArr } from 'e'; import type { Bank } from 'oldschooljs'; import { Events } from './constants'; import { allCLItems, allCollectionLogsFlat, calcCLDetails } from './data/Collections'; -import { calculateMastery } from './mastery'; -import { calculateOwnCLRanking, roboChimpSyncData } from './roboChimp'; - -import { MUserStats } from './structures/MUserStats'; +import { roboChimpSyncData } from './roboChimp'; import { fetchCLLeaderboard } from './util/clLeaderboard'; import { insertUserEvent } from './util/userEvents'; -async function createHistoricalData(user: MUser): Promise { - const clStats = calcCLDetails(user); - const clRank = await roboChimpClient.$queryRawUnsafe<{ count: number }[]>(roboChimpCLRankQuery(BigInt(user.id))); - const { totalMastery, compCapeProgress } = await calculateMastery(user, await MUserStats.fromID(user.id)); - - return { - user_id: user.id, - GP: user.GP, - total_xp: sumArr(Object.values(user.skillsAsXP)), - cl_completion_percentage: clStats.percent, - cl_completion_count: clStats.owned.length, - cl_global_rank: Number(clRank[0].count), - comp_cape_percent: compCapeProgress.totalPercentTrimmed, - comp_cape_percent_untrimmed: compCapeProgress.totalPercentUntrimmed, - mastery_percentage: totalMastery - }; +async function fetchCurrentMastery(user: MUser) { + const [masteryRank, mastery, clPercentRank] = await prisma.$transaction([ + prisma.user.count({ + where: { + mastery: { + gt: user.user.mastery + } + } + }), + prisma.user.findFirst({ + where: { + id: user.id + }, + select: { + mastery: true + } + }), + prisma.user.count({ + where: { + cl_percent: { + gt: user.user.cl_percent + } + } + }) + ]); + return { masteryRank, mastery, clPercentRank }; } export async function handleNewCLItems({ @@ -47,17 +52,31 @@ export async function handleNewCLItems({ .filter(i => !previousCL.has(i.id) && newCL.has(i.id) && allCLItems.includes(i.id)); const didGetNewCLItem = newCLItems && newCLItems.length > 0; - if (didGetNewCLItem || roll(30)) { - await prisma.historicalData.create({ data: await createHistoricalData(user) }); - } - if (!didGetNewCLItem) return; const previousCLDetails = calcCLDetails(previousCL); - const previousCLRank = previousCLDetails.percent >= 80 ? await calculateOwnCLRanking(user.id) : null; - await roboChimpSyncData(user, newCL); - const newCLRank = previousCLDetails.percent >= 80 ? await calculateOwnCLRanking(user.id) : null; + const { mastery: previousMastery } = await fetchCurrentMastery(user); + + await roboChimpSyncData(user); + + const { + mastery: newMastery, + masteryRank: newMasteryRank, + clPercentRank: newCLPercentRank + } = await fetchCurrentMastery(user); + + // Mastery tier message + if (newMastery && previousMastery) { + for (const percentage of [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 99]) { + if (previousMastery.mastery < percentage && newMastery.mastery >= percentage) { + globalClient.emit( + Events.ServerNotification, + `${user.badgedUsername} just reached ${percentage}% Mastery! They are now rank ${newMasteryRank} in mastery.` + ); + } + } + } const newCLDetails = calcCLDetails(newCL); @@ -66,13 +85,7 @@ export async function handleNewCLItems({ const milestonePercentages = [25, 50, 70, 80, 90, 95, 100]; for (const milestone of milestonePercentages) { if (previousCLDetails.percent < milestone && newCLDetails.percent >= milestone) { - newCLPercentMessage = `${user} just reached ${milestone}% Collection Log completion, after receiving ${newCLItems - .toString() - .slice(0, 500)}!`; - - if (previousCLRank !== newCLRank && newCLRank !== null && previousCLRank !== null) { - newCLPercentMessage += ` In the overall CL leaderboard, they went from rank ${previousCLRank} to rank ${newCLRank}.`; - } + newCLPercentMessage = `${user} just reached ${milestone}% Collection Log completion!`; } break; } @@ -96,18 +109,9 @@ export async function handleNewCLItems({ type: UserEventType.CLCompletion, collectionLogName: finishedCL.name }); - const kcString = finishedCL.fmtProg - ? `They finished after... ${await finishedCL.fmtProg({ - getKC: (id: number) => user.getKC(id), - user, - minigames: await user.fetchMinigames(), - stats: await MUserStats.fromID(user.id) - })}!` - : ''; const nthUser = ( await fetchCLLeaderboard({ - ironmenOnly: false, items: finishedCL.items, resultLimit: 100_000, method: 'raw_cl', @@ -115,11 +119,9 @@ export async function handleNewCLItems({ }) ).filter(u => u.qty === finishedCL.items.length).length; - const placeStr = nthUser > 100 ? '' : ` They are the ${formatOrdinal(nthUser)} user to finish this CL.`; - globalClient.emit( Events.ServerNotification, - `${user.badgedUsername} just finished the ${finishedCL.name} collection log!${placeStr} ${kcString}` + `${user.badgedUsername} just finished the ${finishedCL.name} collection log! They are the ${formatOrdinal(nthUser)} user to finish this CL, and they are now rank ${newCLPercentRank} in overall CL completion.` ); } } diff --git a/src/lib/invention/inventions.ts b/src/lib/invention/inventions.ts index b16b634222..e311fc080a 100644 --- a/src/lib/invention/inventions.ts +++ b/src/lib/invention/inventions.ts @@ -9,7 +9,6 @@ import type { IMaterialBank, MaterialType } from '.'; import { type ClueTier, ClueTiers } from '../clues/clueTiers'; import type { ItemBank } from '../types'; import { formatDuration, stringMatches, toKMB } from '../util'; -import { mahojiClientSettingsFetch, mahojiClientSettingsUpdate } from '../util/clientSettings'; import getOSItem from '../util/getOSItem'; import { logError } from '../util/logError'; import { minionIsBusy } from '../util/minionIsBusy'; @@ -539,8 +538,7 @@ export async function transactMaterialsFromUser({ add, remove, addToDisassembledItemsBank, - addToResearchedMaterialsBank, - addToGlobalInventionCostBank + addToResearchedMaterialsBank }: { user: MUser; add?: MaterialBank; @@ -567,14 +565,6 @@ export async function transactMaterialsFromUser({ .add(user.user.researched_materials_bank as IMaterialBank).bank; } - if (addToGlobalInventionCostBank && remove) { - const current = await mahojiClientSettingsFetch({ invention_materials_cost: true }); - await mahojiClientSettingsUpdate({ - invention_materials_cost: new MaterialBank(current.invention_materials_cost as IMaterialBank).add(remove) - .bank - }); - } - await user.update(updateObject); } diff --git a/src/lib/leagues/easyTasks.ts b/src/lib/leagues/easyTasks.ts index 1b90e4c6c4..e473e871ca 100644 --- a/src/lib/leagues/easyTasks.ts +++ b/src/lib/leagues/easyTasks.ts @@ -327,13 +327,6 @@ export const easyTasks: Task[] = [ return contract.contract.contractsCompleted >= 1; } }, - { - id: 44, - name: 'Complete an Item Contract', - has: async ({ mahojiUser }) => { - return mahojiUser.total_item_contracts >= 1; - } - }, { id: 45, name: 'Kill 10 unique monsters', diff --git a/src/lib/leagues/eliteTasks.ts b/src/lib/leagues/eliteTasks.ts index 94c91bb267..9e01119a08 100644 --- a/src/lib/leagues/eliteTasks.ts +++ b/src/lib/leagues/eliteTasks.ts @@ -299,20 +299,6 @@ export const eliteTasks: Task[] = [ return contract.contract.contractsCompleted >= 100; } }, - { - id: 3037, - name: 'Complete 50 Item Contracts', - has: async ({ mahojiUser }) => { - return mahojiUser.total_item_contracts >= 50; - } - }, - { - id: 3038, - name: 'Achieve an Item Contract streak of 20', - has: async ({ mahojiUser }) => { - return mahojiUser.item_contract_streak >= 20; - } - }, { id: 3039, name: 'Kill 150 unique monsters', diff --git a/src/lib/leagues/hardTasks.ts b/src/lib/leagues/hardTasks.ts index b05c1ca915..2ff8ecc032 100644 --- a/src/lib/leagues/hardTasks.ts +++ b/src/lib/leagues/hardTasks.ts @@ -512,20 +512,6 @@ export const hardTasks: Task[] = [ return contract.contract.contractsCompleted >= 50; } }, - { - id: 2060, - name: 'Complete 30 Item Contracts', - has: async ({ mahojiUser }) => { - return mahojiUser.total_item_contracts >= 30; - } - }, - { - id: 2061, - name: 'Achieve an Item Contract streak of 15', - has: async ({ mahojiUser }) => { - return mahojiUser.item_contract_streak >= 15; - } - }, { id: 2062, name: 'Kill 75 unique monsters', diff --git a/src/lib/leagues/leagues.ts b/src/lib/leagues/leagues.ts index 8564343c29..39dd72cfc4 100644 --- a/src/lib/leagues/leagues.ts +++ b/src/lib/leagues/leagues.ts @@ -1,6 +1,6 @@ import type { CommandResponse } from '@oldschoolgg/toolkit'; import { type User, activity_type_enum } from '@prisma/client'; -import { calcWhatPercent, sumArr } from 'e'; +import { calcWhatPercent } from 'e'; import { Bank } from 'oldschooljs'; import { getPOH } from '../../mahoji/lib/abstracted_commands/pohCommand'; @@ -16,7 +16,6 @@ import { personalSpellCastStats, personalWoodcuttingStats } from '../../mahoji/lib/abstracted_commands/statCommand'; -import { BitField } from '../constants'; import { calcCLDetails } from '../data/Collections'; import { getMinigameEntity } from '../settings/minigames'; @@ -31,42 +30,32 @@ import { hardTasks } from './hardTasks'; import { type HasFunctionArgs, type Task, betterHerbloreStats } from './leaguesUtils'; import { masterTasks } from './masterTasks'; import { mediumTasks } from './mediumTasks'; -import { - calcLeaguesRanking, - calculateAllFletchedItems, - calculateDartsFletchedFromScratch, - totalLampedXP -} from './stats'; +import { calculateAllFletchedItems, calculateDartsFletchedFromScratch } from './stats'; export const leagueTasks = [ - { name: 'Easy', tasks: easyTasks, points: 20 }, - { name: 'Medium', tasks: mediumTasks, points: 45 }, - { name: 'Hard', tasks: hardTasks, points: 85 }, - { name: 'Elite', tasks: eliteTasks, points: 150 }, - { name: 'Master', tasks: masterTasks, points: 250 } + { name: 'Easy', tasks: easyTasks }, + { name: 'Medium', tasks: mediumTasks }, + { name: 'Hard', tasks: hardTasks }, + { name: 'Elite', tasks: eliteTasks }, + { name: 'Master', tasks: masterTasks } ]; -export const maxLeaguesPoints = sumArr(leagueTasks.map(group => group.tasks.length * group.points)); - export const allLeagueTasks = leagueTasks.map(i => i.tasks).flat(2); export function generateLeaguesTasksTextFile(finishedTasksIDs: number[], excludeFinished: boolean | undefined) { let totalTasks = 0; - let totalPoints = 0; let str = ''; - for (const { name, tasks, points } of leagueTasks) { + for (const { name, tasks } of leagueTasks) { const realTasks = excludeFinished ? tasks.filter(i => !finishedTasksIDs.includes(i.id)) : tasks; - const ptsThisGroup = realTasks.length * points; - str += `--------- ${name} (${realTasks.length} tasks - ${ptsThisGroup.toLocaleString()} points) -----------\n`; + str += `--------- ${name} (${realTasks.length} tasks - -----------\n`; str += realTasks .map(i => i.name) .sort((a, b) => b.localeCompare(a)) .join('\n'); totalTasks += realTasks.length; - totalPoints += ptsThisGroup; str += '\n\n'; } - str = `There are a total of ${totalTasks} tasks (${totalPoints.toLocaleString()} Points).\n\n${str}`; + str = `There are a total of ${totalTasks} tasks.\n\n${str}`; return { files: [{ attachment: Buffer.from(str), name: 'all-tasks.txt' }] }; } @@ -117,7 +106,6 @@ function calcSuppliesUsedForSmithing(itemsSmithed: Bank) { export async function leaguesCheckUser(userID: string) { const mahojiUser = await mUserFetch(userID); - const roboChimpUser = await mahojiUser.fetchRobochimpUser(); const [ conStats, poh, @@ -135,7 +123,6 @@ export async function leaguesCheckUser(userID: string) { collectingStats, woodcuttingStats, { actualCluesBank }, - ranking, smeltingStats, stashUnits, fletchedItems @@ -156,7 +143,6 @@ export async function leaguesCheckUser(userID: string) { personalCollectingStats(mahojiUser), personalWoodcuttingStats(mahojiUser), mahojiUser.calcActualClues(), - calcLeaguesRanking(roboChimpUser), personalSmeltingStats(mahojiUser), getParsedStashUnits(userID), calculateAllFletchedItems(mahojiUser) @@ -206,7 +192,6 @@ export async function leaguesCheckUser(userID: string) { actualClues: actualCluesBank, smeltingStats, stashUnits, - totalLampedXP: totalLampedXP(userStats), userStats, fletchedItems, fromScratchDarts: calculateDartsFletchedFromScratch({ @@ -237,102 +222,32 @@ export async function leaguesCheckUser(userID: string) { return { content: `**Your Leagues Progress** -**Total Tasks Completed:** ${totalFinished} (${calcWhatPercent(totalFinished, totalTasks).toFixed(1)}%) (Rank ${ - ranking.tasksRanking - }) -**Total Points:** ${roboChimpUser.leagues_points_total.toLocaleString()} (Rank ${ranking.pointsRanking}) -**Points Balance:** ${roboChimpUser.leagues_points_balance_osb.toLocaleString()} OSB / ${roboChimpUser.leagues_points_balance_bso.toLocaleString()} BSO +**Total Tasks Completed:** ${totalFinished} (${calcWhatPercent(totalFinished, totalTasks).toFixed(1)}%) ${resStr}`, - finished: [...finishedIDs, ...roboChimpUser.leagues_completed_tasks_ids] + finished: [...finishedIDs, ...mahojiUser.user.leagues_completed_tasks_ids] }; } -const unlockables: { - name: string; - points: number; - onUnlock: (user: MUser) => Promise; - hasUnlockedAlready: (user: MUser) => boolean; -}[] = [ - { - name: 'Brain lee pet', - points: 40_000, - onUnlock: async (user: MUser) => { - await user.addItemsToBank({ items: new Bank().add('Brain lee'), collectionLog: true }); - return 'You received a very brainly Brain lee!'; - }, - hasUnlockedAlready: (user: MUser) => { - return user.cl.has('Brain lee'); - } - }, - { - name: '+1m max trip length', - points: 50_000, - onUnlock: async (user: MUser) => { - await user.update({ - bitfield: { - push: BitField.HasLeaguesOneMinuteLengthBoost - } - }); - return "You've unlocked a global +1minute trip length boost!"; - }, - hasUnlockedAlready: (user: MUser) => { - return user.bitfield.includes(BitField.HasLeaguesOneMinuteLengthBoost); - } - } -]; +export async function leaguesClaimCommand(user: MUser, finishedTaskIDs: number[]) { + const newlyFinishedTasks = finishedTaskIDs.filter(i => !user.user.leagues_completed_tasks_ids.includes(i)); -export async function leaguesClaimCommand(userID: string, finishedTaskIDs: number[]) { - const mahojiUser = await mUserFetch(userID); - const roboChimpUser = await mahojiUser.fetchRobochimpUser(); - - const newlyFinishedTasks = finishedTaskIDs.filter(i => !roboChimpUser.leagues_completed_tasks_ids.includes(i)); - - const unlockMessages: string[] = []; - for (const unl of unlockables) { - if (roboChimpUser.leagues_points_total >= unl.points && !unl.hasUnlockedAlready(mahojiUser)) { - const result = await unl.onUnlock(mahojiUser); - unlockMessages.push(result); - } - } - - if (unlockMessages.length > 0) { - return `**You unlocked...** ${unlockMessages.join(', ')}`; - } - - if (newlyFinishedTasks.length === 0) return "You don't have any unclaimed points."; + if (newlyFinishedTasks.length === 0) return "You don't have any unclaimed tasks."; const fullNewlyFinishedTasks: Task[] = []; - let pointsToAward = 0; for (const task of newlyFinishedTasks) { const group = leagueTasks.find(i => i.tasks.some(t => t.id === task))!; - pointsToAward += group.points; fullNewlyFinishedTasks.push(group.tasks.find(i => i.id === task)!); } - const newUser = await roboChimpClient.user.update({ - where: { - id: BigInt(userID) + await user.update({ + leagues_completed_tasks_ids: { + push: newlyFinishedTasks }, - data: { - leagues_completed_tasks_ids: { - push: newlyFinishedTasks - }, - leagues_points_balance_osb: { - increment: pointsToAward - }, - leagues_points_balance_bso: { - increment: pointsToAward - }, - leagues_points_total: { - increment: pointsToAward - } - } + leagues_completed_tasks_count: new Set([...user.user.leagues_completed_tasks_ids, ...newlyFinishedTasks]).size }); - const newTotal = newUser.leagues_points_total; - const response: Awaited = { - content: `You claimed ${newlyFinishedTasks.length} tasks, and received ${pointsToAward} points. You now have a balance of ${newUser.leagues_points_balance_osb} OSB points and ${newUser.leagues_points_balance_bso} BSO points, and ${newTotal} total points. You have completed a total of ${newUser.leagues_completed_tasks_ids.length} tasks.` + content: `You claimed ${newlyFinishedTasks.length} tasks, you have completed a total of ${user.user.leagues_completed_tasks_ids.length} tasks.` }; if (newlyFinishedTasks.length > 10) { diff --git a/src/lib/leagues/leaguesUtils.ts b/src/lib/leagues/leaguesUtils.ts index fe36b35157..618f8c217a 100644 --- a/src/lib/leagues/leaguesUtils.ts +++ b/src/lib/leagues/leaguesUtils.ts @@ -49,7 +49,6 @@ export interface HasFunctionArgs { actualClues: Bank; smeltingStats: Bank; stashUnits: ParsedUnit[]; - totalLampedXP: number; userStats: UserStats; fletchedItems: Bank; fromScratchDarts: Bank; diff --git a/src/lib/leagues/masterTasks.ts b/src/lib/leagues/masterTasks.ts index 953c7a4fc8..d5fc2d4d6f 100644 --- a/src/lib/leagues/masterTasks.ts +++ b/src/lib/leagues/masterTasks.ts @@ -759,20 +759,6 @@ export const masterTasks: Task[] = [ ); } }, - { - id: 4104, - name: 'Complete 100 Item Contracts', - has: async ({ mahojiUser }) => { - return mahojiUser.total_item_contracts >= 100; - } - }, - { - id: 4105, - name: 'Achieve an Item Contract streak of 50', - has: async ({ mahojiUser }) => { - return mahojiUser.item_contract_streak >= 50; - } - }, { id: 4106, name: 'Buy every dungeoneering reward', diff --git a/src/lib/leagues/mediumTasks.ts b/src/lib/leagues/mediumTasks.ts index 22430d5ea9..ee9f92c3af 100644 --- a/src/lib/leagues/mediumTasks.ts +++ b/src/lib/leagues/mediumTasks.ts @@ -32,13 +32,13 @@ import { implings } from '../implings'; import { TormentedDemon } from '../minions/data/killableMonsters/custom/TormentedDemon'; import { QueenBlackDragon } from '../minions/data/killableMonsters/custom/demiBosses'; +import { LampTable } from '../simulation/grandmasterClue'; import Darts from '../skilling/skills/fletching/fletchables/darts'; import Javelins from '../skilling/skills/fletching/fletchables/javelins'; import { ashes } from '../skilling/skills/prayer'; import type { ItemBank } from '../types'; import { calcCombatLevel, calcTotalLevel } from '../util'; import resolveItems from '../util/resolveItems'; -import { LampTable } from '../xpLamps'; import { type Task, leaguesHasCatches, leaguesHasKC, leaguesSlayerTaskForMonster } from './leaguesUtils'; import { calculateChargedItems, calculateTiarasMade, calculateTotalMahoganyHomesPoints } from './stats'; @@ -399,20 +399,6 @@ export const mediumTasks: Task[] = [ return (activityCounts.Birdhouse ?? 0) >= 1; } }, - { - id: 1056, - name: 'Complete 10 Item Contracts', - has: async ({ mahojiUser }) => { - return mahojiUser.total_item_contracts >= 10; - } - }, - { - id: 1057, - name: 'Achieve an Item Contract streak of 5', - has: async ({ mahojiUser }) => { - return mahojiUser.item_contract_streak >= 5; - } - }, { id: 1058, name: 'Kill 25 unique monsters', diff --git a/src/lib/leagues/stats.ts b/src/lib/leagues/stats.ts index f330d644ae..0c8debe4d9 100644 --- a/src/lib/leagues/stats.ts +++ b/src/lib/leagues/stats.ts @@ -1,37 +1,10 @@ -import type { UserStats, XpGainSource } from '@prisma/client'; -import type { User as RoboChimpUser } from '@prisma/robochimp'; -import { sumArr } from 'e'; import { Bank } from 'oldschooljs'; import { gloriesInventorySize, wealthInventorySize } from '../constants'; import Darts from '../skilling/skills/fletching/fletchables/darts'; -import type { ItemBank } from '../types'; import { getItem } from '../util/getOSItem'; -export function totalLampedXP(userStats: UserStats) { - return sumArr(Object.values(userStats.lamped_xp as ItemBank)); -} - -export async function calcLeaguesRanking(user: RoboChimpUser) { - const [pointsRanking, tasksRanking] = await Promise.all([ - roboChimpClient.user.count({ - where: { - leagues_points_total: { - gt: user.leagues_points_total - } - } - }), - roboChimpClient.$queryRaw`SELECT COUNT(*)::int AS count -FROM public.user -WHERE COALESCE(cardinality(leagues_completed_tasks_ids), 0) > ${user.leagues_completed_tasks_ids.length};` - ]); - return { - pointsRanking: pointsRanking + 1, - tasksRanking: (tasksRanking[0].count as number) + 1 - }; -} - export async function calculateAllFletchedItems(user: MUser) { const result = await prisma.$queryRawUnsafe<{ name: string; total: number }[]>(`SELECT data->>'fletchableName' AS name, SUM((data->>'quantity')::int) AS total FROM activity @@ -112,16 +85,3 @@ AND user_id = '${user.id}'::bigint AND data->>'points' IS NOT NULL;`); return Number(result[0].total); } - -export async function calculateXPSources(user: MUser) { - const result = await prisma.$queryRawUnsafe<{ source: XpGainSource; total: bigint }[]>(`SELECT "xp_gains"."source" AS source, SUM(xp) AS total -FROM xp_gains -WHERE "xp_gains"."source" IS NOT NULL -AND user_id = '${user.id}'::bigint -GROUP BY "xp_gains"."source";`); - const obj: Partial> = {}; - for (const res of result) { - obj[res.source] = Number(res.total); - } - return obj; -} diff --git a/src/lib/lootTrack.ts b/src/lib/lootTrack.ts deleted file mode 100644 index c35cbffa03..0000000000 --- a/src/lib/lootTrack.ts +++ /dev/null @@ -1,154 +0,0 @@ -import type { LootTrack, loot_track_type } from '@prisma/client'; -import { Time } from 'e'; -import { Bank } from 'oldschooljs'; - -import type { ItemBank } from './types'; -import { cleanString, formatDuration } from './util'; -import { makeBankImage } from './util/makeBankImage'; - -type TrackLootOptions = - | { - id: string; - type: loot_track_type; - duration: number; - kc: number; - totalLoot: Bank; - changeType: 'loot'; - users: { - id: string; - loot: Bank; - duration: number; - }[]; - suffix?: 'tame'; - } - | { - id: string; - type: loot_track_type; - totalCost: Bank; - changeType: 'cost'; - users: { - id: string; - cost: Bank; - }[]; - suffix?: 'tame'; - }; - -async function trackIndividualsLoot({ - key, - userID, - data, - bankToAdd, - duration, - type -}: { - duration: number; - bankToAdd: Bank; - key: string; - userID: bigint | null; - data: TrackLootOptions; - type: loot_track_type; -}) { - // Find the existing loot track - const current = await prisma.lootTrack.findFirst({ - where: { - key, - user_id: userID, - type - } - }); - - // If no existing loot track, create one. - if (!current) { - return prisma.lootTrack.create({ - data: { - key, - total_kc: data.changeType === 'loot' ? data.kc : 0, - total_duration: duration, - [data.changeType]: bankToAdd.bank, - type: data.type, - user_id: userID - } - }); - } - // If there was one, update it. - return prisma.lootTrack.update({ - where: { - id: current.id - }, - data: { - total_duration: - data.changeType === 'loot' - ? { - increment: duration - } - : undefined, - total_kc: - data.changeType === 'loot' - ? { - increment: data.kc - } - : undefined, - [data.changeType]: new Bank(current?.[data.changeType] as ItemBank | undefined).add(bankToAdd).bank, - user_id: userID - } - }); -} - -export async function trackLoot(opts: TrackLootOptions) { - let key = cleanString(opts.id).toLowerCase().replace(/ /g, '_'); - if (opts.suffix) { - key = `${key}-${opts.suffix}`; - } - const totalBank = opts.changeType === 'cost' ? opts.totalCost : opts.totalLoot; - if (totalBank.length === 0) return; - - let teamDuration = 0; - if (opts.changeType === 'loot') { - teamDuration = Math.floor((opts.duration * opts.users.length) / Time.Minute); - } - - if (opts.users) { - await Promise.all( - opts.users.map(u => - trackIndividualsLoot({ - key, - bankToAdd: 'cost' in u ? u.cost : u.loot, - duration: 'duration' in opts ? Math.floor(opts.duration / Time.Minute) : 0, - data: opts, - userID: BigInt(u.id), - type: opts.type - }) - ) - ); - } - return trackIndividualsLoot({ - key, - bankToAdd: totalBank, - duration: teamDuration, - data: opts, - userID: null, - type: opts.type - }); -} - -export async function getAllTrackedLootForUser(userID: string) { - return prisma.lootTrack.findMany({ - where: { - user_id: BigInt(userID) - } - }); -} - -export async function getDetailsOfSingleTrackedLoot(user: MUser, trackedLoot: LootTrack) { - const [cost, loot] = await Promise.all([ - makeBankImage({ bank: new Bank(trackedLoot.cost as ItemBank), title: `Cost For ${trackedLoot.key}` }), - makeBankImage({ bank: new Bank(trackedLoot.loot as ItemBank), title: `Loot For ${trackedLoot.key}` }) - ]); - - return { - content: `Loot/Cost from ${trackedLoot.total_kc.toLocaleString()}x ${trackedLoot.key} for ${user.rawUsername} -**Total Duration:** ${formatDuration(trackedLoot.total_duration * Time.Minute)} -**Total KC:** ${trackedLoot.total_kc}`, - files: [cost.file, loot.file] - }; -} diff --git a/src/lib/marketPrices.ts b/src/lib/marketPrices.ts deleted file mode 100644 index 9333c0e5d3..0000000000 --- a/src/lib/marketPrices.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { notEmpty } from 'e'; -import groupBy from 'lodash/groupBy'; -import mapValues from 'lodash/mapValues'; -import max from 'lodash/max'; -import min from 'lodash/min'; -import orderBy from 'lodash/orderBy'; -import pickBy from 'lodash/pickBy'; -import sortBy from 'lodash/sortBy'; -import sumBy from 'lodash/sumBy'; -import uniqBy from 'lodash/uniqBy'; -import type { Bank } from 'oldschooljs'; - -import { mean, medianSorted, quantileSorted } from 'simple-statistics'; - -import { getItem } from './util/getOSItem'; - -interface MarketPriceData { - totalSold: number; - transactionCount: number; - avgSalePrice: number; - minSalePrice: number | undefined; - maxSalePrice: number | undefined; - medianSalePrice: number; - avgSalePriceWithoutOutliers: number; - itemID: number; - guidePrice: number; - averagePriceLast100: number; -} - -export const marketPricemap = new Map(); - -export const cacheGEPrices = async () => { - const transactionAge = new Date(); - transactionAge.setDate(transactionAge.getDate() - 60); - - const rawTransactions = await prisma.gETransaction.findMany({ - where: { - created_at: { - gte: transactionAge - } - }, - include: { - sell_listing: { - select: { - item_id: true, - user_id: true - } - }, - buy_listing: { - select: { - user_id: true - } - } - } - }); - - // Group transactions by item_id - const groupedByItem = groupBy(rawTransactions, transaction => transaction.sell_listing.item_id); - - // Pick items that have at least 5 transactions from 4 different buyers - const filtered = pickBy(groupedByItem, group => { - const uniqueBuyers = uniqBy(group, transaction => transaction.buy_listing.user_id); - return uniqueBuyers.length >= 4 && group.length >= 5; - }); - - // For each group, calculate necessary metrics. - mapValues(filtered, transactions => { - const prices = transactions.map(t => Number(t.price_per_item_before_tax)); - - // Calculate percentiles and IQR - const sortedPrices = sortBy(prices); - - const q1 = quantileSorted(sortedPrices, 0.25); - const q3 = quantileSorted(sortedPrices, 0.75); - const iqr = q3 - q1; - - // Filter outliers - const filteredPrices = sortedPrices.filter(price => price >= q1 - 1.5 * iqr && price <= q3 + 1.5 * iqr); - - const medianSalePrice = medianSorted(sortedPrices); - const avgSalePriceWithoutOutliers = mean(filteredPrices); - const guidePrice = Math.round((medianSalePrice + avgSalePriceWithoutOutliers) / 2); - - // Sort transactions by date (newest to oldest) - const sortedTransactions = orderBy(transactions, 'created_at', 'desc'); - const latest100Transactions = sortedTransactions.slice(0, 100); - const averagePriceLast100 = mean(latest100Transactions.map(t => Number(t.price_per_item_before_tax))); - - const totalUniqueTraders = new Set( - ...transactions.map(t => [t.buy_listing.user_id, t.sell_listing.user_id].filter(notEmpty)) - ); - - const data = { - totalSold: sumBy(transactions, 'quantity_bought'), - transactionCount: transactions.length, - avgSalePrice: mean(sortedPrices), - minSalePrice: min(sortedPrices), - maxSalePrice: max(sortedPrices), - medianSalePrice, - avgSalePriceWithoutOutliers, - itemID: transactions[0].sell_listing.item_id, - guidePrice, - averagePriceLast100, - totalUniqueTraders - }; - marketPricemap.set(data.itemID, data); - }); -}; - -export function marketPriceOfBank(bank: Bank) { - let value = 0; - for (const [item, qty] of bank.items()) { - if (!item) continue; - value += marketPriceOrBotPrice(item.id) * qty; - } - return Math.ceil(value); -} - -export function marketPriceOrBotPrice(itemID: number) { - const item = getItem(itemID); - if (!item) return 0; - const data = marketPricemap.get(item.id); - if (data) { - return data.guidePrice; - } - return item.price; -} diff --git a/src/lib/mastery.ts b/src/lib/mastery.ts index 8a9a1c5181..e8940fc4da 100644 --- a/src/lib/mastery.ts +++ b/src/lib/mastery.ts @@ -5,14 +5,13 @@ import { calculateCompCapeProgress } from './bso/calculateCompCapeProgress'; import { allCombatAchievementTasks } from './combat_achievements/combatAchievements'; import { MAX_XP } from './constants'; import { getTotalCl } from './data/Collections'; -import { maxLeaguesPoints } from './leagues/leagues'; +import { allLeagueTasks } from './leagues/leagues'; import { MAX_QP } from './minions/data/quests'; import { SkillsEnum } from './skilling/types'; import type { MUserStats } from './structures/MUserStats'; export async function calculateMastery(user: MUser, stats: MUserStats) { const [totalClItems, clItems] = await getTotalCl(user, 'collection', stats); - const roboChimpUser = await user.fetchRobochimpUser(); const clCompletionPercentage = round(calcWhatPercent(clItems, totalClItems), 2); const totalXP = sumArr(Object.values(user.skillsAsXP)); const maxTotalXP = Object.values(SkillsEnum).length * MAX_XP; @@ -23,8 +22,6 @@ export async function calculateMastery(user: MUser, stats: MUserStats) { 2 ); - const leaguesPoints = roboChimpUser.leagues_points_total; - const { totalPercentTrimmed, totalPercentUntrimmed } = await calculateCompCapeProgress(user); const masteryFactors = [ @@ -50,7 +47,7 @@ export async function calculateMastery(user: MUser, stats: MUserStats) { }, { name: 'Leagues', - percentage: calcWhatPercent(leaguesPoints, maxLeaguesPoints) + percentage: calcWhatPercent(user.user.leagues_completed_tasks_count, allLeagueTasks.length) }, { name: 'Trimmed Completion', diff --git a/src/lib/metrics.ts b/src/lib/metrics.ts deleted file mode 100644 index 9bf21dfa47..0000000000 --- a/src/lib/metrics.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { CpuInfo } from 'node:os'; -import os from 'node:os'; -import { monitorEventLoopDelay } from 'node:perf_hooks'; -import type { Prisma } from '@prisma/client'; - -const h = monitorEventLoopDelay(); -h.enable(); - -function getMemoryMetrics() { - const usage = process.memoryUsage(); - return { - memorySizeTotal: usage.heapTotal, - memorySizeUsed: usage.heapUsed, - memorySizeExternal: usage.external, - memorySizeRSS: usage.rss - }; -} - -function _totalCpuTime(cpu: CpuInfo) { - if (!cpu || !cpu.times) return 0; - const { user, nice, sys, idle, irq } = cpu.times; - - return user + nice + sys + idle + irq; -} - -function totalCpusTime(cpus: CpuInfo[]) { - return cpus.map(_totalCpuTime).reduce((a, b) => a + b, 0); -} - -let cpus = os.cpus(); -let startUsage = process.cpuUsage(); -function getCPUMetrics() { - const newCpus = os.cpus(); - const newStartUsage = process.cpuUsage(); - - const elapCpuTimeMs = totalCpusTime(newCpus) - totalCpusTime(cpus); - const elapUsage = process.cpuUsage(startUsage); - - cpus = newCpus; - startUsage = newStartUsage; - - const cpuUser = elapUsage.user / 1000; // microseconds to milliseconds - const cpuSystem = elapUsage.system / 1000; - const cpuPercent = (100 * (cpuUser + cpuSystem)) / elapCpuTimeMs; - - return { - cpuUser, - cpuSystem, - cpuPercent - }; -} - -export async function collectMetrics() { - const prismaMetrics = await prisma.$metrics.json(); - const transformed = Object.fromEntries( - [...prismaMetrics.counters, ...prismaMetrics.gauges, ...prismaMetrics.histograms].map(i => [i.key, i.value]) - ); - - const metrics: Omit = { - eventLoopDelayMin: h.min * 1e-6, - eventLoopDelayMax: h.max * 1e-6, - eventLoopDelayMean: h.mean * 1e-6, - ...getMemoryMetrics(), - ...getCPUMetrics(), - prisma_query_total_queries: transformed.query_total_queries as number, - prisma_pool_active_connections: transformed.pool_active_connections as number, - prisma_pool_idle_connections: transformed.pool_idle_connections as number, - prisma_pool_wait_count: transformed.pool_wait_count as number, - prisma_query_active_transactions: transformed.query_active_transactions as number - }; - h.reset(); - - return metrics; -} diff --git a/src/lib/minions/data/bankBackgrounds.ts b/src/lib/minions/data/bankBackgrounds.ts index 2de059d55b..2413f1850f 100644 --- a/src/lib/minions/data/bankBackgrounds.ts +++ b/src/lib/minions/data/bankBackgrounds.ts @@ -1,7 +1,7 @@ import { StoreBitfield } from '@oldschoolgg/toolkit'; import { Bank } from 'oldschooljs'; -import { BitField, PerkTier } from '../../constants'; +import { PerkTier } from '../../constants'; import type { BankBackground } from '../types'; const backgroundImages: BankBackground[] = [ @@ -193,24 +193,6 @@ const backgroundImages: BankBackground[] = [ gpCost: 10_000_000, storeBitField: StoreBitfield.HasSetOneNaturePermanentBankBackgrounds }, - { - id: 14, - name: 'CoX', - image: null, - available: true, - bitfield: BitField.HasPermanentEventBackgrounds, - hasPurple: true, - purpleImage: null, - storeBitField: StoreBitfield.HasDynamicCoXBackgroundPermanentBankBackgrounds - }, - { - id: 15, - name: 'OSB', - image: null, - available: true, - bitfield: BitField.HasPermanentEventBackgrounds, - storeBitField: StoreBitfield.HasSetTwoDarkPermanentBankBackgrounds - }, { id: 16, name: 'Wilderness', diff --git a/src/lib/minions/data/killableMonsters/bosses/dt.ts b/src/lib/minions/data/killableMonsters/bosses/dt.ts index 44ac00bf79..510e9394b9 100644 --- a/src/lib/minions/data/killableMonsters/bosses/dt.ts +++ b/src/lib/minions/data/killableMonsters/bosses/dt.ts @@ -1,14 +1,11 @@ import { Time, roll } from 'e'; import { Bank, Monsters } from 'oldschooljs'; -import { VirtusTable } from 'oldschooljs/dist/simulation/subtables/VirtusTable'; import { deepResolveItems, resolveItems } from 'oldschooljs/dist/util/util'; -import { OSB_VIRTUS_IDS } from '../../../../constants'; import { dukeSucellusCL, theLeviathanCL, theWhispererCL, vardorvisCL } from '../../../../data/CollectionsExport'; import { GearStat } from '../../../../gear/types'; import { SkillsEnum } from '../../../../skilling/types'; import itemID from '../../../../util/itemID'; -import { removeItemsFromLootTable } from '../../../../util/smallUtils'; import type { KillableMonster } from '../../../types'; import { QuestID } from '../../quests'; @@ -777,6 +774,3 @@ export const desertTreasureKillableBosses: KillableMonster[] = [ deathProps: awakenedDeathProps } ]; - -// Remove virtus from drop tables -removeItemsFromLootTable(VirtusTable, OSB_VIRTUS_IDS); diff --git a/src/lib/minions/data/killableMonsters/custom/Yeti.ts b/src/lib/minions/data/killableMonsters/custom/Yeti.ts index 3dcaf290cd..c5a607a03e 100644 --- a/src/lib/minions/data/killableMonsters/custom/Yeti.ts +++ b/src/lib/minions/data/killableMonsters/custom/Yeti.ts @@ -5,7 +5,7 @@ import { GemTable } from 'oldschooljs/dist/simulation/subtables/RareDropTable'; import UncommonSeedDropTable from 'oldschooljs/dist/simulation/subtables/UncommonSeedDropTable'; import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { BitField, YETI_ID } from '../../../../constants'; +import { YETI_ID } from '../../../../constants'; import { GearStat } from '../../../../gear'; import type { CustomMonster } from './customMonsters'; @@ -70,6 +70,5 @@ export const Yeti: CustomMonster = { highestDeathChance: 30, steepness: 0.99, hardness: 0.4 - }, - requiredBitfield: BitField.HasUnlockedYeti + } }; diff --git a/src/lib/minions/data/killableMonsters/custom/bosses/Akumu.ts b/src/lib/minions/data/killableMonsters/custom/bosses/Akumu.ts index 2f0cb554f1..83e38c2f8e 100644 --- a/src/lib/minions/data/killableMonsters/custom/bosses/Akumu.ts +++ b/src/lib/minions/data/killableMonsters/custom/bosses/Akumu.ts @@ -2,13 +2,18 @@ import { Time } from 'e'; import { Bank, Monsters } from 'oldschooljs'; import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { MysteryBoxes } from '../../../../../bsoOpenables'; import { GearStat } from '../../../../../gear'; import { UncutGemTable } from '../../../../../simulation/sharedTables'; import itemID from '../../../../../util/itemID'; import resolveItems from '../../../../../util/resolveItems'; import type { CustomMonster } from '../customMonsters'; +const MysteryBoxes = new LootTable() + .oneIn(55, 'Pet Mystery Box') + .oneIn(165, 'Holiday Mystery Box') + .oneIn(35, 'Equippable mystery box') + .oneIn(35, 'Clothing Mystery Box') + .add('Tradeable Mystery Box'); export const AkumuLootTable = new LootTable() .tertiary(1000, 'Mini akumu') .every('Nightmarish ashes', [5, 10]) diff --git a/src/lib/minions/data/killableMonsters/custom/bosses/KingGoldemar.ts b/src/lib/minions/data/killableMonsters/custom/bosses/KingGoldemar.ts index 4f723758cc..234fef4980 100644 --- a/src/lib/minions/data/killableMonsters/custom/bosses/KingGoldemar.ts +++ b/src/lib/minions/data/killableMonsters/custom/bosses/KingGoldemar.ts @@ -1,9 +1,13 @@ import { Monsters } from 'oldschooljs'; import LootTable from 'oldschooljs/dist/structures/LootTable'; -import { MysteryBoxes } from '../../../../../bsoOpenables'; import setCustomMonster from '../../../../../util/setCustomMonster'; - +const MysteryBoxes = new LootTable() + .oneIn(55, 'Pet Mystery Box') + .oneIn(165, 'Holiday Mystery Box') + .oneIn(35, 'Equippable mystery box') + .oneIn(35, 'Clothing Mystery Box') + .add('Tradeable Mystery Box'); const dwarvenArmorTable = new LootTable() .add('Dwarven full helm') .add('Dwarven platebody') diff --git a/src/lib/minions/functions/blowpipeCommand.ts b/src/lib/minions/functions/blowpipeCommand.ts index 140608de2b..0eae448955 100644 --- a/src/lib/minions/functions/blowpipeCommand.ts +++ b/src/lib/minions/functions/blowpipeCommand.ts @@ -129,7 +129,7 @@ async function addCommand(user: MUser, itemName: string, quantity = 1) { if (!userBank.has(itemsToRemove.bank)) { return `You don't own ${itemsToRemove}.`; } - await user.removeItemsFromBank(itemsToRemove); + await user.transactItems({ itemsToRemove: itemsToRemove, shouldRemap: false }); await user.update({ blowpipe: currentData as any as Prisma.InputJsonObject }); diff --git a/src/lib/minions/functions/degradeableItemsCommand.ts b/src/lib/minions/functions/degradeableItemsCommand.ts index 79a2985eaf..568b95acfa 100644 --- a/src/lib/minions/functions/degradeableItemsCommand.ts +++ b/src/lib/minions/functions/degradeableItemsCommand.ts @@ -6,7 +6,6 @@ import { mahojiParseNumber } from '../../../mahoji/mahojiSettings'; import { degradeableItems } from '../../degradeableItems'; import { stringMatches } from '../../util'; import { handleMahojiConfirmation } from '../../util/handleMahojiConfirmation'; -import { updateBankSetting } from '../../util/updateBankSetting'; export async function degradeableItemsCommand( interaction: ChatInputCommandInteraction, @@ -64,7 +63,6 @@ ${degradeableItems await user.update({ [item.settingsKey]: newCharges }); - await updateBankSetting('degraded_items_cost', cost); return `You added **${cost}** to your ${item.item.name}, it now has ${newCharges} charges.`; } diff --git a/src/lib/minions/functions/removeFoodFromUser.ts b/src/lib/minions/functions/removeFoodFromUser.ts index 4b813ae3dd..fedb96a874 100644 --- a/src/lib/minions/functions/removeFoodFromUser.ts +++ b/src/lib/minions/functions/removeFoodFromUser.ts @@ -6,7 +6,6 @@ import { itemID } from 'oldschooljs/dist/util'; import { Emoji } from '../../constants'; import { Eatables } from '../../data/eatables'; import type { GearSetupType } from '../../gear/types'; -import { updateBankSetting } from '../../util/updateBankSetting'; import getUserFoodFromBank, { getRealHealAmount } from './getUserFoodFromBank'; export default async function removeFoodFromUser({ @@ -69,12 +68,12 @@ export default async function removeFoodFromUser({ if (!minimumHealAmount) return true; return getRealHealAmount(user, food.healAmount) >= minimumHealAmount; }) + .slice(0, 20) .map(i => i.name) .join(', ')}.` ); } else { await transactItems({ userID: user.id, itemsToRemove: foodToRemove }); - await updateBankSetting('economyStats_PVMCost', foodToRemove); return { foodRemoved: foodToRemove, diff --git a/src/lib/minions/functions/unequipAllCommand.ts b/src/lib/minions/functions/unequipAllCommand.ts index 53a49b87f0..8640837edb 100644 --- a/src/lib/minions/functions/unequipAllCommand.ts +++ b/src/lib/minions/functions/unequipAllCommand.ts @@ -32,6 +32,6 @@ export async function unEquipAllCommand( [`gear_${gearType}`]: defaultGear }); - await user.addItemsToBank({ items: refund, collectionLog: false }); + await user.transactItems({ itemsToAdd: refund, collectionLog: false, shouldRemap: false }); return `You unequipped all items (${refund}) from your ${toTitleCase(gearType)} setup.`; } diff --git a/src/lib/minions/functions/unequipPet.ts b/src/lib/minions/functions/unequipPet.ts index f033e06b95..4c334d6fcb 100644 --- a/src/lib/minions/functions/unequipPet.ts +++ b/src/lib/minions/functions/unequipPet.ts @@ -12,7 +12,7 @@ export async function unequipPet(user: MUser) { const loot = new Bank().add(equippedPet); try { - await user.addItemsToBank({ items: loot, collectionLog: false }); + await user.transactItems({ itemsToAdd: loot, collectionLog: false, shouldRemap: false }); } catch (e) { logError(new Error('Failed to add pet to bank'), { user_id: user.id, diff --git a/src/lib/minions/types.ts b/src/lib/minions/types.ts index 777645904f..aecb469987 100644 --- a/src/lib/minions/types.ts +++ b/src/lib/minions/types.ts @@ -1,13 +1,12 @@ import type { Image } from '@napi-rs/canvas'; import type { PerkTier, StoreBitfield } from '@oldschoolgg/toolkit'; -import type { GearSetupType, XpGainSource } from '@prisma/client'; import type { Bank, MonsterKillOptions } from 'oldschooljs'; import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; import type SimpleMonster from 'oldschooljs/dist/structures/SimpleMonster'; import type { ClueTier } from '../clues/clueTiers'; import type { BitField } from '../constants'; -import type { GearStat, OffenceGearStat } from '../gear'; +import type { GearSetupType, GearStat, OffenceGearStat } from '../gear'; import type { POHBoosts } from '../poh'; import type { MinigameName } from '../settings/minigames'; import type { LevelRequirements, SkillsEnum } from '../skilling/types'; @@ -171,7 +170,7 @@ export interface AddXpParams { minimal?: boolean; artificial?: boolean; masterCapeBoost?: boolean; - source?: XpGainSource; + source?: any; } export interface AddMonsterXpParams { @@ -198,21 +197,6 @@ export interface BlowpipeData { dartID: number | null; } -export interface MegaDuckLocation { - x: number; - y: number; - placesVisited: string[]; - usersParticipated: Record; - steps: [number, number][]; -} - -export const defaultMegaDuckLocation: Readonly = { - x: 1356, - y: 209, - usersParticipated: {}, - placesVisited: [], - steps: [] -}; export type Flags = Record; export type FlagMap = Map; export type ClueBank = Record; diff --git a/src/lib/mysteryTrail.ts b/src/lib/mysteryTrail.ts deleted file mode 100644 index aa631af118..0000000000 --- a/src/lib/mysteryTrail.ts +++ /dev/null @@ -1,351 +0,0 @@ -import { Bank, Monsters } from 'oldschooljs'; - -import { convertStoredActivityToFlatActivity } from './settings/prisma'; -import { getUsersCurrentSlayerInfo } from './slayer/slayerUtil'; -import type { ActivityTaskData } from './types/minions'; -import getOSItem from './util/getOSItem'; -import itemID from './util/itemID'; -import resolveItems from './util/resolveItems'; - -/** - * 1. Get Mysterious clue(1) from mysterious stranger - * 2. Go do first step at cows, get dungsoaked msg - * 3. do the clues - * 4. final clue - * - * - * quest boss - * - * skippers wife is luring you to snow area to kill the yeti and save its child. - * - * hunter based - * yeti is hunter boss - * yeti boss at the end, defeated to get the pet/reward - * - */ - -const firstStep = { - hint: `In Lumbridge's dawn, where bovine graze, -Lay one to rest in the morning haze, -In its yield, your path will blaze.`, - didPass: (data: ActivityTaskData) => data.type === 'MonsterKilling' && data.mi === Monsters.Cow.id && data.q === 1 -}; - -const finalStep = { - hint: "The hunters' trail is hot, the hunteds' trail is cold, in the whisper of the snow, the finale will unfold.", - didPass: async (data: ActivityTaskData) => { - if ( - data.type === 'Hunter' && - [ - 'Polar kebbit', - 'Cerulean twitch', - 'Sapphire glacialis', - 'Snowy knight', - 'Sabre-toothed kebbit', - 'Sabre-toothed kyatt' - ].includes(data.creatureName) - ) { - return true; - } - return false; - } -}; - -export const mysteriousStepData = { - 1: { - clueItem: getOSItem('Dungsoaked message'), - loot: new Bank().add('Dungsoaked message').freeze(), - messages: [ - '{minion} feels adventurous, and wants to go on this quest.', - '{minion} is filled with excitement and anticipation for the journey ahead.', - "{minion} can't wait to embark on this new adventure, feeling invigorated." - ] - }, - 2: { - clueItem: getOSItem('Mysterious clue (2)'), - loot: new Bank().add('Bloodsoaked cowhide').add('Mysterious clue (2)').freeze(), - messages: [ - '{minion} is steadfast in continuing to solve this mystery.', - '{minion} is committed and focused on the task at hand.', - '{minion} remains determined, no challenge will deter him.' - ] - }, - 3: { - clueItem: getOSItem('Mysterious clue (3)'), - loot: new Bank().add('Bloodsoaked fur').add('Mysterious clue (3)').freeze(), - messages: ['{minion} is enjoying the treasure trail.'] - }, - 4: { - clueItem: getOSItem('Mysterious clue (4)'), - loot: new Bank().add("Bloodsoaked children's book").add('Mysterious clue (4)').freeze(), - messages: [ - "{minion} feels uneasy about this quest and its' purpose.", - '{minion} starts to doubt the path, sensing a growing unease.', - "{minion} can't shake off the growing apprehension about the quest." - ] - }, - 5: { - clueItem: getOSItem('Mysterious clue (5)'), - loot: new Bank().add('Torn fur').add('Mysterious clue (5)').freeze(), - messages: [ - "{minion} feels a chill creeping over him, something isn't right. Time is running out.", - "{minion}'s confidence wanes, a cold realization that something is amiss. Time is running out.", - '{minion} feels increasingly uncomfortable, danger seems near. Time is running out.' - ] - }, - 6: { - clueItem: getOSItem('Mysterious clue (6)'), - loot: new Bank().add('Mysterious clue (6)').freeze(), - messages: [ - "{minion} feels uneasy, cold, and almost as if they're being watched.", - '{minion} is on edge, fear grows with every step.', - "{minion}'s nerves are frayed, sensing that they are not alone." - ] - }, - 7: { - clueItem: null, - loot: null, - messages: [ - "Cold chills run down {minion}'s spine, dread sets in, this is a bad idea. They do not want to continue.", - '{minion} is paralyzed with terror, knowing that going further is a grave mistake. They ask you to stop.', - '{minion} wants nothing more than to turn back, the sense of danger is too high. What to do?' - ] - } -}; - -export interface Step { - hint: string; - didPass: (data: ActivityTaskData) => boolean | Promise; -} - -export interface Track { - id: number; - steps: [Step, Step, Step, Step, Step, Step, Step]; -} - -export const mysteriousTrailTracks: Track[] = [ - { - id: 1, - steps: [ - firstStep, - { - hint: 'Swine cloak in shadow, not in mud; crack the secret of the rebel bud.', - didPass: (data: ActivityTaskData) => { - if ( - data.type === 'MonsterKilling' && - [Monsters.MaleHamMember.id, Monsters.FemaleHamMember.id].includes(data.mi) - ) { - return true; - } - if ( - data.type === 'Pickpocket' && - [Monsters.MaleHamMember.id, Monsters.FemaleHamMember.id].includes(data.monsterID) - ) { - return true; - } - - return false; - } - }, - { - hint: 'In rows and columns, a green parade, sitting in the dirt, where they were made.', - didPass: (data: ActivityTaskData) => { - if (data.type === 'Collecting' && data.collectableID === itemID('Cabbage')) { - return true; - } - - return false; - } - }, - { - hint: "Weepless boughs and no rain; enigma solved by blaze's gain.", - didPass: (data: ActivityTaskData) => { - if (data.type === 'Firemaking' && data.burnableID === itemID('Willow logs')) { - return true; - } - - return false; - } - }, - { - hint: 'Pick your own destiny, spin the wheel, pull your own strings.', - didPass: async (data: ActivityTaskData) => { - const lastTwoTrips = await prisma.activity.findMany({ - where: { - user_id: BigInt(data.userID), - completed: true - }, - orderBy: { - start_date: 'desc' - }, - take: 2 - }); - const [_spinTrip, _pickTrip] = lastTwoTrips; - if (!_spinTrip || !_pickTrip) return false; - const [spinTrip, pickTrip] = [ - convertStoredActivityToFlatActivity(_spinTrip), - convertStoredActivityToFlatActivity(_pickTrip) - ]; - if (spinTrip.type !== 'Crafting' || spinTrip.craftableID !== itemID('Bow string')) return false; - if (pickTrip.type !== 'Collecting' || pickTrip.collectableID !== itemID('Flax')) return false; - - return true; - } - }, - { - hint: 'Where legion begins, and a fighter finds their glory.', - didPass: async (data: ActivityTaskData) => { - if (data.type === 'BarbarianAssault') return true; - return false; - } - }, - finalStep - ] - }, - { - id: 2, - steps: [ - firstStep, - { - hint: "On the rooftops, a hidden maze, not easy to see, except for a lovers' gaze.", - didPass: (data: ActivityTaskData) => - data.type === 'Agility' && data.courseID === 'Varrock Rooftop Course' - }, - { - hint: 'In rows and columns, a green parade, sitting in the dirt, where they were made.', - didPass: (data: ActivityTaskData) => { - if (data.type === 'Collecting' && data.collectableID === itemID('Cabbage')) { - return true; - } - - return false; - } - }, - { - hint: 'Amidst the blue, an orange clue.', - didPass: (data: ActivityTaskData) => { - if (data.type === 'Fishing' && data.fishID === itemID('Raw lobster')) { - return true; - } - - return false; - } - }, - { - hint: "In the city of a prince, you'll receive your next task.", - didPass: async (data: ActivityTaskData) => { - const task = await getUsersCurrentSlayerInfo(data.userID); - if ( - task.slayerMaster?.name === 'Turael' && - data.type === 'MonsterKilling' && - task.assignedTask?.monsters.includes(data.mi) - ) { - return true; - } - - return false; - } - }, - { - hint: "Where brawn doesn't meet brain, yet must be slain.", - didPass: (data: ActivityTaskData) => { - if ( - data.type === 'MonsterKilling' && - [Monsters.TrollGeneral.id, Monsters.MountainTroll.id].includes(data.mi) - ) { - return true; - } - - return false; - } - }, - finalStep - ] - }, - { - id: 3, - steps: [ - firstStep, - { - hint: 'In the cinema for the mind, find the entrance, then take the exit.', - didPass: (data: ActivityTaskData) => { - if (data.type === 'MageTrainingArena') { - return true; - } - return false; - } - }, - { - hint: 'Where blade meets bark.', - didPass: (data: ActivityTaskData) => data.type === 'Sawmill' - }, - { - hint: 'In the lantern of the swamp, seek refuge from those that chomp.', - didPass: (data: ActivityTaskData) => - data.type === 'Agility' && data.courseID === 'Canifis Rooftop Course' - }, - { - hint: "Black as night, catch them, but don't let them bite.", - didPass: (data: ActivityTaskData) => data.type === 'Hunter' && data.creatureName === 'Black salamander' - }, - { - hint: 'Hear the echo of a crowned roar, in a place too dangerous to explore.', - didPass: (data: ActivityTaskData) => - data.type === 'MonsterKilling' && data.mi === Monsters.KingBlackDragon.id - }, - finalStep - ] - }, - { - id: 4, - steps: [ - firstStep, - { - hint: "Where water bends and warriors stride, a village lives with primal pride, cast into its' shallow tide.", - didPass: (data: ActivityTaskData) => { - return data.type === 'Fishing' && resolveItems(['Raw trout', 'Raw salmon']).includes(data.fishID); - } - }, - { - hint: "Where the earth compensates, at the rivers' surge.", - didPass: (data: ActivityTaskData) => { - return data.type === 'MotherlodeMining'; - } - }, - { - hint: 'Amidst the blue, an orange clue.', - didPass: (data: ActivityTaskData) => { - if (data.type === 'Fishing' && data.fishID === itemID('Raw lobster')) { - return true; - } - - return false; - } - }, - { - hint: 'A secret is hidden, where the weak are forbidden.', - didPass: (data: ActivityTaskData) => { - if (data.type === 'AnimatedArmour' || data.type === 'Cyclops') { - return true; - } - - return false; - } - }, - { - hint: "Where brawn doesn't meet brain, one must be slain.", - didPass: (data: ActivityTaskData) => { - if ( - data.type === 'MonsterKilling' && - [Monsters.TrollGeneral.id, Monsters.MountainTroll.id].includes(data.mi) - ) { - return true; - } - - return false; - } - }, - finalStep - ] - } -]; diff --git a/src/lib/openables.ts b/src/lib/openables.ts index c329bbcb03..1c47db08c1 100644 --- a/src/lib/openables.ts +++ b/src/lib/openables.ts @@ -1,4 +1,3 @@ -import { formatOrdinal } from '@oldschoolgg/toolkit'; import { percentChance, randInt, roll } from 'e'; import { Bank, LootTable, Openables } from 'oldschooljs'; import { SkillsEnum } from 'oldschooljs/dist/constants'; @@ -11,7 +10,7 @@ import LarransChest, { LarransChestOpenable } from 'oldschooljs/dist/simulation/ import { bsoOpenables } from './bsoOpenables'; import { ClueTiers } from './clues/clueTiers'; -import { Emoji, Events, MIMIC_MONSTER_ID } from './constants'; +import { Emoji, MIMIC_MONSTER_ID } from './constants'; import { clueHunterOutfit } from './data/CollectionsExport'; import { defaultFarmingContract } from './minions/farming'; import type { FarmingContract } from './minions/farming/types'; @@ -67,7 +66,6 @@ export interface OpenArgs { quantity: number; user: MUser; self: UnifiedOpenable; - totalLeaguesPoints: number; } export interface UnifiedOpenable { @@ -91,45 +89,6 @@ export interface UnifiedOpenable { trickableItems?: number[]; } -const clueItemsToNotifyOf = resolveItems([ - '3rd age range coif', - '3rd age range top', - '3rd age range legs', - '3rd age vambraces', - '3rd age robe top', - '3rd age robe', - '3rd age mage hat', - '3rd age amulet', - '3rd age plateskirt', - '3rd age platelegs', - '3rd age platebody', - '3rd age full helmet', - '3rd age kiteshield', - '3rd age longsword', - '3rd age wand', - '3rd age cloak', - '3rd age bow', - '3rd age pickaxe', - '3rd age axe', - '3rd age druidic robe bottoms', - '3rd age druidic robe top', - '3rd age druidic staff', - '3rd age druidic cloak' -]) - .concat(ClueTiers.filter(i => Boolean(i.milestoneReward)).map(i => i.milestoneReward!.itemReward)) - .concat( - resolveItems([ - 'Dwarven blessing', - 'First age tiara', - 'First age amulet', - 'First age cape', - 'First age bracelet', - 'First age ring', - 'First age robe bottom', - 'First age robe top' - ]) - ); - const clueOpenables: UnifiedOpenable[] = []; for (const clueTier of ClueTiers) { const casketItem = getOSItem(clueTier.id); @@ -179,7 +138,6 @@ for (const clueTier of ClueTiers) { const stats = await user.fetchStats({ openable_scores: true }); const nthCasket = ((stats.openable_scores as ItemBank)[clueTier.id] ?? 0) + quantity; - let gotMilestoneReward = false; // If this tier has a milestone reward, and their new score meets the req, and // they don't own it already, add it to the loot. if ( @@ -191,22 +149,6 @@ for (const clueTier of ClueTiers) { items: new Bank().add(clueTier.milestoneReward.itemReward), collectionLog: true }); - gotMilestoneReward = true; - } - - // Here we check if the loot has any ultra-rares (3rd age, gilded, bloodhound), - // and send a notification if they got one. - const announcedLoot = loot.filter(i => clueItemsToNotifyOf.includes(i.id), false); - if (gotMilestoneReward) { - announcedLoot.add(clueTier.milestoneReward?.itemReward); - } - if (announcedLoot.length > 0) { - globalClient.emit( - Events.ServerNotification, - `**${user.badgedUsername}'s** minion, ${user.minionName}, just opened their ${formatOrdinal( - nthCasket - )} ${clueTier.name} casket and received **${announcedLoot}**!` - ); } if (loot.length === 0) { @@ -562,15 +504,13 @@ export const allOpenablesIDs = new Set(allOpenables.map(i => i.id)); export function getOpenableLoot({ openable, quantity, - user, - totalLeaguesPoints + user }: { openable: UnifiedOpenable; quantity: number; user: MUser; - totalLeaguesPoints: number; }) { return openable.output instanceof LootTable ? { bank: openable.output.roll(quantity), message: null } - : openable.output({ user, self: openable, quantity, totalLeaguesPoints }); + : openable.output({ user, self: openable, quantity }); } diff --git a/src/lib/party.ts b/src/lib/party.ts index 7e82e6c19f..5d68b23205 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -4,15 +4,14 @@ import type { TextChannel } from 'discord.js'; import { ButtonBuilder, ButtonStyle, ComponentType, InteractionCollector } from 'discord.js'; import { Time, debounce, noOp } from 'e'; -import { production } from '../config'; import { BLACKLISTED_USERS } from './blacklists'; -import { SILENT_ERROR } from './constants'; +import { SILENT_ERROR, globalConfig } from './constants'; import type { MakePartyOptions } from './types'; import { getUsername } from './util'; import { CACHED_ACTIVE_USER_IDS } from './util/cachedUserIDs'; const partyLockCache = new Set(); -if (production) { +if (globalConfig.isProduction) { setInterval(() => { partyLockCache.clear(); }, Time.Minute * 20); @@ -78,7 +77,7 @@ export async function setupParty(channel: TextChannel, leaderUser: MUser, option let partyCancelled = false; const collector = new InteractionCollector(globalClient, { time: Time.Minute * 5, - maxUsers: options.usersAllowed?.length ?? options.maxSize, + maxUsers: 1, dispose: true, channel, componentType: ComponentType.Button, @@ -87,7 +86,7 @@ export async function setupParty(channel: TextChannel, leaderUser: MUser, option CACHED_ACTIVE_USER_IDS.add(interaction.user.id); const user = await mUserFetch(interaction.user.id); if ( - (!options.ironmanAllowed && user.user.minion_ironman) || + !options.ironmanAllowed || interaction.user.bot || user.minionIsBusy || !user.user.minion_hasBought @@ -169,7 +168,7 @@ export async function setupParty(channel: TextChannel, leaderUser: MUser, option reply('You joined this mass.'); - if (usersWhoConfirmed.length >= options.maxSize) { + if (usersWhoConfirmed.length === 1) { collector.stop('everyoneJoin'); break; } diff --git a/src/lib/patreonUtils.ts b/src/lib/patreonUtils.ts deleted file mode 100644 index bcc964ce44..0000000000 --- a/src/lib/patreonUtils.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { BadgesEnum } from './constants'; -import { populateRoboChimpCache } from './perkTier'; - -export async function handleDeletedPatron(userID: string[]) { - const users = await prisma.user.findMany({ - where: { - id: { - in: userID - } - } - }); - - for (const user of users) { - if (user.badges.includes(BadgesEnum.Patron) || user.badges.includes(BadgesEnum.LimitedPatron)) { - await prisma.user.update({ - where: { - id: user.id - }, - data: { - badges: user.badges.filter(b => b !== BadgesEnum.Patron && b !== BadgesEnum.LimitedPatron) - } - }); - } - } - - await populateRoboChimpCache(); -} - -export async function handleEditPatron(userID: string[]) { - const users = await prisma.user.findMany({ - where: { - id: { - in: userID - } - } - }); - - for (const user of users) { - if (!user.badges.includes(BadgesEnum.Patron) && !user.badges.includes(BadgesEnum.LimitedPatron)) { - await prisma.user.update({ - where: { - id: user.id - }, - data: { - badges: { - push: BadgesEnum.Patron - } - } - }); - } - } - - await populateRoboChimpCache(); -} diff --git a/src/lib/perkTier.ts b/src/lib/perkTier.ts deleted file mode 100644 index a4ccaffc4b..0000000000 --- a/src/lib/perkTier.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { pick } from 'lodash'; -import type { RobochimpUser } from './roboChimp'; - -const robochimpCachedKeys = [ - 'bits', - 'github_id', - 'patreon_id', - 'perk_tier', - 'user_group_id', - 'premium_balance_expiry_date', - 'premium_balance_tier' -] as const; -type CachedRoboChimpUser = Pick; - -export const roboChimpCache = new Map(); - -export async function populateRoboChimpCache() { - const users = await roboChimpClient.user.findMany({ - select: { - id: true, - bits: true, - github_id: true, - patreon_id: true, - perk_tier: true, - premium_balance_expiry_date: true, - premium_balance_tier: true, - user_group_id: true - }, - where: { - perk_tier: { - not: 0 - } - } - }); - for (const user of users) { - roboChimpCache.set(user.id.toString(), user); - } - debugLog(`Populated RoboChimp cache with ${users.length} users.`); -} - -export function cacheRoboChimpUser(user: RobochimpUser) { - if (user.perk_tier === 0) return; - roboChimpCache.set(user.id.toString(), pick(user, robochimpCachedKeys)); -} diff --git a/src/lib/perkTiers.ts b/src/lib/perkTiers.ts deleted file mode 100644 index bd60af1e13..0000000000 --- a/src/lib/perkTiers.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { SupportServer } from '../config'; -import { BitField, PerkTier, Roles } from './constants'; -import { roboChimpCache } from './perkTier'; - -export const allPerkBitfields: BitField[] = [ - BitField.IsPatronTier6, - BitField.IsPatronTier5, - BitField.IsPatronTier4, - BitField.IsPatronTier3, - BitField.IsPatronTier2, - BitField.IsPatronTier1, - BitField.HasPermanentTierOne, - BitField.BothBotsMaxedFreeTierOnePerks -]; - -export function getUsersPerkTier(user: MUser): PerkTier | 0 { - if ( - [BitField.isContributor, BitField.isModerator, BitField.IsWikiContributor].some(bit => - user.bitfield.includes(bit) - ) - ) { - return PerkTier.Four; - } - - const elligibleTiers = []; - if ( - user.bitfield.includes(BitField.IsPatronTier1) || - user.bitfield.includes(BitField.HasPermanentTierOne) || - user.bitfield.includes(BitField.BothBotsMaxedFreeTierOnePerks) - ) { - elligibleTiers.push(PerkTier.Two); - } else { - const guild = globalClient.guilds.cache.get(SupportServer); - const member = guild?.members.cache.get(user.id); - if (member && [Roles.Booster].some(roleID => member.roles.cache.has(roleID))) { - elligibleTiers.push(PerkTier.One); - } - } - - if (user.bitfield.includes(BitField.IsPatronTier2) || user.bitfield.includes(BitField.HasPermanentTierOne)) { - elligibleTiers.push(PerkTier.Three); - } - - const roboChimpCached = roboChimpCache.get(user.id); - if (roboChimpCached) { - elligibleTiers.push(roboChimpCached.perk_tier); - } - - const bitfield = user.bitfield; - - if (bitfield.includes(BitField.IsPatronTier6)) { - elligibleTiers.push(PerkTier.Seven); - } - - if (bitfield.includes(BitField.IsPatronTier5)) { - elligibleTiers.push(PerkTier.Six); - } - - if (bitfield.includes(BitField.IsPatronTier4)) { - elligibleTiers.push(PerkTier.Five); - } - - if (bitfield.includes(BitField.IsPatronTier3)) { - elligibleTiers.push(PerkTier.Four); - } - - if (bitfield.includes(BitField.IsPatronTier2)) { - elligibleTiers.push(PerkTier.Three); - } - - return Math.max(...elligibleTiers, 0); -} diff --git a/src/lib/preStartup.ts b/src/lib/preStartup.ts index 79b604bafb..363b2fa115 100644 --- a/src/lib/preStartup.ts +++ b/src/lib/preStartup.ts @@ -1,26 +1,14 @@ -import { syncCustomPrices } from '../mahoji/lib/events'; import { syncActivityCache } from './Task'; -import { cacheBadges } from './badges'; import { syncBlacklists } from './blacklists'; -import { GrandExchange } from './grandExchange'; -import { cacheGEPrices } from './marketPrices'; -import { populateRoboChimpCache } from './perkTier'; import { runStartupScripts } from './startupScripts'; import { runTimedLoggedFn } from './util'; -import { syncActiveUserIDs } from './util/cachedUserIDs'; import { syncDisabledCommands } from './util/syncDisabledCommands'; export async function preStartup() { await Promise.all([ - syncActiveUserIDs(), runTimedLoggedFn('Sync Activity Cache', syncActivityCache), runTimedLoggedFn('Startup Scripts', runStartupScripts), runTimedLoggedFn('Sync Disabled Commands', syncDisabledCommands), - runTimedLoggedFn('Sync Blacklist', syncBlacklists), - runTimedLoggedFn('Syncing prices', syncCustomPrices), - runTimedLoggedFn('Caching badges', cacheBadges), - runTimedLoggedFn('Init Grand Exchange', () => GrandExchange.init()), - runTimedLoggedFn('populateRoboChimpCache', populateRoboChimpCache), - runTimedLoggedFn('Cache G.E Prices', cacheGEPrices) + runTimedLoggedFn('Sync Blacklist', syncBlacklists) ]); } diff --git a/src/lib/premiumPatronTime.ts b/src/lib/premiumPatronTime.ts deleted file mode 100644 index 9780eca836..0000000000 --- a/src/lib/premiumPatronTime.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { ChatInputCommandInteraction } from 'discord.js'; -import { Time } from 'e'; - -import { formatDuration } from '@oldschoolgg/toolkit'; -import { handleMahojiConfirmation } from './util/handleMahojiConfirmation'; - -export async function premiumPatronTime( - timeMs: number, - tier: number, - userToGive: MUser, - interaction: ChatInputCommandInteraction | null -) { - if (![1, 2, 3, 4, 5, 6].includes(tier)) return 'Invalid input.'; - if (timeMs < Time.Second || timeMs > Time.Year * 3) return 'Invalid input.'; - - const currentBalanceTier = userToGive.user.premium_balance_tier; - - if (interaction && currentBalanceTier !== null && currentBalanceTier !== tier) { - await handleMahojiConfirmation( - interaction, - `They already have Tier ${currentBalanceTier}; this will replace the existing balance entirely, are you sure?` - ); - } - - if (interaction) { - await handleMahojiConfirmation( - interaction, - `Are you sure you want to add ${formatDuration(timeMs)} of Tier ${tier} patron to ${userToGive}?` - ); - } - - await userToGive.update({ - premium_balance_tier: tier - }); - - const currentBalanceTime = - userToGive.user.premium_balance_expiry_date === null - ? null - : Number(userToGive.user.premium_balance_expiry_date); - - let newBalanceExpiryTime = 0; - if (currentBalanceTime !== null && tier === currentBalanceTier) { - newBalanceExpiryTime = currentBalanceTime + timeMs; - } else { - newBalanceExpiryTime = Date.now() + timeMs; - } - await userToGive.update({ - premium_balance_expiry_date: newBalanceExpiryTime - }); - - return `Gave ${formatDuration(timeMs)} of Tier ${tier} patron to ${userToGive}. They have ${formatDuration( - newBalanceExpiryTime - Date.now() - )} remaining.`; -} diff --git a/src/lib/randomEvents.ts b/src/lib/randomEvents.ts index 7abff77db0..72967509e3 100644 --- a/src/lib/randomEvents.ts +++ b/src/lib/randomEvents.ts @@ -187,19 +187,19 @@ const cache = new LRUCache({ max: 500 }); const doesntGetRandomEvent: activity_type_enum[] = [activity_type_enum.TombsOfAmascut]; export async function triggerRandomEvent(user: MUser, type: activity_type_enum, duration: number, messages: string[]) { - if (doesntGetRandomEvent.includes(type)) return {}; + if (doesntGetRandomEvent.includes(type)) return null; const minutes = Math.min(30, duration / Time.Minute); const randomEventChance = 60 - minutes; - if (!roll(randomEventChance)) return {}; + if (!roll(randomEventChance)) return null; if (user.bitfield.includes(BitField.DisabledRandomEvents)) { - return {}; + return null; } const prev = cache.get(user.id); // Max 1 event per 3h mins per user if (prev && Date.now() - prev < Time.Hour * 3) { - return {}; + return null; } cache.set(user.id, Date.now()); @@ -219,8 +219,6 @@ export async function triggerRandomEvent(user: MUser, type: activity_type_enum, messages.push('Found a cute Balloon cat!'); } await userStatsBankUpdate(user, 'random_event_completions_bank', new Bank().add(event.id)); - messages.push(`Did ${event.name} random event and got ${loot}`); - return { - itemsToAddWithCL: loot - }; + const res = await user.addItemsToBank({ items: loot, collectionLog: true }); + messages.push(`Did ${event.name} random event and got ${res.itemsAdded}`); } diff --git a/src/lib/randomizer.ts b/src/lib/randomizer.ts new file mode 100644 index 0000000000..3a6a530cda --- /dev/null +++ b/src/lib/randomizer.ts @@ -0,0 +1,226 @@ +import { writeFileSync } from 'node:fs'; +import { miniID, seedShuffle } from '@oldschoolgg/toolkit'; +import { clamp, objectEntries, shuffleArr } from 'e'; +import { Bank, Items } from 'oldschooljs'; +import { umbTable } from './bsoOpenables'; +import { overallPlusItems } from './data/Collections'; +import { RelicID } from './relics'; +import { SkillsArray } from './skilling/types'; +import type { ItemBank } from './types'; +import { resolveItems } from './util'; + +declare module 'oldschooljs' { + interface Bank { + wasRemapped: boolean; + } +} +const itemsNotRandomized = resolveItems(['Coins', 'Untradeable Mystery Box']); + +const allItems = Array.from(Items.keys()).sort((a, b) => b - a); +const allItemsToRandomize = Array.from(Items.keys()) + .sort((a, b) => b - a) + .filter(i => !itemsNotRandomized.includes(i)); +const clItemsToRandomize: number[] = []; +const notCLItemsToRandomize: number[] = []; +const notCLUMBItemsToRandomize: number[] = []; + +for (const item of allItems) { + if (itemsNotRandomized.includes(item)) continue; + if (overallPlusItems.includes(item)) { + clItemsToRandomize.push(item); + } else if (!umbTable.includes(item)) { + notCLItemsToRandomize.push(item); + } else { + notCLUMBItemsToRandomize.push(item); + } +} +writeFileSync( + 'pool1-cl.txt', + `1st Item Pool (Overall+ CL Items)\n${overallPlusItems.map(i => Items.get(i)!.name).join('\n')}` +); +writeFileSync( + 'pool2-cl.txt', + `2nd Item Pool (Not CL and not UMB)\n${notCLItemsToRandomize.map(i => Items.get(i)!.name).join('\n')}` +); +writeFileSync( + 'pool3-cl.txt', + `3rd Item Pool (Not CL and in UMB)\n${notCLUMBItemsToRandomize.map(i => Items.get(i)!.name).join('\n')}` +); + +export function remapBank(user: MUser, bank: Bank) { + if (bank.wasRemapped) return bank; + const map = user.user.item_map as ItemBank; + const newBank: ItemBank = {}; + for (const [_id, qty] of Object.entries(bank.bank)) { + const id = Number.parseInt(_id); + const newItemID = map[id]; + if (typeof newItemID === 'undefined') { + throw new Error(`${user.id} tried to have their bank remapped, but item[${id}] has no mapping`); + } + newBank[newItemID] = clamp(qty, 0, id === 995 ? 1_000_000_000_000 : 10_000_000); + } + const remapped = new Bank(newBank); + remapped.wasRemapped = true; + return remapped; +} + +export function buildItemMapFromList(key: string, items: number[]) { + const shuffledArrayOfItemIDs = seedShuffle(items, key); + const obj: Record = {}; + for (let i = 0; i < shuffledArrayOfItemIDs.length; i++) { + const fromID = shuffledArrayOfItemIDs[i]; + const toID = items[i]; + if (typeof fromID === 'undefined' || typeof toID === 'undefined') { + throw new Error(`Undefined mapping: ${fromID} -> ${toID}`); + } + obj[fromID] = toID; + } + + return obj; +} + +export const randomizationMethods = [ + { + id: 1, + name: 'True Random', + desc: 'Every item is randomly mapped to another item, with no restrictions.', + emoji: '<:TrueRandom:1271796744837009550>' + }, + { + id: 2, + name: 'CL to CL', + desc: 'CL items are only mapped to other CL items, and non-CL items are only mapped to other non-CL items.', + emoji: '<:Collectionlog:1271797160752713782>' + } +] as const; + +export function buildItemMap(method: (typeof randomizationMethods)[number], key: string) { + let finalItemMap: ItemBank = {}; + if (method.id === 2) { + for (const p of [clItemsToRandomize, notCLItemsToRandomize, notCLUMBItemsToRandomize]) { + const shuffled = buildItemMapFromList(key, p); + finalItemMap = { ...finalItemMap, ...shuffled }; + } + } else { + finalItemMap = buildItemMapFromList(key, allItemsToRandomize); + } + + finalItemMap[995] = 995; + finalItemMap[19_939] = 19_939; + + let i = 0; + for (const item of allItems) { + if (typeof finalItemMap[item] === 'undefined') { + i++; + finalItemMap[item] = item; + } + } + console.log(`${key} - ${i} items not mapped`); + + if (finalItemMap[995] !== 995) { + throw new Error('Invalid coins mapping!'); + } + + if (finalItemMap[19_939] !== 19_939) { + throw new Error('Invalid coins mapping!'); + } + + return finalItemMap; +} + +export async function updateUsersRandomizerMap(user: MUser, method: (typeof randomizationMethods)[number]) { + const key = miniID(10); + + const sourceSkills = shuffleArr(SkillsArray); + const shuffledSkills = seedShuffle(SkillsArray, key); + + const skillMapping: Record = {}; + for (let i = 0; i < sourceSkills.length; i++) { + skillMapping[sourceSkills[i]] = shuffledSkills[i]; + } + + const map: any = buildItemMap(method, key); + Object.freeze(map); + const reverseItemMap: any = {}; + for (const [key, val] of objectEntries(map)) { + reverseItemMap[Number(val)] = Number(key); + } + + console.log( + new Set(Object.values(map)).size, + Object.values(map).length, + Object.values(reverseItemMap).length, + allItems.length + ); + console.log( + Object.keys(map).length, + Object.entries(map).length, + Object.keys(reverseItemMap).length, + allItems.length + ); + + if (Object.keys(map).length !== allItems.length) { + throw new Error( + `${user.usernameOrMention} has a map that doesn't have all items mapped: ${Object.keys(map).length} vs ${allItems.length}: missing these items ${allItems.filter(i => typeof map[i] === 'undefined')}` + ); + } + + if (Object.keys(reverseItemMap).length !== allItems.length) { + throw new Error( + `${user.usernameOrMention} has a reverse map that doesn't have all items mapped: ${Object.keys(reverseItemMap).length} vs ${allItems.length}, missing these: ${allItems.slice(0, 5).filter(i => typeof reverseItemMap[i] === 'undefined')}` + ); + } + + if (Object.values(map).length !== new Set(Object.values(map)).size) { + throw new Error(`${user.usernameOrMention} has a map with duplicate values.`); + } + await user.update({ item_map: map, item_map_key: key, reverse_item_map: reverseItemMap, skill_map: skillMapping }); + + const testBank = new Bank(); + for (const item of allItems) { + testBank.add(item, 1); + } + remapBank(user, testBank); +} + +export const relics = [ + { + id: RelicID.XP, + name: 'Relic of XP', + desc: '10% bonus xp, plus 1% in your lowest skill' + }, + { + id: RelicID.Repetition, + name: 'Relic of Repetition', + desc: '1/3 chance to automatically repeat most trips, global 30% max trip length increase' + }, + { + id: RelicID.Randomness, + name: 'Relic of Randomness', + desc: 'Twice as likely to receive a UMB, and 50% chance of one reroll when opening a UMB if its an item you already have.' + }, + { + id: RelicID.Loot, + name: 'Relic of Loot', + desc: 'Double loot on every trip (for most types) for your minion and tame.' + }, + { + id: RelicID.Speed, + name: 'Relic of Speed', + desc: 'You kill monsters 30% faster, your tame kills monsters 30% faster.' + }, + { + id: RelicID.Slay, + name: 'Relic of Slaying', + desc: 'Unlimited slayer task block list, and 2x slayer points.' + }, + { + id: RelicID.Gambling, + name: 'Relic of Gambling', + desc: "One time use; a 50/50 chance to either add 50 items to your cl(that aren't there already) + double the XP of your lowest skill OR remove 50 items from your cl (and from your bank), and half the XP of your highest skill." + } +]; + +export const RANDOMIZER_HELP = (user: MUser) => `**Help/Information:** +**Your randomization method:** ${user.getRandomizeMethod()!.name} (${user.getRandomizeMethod()?.desc}) +**Your Relics:** ${user.user.relics.length === 0 ? `You haven't picked a relic yet.` : user.user.relics.map(id => relics.find(t => t.id === id)!.name).join(', ')}`; diff --git a/src/lib/reclaimableItems.ts b/src/lib/reclaimableItems.ts deleted file mode 100644 index e8c429d26e..0000000000 --- a/src/lib/reclaimableItems.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Bank } from 'oldschooljs'; - -export async function getReclaimableItemsOfUser(user: MUser) { - const totalElligible = new Bank(); - - const reclaimableItems = await prisma.reclaimableItem.findMany({ where: { user_id: user.id } }); - - for (const item of reclaimableItems) { - totalElligible.add(item.item_id, item.quantity); - } - - const totalCanClaim = totalElligible.clone().remove(user.allItemsOwned); - - return { - totalElligible, - totalCanClaim, - raw: reclaimableItems - }; -} diff --git a/src/lib/relics.ts b/src/lib/relics.ts new file mode 100644 index 0000000000..285537de15 --- /dev/null +++ b/src/lib/relics.ts @@ -0,0 +1,10 @@ +export enum RelicID { + XP = 1, + Repetition = 2, + Randomness = 3, + Loot = 4, + Speed = 5, + Tames = 6, + Slay = 7, + Gambling = 8 +} diff --git a/src/lib/roboChimp.ts b/src/lib/roboChimp.ts index 1c4670a75b..d6b7a0d7c6 100644 --- a/src/lib/roboChimp.ts +++ b/src/lib/roboChimp.ts @@ -1,118 +1,47 @@ -import { formatOrdinal } from '@oldschoolgg/toolkit'; -import type { TriviaQuestion, User } from '@prisma/robochimp'; import { calcWhatPercent, round, sumArr } from 'e'; -import deepEqual from 'fast-deep-equal'; -import type { Bank } from 'oldschooljs'; -import { BOT_TYPE, globalConfig, masteryKey } from './constants'; +import { formatOrdinal } from '@oldschoolgg/toolkit'; import { getTotalCl } from './data/Collections'; import { calculateMastery } from './mastery'; -import { cacheRoboChimpUser } from './perkTier'; import { MUserStats } from './structures/MUserStats'; -export type RobochimpUser = User; - -export async function getRandomTriviaQuestions(): Promise { - if (!globalConfig.isProduction) { - return [ - { - id: 1, - question: 'What is 1+1?', - answers: ['2'] - }, - { - id: 2, - question: 'What is 2+2?', - answers: ['4'] - } - ]; - } - const random: TriviaQuestion[] = await roboChimpClient.$queryRaw`SELECT id, question, answers -FROM trivia_question -ORDER BY random() -LIMIT 10;`; - return random; -} - -const clKey: keyof User = 'bso_cl_percent'; -const levelKey: keyof User = 'bso_total_level'; -const totalXPKey: keyof User = BOT_TYPE === 'OSB' ? 'osb_total_xp' : 'bso_total_xp'; - -export async function roboChimpSyncData(user: MUser, newCL?: Bank) { - const id = BigInt(user.id); - const newCLArray: number[] = Object.keys((newCL ?? user.cl).bank).map(i => Number(i)); - const clArrayUpdateObject = { - cl_array: newCLArray, - cl_array_length: newCLArray.length - } as const; - - const stats = new MUserStats( - await prisma.userStats.upsert({ - where: { - user_id: id - }, - create: { - user_id: id, - ...clArrayUpdateObject - }, - update: { - ...clArrayUpdateObject - } - }) - ); - +export async function roboChimpSyncData(user: MUser) { + const stats = await MUserStats.fromID(user.id); const [totalClItems, clItems] = await getTotalCl(user, 'collection', stats); const clCompletionPercentage = round(calcWhatPercent(clItems, totalClItems), 2); const totalXP = sumArr(Object.values(user.skillsAsXP)); const { totalMastery } = await calculateMastery(user, stats); - const updateObj = { - [clKey]: clCompletionPercentage, - [levelKey]: user.totalLevel, - [totalXPKey]: totalXP, - [masteryKey]: totalMastery - } as const; + const newUser = await user.update({ + cl_percent: clCompletionPercentage, + total_level: user.totalLevel, + total_xp: totalXP, + mastery: totalMastery + }); - const newUser: RobochimpUser = await roboChimpClient.user.upsert({ + const newCLArray = Object.keys(user.cl.bank).map(str => Number(str)); + await prisma.userStats.upsert({ where: { - id: BigInt(user.id) + user_id: BigInt(user.id) + }, + update: { + cl_array: newCLArray }, - update: updateObj, create: { - id: BigInt(user.id), - ...updateObj + user_id: BigInt(user.id), + cl_array: newCLArray } }); - cacheRoboChimpUser(newUser); - if (!deepEqual(newUser.store_bitfield, user.user.store_bitfield)) { - await user.update({ store_bitfield: newUser.store_bitfield }); - } return newUser; } -export async function roboChimpUserFetch(userID: string): Promise { - const result: RobochimpUser = await roboChimpClient.user.upsert({ - where: { - id: BigInt(userID) - }, - create: { - id: BigInt(userID) - }, - update: {} - }); - - cacheRoboChimpUser(result); - - return result; -} - export async function calculateOwnCLRanking(userID: string) { const clPercentRank = ( - await roboChimpClient.$queryRaw<{ count: number }[]>`SELECT COUNT(*)::int -FROM public.user -WHERE osb_cl_percent >= (SELECT osb_cl_percent FROM public.user WHERE id = ${BigInt(userID)});` + await prisma.$queryRaw<{ count: number }[]>`SELECT COUNT(*)::int +FROM public.users +WHERE cl_percent >= (SELECT cl_percent FROM public.user WHERE id = ${BigInt(userID)});` )[0].count; return formatOrdinal(clPercentRank); diff --git a/src/lib/rolesTask.ts b/src/lib/rolesTask.ts deleted file mode 100644 index 12317a1a79..0000000000 --- a/src/lib/rolesTask.ts +++ /dev/null @@ -1,648 +0,0 @@ -import { Prisma } from '@prisma/client'; -import { noOp, notEmpty } from 'e'; -import { Bank } from 'oldschooljs'; - -import { SupportServer, production } from '../config'; -import { ClueTiers } from '../lib/clues/clueTiers'; -import { Roles } from '../lib/constants'; -import { getCollectionItems, overallPlusItems } from '../lib/data/Collections'; -import { Minigames } from '../lib/settings/minigames'; - -import Skills from '../lib/skilling/skills'; -import { assert, convertXPtoLVL, getUsername, getUsernameSync, sanitizeBank } from '../lib/util'; -import { logError } from '../lib/util/logError'; -import { TeamLoot } from './simulation/TeamLoot'; -import type { ItemBank } from './types'; -import { fetchTameCLLeaderboard } from './util/clLeaderboard'; -import resolveItems from './util/resolveItems'; - -function addToUserMap(userMap: Record, id: string, reason: string) { - if (!userMap[id]) userMap[id] = []; - userMap[id].push(reason); -} - -const minigames = Minigames.map(game => game.column).filter(i => i !== 'tithe_farm'); - -const collections = ['skilling', 'clues', 'minigames', 'raids', 'Dyed Items', 'slayer', 'other']; - -for (const cl of collections) { - const items = getCollectionItems(cl); - if (!items || items.length === 0) { - throw new Error(`${cl} isn't a valid CL.`); - } -} - -const mostSlayerPointsQuery = `SELECT id, 'Most Points' as desc -FROM users -WHERE "slayer.points" > 50 -ORDER BY "slayer.points" DESC -LIMIT 1;`; - -const longerSlayerTaskStreakQuery = `SELECT user_id::text as id, 'Longest Task Streak' as desc -FROM user_stats -WHERE "slayer_task_streak" > 20 -ORDER BY "slayer_task_streak" DESC -LIMIT 1;`; - -const mostSlayerTasksDoneQuery = `SELECT user_id::text as id, 'Most Tasks' as desc -FROM slayer_tasks -GROUP BY user_id -ORDER BY count(user_id)::int DESC -LIMIT 1;`; - -async function addRoles({ - users, - role, - badge, - userMap -}: { - users: string[]; - role: string; - badge: number | null; - userMap?: Record; -}): Promise { - if (process.env.TEST) return ''; - const g = globalClient.guilds.cache.get(SupportServer); - if (!g) throw new Error('No support guild'); - const added: string[] = []; - const removed: string[] = []; - const _role = await g.roles.fetch(role).catch(noOp); - if (!_role) return `\nCould not check ${role} role`; - for (const u of users.filter(notEmpty)) { - await g.members.fetch(u).catch(noOp); - } - const roleName = _role.name!; - const noChangeUserDescriptions: string[] = []; - for (const mem of g.members.cache.values()) { - const mUser = await mUserFetch(mem.user.id); - if (mem.roles.cache.has(role) && !users.includes(mem.user.id)) { - if (production) { - await mem.roles.remove(role).catch(noOp); - } - - if (badge && mUser.user.badges.includes(badge)) { - await mUser.update({ - badges: mUser.user.badges.filter(i => i !== badge) - }); - } - removed.push(mem.user.username); - } - - if (users.includes(mem.user.id)) { - noChangeUserDescriptions.push(`${mem.user.username}`); - if (production && !mem.roles.cache.has(role)) { - added.push(mem.user.username); - await mem.roles.add(role).catch(noOp); - } - if (badge && !mUser.user.badges.includes(badge)) { - await mUser.update({ - badges: { - push: badge - } - }); - } - } - } - let str = `\n**${roleName}**`; - if (added.length > 0) { - str += `\nAdded to: ${added.join(', ')}.`; - } - if (removed.length > 0) { - str += `\nRemoved from: ${removed.join(', ')}.`; - } - if (userMap) { - const userArr = []; - for (const [id, arr] of Object.entries(userMap)) { - const username = await getUsername(id); - userArr.push(`${username}(${arr.join(', ')})`); - } - str += `\n${userArr.join(',')}`; - } - if (added.length > 0 || removed.length > 0) { - str += '\n'; - } else { - return `**No Changes:** ${str}`; - } - return str; -} - -export async function runRolesTask() { - const skillVals = Object.values(Skills); - - const results: string[] = []; - const q = async (str: string) => { - const result = await prisma.$queryRawUnsafe(str).catch(err => { - logError(`This query failed: ${str}`, err); - return []; - }); - return result; - }; - - // Top Skillers - async function topSkillers() { - const topSkillers = ( - await Promise.all([ - ...skillVals.map(s => - q< - { - id: string; - xp: string; - }[] - >(`SELECT id, "skills.${s.id}" as xp FROM users ORDER BY xp DESC LIMIT 1;`) - ), - q< - { - id: string; - }[] - >( - `SELECT id, ${skillVals.map(s => `"skills.${s.id}"`)}, ${skillVals - .map(s => `"skills.${s.id}"::bigint`) - .join(' + ')} as totalxp FROM users ORDER BY totalxp DESC LIMIT 1;` - ) - ]) - ).map(i => i[0]?.id); - - // Rank 1 Total Level - const rankOneTotal = ( - await q( - `SELECT id, ${skillVals.map(s => `"skills.${s.id}"`)}, ${skillVals - .map(s => `"skills.${s.id}"::bigint`) - .join(' + ')} as totalxp FROM users ORDER BY totalxp DESC LIMIT 200;` - ) - ) - .map((u: any) => { - let totalLevel = 0; - for (const skill of skillVals) { - totalLevel += convertXPtoLVL(Number(u[`skills.${skill.id}` as keyof any]) as any); - } - return { - id: u.id, - totalLevel - }; - }) - .sort((a: any, b: any) => b.totalLevel - a.totalLevel)[0]; - topSkillers.push(rankOneTotal.id); - - results.push(await addRoles({ users: topSkillers, role: Roles.TopSkiller, badge: 9 })); - } - - // Top Collectors - async function topCollector() { - const userMap = {}; - - function generateQuery(items: number[], ironmenOnly: boolean, limit: number) { - const t = ` -SELECT id, (cardinality(u.cl_keys) - u.inverse_length) as qty - FROM ( - SELECT ARRAY(SELECT * FROM JSONB_OBJECT_KEYS("collectionLogBank")) "cl_keys", - id, "collectionLogBank", - cardinality(ARRAY(SELECT * FROM JSONB_OBJECT_KEYS("collectionLogBank" - array[${items - .map(i => `'${i}'`) - .join(', ')}]))) "inverse_length" - FROM users - WHERE "collectionLogBank" ?| array[${items.map(i => `'${i}'`).join(', ')}] - ${ironmenOnly ? 'AND "minion.ironman" = true' : ''} - ) u - ORDER BY qty DESC - LIMIT ${limit}; -`; - - return t; - } - - const topCollectors = ( - await Promise.all( - collections.map(async clName => { - const items = getCollectionItems(clName); - if (!items || items.length === 0) { - logError(`${clName} collection log doesnt exist`); - return []; - } - - function handleErr(): any[] { - logError(`Failed to select top collectors for ${clName}`); - return []; - } - - const [users, ironUsers] = await Promise.all([ - q(generateQuery(items, false, 1)) - .then(i => i.filter((i: any) => i.qty > 0) as any[]) - .catch(handleErr), - q(generateQuery(items, true, 1)) - .then(i => i.filter((i: any) => i.qty > 0) as any[]) - .catch(handleErr) - ]); - - const result = []; - const userID = users[0]?.id; - const ironmanID = ironUsers[0]?.id; - - if (userID) { - addToUserMap(userMap, userID, `Rank 1 ${clName} CL`); - result.push(userID); - } - if (ironmanID) { - addToUserMap(userMap, ironmanID, `Rank 1 Ironman ${clName} CL`); - result.push(ironmanID); - } - - return result; - }) - ) - ).flat(2); - - const topIronUsers = (await q(generateQuery(getCollectionItems('overall'), true, 3))).filter( - (i: any) => i.qty > 0 - ) as any[]; - for (let i = 0; i < topIronUsers.length; i++) { - const id = topIronUsers[i]?.id; - addToUserMap(userMap, id, `Rank ${i + 1} Ironman Collector`); - topCollectors.push(id); - } - const topNormieUsers = (await q(generateQuery(getCollectionItems('overall'), false, 3))).filter( - (i: any) => i.qty > 0 - ) as any[]; - for (let i = 0; i < topNormieUsers.length; i++) { - const id = topNormieUsers[i]?.id; - addToUserMap(userMap, id, `Rank ${i + 1} Collector`); - topCollectors.push(id); - } - - results.push(await addRoles({ users: topCollectors, role: Roles.TopCollector, badge: 10, userMap })); - } - - // Top sacrificers - async function topSacrificers() { - const userMap = {}; - const topSacrificers: string[] = []; - const mostValue = await q('SELECT id FROM users ORDER BY "sacrificedValue" DESC LIMIT 3;'); - for (let i = 0; i < 3; i++) { - if (mostValue[i] !== undefined) { - topSacrificers.push(mostValue[i].id); - addToUserMap(userMap, mostValue[i].id, `Rank ${i + 1} Sacrifice Value`); - } - } - const mostValueIronman = await q( - 'SELECT id FROM users WHERE "minion.ironman" = true ORDER BY "sacrificedValue" DESC LIMIT 1;' - ); - topSacrificers.push(mostValueIronman[0].id); - addToUserMap(userMap, mostValueIronman[0].id, 'Rank 1 Ironman Sacrificed Value'); - - const mostUniques = await q(`SELECT u.id, u.sacbanklength FROM ( - SELECT (SELECT COUNT(*)::int FROM JSONB_OBJECT_KEYS("sacrificed_bank")) sacbanklength, user_id::text as id FROM user_stats -) u -ORDER BY u.sacbanklength DESC LIMIT 1;`); - - const mostUniquesIron = await q(`SELECT u.id, u.sacbanklength FROM ( - SELECT (SELECT COUNT(*)::int FROM JSONB_OBJECT_KEYS("sacrificed_bank")) sacbanklength, user_id::text as id FROM user_stats - INNER JOIN users ON "user_stats"."user_id"::text = "users"."id" - WHERE "users"."minion.ironman" = true -) u -ORDER BY u.sacbanklength DESC LIMIT 1;`); - topSacrificers.push(mostUniques[0].id); - addToUserMap(userMap, mostUniques[0].id, 'Most Uniques Sacrificed'); - topSacrificers.push(mostUniquesIron[0].id); - addToUserMap(userMap, mostUniquesIron[0].id, 'Most Ironman Uniques Sacrificed'); - - results.push(await addRoles({ users: topSacrificers, role: Roles.TopSacrificer, badge: 8, userMap })); - } - - // Top minigamers - async function topMinigamers() { - const topMinigamers = ( - await Promise.all( - minigames.map(m => - q( - `SELECT user_id, '${m}' as m -FROM minigames -ORDER BY ${m} DESC -LIMIT 1;` - ) - ) - ) - ).map((i: any) => [i[0].user_id, Minigames.find(m => m.column === i[0].m)?.name]); - - const userMap = {}; - for (const [id, m] of topMinigamers) { - addToUserMap(userMap, id, `Rank 1 ${m}`); - } - - results.push( - await addRoles({ - users: topMinigamers.map(i => i[0]), - role: Roles.TopMinigamer, - badge: 11, - userMap - }) - ); - } - - // Top clue hunters - async function topClueHunters() { - const topClueHunters = ( - await Promise.all( - ClueTiers.map(t => - q( - `SELECT id, '${t.name}' as n, (openable_scores->>'${t.id}')::int as qty -FROM users -INNER JOIN "user_stats" ON "user_stats"."user_id"::text = "users"."id" -WHERE "openable_scores"->>'${t.id}' IS NOT NULL -ORDER BY qty DESC -LIMIT 1;` - ) - ) - ) - ) - .filter((i: any) => Boolean(i[0]?.id)) - .map((i: any) => [i[0]?.id, i[0]?.n]); - - const userMap = {}; - - for (const [id, n] of topClueHunters) { - addToUserMap(userMap, id, `Rank 1 ${n} Clues`); - } - - results.push( - await addRoles({ - users: topClueHunters.map(i => i[0]), - role: Roles.TopClueHunter, - badge: null, - userMap - }) - ); - } - - // Top farmers - async function farmers() { - const queries = [ - `SELECT id, 'Top 2 Farming Contracts' as desc -FROM users -WHERE "minion.farmingContract" IS NOT NULL -AND "minion.ironman" = true -ORDER BY ("minion.farmingContract"->>'contractsCompleted')::int DESC -LIMIT 2;`, - `SELECT id, 'Top 2 Ironman Farming Contracts' as desc -FROM users -WHERE "minion.farmingContract" IS NOT NULL -ORDER BY ("minion.farmingContract"->>'contractsCompleted')::int DESC -LIMIT 2;`, - `SELECT user_id::text as id, 'Top 2 Most Farming Trips' as desc -FROM activity -WHERE type = 'Farming' -GROUP BY user_id -ORDER BY count(user_id)::int DESC -LIMIT 2;`, - `SELECT user_id::text as id, 'Top 2 Tithe Farm' as desc -FROM user_stats -ORDER BY "tithe_farms_completed" DESC -LIMIT 2;` - ]; - const res = (await Promise.all(queries.map(q))).map((i: any) => [i[0]?.id, i[0]?.desc]); - const userMap = {}; - for (const [id, desc] of res) { - addToUserMap(userMap, id, desc); - } - - results.push( - await addRoles({ - users: res.map(i => i[0]), - role: '894194027363205150', - badge: null, - userMap - }) - ); - } - - // Top slayers - async function slayer() { - const topSlayers = ( - await Promise.all( - [mostSlayerPointsQuery, longerSlayerTaskStreakQuery, mostSlayerTasksDoneQuery].map(query => q(query)) - ) - ) - .filter((i: any) => Boolean(i[0]?.id)) - .map((i: any) => [i[0]?.id, i[0]?.desc]); - - const userMap = {}; - for (const [id, desc] of topSlayers) { - addToUserMap(userMap, id, desc); - } - - results.push( - await addRoles({ - users: topSlayers.map(i => i[0]), - role: Roles.TopSlayer, - badge: null, - userMap - }) - ); - } - - // Top giveawayers - async function giveaways() { - const GIVEAWAY_CHANNELS = [ - '792691343284764693', - '732207379818479756', - '342983479501389826', - '982989775399174184', - '346304390858145792' - ]; - const res = await prisma.$queryRaw<{ user_id: string; qty: number }[]>`SELECT user_id, COUNT(user_id)::int AS qty -FROM giveaway -WHERE channel_id IN (${Prisma.join(GIVEAWAY_CHANNELS)}) -AND user_id NOT IN ('157797566833098752') -GROUP BY user_id -ORDER BY qty DESC -LIMIT 50;`; - const userIDsToCheck = res.map(i => i.user_id); - - const giveawayBank = new TeamLoot(); - - const giveaways = await prisma.giveaway.findMany({ - where: { - channel_id: { - in: GIVEAWAY_CHANNELS - }, - user_id: { - in: userIDsToCheck - } - } - }); - for (const giveaway of giveaways) { - giveawayBank.add(giveaway.user_id, giveaway.loot as ItemBank); - } - for (const [, bank] of giveawayBank.entries()) { - sanitizeBank(bank); - } - - const userMap = {}; - const [[highestID, loot]] = giveawayBank.entries().sort((a, b) => b[1].value() - a[1].value()); - addToUserMap(userMap, highestID, `Most Value Given Away (${loot.value()})`); - - results.push( - await addRoles({ - users: [highestID], - role: '1104155653745946746', - badge: null, - userMap - }) - ); - } - - async function monkeyKing() { - const res = await q( - 'SELECT id FROM users WHERE monkeys_fought IS NOT NULL ORDER BY cardinality(monkeys_fought) DESC LIMIT 1;' - ); - results.push(await addRoles({ users: [res[0].id], role: '886180040465870918', badge: null })); - } - async function topInventor() { - const userMap = {}; - const topInventors: string[] = []; - const mostUniques = await q<{ id: string; uniques: number; disassembled_items_bank: ItemBank }[]>(`SELECT u.id, u.uniques, u.disassembled_items_bank FROM ( - SELECT (SELECT COUNT(*) FROM JSON_OBJECT_KEYS("disassembled_items_bank")) uniques, id, disassembled_items_bank FROM users WHERE "skills.invention" > 0 -) u -ORDER BY u.uniques DESC LIMIT 300;`); - topInventors.push(mostUniques[0].id); - addToUserMap(userMap, mostUniques[0].id, 'Most Uniques Disassembled'); - const parsed = mostUniques - .map(i => ({ ...i, value: new Bank(i.disassembled_items_bank).value() })) - .sort((a, b) => b.value - a.value); - topInventors.push(parsed[0].id); - addToUserMap(userMap, parsed[0].id, 'Most Value Disassembled'); - results.push(await addRoles({ users: topInventors, role: Roles.TopInventor, badge: null, userMap })); - } - async function topLeagues() { - if (process.env.TEST) return; - const topPoints = await roboChimpClient.user.findMany({ - where: { - leagues_points_total: { - gt: 0 - } - }, - orderBy: { - leagues_points_total: 'desc' - }, - take: 2 - }); - const topTasks: { id: string; tasks_completed: number }[] = await roboChimpClient.$queryRaw`SELECT id::text, COALESCE(cardinality(leagues_completed_tasks_ids), 0) AS tasks_completed - FROM public.user - ORDER BY tasks_completed DESC - LIMIT 2;`; - const userMap = {}; - addToUserMap(userMap, topPoints[0].id.toString(), 'Rank 1 Leagues Points'); - addToUserMap(userMap, topPoints[1].id.toString(), 'Rank 2 Leagues Points'); - addToUserMap(userMap, topTasks[0].id, 'Rank 1 Leagues Tasks'); - addToUserMap(userMap, topTasks[1].id, 'Rank 2 Leagues Tasks'); - const allLeagues = topPoints.map(i => i.id.toString()).concat(topTasks.map(i => i.id)); - assert(allLeagues.length > 0 && allLeagues.length <= 4); - results.push(await addRoles({ users: allLeagues, role: Roles.TopLeagues, badge: null, userMap })); - } - - async function topTamer() { - const [rankOne] = await fetchTameCLLeaderboard({ items: overallPlusItems, resultLimit: 1 }); - if (rankOne) { - results.push( - await addRoles({ - users: [rankOne.user_id], - role: '1054356709222666240', - badge: null, - userMap: { [rankOne.user_id]: ['Rank 1 Tames CL'] } - }) - ); - } - } - - const notes: string[] = []; - - async function mysterious() { - const items = resolveItems([ - 'Pet mystery box', - 'Holiday mystery box', - 'Equippable mystery box', - 'Clothing mystery box', - 'Tradeable mystery box', - 'Untradeable mystery box' - ]); - const res = await Promise.all( - items.map(id => - prisma - .$queryRawUnsafe<{ id: string; score: number }[]>( - `SELECT user_id::text AS id, ("openable_scores"->>'${id}')::int AS score -FROM user_stats -WHERE "openable_scores"->>'${id}' IS NOT NULL -AND ("openable_scores"->>'${id}')::int > 50 -ORDER BY ("openable_scores"->>'${id}')::int DESC -LIMIT 50;` - ) - .then(i => i.map(t => ({ id: t.id, score: Number(t.score) }))) - ) - ); - - const userScoreMap: Record = {}; - for (const lb of res) { - const [rankOne] = lb; - for (const user of lb) { - if (!userScoreMap[user.id]) userScoreMap[user.id] = 0; - userScoreMap[user.id] += user.score / rankOne.score; - } - } - - const entries = Object.entries(userScoreMap).sort((a, b) => b[1] - a[1]); - const [[rankOneID]] = entries; - - let note = '**Top Mystery Box Openers**\n\n'; - for (const [id, score] of entries.slice(0, 10)) { - note += `${getUsernameSync(id) ?? id} - ${score}\n`; - } - - notes.push(note); - - results.push( - await addRoles({ - users: [rankOneID], - role: '1074592096968785960', - badge: null, - userMap: { [rankOneID]: ['Rank 1 Mystery Box Opens'] } - }) - ); - } - - const tup = [ - ['Top Slayer', slayer], - ['Top Clue Hunters', topClueHunters], - ['Top Minigamers', topMinigamers], - ['Top Sacrificers', topSacrificers], - ['Top Collectors', topCollector], - ['Top Skillers', topSkillers], - ['Top Farmers', farmers], - ['Top Giveawayers', giveaways], - ['Monkey King', monkeyKing], - ['Top Farmers', farmers], - ['Top Inventor', topInventor], - ['Top Leagues', topLeagues], - ['Top Tamer', topTamer], - ['Mysterious', mysterious] - ] as const; - - const failed: string[] = []; - await Promise.all( - tup.map(async ([name, fn]) => { - try { - await fn(); - } catch (err: any) { - if (process.env.TEST) { - throw err; - } - failed.push(`${name} (${err.message})`); - logError(err); - } - }) - ); - - const res = `**Roles** -${results.join('\n')} -${failed.length > 0 ? `Failed: ${failed.join(', ')}` : ''} -${notes.join('\n')}`; - - return res; -} diff --git a/src/lib/settings/prisma.ts b/src/lib/settings/prisma.ts index 4e719a2bf1..feac41f803 100644 --- a/src/lib/settings/prisma.ts +++ b/src/lib/settings/prisma.ts @@ -20,12 +20,11 @@ export function convertStoredActivityToFlatActivity(activity: Activity): Activit /** * ⚠️ Uses queryRawUnsafe */ -export async function countUsersWithItemInCl(itemID: number, ironmenOnly: boolean) { +export async function countUsersWithItemInCl(itemID: number) { const query = `SELECT COUNT(id)::int FROM users WHERE ("collectionLogBank"->>'${itemID}') IS NOT NULL - AND ("collectionLogBank"->>'${itemID}')::int >= 1 - ${ironmenOnly ? 'AND "minion.ironman" = true' : ''};`; + AND ("collectionLogBank"->>'${itemID}')::int >= 1;`; const result = Number.parseInt(((await prisma.$queryRawUnsafe(query)) as any)[0].count); if (Number.isNaN(result)) { throw new Error(`countUsersWithItemInCl produced invalid number '${result}' for ${itemID}`); diff --git a/src/lib/settings/settings.ts b/src/lib/settings/settings.ts index 5a81821442..957e62c22d 100644 --- a/src/lib/settings/settings.ts +++ b/src/lib/settings/settings.ts @@ -9,12 +9,11 @@ import type { User } from 'discord.js'; -import { Time } from 'e'; import { isEmpty } from 'lodash'; import { postCommand } from '../../mahoji/lib/postCommand'; import { preCommand } from '../../mahoji/lib/preCommand'; import { convertMahojiCommandToAbstractCommand } from '../../mahoji/lib/util'; -import { PerkTier, minionActivityCache } from '../constants'; +import { minionActivityCache } from '../constants'; import { channelIsSendable, isGroupActivity } from '../util'; import { deferInteraction, handleInteractionError, interactionReply } from '../util/interactionReply'; import { logError } from '../util/logError'; @@ -50,7 +49,7 @@ export async function cancelTask(userID: string) { minionActivityCache.delete(userID); } -async function runMahojiCommand({ +export async function runMahojiCommand({ channelID, userID, guildID, @@ -203,14 +202,3 @@ export function activitySync(activity: Activity) { minionActivityCache.set(user.toString(), convertedActivity); } } - -export async function isElligibleForPresent(user: MUser) { - if (user.isIronman) return true; - if (user.perkTier() >= PerkTier.Four) return true; - if (user.totalLevel >= 2000) return true; - const totalActivityDuration: [{ sum: number }] = await prisma.$queryRawUnsafe(`SELECT SUM(duration) -FROM activity -WHERE user_id = ${BigInt(user.id)};`); - if (totalActivityDuration[0].sum >= Time.Hour * 80) return true; - return false; -} diff --git a/src/lib/simulation/customImplings.ts b/src/lib/simulation/customImplings.ts index 4ea2c7073c..e786b57014 100644 --- a/src/lib/simulation/customImplings.ts +++ b/src/lib/simulation/customImplings.ts @@ -61,7 +61,6 @@ export const MysteryImpling = new SimpleOpenable({ name: 'Mystery impling jar', aliases: ['mystery impling', 'mystery imp'], table: new LootTable() - .add('Untradeable Mystery Box') .add('Tradeable Mystery Box') .add('Pet Mystery Box') .add('Equippable mystery box') diff --git a/src/lib/simulation/elderClue.ts b/src/lib/simulation/elderClue.ts index 7dd917b879..a64db2a1d8 100644 --- a/src/lib/simulation/elderClue.ts +++ b/src/lib/simulation/elderClue.ts @@ -4,15 +4,14 @@ import Clue from 'oldschooljs/dist/structures/Clue'; import LootTable from 'oldschooljs/dist/structures/LootTable'; import resolveItems from '../util/resolveItems'; -import { LampTable } from '../xpLamps'; +import { LampTable } from './grandmasterClue'; const boxTable = new LootTable() .add('Pet mystery box') .add('Holiday mystery box') .add('Equippable mystery box') .add('Clothing mystery box') - .add('Tradeable mystery box', 2) - .add('Untradeable mystery box', 2); + .add('Tradeable mystery box', 2); const table = new LootTable() .tertiary( diff --git a/src/lib/simulation/grandmasterClue.ts b/src/lib/simulation/grandmasterClue.ts index 46f00cd52f..48768aeb42 100644 --- a/src/lib/simulation/grandmasterClue.ts +++ b/src/lib/simulation/grandmasterClue.ts @@ -5,10 +5,15 @@ import { HardClueTable } from 'oldschooljs/dist/simulation/clues/Hard'; import { MasterClueTable } from 'oldschooljs/dist/simulation/clues/Master'; import Clue from 'oldschooljs/dist/structures/Clue'; import LootTable from 'oldschooljs/dist/structures/LootTable'; - -import { LampTable } from '../xpLamps'; import { AllBarrows, BattlestaffTable, CosmeticsTable, StaffOrbTable, runeAlchablesTable } from './sharedTables'; +export const LampTable = new LootTable() + .add(6796, 1, 40) + .add(21_642, 1, 30) + .add(23_516, 1, 20) + .add(22_320, 1, 5) + .add(11_157, 1, 1); + const ClueHunterTable = new LootTable() .add('Helm of raedwald') .add('Clue hunter garb') @@ -44,7 +49,6 @@ export const DragonTable = new LootTable() const boxTable = new LootTable() .add('Tradeable mystery box', [1, 2], 100) - .add('Untradeable mystery box', 1, 40) .add('Equippable mystery box', 1, 5) .add('Pet mystery box'); diff --git a/src/lib/simulation/simulatedKillables.ts b/src/lib/simulation/simulatedKillables.ts deleted file mode 100644 index a8e31df901..0000000000 --- a/src/lib/simulation/simulatedKillables.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { randArrItem, randInt, roll } from 'e'; -import { Bank, Misc } from 'oldschooljs'; - -import { DOANonUniqueTable } from '../bso/doa/doaLootTable'; -import { nexUniqueDrops } from '../data/CollectionsExport'; -import { chanceOfDOAUnique, pickUniqueToGiveUser } from '../depthsOfAtlantis'; -import { MoktangLootTable } from '../minions/data/killableMonsters/custom/bosses/Moktang'; -import { NEX_UNIQUE_DROPRATE, nexLootTable } from '../nex'; -import { zygomiteFarmingSource } from '../skilling/skills/farming/zygomites'; -import { WintertodtCrate } from './wintertodt'; - -interface SimulatedKillable { - name: string; - isCustom: boolean; - loot: (quantity: number) => Bank; -} - -export const simulatedKillables: SimulatedKillable[] = [ - { - name: 'Wintertodt', - isCustom: false, - loot: (quantity: number) => { - const loot = new Bank(); - for (let i = 0; i < quantity; i++) { - const points = randInt(1000, 5000); - - loot.add( - WintertodtCrate.open({ - points, - itemsOwned: {}, - skills: { - firemaking: 99, - herblore: 99, - woodcutting: 99, - crafting: 99, - fishing: 99, - mining: 99, - farming: 99 - }, - firemakingXP: 1 - }) - ); - } - return loot; - } - }, - { - name: 'The Nightmare', - isCustom: false, - loot: (quantity: number) => { - const bank = new Bank(); - for (let i = 0; i < quantity; i++) { - bank.add(Misc.Nightmare.kill({ team: [{ damageDone: 2400, id: 'id' }], isPhosani: false }).id); - } - return bank; - } - }, - { - name: "Phosani's Nightmare", - isCustom: false, - loot: (quantity: number) => { - const bank = new Bank(); - for (let i = 0; i < quantity; i++) { - bank.add(Misc.Nightmare.kill({ team: [{ damageDone: 2400, id: 'id' }], isPhosani: true }).id); - } - return bank; - } - }, - { - name: 'Depths of Atlantis (DOA) - Solo', - isCustom: true, - loot: (quantity: number) => { - const chanceOfUnique = chanceOfDOAUnique(1, false); - const loot = new Bank(); - for (let i = 0; i < quantity; i++) { - if (roll(chanceOfUnique)) { - loot.add(pickUniqueToGiveUser(loot)); - } else { - loot.add(DOANonUniqueTable.roll()); - } - } - return loot; - } - }, - { - name: 'Nex', - isCustom: true, - loot: (quantity: number) => { - const loot = new Bank(); - for (let i = 0; i < quantity; i++) { - if (roll(NEX_UNIQUE_DROPRATE(1))) { - loot.add(randArrItem(nexUniqueDrops), 1); - } - loot.add(nexLootTable.roll()); - } - return loot; - } - }, - { - name: 'Moktang', - isCustom: true, - loot: (quantity: number) => { - return MoktangLootTable.roll(quantity); - } - }, - ...zygomiteFarmingSource.map(src => ({ - name: src.name, - isCustom: true, - loot: (quantity: number) => { - const loot = new Bank(); - for (let i = 0; i < quantity; i++) { - loot.add(src.lootTable?.roll()); - } - return loot; - } - })) -]; diff --git a/src/lib/simulation/toa.ts b/src/lib/simulation/toa.ts index d01273df71..2db12f1b74 100644 --- a/src/lib/simulation/toa.ts +++ b/src/lib/simulation/toa.ts @@ -1,7 +1,6 @@ import { SimpleTable, exponentialPercentScale, mentionCommand } from '@oldschoolgg/toolkit'; import type { CommandResponse } from '@oldschoolgg/toolkit'; import type { Minigame } from '@prisma/client'; -import { XpGainSource } from '@prisma/client'; import { bold } from 'discord.js'; import { Time, @@ -29,7 +28,6 @@ import { getSimilarItems } from '../data/similarItems'; import { degradeItem } from '../degradeableItems'; import type { GearStats, UserFullGearSetup } from '../gear/types'; import { InventionID, canAffordInventionBoost, inventionBoosts, inventionItemBoost } from '../invention/inventions'; -import { trackLoot } from '../lootTrack'; import { setupParty } from '../party'; import { getMinigameScore } from '../settings/minigames'; import { SkillsEnum } from '../skilling/types'; @@ -49,7 +47,6 @@ import { calcMaxTripLength } from '../util/calcMaxTripLength'; import getOSItem from '../util/getOSItem'; import itemID from '../util/itemID'; import { bankToStrShortNames, getToaKCs } from '../util/smallUtils'; -import { updateBankSetting } from '../util/updateBankSetting'; import { TeamLoot } from './TeamLoot'; const teamSizeScale: Record = { @@ -452,7 +449,6 @@ export function calculateXPFromRaid({ skillName: style, amount, duration: realDuration, - source: XpGainSource.TombsOfAmascut, minimal: true }) ); @@ -466,7 +462,6 @@ export function calculateXPFromRaid({ skillName: SkillsEnum.Magic, amount: mageXP, duration: realDuration, - source: XpGainSource.TombsOfAmascut, minimal: true }) ); @@ -477,7 +472,6 @@ export function calculateXPFromRaid({ skillName: SkillsEnum.Ranged, amount: mageXP, duration: realDuration, - source: XpGainSource.TombsOfAmascut, minimal: true }) ); @@ -487,8 +481,7 @@ export function calculateXPFromRaid({ skillName: SkillsEnum.Hitpoints, amount: Math.floor(totalCombatXP / 4), duration: realDuration, - minimal: true, - source: XpGainSource.TombsOfAmascut + minimal: true }) ); return promises; @@ -1236,7 +1229,7 @@ export async function toaStartCommand( } } - const costResult = await Promise.all( + await Promise.all( users.map(async u => { const { cost, blowpipeCost } = await calcTOAInput({ user: u, duration: fakeDuration, quantity }); const { realCost } = await u.specialRemoveItems(cost.clone().add(blowpipeCost)); @@ -1301,19 +1294,6 @@ export async function toaStartCommand( }) ); - updateBankSetting('toa_cost', totalCost); - await trackLoot({ - totalCost, - id: 'tombs_of_amascut', - type: 'Minigame', - changeType: 'cost', - users: costResult.map(i => ({ - id: i.userID, - cost: i.effectiveCost, - duration: realDuration - })) - }); - const userArr: [string, number, number[]][][] = []; for (let i = 0; i < quantity; i++) { const thisQtyArr: [string, number, number[]][] = []; diff --git a/src/lib/skilling/skills/cooking/cooking.ts b/src/lib/skilling/skills/cooking/cooking.ts index 9a2faa7555..3ef45f3546 100644 --- a/src/lib/skilling/skills/cooking/cooking.ts +++ b/src/lib/skilling/skills/cooking/cooking.ts @@ -1,6 +1,5 @@ import { Bank } from 'oldschooljs'; -import { removeDiscontinuedItems } from '../../../bso/bsoUtil'; import { Emoji } from '../../../constants'; import itemID from '../../../util/itemID'; import type { Cookable } from '../../types'; @@ -326,7 +325,7 @@ export const Cookables: Cookable[] = [ } ]; -export const cookingCL = removeDiscontinuedItems(Cookables.map(i => i.id)); +export const cookingCL = Cookables.map(i => i.id); const Cooking = { aliases: ['cooking', 'cook'], diff --git a/src/lib/skilling/skills/dung/dungData.ts b/src/lib/skilling/skills/dung/dungData.ts index cb015a74d6..71aa7d5ac4 100644 --- a/src/lib/skilling/skills/dung/dungData.ts +++ b/src/lib/skilling/skills/dung/dungData.ts @@ -116,25 +116,3 @@ export function determineDgLevelForFloor(floor: number) { export function requiredLevel(floor: number) { return floor * 14; } - -export function requiredSkills(floor: number) { - const lvl = requiredLevel(floor); - const nonCmbLvl = Math.floor(lvl / 1.5); - return { - attack: lvl, - strength: lvl, - defence: lvl, - hitpoints: lvl, - magic: lvl, - ranged: lvl, - herblore: nonCmbLvl, - runecraft: nonCmbLvl, - prayer: nonCmbLvl, - fletching: nonCmbLvl, - fishing: nonCmbLvl, - cooking: nonCmbLvl, - construction: nonCmbLvl, - crafting: nonCmbLvl, - dungeoneering: determineDgLevelForFloor(floor) - }; -} diff --git a/src/lib/skilling/skills/dung/dungDbFunctions.ts b/src/lib/skilling/skills/dung/dungDbFunctions.ts index b317e4708f..6cca126860 100644 --- a/src/lib/skilling/skills/dung/dungDbFunctions.ts +++ b/src/lib/skilling/skills/dung/dungDbFunctions.ts @@ -1,17 +1,7 @@ import { reduceNumByPercent } from 'e'; import { gorajanArcherOutfit, gorajanOccultOutfit, gorajanWarriorOutfit } from '../../../data/CollectionsExport'; -import { skillsMeetRequirements } from '../../../util'; import { SkillsEnum } from '../../types'; -import { requiredSkills } from './dungData'; - -export function hasRequiredLevels(user: MUser, floor: number) { - return skillsMeetRequirements(user.skillsAsXP, requiredSkills(floor)); -} - -export function calcMaxFloorUserCanDo(user: MUser) { - return [7, 6, 5, 4, 3, 2, 1].find(floor => hasRequiredLevels(user, floor)) || 1; -} export function calcUserGorajanShardChance(user: MUser) { return calcGorajanShardChance({ diff --git a/src/lib/skilling/skills/hunter/creatures/bso.ts b/src/lib/skilling/skills/hunter/creatures/bso.ts index b183ca5cf2..28ca37d6dd 100644 --- a/src/lib/skilling/skills/hunter/creatures/bso.ts +++ b/src/lib/skilling/skills/hunter/creatures/bso.ts @@ -17,7 +17,6 @@ const customBSOCreatures: Creature[] = [ .oneIn(90, 'Pet Mystery Box') .oneIn(30, 'Equippable mystery box') .add('Tradeable Mystery Box') - .add('Untradeable Mystery Box') ) .tertiary(500, 'Clue scroll (hard)') .tertiary(5, 'Sand') diff --git a/src/lib/slayer/slayerUtil.ts b/src/lib/slayer/slayerUtil.ts index 6ea275f19a..38af5dde60 100644 --- a/src/lib/slayer/slayerUtil.ts +++ b/src/lib/slayer/slayerUtil.ts @@ -9,6 +9,7 @@ import { CombatOptionsEnum } from '../minions/data/combatConstants'; import { BSOMonsters } from '../minions/data/killableMonsters/custom/customMonsters'; import type { KillableMonster } from '../minions/types'; +import { RelicID } from '../relics'; import { SkillsEnum } from '../skilling/types'; import { roll, stringMatches } from '../util'; import { logError } from '../util/logError'; @@ -98,6 +99,7 @@ export async function calculateSlayerPoints(currentStreak: number, master: Slaye return basePoints * multiplier[i]; } } + return basePoints; } @@ -249,6 +251,9 @@ export async function assignNewSlayerTask(_user: MUser, master: SlayerMaster) { } } + if (!assignedTask) { + return 'Failed to assign you a task.'; + } let quantity = randInt(assignedTask!.amount[0], maxQuantity); const extendReward = SlayerRewardsShop.find(srs => srs.extendID?.includes(assignedTask!.monster.id)); @@ -306,6 +311,10 @@ export async function calcMaxBlockedTasks(user: MUser) { if (hasBlockAndRoll) { blocks += 3; } + + if (user.hasRelic(RelicID.Slay)) { + blocks += 500; + } return blocks; } diff --git a/src/lib/sorts.ts b/src/lib/sorts.ts index a214ce510b..e3b1222c77 100644 --- a/src/lib/sorts.ts +++ b/src/lib/sorts.ts @@ -1,14 +1,11 @@ import type { Item } from 'oldschooljs/dist/meta/types'; -import { marketPriceOrBotPrice } from './marketPrices'; - -export const BankSortMethods = ['value', 'alch', 'name', 'quantity', 'market'] as const; +export const BankSortMethods = ['value', 'alch', 'name', 'quantity'] as const; export type BankSortMethod = (typeof BankSortMethods)[number]; type SortFn = (a: [Item, number], b: [Item, number]) => number; export const sorts: Record = { value: (a, b) => b[0].price * b[1] - a[0].price * a[1], - market: (a, b) => marketPriceOrBotPrice(b[0].id) * b[1] - marketPriceOrBotPrice(a[0].id) * a[1], alch: (a, b) => (b[0].highalch ?? 0) * b[1] - (a[0].highalch ?? 0) * a[1], name: (a, b) => a[0].name.localeCompare(b[0].name), quantity: (a, b) => b[1] - a[1] diff --git a/src/lib/startupScripts.ts b/src/lib/startupScripts.ts index 67b55e420a..c42c27ee87 100644 --- a/src/lib/startupScripts.ts +++ b/src/lib/startupScripts.ts @@ -4,7 +4,6 @@ const startupScripts: { sql: string; ignoreErrors?: true }[] = []; const arrayColumns = [ ['guilds', 'disabledCommands'], - ['guilds', 'staffOnlyChannels'], ['users', 'badges'], ['users', 'bitfield'], ['users', 'favoriteItems'], @@ -18,8 +17,7 @@ const arrayColumns = [ ['users', 'slayer.autoslay_options'], ['users', 'monkeys_fought'], ['users', 'unlocked_blueprints'], - ['users', 'disabled_inventions'], - ['users', 'unlocked_gear_templates'] + ['users', 'disabled_inventions'] ]; for (const [table, column] of arrayColumns) { @@ -34,102 +32,6 @@ ALTER TABLE "${table}" }); } -interface CheckConstraint { - table: string; - column: string; - name: string; - body: string; -} -const checkConstraints: CheckConstraint[] = [ - { - table: 'users', - column: 'lms_points', - name: 'users_lms_points_min', - body: 'lms_points >= 0' - }, - { - table: 'users', - column: '"GP"', - name: 'users_gp', - body: '"GP" >= 0' - }, - { - table: 'users', - column: '"QP"', - name: 'users_qp', - body: '"QP" >= 0' - }, - { - table: 'ge_listing', - column: 'asking_price_per_item', - name: 'asking_price_per_item_min', - body: 'asking_price_per_item >= 1' - }, - { - table: 'ge_listing', - column: 'total_quantity', - name: 'total_quantity_min', - body: 'total_quantity >= 1' - }, - { - table: 'ge_listing', - column: 'quantity_remaining', - name: 'quantity_remaining_min', - body: 'quantity_remaining >= 0' - }, - { - table: 'ge_transaction', - column: 'quantity_bought', - name: 'quantity_bought_min', - body: 'quantity_bought >= 0' - }, - { - table: 'ge_transaction', - column: 'price_per_item_before_tax', - name: 'price_per_item_before_tax_min', - body: 'price_per_item_before_tax >= 1' - }, - { - table: 'ge_transaction', - column: 'price_per_item_after_tax', - name: 'price_per_item_after_tax_min', - body: 'price_per_item_after_tax >= 1' - }, - { - table: 'ge_transaction', - column: 'tax_rate_percent_min', - name: 'tax_rate_percent_min', - body: 'tax_rate_percent >= 1' - }, - { - table: 'ge_transaction', - column: 'total_tax_paid', - name: 'total_tax_paid_min', - body: 'total_tax_paid >= 0' - }, - { - table: 'ge_bank', - column: 'quantity', - name: 'ge_bank_quantity_min', - body: 'quantity >= 0' - } -]; - -for (const { table, name, body } of checkConstraints) { - startupScripts.push({ - sql: `DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 - FROM information_schema.check_constraints - WHERE constraint_name = '${name}' - AND constraint_schema = 'public') - THEN - ALTER TABLE "${table}" ADD CONSTRAINT "${name}" CHECK (${body}); - END IF; -END$$;` - }); -} - startupScripts.push({ sql: 'CREATE UNIQUE INDEX IF NOT EXISTS activity_only_one_task ON activity (user_id, completed) WHERE NOT completed;' }); @@ -137,19 +39,6 @@ startupScripts.push({ sql: 'CREATE UNIQUE INDEX IF NOT EXISTS tame_only_one_task ON tame_activity (user_id, completed) WHERE NOT completed;' }); -startupScripts.push({ - sql: `CREATE INDEX IF NOT EXISTS idx_ge_listing_buy_filter_sort -ON ge_listing (type, fulfilled_at, cancelled_at, user_id, asking_price_per_item DESC, created_at ASC);` -}); -startupScripts.push({ - sql: `CREATE INDEX IF NOT EXISTS idx_ge_listing_sell_filter_sort -ON ge_listing (type, fulfilled_at, cancelled_at, user_id, asking_price_per_item ASC, created_at ASC);` -}); - -startupScripts.push({ - sql: `CREATE INDEX IF NOT EXISTS ge_transaction_sell_listing_id_created_at_idx -ON ge_transaction (sell_listing_id, created_at DESC);` -}); const itemMetaDataNames = Items.map(item => `(${item.id}, '${item.name.replace(/'/g, "''")}')`).join(', '); const itemMetaDataQuery = ` INSERT INTO item_metadata (id, name) diff --git a/src/lib/structures/Boss.ts b/src/lib/structures/Boss.ts index 7f0361998c..33c6d142c9 100644 --- a/src/lib/structures/Boss.ts +++ b/src/lib/structures/Boss.ts @@ -3,15 +3,15 @@ import { Time, calcPercentOfNum, calcWhatPercent, randFloat, reduceNumByPercent, import { Bank } from 'oldschooljs'; import type { GearSetupType, GearStats } from '../gear'; -import { trackLoot } from '../lootTrack'; + import { effectiveMonsters } from '../minions/data/killableMonsters'; import { setupParty } from '../party'; +import { RelicID } from '../relics'; import type { Skills } from '../types'; import type { NewBossOptions } from '../types/minions'; import { formatDuration, formatSkillRequirements, hasSkillReqs, isWeekend, makeTable } from '../util'; import addSubTaskToActivityTask from '../util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../util/calcMaxTripLength'; -import { type ClientBankKey, updateBankSetting } from '../util/updateBankSetting'; import type { Gear } from './Gear'; export const gpCostPerKill = (user: MUser) => @@ -117,7 +117,6 @@ export interface BossOptions { mostImportantStat: keyof GearStats; ignoreStats?: (keyof GearStats)[]; food: Bank | ((user: MUser) => Bank); - settingsKeys?: [ClientBankKey, ClientBankKey]; channel: TextChannel; activity: 'VasaMagus' | 'KingGoldemar' | 'Ignecarus' | 'BossEvent'; massText: string; @@ -173,7 +172,6 @@ export class BossInstance { allowMoreThan1Solo = false; allowMoreThan1Group = false; totalPercent = -1; - settingsKeys?: [ClientBankKey, ClientBankKey]; channel: TextChannel; activity: 'VasaMagus' | 'KingGoldemar' | 'Ignecarus' | 'BossEvent'; massText: string; @@ -206,7 +204,6 @@ export class BossInstance { this.ignoreStats = options.ignoreStats ?? []; this.id = options.id; this.food = options.food; - this.settingsKeys = options.settingsKeys; this.channel = options.channel; this.activity = options.activity; this.leader = options.leader; @@ -429,6 +426,11 @@ export class BossInstance { duration = reduceNumByPercent(duration, 5); } + if (this.users?.some(u => u.hasRelic(RelicID.Speed))) { + this.boosts.push('Your Relic of Speed grants you a 30% speed boost.'); + duration = reduceNumByPercent(duration, 30); + } + return { bossUsers, duration, @@ -462,24 +464,10 @@ export class BossInstance { totalCost.add(bossUser.itemsToRemove); } } - if (this.settingsKeys) { - updateBankSetting(this.settingsKeys[0], totalCost); - } const monster = effectiveMonsters.find(m => m.id === this.id); if (!monster) { console.error(`No monster for ${this.id}`); - } else { - await trackLoot({ - changeType: 'cost', - totalCost, - id: monster.name, - type: 'Monster', - users: this.bossUsers.map(i => ({ - id: i.user.id, - cost: i.itemsToRemove - })) - }); } await addSubTaskToActivityTask({ diff --git a/src/lib/structures/Gear.ts b/src/lib/structures/Gear.ts index d8665db347..0d9fcf4af8 100644 --- a/src/lib/structures/Gear.ts +++ b/src/lib/structures/Gear.ts @@ -1,4 +1,3 @@ -import type { GearPreset } from '@prisma/client'; import { notEmpty, objectKeys, uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; import type { Item } from 'oldschooljs/dist/meta/types'; @@ -9,7 +8,6 @@ import { getSimilarItems, inverseSimilarItems } from '../data/similarItems'; import type { DefenceGearStat, GearSetup, - GearSetupType, GearSlotItem, GearStats, OffenceGearStat, @@ -90,282 +88,6 @@ export const defaultGear: GearSetup = { }; Object.freeze(defaultGear); -export const globalPresets: (GearPreset & { defaultSetup: GearSetupType })[] = [ - { - name: 'graceful', - user_id: '123', - head: itemID('Graceful hood'), - neck: null, - body: itemID('Graceful top'), - legs: itemID('Graceful legs'), - cape: itemID('Graceful cape'), - two_handed: null, - hands: itemID('Graceful gloves'), - feet: itemID('Graceful boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - defaultSetup: 'skilling', - pinned_setup: null - }, - { - name: 'carpenter', - user_id: '123', - head: itemID("Carpenter's helmet"), - neck: null, - body: itemID("Carpenter's shirt"), - legs: itemID("Carpenter's trousers"), - cape: null, - two_handed: null, - hands: null, - feet: itemID("Carpenter's boots"), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - defaultSetup: 'skilling', - pinned_setup: null - }, - { - name: 'rogue', - user_id: '123', - head: itemID('Rogue mask'), - neck: null, - body: itemID('Rogue top'), - legs: itemID('Rogue trousers'), - cape: null, - two_handed: null, - hands: itemID('Rogue gloves'), - feet: itemID('Rogue boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - defaultSetup: 'skilling', - pinned_setup: null - }, - { - name: 'clue_hunter', - user_id: '123', - head: itemID('Helm of raedwald'), - neck: null, - body: itemID('Clue hunter garb'), - legs: itemID('Clue hunter trousers'), - cape: itemID('Clue hunter cloak'), - two_handed: null, - hands: itemID('Clue hunter gloves'), - feet: itemID('Clue hunter boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - defaultSetup: 'skilling', - pinned_setup: null - }, - { - name: 'angler', - user_id: '123', - head: itemID('Angler hat'), - neck: null, - body: itemID('Angler top'), - legs: itemID('Angler waders'), - cape: null, - two_handed: null, - hands: null, - feet: itemID('Angler boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - defaultSetup: 'skilling', - pinned_setup: null - }, - { - name: 'spirit_angler', - user_id: '123', - head: itemID('Spirit angler headband'), - neck: null, - body: itemID('Spirit angler top'), - legs: itemID('Spirit angler waders'), - cape: null, - two_handed: null, - hands: null, - feet: itemID('Spirit angler boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - defaultSetup: 'skilling', - pinned_setup: null - }, - { - name: 'pyromancer', - user_id: '123', - head: itemID('Pyromancer hood'), - neck: null, - body: itemID('Pyromancer garb'), - legs: itemID('Pyromancer robe'), - cape: null, - two_handed: null, - hands: null, - feet: itemID('Pyromancer boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - defaultSetup: 'skilling', - pinned_setup: null - }, - { - name: 'prospector', - user_id: '123', - head: itemID('Prospector helmet'), - neck: null, - body: itemID('Prospector jacket'), - legs: itemID('Prospector legs'), - cape: null, - two_handed: null, - hands: null, - feet: itemID('Prospector boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - defaultSetup: 'skilling', - pinned_setup: null - }, - { - name: 'lumberjack', - user_id: '123', - head: itemID('Lumberjack hat'), - neck: null, - body: itemID('Lumberjack top'), - legs: itemID('Lumberjack legs'), - cape: null, - two_handed: null, - hands: null, - feet: itemID('Lumberjack boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - defaultSetup: 'skilling', - pinned_setup: null - }, - { - name: 'farmer', - user_id: '123', - head: itemID("Farmer's strawhat"), - neck: null, - body: itemID("Farmer's jacket"), - legs: itemID("Farmer's boro trousers"), - cape: null, - two_handed: null, - hands: null, - feet: itemID("Farmer's boots"), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - defaultSetup: 'skilling', - pinned_setup: null - }, - { - name: 'runecraft', - user_id: '123', - head: itemID('Hat of the eye'), - neck: null, - body: itemID('Robe top of the eye'), - legs: itemID('Robe bottoms of the eye'), - cape: null, - two_handed: null, - hands: null, - feet: itemID('Boots of the eye'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - defaultSetup: 'skilling', - pinned_setup: null - }, - { - name: 'diviner', - user_id: '123', - head: itemID("Diviner's headwear"), - neck: null, - body: itemID("Diviner's robe"), - legs: itemID("Diviner's legwear"), - cape: null, - two_handed: null, - hands: itemID("Diviner's handwear"), - feet: itemID("Diviner's footwear"), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - defaultSetup: 'skilling', - pinned_setup: null - }, - { - name: 'smith', - user_id: '123', - head: null, - neck: null, - body: itemID('Smiths tunic'), - legs: itemID('Smiths trousers'), - cape: null, - two_handed: null, - hands: itemID('Smiths gloves'), - feet: itemID('Smiths boots'), - shield: null, - weapon: null, - ring: null, - ammo: null, - ammo_qty: null, - emoji_id: null, - times_equipped: 0, - defaultSetup: 'skilling', - pinned_setup: null - } -]; - export const baseStats: GearStats = { attack_stab: 0, attack_slash: 0, @@ -398,29 +120,7 @@ export class Gear { [EquipmentSlot.Weapon]: GearSlotItem | null = null; stats = baseStats; - constructor(_setup: GearSetup | PartialGearSetup | GearPreset = {}) { - if ('user_id' in _setup) { - const gear: GearSetup = {} as GearSetup; - for (const key of [ - 'cape', - 'feet', - 'hands', - 'head', - 'legs', - 'neck', - 'ring', - 'shield', - 'weapon', - 'body' - ] as const) { - const val = _setup[key]; - gear[key] = val !== null ? { item: val, quantity: 1 } : null; - } - - gear.ammo = _setup.ammo ? { item: _setup.ammo, quantity: _setup.ammo_qty ?? 1 } : null; - gear['2h'] = _setup.two_handed ? { item: _setup.two_handed, quantity: 1 } : null; - return new Gear(gear); - } + constructor(_setup: GearSetup | PartialGearSetup = {}) { const setup = (typeof _setup.ammo === 'undefined' || typeof _setup.ammo === 'string') && !('user_id' in _setup) ? constructGearSetup(_setup as PartialGearSetup) diff --git a/src/lib/structures/LastManStandingUsage.ts b/src/lib/structures/LastManStandingUsage.ts deleted file mode 100644 index 41330ba4f7..0000000000 --- a/src/lib/structures/LastManStandingUsage.ts +++ /dev/null @@ -1,244 +0,0 @@ -export default class LastManStandingUsage { - public contestants = 0; - public deaths: Set = new Set(); - public killers: Set = new Set(); - private readonly parts: (string | number)[] = []; - - public constructor(usage: string) { - this.parse(usage); - } - - public display(...values: string[]) { - return this.parts.map(part => (typeof part === 'number' ? `**${values[part]}**` : part)).join(''); - } - - public parse(usage: string): void { - let current = ''; - let char: string | undefined = undefined; - for (let i = 0; i < usage.length; i++) { - char = usage.charAt(i); - if (char === '{') { - // If there was text, push buffer - if (current) { - this.parts.push(current); - current = ''; - } - - // Parse tag {NT?} - const n = Number(usage.charAt(++i)); - - // If N > contestants, assign contestants to N - if (n > this.contestants) this.contestants = n; - - const nextChar = ++i; - // If D, add death; if K, add killer - if (usage.charAt(nextChar) === 'D') { - this.deaths.add(n - 1); - i++; - } else if (usage.charAt(nextChar) === 'K') { - this.killers.add(n - 1); - i++; - } - - this.parts.push(n - 1); - } else { - current += char; - } - } - - if (current) this.parts.push(current); - } -} - -export const LMS_PREP = [ - '{1} grabs a Dragon dagger.', - '{1} grabs an Abyssal whip and retreats.', - '{1} and {2} fight for a whip. {1} gives up and retreats.', - "{1} finds a Heavy ballista, some Javelins, and an Ava's assembler.", - '{1} runs into a building and hides upstairs.', - "{1} takes a handful of Morrigan's Javelins.", - "{1} rips a Zuriel's staff out of {2}'s hands.", - '{1} finds a Saradomin brew.', - '{1} gathers as much food as they can.', - "{1} grabs a Vesta's longsword.", - '{1} takes a Ghrazi Rapier from inside the Trinity Outpost.', - '{1} finds a Crate with an Infernal cape.', - '{1} takes a AGS from inside the Moser Settlement.', - '{1}, {2}, and {3} work together to get as many supplies as possible.', - '{1} runs away with a pair of Eternal boots and some sharks.', - '{1} snatches a vial of Super restore and a Stamina potion.', - '{1} finds a box full of supplies and a Granite maul.', - '{1} searches a supply box, not realizing it is empty.', - '{1}, {2}, {3}, and {4} share everything they gathered before running.', - '{1} retrieves a Staff of the Dead from the Debtor hideout .', - "{1} grabs a Stamina potion while {2} finds a set of Ahrim's robes.", - '{1} scares {2} away from the Moser Settlement.', - '{1} grabs a Dragonfire shield from the ground on The Mountain.', - '{1} snatches a pair of Dragon claws.', - '{1} grabs a lone pair of pants.', - '{1} grabs a lone ranger boot and wears it on their head.', - '{1} trips over while running to a building, {2} picks them up and they run off together.', - '{1} has to go to the bathroom and comes back to {2} picking their pockets' -].map(string => new LastManStandingUsage(string)); - -export const LMS_FINAL = [ - '{1} eats a shark.', - '{1} attacks {2}, but they manage to escape.', - '{1} chases {2}.', - '{1} runs away from {2}.', - '{1} defeats {2} in a fight, but spares their life.', - '{1} and {2} work together for now...', - '{1} travels to higher ground.', - '{1} forces {2} to eat pant.', - '{1} searches for a bloody chest.', - '{1} uses their bloodier key and receives a full inventory of food.', - "{1K} throws a dragon knife into {2D}'s head.", - '{1D} begs for {2K} to kill them. They reluctantly oblige, killing {1D}.', - '{1K} decapitates {2D} with an AGS spec.', - '{1K} severely injures {2D}, but puts them out of their misery.', - '{1K} severely injures {2D} and leaves them to die.', - "{1K} bashes {2D}'s head in with a Dragon mace.", - "{1K} throws a dragon knife into {2D}'s chest.", - '{1D} is unable to convince {2K} to not kill them.', - '{1K} convinces {2D} to not kill them, only to kill {2D} instead.', - '{1D} unknowingly runs into the fog and dies.', - '{1K} throws a chinchompa, killing {2D}.', - '{1K} throws a chinchompa, killing {2D}, and {3D}.', - '{1D} dies from poison.', - '{1D} dies from venom.', - "{1K} kills {2D} with a Morrigan's throwing axe.", - '{1D} dies in the fog trying to escape the arena.', - '{1D} accidently detonates a chinchompa trying to throw it.', - '{1K} ambushes {2D} and kills them.', - "{1K} KO's {2D} with an AGS spec.", - '{1K} shoots {2D} with a Dark bow.', - "{1K} stabs {2D} in the back with a Vesta's longsword.", - '{1K} kills {2D} with a Ghrazi rapier.', - '{1K} punches {2D} in the face.', - '{1D} Talks about how good they are while fighting {2K} and is promptly stacked out by {2K}.', - '{1K} repeatedly stabs {2D} to death with Draggon dagger.', - '{1D} is found to be using an auto-prayer client and is banned mid game.' -].map(string => new LastManStandingUsage(string)); - -export const LMS_ROUND = [ - '{1D} steps out of the safezone too soon and gets killed by the fog.', - "{1K} throws a Morrigan's Javelin into {2D}'s head.", - '{1D} gets 6 Hour logged and dies.', - '{1K} catches {2D} off guard and AGS specs a 73.', - "{1K} shoots a Dragon arrow into {2D}'s head.", - '{1D} cannot handle the circumstances and runs off into the fog.', - "{1K} bashes {2D}'s head with a Granite maul several times.", - '{1K} exsanguinates {2D} with a Ghrazi Rapier.', - "{1K} plunges a Dragon dagger into {2D}'s abdomen.", - '{1K} sets {2D} on fire with an Infernal cape.', - '{1D} falls into a pit and dies.', - '{1K} stabs {2D} while their back is turned.', - '{1K} severely injures {2D}, but puts them out of their misery.', - '{1K} severely injures {2D} and leaves them to die.', - "{1K} bashes {2D}'s head in with an Elder maul.", - '{1K} pushes {2D} off a cliff during a whip fight.', - "{1K} throws a Dragon knife into {2D}'s chest.", - '{1D} is unable to convince {2K} to not kill them.', - '{1K} convinces {2D} to not kill them, only to kill {2D} instead.', - '{1K}, {2}, and {3D} start fighting, but {2} runs away as {1K} kills {3D}.', - '{1K} kills {2D} with their own spec weapon.', - '{1K} overpowers {2D}, killing them.', - '{1K} throws a Chinchompa, killing {2D}.', - '{1K} throws a Chinchompa, killing {2D}, and {3D}.', - '{1K} throws a Chinchompa, killing {2D}, {3D}, and {4D}.', - '{1K} throws a Chinchompa, killing {2D}, {3D}, {4D} and {5D}.', - '{1D} trips over a chinchompa while running away from {2}, blowing themself up.', - '{1D} trips over a chinchompa while running away from {2D}, blowing them both up.', - '{1K} kills {2D} as they try to run away.', - '{1D}, {2D}, {3D}, and {4D} form a suicide pact, and run off into the fog.', - '{1K} kills {2D} with a Dragon thrownaxe.', - '{1K} and {2K} fight {3D} and {4D}. {1K} and {2K} survive.', - '{1D} and {2D} fight {3K} and {4K}. {3K} and {4K} survive.', - '{1D} attacks {2}, but {3K} protects them, killing {1D}.', - '{1K} severely slices {2D} with an Armadyl godsword.', - '{1K} strangles {2D} with an Abyssal whip.', - '{1K} kills {2D} for their supplies.', - '{1K} hurls a Dragon Javelin at {2}, but misses and kills {3D} instead.', - "{1K} shoots a poisonous Dragon dart into {2D}'s neck, slowly killing them.", - '{1K} stabs {2D} with a Ghrazi rapier.', - '{1K} stabs {2D} in the back with a Zuriels Staff.', - '{1K}, {2D}, and {3D} get into a fight. {1K} triumphantly kills them both.', - '{1K} finds {2D} hiding in the Debtor Hideout and kills them.', - '{1D} finds {2K} hiding in the Debtor Hideout, but {2K} kills them.', - '{1K} kills {2D} with a Dragon scimitar.', - '{1K} and {2D} fight for a crate.{1K} strangles {2D} with a whip and runs.', - '{1K} repeatedly stabs {2D} to death with a Ghrazi Rapier.', - '{1D} trips over while running from the fog, and is killed by {2K}.', - '{1} trips over while running from the fog, {2} picks them up and they run off together.', - "{1K} aims a Dragon arrow at {2}'s head and shoots, {3D} jumps in the way and sacrifices their life to save them.", - '{1} searches for a bloody chest.', - '{1} hides in a building to rest.', - '{1} eats all their food.', - '{1} and {2} run into each other and decide to truce for the round.', - '{1} thinks about winning.', - '{1} looks at the night sky.', - '{1} defeats {2} in a fight, but spares their life.', - '{1} begs for {2} to kill them. They refuse, keeping {1} alive.', - '{1} finds a Dragon hatchet in a chest.', - '{1} finds a combat potion in a chest.', - '{1} finds potions in a chest.', - '{1} finds food in a chest.', - '{1} finds a chinchompa in a chest.', - '{1} questions their sanity.', - '{1} forces {2} to eat pant.', - '{1K} forces {2D} to eat pant. {2D} chokes and dies.', - '{1K} catches {2D} off guard and kills them.', - "{1K} throws a dragon knife into {2D}'s head.", - '{1D} begs for {2K} to kill them. They reluctantly oblige, killing {1D}.', - '{1K} and {2K} work together to kill {3D}.', - "{1K} shoots a Dark bow spec into {2D}'s head.", - '{1D} bleeds out due to untreated injuries.', - '{1D} cannot handle the circumstances and logs out.', - '{1D} unknowingly eats cadava berries and dies.', - '{1K} silently specs out {2D}.', - '{1K} decapitates {2D} with an AGS spec.', - '{1K} spears {2D} in the abdomen.', - '{1K} sets {2D} on fire with Flames of Zamorak.', - '{1K} stabs {2D} while their back is turned.', - '{1K} severely injures {2D}, but puts them out of their misery.', - '{1K} severely injures {2D} and leaves them to die.', - "{1K} bashes {2D}'s head in with a mace.", - '{1K} pushes {2D} off a cliff during a knife fight.', - "{1K} throws a knife into {2D}'s chest.", - '{1D} is unable to convince {2K} to not kill them.', - '{1K} convinces {2D} to not kill them, only to kill {2D} instead.', - '{1K}, {2}, and {3D} start fighting, but {2} runs away as {1K} kills {3D}.', - '{1K} kills {2D} with their own weapon.', - '{1K} overpowers {2D}, killing them.', - '{1K} kills {2D} as they try to run.', - '{1K} kills {2D} with a dragon hatchet.', - '{1K} and {2K} fight {3D} and {4D}. {1K} and {2K} survive.', - '{1D} dies in the fog trying to escape the arena.', - '{1D} accidentally detonates a Chinchompa while trying to pet it.', - '{1D} attacks {2}, but {3K} protects them, killing {1D}.', - '{1K} ambushes {2D} and kills them.', - '{1D} accidently sets off a wild chinchompa, getting blown to pieces.', - "{1K} severely slices {2D} with a Vesta's longsword.", - '{1K} strangles {2D} with an Abyssal whip.', - '{1K} kills {2D} for their supplies.', - '{1K} shoots an arrow at {2}, but misses and kills {3D} instead.', - "{1K} shoots a poisonous bronze dart into {2D}'s neck, slowly killing them.", - '{1K}, {2K}, and {3K} successfully ambush and kill {4D}, {5D}, and {6D}.', - '{1D}, {2D}, and {3D} unsuccessfully ambush {4K}, {5K}, and {6K}, who kill them instead.', - "{1K} stabs {2D} with a Vesta's spear.", - '{1} forces {2K} to kill {3D} or {4}. They decide to kill {3D}.', - '{1K} forces {2D} to kill {3} or {4}. They refuse to kill, so {1K} kills them instead.', - "{1D} poisons {2}'s potion, but mistakes it for their own and dies.", - "{1K} poisons {2D}'s potion. They drink it and die.", - '{1K}, {2D}, and {3D} get into a fight. {1K} triumphantly kills them both.', - "{1K} kills {2D} with a Statius's warhammer.", - '{1K} and {2K} track down and kill {3D}.', - '{1K} tracks down and kills {2D}.', - '{1K} repeatedly stabs {2D} to death with a Dragon dagger.', - '{1D} trips and falls into the river.', - '{1D} gets killed by a bot using an auto prayer switcher.', - '{1D} gets hit in the head by a falling supply crate.', - '{1D} disconnects due to their mom pulling out the internet cord', - '{1K} smashes {2D} over the head with a pet rock.', - "{1D} gets told it's time for dinner" -].map(string => new LastManStandingUsage(string)); diff --git a/src/lib/structures/MUserStats.ts b/src/lib/structures/MUserStats.ts index 3f4e3c8008..a97984d744 100644 --- a/src/lib/structures/MUserStats.ts +++ b/src/lib/structures/MUserStats.ts @@ -95,14 +95,6 @@ export class MUserStats { return new Bank(this.userStats.clue_upgrader_bank as ItemBank); } - get icCostBank(): Bank { - return new Bank(this.userStats.ic_cost_bank as ItemBank); - } - - get icLootBank(): Bank { - return new Bank(this.userStats.ic_loot_bank as ItemBank); - } - get pekyLootBank(): Bank { return new Bank(this.userStats.peky_loot_bank as ItemBank); } @@ -135,14 +127,6 @@ export class MUserStats { return new Bank(this.userStats.doubled_loot_bank as ItemBank); } - get icDonationsGivenBank(): Bank { - return new Bank(this.userStats.ic_donations_given_bank as ItemBank); - } - - get icDonationsReceivedBank(): Bank { - return new Bank(this.userStats.ic_donations_received_bank as ItemBank); - } - get tameClBank(): Bank { return new Bank(this.userStats.tame_cl_bank as ItemBank); } diff --git a/src/lib/structures/Requirements.ts b/src/lib/structures/Requirements.ts index 6a215a736d..19147e5759 100644 --- a/src/lib/structures/Requirements.ts +++ b/src/lib/structures/Requirements.ts @@ -10,7 +10,6 @@ import { BOT_TYPE, BitFieldData } from '../constants'; import { diaries, userhasDiaryTierSync } from '../diaries'; import { effectiveMonsters } from '../minions/data/killableMonsters'; import type { ClueBank, DiaryID, DiaryTierName } from '../minions/types'; -import type { RobochimpUser } from '../roboChimp'; import type { MinigameName } from '../settings/minigames'; import Agility from '../skilling/skills/agility'; import type { Skills } from '../types'; @@ -27,7 +26,6 @@ interface RequirementUserArgs { stashUnits: ParsedUnit[]; minigames: Minigame; stats: MUserStats; - roboChimpUser: RobochimpUser; clueCounts: ClueBank; poh: PlayerOwnedHouse; uniqueRunesCrafted: number[]; @@ -319,7 +317,6 @@ export class Requirements { const minigames = await user.fetchMinigames(); const stashUnits = await getParsedStashUnits(user.id); const stats = await MUserStats.fromID(user.id); - const roboChimpUser = await user.fetchRobochimpUser(); const clueCounts = BOT_TYPE === 'OSB' ? stats.clueScoresFromOpenables() : (await user.calcActualClues()).clueCounts; @@ -344,7 +341,6 @@ GROUP BY type;`, minigames, stashUnits, stats, - roboChimpUser, clueCounts, poh, uniqueRunesCrafted, diff --git a/src/lib/tableBank.ts b/src/lib/tableBank.ts deleted file mode 100644 index d8c4414a48..0000000000 --- a/src/lib/tableBank.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Bank } from 'oldschooljs'; - -import type { ItemBank } from './types'; -import { validateBankAndThrow } from './util'; - -export function makeTransactFromTableBankQueries({ - bankToAdd, - bankToRemove -}: { - bankToAdd?: Bank; - bankToRemove?: Bank; -}) { - const queries = []; - - if (bankToAdd) { - for (const [item, quantity] of bankToAdd.items()) { - queries.push( - prisma.$queryRawUnsafe(`INSERT INTO ge_bank(item_id, quantity) VALUES(${item.id}, ${quantity}) -ON CONFLICT(item_id) -DO UPDATE SET quantity = "ge_bank"."quantity" + ${quantity};`) - ); - } - } - - if (bankToRemove) { - for (const [item, quantity] of bankToRemove.items()) { - queries.push( - prisma.$queryRawUnsafe(`UPDATE ge_bank - SET quantity = "ge_bank"."quantity" - ${quantity} - WHERE item_id = ${item.id};`) - ); - } - } - - return queries; -} - -export async function fetchTableBank() { - const result = await prisma.$queryRawUnsafe<{ bank: ItemBank }[]>( - 'SELECT json_object_agg(item_id, quantity) as bank FROM ge_bank WHERE quantity != 0;' - ); - const bank = new Bank(result[0].bank); - validateBankAndThrow(bank); - return bank; -} diff --git a/src/lib/tickers.ts b/src/lib/tickers.ts index 1691491be3..d9fa3db759 100644 --- a/src/lib/tickers.ts +++ b/src/lib/tickers.ts @@ -1,45 +1,11 @@ -import { Stopwatch } from '@oldschoolgg/toolkit'; -import type { TextChannel } from 'discord.js'; -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from 'discord.js'; -import { Time, noOp, randInt, removeFromArr, shuffleArr } from 'e'; +import { Time, randInt, shuffleArr } from 'e'; -import { production } from '../config'; -import { userStatsUpdate } from '../mahoji/mahojiSettings'; import { runTameTask } from '../tasks/tames/tameTasks'; -import { mahojiUserSettingsUpdate } from './MUser'; import { processPendingActivities } from './Task'; -import { BitField, Channel, PeakTier, informationalButtons } from './constants'; -import { GrandExchange } from './grandExchange'; -import { collectMetrics } from './metrics'; -import { queryCountStore } from './settings/prisma'; -import { runCommand } from './settings/settings'; -import { getFarmingInfo } from './skilling/functions/getFarmingInfo'; -import Farming from './skilling/skills/farming'; -import { awaitMessageComponentInteraction, getSupportGuild, makeComponents, stringMatches } from './util'; -import { farmingPatchNames, getFarmingKeyFromName } from './util/farmingHelpers'; -import { handleGiveawayCompletion } from './util/giveaway'; -import { logError } from './util/logError'; -import { minionIsBusy } from './util/minionIsBusy'; +import { PeakTier, globalConfig } from './constants'; -let lastMessageID: string | null = null; -const supportEmbed = new EmbedBuilder() - .setAuthor({ name: '⚠️ ⚠️ ⚠️ ⚠️ READ THIS ⚠️ ⚠️ ⚠️ ⚠️' }) - .addFields({ - name: '📖 Read the FAQ', - value: 'The FAQ answers commonly asked questions: https://wiki.oldschool.gg/faq - also make sure to read the other pages of the website, which might contain the information you need.' - }) - .addFields({ - name: '🔎 Search', - value: 'Search this channel first, you might find your question has already been asked and answered.' - }) - .addFields({ - name: '💬 Ask', - value: "If your question isn't answered in the FAQ, and you can't find it from searching, simply ask your question and wait for someone to answer. If you don't get an answer, you can post your question again." - }) - .addFields({ - name: '⚠️ Dont ping anyone', - value: 'Do not ping mods, or any roles/people in here. You will be muted. Ask your question, and wait.' - }); +import { Stopwatch } from '@oldschoolgg/toolkit'; +import { logError } from './util/logError'; export interface Peak { startTime: number; @@ -57,91 +23,15 @@ export const tickers: { timer: NodeJS.Timeout | null; cb: () => Promise; }[] = [ - { - name: 'giveaways', - startupWait: Time.Second * 30, - interval: Time.Second * 10, - timer: null, - cb: async () => { - const result = await prisma.giveaway.findMany({ - where: { - completed: false, - finish_date: { - lt: new Date() - } - } - }); - - await Promise.all(result.map(t => handleGiveawayCompletion(t))); - } - }, - { - name: 'metrics', - timer: null, - interval: Time.Minute, - cb: async () => { - const storedCount = queryCountStore.value; - queryCountStore.value = 0; - const data = { - timestamp: Math.floor(Date.now() / 1000), - ...(await collectMetrics()), - qps: storedCount / 60 - }; - if (Number.isNaN(data.eventLoopDelayMean)) { - data.eventLoopDelayMean = 0; - } - await prisma.metric.create({ - data - }); - } - }, { name: 'minion_activities', startupWait: Time.Second * 10, timer: null, - interval: production ? Time.Second * 5 : 500, + interval: globalConfig.isProduction ? Time.Second * 5 : 500, cb: async () => { await processPendingActivities(); } }, - { - name: 'daily_reminders', - interval: Time.Minute * 3, - startupWait: Time.Minute, - timer: null, - cb: async () => { - const result = await prisma.$queryRawUnsafe<{ id: string; last_daily_timestamp: bigint }[]>( - ` -SELECT users.id, user_stats.last_daily_timestamp -FROM users -JOIN user_stats ON users.id::bigint = user_stats.user_id -WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_timestamp" != -1 AND to_timestamp(user_stats."last_daily_timestamp" / 1000) < now() - INTERVAL '4 hours'; -` - ); - const dailyDMButton = new ButtonBuilder() - .setCustomId('CLAIM_DAILY') - .setLabel('Claim Daily') - .setEmoji('493286312854683654') - .setStyle(ButtonStyle.Secondary); - const components = [dailyDMButton]; - const str = 'Your daily is ready!'; - - for (const row of result.values()) { - if (!production) continue; - if (Number(row.last_daily_timestamp) === -1) continue; - - await userStatsUpdate( - row.id, - { - last_daily_timestamp: -1 - }, - {} - ); - const user = await globalClient.fetchUser(row.id); - await user.send({ content: str, components: makeComponents(components) }).catch(noOp); - } - } - }, { name: 'wilderness_peak_times', timer: null, @@ -185,151 +75,6 @@ WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_t globalClient._peakIntervalCache = peakInterval; } }, - { - name: 'farming_reminder_ticker', - startupWait: Time.Minute, - interval: Time.Minute * 3.5, - timer: null, - cb: async () => { - if (!production) return; - const basePlantTime = 1_626_556_507_451; - const now = Date.now(); - const users = await prisma.user.findMany({ - where: { - bitfield: { - hasSome: [ - BitField.IsPatronTier3, - BitField.IsPatronTier4, - BitField.IsPatronTier5, - BitField.IsPatronTier6, - BitField.isContributor, - BitField.isModerator - ] - } - }, - select: { - id: true, - bitfield: true - } - }); - for (const { id, bitfield } of users) { - if (bitfield.includes(BitField.DisabledFarmingReminders)) continue; - const { patches } = await getFarmingInfo(id); - for (const patchType of farmingPatchNames) { - const patch = patches[patchType]; - if (!patch) continue; - if (patch.plantTime < basePlantTime) continue; - - const storeHarvestablePlant = patch.lastPlanted; - const planted = storeHarvestablePlant - ? Farming.Plants.find(plants => stringMatches(plants.name, storeHarvestablePlant)) ?? - Farming.Plants.find( - plants => - stringMatches(plants.name, storeHarvestablePlant) || - stringMatches(plants.name.split(' ')[0], storeHarvestablePlant) - ) - : null; - const difference = now - patch.plantTime; - if (!planted) continue; - if (difference < planted.growthTime * Time.Minute) continue; - if (patch.wasReminded) continue; - await mahojiUserSettingsUpdate(id, { - [getFarmingKeyFromName(patchType)]: { ...patch, wasReminded: true } - }); - - // Build buttons (only show Harvest/replant if not busy): - const farmingReminderButtons = new ActionRowBuilder(); - if (!minionIsBusy(id)) { - farmingReminderButtons.addComponents( - new ButtonBuilder() - .setLabel('Harvest & Replant') - .setStyle(ButtonStyle.Primary) - .setCustomId('HARVEST') - ); - } - // Always show disable reminders: - farmingReminderButtons.addComponents( - new ButtonBuilder() - .setLabel('Disable Reminders') - .setStyle(ButtonStyle.Secondary) - .setCustomId('DISABLE') - ); - const user = await globalClient.users.cache.get(id); - if (!user) continue; - const message = await user - .send({ - content: `The ${planted.name} planted in your ${patchType} patches are ready to be harvested!`, - components: [farmingReminderButtons] - }) - .catch(noOp); - if (!message) return; - try { - const selection = await awaitMessageComponentInteraction({ - message, - time: Time.Minute * 5, - filter: () => true - }); - if (!selection.isButton()) return; - message.edit({ components: [] }); - - // Check disable first so minion doesn't have to be free to disable reminders. - if (selection.customId === 'DISABLE') { - await mahojiUserSettingsUpdate(user.id, { - bitfield: removeFromArr(bitfield, BitField.DisabledFarmingReminders) - }); - await user.send('Farming patch reminders have been disabled.'); - return; - } - if (minionIsBusy(user.id)) { - selection.reply({ content: 'Your minion is busy.' }); - return; - } - if (selection.customId === 'HARVEST') { - message.author = user; - runCommand({ - commandName: 'farming', - args: { harvest: { patch_name: patchType } }, - bypassInhibitors: true, - channelID: message.channel.id, - guildID: undefined, - user: await mUserFetch(user.id), - member: message.member, - interaction: selection, - continueDeltaMillis: selection.createdAt.getTime() - message.createdAt.getTime() - }); - } - } catch { - message.edit({ components: [] }); - } - } - } - } - }, - { - name: 'support_channel_messages', - timer: null, - startupWait: Time.Second * 22, - interval: Time.Minute * 20, - cb: async () => { - if (!production) return; - const guild = getSupportGuild(); - const channel = guild?.channels.cache.get(Channel.HelpAndSupport) as TextChannel | undefined; - if (!channel) return; - const messages = await channel.messages.fetch({ limit: 5 }); - if (messages.some(m => m.author.id === globalClient.user?.id)) return; - if (lastMessageID) { - const message = await channel.messages.fetch(lastMessageID).catch(noOp); - if (message) { - await message.delete(); - } - } - const res = await channel.send({ - embeds: [supportEmbed], - components: [new ActionRowBuilder().addComponents(informationalButtons)] - }); - lastMessageID = res.id; - } - }, { name: 'tame_activities', startupWait: Time.Second * 15, @@ -338,7 +83,7 @@ WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_t cb: async () => { const tameTasks = await prisma.tameActivity.findMany({ where: { - finish_date: production + finish_date: globalConfig.isProduction ? { lt: new Date() } @@ -363,18 +108,9 @@ WHERE bitfield && '{2,3,4,5,6,7,8,12,21,24}'::int[] AND user_stats."last_daily_t }); for (const task of tameTasks) { - await runTameTask(task, task.tame); + runTameTask(task, task.tame); } } - }, - { - name: 'ge_ticker', - startupWait: Time.Second * 30, - timer: null, - interval: Time.Second * 10, - cb: async () => { - await GrandExchange.tick(); - } } ]; diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index 9846a53418..7e0526b2a9 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -35,11 +35,3 @@ export type CategoryFlag = | 'utility' | 'fun' | 'simulation'; - -export interface IDiscordSettings { - Roles: Record; - Channels: Record; - Emojis: Record; - SupportServer: string; - BotID: string; -} diff --git a/src/lib/types/minions.ts b/src/lib/types/minions.ts index a2d9ce47b8..e6592b9a64 100644 --- a/src/lib/types/minions.ts +++ b/src/lib/types/minions.ts @@ -342,7 +342,6 @@ export interface InfernoOptions extends ActivityTaskOptions { export interface FarmingActivityTaskOptions extends ActivityTaskOptions { type: 'Farming'; - pid?: number; plantsName: string | null; quantity: number; upgradeType: CropUpgradeType | null; diff --git a/src/lib/util.ts b/src/lib/util.ts index 189a731222..177e51f119 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -6,7 +6,6 @@ import { type CacheType, type Collection, type CollectorFilter, - type Guild, type InteractionReplyOptions, type Message, type MessageEditOptions, @@ -20,16 +19,15 @@ import { Time, calcWhatPercent, notEmpty, objectEntries, randArrItem, randInt, s import { Bank, Items, Monsters } from 'oldschooljs'; import { bool, integer, nativeMath, nodeCrypto, real } from 'random-js'; -import type { Prisma, PrismaClient } from '@prisma/client'; +import type { Prisma } from '@prisma/client'; import { LRUCache } from 'lru-cache'; import type { Item } from 'oldschooljs/dist/meta/types'; import type Monster from 'oldschooljs/dist/structures/Monster'; import { convertLVLtoXP } from 'oldschooljs/dist/util/util'; -import { ADMIN_IDS, OWNER_IDS, SupportServer } from '../config'; import type { MUserClass } from './MUser'; import { PaginatedMessage } from './PaginatedMessage'; import { ClueTiers } from './clues/clueTiers'; -import { BitField, ONE_TRILLION, type ProjectileType, globalConfig, projectiles } from './constants'; +import { BitField, ONE_TRILLION, type ProjectileType, projectiles } from './constants'; import { doaCL } from './data/CollectionsExport'; import { getSimilarItems } from './data/similarItems'; import type { DefenceGearStat, GearSetupType, OffenceGearStat } from './gear/types'; @@ -50,7 +48,6 @@ import getOSItem, { getItem } from './util/getOSItem'; import itemID from './util/itemID'; import { makeBadgeString } from './util/makeBadgeString'; import resolveItems from './util/resolveItems'; -import { itemNameFromID } from './util/smallUtils'; export * from '@oldschoolgg/toolkit'; export * from 'oldschooljs/dist/util/index'; @@ -64,11 +61,6 @@ export function inlineCodeblock(input: string) { return `\`${input.replace(/ /g, '\u00A0').replace(/`/g, '`\u200B')}\``; } -export function britishTime() { - const currentDate = new Date(Date.now() - Time.Hour * 10); - return currentDate; -} - export function isWeekend() { const currentDate = new Date(Date.now() - Time.Hour * 6); return [6, 0].includes(currentDate.getDay()); @@ -133,13 +125,6 @@ export function isNexActivity(data: any): data is NexTaskOptions { return 'wipedKill' in data && 'userDetails' in data && 'leader' in data; } -export function getSupportGuild(): Guild | null { - if (!globalClient || Object.keys(globalClient).length === 0) return null; - const guild = globalClient.guilds.cache.get(SupportServer); - if (!guild) return null; - return guild; -} - export function calcCombatLevel(skills: Skills) { const defence = skills.defence ? convertXPtoLVL(skills.defence) : 1; const ranged = skills.ranged ? convertXPtoLVL(skills.ranged) : 1; @@ -403,23 +388,6 @@ export function moidLink(items: number[]) { return `https://chisel.weirdgloop.org/moid/item_id.html#${items.join(',')}`; } -export async function bankValueWithMarketPrices(prisma: PrismaClient, bank: Bank) { - const marketPrices = (await prisma.clientStorage.findFirst({ - where: { id: globalConfig.clientID }, - select: { - market_prices: true - } - }))!.market_prices as ItemBank; - let price = 0; - for (const [item, qty] of bank.items()) { - if (!item) { - continue; - } - price += (marketPrices[item.id] ?? item.price * 0.8) * qty; - } - return price; -} - export function isValidSkill(skill: string): skill is SkillsEnum { return Object.values(SkillsEnum).includes(skill as SkillsEnum); } @@ -498,19 +466,10 @@ export async function getUsername(_id: string | bigint): Promise { const id = _id.toString(); const cached = usernameWithBadgesCache.get(id); if (cached) return cached; - const user = await prisma.user.findFirst({ - where: { - id - }, - select: { - username: true, - badges: true, - minion_ironman: true - } - }); - if (!user?.username) return 'Unknown'; - const badges = makeBadgeString(user.badges, user.minion_ironman); - const newValue = `${badges ? `${badges} ` : ''}${user.username}`; + const user = await mUserFetch(id); + if (!user?.user.username) return 'Unknown'; + const badges = makeBadgeString(user, user.user.badges); + const newValue = `${badges ? `${badges} ` : ''}${user.user.username}`; usernameWithBadgesCache.set(id, newValue); return newValue; } @@ -559,15 +518,11 @@ export function awaitMessageComponentInteraction({ }); } -export async function runTimedLoggedFn(name: string, fn: () => Promise, threshholdToLog = 100): Promise { - const logger = globalConfig.isProduction ? debugLog : console.log; +export async function runTimedLoggedFn(_name: string, fn: () => Promise, _threshholdToLog = 100): Promise { const stopwatch = new Stopwatch(); stopwatch.start(); const result = await fn(); stopwatch.stop(); - if (!globalConfig.isProduction || stopwatch.duration > threshholdToLog) { - logger(`Took ${stopwatch} to do ${name}`); - } return result; } @@ -578,10 +533,6 @@ export function logWrapFn Promise>( return (...args: Parameters): ReturnType => runTimedLoggedFn(name, () => fn(...args)) as ReturnType; } -export function isModOrAdmin(user: MUser) { - return [...OWNER_IDS, ...ADMIN_IDS].includes(user.id) || user.bitfield.includes(BitField.isModerator); -} - export async function calcClueScores(user: MUser) { const { actualCluesBank } = await user.calcActualClues(); const stats = await user.fetchStats({ openable_scores: true }); @@ -617,9 +568,8 @@ export function checkRangeGearWeapon(gear: Gear) { ); if (!projectileCategory) return 'You have an invalid range weapon.'; if (!projectileCategory[1].items.includes(ammo.item)) { - return `You have invalid ammo for your equipped weapon. For ${ - projectileCategory[0] - }-based weapons, you can use: ${projectileCategory[1].items.map(itemNameFromID).join(', ')}.`; + return `You have invalid ammo for your equipped weapon. For $ + projectileCategory[0]-based weapons, you can use: $projectileCategory[1].items.map(itemNameFromID).join(', ').`; } return { diff --git a/src/lib/util/cachedUserIDs.ts b/src/lib/util/cachedUserIDs.ts index fa757b1e33..a04074aa9a 100644 --- a/src/lib/util/cachedUserIDs.ts +++ b/src/lib/util/cachedUserIDs.ts @@ -1,166 +1,4 @@ -import { Prisma } from '@prisma/client'; -import { ChannelType } from 'discord.js'; -import { objectEntries } from 'e'; - -import { OWNER_IDS, SupportServer } from '../../config'; import { globalConfig } from '../constants'; -import { logWrapFn, runTimedLoggedFn } from '../util'; export const CACHED_ACTIVE_USER_IDS = new Set(); CACHED_ACTIVE_USER_IDS.add(globalConfig.clientID); -for (const id of OWNER_IDS) CACHED_ACTIVE_USER_IDS.add(id); - -export const syncActiveUserIDs = logWrapFn('Sync Active User IDs', async () => { - const users = await prisma.$queryRawUnsafe< - { user_id: string }[] - >(`SELECT DISTINCT(${Prisma.ActivityScalarFieldEnum.user_id}::text) -FROM activity -WHERE finish_date > now() - INTERVAL '48 hours'`); - - const perkTierUsers = await roboChimpClient.$queryRawUnsafe<{ id: string }[]>(`SELECT id::text -FROM "user" -WHERE perk_tier > 0;`); - - for (const id of [...users.map(i => i.user_id), ...perkTierUsers.map(i => i.id)]) { - CACHED_ACTIVE_USER_IDS.add(id); - } - debugLog(`${CACHED_ACTIVE_USER_IDS.size} cached active user IDs`); -}); - -export function memoryAnalysis() { - const guilds = globalClient.guilds.cache.size; - let emojis = 0; - const channels = globalClient.channels.cache.size; - const voiceChannels = 0; - const guildTextChannels = 0; - let roles = 0; - let members = 0; - const channelCounter: Record = {} as any; - let messages = 0; - let voiceStates = 0; - let commands = 0; - let permissionOverwrites = 0; - - for (const guild of globalClient.guilds.cache.values()) { - for (const channel of guild.channels.cache.values()) { - channelCounter[channel.type] ? channelCounter[channel.type]++ : (channelCounter[channel.type] = 1); - if ('messages' in channel) { - messages += channel.messages.cache.size; - } - if ('permissionOverwrites' in channel) { - permissionOverwrites += channel.permissionOverwrites.cache.size; - } - } - roles += guild.roles.cache.size; - members += guild.members.cache.size; - emojis += guild.emojis.cache.size; - voiceStates += guild.voiceStates.cache.size; - commands += guild.commands.cache.size; - } - - const channelTypeEntries = Object.entries(ChannelType); - - for (const [key, value] of objectEntries(channelCounter)) { - const name = channelTypeEntries.find(i => i[1] === Number(key))?.[0] ?? 'Unknown'; - delete channelCounter[key]; - channelCounter[`${name} Channels`] = value; - } - - return { - guilds, - emojis, - channels, - voiceChannels, - guildTextChannels, - roles, - activeIDs: CACHED_ACTIVE_USER_IDS.size, - members, - ...channelCounter, - messages, - voiceStates, - commands, - permissionOverwrites - }; -} - -export const emojiServers = new Set([ - '342983479501389826', - '940758552425955348', - '869497440947015730', - '324127314361319427', - '363252822369894400', - '395236850119213067', - '325950337271857152', - '395236894096621568' -]); - -export function cacheCleanup() { - if (!globalClient.isReady()) return; - return runTimedLoggedFn('Cache Cleanup', async () => { - await runTimedLoggedFn('Clear Channels', async () => { - for (const channel of globalClient.channels.cache.values()) { - if (channel.type === ChannelType.GuildVoice || channel.type === ChannelType.GuildCategory) { - globalClient.channels.cache.delete(channel.id); - } - // @ts-ignore ignore - channel.topic = undefined; - // @ts-ignore ignore - channel.rateLimitPerUser = undefined; - // @ts-ignore ignore - channel.nsfw = undefined; - // @ts-ignore ignore - channel.parentId = undefined; - // @ts-ignore ignore - channel.name = undefined; - // @ts-ignore ignore - channel.lastMessageId = null; - // @ts-ignore ignore - channel.lastPinTimestamp = null; - if ('permissionOverwrites' in channel) { - channel.permissionOverwrites.cache.clear(); - } - if ('threads' in channel) { - channel.threads.cache.clear(); - } - } - }); - - await runTimedLoggedFn('Guild Emoji/Roles/Member cache clear', async () => { - for (const guild of globalClient.guilds.cache.values()) { - if (guild.id !== SupportServer) { - for (const member of guild.members.cache.values()) { - if (member.user.id === globalConfig.clientID) continue; - guild.members.cache.delete(member.user.id); - } - } - - if (emojiServers.has(guild.id)) continue; - guild.emojis?.cache.clear(); - - for (const channel of guild.channels.cache.values()) { - if (channel.type === ChannelType.GuildVoice || channel.type === ChannelType.AnnouncementThread) { - guild.channels.cache.delete(channel.id); - } - } - for (const role of guild.roles.cache.values()) { - // @ts-ignore ignore - role.managed = undefined; - // @ts-ignore ignore - role.name = undefined; - // @ts-ignore ignore - role.tags = undefined; - // @ts-ignore ignore - role.icon = undefined; - // @ts-ignore ignore - role.unicodeEmoji = undefined; - // @ts-ignore ignore - role.rawPosition = undefined; - // @ts-ignore ignore - role.color = undefined; - // @ts-ignore ignore - role.hoist = undefined; - } - } - }); - }); -} diff --git a/src/lib/util/calcMaxTripLength.ts b/src/lib/util/calcMaxTripLength.ts index 9dc67ec3df..00e8909976 100644 --- a/src/lib/util/calcMaxTripLength.ts +++ b/src/lib/util/calcMaxTripLength.ts @@ -1,7 +1,8 @@ import type { activity_type_enum } from '@prisma/client'; import { Time, calcPercentOfNum, calcWhatPercent } from 'e'; -import { BitField, PerkTier } from '../constants'; +import { PerkTier } from '../constants'; +import { RelicID } from '../relics'; import { SkillsEnum } from '../skilling/types'; import { skillLevel } from './minionUtils'; @@ -100,9 +101,8 @@ export function calcMaxTripLength(user: MUser, activity?: activity_type_enum) { const perkTier = user.perkTier(); max += calcPercentOfNum(sacPercent, perkTier >= PerkTier.Four ? Time.Minute * 3 : Time.Minute); - if (user.bitfield.includes(BitField.HasLeaguesOneMinuteLengthBoost)) { - max += Time.Minute; + if (user.hasRelic(RelicID.Repetition)) { + max *= 1.3; } - return max; } diff --git a/src/lib/util/clLeaderboard.ts b/src/lib/util/clLeaderboard.ts index 70f4c74016..124d4e366c 100644 --- a/src/lib/util/clLeaderboard.ts +++ b/src/lib/util/clLeaderboard.ts @@ -3,13 +3,11 @@ import type { UserEvent } from '@prisma/client'; import { userEventsToMap } from './userEvents'; export async function fetchCLLeaderboard({ - ironmenOnly, items, resultLimit, method = 'cl_array', userEvents }: { - ironmenOnly: boolean; items: number[]; resultLimit: number; method?: 'cl_array' | 'raw_cl'; @@ -26,7 +24,7 @@ export async function fetchCLLeaderboard({ .map(i => `${i}`) .join(', ')}]) AS qty FROM user_stats s INNER JOIN users u ON s.user_id::text = u.id - WHERE ${ironmenOnly ? 'u."minion.ironman" = true AND' : ''} user_id::text IN (${userIdsList}) + WHERE user_id::text IN (${userIdsList}) `) : []; const generalUsers = await prisma.$queryRawUnsafe<{ id: string; qty: number }[]>(` @@ -34,9 +32,9 @@ export async function fetchCLLeaderboard({ .map(i => `${i}`) .join(', ')}]) AS qty FROM user_stats - ${ironmenOnly ? 'INNER JOIN "users" on "users"."id" = "user_stats"."user_id"::text' : ''} + WHERE (cl_array && array[${items.map(i => `${i}`).join(', ')}] - ${ironmenOnly ? 'AND "users"."minion.ironman" = true' : ''}) + ) ${userIds.length > 0 ? `AND user_id::text NOT IN (${userIdsList})` : ''} ORDER BY qty DESC LIMIT ${resultLimit} @@ -75,12 +73,8 @@ SELECT id, (cardinality(u.cl_keys) - u.inverse_length) as qty .join(', ')}]))) "inverse_length" FROM users WHERE ("collectionLogBank" ?| array[${items.map(i => `'${i}'`).join(', ')}] - ${ironmenOnly ? 'AND "minion.ironman" = true' : ''}) - ${ - userIds.length > 0 - ? `OR (id in (${userIds.map(i => `'${i}'`).join(', ')})${ironmenOnly ? 'AND "minion.ironman" = true' : ''})` - : '' - } + ) + ${userIds.length > 0 ? `OR (id in (${userIds.map(i => `'${i}'`).join(', ')}))` : ''} ) u ORDER BY qty DESC LIMIT ${resultLimit}; diff --git a/src/lib/util/clientSettings.ts b/src/lib/util/clientSettings.ts index ad8faa445b..4288a5ece0 100644 --- a/src/lib/util/clientSettings.ts +++ b/src/lib/util/clientSettings.ts @@ -2,13 +2,15 @@ import type { ClientStorage, Prisma } from '@prisma/client'; import { globalConfig } from '../constants'; -// Is not typesafe, returns only what is selected, but will say it contains everything. -export async function mahojiClientSettingsFetch(select: Prisma.ClientStorageSelect = { id: true }) { - const clientSettings = await prisma.clientStorage.findFirst({ +export async function mahojiClientSettingsFetch() { + const clientSettings = await prisma.clientStorage.upsert({ + create: { + id: globalConfig.clientID + }, where: { id: globalConfig.clientID }, - select + update: {} }); return clientSettings as ClientStorage; } diff --git a/src/lib/util/commandUsage.ts b/src/lib/util/commandUsage.ts index 153064fcdb..57036cf431 100644 --- a/src/lib/util/commandUsage.ts +++ b/src/lib/util/commandUsage.ts @@ -1,5 +1,5 @@ import type { CommandOptions } from '@oldschoolgg/toolkit'; -import type { Prisma, command_name_enum } from '@prisma/client'; +import type { Prisma } from '@prisma/client'; import { getCommandArgs } from '../../mahoji/lib/util'; @@ -24,7 +24,7 @@ export function makeCommandUsage({ }): Prisma.CommandUsageCreateInput { return { user_id: BigInt(userID), - command_name: commandName as command_name_enum, + command_name: commandName, args: getCommandArgs(commandName, args), channel_id: BigInt(channelID), guild_id: guildID ? BigInt(guildID) : null, diff --git a/src/lib/util/findGroupOfUser.ts b/src/lib/util/findGroupOfUser.ts deleted file mode 100644 index e9528d5b0c..0000000000 --- a/src/lib/util/findGroupOfUser.ts +++ /dev/null @@ -1,15 +0,0 @@ -export async function findGroupOfUser(userID: string) { - const user = await roboChimpClient.user.findUnique({ - where: { - id: BigInt(userID) - } - }); - if (!user || !user.user_group_id) return [userID]; - const group = await roboChimpClient.user.findMany({ - where: { - user_group_id: user.user_group_id - } - }); - if (!group) return [userID]; - return group.map(u => u.id.toString()); -} diff --git a/src/lib/util/giveaway.ts b/src/lib/util/giveaway.ts deleted file mode 100644 index 21af395ab0..0000000000 --- a/src/lib/util/giveaway.ts +++ /dev/null @@ -1,114 +0,0 @@ -import type { Giveaway } from '@prisma/client'; -import { type MessageEditOptions, time, userMention } from 'discord.js'; -import { Time, debounce, noOp, randArrItem } from 'e'; -import { Bank } from 'oldschooljs'; -import type { ItemBank } from 'oldschooljs/dist/meta/types'; - -import { Events } from '../constants'; - -import { channelIsSendable } from '../util'; -import { logError } from './logError'; -import { sendToChannelID } from './webhook'; - -async function refundGiveaway(creator: MUser, loot: Bank) { - await transactItems({ - userID: creator.id, - itemsToAdd: loot - }); - const user = await globalClient.fetchUser(creator.id); - debugLog('Refunding a giveaway.', { type: 'GIVEAWAY_REFUND', user_id: creator.id, loot: loot.bank }); - user.send(`Your giveaway failed to finish, you were refunded the items: ${loot}.`).catch(noOp); -} - -async function getGiveawayMessage(giveaway: Giveaway) { - const channel = globalClient.channels.cache.get(giveaway.channel_id); - if (channelIsSendable(channel)) { - const message = await channel.messages.fetch(giveaway.message_id).catch(noOp); - if (message) return message; - } -} - -export function generateGiveawayContent(host: string, finishDate: Date, usersEntered: string[]) { - return `${userMention(host)} created a giveaway that will finish at ${time(finishDate, 'F')} (${time( - finishDate, - 'R' - )}). - -There are ${usersEntered.length} users entered in this giveaway.`; -} - -export const updateGiveawayMessage = debounce(async (_giveaway: Giveaway) => { - const giveaway = await prisma.giveaway.findFirst({ where: { id: _giveaway.id } }); - if (!giveaway) return; - const message = await getGiveawayMessage(giveaway); - if (!message) return; - const newContent = generateGiveawayContent(giveaway.user_id, giveaway.finish_date, giveaway.users_entered); - const edits: MessageEditOptions = {}; - if (giveaway.completed) edits.components = []; - if (message.content !== newContent) { - edits.content = newContent; - } - if (Object.keys(edits).length > 0) { - await message.edit(edits); - } -}, Time.Second); - -export async function handleGiveawayCompletion(_giveaway: Giveaway) { - debugLog('Completing a giveaway.', { type: 'GIVEAWAY_COMPLETE', giveaway_id: _giveaway.id }); - if (_giveaway.completed) { - throw new Error('Tried to complete an already completed giveaway.'); - } - - const loot = new Bank(_giveaway.loot as ItemBank); - - try { - const giveaway = await prisma.giveaway.update({ - where: { - id: _giveaway.id - }, - data: { - completed: true - } - }); - - const creator = await mUserFetch(giveaway.user_id); - - const users = (await Promise.all(giveaway.users_entered.map(i => mUserFetch(i)))).filter( - u => !u.isIronman && u.id !== giveaway.user_id - ); - await updateGiveawayMessage(giveaway); - - if (users.length === 0) { - await refundGiveaway(creator, loot); - return; - } - - const winner = randArrItem(users); - await transactItems({ userID: winner.id, itemsToAdd: loot }); - await prisma.economyTransaction.create({ - data: { - guild_id: undefined, - sender: BigInt(creator.id), - recipient: BigInt(winner.id), - items_sent: loot.bank, - items_received: undefined, - type: 'giveaway' - } - }); - - globalClient.emit( - Events.EconomyLog, - `${winner.mention}[${winner.id}] won ${loot} in a giveaway of ${users.length} made by ${creator.mention}[${creator.id}].` - ); - - const str = `<@${giveaway.user_id}> **Giveaway finished:** ${users.length} users joined, the winner is... **${winner.mention}** - -They received these items: ${loot}.`; - - await sendToChannelID(giveaway.channel_id, { - content: str - }); - } catch (err) { - logError(err); - } -} diff --git a/src/lib/util/globalInteractions.ts b/src/lib/util/globalInteractions.ts index bdcc548043..3ce24cd6da 100644 --- a/src/lib/util/globalInteractions.ts +++ b/src/lib/util/globalInteractions.ts @@ -1,33 +1,21 @@ -import { mentionCommand } from '@oldschoolgg/toolkit'; import type { ButtonInteraction, Interaction } from 'discord.js'; import { ButtonBuilder, ButtonStyle } from 'discord.js'; -import { Time, removeFromArr, uniqueArr } from 'e'; -import { Bank } from 'oldschooljs'; +import { Time } from 'e'; -import { getItemContractDetails, handInContract } from '../../mahoji/commands/ic'; -import { cancelGEListingCommand } from '../../mahoji/lib/abstracted_commands/cancelGEListingCommand'; import { autoContract } from '../../mahoji/lib/abstracted_commands/farmingContractCommand'; import { shootingStarsCommand, starCache } from '../../mahoji/lib/abstracted_commands/shootingStarsCommand'; -import { userStatsBankUpdate } from '../../mahoji/mahojiSettings'; import { repeatTameTrip } from '../../tasks/tames/tameTasks'; -import { modifyBusyCounter } from '../busyCounterCache'; import type { ClueTier } from '../clues/clueTiers'; -import { BitField, PerkTier } from '../constants'; +import { PerkTier } from '../constants'; import { RateLimitManager } from '@sapphire/ratelimits'; import { InteractionID } from '../InteractionID'; import { runCommand } from '../settings/settings'; import { toaHelpCommand } from '../simulation/toa'; -import type { ItemBank } from '../types'; -import { formatDuration, stringMatches } from '../util'; -import { CACHED_ACTIVE_USER_IDS } from './cachedUserIDs'; -import { updateGiveawayMessage } from './giveaway'; -import { handleMahojiConfirmation } from './handleMahojiConfirmation'; +import { formatDuration } from '../util'; import { interactionReply } from './interactionReply'; -import { logErrorForInteraction } from './logError'; import { minionIsBusy } from './minionIsBusy'; import { fetchRepeatTrips, repeatTrip } from './repeatStoredTrip'; -import { tradePlayerItems } from './tradePlayerItems'; const globalInteractionActions = [ 'DO_BEGINNER_CLUE', @@ -55,12 +43,10 @@ const globalInteractionActions = [ 'AUTO_FARMING_CONTRACT', 'FARMING_CONTRACT_EASIER', 'OPEN_SEED_PACK', - 'BUY_MINION', 'BUY_BINGO_TICKET', 'NEW_SLAYER_TASK', 'SPAWN_LAMP', 'REPEAT_TAME_TRIP', - 'ITEM_CONTRACT_SEND', 'DO_FISHING_CONTEST', 'DO_SHOOTING_STAR', 'CHECK_TOA' @@ -144,104 +130,8 @@ export function makeNewSlayerTaskButton() { .setEmoji('630911040560824330'); } -async function giveawayButtonHandler(user: MUser, customID: string, interaction: ButtonInteraction) { - const split = customID.split('_'); - if (split[0] !== 'GIVEAWAY') return; - const giveawayID = Number(split[2]); - const giveaway = await prisma.giveaway.findFirst({ - where: { - id: giveawayID - } - }); - if (!giveaway) { - return interactionReply(interaction, { content: 'Invalid giveaway.', ephemeral: true }); - } - if (split[1] === 'REPEAT') { - if (user.id !== giveaway.user_id) { - return interactionReply(interaction, { - content: "You cannot repeat other peoples' giveaways.", - ephemeral: true - }); - } - - return runCommand({ - commandName: 'giveaway', - args: { - start: { - duration: `${giveaway.duration}ms`, - items: new Bank(giveaway.loot as ItemBank) - .items() - .map(t => `${t[1]} ${t[0].name}`) - .join(', ') - } - }, - user, - member: interaction.member, - channelID: interaction.channelId, - guildID: interaction.guildId, - interaction, - continueDeltaMillis: null - }); - } - - if (giveaway.finish_date.getTime() < Date.now() || giveaway.completed) { - return interactionReply(interaction, { content: 'This giveaway has finished.', ephemeral: true }); - } - - const action = split[1] === 'ENTER' ? 'ENTER' : 'LEAVE'; - - if (user.isIronman) { - return interactionReply(interaction, { - content: 'You are an ironman, you cannot enter giveaways.', - ephemeral: true - }); - } - - if (user.id === giveaway.user_id) { - return interactionReply(interaction, { content: 'You cannot join your own giveaway.', ephemeral: true }); - } - - if (action === 'ENTER') { - if (giveaway.users_entered.includes(user.id)) { - return interactionReply(interaction, { - content: 'You are already entered in this giveaway.', - ephemeral: true - }); - } - await prisma.giveaway.update({ - where: { - id: giveaway.id - }, - data: { - users_entered: { - push: user.id - } - } - }); - updateGiveawayMessage(giveaway); - return interactionReply(interaction, { content: 'You are now entered in this giveaway.', ephemeral: true }); - } - if (!giveaway.users_entered.includes(user.id)) { - return interactionReply(interaction, { - content: "You aren't entered in this giveaway, so you can't leave it.", - ephemeral: true - }); - } - await prisma.giveaway.update({ - where: { - id: giveaway.id - }, - data: { - users_entered: uniqueArr(removeFromArr(giveaway.users_entered, user.id)) - } - }); - updateGiveawayMessage(giveaway); - return interactionReply(interaction, { content: 'You left the giveaway.', ephemeral: true }); -} - async function repeatTripHandler(interaction: ButtonInteraction) { - if (minionIsBusy(interaction.user.id)) - return interactionReply(interaction, { content: 'Your minion is busy.', ephemeral: true }); + if (minionIsBusy(interaction.user.id)) return interactionReply(interaction, { content: 'Your minion is busy.' }); const trips = await fetchRepeatTrips(interaction.user.id); if (trips.length === 0) { return interactionReply(interaction, { content: "Couldn't find a trip to repeat.", ephemeral: true }); @@ -255,160 +145,6 @@ async function repeatTripHandler(interaction: ButtonInteraction) { return repeatTrip(interaction, matchingActivity); } -function icDonateValidation(user: MUser, donator: MUser) { - if (user.isIronman || donator.isIronman) { - return 'Ironmen stand alone!'; - } - if (user.id === donator.id) { - return 'You cannot donate to yourself.'; - } - if (user.bitfield.includes(BitField.NoItemContractDonations)) { - return "That user doesn't want donations."; - } - const details = getItemContractDetails(user); - if (!details.nextContractIsReady || !details.currentItem) { - return "That user's Item Contract isn't ready."; - } - - if (user.isBusy || donator.isBusy) { - return 'One of you is busy, and cannot do this trade right now.'; - } - - const cost = new Bank().add(details.currentItem.id); - if (!donator.bank.has(cost)) { - return `You don't own ${cost}.`; - } - - return { - cost, - details - }; -} - -async function donateICHandler(interaction: ButtonInteraction) { - const userID = interaction.customId.split('_')[2]; - if (!userID || !CACHED_ACTIVE_USER_IDS.has(userID)) { - return interactionReply(interaction, { content: 'Invalid user.', ephemeral: true }); - } - - const user = await mUserFetch(userID); - const donator = await mUserFetch(interaction.user.id); - - const errorStr = icDonateValidation(user, donator); - if (typeof errorStr === 'string') return interactionReply(interaction, { content: errorStr, ephemeral: true }); - - await handleMahojiConfirmation( - interaction, - `${donator}, are you sure you want to give ${errorStr.cost} to ${ - user.badgedUsername - }? You own ${donator.bank.amount(errorStr.details.currentItem!.id)} of this item.`, - [donator.id] - ); - - await user.sync(); - await donator.sync(); - - const secondaryErrorStr = icDonateValidation(user, donator); - if (typeof secondaryErrorStr === 'string') return interactionReply(interaction, { content: secondaryErrorStr }); - const { cost } = secondaryErrorStr; - - try { - modifyBusyCounter(donator.id, 1); - await tradePlayerItems(donator, user, cost); - await userStatsBankUpdate(donator.id, 'ic_donations_given_bank', cost); - await userStatsBankUpdate(user.id, 'ic_donations_received_bank', cost); - - return interactionReply(interaction, { - content: `${donator}, you donated ${cost} to ${user}! - -${user.mention} ${await handInContract(null, user)}`, - allowedMentions: { - users: [user.id] - } - }); - } catch (err) { - logErrorForInteraction(err, interaction); - } finally { - modifyBusyCounter(donator.id, -1); - } -} - -async function handleGearPresetEquip(user: MUser, id: string, interaction: ButtonInteraction) { - const [, setupName, presetName] = id.split('_'); - if (!setupName || !presetName) return; - const presets = await prisma.gearPreset.findMany({ where: { user_id: user.id } }); - const matchingPreset = presets.find(p => stringMatches(p.name, presetName)); - if (!matchingPreset) { - return interactionReply(interaction, { content: "You don't have a preset with this name.", ephemeral: true }); - } - await runCommand({ - commandName: 'gearpresets', - args: { equip: { gear_setup: setupName, preset: presetName } }, - user, - member: interaction.member, - channelID: interaction.channelId, - guildID: interaction.guildId, - interaction, - continueDeltaMillis: null - }); -} - -async function handlePinnedTripRepeat(user: MUser, id: string, interaction: ButtonInteraction) { - const [, pinnedTripID] = id.split('_'); - if (!pinnedTripID) return; - const trip = await prisma.pinnedTrip.findFirst({ where: { user_id: user.id, id: pinnedTripID } }); - if (!trip) { - return interactionReply(interaction, { - content: "You don't have a pinned trip with this ID, and you cannot repeat trips of other users.", - ephemeral: true - }); - } - await repeatTrip(interaction, { data: trip.data, type: trip.activity_type }); -} - -async function handleGEButton(user: MUser, id: string, interaction: ButtonInteraction) { - if (id === 'ge_cancel_dms') { - const mention = mentionCommand(globalClient, 'config', 'user', 'toggle'); - if (user.bitfield.includes(BitField.DisableGrandExchangeDMs)) { - return interactionReply(interaction, { - content: `You already disabled Grand Exchange DM's, you can re-enable them using ${mention}.`, - ephemeral: true - }); - } - await user.update({ - bitfield: { - push: BitField.DisableGrandExchangeDMs - } - }); - return interactionReply(interaction, { - content: `You have disabled Grand Exchange DM's, and won't receive anymore DM's, you can re-enable them using ${mention}.`, - ephemeral: true - }); - } - if (id.startsWith('ge_cancel_')) { - const cancelUserFacingID = id.split('_')[2]; - const listing = await prisma.gEListing.findFirst({ - where: { - userfacing_id: cancelUserFacingID, - user_id: user.id, - cancelled_at: null, - fulfilled_at: null, - quantity_remaining: { - gt: 0 - } - } - }); - if (!listing) { - return interactionReply(interaction, { - content: 'You cannot cancel this listing, it is either already cancelled, fulfilled or not yours.', - ephemeral: true - }); - } - const response = await cancelGEListingCommand(user, listing.userfacing_id); - return interactionReply(interaction, { content: response, ephemeral: true }); - } -} - export async function interactionHook(interaction: Interaction) { if (!interaction.isButton()) return; const ignoredInteractionIDs = [ @@ -442,11 +178,6 @@ export async function interactionHook(interaction: Interaction) { if (id.includes('REPEAT_TRIP')) return repeatTripHandler(interaction); const user = await mUserFetch(userID); - - if (id.includes('GIVEAWAY_')) return giveawayButtonHandler(user, id, interaction); - if (id.includes('DONATE_IC')) return donateICHandler(interaction); - if (id.startsWith('GPE_')) return handleGearPresetEquip(user, id, interaction); - if (id.startsWith('PTR_')) return handlePinnedTripRepeat(user, id, interaction); if (id === 'TOA_CHECK') { const response = await toaHelpCommand(user, interaction.channelId); return interactionReply(interaction, { @@ -454,7 +185,6 @@ export async function interactionHook(interaction: Interaction) { ephemeral: true }); } - if (id.startsWith('ge_')) return handleGEButton(user, id, interaction); if (!isValidGlobalInteraction(id)) return; if (user.isBusy) { @@ -535,34 +265,9 @@ export async function interactionHook(interaction: Interaction) { }); } - if (id === 'SPAWN_LAMP') { - return runCommand({ - commandName: 'tools', - args: { patron: { spawnlamp: {} } }, - bypassInhibitors: true, - ...options - }); - } if (id === 'REPEAT_TAME_TRIP') { return repeatTameTrip({ ...options }); } - if (id === 'ITEM_CONTRACT_SEND') { - return runCommand({ - commandName: 'ic', - args: { send: {} }, - bypassInhibitors: true, - ...options - }); - } - - if (id === 'BUY_MINION') { - return runCommand({ - commandName: 'minion', - args: { buy: {} }, - bypassInhibitors: true, - ...options - }); - } if (id === 'DO_FISHING_CONTEST') { if (user.perkTier() < PerkTier.Four) { diff --git a/src/lib/util/handleCrateSpawns.ts b/src/lib/util/handleCrateSpawns.ts index b0a9bb1815..551005110f 100644 --- a/src/lib/util/handleCrateSpawns.ts +++ b/src/lib/util/handleCrateSpawns.ts @@ -1,29 +1,23 @@ -import { Time, reduceNumByPercent, roll } from 'e'; +import { Time, roll } from 'e'; import { Bank } from 'oldschooljs'; - -import getOSItem from './getOSItem'; - -const crateItem = getOSItem('Birthday crate (s6)'); +import { keyCrates } from '../keyCrates'; +import { RelicID } from '../relics'; export function handleCrateSpawns(user: MUser, duration: number) { - const accountAge = user.accountAgeInDays(); - let dropratePerMinute = 50 * 60; - if (accountAge) { - if (accountAge < 31) return null; - if (user.isIronman) { - dropratePerMinute = reduceNumByPercent(dropratePerMinute, 15); - } - } - dropratePerMinute = Math.ceil(dropratePerMinute / 3); - dropratePerMinute = Math.ceil(dropratePerMinute / 2); - if (user.isIronman) { - dropratePerMinute = Math.ceil(dropratePerMinute / 3); - } + const dropratePerMinute = 30; const minutes = Math.floor(duration / Time.Minute); const loot = new Bank(); + + const umbDroprate = user.hasRelic(RelicID.Randomness) ? 20 : 40; + for (let i = 0; i < minutes; i++) { - if (roll(dropratePerMinute)) { - loot.add(crateItem); + for (const crate of keyCrates) { + if (roll(dropratePerMinute)) { + loot.add(crate.item.id); + } + } + if (roll(umbDroprate)) { + loot.add('Untradeable Mystery Box'); } } diff --git a/src/lib/util/handleTripFinish.ts b/src/lib/util/handleTripFinish.ts index 26879f0cea..7427024043 100644 --- a/src/lib/util/handleTripFinish.ts +++ b/src/lib/util/handleTripFinish.ts @@ -1,39 +1,34 @@ -import { Stopwatch, channelIsSendable, mentionCommand } from '@oldschoolgg/toolkit'; +import { channelIsSendable } from '@oldschoolgg/toolkit'; import { activity_type_enum } from '@prisma/client'; -import { - type AttachmentBuilder, - type ButtonBuilder, - type MessageCollector, - type MessageCreateOptions, - bold -} from 'discord.js'; -import { Time, notEmpty, randArrItem, randInt, roll } from 'e'; +import type { AttachmentBuilder, ButtonBuilder, MessageCollector, MessageCreateOptions } from 'discord.js'; +import { Time, randInt, roll } from 'e'; import { Bank } from 'oldschooljs'; import { alching } from '../../mahoji/commands/laps'; import { calculateBirdhouseDetails } from '../../mahoji/lib/abstracted_commands/birdhousesCommand'; import { canRunAutoContract } from '../../mahoji/lib/abstracted_commands/farmingContractCommand'; import { handleTriggerShootingStar } from '../../mahoji/lib/abstracted_commands/shootingStarsCommand'; -import { updateClientGPTrackSetting, userStatsBankUpdate, userStatsUpdate } from '../../mahoji/mahojiSettings'; +import { userStatsBankUpdate, userStatsUpdate } from '../../mahoji/mahojiSettings'; import { PortentID, chargePortentIfHasCharges, getAllPortentCharges } from '../bso/divination'; import { gods } from '../bso/divineDominion'; import { MysteryBoxes } from '../bsoOpenables'; import { ClueTiers } from '../clues/clueTiers'; import { buildClueButtons } from '../clues/clueUtils'; import { combatAchievementTripEffect } from '../combat_achievements/combatAchievements'; -import { BitField, COINS_ID, Emoji, PerkTier } from '../constants'; +import { BitField, Emoji, PerkTier } from '../constants'; import { handleGrowablePetGrowth } from '../growablePets'; import { handlePassiveImplings } from '../implings'; import { InventionID, inventionBoosts, inventionItemBoost } from '../invention/inventions'; -import { mysteriousStepData } from '../mysteryTrail'; import { triggerRandomEvent } from '../randomEvents'; +import { relics } from '../randomizer'; +import { RelicID } from '../relics'; +import { runMahojiCommand } from '../settings/settings'; import { RuneTable, WilvusTable, WoodTable } from '../simulation/seedTable'; import { DougTable, PekyTable } from '../simulation/sharedTables'; import { calculateZygomiteLoot } from '../skilling/skills/farming/zygomites'; import { SkillsEnum } from '../skilling/types'; import { getUsersCurrentSlayerInfo } from '../slayer/slayerUtil'; import type { ActivityTaskData } from '../types/minions'; -import { makeComponents, toKMB } from '../util'; -import { mahojiChatHead } from './chatHeadImage'; +import { makeComponents } from '../util'; import { makeAutoContractButton, makeAutoSlayButton, @@ -45,20 +40,12 @@ import { } from './globalInteractions'; import { handleCrateSpawns } from './handleCrateSpawns'; import itemID from './itemID'; -import { logError } from './logError'; +import { fetchRepeatTrips, tripHandlers } from './repeatStoredTrip'; import { perHourChance } from './smallUtils'; -import { updateBankSetting } from './updateBankSetting'; import { sendToChannelID } from './webhook'; const collectors = new Map(); -const activitiesToTrackAsPVMGPSource: activity_type_enum[] = [ - 'GroupMonsterKilling', - 'MonsterKilling', - 'Raids', - 'ClueCompletion' -]; - interface TripFinishEffectOptions { data: ActivityTaskData; user: MUser; @@ -67,43 +54,22 @@ interface TripFinishEffectOptions { portents?: Awaited>; } -type TripEffectReturn = { - itemsToAddWithCL?: Bank; - itemsToRemove?: Bank; -}; - export interface TripFinishEffect { name: string; // biome-ignore lint/suspicious/noConfusingVoidType: - fn: (options: TripFinishEffectOptions) => Promise; + fn: (options: TripFinishEffectOptions) => Promise; } const tripFinishEffects: TripFinishEffect[] = [ - { - name: 'Track GP Analytics', - fn: async ({ data, loot }) => { - if (loot && activitiesToTrackAsPVMGPSource.includes(data.type)) { - const GP = loot.amount(COINS_ID); - if (typeof GP === 'number') { - await updateClientGPTrackSetting('gp_pvm', GP); - } - } - return {}; - } - }, { name: 'Implings', fn: async ({ data, messages, user }) => { const imp = await handlePassiveImplings(user, data, messages); if (imp && imp.bank.length > 0) { - const many = imp.bank.length > 1; - messages.push(`Caught ${many ? 'some' : 'an'} impling${many ? 's' : ''}, you received: ${imp.bank}`); await userStatsBankUpdate(user, 'passive_implings_bank', imp.bank); - return { - itemsToAddWithCL: imp.bank - }; + const res = await user.addItemsToBank({ items: imp.bank, collectionLog: true }); + messages.push(`You received these items from implings spawns: ${res.itemsAdded}`); } - return {}; } }, { @@ -115,31 +81,16 @@ const tripFinishEffects: TripFinishEffect[] = [ { name: 'Random Events', fn: async ({ data, messages, user }) => { - return triggerRandomEvent(user, data.type, data.duration, messages); + await triggerRandomEvent(user, data.type, data.duration, messages); } }, { name: 'Loot Doubling', fn: async ({ data, messages, user, loot }) => { - const cantBeDoubled = ['GroupMonsterKilling', 'KingGoldemar', 'Ignecarus', 'Inferno', 'Alching', 'Agility']; - if ( - loot && - !data.cantBeDoubled && - !cantBeDoubled.includes(data.type) && - data.duration > Time.Minute * 20 && - roll(user.usingPet('Mr. E') ? 12 : 15) - ) { - const otherLoot = new Bank().add(MysteryBoxes.roll()); - const bonusLoot = new Bank().add(loot).add(otherLoot); - messages.push(`<:mysterybox:680783258488799277> **You received 2x loot and ${otherLoot}.**`); - - await Promise.all([ - userStatsBankUpdate(user.id, 'doubled_loot_bank', bonusLoot), - updateBankSetting('trip_doubling_loot', bonusLoot) - ]); - return { - itemsToAddWithCL: bonusLoot - }; + if (loot && !data.cantBeDoubled && roll(user.usingPet('Mr. E') ? 12 : 15) && user.hasRelic(RelicID.Loot)) { + const bonusLoot = new Bank().add(loot); + const res = await user.addItemsToBank({ items: bonusLoot, collectionLog: true }); + messages.push(`Your Relic of Loot granted you double loot: ${res.itemsAdded}`); } } }, @@ -149,6 +100,7 @@ const tripFinishEffects: TripFinishEffect[] = [ const pet = user.user.minion_equippedPet; const minutes = Math.floor(data.duration / Time.Minute); if (minutes < 5) return; + let prefix = ''; const bonusLoot = new Bank(); switch (pet) { case itemID('Peky'): { @@ -158,9 +110,7 @@ const tripFinishEffects: TripFinishEffect[] = [ } } userStatsBankUpdate(user.id, 'peky_loot_bank', bonusLoot); - messages.push( - `<:peky:787028037031559168> Peky flew off and got you some seeds during this trip: ${bonusLoot}.` - ); + prefix = '<:peky:787028037031559168> Peky flew off and got you some seeds during this trip:'; break; } case itemID('Obis'): { @@ -169,9 +119,7 @@ const tripFinishEffects: TripFinishEffect[] = [ bonusLoot.add(RuneTable.roll()); } userStatsBankUpdate(user.id, 'obis_loot_bank', bonusLoot); - messages.push( - `<:obis:787028036792614974> Obis did some runecrafting during this trip and got you: ${bonusLoot}.` - ); + prefix = '<:obis:787028036792614974> Obis did some runecrafting during this trip and got you:'; break; } case itemID('Brock'): { @@ -180,9 +128,7 @@ const tripFinishEffects: TripFinishEffect[] = [ bonusLoot.add(WoodTable.roll()); } userStatsBankUpdate(user.id, 'brock_loot_bank', bonusLoot); - messages.push( - `<:brock:787310793183854594> Brock did some woodcutting during this trip and got you: ${bonusLoot}.` - ); + prefix = '<:brock:787310793183854594> Brock did some woodcutting during this trip and got you:'; break; } case itemID('Wilvus'): { @@ -191,9 +137,7 @@ const tripFinishEffects: TripFinishEffect[] = [ bonusLoot.add(WilvusTable.roll()); } userStatsBankUpdate(user.id, 'wilvus_loot_bank', bonusLoot); - messages.push( - `<:wilvus:787320791011164201> Wilvus did some pickpocketing during this trip and got you: ${bonusLoot}.` - ); + prefix = '<:wilvus:787320791011164201> Wilvus did some pickpocketing during this trip and got you:'; break; } case itemID('Smokey'): { @@ -204,9 +148,8 @@ const tripFinishEffects: TripFinishEffect[] = [ } userStatsBankUpdate(user.id, 'smokey_loot_bank', bonusLoot); if (bonusLoot.length > 0) { - messages.push( - `<:smokey:787333617037869139> Smokey did some walking around while you were on your trip and found you ${bonusLoot}.` - ); + prefix = + '<:smokey:787333617037869139> Smokey did some walking around while you were on your trip and found you:'; } break; } @@ -215,7 +158,7 @@ const tripFinishEffects: TripFinishEffect[] = [ bonusLoot.add(DougTable.roll()); } userStatsBankUpdate(user.id, 'doug_loot_bank', bonusLoot); - messages.push(`Doug did some mining while you were on your trip and got you: ${bonusLoot}.`); + prefix = 'Doug did some mining while you were on your trip and got you:'; break; } case itemID('Harry'): { @@ -223,16 +166,16 @@ const tripFinishEffects: TripFinishEffect[] = [ bonusLoot.add('Banana', randInt(1, 3)); } userStatsBankUpdate(user.id, 'harry_loot_bank', bonusLoot); - messages.push(`<:harry:749945071104819292>: ${bonusLoot}.`); + prefix = '<:harry:749945071104819292> Harry got you:'; break; } default: { } } - - return { - itemsToAddWithCL: bonusLoot - }; + if (bonusLoot.length > 0) { + const res = await user.addItemsToBank({ items: bonusLoot, collectionLog: true }); + messages.push(`${prefix} ${res.itemsAdded}`); + } } }, { @@ -254,23 +197,12 @@ const tripFinishEffects: TripFinishEffect[] = [ ); } - await Promise.all([ - updateBankSetting('magic_cost_bank', alchResult.bankToRemove), - updateClientGPTrackSetting('gp_alch', alchResult.bankToAdd.amount('Coins')) - ]); - messages.push( - `<:Voidling:886284972380545034> ${alchResult.maxCasts}x ${ - alchResult.itemToAlch.name - } <:alch:739456571347566623> ${toKMB(alchResult.bankToAdd.amount('Coins'))} GP ${ - !voidlingEquipped && !user.hasEquipped('Magic master cape') - ? '<:bank:739459924693614653>⏬' - : '' - }${user.hasEquipped('Magic master cape') ? '<:Magicmastercape:1115026341314703492>⏫' : ''}` - ); - return { - itemsToAddWithCL: alchResult.bankToAdd, - itemsToRemove: alchResult.bankToRemove - }; + const res = await user.transactItems({ + itemsToAdd: alchResult.bankToAdd, + itemsToRemove: alchResult.bankToRemove, + collectionLog: true + }); + messages.push(`Voilding alched ${alchResult.bankToRemove} into ${res.itemsAdded}`); } else if (user.favAlchs(Time.Minute * 30).length !== 0) { messages.push( "Your Voidling didn't alch anything because you either don't have any nature runes or fire runes." @@ -297,20 +229,20 @@ const tripFinishEffects: TripFinishEffect[] = [ increment: xpToReceive } }); - await user.addXP({ + const res = await user.addXP({ skillName: SkillsEnum.Agility, amount: xpToReceive, multiplier: false, duration: data.duration }); - messages.push(`+${toKMB(xpToReceive)} Agility XP from Silverhawk boots (${costRes.messages})`); + messages.push(`+${res} from Silverhawk boots (${costRes.messages})`); } } } }, { name: 'Message in a Bottle', - fn: async ({ data, messages }) => { + fn: async ({ data, messages, user }) => { const underwaterTrips: activity_type_enum[] = [ activity_type_enum.UnderwaterAgilityThieving, activity_type_enum.DepthsOfAtlantis @@ -319,21 +251,16 @@ const tripFinishEffects: TripFinishEffect[] = [ if (!roll(500)) return; messages.push('You found a message in a bottle!'); const bottleLoot = new Bank().add('Message in a bottle'); - return { - itemsToAddWithCL: bottleLoot - }; + const res = await user.addItemsToBank({ items: bottleLoot }); + messages.push(`You received these items from a message in a bottle spawn: ${res.itemsAdded}`); } }, { name: 'Crate Spawns', - fn: async ({ data, messages, user }) => { + fn: async ({ data, user, messages }) => { const crateRes = handleCrateSpawns(user, data.duration); - if (crateRes && crateRes.length > 0) { - messages.push(bold(`You found ${crateRes}!`)); - return { - itemsToAddWithCL: crateRes - }; - } + const res = await user.addItemsToBank({ items: crateRes }); + messages.push(`You received these items from crate spawns: ${res.itemsAdded}`); } }, { @@ -354,56 +281,6 @@ const tripFinishEffects: TripFinishEffect[] = [ name: 'Combat Achievements', fn: combatAchievementTripEffect }, - { - name: 'Mysterious trail', - fn: async ({ data, user, messages }) => { - if (user.skillsAsLevels.hunter < 100) return; - if (!user.owns('Mysterious clue (1)')) return; - if (user.user.bso_mystery_trail_current_step_id === null) return; - const { step, stepData, previousStepData, nextStep } = user.getMysteriousTrailData(); - if (!step || !(await step.didPass(data))) { - return; - } - if (stepData.loot) { - if (user.cl.has(stepData.loot)) return; - await user.addItemsToBank({ items: stepData.loot, collectionLog: true }); - } - if (previousStepData?.clueItem && user.owns(previousStepData.clueItem.id)) { - await user.removeItemsFromBank(new Bank().add(previousStepData.clueItem.id)); - } - if (nextStep) { - await user.update({ - bso_mystery_trail_current_step_id: user.user.bso_mystery_trail_current_step_id + 1 - }); - messages.push(`❔You found ${stepData.loot}.`); - } else { - if (user.bitfield.includes(BitField.HasUnlockedYeti)) return; - await user.update({ - bitfield: { - push: BitField.HasUnlockedYeti - } - }); - for (const item of [ - ...Object.values(mysteriousStepData).map(i => i.clueItem?.id), - itemID('Mysterious clue (1)') - ].filter(notEmpty)) { - if (user.owns(item)) { - try { - await user.removeItemsFromBank(new Bank().add(item)); - } catch (err) { - logError(err); - } - } - } - const message = `${ - user.minionName - } arrives at the snowy area north of rellekka, finding a giant, monstrous Yeti. At his feet, lay a slain animal. The Yeti looks at ${ - user.minionName - }, and prepares to attack. Use ${mentionCommand(globalClient, 'k')} to fight the yeti!.`; - messages.push(bold(message)); - } - } - }, { name: 'Divine eggs', fn: async ({ data, user, portents, messages }) => { @@ -436,12 +313,10 @@ const tripFinishEffects: TripFinishEffect[] = [ charges: eggsReceived }); if (chargeResult.didCharge) { + const res = await user.addItemsToBank({ items: loot, collectionLog: true }); messages.push( - `You received ${loot}, your Rebirth portent has ${chargeResult.portent.charges_remaining}x charges remaining.` + `You received ${res}, your Rebirth portent has ${chargeResult.portent.charges_remaining}x charges remaining.` ); - return { - itemsToAddWithCL: loot - }; } } }, @@ -461,16 +336,17 @@ const tripFinishEffects: TripFinishEffect[] = [ return; } + const x = await user.transactItems({ + itemsToAdd: loot, + itemsToRemove: cost, + collectionLog: true + }); + if (cost.length > 0 && loot.length === 0) { messages.push(`<:moonlightMutator:1220590471613513780> Mutated ${cost}, but all died`); } else if (loot.length > 0) { - messages.push(`<:moonlightMutator:1220590471613513780> Mutated ${cost}; ${loot} survived`); + messages.push(`<:moonlightMutator:1220590471613513780> Mutated ${cost}; ${x.itemsAdded} survived`); } - - return { - itemsToAddWithCL: loot, - itemsToRemove: cost - }; } } } @@ -500,20 +376,21 @@ export async function handleTripFinish( const messages: string[] = []; const portents = await getAllPortentCharges(user); - const itemsToAddWithCL = new Bank(); - const itemsToRemove = new Bank(); for (const effect of tripFinishEffects) { - const stopwatch = new Stopwatch().start(); - const res = await effect.fn({ data, user, loot, messages, portents }); - if (res?.itemsToAddWithCL) itemsToAddWithCL.add(res.itemsToAddWithCL); - if (res?.itemsToRemove) itemsToRemove.add(res.itemsToRemove); - stopwatch.stop(); - if (stopwatch.duration > 500) { - debugLog(`Finished ${effect.name} trip effect for ${user.id} in ${stopwatch}`); - } + await effect.fn({ data, user, loot, messages, portents }); } - if (itemsToAddWithCL.length > 0 || itemsToRemove.length > 0) { - await user.transactItems({ itemsToAdd: itemsToAddWithCL, collectionLog: true, itemsToRemove }); + + const minutes = Math.floor(data.duration / Time.Minute); + if (minutes > 1) { + for (let i = 0; i < minutes; i++) { + if (roll(4800)) { + const randomRelic = relics.find(r => !user.hasRelic(r.id)); + if (randomRelic) { + await user.update({ relics: { push: randomRelic.id } }); + messages.push(`**You found a new relic:** ${randomRelic.name} (${randomRelic.desc}!`); + } + } + } } const clueReceived = loot ? ClueTiers.filter(tier => loot.amount(tier.scrollID) > 0) : []; @@ -573,30 +450,33 @@ export async function handleTripFinish( message.components = makeComponents(components); } - if (!user.owns('Mysterious clue (1)') && roll(10) && !user.bitfield.includes(BitField.HasUnlockedYeti)) { - const img = await mahojiChatHead({ - content: randArrItem([ - 'Traveller, I need your help... Use this clue to guide you.', - 'I have a task for you.... Use this clue to guide you.', - 'I have a quest for you... Use this clue to guide you.', - 'Duty calls. Use this clue to guide you.' - ]), - head: 'mysteriousFigure' - }); - message.files = img.files; - const loot = new Bank().add('Mysterious clue (1)'); - await user.addItemsToBank({ items: loot, collectionLog: true }); - if (user.user.bso_mystery_trail_current_step_id === null) { - await user.update({ - bso_mystery_trail_current_step_id: 1 - }); - } - if (message.content) { - message.content += `\nYou received ${loot}.`; - } - } - handleTriggerShootingStar(user, data, components); sendToChannelID(channelID, message); + + if (user.hasRelic(RelicID.Repetition) && !user.bitfield.includes(BitField.DisableRelicOfRepitition) && roll(3)) { + setTimeout(async () => { + try { + const trips = await fetchRepeatTrips(user.id); + if (trips.length === 0) { + return; + } + const handler = tripHandlers[data.type]; + const result = await runMahojiCommand({ + options: handler.args(trips[0].data as any), + commandName: handler.commandName, + guildID: null, + channelID, + userID: user.id, + member: null, + user, + interaction: null as any + }); + + if (result) { + sendToChannelID(channelID, result as any); + } + } catch (_) {} + }); + } } diff --git a/src/lib/util/logError.ts b/src/lib/util/logError.ts index 5917b1c75e..5cee969bfc 100644 --- a/src/lib/util/logError.ts +++ b/src/lib/util/logError.ts @@ -1,37 +1,16 @@ -import { convertAPIOptionsToCommandOptions, deepMerge } from '@oldschoolgg/toolkit'; -import { captureException } from '@sentry/node'; +import { convertAPIOptionsToCommandOptions } from '@oldschoolgg/toolkit'; import type { Interaction } from 'discord.js'; import { isObject } from 'e'; -import { production } from '../../config'; -import { globalConfig } from '../constants'; export function assert(condition: boolean, desc?: string, context?: Record) { if (!condition) { - if (production) { - logError(new Error(desc ?? 'Failed assertion'), context); - } else { - throw new Error(desc ?? 'Failed assertion'); - } + logError(new Error(desc ?? 'Failed assertion'), context); } } export function logError(err: Error | unknown, context?: Record, extra?: Record) { - const metaInfo = deepMerge(context ?? {}, extra ?? {}); - debugLog(`${(err as any)?.message ?? JSON.stringify(err)}`, { - type: 'ERROR', - raw: JSON.stringify(err), - metaInfo: JSON.stringify(metaInfo) - }); - if (globalConfig.isProduction) { - captureException(err, { - tags: context, - extra: metaInfo - }); - } else { - console.error(err); - console.log(metaInfo); - } + console.error(err, context, extra); } export function logErrorForInteraction( diff --git a/src/lib/util/makeBadgeString.ts b/src/lib/util/makeBadgeString.ts index 5a6598d358..a88159fdd3 100644 --- a/src/lib/util/makeBadgeString.ts +++ b/src/lib/util/makeBadgeString.ts @@ -1,9 +1,8 @@ -import { Emoji, badges } from '../constants'; +import { badges } from '../constants'; -export function makeBadgeString(badgeIDs: number[] | null | undefined, isIronman: boolean) { +export function makeBadgeString(user: MUser, badgeIDs: number[] | null | undefined) { + const method = user.getRandomizeMethod(); const rawBadges: string[] = (badgeIDs ?? []).map(num => badges[num]); - if (isIronman) { - rawBadges.push(Emoji.Ironman); - } + rawBadges.push(method?.emoji ?? ''); return rawBadges.join(' ').trim(); } diff --git a/src/lib/util/migrateUser.ts b/src/lib/util/migrateUser.ts deleted file mode 100644 index 16ec3a3bef..0000000000 --- a/src/lib/util/migrateUser.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { UserError } from '@oldschoolgg/toolkit'; - -import { cancelUsersListings } from '../../mahoji/lib/abstracted_commands/cancelGEListingCommand'; - -import { logError } from './logError'; - -export async function migrateUser(_source: string | MUser, _dest: string | MUser): Promise { - const sourceUser = typeof _source === 'string' ? await mUserFetch(_source) : _source; - const destUser = typeof _dest === 'string' ? await mUserFetch(_dest) : _dest; - - if (sourceUser.id === destUser.id) { - throw new UserError('Destination user cannot be the same as the source!'); - } - - // First check for + cancel active GE Listings: - await Promise.all([cancelUsersListings(sourceUser), cancelUsersListings(destUser)]); - - const transactions = []; - transactions.push(prisma.$executeRaw`SET CONSTRAINTS ALL DEFERRED`); - - // Delete Queries - // Slayer task must come before new_user since it's linked to new_users 500 IQ. - transactions.push(prisma.slayerTask.deleteMany({ where: { user_id: destUser.id } })); - - transactions.push(prisma.newUser.deleteMany({ where: { id: destUser.id } })); - - transactions.push(prisma.gearPreset.deleteMany({ where: { user_id: destUser.id } })); - transactions.push(prisma.giveaway.deleteMany({ where: { user_id: destUser.id } })); - transactions.push(prisma.botItemSell.deleteMany({ where: { user_id: destUser.id } })); - transactions.push(prisma.historicalData.deleteMany({ where: { user_id: destUser.id } })); - transactions.push(prisma.farmedCrop.deleteMany({ where: { user_id: destUser.id } })); - transactions.push(prisma.minigame.deleteMany({ where: { user_id: destUser.id } })); - transactions.push(prisma.playerOwnedHouse.deleteMany({ where: { user_id: destUser.id } })); - transactions.push(prisma.pinnedTrip.deleteMany({ where: { user_id: destUser.id } })); - transactions.push(prisma.reclaimableItem.deleteMany({ where: { user_id: destUser.id } })); - - transactions.push(prisma.activity.deleteMany({ where: { user_id: BigInt(destUser.id) } })); - transactions.push(prisma.xPGain.deleteMany({ where: { user_id: BigInt(destUser.id) } })); - transactions.push(prisma.lastManStandingGame.deleteMany({ where: { user_id: BigInt(destUser.id) } })); - transactions.push(prisma.userStats.deleteMany({ where: { user_id: BigInt(destUser.id) } })); - transactions.push(prisma.lootTrack.deleteMany({ where: { user_id: BigInt(destUser.id) } })); - transactions.push(prisma.buyCommandTransaction.deleteMany({ where: { user_id: BigInt(destUser.id) } })); - transactions.push(prisma.stashUnit.deleteMany({ where: { user_id: BigInt(destUser.id) } })); - transactions.push(prisma.bingoParticipant.deleteMany({ where: { user_id: destUser.id } })); - - // BSO: Delete target - transactions.push(prisma.tameActivity.deleteMany({ where: { user_id: destUser.id } })); - transactions.push(prisma.tame.deleteMany({ where: { user_id: destUser.id } })); - transactions.push(prisma.fishingContestCatch.deleteMany({ where: { user_id: BigInt(destUser.id) } })); - - // BSO Event: (The update commands are later...) - transactions.push( - prisma.mortimerTricks.deleteMany({ where: { OR: [{ trickster_id: destUser.id }, { target_id: destUser.id }] } }) - ); - - // For tables that aren't deleted, we often have to convert from target => source first to avoid FK errors, or null - transactions.push( - prisma.bingo.updateMany({ where: { creator_id: destUser.id }, data: { creator_id: sourceUser.id } }) - ); - - // Without this, the user_id will be set to null when the Key'd users row is deleted: - transactions.push( - prisma.gEListing.updateMany({ where: { user_id: destUser.id }, data: { user_id: sourceUser.id } }) - ); - - // Delete destUser.id user: - transactions.push(prisma.user.deleteMany({ where: { id: destUser.id } })); - - // Update queries: - transactions.push(prisma.user.updateMany({ where: { id: sourceUser.id }, data: { id: destUser.id } })); - transactions.push(prisma.newUser.updateMany({ where: { id: sourceUser.id }, data: { id: destUser.id } })); - - transactions.push( - prisma.bingo.updateMany({ where: { creator_id: sourceUser.id }, data: { creator_id: destUser.id } }) - ); - transactions.push( - prisma.bingoParticipant.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) - ); - - transactions.push( - prisma.gearPreset.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) - ); - transactions.push( - prisma.giveaway.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) - ); - transactions.push( - prisma.minigame.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) - ); - transactions.push( - prisma.playerOwnedHouse.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) - ); - transactions.push( - prisma.slayerTask.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) - ); - transactions.push( - prisma.farmedCrop.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) - ); - transactions.push( - prisma.botItemSell.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) - ); - transactions.push( - prisma.pinnedTrip.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) - ); - transactions.push( - prisma.historicalData.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) - ); - transactions.push( - prisma.reclaimableItem.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) - ); - - transactions.push( - prisma.activity.updateMany({ - where: { user_id: BigInt(sourceUser.id) }, - data: { user_id: BigInt(destUser.id) } - }) - ); - transactions.push( - prisma.xPGain.updateMany({ where: { user_id: BigInt(sourceUser.id) }, data: { user_id: BigInt(destUser.id) } }) - ); - transactions.push( - prisma.lastManStandingGame.updateMany({ - where: { user_id: BigInt(sourceUser.id) }, - data: { user_id: BigInt(destUser.id) } - }) - ); - transactions.push( - prisma.userStats.updateMany({ - where: { user_id: BigInt(sourceUser.id) }, - data: { user_id: BigInt(destUser.id) } - }) - ); - transactions.push( - prisma.lootTrack.updateMany({ - where: { user_id: BigInt(sourceUser.id) }, - data: { user_id: BigInt(destUser.id) } - }) - ); - transactions.push( - prisma.buyCommandTransaction.updateMany({ - where: { user_id: BigInt(sourceUser.id) }, - data: { user_id: BigInt(destUser.id) } - }) - ); - transactions.push( - prisma.stashUnit.updateMany({ - where: { user_id: BigInt(sourceUser.id) }, - data: { user_id: BigInt(destUser.id) } - }) - ); - - // CommandUsage/EconomyTx aren't wiped on the destUser.id first, so we can preserve that data: - transactions.push( - prisma.commandUsage.updateMany({ - where: { user_id: BigInt(sourceUser.id) }, - data: { user_id: BigInt(destUser.id) } - }) - ); - transactions.push( - prisma.economyTransaction.updateMany({ - where: { sender: BigInt(sourceUser.id) }, - data: { sender: BigInt(destUser.id) } - }) - ); - transactions.push( - prisma.economyTransaction.updateMany({ - where: { recipient: BigInt(sourceUser.id) }, - data: { recipient: BigInt(destUser.id) } - }) - ); - // GE Listing isn't wiped for destUser.id as that could mess up the GE - transactions.push( - prisma.gEListing.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) - ); - - // BSO Updates: - transactions.push( - prisma.tameActivity.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } }) - ); - transactions.push(prisma.tame.updateMany({ where: { user_id: sourceUser.id }, data: { user_id: destUser.id } })); - transactions.push( - prisma.fishingContestCatch.updateMany({ - where: { user_id: BigInt(sourceUser.id) }, - data: { user_id: BigInt(destUser.id) } - }) - ); - - // BSO Event Updates: - transactions.push( - prisma.mortimerTricks.updateMany({ - where: { trickster_id: sourceUser.id }, - data: { trickster_id: destUser.id } - }) - ); - transactions.push( - prisma.mortimerTricks.updateMany({ where: { target_id: sourceUser.id }, data: { target_id: destUser.id } }) - ); - - // Update Users in group activities: - const updateUsers = `UPDATE activity - SET data = data::jsonb - || CONCAT('{"users":', REPLACE(data->>'users', '${sourceUser.id}', '${destUser.id}'),'}')::jsonb - || CONCAT('{"leader":"', REPLACE(data->>'leader', '${sourceUser.id}', '${destUser.id}'), '"}')::jsonb - WHERE (data->'users')::jsonb ? '${sourceUser.id}'`; - transactions.push(prisma.$queryRawUnsafe(updateUsers)); - - // Update `detailedUsers` in ToA - const updateToAUsers = `UPDATE activity SET data = data::jsonb || CONCAT('{"detailedUsers":', REPLACE(data->>'detailedUsers', '${sourceUser.id}', '${destUser.id}'),'}')::jsonb WHERE type = 'TombsOfAmascut' AND data->>'detailedUsers' LIKE '%${sourceUser.id}%'`; - transactions.push(prisma.$queryRawUnsafe(updateToAUsers)); - - try { - await prisma.$transaction(transactions); - } catch (err: any) { - logError(err); - throw new UserError('Error migrating user. Sorry about that!'); - } - - const roboChimpTarget = await roboChimpClient.user.findFirst({ - select: { migrated_user_id: true }, - where: { id: BigInt(destUser.id) } - }); - if (!roboChimpTarget || roboChimpTarget.migrated_user_id !== BigInt(sourceUser.id)) { - // Only migrate robochimp data if it's not already been migrated: - const robochimpTx = []; - robochimpTx.push(roboChimpClient.user.deleteMany({ where: { id: BigInt(destUser.id) } })); - robochimpTx.push( - roboChimpClient.user.updateMany({ - where: { id: BigInt(sourceUser.id) }, - data: { id: BigInt(destUser.id) } - }) - ); - // Set the migrated_user_id value to prevent duplicate robochimp migrations. - robochimpTx.push( - roboChimpClient.user.updateMany({ - where: { id: BigInt(destUser.id) }, - data: { migrated_user_id: BigInt(sourceUser.id) } - }) - ); - try { - await roboChimpClient.$transaction(robochimpTx); - } catch (err: any) { - err.message += ' - User already migrated! Robochimp migration failed!'; - logError(err); - throw new UserError('Robochimp migration failed, but minion data migrated already!'); - } - } - - // This regenerates a default users table row for the now-clean sourceUser - await mUserFetch(sourceUser.id); - - return true; -} diff --git a/src/lib/util/minionStatsEmbed.ts b/src/lib/util/minionStatsEmbed.ts index 24bb83ecaa..c2fa3c370f 100644 --- a/src/lib/util/minionStatsEmbed.ts +++ b/src/lib/util/minionStatsEmbed.ts @@ -40,10 +40,6 @@ export async function minionStatsEmbed(user: MUser): Promise { openable_scores: true, fight_caves_attempts: true, firecapes_sacrificed: true, - dice_losses: true, - dice_wins: true, - duel_losses: true, - duel_wins: true, tithe_farms_completed: true, laps_scores: true, monster_scores: true, @@ -155,10 +151,6 @@ export async function minionStatsEmbed(user: MUser): Promise { ['Fight Caves Attempts', userStats.fight_caves_attempts], ['Fire Capes Sacrificed', userStats.firecapes_sacrificed], ['Tithe Farm Score', userStats.tithe_farms_completed], - ['Dice Wins', userStats.dice_wins], - ['Dice Losses', userStats.dice_losses], - ['Duel Wins', userStats.duel_wins], - ['Duel Losses', userStats.duel_losses], ['High Gambles', userStats.high_gambles], ['Carpenter Points', user.user.carpenter_points], ['Sacrificed', toKMB(Number(user.user.sacrificedValue))] diff --git a/src/lib/util/minionStatus.ts b/src/lib/util/minionStatus.ts index d043de2648..19544f536d 100644 --- a/src/lib/util/minionStatus.ts +++ b/src/lib/util/minionStatus.ts @@ -4,7 +4,6 @@ import { SkillsEnum } from 'oldschooljs/dist/constants'; import { shades, shadesLogs } from '../../mahoji/lib/abstracted_commands/shadesOfMortonCommand'; import { collectables } from '../../mahoji/lib/collectables'; -import { bossEvents } from '../bossEvents'; import { divinationEnergies, memoryHarvestTypes } from '../bso/divination'; import { ClueTiers } from '../clues/clueTiers'; import { Emoji } from '../constants'; @@ -667,11 +666,6 @@ export function minionStatus(user: MUser) { data.quantity } fights in Monkey Rumble. ${formattedDuration}`; } - case 'BossEvent': { - const data = currentTask as NewBossOptions; - const bossDoing = bossEvents.find(b => b.id === data.bossID)!; - return `${name} is currently doing a ${bossDoing.name} Boss Event! ${formattedDuration}`; - } case 'FishingContest': { const data = currentTask as FishingContestOptions; return `${name} is currently fishing for the fishing contest at ${ diff --git a/src/lib/util/minionUtils.ts b/src/lib/util/minionUtils.ts index 3f009dc88e..9c77b65f27 100644 --- a/src/lib/util/minionUtils.ts +++ b/src/lib/util/minionUtils.ts @@ -77,9 +77,9 @@ export const bolts = resolveItems([ ]); export function minionName(user: MUser) { - let [name, isIronman, icon] = [user.user.minion_name, user.user.minion_ironman, user.user.minion_icon]; + let [name, icon] = [user.user.minion_name, user.user.minion_icon]; - const prefix = isIronman ? Emoji.Ironman : ''; + const prefix = Emoji.Ironman; icon ??= Emoji.Minion; const strPrefix = prefix ? `${prefix} ` : ''; diff --git a/src/lib/util/repairBrokenItems.ts b/src/lib/util/repairBrokenItems.ts index 7282197634..5e2c4b581e 100644 --- a/src/lib/util/repairBrokenItems.ts +++ b/src/lib/util/repairBrokenItems.ts @@ -1,10 +1,10 @@ import { deepEqual, deepObjectDiff } from '@oldschoolgg/toolkit'; -import type { GearSetupType, Prisma } from '@prisma/client'; +import type { Prisma } from '@prisma/client'; import { deepClone, notEmpty, uniqueArr } from 'e'; import { Items } from 'oldschooljs'; import { userStatsUpdate } from '../../mahoji/mahojiSettings'; -import { type GearSetup, GearSetupTypes } from '../gear'; +import { type GearSetup, type GearSetupType, GearSetupTypes } from '../gear'; import type { ItemBank } from '../types'; import { moidLink } from '../util'; @@ -13,7 +13,6 @@ type GearX = Required>; type Changes = { bank: ItemBank; collectionLogBank: ItemBank; - temp_cl: ItemBank; sacrificedBank: ItemBank; favoriteItems: number[]; tames: { id: number; max_total_loot: ItemBank; fed_items: ItemBank }[]; @@ -31,7 +30,6 @@ export async function repairBrokenItemsFromUser(mUser: MUser) { const __currentValues: Changes = { bank: user.bank as ItemBank, collectionLogBank: user.collectionLogBank as ItemBank, - temp_cl: user.temp_cl as ItemBank, sacrificedBank: (await mUser.fetchStats({ sacrificed_bank: true })).sacrificed_bank as ItemBank, favoriteItems: user.favoriteItems, tames: userTames.map(t => ({ @@ -74,7 +72,6 @@ export async function repairBrokenItemsFromUser(mUser: MUser) { const allItemsToCheck = uniqueArr([ ...Object.keys(currentValues.bank), ...Object.keys(currentValues.collectionLogBank), - ...Object.keys(currentValues.temp_cl), ...Object.keys(currentValues.sacrificedBank), ...currentValues.favoriteItems, ...allGearItemIDs @@ -91,7 +88,6 @@ export async function repairBrokenItemsFromUser(mUser: MUser) { for (const id of brokenBank) { delete newValues.bank[id]; delete newValues.collectionLogBank[id]; - delete newValues.temp_cl[id]; delete newValues.sacrificedBank[id]; for (const tame of newValues.tames) { delete tame.fed_items[id]; @@ -121,9 +117,7 @@ export async function repairBrokenItemsFromUser(mUser: MUser) { if (!deepEqual(currentValues.collectionLogBank, newValues.collectionLogBank)) { changes.collectionLogBank = newValues.collectionLogBank; } - if (!deepEqual(currentValues.temp_cl, newValues.temp_cl)) { - changes.temp_cl = newValues.temp_cl; - } + if (!deepEqual(currentValues.favoriteItems, newValues.favoriteItems)) { changes.favoriteItems = newValues.favoriteItems; } diff --git a/src/lib/util/smallUtils.ts b/src/lib/util/smallUtils.ts index 8a70223970..98f63dd4fd 100644 --- a/src/lib/util/smallUtils.ts +++ b/src/lib/util/smallUtils.ts @@ -1,11 +1,11 @@ -import { deepMerge, md5sum, miniID, toTitleCase } from '@oldschoolgg/toolkit'; +import { deepMerge, md5sum, toTitleCase } from '@oldschoolgg/toolkit'; import type { CommandResponse } from '@oldschoolgg/toolkit'; import type { Prisma } from '@prisma/client'; import { AlignmentEnum, AsciiTable3 } from 'ascii-table3'; import type { InteractionReplyOptions } from 'discord.js'; import { ButtonBuilder, ButtonStyle } from 'discord.js'; import { clamp, objectEntries, roll } from 'e'; -import { type Bank, Items, LootTable } from 'oldschooljs'; +import { type Bank, Items } from 'oldschooljs'; import type { ItemBank } from 'oldschooljs/dist/meta/types'; import type { ArrayItemsResolved } from 'oldschooljs/dist/util/util'; import { MersenneTwister19937, shuffle } from 'random-js'; @@ -122,8 +122,6 @@ export function makeAutoFarmButton() { export const SQL_sumOfAllCLItems = (clItems: number[]) => `NULLIF(${clItems.map(i => `COALESCE(("collectionLogBank"->>'${i}')::int, 0)`).join(' + ')}, 0)`; -export const generateGrandExchangeID = () => miniID(6).toLowerCase(); - export function getToaKCs(toaRaidLevelsBank: Prisma.JsonValue) { let entryKC = 0; let normalKC = 0; @@ -165,22 +163,6 @@ export function calculateSimpleMonsterDeathChance({ return clamp(deathChance, lowestDeathChance, highestDeathChance); } -export function removeItemsFromLootTable(lootTable: LootTable, itemsToRemove: number[]): void { - const filterFunction = (item: any) => !itemsToRemove.includes(item); - - lootTable.table = lootTable.table.filter(item => filterFunction(item.item)); - lootTable.oneInItems = lootTable.oneInItems.filter(item => filterFunction(item.item)); - lootTable.tertiaryItems = lootTable.tertiaryItems.filter(item => filterFunction(item.item)); - lootTable.everyItems = lootTable.everyItems.filter(item => filterFunction(item.item)); - lootTable.allItems = lootTable.allItems.filter(filterFunction); - - for (const item of lootTable.table) { - if (item.item instanceof LootTable) { - removeItemsFromLootTable(item.item, itemsToRemove); - } - } -} - export function perHourChance( durationMilliseconds: number, oneInXPerHourChance: number, diff --git a/src/lib/util/tradePlayerItems.ts b/src/lib/util/tradePlayerItems.ts deleted file mode 100644 index e07a82482e..0000000000 --- a/src/lib/util/tradePlayerItems.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Bank } from 'oldschooljs'; - -import { modifyBusyCounter } from '../busyCounterCache'; - -import { logError } from './logError'; -import { userQueueFn } from './userQueues'; - -export async function tradePlayerItems(sender: MUser, recipient: MUser, _itemsToSend?: Bank, _itemsToReceive?: Bank) { - if (recipient.isBusy) { - return { success: false, message: `${recipient.usernameOrMention} is busy.` }; - } - modifyBusyCounter(sender.id, 1); - modifyBusyCounter(recipient.id, 1); - - const itemsToSend = _itemsToSend ? _itemsToSend.clone() : new Bank(); - const itemsToReceive = _itemsToReceive ? _itemsToReceive.clone() : new Bank(); - - // Queue the primary trade function: - return userQueueFn(sender.id, async () => { - return userQueueFn(recipient.id, async () => { - try { - await Promise.all([sender.sync(), recipient.sync()]); - if (!sender.owns(itemsToSend)) { - return { success: false, message: `${sender.usernameOrMention} doesn't own all items.` }; - } - if (!recipient.owns(itemsToReceive)) { - return { success: false, message: `${recipient.usernameOrMention} doesn't own all items.` }; - } - const newSenderBank = sender.bank.clone(); - const newRecipientBank = recipient.bank.clone(); - - let senderGP = sender.GP; - let recipientGP = recipient.GP; - - if (itemsToSend.has('Coins')) { - const sentCoins = itemsToSend.amount('Coins'); - senderGP -= sentCoins; - itemsToSend.remove('Coins', sentCoins); - recipientGP += sentCoins; - } - if (itemsToReceive.has('Coins')) { - const receivedCoins = itemsToReceive.amount('Coins'); - recipientGP -= receivedCoins; - itemsToReceive.remove('Coins', receivedCoins); - senderGP += receivedCoins; - } - - newSenderBank.remove(itemsToSend); - newRecipientBank.remove(itemsToReceive); - newSenderBank.add(itemsToReceive); - newRecipientBank.add(itemsToSend); - - const updateSender = prisma.user.update({ - data: { - GP: senderGP, - bank: newSenderBank.bank - }, - where: { - id: sender.id - } - }); - const updateRecipient = prisma.user.update({ - data: { - GP: recipientGP, - bank: newRecipientBank.bank - }, - where: { - id: recipient.id - } - }); - // Run both in a single transaction, so it all succeeds or all fails: - await prisma.$transaction([updateSender, updateRecipient]); - await Promise.all([sender.sync(), recipient.sync()]); - return { success: true, message: null }; - } catch (e: any) { - // We should only get here if something bad happened... most likely prisma, but possibly command competition - logError(e, undefined, { - sender: sender.id, - recipient: recipient.id, - command: 'trade', - items_sent: itemsToSend.toString(), - items_received: itemsToReceive.toString() - }); - return { success: false, message: 'Temporary error, please try again.' }; - } finally { - modifyBusyCounter(sender.id, -1); - modifyBusyCounter(recipient.id, -1); - } - }); - }); -} diff --git a/src/lib/util/transactItemsFromBank.ts b/src/lib/util/transactItemsFromBank.ts index 8dbfdcd5e8..7487646e66 100644 --- a/src/lib/util/transactItemsFromBank.ts +++ b/src/lib/util/transactItemsFromBank.ts @@ -1,9 +1,9 @@ import type { Prisma } from '@prisma/client'; import { Bank } from 'oldschooljs'; -import { findBingosWithUserParticipating } from '../../mahoji/lib/bingo/BingoManager'; import { mahojiUserSettingsUpdate } from '../MUser'; import { handleNewCLItems } from '../handleNewCLItems'; +import { remapBank } from '../randomizer'; import { filterLootReplace } from '../slayer/slayerUtil'; import type { ItemBank } from '../types'; import { logError } from './logError'; @@ -18,6 +18,7 @@ export interface TransactItemsArgs { dontAddToTempCL?: boolean; neverUpdateHistory?: boolean; otherUpdates?: Prisma.UserUpdateArgs['data']; + shouldRemap?: boolean; } declare global { @@ -30,13 +31,18 @@ async function transactItemsFromBank({ collectionLog = false, filterLoot = true, dontAddToTempCL = false, + shouldRemap = true, ...options }: TransactItemsArgs) { let itemsToAdd = options.itemsToAdd ? options.itemsToAdd.clone() : undefined; + const itemsToRemove = options.itemsToRemove ? options.itemsToRemove.clone() : undefined; return userQueueFn(userID, async function transactItemsInner() { const settings = await mUserFetch(userID); + if (itemsToAdd && shouldRemap) { + itemsToAdd.bank = remapBank(settings, itemsToAdd).bank; + } const gpToRemove = (itemsToRemove?.amount('Coins') ?? 0) - (itemsToAdd?.amount('Coins') ?? 0); if (itemsToRemove && settings.GP < gpToRemove) { @@ -108,6 +114,12 @@ async function transactItemsFromBank({ newBank.remove(itemsToRemove); } + for (const [key, val] of Object.entries(newBank.bank)) { + if (key.toString() === '995') continue; + if (val > 10_000_000) { + newBank.bank[key] = 10_000_000; + } + } const { newUser } = await mahojiUserSettingsUpdate(userID, { bank: newBank.bank, GP: gpUpdate, @@ -127,19 +139,16 @@ async function transactItemsFromBank({ const newCL = new Bank(newUser.collectionLogBank as ItemBank); - if (!dontAddToTempCL && collectionLog) { - const activeBingos = await findBingosWithUserParticipating(userID); - for (const bingo of activeBingos) { - if (bingo.isActive()) { - bingo.handleNewItems(userID, itemsAdded); - } - } - } - if (!options.neverUpdateHistory) { await handleNewCLItems({ itemsAdded, user: settings, previousCL, newCL }); } + if (options.itemsToAdd) { + options.itemsToAdd.toString = () => { + return itemsAdded.toString(); + }; + } + return { previousCL, itemsAdded, diff --git a/src/lib/util/updateBankSetting.ts b/src/lib/util/updateBankSetting.ts deleted file mode 100644 index 8ff8e69b11..0000000000 --- a/src/lib/util/updateBankSetting.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Bank } from 'oldschooljs'; - -import type { ItemBank } from '../types'; -import { mahojiClientSettingsFetch, mahojiClientSettingsUpdate } from './clientSettings'; - -export type ClientBankKey = - | 'sold_items_bank' - | 'herblore_cost_bank' - | 'construction_cost_bank' - | 'farming_cost_bank' - | 'farming_loot_bank' - | 'buy_cost_bank' - | 'buy_loot_bank' - | 'magic_cost_bank' - | 'crafting_cost' - | 'gnome_res_cost' - | 'gnome_res_loot' - | 'rogues_den_cost' - | 'gauntlet_loot' - | 'cox_cost' - | 'cox_loot' - | 'collecting_cost' - | 'collecting_loot' - | 'mta_cost' - | 'bf_cost' - | 'mage_arena_cost' - | 'hunter_cost' - | 'hunter_loot' - | 'revs_cost' - | 'revs_loot' - | 'inferno_cost' - | 'dropped_items' - | 'runecraft_cost' - | 'smithing_cost' - | 'economyStats_dicingBank' - | 'economyStats_duelTaxBank' - | 'economyStats_dailiesAmount' - | 'economyStats_itemSellTaxBank' - | 'economyStats_bankBgCostBank' - | 'economyStats_sacrificedBank' - | 'economyStats_wintertodtCost' - | 'economyStats_wintertodtLoot' - | 'economyStats_fightCavesCost' - | 'economyStats_PVMCost' - | 'economyStats_thievingCost' - | 'nightmare_cost' - | 'create_cost' - | 'create_loot' - | 'tob_cost' - | 'tob_loot' - | 'degraded_items_cost' - | 'tks_cost' - | 'tks_loot' - | 'gotr_cost' - | 'gotr_loot' - | 'ignecarus_cost' - | 'ignecarus_loot' - | 'kibble_cost' - | 'mr_cost' - | 'mr_loot' - | 'item_contract_cost' - | 'item_contract_loot' - | 'kg_cost' - | 'kg_loot' - | 'gf_cost' - | 'gf_loot' - | 'nex_cost' - | 'nex_loot' - | 'kk_cost' - | 'kk_loot' - | 'vasa_cost' - | 'vasa_loot' - | 'ods_cost' - | 'ods_loot' - | 'naxxus_loot' - | 'naxxus_cost' - | 'tame_merging_cost' - | 'trip_doubling_loot' - | 'fc_cost' - | 'fc_loot' - | 'zippy_loot' - | 'market_prices' - | 'bb_cost' - | 'bb_loot' - | 'moktang_cost' - | 'moktang_loot' - | 'nmz_cost' - | 'toa_cost' - | 'toa_loot' - | 'doa_cost' - | 'doa_loot' - | 'xmas_ironman_food_bank' - | 'colo_cost' - | 'colo_loot'; - -export async function updateBankSetting(key: ClientBankKey, bankToAdd: Bank) { - if (bankToAdd === undefined || bankToAdd === null) throw new Error(`Gave null bank for ${key}`); - const currentClientSettings = await mahojiClientSettingsFetch({ - [key]: true - }); - const current = currentClientSettings[key] as ItemBank; - const newBank = new Bank(current).add(bankToAdd); - - const res = await mahojiClientSettingsUpdate({ - [key]: newBank.bank - }); - return res; -} diff --git a/src/lib/util/webhook.ts b/src/lib/util/webhook.ts index 6b19ddaa7b..2725ed2a11 100644 --- a/src/lib/util/webhook.ts +++ b/src/lib/util/webhook.ts @@ -3,8 +3,7 @@ import type { AttachmentBuilder, BaseMessageOptions, EmbedBuilder, Message } fro import { PartialGroupDMChannel, PermissionsBitField, WebhookClient } from 'discord.js'; import PQueue from 'p-queue'; -import { production } from '../../config'; - +import { globalConfig } from '../constants'; import { channelIsSendable } from '../util'; import { logError } from './logError'; @@ -18,7 +17,10 @@ async function resolveChannel(channelID: string): Promise => { - const clueTier = ClueTiers.find(tier => tier.id === clueTierID)!; - - const loot = clueTier.table.open(quantity, { cl: new Bank() } as MUser); - - for (let i = 0; i < quantity; i++) { - const qty = randInt(1, 3); - loot.add(clueTier.table.open(qty, { cl: loot } as MUser)); - } - - let mimicNumber = 0; - if (clueTier.mimicChance) { - for (let i = 0; i < quantity; i++) { - if (roll(clueTier.mimicChance)) { - loot.add(Misc.Mimic.open(clueTier.name as 'master' | 'elite')); - mimicNumber++; - } - } - } - - const opened = `You opened ${quantity} ${clueTier.name} Clue Casket${quantity > 1 ? 's' : ''}${ - mimicNumber > 0 - ? ` and defeated ${mimicNumber} - mimic${mimicNumber > 1 ? 's' : ''}` - : '' - }`; - - return [new Bank(loot), opened]; -}; diff --git a/src/lib/workers/finish.worker.ts b/src/lib/workers/finish.worker.ts deleted file mode 100644 index be38885901..0000000000 --- a/src/lib/workers/finish.worker.ts +++ /dev/null @@ -1,55 +0,0 @@ -import '../../lib/customItems/customItems'; -import '../data/itemAliases'; - -import { removeFromArr } from 'e'; -import { Bank } from 'oldschooljs'; - -import type { FinishWorkerArgs, FinishWorkerReturn } from '.'; -import getOSItem from '../util/getOSItem'; - -if (global.prisma) { - throw new Error('Prisma is loaded in the finish worker!'); -} - -export default async ({ name, tertiaries }: FinishWorkerArgs): FinishWorkerReturn => { - const { finishables } = await import('../finishables.js'); - const val = finishables.find(i => i.name === name)!; - let finishCL = [...val.cl]; - if (val.tertiaryDrops && !tertiaries) { - for (const { itemId } of val.tertiaryDrops) { - finishCL = removeFromArr(finishCL, itemId); - } - } - const cost = new Bank(); - const loot = new Bank(); - const kcBank = new Bank(); - let kc = 0; - const maxAttempts = val.maxAttempts ?? 100_000; - for (let i = 0; i < maxAttempts; i++) { - if (finishCL.every(id => loot.has(id))) break; - kc++; - const res = val.kill({ accumulatedLoot: loot, totalRuns: i }); - const thisLoot = 'cost' in res ? res.loot : res; - if ('cost' in res) cost.add(res.cost); - - if (tertiaries && val.tertiaryDrops) { - for (const drop of val.tertiaryDrops) { - if (kc === drop.kcNeeded) { - thisLoot.add(drop.itemId); - } - } - } - - const purpleItems = thisLoot.items().filter(i => finishCL.includes(i[0].id) && !loot.has(i[0].id)); - for (const p of purpleItems) kcBank.add(p[0].id, kc); - loot.add(thisLoot); - if (kc === maxAttempts) { - return `After ${maxAttempts.toLocaleString()} KC, you still didn't finish the CL, so we're giving up! Missing: ${finishCL - .filter(id => !loot.has(id)) - .map(getOSItem) - .map(i => i.name) - .join(', ')}`; - } - } - return { kc, kcBank: kcBank.bank, loot: loot.bank, cost: cost.bank }; -}; diff --git a/src/lib/workers/index.ts b/src/lib/workers/index.ts deleted file mode 100644 index 880417e7eb..0000000000 --- a/src/lib/workers/index.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { resolve } from 'node:path'; - -import type { Bank } from 'oldschooljs'; -import Piscina from 'piscina'; - -import { production } from '../../config'; -import type { ItemBank } from '../types'; - -export interface CasketWorkerArgs { - clueTierID: number; - quantity: number; -} - -export interface KillWorkerArgs { - bossName: string; - quantity: number; - limit: number; - onTask: boolean; - catacombs: boolean; - lootTableTertiaryChanges: [string, number][]; -} - -export type KillWorkerReturn = Promise<{ - bank?: Bank; - error?: string; - title?: string; - content?: string; -}>; - -export interface FinishWorkerArgs { - name: string; - tertiaries?: boolean; -} - -export type FinishWorkerReturn = Promise< - | { - loot: ItemBank; - kcBank: ItemBank; - kc: number; - cost: ItemBank; - } - | string ->; - -const maxThreads = production ? 3 : 1; - -let dirName = __dirname.replace('src/lib', 'dist/lib'); -if (dirName.endsWith('dist')) { - dirName = resolve(dirName, 'lib', 'workers'); -} - -const finishWorkerPath = resolve(dirName, 'finish.worker.js'); -const killWorkerPath = resolve(dirName, 'kill.worker.js'); -const casketWorkerPath = resolve(dirName, 'casket.worker.js'); - -const finishWorker = new Piscina({ - filename: finishWorkerPath, - maxThreads -}); -const killWorker = new Piscina({ - filename: killWorkerPath, - maxThreads -}); -const casketWorker = new Piscina({ - filename: casketWorkerPath, - maxThreads -}); - -export const Workers = { - casketOpen: (args: CasketWorkerArgs): Promise<[Bank, string]> => casketWorker.run(args), - kill: (args: KillWorkerArgs): KillWorkerReturn => killWorker.run(args), - finish: (args: FinishWorkerArgs): FinishWorkerReturn => finishWorker.run(args) -}; diff --git a/src/lib/workers/kill.worker.ts b/src/lib/workers/kill.worker.ts deleted file mode 100644 index 0f00bdf313..0000000000 --- a/src/lib/workers/kill.worker.ts +++ /dev/null @@ -1,66 +0,0 @@ -import '../customItems/customItems'; -import '../data/itemAliases'; - -import { stringMatches } from '@oldschoolgg/toolkit'; -import { Bank, Monsters } from 'oldschooljs'; - -import type { KillWorkerArgs, KillWorkerReturn } from '.'; -import { production } from '../../config'; -import { YETI_ID } from '../constants'; -import killableMonsters from '../minions/data/killableMonsters/index'; -import { simulatedKillables } from '../simulation/simulatedKillables'; - -if (global.prisma) { - throw new Error('Prisma is loaded in the kill worker!'); -} -export default async ({ - quantity, - bossName, - catacombs, - onTask, - limit, - lootTableTertiaryChanges -}: KillWorkerArgs): KillWorkerReturn => { - const osjsMonster = Monsters.find(mon => mon.aliases.some(alias => stringMatches(alias, bossName))); - - if (osjsMonster) { - if (osjsMonster.id === YETI_ID && production) { - return { error: 'The bot is too scared to simulate fighting the yeti.' }; - } - if (quantity > limit) { - return { - error: `The quantity you gave exceeds your limit of ${limit.toLocaleString()}! *You can increase your limit by up to 1 million by becoming a patron at ` - }; - } - - const result = { - bank: osjsMonster.kill(quantity, { - inCatacombs: catacombs, - onSlayerTask: onTask, - lootTableOptions: { - tertiaryItemPercentageChanges: new Map(lootTableTertiaryChanges) - } - }) - }; - - const killableMonster = killableMonsters.find(mon => mon.id === osjsMonster.id); - if (killableMonster?.specialLoot) { - killableMonster.specialLoot({ ownedItems: result.bank, loot: result.bank, quantity, cl: new Bank() }); - } - - return result; - } - - const simulatedKillable = simulatedKillables.find(i => stringMatches(i.name, bossName)); - if (simulatedKillable) { - if (quantity > limit) { - return { - error: `The quantity you gave exceeds your limit of ${limit.toLocaleString()}! *You can increase your limit by up to 1 million by becoming a patron at ` - }; - } - - return { bank: simulatedKillable.loot(quantity) }; - } - - return { error: "I don't have that monster!" }; -}; diff --git a/src/lib/xpLamps.ts b/src/lib/xpLamps.ts deleted file mode 100644 index 1a45002408..0000000000 --- a/src/lib/xpLamps.ts +++ /dev/null @@ -1,64 +0,0 @@ -import LootTable from 'oldschooljs/dist/structures/LootTable'; - -export interface XPLamp { - itemID: number; - amount: number; - name: string; -} - -export const XPLamps: XPLamp[] = [ - // Achievement diary lamps - { - itemID: 11_137, - amount: 2500, - name: 'Antique lamp 1' - }, - { - itemID: 11_139, - amount: 7500, - name: 'Antique lamp 2' - }, - { - itemID: 11_141, - amount: 15_000, - name: 'Antique lamp 3' - }, - { - itemID: 11_185, - amount: 50_000, - name: 'Antique lamp 4' - }, - // BSO Lamps - { - itemID: 6796, - amount: 20_000, - name: 'Tiny lamp' - }, - { - itemID: 21_642, - amount: 50_000, - name: 'Small lamp' - }, - { - itemID: 23_516, - amount: 100_000, - name: 'Average lamp' - }, - { - itemID: 22_320, - amount: 1_000_000, - name: 'Large lamp' - }, - { - itemID: 11_157, - amount: 5_000_000, - name: 'Huge lamp' - } -]; - -export const LampTable = new LootTable() - .add(6796, 1, 40) - .add(21_642, 1, 30) - .add(23_516, 1, 20) - .add(22_320, 1, 5) - .add(11_157, 1, 1); diff --git a/src/mahoji/commands/admin.ts b/src/mahoji/commands/admin.ts index 7227ab8984..f893044fcd 100644 --- a/src/mahoji/commands/admin.ts +++ b/src/mahoji/commands/admin.ts @@ -1,56 +1,16 @@ -import { type CommandRunOptions, bulkUpdateCommands, convertBankToPerHourStats, dateFm } from '@oldschoolgg/toolkit'; +import { type CommandRunOptions, bulkUpdateCommands, dateFm } from '@oldschoolgg/toolkit'; import type { MahojiUserOption } from '@oldschoolgg/toolkit'; -import type { ClientStorage } from '@prisma/client'; -import { economy_transaction_type } from '@prisma/client'; -import { Duration } from '@sapphire/time-utilities'; -import type { InteractionReplyOptions, Message, TextChannel } from 'discord.js'; -import { AttachmentBuilder, userMention } from 'discord.js'; import { ApplicationCommandOptionType } from 'discord.js'; -import { Time, calcPercentOfNum, calcWhatPercent, noOp, notEmpty, randArrItem, roll, sleep, uniqueArr } from 'e'; -import { Bank } from 'oldschooljs'; -import type { ItemBank } from 'oldschooljs/dist/meta/types'; +import { Time, noOp, randArrItem, sleep } from 'e'; -import { ADMIN_IDS, OWNER_IDS, SupportServer, production } from '../../config'; -import { mahojiUserSettingsUpdate } from '../../lib/MUser'; -import { BLACKLISTED_GUILDS, BLACKLISTED_USERS, syncBlacklists } from '../../lib/blacklists'; -import { boxFrenzy } from '../../lib/boxFrenzy'; -import { - BadgesEnum, - BitField, - BitFieldData, - COINS_ID, - Channel, - DISABLED_COMMANDS, - META_CONSTANTS, - badges, - globalConfig -} from '../../lib/constants'; -import { slayerMaskHelms } from '../../lib/data/slayerMaskHelms'; -import { addToDoubleLootTimer, syncDoubleLoot } from '../../lib/doubleLoot'; -import { economyLog } from '../../lib/economyLogs'; -import type { GearSetup } from '../../lib/gear/types'; -import { GrandExchange } from '../../lib/grandExchange'; -import { countUsersWithItemInCl } from '../../lib/settings/prisma'; +import { DISABLED_COMMANDS, META_CONSTANTS, globalConfig } from '../../lib/constants'; import { cancelTask, minionActivityCacheDelete } from '../../lib/settings/settings'; -import { sorts } from '../../lib/sorts'; -import { calcPerHour, cleanString, formatDuration, sanitizeBank, stringMatches, toKMB } from '../../lib/util'; -import { memoryAnalysis } from '../../lib/util/cachedUserIDs'; -import { mahojiClientSettingsFetch, mahojiClientSettingsUpdate } from '../../lib/util/clientSettings'; -import getOSItem, { getItem } from '../../lib/util/getOSItem'; -import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; +import { stringMatches } from '../../lib/util'; import { deferInteraction, interactionReply } from '../../lib/util/interactionReply'; -import { makeBankImage } from '../../lib/util/makeBankImage'; -import { parseBank } from '../../lib/util/parseStringBank'; -import { slayerMaskLeaderboardCache } from '../../lib/util/slayerMaskLeaderboard'; import { sendToChannelID } from '../../lib/util/webhook'; -import { LampTable } from '../../lib/xpLamps'; import { Cooldowns } from '../lib/Cooldowns'; -import { syncCustomPrices } from '../lib/events'; -import { itemOption } from '../lib/mahojiCommandOptions'; import type { OSBMahojiCommand } from '../lib/util'; import { allAbstractCommands } from '../lib/util'; -import { mahojiUsersSettingsFetch } from '../mahojiSettings'; -import { getLotteryBank } from './lottery'; export const gifs = [ 'https://tenor.com/view/angry-stab-monkey-knife-roof-gif-13841993', @@ -58,380 +18,10 @@ export const gifs = [ 'https://tenor.com/view/monkey-monito-mask-gif-23036908' ]; -async function allEquippedPets() { - const pets = await prisma.$queryRawUnsafe<{ pet: number; qty: number }[]>(`SELECT "minion.equippedPet" AS pet, COUNT("minion.equippedPet")::int AS qty -FROM users -WHERE "minion.equippedPet" IS NOT NULL -GROUP BY "minion.equippedPet" -ORDER BY qty DESC;`); - const bank = new Bank(); - for (const { pet, qty } of pets) { - bank.add(pet, qty); - } - return bank; -} - -async function getAllTradedItems(giveUniques = false) { - const economyTrans = await prisma.economyTransaction.findMany({ - where: { - date: { - gt: new Date(Date.now() - Time.Month) - }, - type: economy_transaction_type.trade - }, - select: { - items_received: true, - items_sent: true - } - }); - - const total = new Bank(); - - if (giveUniques) { - for (const trans of economyTrans) { - const bank = new Bank().add(trans.items_received as ItemBank).add(trans.items_sent as ItemBank); - - for (const item of bank.items()) { - total.add(item[0].id); - } - } - } else { - for (const trans of economyTrans) { - total.add(trans.items_received as ItemBank); - total.add(trans.items_sent as ItemBank); - } - } - - return total; -} - -const viewableThings: { - name: string; - run: (clientSettings: ClientStorage) => Promise; -}[] = [ - { - name: 'ToB Cost', - run: async clientSettings => { - return new Bank(clientSettings.tob_cost as ItemBank); - } - }, - { - name: 'Invention Disassembly Cost', - run: async clientSettings => { - return new Bank(clientSettings.items_disassembled_cost as ItemBank); - } - }, - { - name: 'All Equipped Items', - run: async () => { - const res = await prisma.$queryRaw[]>`SELECT "gear.melee", -"gear.mage", -"gear.range", -"gear.misc", -"gear.skilling", -"gear.wildy", -"gear.fashion", -"gear.other" -FROM users -WHERE last_command_date > now() - INTERVAL '1 weeks' -AND ("gear.melee" IS NOT NULL OR -"gear.mage" IS NOT NULL OR -"gear.range" IS NOT NULL OR -"gear.misc" IS NOT NULL OR -"gear.skilling" IS NOT NULL OR -"gear.wildy" IS NOT NULL OR -"gear.fashion" IS NOT NULL OR -"gear.other" IS NOT NULL);`; - const bank = new Bank(); - for (const user of res) { - for (const gear of Object.values(user) - .flatMap(i => (i === null ? [] : Object.values(i))) - .filter(notEmpty)) { - const item = getItem(gear.item); - if (item) { - bank.add(gear.item, gear.quantity); - } - } - } - return bank; - } - }, - { - name: 'Most Traded Items (30d, Total Volume)', - run: async () => { - const items = await getAllTradedItems(); - return { - content: items - .items() - .sort(sorts.quantity) - .slice(0, 10) - .map((i, index) => `${++index}. ${i[0].name} - ${i[1].toLocaleString()}x traded`) - .join('\n') - }; - } - }, - { - name: 'Most Traded Items (30d, Unique trades)', - run: async () => { - const items = await getAllTradedItems(true); - return { - content: items - .items() - .sort(sorts.quantity) - .slice(0, 10) - .map((i, index) => `${++index}. ${i[0].name} - Traded ${i[1].toLocaleString()}x times`) - .join('\n') - }; - } - }, - { - name: 'Memory Analysis', - run: async () => { - return { - content: Object.entries(memoryAnalysis()) - .map(i => `${i[0]}: ${i[1]}`) - .join('\n') - }; - } - }, - { - name: 'Slayer Mask Leaderboard', - run: async () => { - let res = ''; - - for (const [maskID, userID] of slayerMaskLeaderboardCache.entries()) { - const mask = slayerMaskHelms.find(i => i.mask.id === maskID); - if (!mask) continue; - res += `${mask.mask.name}: ${userMention(userID)}\n`; - } - - return { - content: res, - allowedMentions: { - users: [] - } - }; - } - }, - { - name: 'Economy Bank', - run: async () => { - const [blowpipeRes, totalGP, result] = await prisma.$transaction([ - prisma.$queryRawUnsafe<{ scales: number; dart: number; qty: number }[]>(`SELECT (blowpipe->>'scales')::int AS scales, (blowpipe->>'dartID')::int AS dart, (blowpipe->>'dartQuantity')::int AS qty -FROM users -WHERE blowpipe iS NOT NULL and (blowpipe->>'dartQuantity')::int != 0;`), - prisma.$queryRawUnsafe<{ sum: number }[]>('SELECT SUM("GP") FROM users;'), - prisma.$queryRawUnsafe<{ banks: ItemBank }[]>(`SELECT - json_object_agg(itemID, itemQTY)::jsonb as banks - from ( - select key as itemID, sum(value::bigint) as itemQTY - from users - cross join json_each_text(bank) - group by key - ) s;`) - ]); - const totalBank: ItemBank = result[0].banks; - const economyBank = new Bank(totalBank); - economyBank.add('Coins', totalGP[0].sum); - - const allPets = await allEquippedPets(); - economyBank.add(allPets); - - for (const { dart, scales, qty } of blowpipeRes) { - economyBank.add("Zulrah's scales", scales); - economyBank.add(dart, qty); - } - sanitizeBank(economyBank); - return { - files: [ - (await makeBankImage({ bank: economyBank })).file, - new AttachmentBuilder(Buffer.from(JSON.stringify(economyBank.bank, null, 4)), { - name: 'bank.json' - }) - ] - }; - } - }, - { - name: 'Equipped Pets', - run: async () => { - return allEquippedPets(); - } - }, - { - name: 'Most Active', - run: async () => { - const res = await prisma.$queryRawUnsafe<{ num: number; username: string }[]>(` -SELECT sum(duration)::int as num, "new_user"."username", user_id -FROM activity -INNER JOIN "new_users" "new_user" on "new_user"."id" = "activity"."user_id"::text -WHERE start_date > now() - interval '2 days' -GROUP BY user_id, "new_user"."username" -ORDER BY num DESC -LIMIT 10; -`); - return { - content: `Most Active Users in past 48h\n${res - .map((i, ind) => `${ind + 1} ${i.username}: ${formatDuration(i.num)}`) - .join('\n')}` - }; - } - }, - { - name: 'Grand Exchange', - run: async () => { - const settings = await GrandExchange.fetchData(); - - const allTx: string[][] = []; - const allTransactions = await prisma.gETransaction.findMany({ - orderBy: { - created_at: 'desc' - } - }); - if (allTransactions.length > 0) { - allTx.push(Object.keys(allTransactions[0])); - for (const tx of allTransactions) { - allTx.push(Object.values(tx).map(i => i.toString())); - } - } - - const allLi: string[][] = []; - const allListings = await prisma.gEListing.findMany({ - orderBy: { - created_at: 'desc' - } - }); - if (allListings.length > 0) { - allLi.push(Object.keys(allListings[0])); - for (const tx of allListings) { - allLi.push(Object.values(tx).map(i => (i === null ? '' : i.toString()))); - } - } - - const buyLimitInterval = GrandExchange.getInterval(); - return { - content: `**Grand Exchange Data** - -The next buy limit reset is at: ${buyLimitInterval.nextResetStr}, it resets every ${formatDuration( - GrandExchange.config.buyLimit.interval - )}. -**Tax Rate:** ${GrandExchange.config.tax.rate()}% -**Tax Cap (per item):** ${toKMB(GrandExchange.config.tax.cap())} -**Total GP Removed From Taxation:** ${settings.totalTax.toLocaleString()} GP -**Total Tax GP G.E Has To Spend on Item Sinks:** ${settings.taxBank.toLocaleString()} GP -`, - files: [ - ( - await makeBankImage({ - bank: await GrandExchange.fetchOwnedBank(), - title: 'Items in the G.E' - }) - ).file, - new AttachmentBuilder(Buffer.from(allTx.map(i => i.join('\t')).join('\n')), { - name: 'transactions.txt' - }), - new AttachmentBuilder(Buffer.from(allLi.map(i => i.join('\t')).join('\n')), { - name: 'listings.txt' - }) - ] - }; - } - }, - { - name: 'Buy GP Sinks', - run: async () => { - const result = await prisma.$queryRawUnsafe<{ item_id: string; total_gp_spent: bigint }[]>(`SELECT - key AS item_id, - sum((cost_gp / total_items) * value::integer) AS total_gp_spent -FROM - buy_command_transaction, - json_each_text(loot_bank), - (SELECT id, sum(value::integer) as total_items FROM buy_command_transaction, json_each_text(loot_bank) GROUP BY id) subquery -WHERE - buy_command_transaction.id = subquery.id -GROUP BY - key -ORDER BY - total_gp_spent DESC -LIMIT - 20; -`); - - return { - content: result - .map( - (row, index) => - `${index + 1}. ${ - getOSItem(Number(row.item_id)).name - } - ${row.total_gp_spent.toLocaleString()} GP` - ) - .join('\n') - }; - } - }, - { - name: 'Sell GP Sources', - run: async () => { - const result = await prisma.$queryRawUnsafe<{ item_id: number; gp: number }[]>(`select item_id, sum(gp_received) as gp -from bot_item_sell -group by item_id -order by gp desc -limit 80; -`); - - const totalGPGivenOut = await prisma.$queryRawUnsafe<{ total_gp_given_out: number }[]>(`select sum(gp_received) as total_gp_given_out -from bot_item_sell;`); - - return { - files: [ - new AttachmentBuilder( - Buffer.from( - result - .map( - (row, index) => - `${index + 1}. ${ - getOSItem(Number(row.item_id)).name - } - ${row.gp.toLocaleString()} GP (${calcWhatPercent( - row.gp, - totalGPGivenOut[0].total_gp_given_out - ).toFixed(1)}%)` - ) - .join('\n') - ), - { name: 'output.txt' } - ) - ] - }; - } - }, - { - name: 'Max G.E Slot users', - run: async () => { - const res = await prisma.$queryRawUnsafe<{ user_id: string; slots_used: number }[]>(` -SELECT user_id, COUNT(*)::int AS slots_used -FROM ge_listing -WHERE cancelled_at IS NULL AND fulfilled_at IS NULL -GROUP BY user_id -HAVING COUNT(*) >= 3 -ORDER BY slots_used DESC; -`); - let usersUsingAllSlots = 0; - for (const row of res) { - const user = await mUserFetch(row.user_id); - const { slots } = await GrandExchange.calculateSlotsOfUser(user); - if (row.slots_used >= slots) usersUsingAllSlots++; - } - return { - content: `There are ${usersUsingAllSlots}x users using all their G.E slots.` - }; - } - } -]; - export const adminCommand: OSBMahojiCommand = { name: 'admin', description: 'Allows you to trade items with other players.', - guildID: SupportServer, + guildID: globalConfig.mainServerID, options: [ { type: ApplicationCommandOptionType.Subcommand, @@ -449,37 +39,6 @@ export const adminCommand: OSBMahojiCommand = { description: 'Sync commands', options: [] }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'item_stats', - description: 'item stats', - options: [{ ...itemOption(), required: true }] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'sync_blacklist', - description: 'Sync blacklist' - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'loot_track', - description: 'Loot track', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'name', - description: 'The name', - autocomplete: async (value: string) => { - const tracks = await prisma.lootTrack.findMany({ select: { id: true } }); - return tracks - .filter(i => (!value ? true : i.id.includes(value))) - .map(i => ({ name: i.id, value: i.id })); - }, - required: true - } - ] - }, - // { type: ApplicationCommandOptionType.Subcommand, name: 'cancel_task', @@ -493,55 +52,6 @@ export const adminCommand: OSBMahojiCommand = { } ] }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'bypass_age', - description: 'Bypass age for a user', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user', - required: true - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'sync_roles', - description: 'Sync roles' - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'badges', - description: 'Manage badges of a user', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'add', - description: 'The badge to add', - required: false, - autocomplete: async () => { - return Object.keys(BadgesEnum).map(i => ({ name: i, value: i })); - } - }, - { - type: ApplicationCommandOptionType.String, - name: 'remove', - description: 'The badge to remove', - required: false, - autocomplete: async () => { - return Object.keys(BadgesEnum).map(i => ({ name: i, value: i })); - } - } - ] - }, { type: ApplicationCommandOptionType.Subcommand, name: 'command', @@ -574,69 +84,6 @@ export const adminCommand: OSBMahojiCommand = { } ] }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'set_price', - description: 'item stats', - options: [ - { ...itemOption(), required: true }, - { - type: ApplicationCommandOptionType.Integer, - name: 'price', - description: 'The price to set', - required: true, - min_value: 1 - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'bitfield', - description: 'Manage bitfield of a user', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'add', - description: 'The bitfield to add', - required: false, - autocomplete: async value => { - return Object.entries(BitFieldData) - .filter(bf => (!value ? true : bf[1].name.toLowerCase().includes(value.toLowerCase()))) - .map(i => ({ name: i[1].name, value: i[0] })); - } - }, - { - type: ApplicationCommandOptionType.String, - name: 'remove', - description: 'The bitfield to remove', - required: false, - autocomplete: async value => { - return Object.entries(BitFieldData) - .filter(bf => (!value ? true : bf[1].name.toLowerCase().includes(value.toLowerCase()))) - .map(i => ({ name: i[1].name, value: i[0] })); - } - } - ] - }, - // { - // type: ApplicationCommandOptionType.Subcommand, - // name: 'ltc', - // description: 'Ltc?', - // options: [ - // { - // ...itemOption(), - // name: 'item', - // description: 'The item.', - // required: false - // } - // ] - // }, { type: ApplicationCommandOptionType.Subcommand, name: 'double_loot', @@ -655,123 +102,30 @@ export const adminCommand: OSBMahojiCommand = { required: false } ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'view', - description: 'View something', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'thing', - description: 'The thing', - required: true, - choices: viewableThings.map(i => ({ name: i.name, value: i.name })) - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'give_items', - description: 'Spawn items for a user', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'items', - description: 'The items to give', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'reason', - description: 'The reason' - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'box_frenzy', - description: 'Box frenzy', - options: [ - { - type: ApplicationCommandOptionType.Integer, - name: 'amount', - description: 'The amount', - required: true, - min_value: 1, - max_value: 500 - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'lamp_frenzy', - description: 'Lamp frenzy', - options: [ - { - type: ApplicationCommandOptionType.Integer, - name: 'amount', - description: 'The amount', - required: true, - min_value: 1, - max_value: 20 - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'lottery_dump', - description: 'lottery_dump', - options: [] } ], run: async ({ options, userID, - interaction, - guildID, - channelID + interaction }: CommandRunOptions<{ reboot?: {}; shut_down?: {}; sync_commands?: {}; - item_stats?: { item: string }; - sync_blacklist?: {}; - loot_track?: { name: string }; cancel_task?: { user: MahojiUserOption }; - sync_roles?: {}; - badges?: { user: MahojiUserOption; add?: string; remove?: string }; - bypass_age?: { user: MahojiUserOption }; command?: { enable?: string; disable?: string }; - set_price?: { item: string; price: number }; - bitfield?: { user: MahojiUserOption; add?: string; remove?: string }; - ltc?: { item?: string }; double_loot?: { reset?: boolean; add?: string }; - view?: { thing: string }; - give_items?: { user: MahojiUserOption; items: string; reason?: string }; - box_frenzy?: { amount: number }; - lamp_frenzy?: { amount: number }; - lottery_dump?: {}; }>) => { await deferInteraction(interaction); + const mods = [ + '794368001856110594', + '604278562320810009', + '425134194436341760', + '157797566833098752', + '507686806624534529' + ]; + if (!mods.includes(userID)) return randArrItem(gifs); - const adminUser = await mUserFetch(userID); - const isOwner = OWNER_IDS.includes(userID.toString()); - const isMod = isOwner || adminUser.bitfield.includes(BitField.isModerator); - - if (!guildID || !isMod || (production && guildID.toString() !== '342983479501389826')) return randArrItem(gifs); - /** - * - * Mod Only Commands - * - */ - if (!isMod) return randArrItem(gifs); if (options.cancel_task) { const { user } = options.cancel_task.user; await cancelTask(user.id); @@ -780,68 +134,6 @@ export const adminCommand: OSBMahojiCommand = { minionActivityCacheDelete(user.id); return 'Done.'; } - if (options.sync_roles) { - // try { - // const result = await runRolesTask(); - // if (result.length < 2000) return result; - // return { - // content: 'The result was too big! Check the file.', - // files: [new AttachmentBuilder(Buffer.from(result), { name: 'roles.txt' })] - // }; - // } catch (err: any) { - // logError(err); - // return `Failed to run roles task. ${err.message}`; - // } - return 'The roles task is disabled for now.'; - } - - if (options.badges) { - if ((!options.badges.remove && !options.badges.add) || (options.badges.add && options.badges.remove)) { - return Object.entries(badges) - .map(entry => `**${entry[1]}:** ${entry[0]}`) - .join('\n'); - } - const badgeInput = options.badges.remove ?? options.badges.add; - const action: 'add' | 'remove' = !options.badges.remove ? 'add' : 'remove'; - const badge: [string, number] | undefined = Object.entries(BadgesEnum).find(i => i[0] === badgeInput); - if (!badge) return 'Invalid badge.'; - const [badgeName, badgeID] = badge; - - const userToUpdateBadges = await mahojiUsersSettingsFetch(options.badges.user.user.id, { - badges: true, - id: true - }); - let newBadges = [...userToUpdateBadges.badges]; - - if (action === 'add') { - if (newBadges.includes(badgeID)) return "Already has this badge, so can't add."; - newBadges.push(badgeID); - } else { - if (!newBadges.includes(badgeID)) return "Doesn't have this badge, so can't remove."; - newBadges = newBadges.filter(i => i !== badgeID); - } - - await mahojiUserSettingsUpdate(userToUpdateBadges.id, { - badges: uniqueArr(newBadges) - }); - - return `${action === 'add' ? 'Added' : 'Removed'} ${badgeName} ${badges[badgeID]} badge to ${ - options.badges.user.user.username - }.`; - } - - if (options.bypass_age) { - const input = await mahojiUsersSettingsFetch(options.bypass_age.user.user.id, { bitfield: true, id: true }); - if (input.bitfield.includes(BitField.BypassAgeRestriction)) { - return 'This user is already bypassed.'; - } - await mahojiUserSettingsUpdate(input.id, { - bitfield: { - push: BitField.BypassAgeRestriction - } - }); - return `Bypassed age restriction for ${options.bypass_age.user.user.username}.`; - } if (options.command) { const { disable } = options.command; @@ -890,78 +182,14 @@ export const adminCommand: OSBMahojiCommand = { } return 'Invalid.'; } - if (options.set_price) { - const item = getItem(options.set_price.item); - if (!item) return 'Invalid item.'; - const { price } = options.set_price; - if (!price || price < 1 || price > 1_000_000_000) return 'Invalid price.'; - await handleMahojiConfirmation( - interaction, - `Are you sure you want to set the price of \`${item.name}\`(ID: ${item.id}, Wiki: ${ - item.wiki_url - }) to \`${price.toLocaleString()}\`?` - ); - const settings = await mahojiClientSettingsFetch({ custom_prices: true }); - const current = settings.custom_prices as ItemBank; - const newPrices = { ...current, [item.id]: price }; - await mahojiClientSettingsUpdate({ - custom_prices: newPrices - }); - await syncCustomPrices(); - return `Set the price of \`${item.name}\` to \`${price.toLocaleString()}\`.`; - } - - if (options.bitfield) { - const bitInput = options.bitfield.add ?? options.bitfield.remove; - const user = await mUserFetch(options.bitfield.user.user.id); - const bitEntry = Object.entries(BitFieldData).find(i => i[0] === bitInput); - const action: 'add' | 'remove' = options.bitfield.add ? 'add' : 'remove'; - if (!bitEntry) { - return Object.entries(BitFieldData) - .map(entry => `**${entry[0]}:** ${entry[1]?.name}`) - .join('\n'); - } - const bit = Number.parseInt(bitEntry[0]); - - if ( - !bit || - !(BitFieldData as any)[bit] || - [7, 8].includes(bit) || - (action !== 'add' && action !== 'remove') - ) { - return 'Invalid bitfield.'; - } - - let newBits = [...user.bitfield]; - - if (action === 'add') { - if (newBits.includes(bit)) { - return "Already has this bit, so can't add."; - } - newBits.push(bit); - } else { - if (!newBits.includes(bit)) { - return "Doesn't have this bit, so can't remove."; - } - newBits = newBits.filter(i => i !== bit); - } - - await user.update({ - bitfield: uniqueArr(newBits) - }); - return `${action === 'add' ? 'Added' : 'Removed'} '${(BitFieldData as any)[bit].name}' bit to ${ - options.bitfield.user.user.username - }.`; - } if (options.reboot) { globalClient.isShuttingDown = true; - await economyLog('Flushing economy log due to reboot', true); await interactionReply(interaction, { content: 'https://media.discordapp.net/attachments/357422607982919680/1004657720722464880/freeze.gif' }); await sleep(Time.Second * 20); - await sendToChannelID(Channel.GeneralChannel, { + await sendToChannelID(globalConfig.generalChannelID, { content: `I am shutting down! Goodbye :( ${META_CONSTANTS.RENDERED_STR}` @@ -971,13 +199,12 @@ ${META_CONSTANTS.RENDERED_STR}` if (options.shut_down) { debugLog('SHUTTING DOWN'); globalClient.isShuttingDown = true; - const timer = production ? Time.Second * 30 : Time.Second * 5; + const timer = Time.Second * 30; await interactionReply(interaction, { content: `Shutting down in ${dateFm(new Date(Date.now() + timer))}.` }); - await economyLog('Flushing economy log due to shutdown', true); - await Promise.all([sleep(timer), GrandExchange.queue.onEmpty()]); - await sendToChannelID(Channel.GeneralChannel, { + await sleep(timer); + await sendToChannelID(globalConfig.generalChannelID, { content: `I am shutting down! Goodbye :( ${META_CONSTANTS.RENDERED_STR}` @@ -985,53 +212,17 @@ ${META_CONSTANTS.RENDERED_STR}` process.exit(0); } - if (options.sync_blacklist) { - await syncBlacklists(); - return `Users Blacklisted: ${BLACKLISTED_USERS.size} -Guilds Blacklisted: ${BLACKLISTED_GUILDS.size}`; - } - - /** - * - * Admin Only Commands - * - */ - if (!isOwner && !ADMIN_IDS.includes(userID)) { - return randArrItem(gifs); - } - if (options.sync_commands) { - const global = Boolean(production); + const global = Boolean(globalConfig.isProduction); const totalCommands = Array.from(globalClient.mahojiClient.commands.values()); const globalCommands = totalCommands.filter(i => !i.guildID); const guildCommands = totalCommands.filter(i => Boolean(i.guildID)); - if (global) { - await bulkUpdateCommands({ - client: globalClient.mahojiClient, - commands: globalCommands, - guildID: null - }); - await bulkUpdateCommands({ - client: globalClient.mahojiClient, - commands: guildCommands, - guildID: guildID.toString() - }); - } else { - await bulkUpdateCommands({ - client: globalClient.mahojiClient, - commands: totalCommands, - guildID: guildID.toString() - }); - } - // If not in production, remove all global commands. - if (!production) { - await bulkUpdateCommands({ - client: globalClient.mahojiClient, - commands: [], - guildID: null - }); - } + await bulkUpdateCommands({ + client: globalClient.mahojiClient, + commands: globalCommands, + guildID: null + }); return `Synced commands ${global ? 'globally' : 'locally'}. ${totalCommands.length} Total commands @@ -1039,237 +230,6 @@ ${globalCommands.length} Global commands ${guildCommands.length} Guild commands`; } - if (options.view) { - const thing = viewableThings.find(i => i.name === options.view?.thing); - if (!thing) return 'Invalid'; - const clientSettings = await mahojiClientSettingsFetch(); - const res = await thing.run(clientSettings); - if (!(res instanceof Bank)) return res; - const image = await makeBankImage({ - bank: res, - title: thing.name, - flags: { sort: thing.name === 'All Equipped Items' ? 'name' : (undefined as any) } - }); - return { files: [image.file] }; - } - - if (options.give_items) { - const items = parseBank({ inputStr: options.give_items.items, noDuplicateItems: true }); - const user = await mUserFetch(options.give_items.user.user.id); - await handleMahojiConfirmation( - interaction, - `Are you sure you want to give ${items} to ${user.usernameOrMention}?` - ); - await sendToChannelID(Channel.BotLogs, { - content: `${adminUser.logName} sent \`${items}\` to ${user.logName} for ${ - options.give_items.reason ?? 'No reason' - }` - }); - - await user.addItemsToBank({ items, collectionLog: false }); - return `Gave ${items} to ${user.mention}`; - } - - /** - * - * Owner Only Commands - * - */ - if (!isOwner) { - return randArrItem(gifs); - } - - if (options.item_stats) { - const item = getItem(options.item_stats.item); - if (!item) return 'Invalid item.'; - const isIron = false; - const ownedResult: any = await prisma.$queryRawUnsafe(`SELECT SUM((bank->>'${item.id}')::int) as qty -FROM users -WHERE bank->>'${item.id}' IS NOT NULL;`); - return `There are ${ownedResult[0].qty.toLocaleString()} ${item.name} owned by everyone. -There are ${await countUsersWithItemInCl(item.id, isIron)} ${isIron ? 'ironmen' : 'people'} with atleast 1 ${ - item.name - } in their collection log.`; - } - - if (options.loot_track) { - const loot = await prisma.lootTrack.findFirst({ - where: { - id: options.loot_track.name - } - }); - if (!loot) return 'Invalid'; - - const durationMillis = loot.total_duration * Time.Minute; - - const arr = [ - ['Cost', new Bank(loot.cost as ItemBank)], - ['Loot', new Bank(loot.loot as ItemBank)] - ] as const; - - let content = `${loot.id} ${formatDuration(loot.total_duration * Time.Minute)} KC${loot.total_kc}`; - const files = []; - for (const [name, bank] of arr) { - content += `\n${convertBankToPerHourStats(bank, durationMillis).join(', ')}`; - files.push((await makeBankImage({ bank, title: name })).file); - } - return { content, files }; - } - if (options.double_loot) { - if (options.double_loot.reset) { - await mahojiClientSettingsUpdate({ - double_loot_finish_time: 0 - }); - await syncDoubleLoot(); - return 'Reset the double loot timer.'; - } - if (options.double_loot.add) { - const duration = new Duration(options.double_loot.add); - const ms = duration.offset; - await handleMahojiConfirmation(interaction, `Add ${formatDuration(ms)} to double loot timer?`); - addToDoubleLootTimer(ms, 'added by RP command'); - return `Added ${formatDuration(ms)} to the double loot timer.`; - } - } - if (options.ltc) { - let str = ''; - const results = await prisma.lootTrack.findMany(); - - if (options.ltc.item) { - str += `${['id', 'total_of_item', 'item_per_kc', 'per_hour'].join('\t')}\n`; - const item = getOSItem(options.ltc.item); - - for (const res of results) { - const loot = new Bank(res.loot as ItemBank); - if (!loot.has(item.id)) continue; - const qty = loot.amount(item.id); - str += `${[ - res.id, - qty, - qty / res.total_kc, - calcPerHour(qty, res.total_duration * Time.Minute) - ].join('\t')}\n`; - } - - return { - files: [{ attachment: Buffer.from(str), name: `${cleanString(item.name)}.txt` }] - }; - } - - str += `${['id', 'cost_h', 'cost', 'loot_h', 'loot', 'per_hour_h', 'per_hour', 'ratio'].join('\t')}\n`; - for (const res of results) { - if (!res.total_duration || !res.total_kc) continue; - if (Object.keys({ ...(res.cost as ItemBank), ...(res.loot as ItemBank) }).length === 0) continue; - const cost = new Bank(res.cost as ItemBank); - const loot = new Bank(res.loot as ItemBank); - sanitizeBank(cost); - sanitizeBank(loot); - const marketValueCost = Math.round(cost.value()); - const marketValueLoot = Math.round(loot.value()); - const ratio = marketValueLoot / marketValueCost; - - if (!marketValueCost || !marketValueLoot || ratio === Number.POSITIVE_INFINITY) continue; - - str += `${[ - res.id, - toKMB(marketValueCost), - marketValueCost, - toKMB(marketValueLoot), - marketValueLoot, - toKMB(calcPerHour(marketValueLoot, res.total_duration * Time.Minute)), - calcPerHour(marketValueLoot, res.total_duration * Time.Minute), - ratio - ].join('\t')}\n`; - } - - return { - files: [{ attachment: Buffer.from(str), name: 'output.txt' }] - }; - } - - if (options.lottery_dump) { - const res = await getLotteryBank(); - for (const user of res.users) { - if (!globalClient.users.cache.has(user.id)) { - await globalClient.users.fetch(user.id); - } - } - const taxedBank = new Bank(); - for (const [item, qty] of res.totalLoot.items()) { - if (item.id === COINS_ID) { - taxedBank.add('Coins', qty); - continue; - } - const fivePercent = Math.ceil(calcPercentOfNum(5, qty)); - taxedBank.add(item, Math.max(fivePercent, 1)); - } - - const actualLootBank = res.totalLoot.clone().remove(taxedBank); - - return { - files: [ - { - name: 'lottery.txt', - attachment: Buffer.from( - JSON.stringify( - res.users.map(i => [globalClient.users.cache.get(i.id)?.username ?? i.id, i.tickets]) - ) - ) - }, - { - name: 'totalloot.json', - attachment: Buffer.from(JSON.stringify(actualLootBank.bank)) - }, - { - name: 'taxedbank.json', - attachment: Buffer.from(JSON.stringify(taxedBank.bank)) - }, - (await makeBankImage({ bank: taxedBank, title: 'Taxed Bank' })).file, - (await makeBankImage({ bank: actualLootBank, title: 'Actual Loot' })).file - ] - }; - } - - if (options.box_frenzy) { - boxFrenzy(channelID, 'Box Frenzy started!', options.box_frenzy.amount); - return null; - } - - if (options.lamp_frenzy) { - const channel = globalClient.channels.cache.get(channelID)! as TextChannel; - const wonCache = new Set(); - - const message = await channel.send( - `Giving out ${options.lamp_frenzy.amount} lamps! To win, just say something.` - ); - - const totalLoot = new Bank(); - - await channel - .awaitMessages({ - time: 20_000, - errors: ['time'], - filter: async (_msg: Message) => { - if (!roll(3)) return false; - if (wonCache.has(_msg.author.id)) return false; - if (wonCache.size >= options.lamp_frenzy!.amount) return false; - wonCache.add(_msg.author.id); - - const loot = LampTable.roll(); - totalLoot.add(loot); - - const user = await mUserFetch(_msg.author.id); - await user.addItemsToBank({ items: loot, collectionLog: true }); - _msg.channel.send(`${_msg.author} won ${loot}.`); - return false; - } - }) - .then(() => message.edit({ content: 'Finished!', files: [] })) - .catch(noOp); - - await channel.send(`Spawnlamp frenzy finished! ${totalLoot} was given out.`); - } - return 'Invalid command.'; } }; diff --git a/src/mahoji/commands/allCommands.ts b/src/mahoji/commands/allCommands.ts index 86043d7994..4f63f21257 100644 --- a/src/mahoji/commands/allCommands.ts +++ b/src/mahoji/commands/allCommands.ts @@ -1,21 +1,16 @@ -import { production } from '../../config'; +import { writeFileSync } from 'node:fs'; +import { globalConfig } from '../../lib/constants'; import type { OSBMahojiCommand } from '../lib/util'; import { activitiesCommand } from './activities'; import { adminCommand } from './admin'; -import { askCommand } from './ask'; import { bankCommand } from './bank'; -import { bingoCommand } from './bingo'; -import { bossrecordCommand } from './bossrecords'; import { bsCommand } from './bs'; import { bsoMinigamesCommand } from './bsominigames'; import { buildCommand } from './build'; import { buyCommand } from './buy'; import { caCommand } from './ca'; -import { casketCommand } from './casket'; -import { chooseCommand } from './choose'; import { chopCommand } from './chop'; import { collectionLogCommand } from './cl'; -import { claimCommand } from './claim'; import { clueCommand } from './clue'; import { completionCommand } from './completion'; import { configCommand } from './config'; @@ -26,38 +21,20 @@ import { dataCommand } from './data'; import { dgCommand } from './dg'; import { divinationCommand } from './divination'; import { dropCommand } from './drop'; -import { dropRatesCommand } from './droprates'; -import { fakeCommand } from './fake'; -import { fakepmCommand } from './fakepm'; import { farmingCommand } from './farming'; -import { finishCommand } from './finish'; import { fishCommand } from './fish'; import { fletchCommand } from './fletch'; -import { gambleCommand } from './gamble'; -import { geCommand } from './ge'; import { gearCommand } from './gear'; -import { gearPresetsCommand } from './gearpresets'; -import { giftCommand } from './gift'; -import { giveawayCommand } from './giveaway'; import { gpCommand } from './gp'; -import { helpCommand } from './help'; import { huntCommand } from './hunt'; -import { icCommand } from './ic'; import { inventionCommand } from './invention'; -import { inviteCommand } from './invite'; import { minionKCommand } from './k'; -import { kcCommand } from './kc'; import { kibbleCommand } from './kibble'; -import { killCommand } from './kill'; import { lapsCommand } from './laps'; import { leaderboardCommand } from './leaderboard'; import { bsoLeaguesCommand } from './leagues'; import { lightCommand } from './light'; -import { lootCommand } from './loot'; -import { lotteryCommand } from './lottery'; import { mCommand } from './m'; -import { massCommand } from './mass'; -import { megaDuckCommand } from './megaduck'; import { mineCommand } from './mine'; import { minigamesCommand } from './minigames'; import { minionCommand } from './minion'; @@ -66,49 +43,39 @@ import { nurseryCommand } from './nursery'; import { offerCommand } from './offer'; import { openCommand } from './open'; import { paintCommand } from './paint'; -import { patreonCommand } from './patreon'; -import { payCommand } from './pay'; import { pohCommand } from './poh'; -import { pollCommand } from './poll'; -import { priceCommand } from './price'; import { raidCommand } from './raid'; -import { ratesCommand } from './rates'; -import { redeemCommand } from './redeem'; -import { rollCommand } from './roll'; +import { randomizerCommand } from './randomizer'; + +import { ApplicationCommandOptionType } from 'discord.js'; +import { gambleCommand } from './gamble'; +import { relicCommand } from './relic'; import { rpCommand } from './rp'; import { runecraftCommand } from './runecraft'; import { sacrificeCommand } from './sacrifice'; import { sellCommand } from './sell'; -import { simulateCommand } from './simulate'; import { slayerCommand } from './slayer'; import { smeltingCommand } from './smelt'; import { smithCommand } from './smith'; import { stealCommand } from './steal'; import { tamesCommand } from './tames'; -import { testerShopCommand } from './testershop'; import { testPotatoCommand } from './testpotato'; import { tksCommand } from './tokkulshop'; import { toolsCommand } from './tools'; -import { tradeCommand } from './trade'; -import { triviaCommand } from './trivia'; import { mahojiUseCommand } from './use'; export const allCommands: OSBMahojiCommand[] = [ adminCommand, - askCommand, bsCommand, buildCommand, buyCommand, caCommand, - chooseCommand, chopCommand, cookCommand, clueCommand, configCommand, - claimCommand, mCommand, gpCommand, - payCommand, craftCommand, fishCommand, farmingCommand, @@ -116,78 +83,50 @@ export const allCommands: OSBMahojiCommand[] = [ createCommand, activitiesCommand, dataCommand, - fakeCommand, - fakepmCommand, fletchCommand, - gambleCommand, gearCommand, - giveawayCommand, - helpCommand, huntCommand, - giftCommand, - inviteCommand, - kcCommand, minionKCommand, lapsCommand, leaderboardCommand, lightCommand, mineCommand, - massCommand, minigamesCommand, minionCommand, - simulateCommand, sellCommand, sacrificeCommand, - rollCommand, runecraftCommand, raidCommand, - pollCommand, pohCommand, - priceCommand, openCommand, offerCommand, mixCommand, - lootCommand, smeltingCommand, slayerCommand, - redeemCommand, - patreonCommand, smithCommand, stealCommand, - tradeCommand, - triviaCommand, toolsCommand, tksCommand, mahojiUseCommand, - bingoCommand, bankCommand, - bossrecordCommand, - casketCommand, - finishCommand, - killCommand, - geCommand, rpCommand, collectionLogCommand, - gearPresetsCommand, bsoMinigamesCommand, completionCommand, dgCommand, divinationCommand, - dropRatesCommand, - icCommand, inventionCommand, kibbleCommand, - lotteryCommand, - megaDuckCommand, nurseryCommand, paintCommand, - ratesCommand, tamesCommand, - testerShopCommand, - bsoLeaguesCommand + bsoLeaguesCommand, + randomizerCommand, + gambleCommand, + relicCommand ]; -if (!production && testPotatoCommand) { +if (!globalConfig.isProduction && testPotatoCommand) { allCommands.push(testPotatoCommand); } @@ -198,3 +137,23 @@ for (const cmd of allCommands) { } names.add(cmd.name); } + +let str2 = ''; +for (const cmd of allCommands) { + if (['testpotato', 'admin', 'rp', 'lb', 'cl'].includes(cmd.name)) continue; + str2 += `------ ${cmd.name}------\n`; + for (const a of cmd.options) { + if (a.type === ApplicationCommandOptionType.SubcommandGroup) { + str2 += `${cmd.name} ${a.name}\n`; + for (const b of a.options!) { + str2 += `${cmd.name} ${a.name} ${b.name}\n`; + } + } else if (a.type === ApplicationCommandOptionType.Subcommand) { + str2 += `${cmd.name} ${a.name}\n`; + } else { + str2 += `${cmd.name} ${a.name}\n`; + } + } + str2 += '\n\n'; +} +writeFileSync('commands.txt', str2); diff --git a/src/mahoji/commands/ask.ts b/src/mahoji/commands/ask.ts deleted file mode 100644 index 464306e37b..0000000000 --- a/src/mahoji/commands/ask.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { randArrItem } from 'e'; - -import type { OSBMahojiCommand } from '../lib/util'; - -export const askCommand: OSBMahojiCommand = { - name: 'ask', - description: 'Ask a yes/no question to the bot and receive an answer.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'question', - description: 'The question you want to ask.', - required: true - } - ], - run: async ({ user, options }: CommandRunOptions<{ question: string }>) => { - const answer = randArrItem([ - 'Yes.', - 'Definitely.', - 'Obviously yes.', - 'Without a doubt.', - 'I think so.', - '100%.', - - "It's possible.", - 'Maybe.', - - 'No.', - 'No chance.', - 'Unlikely.', - '0 chance.', - 'No way.' - ]); - return `${user.username} asked: *${options.question}*, and my answer is **${answer}**.`; - } -}; diff --git a/src/mahoji/commands/bingo.ts b/src/mahoji/commands/bingo.ts deleted file mode 100644 index 5ebfbfb68a..0000000000 --- a/src/mahoji/commands/bingo.ts +++ /dev/null @@ -1,1036 +0,0 @@ -import { dateFm, formatOrdinal, mentionCommand, stringMatches, truncateString } from '@oldschoolgg/toolkit'; -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import type { CommandResponse } from '@oldschoolgg/toolkit'; -import type { MahojiUserOption } from '@oldschoolgg/toolkit'; -import type { Prisma } from '@prisma/client'; -import type { ChatInputCommandInteraction, User } from 'discord.js'; -import { bold, userMention } from 'discord.js'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { Time, chunk, noOp, notEmpty, uniqueArr } from 'e'; -import { Bank } from 'oldschooljs'; -import type { ItemBank } from 'oldschooljs/dist/meta/types'; - -import { production } from '../../config'; -import { BLACKLISTED_USERS } from '../../lib/blacklists'; -import { clImageGenerator } from '../../lib/collectionLogTask'; -import { BOT_TYPE, Emoji } from '../../lib/constants'; - -import { - channelIsSendable, - getUsername, - getUsernameSync, - isValidDiscordSnowflake, - isValidNickname, - md5sum, - toKMB -} from '../../lib/util'; -import { getItem } from '../../lib/util/getOSItem'; -import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import { parseBank } from '../../lib/util/parseStringBank'; -import { BingoManager, BingoTrophies } from '../lib/bingo/BingoManager'; -import type { StoredBingoTile } from '../lib/bingo/bingoUtil'; -import { generateTileName, getAllTileItems, isGlobalTile } from '../lib/bingo/bingoUtil'; -import { globalBingoTiles } from '../lib/bingo/globalTiles'; -import type { OSBMahojiCommand } from '../lib/util'; -import { doMenu, getPos } from './leaderboard'; - -const bingoAutocomplete = async (value: string, user: User) => { - const bingos = await fetchBingosThatUserIsInvolvedIn(user.id); - return bingos - .map(i => new BingoManager(i)) - .filter(bingo => (!value ? true : bingo.id.toString() === value)) - .map(bingo => ({ name: bingo.title, value: bingo.id.toString() })); -}; - -type MakeTeamOptions = { - second_user?: MahojiUserOption; - third_user?: MahojiUserOption; - fourth_user?: MahojiUserOption; - fifth_user?: MahojiUserOption; -} & { - bingo: string; -}; - -export async function fetchBingosThatUserIsInvolvedIn(userID: string) { - const bingos = await prisma.bingo.findMany({ - where: { - OR: [ - { - bingo_participant: { - some: { - user_id: userID - } - } - }, - { - creator_id: userID - }, - { - organizers: { - has: userID - } - } - ] - } - }); - - return bingos; -} - -async function bingoTeamLeaderboard( - interaction: ChatInputCommandInteraction, - user: MUser, - channelID: string, - bingo: BingoManager -): CommandResponse { - const { teams } = await bingo.fetchAllParticipants(); - - doMenu( - interaction, - user, - channelID, - chunk(teams, 10).map((subList, i) => - subList - .map( - (team, j) => - `${getPos(i, j)}** ${`${team.trophy?.emoji} ` ?? ''}${team.participants - .map(pt => getUsernameSync(pt.user_id)) - .join(', ')}:** ${team.tilesCompletedCount.toLocaleString()}` - ) - .join('\n') - ), - 'Bingo Team Leaderboard' - ); - return { - ephemeral: true, - content: 'Loading Bingo Leaderboard...' - }; -} - -async function makeTeamCommand( - interaction: ChatInputCommandInteraction, - bingo: BingoManager, - creatorUser: MUser, - options: MakeTeamOptions -) { - if (bingo.isActive()) { - return 'You cannot make a Bingo team, because the bingo has already started!'; - } - const allUsers = await Promise.all( - uniqueArr( - [ - creatorUser.id, - options.second_user?.user.id, - options.third_user?.user.id, - options.fourth_user?.user.id, - options.fifth_user?.user.id - ].filter(notEmpty) - ).map(id => mUserFetch(id)) - ); - if (allUsers.length !== bingo.teamSize) return `Your team must have only ${bingo.teamSize} users, no more or less.`; - if (allUsers.some(u => BLACKLISTED_USERS.has(u.id))) return 'You cannot have blacklisted users on your team.'; - - await handleMahojiConfirmation( - interaction, - `${allUsers.map(i => userMention(i.id)).join(', ')} - Do you want to join a bingo team with eachother? All ${ - bingo.teamSize - } users need to confirm. ${bold(`You will be charged ${toKMB(bingo.ticketPrice)}`)}`, - allUsers.map(i => i.id) - ); - - for (const user of allUsers) { - const teamWithUser = await bingo.findTeamWithUser(user.id); - if (teamWithUser) { - return `${user} is already in a team.`; - } - if (!user.isIronman && user.GP < bingo.ticketPrice) { - return `${user} doesn't have enough GP to buy a ticket! They need ${toKMB( - bingo.ticketPrice - )} GP, but only have ${toKMB(user.GP)} GP.`; - } - } - await prisma.$transaction([ - prisma.user.updateMany({ - where: { - id: { - in: allUsers.filter(i => !i.isIronman).map(i => i.id) - } - }, - data: { - GP: { - decrement: bingo.ticketPrice - } - } - }), - prisma.bingoTeam.create({ - data: { - bingo_id: bingo.id, - users: { - createMany: { - data: allUsers.map(u => ({ - user_id: u.id, - tickets_bought: 1, - bingo_id: bingo.id - })) - } - } - } - }) - ]); - - return "Successfully created a bingo team! Have fun. You can leave the team before the bingo starts if you'd like, but doing so will delete the team, causing all users to have to join or make a new team."; -} - -async function leaveTeamCommand(interaction: ChatInputCommandInteraction, bingo: BingoManager) { - if (bingo.isActive()) return "You can't leave a bingo team after bingo has started."; - if (bingo.wasFinalized) { - return "You can't leave a bingo team after bingo has ended."; - } - - const team = await bingo.findTeamWithUser(interaction.user.id); - if (!team) return "You're not in a team for this bingo."; - - await handleMahojiConfirmation( - interaction, - 'Are you sure you want to leave your team? Doing so will delete/disband the team, and all of you will need to join a new team.' - ); - - const allUsers = await Promise.all(team.participants.map(i => i.user).map(id => mUserFetch(id.id))); - await prisma.$transaction([ - prisma.user.updateMany({ - where: { - id: { - in: allUsers.filter(i => !i.isIronman).map(i => i.id) - } - }, - data: { - GP: { - increment: bingo.ticketPrice - } - } - }), - prisma.bingoTeam.delete({ - where: { - id: team.team_id - } - }) - ]); - return `${team.participants - .map(pt => userMention(pt.user_id)) - .join( - ', ' - )} Your Bingo team was deleted, you no longer are in a team. All non-ironmen were refunded their ticket price of ${bingo.ticketPrice.toLocaleString()} GP.`; -} - -function parseTileAddInput(input: string): StoredBingoTile | null { - const plus = input.includes('+'); - const pipe = input.includes('|'); - - if (plus && pipe) { - return null; - } - - if (!plus && !pipe) { - return { bank: parseBank({ inputStr: input, noDuplicateItems: true }) }.bank; - } - - const delimiter = plus ? '+' : '|'; - const arr = input.split(delimiter); - const items = []; - - for (const name of arr) { - const item = getItem(name); - if (item) { - items.push(item); - } - } - if (items.length === 0) { - return null; - } - - return delimiter === '+' ? { allOf: items.map(i => i.id) } : { oneOf: items.map(i => i.id) }; -} - -async function getBingoFromUserInput(input: string) { - const where = Number.isNaN(Number(input)) - ? { - title: input - } - : { - id: Number(input) - }; - const bingo = await prisma.bingo.findFirst({ - where - }); - return bingo; -} - -export const bingoCommand: OSBMahojiCommand = { - name: 'bingo', - description: 'Bingo!', - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'make_team', - description: 'Make your own bingo team, with other players.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'bingo', - description: 'The bingo.', - required: true, - autocomplete: async (value: string, _: User, member) => { - if (!member || !member.guild) return []; - const bingos = await prisma.bingo.findMany({ - where: { - OR: [ - { - guild_id: member.guild.id, - was_finalized: false - }, - { - is_global: true, - was_finalized: false - } - ] - } - }); - return bingos - .map(i => new BingoManager(i)) - .filter(bingo => !bingo.isActive()) - .filter(bingo => (!value ? true : bingo.id.toString() === value)) - .map(bingo => ({ name: bingo.title, value: bingo.id.toString() })); - } - }, - { - type: ApplicationCommandOptionType.User, - name: 'second_user', - description: 'The second user.', - required: false - }, - { - type: ApplicationCommandOptionType.User, - name: 'third_user', - description: 'The third user.', - required: false - }, - { - type: ApplicationCommandOptionType.User, - name: 'fourth_user', - description: 'The fourth user.', - required: false - }, - { - type: ApplicationCommandOptionType.User, - name: 'fifth_user', - description: 'The fifth user.', - required: false - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'leave_team', - description: 'Leave your bingo team.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'bingo', - description: 'The bingo.', - required: true, - autocomplete: async (value: string, user: User) => { - const bingos = await prisma.bingo.findMany({ - where: { - OR: [ - { - bingo_participant: { - some: { - user_id: user.id - } - } - } - ] - } - }); - return bingos - .map(i => new BingoManager(i)) - .filter(bingo => (!value ? true : bingo.id.toString() === value)) - .map(bingo => ({ name: bingo.title, value: bingo.id.toString() })); - } - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'view', - description: 'View bingo info.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'bingo', - description: 'The bingo.', - required: true, - autocomplete: bingoAutocomplete - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'leaderboard', - description: 'View the bingo leaderboard.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'bingo', - description: 'The bingo to check the leaderboard of.', - required: true, - autocomplete: bingoAutocomplete - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'create_bingo', - description: 'Create a bingo.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'title', - description: 'The title of the bingo.', - required: true - }, - { - type: ApplicationCommandOptionType.Integer, - name: 'duration_days', - description: 'The duration of the bingo in days.', - required: true, - min_value: 1, - max_value: 31 - }, - { - type: ApplicationCommandOptionType.Integer, - name: 'start_date_unix_seconds', - description: 'The start date in unix seconds.', - required: true - }, - { - type: ApplicationCommandOptionType.Integer, - name: 'ticket_price', - description: 'The ticket price.', - required: true, - min_value: 1 - }, - { - type: ApplicationCommandOptionType.Integer, - name: 'team_size', - description: 'The team size.', - required: true, - min_value: 1, - max_value: 5 - }, - { - type: ApplicationCommandOptionType.String, - name: 'notifications_channel_id', - description: 'The channel to send notifications to.', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'organizers', - description: 'The organizers (user IDs separated by comma).', - required: true - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'manage_bingo', - description: 'Manage your bingo.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'bingo', - description: 'The bingo.', - required: true, - autocomplete: async (value: string, user: User) => { - const bingos = await fetchBingosThatUserIsInvolvedIn(user.id); - return bingos - .map(i => new BingoManager(i)) - .filter(b => b.creatorID === user.id || b.organizers.includes(user.id)) - .filter(bingo => (!value ? true : bingo.id.toString() === value)) - .map(bingo => ({ name: bingo.title, value: bingo.id.toString() })); - } - }, - { - type: ApplicationCommandOptionType.String, - name: 'add_tile', - description: 'Add a tile to your bingo.', - required: false, - autocomplete: async (value: string) => { - return globalBingoTiles - .filter(t => (!value ? true : t.name.toLowerCase().includes(value.toLowerCase()))) - .map(t => ({ - name: t.name, - value: t.id - })); - } - }, - { - type: ApplicationCommandOptionType.Boolean, - name: 'csv_dump', - description: 'Dump a csv file with all the bingo results.', - required: false - }, - { - type: ApplicationCommandOptionType.String, - name: 'remove_tile', - description: 'Remove a tile from your bingo.', - required: false, - autocomplete: async (value: string, user: User) => { - const bingos = await prisma.bingo.findMany({ - where: { - OR: [ - { - creator_id: user.id - }, - { - organizers: { - has: user.id - } - } - ], - was_finalized: false, - start_date: { - gt: new Date() - } - } - }); - return bingos - .flatMap(b => new BingoManager(b).bingoTiles) - .filter(b => b.name.toLowerCase().includes(value.toLowerCase())) - .map(b => ({ - name: truncateString(b.name, 100), - value: b.id ? b.id.toString() : md5sum(b.name) - })); - } - }, - { - type: ApplicationCommandOptionType.Boolean, - name: 'finalize', - description: 'Finalize/end the bingo.', - required: false - }, - { - type: ApplicationCommandOptionType.Integer, - name: 'add_extra_gp', - description: 'Add extra gp to the prize.', - required: false, - min_value: 1_000_000 - }, - { - type: ApplicationCommandOptionType.Boolean, - name: 'trophy_handout', - description: 'Hand out trophies.', - required: false - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'items', - description: 'View your progress/items.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'bingo', - description: 'The bingo to check your items of.', - required: true, - autocomplete: async (value: string, user: User) => { - const bingos = await fetchBingosThatUserIsInvolvedIn(user.id); - return bingos - .map(i => new BingoManager(i)) - .filter(b => b.isActive()) - .filter(bingo => (!value ? true : bingo.id.toString() === value)) - .map(bingo => ({ name: bingo.title, value: bingo.id.toString() })); - } - } - ] - } - ], - run: async ({ - userID, - options, - interaction, - channelID - }: CommandRunOptions<{ - items?: { - bingo: string; - }; - leaderboard?: { - bingo: string; - }; - make_team?: MakeTeamOptions; - leave_team?: { - bingo: string; - }; - create_bingo?: { - title: string; - duration_days: number; - start_date_unix_seconds: number; - ticket_price: number; - team_size: number; - notifications_channel_id: string; - organizers: string; - }; - manage_bingo?: { - bingo: string; - csv_dump?: boolean; - add_tile?: string; - remove_tile?: string; - finalize?: boolean; - add_extra_gp?: number; - trophy_handout?: boolean; - }; - view?: { - bingo: string; - }; - }>) => { - const user = await mUserFetch(userID); - - if (options.items) { - const bingoID = Number(options.items.bingo); - if (Number.isNaN(bingoID)) { - return 'Invalid bingo.'; - } - const bingoParticipant = await prisma.bingoParticipant.findFirst({ - where: { - bingo_id: Number(options.items.bingo), - user_id: user.id - }, - include: { - bingo: true - } - }); - - if (!bingoParticipant) return 'Invalid bingo.'; - const bingo = new BingoManager(bingoParticipant.bingo); - const teamProgress = (await bingo.determineProgressOfTeam(bingoParticipant.bingo_team_id))!; - - const clItems = []; - - for (const tile of teamProgress.tilesNotCompleted) { - const tileItems = getAllTileItems(tile); - clItems.push(...tileItems); - } - - for (const tile of teamProgress.tilesCompleted) { - if ('oneOf' in tile) { - const completed = tile.oneOf.find(i => teamProgress.cl.has(i)); - if (completed) { - clItems.push(completed); - } else { - clItems.push(...tile.oneOf); - } - } - - if ('allOf' in tile) { - clItems.push(...tile.allOf); - } - - if ('customReq' in tile) { - clItems.push(...tile.allItems.filter(i => teamProgress.cl.has(i))); - } - } - - const image = await clImageGenerator.makeArbitraryCLImage({ - user, - clItems, - userBank: teamProgress.cl, - title: 'Bingo' - }); - - return image; - } - if (options.make_team) { - const bingo = await getBingoFromUserInput(options.make_team.bingo); - if (!bingo) return 'Invalid bingo.'; - return makeTeamCommand(interaction, new BingoManager(bingo), user, options.make_team); - } - if (options.leave_team) { - const bingo = await getBingoFromUserInput(options.leave_team.bingo); - if (!bingo) return 'Invalid bingo.'; - return leaveTeamCommand(interaction, new BingoManager(bingo)); - } - if (options.leaderboard) { - const bingo = await getBingoFromUserInput(options.leaderboard.bingo); - if (!bingo) return 'Invalid bingo.'; - return bingoTeamLeaderboard(interaction, user, channelID, new BingoManager(bingo)); - } - - if (options.create_bingo) { - if (user.isIronman) { - return 'Ironmen cannot create bingos.'; - } - const fee = BOT_TYPE === 'OSB' ? 20_000_000 : 50_000_000; - const creationCost = new Bank().add('Coins', fee); - if (user.GP < creationCost.amount('Coins')) { - return `You need atleast ${creationCost} to create a bingo.`; - } - - const channel = globalClient.channels.cache.get(options.create_bingo.notifications_channel_id); - if (!channel || !channelIsSendable(channel)) { - return 'Invalid notifications channel.'; - } - if (!isValidNickname(options.create_bingo.title)) { - return 'Invalid title.'; - } - const member = await channel.guild.members.fetch(userID).catch(noOp); - if (production && (!member || !member.permissions.has('Administrator'))) { - return 'You can only use a notifications channel if you are an Administrator of that server.'; - } - if (channel.guild.id !== interaction.guildId) { - return 'The notifications channel must be in the same server as the command.'; - } - - const createOptions = { - title: options.create_bingo.title, - duration_days: options.create_bingo.duration_days, - start_date: new Date(options.create_bingo.start_date_unix_seconds * 1000), - ticket_price: options.create_bingo.ticket_price, - team_size: options.create_bingo.team_size, - notifications_channel_id: options.create_bingo.notifications_channel_id, - organizers: options.create_bingo.organizers - .split(',') - .map(i => i.trim()) - .filter(id => isValidDiscordSnowflake(id)), - bingo_tiles: [], - creator_id: user.id, - guild_id: channel.guildId - }; - - if (createOptions.team_size < 1 || createOptions.team_size > 5) { - return 'Team size must be between 1 and 5.'; - } - - // Start date must be atleast 3 hours into the future - if (createOptions.start_date.getTime() < Date.now() + Time.Minute * 3) { - return 'Start date must be atleast 3 minutes into the future.'; - } - - // Start date cannot be more than 31 days into the future - if (createOptions.start_date.getTime() > Date.now() + Time.Day * 31) { - return 'Start date cannot be more than 31 days into the future.'; - } - - const disclaimer = `You are creating a bingo, please adhere to these rules. If you are found to be breaking these rules, you will be banned. - -- The title must not be inappropriate or offensive. -- The notifications channel ID must be of a server you own, or have consent to use. The bot will only let you use the channel if you are an Administrator of that server. -- The organizers must consent to being added as organizers of your Bingo. They are people who have access to moderate/manage the Bingo. Organizers can add tiles to the Bingo, and end the Bingo. -- Once your Bingo starts, you cannot stop it, or change any settings. Ensure everything is accurate before then. -- You can only have 1 Bingo active at a time. -- Ironmen will be able to enter, for free. However, they cannot win rewards. -- Note: You need to add tiles yourself, using our predefined tiles AND/OR your own custom tiles. You can add tiles using ${mentionCommand( - globalClient, - 'bingo', - 'manage_bingo', - 'add_tile' - )} command. - -**Your Bingo settings(Can be edited):** -**Title:** ${createOptions.title} (*Cannot be changed after the bingo starts*) -**Duration:** ${createOptions.duration_days} days (*Cannot be changed after the bingo starts*) -**Start Date:** ${dateFm(createOptions.start_date)} (*Cannot be changed after the bingo starts*) -**Finish Date:** ${dateFm(new Date(createOptions.start_date.getTime() + createOptions.duration_days * Time.Day))} -**Ticket Price:** ${toKMB(createOptions.ticket_price)} (*Cannot be changed later*) -**Team Size:** ${createOptions.team_size} (*Cannot be changed later*) -**Notifications Channel:** ${createOptions.notifications_channel_id} -**Organizers:** ${createOptions.organizers.map(userMention).join(', ')} - -${Emoji.Warning} **You will pay a ${toKMB(fee)} GP fee to create this bingo, you will be charged after confirming.** ${ - Emoji.Warning - } -`; - - await handleMahojiConfirmation(interaction, disclaimer); - - await user.removeItemsFromBank(new Bank().add('Coins', fee)); - await prisma.bingo.create({ - data: createOptions - }); - - debugLog('Created bingo', createOptions); - - return 'Created your Bingo succesfully!'; - } - - if (options.manage_bingo) { - if (!options.manage_bingo.bingo) { - return 'You need to pick which bingo to manage.'; - } - const _bingo = await getBingoFromUserInput(options.manage_bingo.bingo); - if (!_bingo) return 'Invalid bingo.'; - if (_bingo.creator_id !== user.id && !_bingo.organizers.includes(user.id)) { - return 'You are not an organizer of this bingo.'; - } - const bingo = new BingoManager(_bingo); - if (options.manage_bingo.finalize) { - if (user.id !== bingo.creatorID) { - return 'Only the creator of the bingo can finalize it.'; - } - const creator = await mUserFetch(bingo.creatorID); - if (bingo.wasFinalized) { - return 'This bingo was already finalized.'; - } - const loot = new Bank().add('Coins', await bingo.countTotalGPInPrizePool()); - await handleMahojiConfirmation( - interaction, - `Are you sure you want to end the Bingo? ${bold('This cannot be undone.')} - -The creator of the bingo (${userMention( - bingo.creatorID - )}) will receive the ${loot} prize pool, so they can distribute it.` - ); - - await creator.addItemsToBank({ items: loot, collectionLog: false }); - await prisma.bingo.update({ - where: { - id: bingo.id - }, - data: { - was_finalized: true - } - }); - return `${creator} received ${loot}. The Bingo has now ended.`; - } - if (options.manage_bingo.csv_dump) { - const { users, teams } = await bingo.fetchAllParticipants(); - return { - files: [ - { - attachment: Buffer.from( - teams - .map(team => - [ - team.participants.map(u => getUsernameSync(u.user_id)).join(','), - team.tilesCompletedCount, - team.trophy?.item.name ?? 'No Trophy' - ].join('\t') - ) - .join('\n') - ), - name: 'teams.txt' - }, - { - attachment: Buffer.from( - users.map(u => [u.id, u.tilesCompletedCount].join('\t')).join('\n') - ), - name: 'users.txt' - } - ] - }; - } - - if (options.manage_bingo.add_tile) { - if (bingo.isActive()) { - return "You can't add tiles to a bingo after it has started."; - } - const globalTile = globalBingoTiles.find(t => stringMatches(t.id, options.manage_bingo?.add_tile)); - let tileToAdd: StoredBingoTile | null = null; - if (globalTile) { - tileToAdd = { global: globalTile.id }; - } else { - tileToAdd = parseTileAddInput(options.manage_bingo.add_tile); - } - if (!tileToAdd) { - return `Invalid tile to add. You can either select a global/predefined tile, or input a custom tile. - -Example: \`add_tile:Coal+Trout+Egg\` is a tile where you have to receive a coal AND trout AND egg. -Example: \`add_tile:Coal|Trout|Egg\` is a tile where you have to receive a coal OR trout OR egg.`; - } - await prisma.bingo.update({ - where: { - id: bingo.id - }, - data: { - bingo_tiles: { - push: tileToAdd as any as Prisma.InputJsonObject - } - } - }); - return `Added tile "${generateTileName(tileToAdd)}" to your bingo.`; - } - - if (options.manage_bingo.remove_tile) { - if (bingo.isActive()) { - return "You can't remove tiles to a bingo after it has started."; - } - let newTiles = [...bingo.rawBingoTiles]; - const globalTile = globalBingoTiles.find(t => stringMatches(t.id, options.manage_bingo?.remove_tile)); - let tileName = ''; - if (globalTile) { - newTiles = newTiles.filter( - t => (isGlobalTile(t) && t.global !== globalTile.id) || !isGlobalTile(t) - ); - tileName = generateTileName(globalTile); - } else { - const tileToRemove = newTiles.find( - t => md5sum(generateTileName(t)) === options.manage_bingo?.remove_tile - ); - if (tileToRemove) { - newTiles = newTiles.filter( - t => md5sum(generateTileName(t)) !== options.manage_bingo?.remove_tile! - ); - tileName = generateTileName(tileToRemove); - } - } - - if (newTiles.length === bingo.rawBingoTiles.length) { - return 'Invalid tile to remove.'; - } - - await handleMahojiConfirmation( - interaction, - `Are you sure you want to remove this tile?\n\n${tileName}` - ); - await prisma.bingo.update({ - where: { - id: bingo.id - }, - data: { - bingo_tiles: newTiles as any as Prisma.InputJsonObject[] - } - }); - - return `Removed "${tileName}" from your bingo.`; - } - - if (options.manage_bingo.add_extra_gp) { - const amount = Number(options.manage_bingo.add_extra_gp); - if (Number.isNaN(amount) || amount < 1) { - return 'Invalid amount.'; - } - - const cost = new Bank().add('Coins', amount); - if (user.GP < cost.amount('Coins')) { - return `You need atleast ${cost} to add that much GP to the prize pool.`; - } - - await handleMahojiConfirmation( - interaction, - `Are you sure you want to add ${cost} to the prize pool? You cannot undo this.` - ); - - await user.removeItemsFromBank(cost); - await prisma.bingo.update({ - where: { - id: bingo.id - }, - data: { - extra_gp: { - increment: amount - } - } - }); - debugLog('Added extra gp to bingo', { bingoID: bingo.id, amount }); - - return `Added ${cost} to the prize pool.`; - } - - if (options.manage_bingo.trophy_handout) { - if (!bingo.isGlobal || !bingo.wasFinalized || !bingo.trophiesApply) { - return 'This bingo is not eligible for trophies.'; - } - const result = await bingo.fetchAllParticipants(); - - const toInsert: Prisma.ReclaimableItemCreateManyInput[] = []; - - for (const team of result.teams) { - if (!team.trophy) continue; - const trophiesToReceive = BingoTrophies.filter( - trophy => trophy.percentile >= team.trophy!.percentile - ); - - for (const userID of team.participants.map(t => t.user_id)) { - const reclaimableItems: Prisma.ReclaimableItemCreateManyInput[] = await Promise.all( - trophiesToReceive.map(async trophy => ({ - name: `Bingo Trophy (${trophy.item.name})`, - quantity: 1, - key: `bso-bingo-2-${trophy.item.id}`, - item_id: trophy.item.id, - description: `Awarded for placing in the top ${trophy.percentile}% of ${ - bingo.title - }. Your team (${(await Promise.all(team.participants.map(async t => await getUsername(t.user_id)))).join(', ')}) placed ${formatOrdinal(team.rank)} with ${ - team.tilesCompletedCount - } tiles completed.`, - date: bingo.endDate.toISOString(), - user_id: userID - })) - ); - toInsert.push(...reclaimableItems); - } - } - - await prisma.reclaimableItem.createMany({ - data: toInsert - }); - - return 'Handed out trophies.'; - } - } - - if (options.view) { - const _bingo = await getBingoFromUserInput(options.view.bingo); - if (!_bingo) return 'Invalid bingo.'; - const bingo = new BingoManager(_bingo); - - const { teams } = await bingo.fetchAllParticipants(); - const yourTeam = teams.find(t => t.participants.some(p => p.user_id === user.id)); - const yourParticipant = yourTeam?.participants.find(p => p.user_id === user.id); - - let progressString = ''; - if (yourTeam && yourParticipant) { - const yourProgress = bingo.determineProgressOfBank(yourParticipant.cl as ItemBank); - - progressString = bingo.isActive() - ? `You have ${yourProgress.tilesCompletedCount} tiles completed. -${yourProgress.bingoTableStr} -**Your team:** ${yourTeam.participants.map(p => userMention(p.user_id)).join(', ')} -Your team has ${yourTeam.tilesCompletedCount} tiles completed. -${yourTeam.bingoTableStr}` - : ''; - - if (bingo.isGlobal) { - progressString += `\n${ - yourTeam.trophy - ? `**Trophy:** ${yourTeam.trophy.emoji} ${yourTeam.trophy.item.name}\n` - : 'Your team has not qualified for a trophy.' - }`; - } - } - - const str = `**${bingo.title}** ${teams.length} teams, ${toKMB(await bingo.countTotalGPInPrizePool())} GP Prize Pool -**Start:** ${dateFm(bingo.startDate)} -**Finish:** ${dateFm(bingo.endDate)} -${progressString} -`; - - return { - content: str, - allowedMentions: { - parse: [], - users: [], - roles: [] - }, - files: [ - { - attachment: Buffer.from(bingo.bingoTiles.map((t, i) => `${++i}. ${t.name}`).join('\n')), - name: 'tiles_board.txt' - } - ] - }; - } - - return 'Invalid command.'; - } -}; diff --git a/src/mahoji/commands/bossrecords.ts b/src/mahoji/commands/bossrecords.ts deleted file mode 100644 index f6466eeafb..0000000000 --- a/src/mahoji/commands/bossrecords.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { toTitleCase } from '@oldschoolgg/toolkit'; -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import type { MessageEditOptions } from 'discord.js'; -import { EmbedBuilder } from 'discord.js'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { chunk } from 'e'; -import { Hiscores } from 'oldschooljs'; -import { bossNameMap } from 'oldschooljs/dist/constants'; -import type { BossRecords } from 'oldschooljs/dist/meta/types'; - -import pets from '../../lib/data/pets'; -import { channelIsSendable, makePaginatedMessage } from '../../lib/util'; -import { deferInteraction } from '../../lib/util/interactionReply'; -import type { OSBMahojiCommand } from '../lib/util'; - -// Emojis for bosses with no pets -const miscEmojis = { - barrowsChests: '<:Dharoks_helm:403038864199122947>', - hespori: '<:Bottomless_compost_bucket:545978484078411777>', - bryophyta: '<:Bryophytas_essence:455835859799769108>', - crazyArchaeologist: '<:Fedora:456179157303427092>', - derangedArchaeologist: '<:Fedora:456179157303427092>', - mimic: '<:Casket:365003978678730772>', - obor: '<:Hill_giant_club:421045456194240523>' -}; - -type MiscEmojisKeys = keyof typeof miscEmojis; - -function getEmojiForBoss(key: MiscEmojisKeys | string) { - if (key in miscEmojis) { - return miscEmojis[key as MiscEmojisKeys]; - } - - const pet = pets.find(_pet => _pet.bossKeys?.includes(key)); - if (pet) return pet.emoji; -} - -export const bossrecordCommand: OSBMahojiCommand = { - name: 'bossrecords', - description: 'Shows your OSRS boss records.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'rsn', - description: 'The runescape username you want to check.', - required: true - } - ], - run: async ({ options, channelID, userID, interaction }: CommandRunOptions<{ rsn: string }>) => { - await deferInteraction(interaction); - const { bossRecords } = await Hiscores.fetch(options.rsn).catch(err => { - throw err.message; - }); - - const sortedEntries = Object.entries(bossRecords) - .filter(([, { rank, score }]) => rank !== -1 && score !== -1) - .sort(([, a], [, b]) => a.rank - b.rank); - - if (sortedEntries.length === 0) { - return 'You have no boss records!. Try logging into the game, and logging out.'; - } - - const pages: MessageEditOptions[] = []; - for (const page of chunk(sortedEntries, 12)) { - const embed = new EmbedBuilder() - .setAuthor({ name: `${toTitleCase(options.rsn)} - Boss Records` }) - .setColor(52_224); - - for (const [name, { rank, score }] of page) { - embed.addFields({ - name: `${getEmojiForBoss(name) || ''} ${bossNameMap.get(name as keyof BossRecords)}`, - value: `**KC:** ${score.toLocaleString()}\n**Rank:** ${rank.toLocaleString()}`, - inline: true - }); - } - - pages.push({ embeds: [embed] }); - } - - const channel = globalClient.channels.cache.get(channelID.toString()); - if (!channelIsSendable(channel)) return 'Invalid channel.'; - - await makePaginatedMessage(channel, pages, userID.toString()); - return { - content: `Showing OSRS Boss Records for \`${options.rsn}\`.`, - ephemeral: true - }; - } -}; diff --git a/src/mahoji/commands/build.ts b/src/mahoji/commands/build.ts index 43efd01632..1c7d1bfdc3 100644 --- a/src/mahoji/commands/build.ts +++ b/src/mahoji/commands/build.ts @@ -10,7 +10,6 @@ import type { ConstructionActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration, hasSkillReqs } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import type { OSBMahojiCommand } from '../lib/util'; const ds2Requirements: Skills = { @@ -162,8 +161,6 @@ export const buildCommand: OSBMahojiCommand = { await transactItems({ userID: user.id, itemsToRemove: cost }); - updateBankSetting('construction_cost_bank', cost); - await addSubTaskToActivityTask({ objectID: object.id, userID: user.id, diff --git a/src/mahoji/commands/buy.ts b/src/mahoji/commands/buy.ts index 6ba46e1b62..ea0ea8d916 100644 --- a/src/mahoji/commands/buy.ts +++ b/src/mahoji/commands/buy.ts @@ -4,26 +4,31 @@ import { ApplicationCommandOptionType } from 'discord.js'; import { Bank } from 'oldschooljs'; import type { ItemBank } from 'oldschooljs/dist/meta/types'; +import { writeFileSync } from 'node:fs'; import { formatOrdinal } from '@oldschoolgg/toolkit'; import { Events } from '../../lib/constants'; import Buyables from '../../lib/data/buyables/buyables'; import { quests } from '../../lib/minions/data/quests'; +import { remapBank } from '../../lib/randomizer'; import { Minigames, getMinigameScore } from '../../lib/settings/minigames'; import { countUsersWithItemInCl } from '../../lib/settings/prisma'; -import { isElligibleForPresent } from '../../lib/settings/settings'; import { MUserStats } from '../../lib/structures/MUserStats'; import { formatSkillRequirements, itemID, itemNameFromID, stringMatches } from '../../lib/util'; -import getOSItem from '../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { deferInteraction } from '../../lib/util/interactionReply'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { buyFossilIslandNotes } from '../lib/abstracted_commands/buyFossilIslandNotes'; import { buyKitten } from '../lib/abstracted_commands/buyKitten'; import type { OSBMahojiCommand } from '../lib/util'; -import { mahojiParseNumber, userStatsUpdate } from '../mahojiSettings'; +import { mahojiParseNumber } from '../mahojiSettings'; const allBuyablesAutocomplete = [...Buyables, { name: 'Kitten' }, { name: 'Fossil Island Notes' }]; +let str = ''; +for (const a of Buyables) { + str += `${a.name}\n`; +} +writeFileSync('buyables.txt', str); + export const buyCommand: OSBMahojiCommand = { name: 'buy', description: 'Allows you to purchase items.', @@ -49,7 +54,7 @@ export const buyCommand: OSBMahojiCommand = { run: async ({ options, userID, interaction }: CommandRunOptions<{ name: string; quantity?: string }>) => { const user = await mUserFetch(userID.toString()); const { name } = options; - let quantity = mahojiParseNumber({ input: options.quantity, min: 1 }) ?? 1; + let quantity = mahojiParseNumber({ input: options.quantity, min: 1, max: 10_000_000 }) ?? 1; if (stringMatches(name, 'kitten')) { return buyKitten(user); } @@ -118,20 +123,7 @@ export const buyCommand: OSBMahojiCommand = { } } - let gpCost = user.isIronman && buyable.ironmanPrice !== undefined ? buyable.ironmanPrice : buyable.gpCost; - - if (buyable.name === getOSItem('Festive present').name) { - if (!(await isElligibleForPresent(user))) { - return "Santa doesn't want to sell you a Festive present!"; - } - quantity = 1; - const previouslyBought = user.cl.amount('Festive present'); - if (user.isIronman) { - gpCost = Math.floor(10_000_000 * (previouslyBought + 1) * ((previouslyBought + 1) / 6)); - } else { - gpCost = Math.floor(100_000_000 * (previouslyBought + 1) * ((previouslyBought + 1) / 3)); - } - } + const gpCost = user.isIronman && buyable.ironmanPrice !== undefined ? buyable.ironmanPrice : buyable.gpCost; if (buyable.name === 'Golden cape shard') { quantity = 1; @@ -157,7 +149,7 @@ export const buyCommand: OSBMahojiCommand = { await handleMahojiConfirmation( interaction, - `${user}, please confirm that you want to buy **${outItems}** for: ${totalCost}.` + `${user}, please confirm that you want to buy **${remapBank(user, outItems)}** for: ${totalCost}.` ); if ( @@ -166,8 +158,8 @@ export const buyCommand: OSBMahojiCommand = { !user.cl.has(buyable.name) ) { const [count, ironCount] = await Promise.all([ - countUsersWithItemInCl(itemID(buyable.name), false), - countUsersWithItemInCl(itemID(buyable.name), true) + countUsersWithItemInCl(itemID(buyable.name)), + countUsersWithItemInCl(itemID(buyable.name)) ]); let announcement = `**${user.badgedUsername}'s** minion, ${user.minionName}, just purchased their first ${ @@ -181,6 +173,10 @@ export const buyCommand: OSBMahojiCommand = { globalClient.emit(Events.ServerNotification, announcement); } + if (gpCost && user.GP < gpCost) { + return `You don't have enough GP to buy this item. You need ${gpCost.toLocaleString()} GP.`; + } + await transactItems({ userID: user.id, itemsToAdd: outItems, @@ -193,24 +189,6 @@ export const buyCommand: OSBMahojiCommand = { .remove('Coins', totalCost.amount('Coins')).bank; if (Object.keys(costBankExcludingGP).length === 0) costBankExcludingGP = undefined; - const currentStats = await user.fetchStats({ buy_cost_bank: true, buy_loot_bank: true }); - await Promise.all([ - updateBankSetting('buy_cost_bank', totalCost), - updateBankSetting('buy_loot_bank', outItems), - userStatsUpdate(user.id, { - buy_cost_bank: totalCost.clone().add(currentStats.buy_cost_bank as ItemBank).bank, - buy_loot_bank: outItems.clone().add(currentStats.buy_loot_bank as ItemBank).bank - }), - prisma.buyCommandTransaction.create({ - data: { - user_id: BigInt(user.id), - cost_gp: totalCost.amount('Coins'), - cost_bank_excluding_gp: costBankExcludingGP, - loot_bank: outItems.bank - } - }) - ]); - return `You purchased ${outItems}.`; } }; diff --git a/src/mahoji/commands/casket.ts b/src/mahoji/commands/casket.ts deleted file mode 100644 index cb1356df95..0000000000 --- a/src/mahoji/commands/casket.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { Bank } from 'oldschooljs'; -import { toKMB } from 'oldschooljs/dist/util'; - -import { ClueTiers } from '../../lib/clues/clueTiers'; -import { PerkTier } from '../../lib/constants'; -import { marketPriceOfBank } from '../../lib/marketPrices'; -import { calcDropRatesFromBankWithoutUniques } from '../../lib/util/calcDropRatesFromBank'; -import { deferInteraction } from '../../lib/util/interactionReply'; -import { makeBankImage } from '../../lib/util/makeBankImage'; -import { Workers } from '../../lib/workers'; -import type { OSBMahojiCommand } from '../lib/util'; - -function determineLimit(user: MUser) { - const perkTier = user.perkTier(); - if (perkTier >= PerkTier.Six) return 300_000; - if (perkTier >= PerkTier.Five) return 200_000; - if (perkTier >= PerkTier.Four) return 100_000; - if (perkTier === PerkTier.Three) return 40_000; - if (perkTier === PerkTier.Two) return 20_000; - if (perkTier === PerkTier.One) return 1000; - - return 50; -} - -export const casketCommand: OSBMahojiCommand = { - name: 'casket', - description: 'Simulate opening lots of clues caskets.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'name', - description: 'The casket you want to open.', - required: true, - choices: ClueTiers.map(i => ({ name: i.name, value: i.name })) - }, - { - type: ApplicationCommandOptionType.Integer, - name: 'quantity', - description: 'The quantity you want to open.', - required: true, - min_value: 1, - max_value: 300_000 - } - ], - run: async ({ options, userID, interaction }: CommandRunOptions<{ name: string; quantity: number }>) => { - await deferInteraction(interaction); - const user = await mUserFetch(userID.toString()); - const limit = determineLimit(user); - if (options.quantity > limit) { - return `The quantity you gave exceeds your limit of ${limit.toLocaleString()}! *You can increase your limit by up to 100,000 by becoming a patron at .*`; - } - - const clueTier = ClueTiers.find(_tier => _tier.name.toLowerCase() === options.name.toLowerCase()); - - if (!clueTier) { - return `Not a valid clue tier. The valid tiers are: ${ClueTiers.map(_tier => _tier.name).join(', ')}`; - } - - await deferInteraction(interaction); - - const [_loot, title] = await Workers.casketOpen({ quantity: options.quantity, clueTierID: clueTier.id }); - const loot = new Bank(_loot.bank); - - if (Object.keys(loot.bank).length === 0) return `${title} and got nothing :(`; - - const image = await makeBankImage({ - bank: new Bank(loot.bank), - title, - user - }); - - return { - content: `You opened ${options.quantity} ${clueTier.name} caskets. -**Bot Value:** ${toKMB(loot.value())} (Average of ${toKMB(loot.value() / options.quantity)} per casket) -**Market Value:** ${toKMB(marketPriceOfBank(loot))} (Average of ${toKMB(marketPriceOfBank(loot) / options.quantity)} per casket) -**Droprates:** ${calcDropRatesFromBankWithoutUniques(loot, options.quantity).slice(0, 20).join(', ')}`, - - files: [image.file] - }; - } -}; diff --git a/src/mahoji/commands/choose.ts b/src/mahoji/commands/choose.ts deleted file mode 100644 index cb69b5e43c..0000000000 --- a/src/mahoji/commands/choose.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { inlineCode } from 'discord.js'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { randArrItem } from 'e'; - -import type { OSBMahojiCommand } from '../lib/util'; - -export const chooseCommand: OSBMahojiCommand = { - name: 'choose', - description: 'Have the bot make a choice from a list of things.', - attributes: { - examples: ['/choose list:First option, second option, third option'] - }, - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'list', - description: 'The list of things to choose from, each separated by a comma.', - required: true - } - ], - run: async ({ options }: CommandRunOptions<{ list: string }>) => { - const list = options.list.split(','); - if (list.length === 0) return "You didn't supply a list."; - return { - content: `Out of ${list - .map(i => i.trim().replace(/`/g, '')) - .map(inlineCode) - .join(', ')} - -I choose... **${randArrItem(list)}**.`, - allowedMentions: { parse: [], roles: [], users: [] } - }; - } -}; diff --git a/src/mahoji/commands/claim.ts b/src/mahoji/commands/claim.ts deleted file mode 100644 index e6ad7c3265..0000000000 --- a/src/mahoji/commands/claim.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { type CommandRunOptions, dateFm, stringMatches } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { Bank } from 'oldschooljs'; - -import { BSO_MAX_TOTAL_LEVEL, BitField, Channel } from '../../lib/constants'; -import { getReclaimableItemsOfUser } from '../../lib/reclaimableItems'; -import { roboChimpUserFetch } from '../../lib/roboChimp'; - -import getOSItem from '../../lib/util/getOSItem'; -import { sendToChannelID } from '../../lib/util/webhook'; -import type { OSBMahojiCommand } from '../lib/util'; - -const claimables = [ - { - name: 'Free T1 Perks', - hasRequirement: async (user: MUser): Promise => { - const roboChimpUser = await roboChimpUserFetch(user.id); - if (roboChimpUser.osb_total_level === 2277 && roboChimpUser.bso_total_level === BSO_MAX_TOTAL_LEVEL) { - return true; - } - return 'You need to be maxed in both bots (OSB and BSO) to claim this.'; - }, - action: async (user: MUser) => { - if (user.bitfield.includes(BitField.BothBotsMaxedFreeTierOnePerks)) { - return 'You already claimed this!'; - } - sendToChannelID(Channel.BSOGeneral, { - content: `${user.mention} just claimed free T1 patron perks for being maxed in both bots!` - }); - await user.update({ - bitfield: { - push: BitField.BothBotsMaxedFreeTierOnePerks - } - }); - return 'You claimed free T1 patron perks in BSO for being maxed in both bots. You can claim this on OSB too for free patron perks on OSB.'; - } - } -]; - -export const claimCommand: OSBMahojiCommand = { - name: 'claim', - description: 'Claim prizes, rewards and other things.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'name', - description: 'The thing you want to claim.', - required: true, - autocomplete: async (value, user) => { - const claimableItems = await prisma.reclaimableItem.findMany({ - where: { - user_id: user.id - } - }); - return [...claimables, ...claimableItems] - .filter(i => (!value ? true : i.name.toLowerCase().includes(value.toLowerCase()))) - .map(i => ({ - name: i.name, - value: i.name - })); - } - } - ], - run: async ({ options, userID }: CommandRunOptions<{ name: string }>) => { - const user = await mUserFetch(userID); - const claimable = claimables.find(i => stringMatches(i.name, options.name)); - if (!claimable) { - const reclaimableData = await getReclaimableItemsOfUser(user); - const rawData = reclaimableData.raw.find(i => i.name === options.name); - if (!rawData) { - return 'You are not elligible for this item.'; - } - if (!reclaimableData.totalCanClaim.has(rawData.item_id)) { - return 'You already claimed this item. If you lose it, you can reclaim it.'; - } - const item = getOSItem(rawData.item_id); - const loot = new Bank().add(item.id); - await user.addItemsToBank({ items: loot, collectionLog: false }); - return `You claimed ${loot}. - -${rawData.name}: ${rawData.description}. -${dateFm(new Date(rawData.date))}`; - } - - const requirementCheck = await claimable.hasRequirement(user); - if (typeof requirementCheck === 'string') { - return `You are not eligible to claim this: ${requirementCheck}`; - } - - const result = await claimable.action(user); - return result; - } -}; diff --git a/src/mahoji/commands/config.ts b/src/mahoji/commands/config.ts index 1d70144e0f..cca47cd36e 100644 --- a/src/mahoji/commands/config.ts +++ b/src/mahoji/commands/config.ts @@ -1,34 +1,26 @@ -import { channelIsSendable, hasBanMemberPerms, miniID } from '@oldschoolgg/toolkit'; import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import type { CommandResponse } from '@oldschoolgg/toolkit'; -import type { activity_type_enum } from '@prisma/client'; -import type { ChatInputCommandInteraction, Guild, HexColorString, User } from 'discord.js'; -import { EmbedBuilder, bold, inlineCode, resolveColor } from 'discord.js'; +import type { ChatInputCommandInteraction, User } from 'discord.js'; +import { inlineCode } from 'discord.js'; import { ApplicationCommandOptionType } from 'discord.js'; -import { Time, clamp, removeFromArr, uniqueArr } from 'e'; +import { removeFromArr, uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; import type { ItemBank } from 'oldschooljs/dist/meta/types'; -import { production } from '../../config'; import { mahojiUserSettingsUpdate } from '../../lib/MUser'; -import { BitField, ItemIconPacks, ParsedCustomEmojiWithGroups, PerkTier } from '../../lib/constants'; +import { BitField } from '../../lib/constants'; import { Eatables } from '../../lib/data/eatables'; -import { gearImages } from '../../lib/gear/functions/generateGearImage'; import { Inventions } from '../../lib/invention/inventions'; import { CombatOptionsArray, CombatOptionsEnum } from '../../lib/minions/data/combatConstants'; -import { DynamicButtons } from '../../lib/DynamicButtons'; import { birdhouseSeeds } from '../../lib/skilling/skills/hunter/birdHouseTrapping'; import { autoslayChoices, slayerMasterChoices } from '../../lib/slayer/constants'; import { setDefaultAutoslay, setDefaultSlayerMaster } from '../../lib/slayer/slayerUtil'; import { BankSortMethods } from '../../lib/sorts'; -import { formatDuration, isValidNickname, itemNameFromID, stringMatches } from '../../lib/util'; -import { emojiServers } from '../../lib/util/cachedUserIDs'; +import { itemNameFromID, stringMatches } from '../../lib/util'; import { getItem } from '../../lib/util/getOSItem'; -import { deferInteraction } from '../../lib/util/interactionReply'; import { makeBankImage } from '../../lib/util/makeBankImage'; import { parseBank } from '../../lib/util/parseStringBank'; -import { mahojiGuildSettingsFetch, mahojiGuildSettingsUpdate } from '../guildSettings'; import { itemOption } from '../lib/mahojiCommandOptions'; import type { OSBMahojiCommand } from '../lib/util'; import { allAbstractCommands } from '../lib/util'; @@ -47,10 +39,6 @@ const toggles: UserConfigToggle[] = [ name: 'Disable Random Events', bit: BitField.DisabledRandomEvents }, - { - name: 'Small Bank Images', - bit: BitField.AlwaysSmallBank - }, { name: 'Disable Birdhouse Run Button', bit: BitField.DisableBirdhouseRunButton @@ -67,10 +55,6 @@ const toggles: UserConfigToggle[] = [ name: 'Disable Auto Farm Contract Button', bit: BitField.DisableAutoFarmContractButton }, - { - name: "Disable Grand Exchange DM's", - bit: BitField.DisableGrandExchangeDMs - }, { name: 'Disable Scroll of Longevity', bit: BitField.ScrollOfLongevityDisabled @@ -79,68 +63,6 @@ const toggles: UserConfigToggle[] = [ name: 'Clean herbs during farm runs', bit: BitField.CleanHerbsFarming }, - { - name: 'Lock Self From Gambling', - bit: BitField.SelfGamblingLocked, - canToggle: async (user, interaction) => { - if (user.bitfield.includes(BitField.SelfGamblingLocked)) { - if (user.user.gambling_lockout_expiry && user.user.gambling_lockout_expiry.getTime() > Date.now()) { - const timeRemaining = user.user.gambling_lockout_expiry.getTime() - Date.now(); - return { - result: false, - message: `You cannot toggle this off for another ${formatDuration( - timeRemaining - )}, you locked yourself from gambling!` - }; - } - return { result: true, message: 'Your Gambling lockout time has expired.' }; - } else if (interaction) { - const durations = [ - { display: '1 day', duration: Time.Day }, - { display: '7 days', duration: Time.Day * 7 }, - { display: '1 month', duration: Time.Month }, - { display: '6 months', duration: Time.Month * 6 }, - { display: '1 year', duration: Time.Year } - ]; - const channel = globalClient.channels.cache.get(interaction.channelId); - if (!channelIsSendable(channel)) return { result: false, message: 'Could not find channel.' }; - await deferInteraction(interaction); - const buttons = new DynamicButtons({ - channel: channel, - usersWhoCanInteract: [user.id], - deleteAfterConfirm: true - }); - for (const dur of durations) { - buttons.add({ - name: dur.display - }); - } - const pickedButton = await buttons.render({ - messageOptions: { - content: `${user}, This will lockout your ability to gamble for the specified time. Choose carefully!` - }, - isBusy: false - }); - - const pickedDuration = durations.find(d => stringMatches(d.display, pickedButton?.name ?? '')); - - if (pickedDuration) { - await user.update({ gambling_lockout_expiry: new Date(Date.now() + pickedDuration.duration) }); - return { - result: true, - message: `Locking out gambling for ${formatDuration(pickedDuration.duration)}` - }; - } - return { result: false, message: 'Cancelled.' }; - } - // If handleToggle called without an interaction, perhaps by non-interactive code, allow toggle. - return { result: true }; - } - }, - { - name: 'Disable farming reminders', - bit: BitField.DisabledFarmingReminders - }, { name: 'Disable Gorajan Bonecrusher', bit: BitField.DisabledGorajanBoneCrusher @@ -168,6 +90,10 @@ const toggles: UserConfigToggle[] = [ { name: 'Use super restores for Dwarven blessing', bit: BitField.UseSuperRestoresForDwarvenBlessing + }, + { + name: 'Disable Relic of repitition', + bit: BitField.DisableRelicOfRepitition } ]; @@ -357,8 +283,8 @@ async function bankSortConfig( const currentWeightingBank = new Bank(user.user.bank_sort_weightings as ItemBank); const perkTier = user.perkTier(); - if (perkTier < PerkTier.Two) { - return patronMsg(PerkTier.Two); + if (perkTier === 1) { + return patronMsg(1); } if (!sortMethod && !addWeightingBank && !removeWeightingBank && !resetWeightingBank) { @@ -414,142 +340,6 @@ async function bankSortConfig( return bankSortConfig(await mUserFetch(user.id), undefined, undefined, undefined, undefined); } -async function bgColorConfig(user: MUser, hex?: string) { - const currentColor = user.user.bank_bg_hex; - - const embed = new EmbedBuilder(); - - if (hex === 'reset') { - await user.update({ - bank_bg_hex: null - }); - return 'Reset your bank background color.'; - } - - if (!hex) { - if (!currentColor) { - return 'You have no background color set.'; - } - return { - embeds: [ - embed - .setColor(resolveColor(currentColor as HexColorString)) - .setDescription(`Your current background color is \`${currentColor}\`.`) - ] - }; - } - - hex = hex.toUpperCase(); - const isValid = hex.length === 7 && /^#([0-9A-F]{3}){1,2}$/i.test(hex); - if (!isValid) { - return "That's not a valid hex color. It needs to be 7 characters long, starting with '#', for example: #4e42f5 - use this to pick one: "; - } - - await user.update({ - bank_bg_hex: hex - }); - - return { - embeds: [ - embed - .setColor(resolveColor(hex as HexColorString)) - .setDescription(`Your background color is now \`${hex}\``) - ] - }; -} - -async function handleChannelEnable(user: MUser, guild: Guild | null, channelID: string, choice: 'enable' | 'disable') { - if (!guild) return 'This command can only be run in servers.'; - if (!(await hasBanMemberPerms(user.id, guild))) - return "You need to be 'Ban Member' permissions to use this command."; - const cID = channelID.toString(); - const settings = await mahojiGuildSettingsFetch(guild); - const isDisabled = settings.staffOnlyChannels.includes(cID); - - if (choice === 'disable') { - if (isDisabled) return 'This channel is already disabled.'; - - await mahojiGuildSettingsUpdate(guild.id, { - staffOnlyChannels: [...settings.staffOnlyChannels, cID] - }); - - return 'Channel disabled. Staff of this server can still use commands in this channel.'; - } - if (!isDisabled) return 'This channel is already enabled.'; - - await mahojiGuildSettingsUpdate(guild.id, { - staffOnlyChannels: settings.staffOnlyChannels.filter(i => i !== cID) - }); - - return 'Channel enabled. Anyone can use commands in this channel now.'; -} - -async function handlePetMessagesEnable( - user: MUser, - guild: Guild | null, - channelID: string, - choice: 'enable' | 'disable' -) { - if (!guild) return 'This command can only be run in servers.'; - if (!(await hasBanMemberPerms(user.id, guild))) - return "You need to be 'Ban Member' permissions to use this command."; - const settings = await mahojiGuildSettingsFetch(guild); - - const cID = channelID.toString(); - if (choice === 'enable') { - if (settings.petchannel) { - return 'Pet Messages are already enabled in this guild.'; - } - await mahojiGuildSettingsUpdate(guild.id, { - petchannel: cID - }); - return 'Enabled Pet Messages in this guild.'; - } - if (settings.petchannel === null) { - return "Pet Messages aren't enabled, so you can't disable them."; - } - await mahojiGuildSettingsUpdate(guild.id, { - petchannel: null - }); - return 'Disabled Pet Messages in this guild.'; -} - -async function handleCommandEnable( - user: MUser, - guild: Guild | null, - commandName: string, - choice: 'enable' | 'disable' -) { - if (!guild) return 'This command can only be run in servers.'; - if (!(await hasBanMemberPerms(user.id, guild))) - return "You need to be 'Ban Member' permissions to use this command."; - const settings = await mahojiGuildSettingsFetch(guild); - const command = allAbstractCommands(globalClient.mahojiClient).find( - i => i.name.toLowerCase() === commandName.toLowerCase() - ); - if (!command) return "That's not a valid command."; - - if (choice === 'enable') { - if (!settings.disabledCommands.includes(commandName)) { - return "That command isn't disabled."; - } - await mahojiGuildSettingsUpdate(guild.id, { - disabledCommands: settings.disabledCommands.filter(i => i !== command.name) - }); - - return `Successfully enabled the \`${commandName}\` command.`; - } - - if (settings.disabledCommands.includes(command.name)) { - return 'That command is already disabled.'; - } - await mahojiGuildSettingsUpdate(guild.id, { - disabledCommands: [...settings.disabledCommands, command.name] - }); - - return `Successfully disabled the \`${command.name}\` command.`; -} - const priorityWarningMsg = "\n\n**Important: By default, 'Always barrage/burst' will take priority if 'Always cannon' is also enabled.**"; async function handleCombatOptions(user: MUser, command: 'add' | 'remove' | 'list' | 'help', option?: string) { @@ -621,71 +411,6 @@ async function handleCombatOptions(user: MUser, command: 'add' | 'remove' | 'lis return `${newcbopt.name} is now ${nextBool ? 'enabled' : 'disabled'} for you.${warningMsg}`; } -function pinnedTripLimit(perkTier: number) { - return clamp(perkTier + 1, 1, 4); -} -export async function pinTripCommand( - user: MUser, - tripId: string | undefined, - emoji: string | undefined, - customName: string | undefined -) { - if (!tripId) return 'Invalid trip.'; - const id = Number(tripId); - const trip = await prisma.activity.findFirst({ where: { id, user_id: BigInt(user.id) } }); - if (!trip) return 'Invalid trip.'; - - if (emoji) { - const res = ParsedCustomEmojiWithGroups.exec(emoji); - if (!res || !res[3]) return "That's not a valid emoji."; - emoji = res[3]; - - const cachedEmoji = globalClient.emojis.cache.get(emoji); - if ((!cachedEmoji || !emojiServers.has(cachedEmoji.guild.id)) && production) { - return "Sorry, that emoji can't be used. Only emojis in the main support server, or our emoji servers can be used."; - } - } - - if (customName) { - if (!isValidNickname(customName) || customName.length >= 32) return 'Invalid custom name.'; - } - - const limit = pinnedTripLimit(user.perkTier()); - const currentPinnedTripsCount = await prisma.pinnedTrip.count({ where: { user_id: user.id } }); - if (currentPinnedTripsCount >= limit) { - return `You cannot have more than ${limit}x pinned trips, unpin one first. Your limit is ${limit}, you can get up to 4 by being a patron.`; - } - - await prisma.pinnedTrip.create({ - data: { - id: miniID(7), - emoji_id: emoji, - custom_name: customName, - activity: { - connect: { - id: trip.id - } - }, - user: { - connect: { - id: user.id - } - }, - activity_type: trip.type, - data: trip.data as object - } - }); - - return `You pinned a ${trip.type} trip. You can now see it in your buttons.`; -} - -async function unpinTripCommand(user: MUser, tripId: string | undefined) { - const trip = await prisma.pinnedTrip.findFirst({ where: { id: tripId, user_id: user.id } }); - if (!trip) return 'Invalid trip.'; - await prisma.pinnedTrip.delete({ where: { id: trip.id } }); - return `You unpinned a ${trip.activity_type} trip.`; -} - export const configCommand: OSBMahojiCommand = { name: 'config', description: 'Commands configuring settings and options.', @@ -827,19 +552,6 @@ export const configCommand: OSBMahojiCommand = { } ] }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'bg_color', - description: 'Set a custom color for transparent bank backgrounds.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'color', - description: 'The color in hex format.', - required: false - } - ] - }, { type: ApplicationCommandOptionType.Subcommand, name: 'bank_sort', @@ -1080,100 +792,6 @@ export const configCommand: OSBMahojiCommand = { } } ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'pin_trip', - description: 'Pin a trip so you can easily repeat it whenever you want.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'trip', - description: 'The trip you want to pin.', - required: false, - autocomplete: async (_, user) => { - const res = await prisma.$queryRawUnsafe< - { type: activity_type_enum; data: object; id: number; finish_date: string }[] - >(` -SELECT DISTINCT ON (activity.type) activity.type, activity.data, activity.id, activity.finish_date -FROM activity -WHERE finish_date::date > now() - INTERVAL '31 days' -AND user_id = '${user.id}'::bigint -ORDER BY activity.type, finish_date DESC -LIMIT 20; -;`); - return res.map(i => ({ - name: `${i.type} (Finished ${formatDuration( - Date.now() - new Date(i.finish_date).getTime() - )} ago)`, - value: i.id.toString() - })); - } - }, - { - type: ApplicationCommandOptionType.String, - required: false, - name: 'emoji', - description: 'Pick an emoji for the button (optional).' - }, - { - type: ApplicationCommandOptionType.String, - required: false, - name: 'custom_name', - description: 'Custom name for the button (optional).' - }, - { - type: ApplicationCommandOptionType.String, - name: 'unpin_trip', - description: 'The trip you want to unpin.', - required: false, - autocomplete: async (_, user) => { - const res = await prisma.pinnedTrip.findMany({ where: { user_id: user.id } }); - return res.map(i => ({ - name: `${i.activity_type}${i.custom_name ? `- ${i.custom_name}` : ''}`, - value: i.id.toString() - })); - } - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'gearframe', - description: 'Change your gear frame.', - options: [ - { - name: 'name', - type: ApplicationCommandOptionType.String, - description: 'The gear frame you want to use.', - required: true, - autocomplete: async value => { - return gearImages - .filter(i => (!value ? true : i.name.toLowerCase().includes(value.toLowerCase()))) - .map(i => ({ - name: i.name, - value: i.name - })); - } - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'icon_pack', - description: 'Change your icon pack', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'name', - description: 'The icon pack you want to use.', - required: true, - choices: ['Default', ...ItemIconPacks.map(i => i.name)].map(i => ({ - name: i, - value: i - })) - } - ] } ] } @@ -1181,19 +799,11 @@ LIMIT 20; run: async ({ options, userID, - guildID, - channelID, interaction }: CommandRunOptions<{ - server?: { - channel?: { choice: 'enable' | 'disable' }; - pet_messages?: { choice: 'enable' | 'disable' }; - command?: { command: string; choice: 'enable' | 'disable' }; - }; user?: { toggle?: { name: string }; combat_options?: { action: 'add' | 'remove' | 'list' | 'help'; input: string }; - set_rsn?: { username: string }; bg_color?: { color?: string }; bank_sort?: { sort_method?: string; @@ -1207,62 +817,21 @@ LIMIT 20; favorite_bh_seeds?: { add?: string; remove?: string; reset?: boolean }; slayer?: { master?: string; autoslay?: string }; toggle_invention?: { invention: string }; - gearframe?: { name: string }; - pin_trip?: { trip?: string; unpin_trip?: string; emoji?: string; custom_name?: string }; - icon_pack?: { name?: string }; }; }>) => { const user = await mUserFetch(userID); - const guild = guildID ? globalClient.guilds.cache.get(guildID.toString()) ?? null : null; - if (options.server) { - if (options.server.channel) { - return handleChannelEnable(user, guild, channelID, options.server.channel.choice); - } - if (options.server.pet_messages) { - return handlePetMessagesEnable(user, guild, channelID, options.server.pet_messages.choice); - } - if (options.server.command) { - return handleCommandEnable(user, guild, options.server.command.command, options.server.command.choice); - } - } + if (options.user) { const { toggle, combat_options, - bg_color, bank_sort, favorite_alchs, favorite_food, favorite_items, favorite_bh_seeds, - slayer, - pin_trip, - icon_pack + slayer } = options.user; - if (icon_pack) { - if (icon_pack.name) { - if (icon_pack.name === 'Default') { - if (user.user.icon_pack_id) { - await user.update({ - icon_pack_id: null - }); - return 'Your icon pack is now set to default.'; - } - return 'Your icon pack is already set to default.'; - } - - const pack = ItemIconPacks.find(i => i.name === icon_pack.name); - if (!pack) return 'Invalid icon pack.'; - - if (!user.user.store_bitfield.includes(pack.storeBitfield)) { - return 'You do not own this icon pack.'; - } - await user.update({ - icon_pack_id: pack.id - }); - return `Your icon pack is now set to ${bold(pack.name)}.`; - } - } if (toggle) { return handleToggle(user, toggle.name, interaction); @@ -1270,9 +839,6 @@ LIMIT 20; if (combat_options) { return handleCombatOptions(user, combat_options.action, combat_options.input); } - if (bg_color) { - return bgColorConfig(user, bg_color.color); - } if (bank_sort) { return bankSortConfig( user, @@ -1333,26 +899,6 @@ LIMIT 20; }); return `${invention.name} is now **Disabled**.`; } - if (options.user.gearframe) { - const matchingFrame = gearImages.find(i => stringMatches(i.name, options.user?.gearframe?.name ?? '')); - if (!matchingFrame) return 'Invalid name.'; - if (!user.user.unlocked_gear_templates.includes(matchingFrame.id) && matchingFrame.id !== 0) { - return "You don't have this gear frame unlocked."; - } - await user.update({ - gear_template: matchingFrame.id - }); - return `Your gear frame is now set to **${matchingFrame.name}**!`; - } - if (pin_trip) { - if (pin_trip.trip) { - return pinTripCommand(user, pin_trip.trip, pin_trip.emoji, pin_trip.custom_name); - } - if (pin_trip.unpin_trip) { - return unpinTripCommand(user, pin_trip.unpin_trip); - } - return 'You need to provide a trip to pin or unpin.'; - } } return 'Invalid command.'; } diff --git a/src/mahoji/commands/craft.ts b/src/mahoji/commands/craft.ts index 8104723859..8f4bc17306 100644 --- a/src/mahoji/commands/craft.ts +++ b/src/mahoji/commands/craft.ts @@ -10,7 +10,6 @@ import { SkillsEnum } from '../../lib/skilling/types'; import type { CraftingActivityTaskOptions } from '../../lib/types/minions'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import type { OSBMahojiCommand } from '../lib/util'; export const craftCommand: OSBMahojiCommand = { @@ -144,8 +143,6 @@ export const craftCommand: OSBMahojiCommand = { await user.removeItemsFromBank(itemsNeeded); - updateBankSetting('crafting_cost', itemsNeeded); - await addSubTaskToActivityTask({ craftableID: craftable.id, userID: user.id, diff --git a/src/mahoji/commands/create.ts b/src/mahoji/commands/create.ts index ba48cb2950..df41e41239 100644 --- a/src/mahoji/commands/create.ts +++ b/src/mahoji/commands/create.ts @@ -1,4 +1,4 @@ -import { readFileSync } from 'node:fs'; +import { readFileSync, writeFileSync } from 'node:fs'; import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { ApplicationCommandOptionType } from 'discord.js'; import { isFunction, reduceNumByPercent } from 'e'; @@ -12,9 +12,8 @@ import type { SlayerTaskUnlocksEnum } from '../../lib/slayer/slayerUnlocks'; import { hasSlayerUnlock } from '../../lib/slayer/slayerUtil'; import { stringMatches } from '../../lib/util'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import type { OSBMahojiCommand } from '../lib/util'; -import { mahojiUsersSettingsFetch, userStatsBankUpdate } from '../mahojiSettings'; +import { mahojiUsersSettingsFetch } from '../mahojiSettings'; const creatablesTable = readFileSync('./src/lib/data/creatablesTable.txt', 'utf8'); @@ -24,6 +23,12 @@ const allCreatablesTable = { files: [{ attachment: Buffer.from(creatablesTable), name: 'Creatables.txt' }] }; +let str = ''; +for (const a of Createables) { + str += `${a.name}\n`; +} +writeFileSync('creatables.txt', str); + export const createCommand: OSBMahojiCommand = { name: 'create', description: 'Allows you to create items, like godswords or spirit shields - and pack barrows armor sets.', @@ -242,11 +247,6 @@ export const createCommand: OSBMahojiCommand = { itemsToRemove: inItems }); - await updateBankSetting('create_cost', inItems); - await updateBankSetting('create_loot', outItems); - await userStatsBankUpdate(user, 'create_cost_bank', inItems); - await userStatsBankUpdate(user, 'create_loot_bank', outItems); - if (action === 'revert') { return `You reverted ${inItems} into ${outItems}.${extraMessage}`; } diff --git a/src/mahoji/commands/dg.ts b/src/mahoji/commands/dg.ts index 8546c125ad..fc3c7bcc7a 100644 --- a/src/mahoji/commands/dg.ts +++ b/src/mahoji/commands/dg.ts @@ -3,23 +3,17 @@ import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { ApplicationCommandOptionType } from 'discord.js'; import { Time, reduceNumByPercent } from 'e'; +import { Bank } from 'oldschooljs'; import { setupParty } from '../../lib/party'; +import { determineDgLevelForFloor, dungBuyables, isValidFloor } from '../../lib/skilling/skills/dung/dungData'; import { - determineDgLevelForFloor, - dungBuyables, - isValidFloor, - requiredSkills -} from '../../lib/skilling/skills/dung/dungData'; -import { - calcMaxFloorUserCanDo, calcUserGorajanShardChance, - hasRequiredLevels, numberOfGorajanOutfitsEquipped } from '../../lib/skilling/skills/dung/dungDbFunctions'; import { SkillsEnum } from '../../lib/skilling/types'; import type { MakePartyOptions } from '../../lib/types'; import type { DungeoneeringOptions } from '../../lib/types/minions'; -import { channelIsSendable, formatDuration, formatSkillRequirements, stringMatches } from '../../lib/util'; +import { channelIsSendable, formatDuration, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { deferInteraction } from '../../lib/util/interactionReply'; @@ -34,7 +28,13 @@ const boostPerPlayer = 5; async function startCommand(channelID: string, user: MUser, floor: string | undefined, solo: boolean | undefined) { const isSolo = Boolean(solo); - const floorToDo = floor ? Number(floor) : calcMaxFloorUserCanDo(user); + let floorToDo = floor ? Number(floor) : 1; + for (const lvl of [7, 6, 5, 4, 3, 2, 1]) { + if (determineDgLevelForFloor(lvl) <= user.skillLevel(SkillsEnum.Dungeoneering)) { + floorToDo = lvl; + break; + } + } if (!isValidFloor(floorToDo)) { return "That's an invalid floor."; @@ -51,8 +51,7 @@ async function startCommand(channelID: string, user: MUser, floor: string | unde const message = `${user.usernameOrMention} has created a Dungeoneering party! Use the buttons below to join/leave. **Floor:** ${floorToDo} **Duration:** ${formatDuration(duration)} -**Min. Quantity:** ${quantity} -**Required Stats:** ${formatSkillRequirements(requiredSkills(floorToDo))}`; +**Min. Quantity:** ${quantity}`; const partyOptions: MakePartyOptions = { leader: user, @@ -68,25 +67,6 @@ async function startCommand(channelID: string, user: MUser, floor: string | unde return [true, 'your minion is busy.']; } - const max = calcMaxFloorUserCanDo(user); - if (max < floorToDo) { - return [ - true, - `this party is doing Floor ${floorToDo}, you can't do this floor because you need level ${determineDgLevelForFloor( - floorToDo - )} Dungeoneering.` - ]; - } - - if (!hasRequiredLevels(user, floorToDo)) { - return [ - true, - `you don't have the required stats for this floor, you need: ${formatSkillRequirements( - requiredSkills(floorToDo) - )}.` - ]; - } - return [false]; } }; @@ -197,15 +177,16 @@ async function buyCommand(user: MUser, name?: string, quantity?: number) { item.name }. You need ${overallCost}, but you have only ${balance.toLocaleString()}.`; } + const loot = new Bank().add(item.id, quantity); - await user.addItemsToBank({ items: { [item.id]: quantity }, collectionLog: true }); + await user.addItemsToBank({ items: loot, collectionLog: true }); await user.update({ dungeoneering_tokens: { decrement: overallCost } }); - return `Successfully purchased ${quantity}x ${item.name} for ${overallCost} Dungeoneering tokens.`; + return `Successfully purchased ${loot} for ${overallCost} Dungeoneering tokens.`; } export const dgCommand: OSBMahojiCommand = { @@ -224,11 +205,8 @@ export const dgCommand: OSBMahojiCommand = { type: ApplicationCommandOptionType.String, name: 'floor', description: 'The floor you want to do. (Optional, defaults to max)', - autocomplete: async (_, user) => { - const kUser = await mUserFetch(user.id); - return [7, 6, 5, 4, 3, 2, 1] - .filter(floor => hasRequiredLevels(kUser, floor)) - .map(i => ({ name: `Floor ${i}`, value: i.toString() })); + autocomplete: async () => { + return [7, 6, 5, 4, 3, 2, 1].map(i => ({ name: `Floor ${i}`, value: i.toString() })); }, required: false }, @@ -285,8 +263,7 @@ export const dgCommand: OSBMahojiCommand = { const user = await mUserFetch(userID); if (options.start) return startCommand(channelID, user, options.start.floor, options.start.solo); if (options.buy) return buyCommand(user, options.buy.item, options.buy.quantity); - let str = `<:dungeoneeringToken:829004684685606912> **Dungeoneering Tokens:** ${user.user.dungeoneering_tokens.toLocaleString()} -**Max floor:** ${calcMaxFloorUserCanDo(user)}`; + let str = `<:dungeoneeringToken:829004684685606912> **Dungeoneering Tokens:** ${user.user.dungeoneering_tokens.toLocaleString()}`; const { boosts } = calcUserGorajanShardChance(user); if (boosts.length > 0) { str += `\n**Gorajan shard boosts:** ${boosts.join(', ')}`; diff --git a/src/mahoji/commands/drop.ts b/src/mahoji/commands/drop.ts index 3332994585..8b5dc7b901 100644 --- a/src/mahoji/commands/drop.ts +++ b/src/mahoji/commands/drop.ts @@ -5,7 +5,6 @@ import { ClueTiers } from '../../lib/clues/clueTiers'; import { ellipsize, itemNameFromID, returnStringOrFile } from '../../lib/util'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { parseBank } from '../../lib/util/parseStringBank'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { filterOption } from '../lib/mahojiCommandOptions'; import type { OSBMahojiCommand } from '../lib/util'; @@ -88,7 +87,6 @@ export const dropCommand: OSBMahojiCommand = { } await user.removeItemsFromBank(bank); - updateBankSetting('dropped_items', bank); return returnStringOrFile(`Dropped ${bank}.`); } diff --git a/src/mahoji/commands/droprates.ts b/src/mahoji/commands/droprates.ts deleted file mode 100644 index 4b30ebc45c..0000000000 --- a/src/mahoji/commands/droprates.ts +++ /dev/null @@ -1,135 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { Time } from 'e'; -import { Bank } from 'oldschooljs'; - -import { MAX_XP, MIN_LENGTH_FOR_PET, herbertDroprate } from '../../lib/constants'; -import { globalDroprates } from '../../lib/data/globalDroprates'; -import { slayerMaskHelms } from '../../lib/data/slayerMaskHelms'; -import Constructables from '../../lib/skilling/skills/construction/constructables'; -import Potions from '../../lib/skilling/skills/herblore/mixables/potions'; -import { - calcBabyYagaHouseDroprate, - clAdjustedDroprate, - formatDuration, - makeTable, - stringMatches -} from '../../lib/util'; -import type { OSBMahojiCommand } from '../lib/util'; - -interface GlobalDroprate { - name: string; - output: (opts: { user: MUser }) => string; - notes?: string[]; -} - -const droprates: GlobalDroprate[] = [ - { - name: 'Baby yaga house pet', - output: () => { - const thirtyMinTicks = (Time.Minute * 30) / (Time.Millisecond * 600); - - const rows: [string, number, string][] = []; - for (const con of Constructables) { - const droprate = calcBabyYagaHouseDroprate(con.xp, new Bank()); - const numBuiltPerTrip = thirtyMinTicks / con.ticks; - rows.push([con.name, droprate, (droprate / numBuiltPerTrip).toFixed(2)]); - } - rows.sort((a, b) => a[1] - b[1]); - return makeTable(['Object', '1 in X Droprate', 'Num 30min trips'], rows); - }, - notes: ['If more than 1 in CL, droprate is multipled by the amount you have in your CL'] - }, - { - name: 'Slayer masks/helms', - output: () => { - const rows = []; - for (const a of slayerMaskHelms) { - rows.push([a.helm.name, a.killsRequiredForUpgrade, a.maskDropRate]); - } - - return makeTable(['Name', 'Kills For Helm', 'Mask Droprate'], rows); - } - }, - { - name: 'Herbert (pet)', - output: () => { - const rows = []; - - for (const pot of Potions) { - const dropratePerMinute = herbertDroprate(1, pot.level); - const dropratePerMinuteAtMax = herbertDroprate(MAX_XP, pot.level); - rows.push([pot.item.name, 1 / (60 / dropratePerMinute), 1 / (60 / dropratePerMinuteAtMax)]); - } - - return `Herbert is rolled per minute of your trip, and the droprate halves (becomes twice as common) when you have the max (${MAX_XP.toLocaleString()}) Herblore XP. - -${makeTable(['Potion Name', 'Droprate per hour', 'Droprate per hour at max xp'], rows)}`; - } - } -]; - -for (const droprate of Object.values(globalDroprates)) { - droprates.push({ - name: droprate.name, - output: ({ user }) => { - let str = `**${droprate.name}**\n\n`; - str += `${droprate.name} drops at a rate of **1/${droprate.baseRate}** per ${droprate.rolledPer}.\n`; - if ('minLength' in droprate && droprate.minLength) { - str += `Requires a minimum trip length of **${formatDuration(MIN_LENGTH_FOR_PET)}** to receive.\n`; - } - if ('tameBaseRate' in droprate) { - str += `Tames have a different base rate of **1/${droprate.tameBaseRate}**.\n`; - } - if ('clIncrease' in droprate) { - str += `For each pet in your CL, the droprate is multiplied (made rarer) by **${droprate.clIncrease}x**.\n`; - } - if ('notes' in droprate) { - str += `\n**Notes:**\n${droprate.notes.join('\n')}`; - } - - if ('clIncrease' in droprate && 'item' in droprate) { - const inCL = user.cl.amount(droprate.item.id); - str += `\n\nYou have ${inCL}x ${ - droprate.item.name - } in your CL, so your current droprate is: **1 in ${clAdjustedDroprate( - user, - droprate.item.id, - droprate.baseRate, - droprate.clIncrease - )}**.`; - } - return str; - } - }); -} - -export const dropRatesCommand: OSBMahojiCommand = { - name: 'droprate', - description: 'Check the droprate of something.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'thing', - description: 'The thing you want to check.', - required: true, - autocomplete: async (val: string) => { - return droprates - .filter(i => (!val ? true : i.name.toLowerCase().includes(val.toLowerCase()))) - .map(i => ({ name: i.name, value: i.name })); - } - } - ], - run: async ({ options, user }: CommandRunOptions<{ thing: string }>) => { - const obj = droprates.find(drop => stringMatches(drop.name, options.thing)); - if (!obj) return 'Invalid thing'; - let output = obj.output({ user: await mUserFetch(user.id) }); - if (obj.notes) { - output += `\n\n**Notes:**\n${obj.notes.join('\n')}`; - } - if (output.length >= 2000) { - return { files: [{ attachment: Buffer.from(output), name: 'droprates.txt' }] }; - } - return output; - } -}; diff --git a/src/mahoji/commands/fake.ts b/src/mahoji/commands/fake.ts deleted file mode 100644 index fc93c8261a..0000000000 --- a/src/mahoji/commands/fake.ts +++ /dev/null @@ -1,208 +0,0 @@ -import type { SKRSContext2D } from '@napi-rs/canvas'; -import { Canvas } from '@napi-rs/canvas'; -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { randInt } from 'e'; - -import { loadAndCacheLocalImage, measureTextWidth } from '../../lib/util/canvasUtil'; -import type { OSBMahojiCommand } from '../lib/util'; - -const bg = loadAndCacheLocalImage('./src/lib/resources/images/tob-bg.png'); - -const randomMessages = ['omfgggggg', '!#@$@#$@##@$', 'adfsjklfadkjsl;l', 'l00000l wtf']; - -function arma(ctx: SKRSContext2D, username: string) { - ctx.fillText("Your Kree'arra kill count is: ", 11, 10); - ctx.fillStyle = '#ff0000'; - ctx.fillText(randInt(1, 20).toString(), 12 + measureTextWidth(ctx, "Your Kree'arra kill count is: "), 10); - - ctx.fillStyle = '#ff0000'; - ctx.fillText("You have a funny feeling like you're being followed.", 11, 25); - - ctx.fillStyle = '#005f00'; - ctx.fillText(`${username} received a drop: Pet kree'arra`, 11, 40); - - ctx.fillStyle = '#005f00'; - ctx.fillText(`${username} received a drop: ${Math.random() > 0.5 ? 'Armadyl hilt' : 'Armadyl chestplate'}`, 11, 54); - - /* Username */ - const randMessage = randomMessages[Math.floor(Math.random() * randomMessages.length)]; - ctx.fillStyle = '#000000'; - ctx.fillText(`${username}: `, 11, 69); - ctx.fillStyle = '#0000ff'; - ctx.fillText(`${randMessage}*`, 12 + measureTextWidth(ctx, `${username}: `), 69); -} - -function bandos(ctx: SKRSContext2D, username: string) { - ctx.fillText('Your General Graardor kill count is: ', 11, 10); - ctx.fillStyle = '#ff0000'; - ctx.fillText(randInt(1, 20).toString(), 12 + measureTextWidth(ctx, 'Your General Graardor kill count is: '), 10); - - ctx.fillStyle = '#ff0000'; - ctx.fillText("You have a funny feeling like you're being followed.", 11, 25); - - ctx.fillStyle = '#005f00'; - ctx.fillText(`${username} received a drop: Pet general graardor`, 11, 40); - - ctx.fillStyle = '#005f00'; - ctx.fillText(`${username} received a drop: Bandos ${Math.random() > 0.5 ? 'chestplate' : 'tassets'}`, 11, 54); - - /* Username */ - const randMessage = randomMessages[Math.floor(Math.random() * randomMessages.length)]; - ctx.fillStyle = '#000000'; - ctx.fillText(`${username}: `, 11, 69); - ctx.fillStyle = '#0000ff'; - ctx.fillText(`${randMessage}*`, 12 + measureTextWidth(ctx, `${username}: `), 69); -} - -function ely(ctx: SKRSContext2D, username: string) { - ctx.fillText('Your Corporeal Beast kill count is: ', 11, 40); - ctx.fillStyle = '#ff0000'; - ctx.fillText( - randInt(1, 20).toLocaleString(), - 12 + measureTextWidth(ctx, 'Your Corporeal Beast kill count is: '), - 40 - ); - - ctx.fillStyle = '#005f00'; - ctx.fillText(`${username} received a drop: Elysian sigil`, 11, 54); - - ctx.fillStyle = '#000000'; - ctx.fillText(`${username}: `, 11, 70); - ctx.fillStyle = '#0000ff'; - ctx.fillText('*', 12 + measureTextWidth(ctx, `${username}: `), 70); -} - -function sara(ctx: SKRSContext2D, username: string) { - ctx.fillText('Your Commander Zilyana kill count is: ', 11, 10); - ctx.fillStyle = '#ff0000'; - ctx.fillText(randInt(1, 20).toString(), 12 + measureTextWidth(ctx, 'Your Commander Zilyana kill count is: '), 10); - - ctx.fillStyle = '#ff0000'; - ctx.fillText("You have a funny feeling like you're being followed.", 11, 25); - - ctx.fillStyle = '#005f00'; - ctx.fillText(`${username} received a drop: Pet zilyana`, 11, 40); - - ctx.fillStyle = '#005f00'; - ctx.fillText(`${username} received a drop: ${Math.random() > 0.5 ? 'Saradomin hilt' : 'Armadyl crossbow'}`, 11, 54); - const randMessage = randomMessages[Math.floor(Math.random() * randomMessages.length)]; - ctx.fillStyle = '#000000'; - ctx.fillText(`${username}: `, 11, 69); - ctx.fillStyle = '#0000ff'; - ctx.fillText(`${randMessage}*`, 12 + measureTextWidth(ctx, `${username}: `), 69); -} - -function scythe(ctx: SKRSContext2D, username: string) { - const kc = randInt(1, 20); - /* Your completed Theatre of Blood count is: X. */ - ctx.fillText('Your completed Theatre of Blood count is: ', 11, 10); - ctx.fillStyle = '#ff0000'; - ctx.fillText(kc.toString(), 12 + measureTextWidth(ctx, 'Your completed Theatre of Blood count is: '), 10); - ctx.fillStyle = '#000000'; - ctx.fillText('.', 12 + measureTextWidth(ctx, `Your completed Theatre of Blood count is: ${kc}`), 10); - - /* Username found something special: Scythe of vitur (uncharged) */ - ctx.fillStyle = '#ff0000'; - ctx.fillText(username, 11, 25); - ctx.fillStyle = '#000000'; - ctx.fillText(' found something special: ', 12 + measureTextWidth(ctx, username), 25); - ctx.fillStyle = '#ff0000'; - ctx.fillText( - 'Scythe of vitur (uncharged)', - 12 + measureTextWidth(ctx, `${username} found something special: `), - 25 - ); - - /* Username found something special: Lil' Zik */ - ctx.fillStyle = '#ff0000'; - ctx.fillText(username, 11, 54); - ctx.fillStyle = '#000000'; - ctx.fillText(' found something special: ', 12 + measureTextWidth(ctx, username), 54); - ctx.fillStyle = '#ff0000'; - ctx.fillText("Lil' Zik", 12 + measureTextWidth(ctx, `${username} found something special: `), 54); - - /* You have a funny feeling like you're being followed. */ - ctx.fillText("You have a funny feeling like you're being followed.", 11, 40); - - /* Username */ - ctx.fillStyle = '#000000'; - ctx.fillText(`${username}: `, 11, 70); - ctx.fillStyle = '#0000ff'; - ctx.fillText('*', 12 + measureTextWidth(ctx, `${username}: `), 70); -} - -function zammy(ctx: SKRSContext2D, username: string) { - ctx.fillText("Your K'ril Tsutsaroth kill count is: ", 11, 10); - ctx.fillStyle = '#ff0000'; - ctx.fillText(randInt(1, 20).toString(), 12 + measureTextWidth(ctx, "Your K'ril Tsutsaroth kill count is: "), 10); - - ctx.fillStyle = '#ff0000'; - ctx.fillText("You have a funny feeling like you're being followed.", 11, 25); - - ctx.fillStyle = '#005f00'; - ctx.fillText(`${username} received a drop: Pet k'ril tsutsaroth`, 11, 40); - - ctx.fillStyle = '#005f00'; - ctx.fillText(`${username} received a drop: ${Math.random() > 0.5 ? 'Zamorak hilt' : 'Staff of the dead'}`, 11, 54); - - /* Username */ - const randMessage = randomMessages[Math.floor(Math.random() * randomMessages.length)]; - ctx.fillStyle = '#000000'; - ctx.fillText(`${username}: `, 11, 69); - ctx.fillStyle = '#0000ff'; - ctx.fillText(`${randMessage}*`, 12 + measureTextWidth(ctx, `${username}: `), 69); -} - -const thingMap = [ - [new Set(['zammy', 'zamorak']), zammy], - [new Set(['tob', 'scythe']), scythe], - [new Set(['sara', 'zilyana', 'zily']), sara], - [new Set(['corp', 'ely']), ely], - [new Set(['bandos']), bandos], - [new Set(['arma', 'armadyl']), arma] -] as const; - -export const fakeCommand: OSBMahojiCommand = { - name: 'fake', - description: 'Generate fake images of getting loot.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'type', - description: 'The type you want to generate.', - required: true, - choices: thingMap.map(i => Array.from(i[0])[0]).map(i => ({ name: i, value: i })) - }, - { - type: ApplicationCommandOptionType.String, - name: 'username', - description: 'The username to put on the image.', - required: true - } - ], - run: async ({ options }: CommandRunOptions<{ type: string; username: string }>) => { - const canvas = new Canvas(399, 100); - const ctx = canvas.getContext('2d'); - - ctx.font = '16px OSRSFont'; - ctx.fillStyle = '#000000'; - - const image = await bg; - ctx.drawImage(image, 0, 0, image.width, image.height); - for (const [names, fn] of thingMap) { - if (names.has(options.type.toLowerCase())) { - fn(ctx, options.username); - return { - files: [ - { - attachment: await canvas.encode('png'), - name: `${Math.round(Math.random() * 10_000)}.jpg` - } - ] - }; - } - } - return 'Invalid input.'; - } -}; diff --git a/src/mahoji/commands/fakepm.ts b/src/mahoji/commands/fakepm.ts deleted file mode 100644 index 9b4f3927f6..0000000000 --- a/src/mahoji/commands/fakepm.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Canvas } from '@napi-rs/canvas'; -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; - -import { loadAndCacheLocalImage } from '../../lib/util/canvasUtil'; -import type { OSBMahojiCommand } from '../lib/util'; - -const bg = loadAndCacheLocalImage('./src/lib/resources/images/pm-bg.png'); - -export const fakepmCommand: OSBMahojiCommand = { - name: 'fakepm', - description: 'Generate fake images of PMs.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'username', - description: 'The username to put on the image.', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'message', - description: 'The message.', - required: true - } - ], - run: async ({ options }: CommandRunOptions<{ message: string; username: string }>) => { - const canvas = new Canvas(376, 174); - const ctx = canvas.getContext('2d'); - ctx.font = '16px OSRSFont'; - const img = await bg; - ctx.drawImage(img, 0, 0, img.width, img.height); - - ctx.fillStyle = '#000000'; - ctx.fillText(`From ${options.username}: ${options.message}`, 6, 98); - ctx.fillStyle = '#00ffff'; - ctx.fillText(`From ${options.username}: ${options.message}`, 5, 97); - - return { - files: [{ attachment: await canvas.encode('png'), name: `${Math.round(Math.random() * 10_000)}.jpg` }] - }; - } -}; diff --git a/src/mahoji/commands/finish.ts b/src/mahoji/commands/finish.ts deleted file mode 100644 index 5f6e51f324..0000000000 --- a/src/mahoji/commands/finish.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { AttachmentBuilder } from 'discord.js'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { notEmpty } from 'e'; -import { Bank } from 'oldschooljs'; - -import { finishables } from '../../lib/finishables'; -import { sorts } from '../../lib/sorts'; -import { stringMatches } from '../../lib/util'; -import { deferInteraction } from '../../lib/util/interactionReply'; -import { makeBankImage } from '../../lib/util/makeBankImage'; -import { Workers } from '../../lib/workers'; -import type { OSBMahojiCommand } from '../lib/util'; - -export const finishCommand: OSBMahojiCommand = { - name: 'finish', - description: 'Simulate finishing a CL.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'input', - description: 'The CL/thing you want to finish. (e.g. corp, pets, raids)', - required: true, - autocomplete: async (value: string) => { - return finishables - .filter(i => (!value ? true : i.name.toLowerCase().includes(value.toLowerCase()))) - .map(i => ({ name: i.name, value: i.name })); - } - }, - { - type: ApplicationCommandOptionType.Boolean, - name: 'tertiaries', - description: 'Whether or not to include Tertiaries (e.g. capes)', - required: false - } - ], - run: async ({ interaction, options }: CommandRunOptions<{ input: string; tertiaries?: boolean }>) => { - await deferInteraction(interaction); - const { input: finishable, tertiaries } = options; - const val = finishables.find( - i => stringMatches(i.name, finishable) || i.aliases?.some(alias => stringMatches(alias, finishable)) - ); - if (!val) return "That's not a valid thing you can simulate finishing."; - const workerRes = await Workers.finish({ name: val.name, tertiaries }); - if (typeof workerRes === 'string') return workerRes; - const { kc } = workerRes; - const kcBank = new Bank(workerRes.kcBank); - const loot = new Bank(workerRes.loot); - if ('customResponse' in val && val.customResponse) { - return val.customResponse(kc); - } - const image = await makeBankImage({ bank: loot, title: `Loot from ${kc}x ${val.name}` }); - - const result = `It took you ${kc.toLocaleString()} KC to finish the ${val.name} CL.`; - const finishStr = kcBank.items().sort(sorts.quantity).reverse(); - - const cost = new Bank(workerRes.cost); - const costImage = - cost.length > 0 - ? await makeBankImage({ bank: cost, title: `Estimated Cost for ${kc}x ${val.name}` }) - : undefined; - - if (finishStr.length < 20) { - return { - content: `${result} -${finishStr.map(i => `**${i[0].name}:** ${i[1]} KC`).join('\n')}`, - files: [image.file, costImage?.file].filter(notEmpty) - }; - } - - return { - content: `It took you ${kc.toLocaleString()} KC to finish the ${val.name} CL.`, - files: [ - image.file, - new AttachmentBuilder(Buffer.from(finishStr.map(i => `${i[0].name}: ${i[1]} KC`).join('\n')), { - name: 'finish.txt' - }), - costImage?.file - ].filter(notEmpty) - }; - } -}; diff --git a/src/mahoji/commands/gamble.ts b/src/mahoji/commands/gamble.ts index 4d553cf0cf..23a5d47c15 100644 --- a/src/mahoji/commands/gamble.ts +++ b/src/mahoji/commands/gamble.ts @@ -1,280 +1,97 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import type { MahojiUserOption } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { randArrItem } from 'e'; +import { deepClone, randArrItem, removeFromArr } from 'e'; +import { omit } from 'lodash'; import { Bank } from 'oldschooljs'; - -import { BitField } from '../../lib/constants'; - -import { isSuperUntradeable } from '../../lib/util'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; +import { Events } from '../../lib/constants'; +import { allCLItemsFiltered } from '../../lib/data/Collections'; +import { RelicID } from '../../lib/relics'; +import type { SkillNameType } from '../../lib/skilling/types'; +import { cryptoRand, toKMB } from '../../lib/util'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import { capeGambleCommand, capeGambleStatsCommand } from '../lib/abstracted_commands/capegamble'; -import { diceCommand } from '../lib/abstracted_commands/diceCommand'; -import { duelCommand } from '../lib/abstracted_commands/duelCommand'; -import { hotColdCommand } from '../lib/abstracted_commands/hotColdCommand'; -import { luckyPickCommand } from '../lib/abstracted_commands/luckyPickCommand'; -import { slotsCommand } from '../lib/abstracted_commands/slotsCommand'; import type { OSBMahojiCommand } from '../lib/util'; export const gambleCommand: OSBMahojiCommand = { name: 'gamble', - description: 'Partake in various gambling activities.', - options: [ - /** - * - * Cape - * - */ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'item', - description: 'Allows you to gamble fire/infernal capes/quivers for a chance at the pets.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'item', - description: 'The item you wish to gamble.', - required: false, - choices: [ - { name: 'fire', value: 'fire' }, - { name: 'infernal', value: 'infernal' }, - { name: 'quiver', value: 'quiver' } - ] - }, - { - type: ApplicationCommandOptionType.Boolean, - name: 'autoconfirm', - description: "Don't ask confirmation message", - required: false - } - ] - }, - /** - * - * Dice - * - */ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'dice', - description: 'Allows you to simulate dice rolls, or dice your bot GP.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'amount', - description: 'Amount you wish to gamble.', - required: false - } - ] - }, - /** - * - * Duel - * - */ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'duel', - description: 'Simulates dueling another player, or allows you to duel another player for their bot GP.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user you want to duel.', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'amount', - description: 'The GP you want to duel for.', - required: false - } - ] - }, - /** - * - * Lucky Pick - * - */ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'lucky_pick', - description: 'Allows you play lucky pick and risk your GP.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'amount', - description: 'Amount you wish to gamble.', - required: true - } - ] - }, - /** - * - * Slots - * - */ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'slots', - description: 'Allows you play slots and risk your GP to win big.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'amount', - description: 'Amount you wish to gamble.', - required: false - } - ] - }, - /** - * - * Hot Cold - * - */ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'hot_cold', - description: 'Allows you play Hot Cold and risk your GP to win big.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'choice', - description: 'The flower type you want to guess.', - required: false, - choices: ['hot', 'cold'].map(i => ({ name: i, value: i })) - }, - { - type: ApplicationCommandOptionType.String, - name: 'amount', - description: 'Amount you wish to gamble.', - required: false - } - ] - }, - /** - * - * Give Random Item - * - */ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'give_random_item', - description: 'Give a random item from your bank to someone.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user to give a random item too.', - required: true - } - ] - } - ], - run: async ({ - options, - interaction, - guildID, - userID - }: CommandRunOptions<{ - item?: { item?: string; autoconfirm?: boolean }; - dice?: { amount?: string }; - duel?: { user: MahojiUserOption; amount?: string }; - lucky_pick?: { amount: string }; - slots?: { amount?: string }; - hot_cold?: { choice?: 'hot' | 'cold'; amount?: string }; - give_random_item?: { user: MahojiUserOption }; - }>) => { + description: 'Use your Relic of gambling.', + options: [], + run: async ({ interaction, userID }) => { const user = await mUserFetch(userID); - - if (options.item) { - if (options.item.item) { - return capeGambleCommand(user, options.item.item, interaction, options.item.autoconfirm); - } - return capeGambleStatsCommand(user); - } - - if (options.duel) { - const targetUser = await mUserFetch(options.duel.user.user.id); - // Block duels when one user has the BitField set, but only when wagering an amount - if (options.duel.amount && [user, targetUser].some(u => u.bitfield.includes(BitField.SelfGamblingLocked))) { - return 'One of you has gambling disabled and cannot participate in this duel!'; - } - return duelCommand(user, interaction, targetUser, options.duel.user, options.duel.amount); - } - - if (options.dice) { - if (user.bitfield.includes(BitField.SelfGamblingLocked) && options.dice.amount) { - return 'You have gambling disabled and cannot gamble!'; - } - return diceCommand(user, interaction, options.dice.amount); - } - - // Block GP Gambling from users with the BitField set: - if (user.bitfield.includes(BitField.SelfGamblingLocked)) { - return 'You have gambling disabled and cannot gamble!'; + if (!user.hasRelic(RelicID.Gambling)) { + return 'You need the Relic of Gambling to use this command.'; } - if (options.lucky_pick) { - return luckyPickCommand(user, options.lucky_pick.amount, interaction); + if (user.cl.length < 50) { + return 'You need at least 50 items in your collection log to use this command.'; } - if (options.slots) { - return slotsCommand(interaction, user, options.slots.amount); - } - - if (options.hot_cold) { - return hotColdCommand(interaction, user, options.hot_cold.choice, options.hot_cold.amount); - } - - if (options.give_random_item) { - const senderUser = user; - const recipientuser = await mUserFetch(options.give_random_item.user.user.id); - if (recipientuser.id === senderUser.id) { - return "You can't do it with yourself."; - } - - if (senderUser.isIronman || recipientuser.isIronman) { - return 'One of you is an ironman.'; + await handleMahojiConfirmation(interaction, 'Are you sure?'); + if (cryptoRand(0, 1) === 0) { + let i = 0; + const itemsToRemove = new Bank(); + while (itemsToRemove.length < 50) { + const item = user.cl.random(); + if (!item) { + return 'Something went very wrong...'; + } + if (!itemsToRemove.has(item.id)) { + itemsToRemove.add(item.id); + } + if (i++ > 200) { + return 'Something went wrong...'; + } } - await handleMahojiConfirmation( - interaction, - `Are you sure you want to give a random stack of items from your bank to ${recipientuser.usernameOrMention}? Untradeable and favorited items are not included.` + let newCL: ItemBank = deepClone(user.user.collectionLogBank as ItemBank); + newCL = omit( + newCL, + itemsToRemove.items().map(i => i[0].id) ); - - await senderUser.sync(); - const bank = senderUser.bank - .items() - .filter(i => !isSuperUntradeable(i[0].id)) - .filter(i => !user.user.favoriteItems.includes(i[0].id)); - const entry = randArrItem(bank); - if (!entry) return 'You have no items you can give away!'; - const [item, qty] = entry; - const loot = new Bank().add(item.id, qty); - - await transactItems({ userID: senderUser.id, itemsToRemove: loot }); - await transactItems({ - userID: recipientuser.id, - itemsToAdd: loot, - collectionLog: false, - filterLoot: false - }); - await prisma.economyTransaction.create({ - data: { - guild_id: guildID ? BigInt(guildID) : undefined, - sender: BigInt(senderUser.id), - recipient: BigInt(recipientuser.id), - items_sent: loot.bank, - items_received: undefined, - type: 'gri' - } + let newBank: ItemBank = deepClone(user.user.bank as ItemBank); + newBank = omit( + newBank, + itemsToRemove.items().map(i => i[0].id) + ); + const highestSkill = Object.entries(user.skillsAsXP).sort((a, b) => b[1] - a[1])[0][0]; + const currentHighestXP = user.skillsAsXP[highestSkill as SkillNameType]; + await user.update({ + bank: newBank, + collectionLogBank: newCL, + [`skills_${highestSkill}`]: currentHighestXP / 2, + relics: removeFromArr(user.user.relics, RelicID.Gambling) }); - const debug = new Bank(); - for (const t of bank) debug.add(t[0].id); - return `You gave ${qty.toLocaleString()}x ${item.name} to ${recipientuser.usernameOrMention}.`; + globalClient.emit( + Events.ServerNotification, + `${user} just lost 50 items from their collection log and ${toKMB(currentHighestXP / 2)} ${highestSkill} XP.` + ); + return `You lost 50 items from your collection log and ${toKMB(currentHighestXP / 2)} ${highestSkill} XP.`; } - return 'Invalid command.'; + /** + * Won + */ + const itemsToAddToCL = new Bank(); + while (itemsToAddToCL.length < 50) { + const itemToAdd = randArrItem(allCLItemsFiltered); + if (!itemsToAddToCL.has(itemToAdd) && !user.cl.has(itemToAdd)) { + itemsToAddToCL.add(itemToAdd); + } + } + const newCL: ItemBank = deepClone(user.user.collectionLogBank as ItemBank); + for (const [{ id }, _] of itemsToAddToCL.items()) { + newCL[id] = 1; + } + const lowestSkill = Object.entries(user.skillsAsXP).sort((a, b) => a[1] - b[1])[0][0]; + const currentLowestXP = user.skillsAsXP[lowestSkill as SkillNameType]; + await user.update({ + collectionLogBank: newCL, + [`skills_${lowestSkill}`]: currentLowestXP * 2, + relics: removeFromArr(user.user.relics, RelicID.Gambling) + }); + + globalClient.emit( + Events.ServerNotification, + `${user} just won 50 items added to their collection log and ${toKMB(currentLowestXP * 2)} ${lowestSkill} XP.` + ); + return `You lost 50 items from your collection log and ${toKMB(currentLowestXP * 2)} ${lowestSkill} XP.`; } }; diff --git a/src/mahoji/commands/ge.ts b/src/mahoji/commands/ge.ts deleted file mode 100644 index 15318686ff..0000000000 --- a/src/mahoji/commands/ge.ts +++ /dev/null @@ -1,527 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import type { CommandOption } from '@oldschoolgg/toolkit'; -import { evalMathExpression } from '@oldschoolgg/toolkit'; -import type { GEListing, GETransaction } from '@prisma/client'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { sumArr, uniqueArr } from 'e'; - -import { PerkTier } from '../../lib/constants'; -import { GrandExchange, createGECancelButton } from '../../lib/grandExchange'; -import { marketPricemap } from '../../lib/marketPrices'; - -import { Bank } from 'oldschooljs'; -import type { ItemBank } from 'oldschooljs/dist/meta/types'; -import { - formatDuration, - isGEUntradeable, - itemNameFromID, - makeComponents, - returnStringOrFile, - toKMB -} from '../../lib/util'; -import { createChart } from '../../lib/util/chart'; -import getOSItem, { getItem } from '../../lib/util/getOSItem'; -import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import { deferInteraction } from '../../lib/util/interactionReply'; -import itemIsTradeable from '../../lib/util/itemIsTradeable'; -import { cancelGEListingCommand } from '../lib/abstracted_commands/cancelGEListingCommand'; -import { itemArr, itemOption } from '../lib/mahojiCommandOptions'; -import type { OSBMahojiCommand } from '../lib/util'; -import { mahojiUsersSettingsFetch } from '../mahojiSettings'; - -export type GEListingWithTransactions = GEListing & { - buyTransactions: GETransaction[]; - sellTransactions: GETransaction[]; -}; - -function parseNumber(str: string) { - if (typeof str === 'number') str = `${str}`; - const number = evalMathExpression(str); - if (!number) return -1; - return number; -} - -function geListingToString( - listing: GEListingWithTransactions, - buyLimit?: Awaited> -) { - const item = getOSItem(listing.item_id); - const allTransactions = [...listing.buyTransactions, ...listing.sellTransactions]; - const verb = listing.type === 'Buy' ? 'Buying' : 'Selling'; - const pastVerb = listing.type === 'Buy' ? 'bought' : 'sold'; - const action = listing.type.toLowerCase(); - const itemQty = `${toKMB(listing.total_quantity)} ${item.name}`; - - const totalSold = listing.total_quantity - listing.quantity_remaining; - const totalPricePaidSoFar = toKMB( - sumArr(allTransactions.map(i => i.quantity_bought * Number(i.price_per_item_after_tax))) - ); - const totalTaxPaidSoFar = toKMB(sumArr(allTransactions.map(i => Number(i.total_tax_paid)))); - - if (listing.cancelled_at) { - return `Cancelled offer to ${action} ${itemQty}. ${totalSold} ${pastVerb}.`; - } - - if (listing.fulfilled_at) { - return `Completed offer to ${action} ${itemQty}. ${totalSold} ${pastVerb} for a total amount of ${totalPricePaidSoFar} (${totalTaxPaidSoFar} tax paid).`; - } - - const buyLimitStr = - buyLimit !== undefined && buyLimit.remainingItemsCanBuy !== buyLimit.buyLimit - ? ` (${buyLimit.remainingItemsCanBuy.toLocaleString()}/${buyLimit.buyLimit.toLocaleString()} remaining in buy limit currently)` - : ''; - - return `${verb} ${itemQty}, ${toKMB( - listing.quantity_remaining - )} are remaining to ${listing.type.toLowerCase()}, asking for ${toKMB( - Number(listing.asking_price_per_item) - )} GP each. ${allTransactions.length}x transactions made.${buyLimitStr}`; -} - -const quantityOption: CommandOption = { - name: 'quantity', - description: 'The quantity of the item to exchange (e.g. 7, 5k, 10m).', - type: ApplicationCommandOptionType.String, - required: true -}; - -const priceOption: CommandOption = { - name: 'price', - description: 'The price to exchange each item at. (e.g. 7, 5k, 10m).', - type: ApplicationCommandOptionType.String, - required: true -}; - -export const geCommand: OSBMahojiCommand = { - name: 'ge', - description: 'Exchange grandly with other players on the bot!', - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'buy', - description: 'Purchase something from the grand exchange.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'item', - description: 'The item you want to pick.', - required: true, - autocomplete: async (value, user) => { - if (!value) { - const tradesOfUser = ( - await prisma.gEListing.findMany({ - where: { - user_id: user.id, - type: 'Buy' - }, - select: { - item_id: true - }, - take: 10 - }) - ).map(i => i.item_id); - return uniqueArr(tradesOfUser).map(itemID => ({ - name: itemNameFromID(itemID)!, - value: itemID.toString() - })); - } - const res = itemArr.filter(i => i.key.includes(value.toLowerCase())); - return res.map(i => ({ name: `${i.name}`, value: i.id.toString() })); - } - }, - quantityOption, - priceOption - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'sell', - description: 'Sell something on the grand exchange.', - options: [ - { - name: 'item', - type: ApplicationCommandOptionType.String, - description: 'The item you want to sell.', - required: true, - autocomplete: async (value, { id }) => { - const raw = await mahojiUsersSettingsFetch(id, { bank: true }); - const bank = new Bank(raw.bank as ItemBank); - - return bank - .items() - .filter(i => !isGEUntradeable(i[0].id)) - .filter(i => (!value ? true : i[0].name.toLowerCase().includes(value.toLowerCase()))) - .map(i => ({ name: `${i[0].name} (${i[1]}x Owned)`, value: i[0].name })); - } - }, - quantityOption, - priceOption - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'my_listings', - description: 'View your listings', - options: [ - { - type: ApplicationCommandOptionType.Integer, - name: 'page', - description: 'The page you want to view.', - required: false, - min_value: 1, - max_value: 10 - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'cancel', - description: 'Cancel one of your listings.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'listing', - description: 'The listing to cancel.', - required: true, - autocomplete: async (_, user) => { - const listings = await prisma.gEListing.findMany({ where: { user_id: user.id } }); - return listings - .filter(i => !i.cancelled_at && !i.fulfilled_at && i.quantity_remaining > 0) - .map(l => ({ - name: `${l.type} ${l.total_quantity}x ${itemNameFromID(l.item_id)!}`, - value: l.userfacing_id - })); - } - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'stats', - description: 'View your g.e stats', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'price', - description: 'Lookup the market price of an item on the g.e', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'item', - description: 'The item to lookup.', - required: true, - autocomplete: async input => { - const listings = Array.from(marketPricemap.values()); - return listings - .filter(i => - !input ? true : itemNameFromID(i.itemID)?.toLowerCase().includes(input.toLowerCase()) - ) - .map(l => ({ - name: `${itemNameFromID(l.itemID)!}`, - value: l.itemID - })); - } - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'view', - description: 'Lookup information about an item on the g.e', - options: [ - { - ...itemOption(item => Boolean(item.tradeable_on_ge)), - name: 'item', - description: 'The item to view.' - } - ] - } - ], - run: async ({ - options, - userID, - interaction - }: CommandRunOptions<{ - buy?: { - item: string; - quantity: string; - price: string; - }; - sell?: { - item: string; - quantity: string; - price: string; - }; - cancel?: { - listing: string; - }; - my_listings?: { - page: number; - }; - stats?: {}; - price?: { item: string }; - view?: { item?: string }; - }>) => { - await deferInteraction(interaction); - const user = await mUserFetch(userID); - - if (options.price) { - const data = marketPricemap.get(Number(options.price.item)); - if (!data) { - return "We don't have price data for that item."; - } - return `The current market price of ${itemNameFromID(data.itemID)!} is ${toKMB( - data.guidePrice - )} (${data.guidePrice.toLocaleString()}) GP. - -**Recent price:** ${toKMB(data.averagePriceLast100)} (${data.averagePriceLast100.toLocaleString()}) GP. - -This price is a guide only, calculated from average sale prices, it may not be accurate, it may change, or not reflect the true market price.`; - } - - if (options.stats) { - const totalGPYourSales = await prisma.gETransaction.aggregate({ - where: { - sell_listing: { - user_id: userID - } - }, - _sum: { - total_tax_paid: true - } - }); - const totalGPYourTransactions = await prisma.gETransaction.aggregate({ - where: { - OR: [ - { - sell_listing: { - user_id: userID - } - }, - { - buy_listing: { - user_id: userID - } - } - ] - }, - _sum: { - total_tax_paid: true - } - }); - - const { slots } = await GrandExchange.calculateSlotsOfUser(user); - - return ` -The next buy limit reset is at: ${GrandExchange.getInterval().nextResetStr}, it resets every ${formatDuration( - GrandExchange.config.buyLimit.interval - )}. - - -**G.E Slots You Can Use:** ${slots} -**Taxes you have paid:** ${(totalGPYourSales._sum.total_tax_paid ?? 0).toLocaleString()} GP -**Total Tax Paid on your sales AND purchases:** ${(totalGPYourTransactions._sum.total_tax_paid ?? 0).toLocaleString()} GP`; - } - - if (options.my_listings) { - const activeListings = await prisma.gEListing.findMany({ - where: { - user_id: userID, - quantity_remaining: { - gt: 0 - }, - fulfilled_at: null, - cancelled_at: null - }, - include: { - buyTransactions: true, - sellTransactions: true - }, - orderBy: { - created_at: 'desc' - } - }); - const recentInactiveListings = await prisma.gEListing.findMany({ - where: { - user_id: userID, - OR: [ - { - fulfilled_at: { - not: null - } - }, - { - cancelled_at: { - not: null - } - }, - { - quantity_remaining: 0 - } - ] - }, - include: { - buyTransactions: true, - sellTransactions: true - }, - orderBy: { - created_at: 'desc' - }, - take: 5 - }); - - const image = await geImageGenerator.createInterface({ - user, - page: options.my_listings.page ?? 1, - activeListings - }); - - const geResult = { - content: `**Active Listings**\n${( - await Promise.all( - activeListings.map(async listing => { - const buyLimit = await GrandExchange.checkBuyLimitForListing(listing); - return geListingToString(listing, buyLimit); - }) - ) - ).join('\n')}\n\n**Recent Fulfilled/Cancelled Listings**\n${recentInactiveListings - .map(i => geListingToString(i)) - .join('\n')}`, - files: [ - { - name: 'ge.png', - attachment: image! - } - ] - }; - - return returnStringOrFile(geResult); - } - - if (options.view) { - if (options.view.item) { - const item = getItem(options.view.item); - if (!item) return 'Invalid item.'; - if (!itemIsTradeable(item.id)) return 'That item is not tradeable on the Grand Exchange.'; - const priceData = marketPricemap.get(item.id); - let baseMessage = `**${item.name}** -**Buy Limit Per 4 hours:** ${GrandExchange.getItemBuyLimit(item).toLocaleString()}`; - if (priceData) { - baseMessage += ` -**Market Price:** ${toKMB(priceData.guidePrice)} (${priceData.guidePrice.toLocaleString()}) GP. -**Recent Price:** ${toKMB(priceData.averagePriceLast100)} (${priceData.averagePriceLast100.toLocaleString()}) GP. - -`; - } - if (user.perkTier() < PerkTier.Four) { - return baseMessage; - } - let result = await prisma.$queryRawUnsafe< - { total_quantity_bought: number; average_price_per_item_before_tax: number; week: Date }[] - >(`SELECT - DATE_TRUNC('week', sellTransactions.created_at) AS week, - AVG(sellTransactions.price_per_item_before_tax) AS average_price_per_item_before_tax, - SUM(sellTransactions.quantity_bought)::int AS total_quantity_bought -FROM - ge_listing -INNER JOIN - ge_transaction AS sellTransactions ON ge_listing.id = sellTransactions.sell_listing_id -WHERE - ge_listing.item_id = ${item.id} -AND - ge_listing.cancelled_at IS NULL -AND - ge_listing.fulfilled_at IS NOT NULL -GROUP BY - week -ORDER BY - week ASC; -`); - if (result.length < 1) return baseMessage; - if (result[0].average_price_per_item_before_tax <= 1_000_000) { - result = result.filter(i => i.total_quantity_bought > 1); - } - const buffer = await createChart({ - title: `Price History for ${item.name}`, - format: 'kmb', - values: result.map(i => [new Date(i.week).toDateString(), i.average_price_per_item_before_tax]), - type: 'line' - }); - - return { - content: baseMessage, - files: [buffer] - }; - } - } - - if (GrandExchange.locked) { - return 'The Grand Exchange is currently locked, please try again later.'; - } - - if (options.buy || options.sell) { - const result = await GrandExchange.preCreateListing({ - user, - itemName: (options.buy?.item ?? options.sell?.item)!, - price: parseNumber((options.buy?.price ?? options.sell?.price)!), - quantity: parseNumber((options.buy?.quantity ?? options.sell?.quantity)!), - type: options.buy ? 'Buy' : 'Sell' - }); - - if ('error' in result) return result.error; - - await handleMahojiConfirmation(interaction, result.confirmationStr); - } - - if (options.cancel) { - return cancelGEListingCommand(user, options.cancel.listing); - } - - if (options.buy) { - const result = await GrandExchange.createListing({ - user, - itemName: options.buy?.item, - price: parseNumber(options.buy?.price), - quantity: parseNumber(options.buy?.quantity), - type: 'Buy' - }); - - if (typeof result.error === 'string') return result.error; - const { createdListing } = result; - - return { - content: `Successfully created a listing to buy ${toKMB( - createdListing.total_quantity - )} ${itemNameFromID(createdListing.item_id)} for ${toKMB( - Number(createdListing.asking_price_per_item) - )} GP each.`, - components: makeComponents([createGECancelButton(createdListing)]) - }; - } - - if (options.sell) { - const result = await GrandExchange.createListing({ - user, - itemName: options.sell.item, - price: parseNumber(options.sell.price), - quantity: parseNumber(options.sell.quantity), - type: 'Sell' - }); - - if (typeof result.error === 'string') return result.error; - const { createdListing } = result; - - return { - content: `Successfully created a listing to sell ${toKMB( - createdListing.total_quantity - )} ${itemNameFromID(createdListing.item_id)} for ${toKMB( - Number(createdListing.asking_price_per_item) - )} GP each.`, - components: makeComponents([createGECancelButton(createdListing)]) - }; - } - - return 'Invalid command.'; - } -}; diff --git a/src/mahoji/commands/gear.ts b/src/mahoji/commands/gear.ts index c1d80cae2b..af03654b95 100644 --- a/src/mahoji/commands/gear.ts +++ b/src/mahoji/commands/gear.ts @@ -3,10 +3,8 @@ import { toTitleCase } from '@oldschoolgg/toolkit'; import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { ApplicationCommandOptionType } from 'discord.js'; -import type { GearSetupType } from '@prisma/client'; -import { gearValidationChecks } from '../../lib/constants'; import { allPetIDs } from '../../lib/data/CollectionsExport'; -import { GearSetupTypes, GearStat } from '../../lib/gear'; +import { type GearSetupType, GearSetupTypes, GearStat } from '../../lib/gear'; import { generateGearImage } from '../../lib/gear/functions/generateGearImage'; import { equipPet } from '../../lib/minions/functions/equipPet'; import { unequipPet } from '../../lib/minions/functions/unequipPet'; @@ -19,7 +17,7 @@ import { gearUnequipCommand, gearViewCommand } from '../lib/abstracted_commands/gearCommands'; -import { equippedItemOption, gearPresetOption, gearSetupOption, ownedItemOption } from '../lib/mahojiCommandOptions'; +import { equippedItemOption, gearSetupOption, ownedItemOption } from '../lib/mahojiCommandOptions'; import type { OSBMahojiCommand } from '../lib/util'; import { getMahojiBank, mahojiUsersSettingsFetch } from '../mahojiSettings'; @@ -36,21 +34,11 @@ export const gearCommand: OSBMahojiCommand = { ...gearSetupOption, required: true }, - { - type: ApplicationCommandOptionType.String, - name: 'items', - description: 'A list of equippable items to equip.' - }, { ...ownedItemOption(item => Boolean(item.equipable_by_player) && Boolean(item.equipment)), name: 'item', description: 'The item you want to equip.' }, - { - ...gearPresetOption, - required: false, - name: 'preset' - }, { type: ApplicationCommandOptionType.Integer, name: 'quantity', @@ -197,8 +185,6 @@ export const gearCommand: OSBMahojiCommand = { equip?: { gear_setup: GearSetupType; item?: string; - items?: string; - preset?: string; quantity?: number; auto?: string; }; @@ -234,20 +220,13 @@ ${res files: [await totalCanvas.encode('png')] }; } - if ((options.equip || options.unequip) && !gearValidationChecks.has(userID)) { - const { itemsUnequippedAndRefunded } = await user.validateEquippedGear(); - if (itemsUnequippedAndRefunded.length > 0) { - return `You had some items equipped that you didn't have the requirements to use, so they were unequipped and refunded to your bank: ${itemsUnequippedAndRefunded}`; - } - } + if (options.equip) { return gearEquipCommand({ interaction, userID: user.id, setup: options.equip.gear_setup, item: options.equip.item, - items: options.equip.items, - preset: options.equip.preset, quantity: options.equip.quantity, unEquippedItem: undefined, auto: options.equip.auto diff --git a/src/mahoji/commands/gearpresets.ts b/src/mahoji/commands/gearpresets.ts deleted file mode 100644 index 79cfd86e61..0000000000 --- a/src/mahoji/commands/gearpresets.ts +++ /dev/null @@ -1,405 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import type { CommandOption } from '@oldschoolgg/toolkit'; -import type { GearPreset } from '@prisma/client'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { EquipmentSlot } from 'oldschooljs/dist/meta/types'; - -import { production } from '../../config'; -import { ParsedCustomEmojiWithGroups } from '../../lib/constants'; -import { generateGearImage } from '../../lib/gear/functions/generateGearImage'; -import type { GearSetup, GearSetupType } from '../../lib/gear/types'; -import { GearSetupTypes } from '../../lib/gear/types'; - -import { Gear, defaultGear, globalPresets } from '../../lib/structures/Gear'; -import { cleanString, isValidGearSetup, isValidNickname, stringMatches } from '../../lib/util'; -import { emojiServers } from '../../lib/util/cachedUserIDs'; -import { getItem } from '../../lib/util/getOSItem'; -import { gearEquipCommand } from '../lib/abstracted_commands/gearCommands'; -import { allEquippableItems, gearPresetOption, gearSetupOption } from '../lib/mahojiCommandOptions'; -import type { OSBMahojiCommand } from '../lib/util'; - -function maxPresets(user: MUser) { - return user.perkTier() * 2 + 4; -} - -type InputGear = Partial>; -type ParsedInputGear = Partial>; -function parseInputGear(inputGear: InputGear) { - const gear: ParsedInputGear = {}; - const remove: EquipmentSlot[] = []; - for (const [key, val] of Object.entries(inputGear)) { - if (val?.toLowerCase() === 'none') { - remove.push(key as EquipmentSlot); - continue; - } - const item = getItem(val); - if (item && item.equipment?.slot === key) { - gear[key as EquipmentSlot] = item.id; - } - } - return { gear, remove }; -} - -function gearPresetToGear(preset: GearPreset): GearSetup { - function gearItem(val: null | number) { - if (val === null) return null; - return { - item: val, - quantity: 1 - }; - } - const newGear = { ...defaultGear }; - newGear.head = gearItem(preset.head); - newGear.neck = gearItem(preset.neck); - newGear.body = gearItem(preset.body); - newGear.legs = gearItem(preset.legs); - newGear.cape = gearItem(preset.cape); - newGear['2h'] = gearItem(preset.two_handed); - newGear.hands = gearItem(preset.hands); - newGear.feet = gearItem(preset.feet); - newGear.shield = gearItem(preset.shield); - newGear.weapon = gearItem(preset.weapon); - newGear.ring = gearItem(preset.ring); - return newGear; -} -export async function createOrEditGearSetup( - user: MUser, - setupToCopy: GearSetupType | undefined, - name: string, - isUpdating: boolean, - gearInput: InputGear, - emoji: string | undefined, - pinned_setup: GearSetupType | 'reset' | undefined -) { - if (name) { - name = cleanString(name).toLowerCase(); - } - if (name.length > 24) return 'Gear preset names must be less than 25 characters long.'; - if (!name) return "You didn't supply a name."; - if (!isUpdating && !isValidNickname(name)) { - return 'Invalid name.'; - } - - if (setupToCopy && !isValidGearSetup(setupToCopy)) { - return "That's not a valid gear setup."; - } - - const userPresets = await prisma.gearPreset.findMany({ where: { user_id: user.id } }); - if (userPresets.some(pre => pre.name === name) && !isUpdating) { - return `You already have a gear preset called \`${name}\`.`; - } - if (!userPresets.some(pre => pre.name === name) && isUpdating) { - return 'You cant update a gearpreset you dont have.'; - } - - const max = maxPresets(user); - if (userPresets.length >= max && !isUpdating) { - return `The maximum amount of gear presets you can have is ${max}, you can unlock more slots by becoming a patron!`; - } - - const { gear: parsedInputGear, remove: forceRemove } = parseInputGear(gearInput); - let gearSetup: Gear | GearSetup | null = null; - if (setupToCopy) { - gearSetup = user.gear[setupToCopy]; - } else if (isUpdating) { - gearSetup = gearPresetToGear(userPresets.find(pre => pre.name === name)!); - } - - // This is required to enable removal of items while editing - for (const slot of forceRemove) { - if (gearSetup !== null) gearSetup[slot] = null; - } - - if (emoji) { - const res = ParsedCustomEmojiWithGroups.exec(emoji); - if (!res || !res[3]) return "That's not a valid emoji."; - emoji = res[3]; - - const cachedEmoji = globalClient.emojis.cache.get(emoji); - if ((!cachedEmoji || !emojiServers.has(cachedEmoji.guild.id)) && production) { - return "Sorry, that emoji can't be used. Only emojis in the main support server, or our emoji servers can be used."; - } - } - - const gearData = { - head: parsedInputGear.head ?? gearSetup?.head?.item ?? null, - neck: parsedInputGear.neck ?? gearSetup?.neck?.item ?? null, - body: parsedInputGear.body ?? gearSetup?.body?.item ?? null, - legs: parsedInputGear.legs ?? gearSetup?.legs?.item ?? null, - cape: parsedInputGear.cape ?? gearSetup?.cape?.item ?? null, - two_handed: parsedInputGear['2h'] ?? gearSetup?.['2h']?.item ?? null, - hands: parsedInputGear.hands ?? gearSetup?.hands?.item ?? null, - feet: parsedInputGear.feet ?? gearSetup?.feet?.item ?? null, - shield: parsedInputGear.shield ?? gearSetup?.shield?.item ?? null, - weapon: parsedInputGear.weapon ?? gearSetup?.weapon?.item ?? null, - ring: parsedInputGear.ring ?? gearSetup?.ring?.item ?? null, - ammo: parsedInputGear.ammo ?? gearSetup?.ammo?.item ?? null, - ammo_qty: gearSetup?.ammo?.quantity ?? null, - emoji_id: emoji ?? undefined, - pinned_setup: !pinned_setup || pinned_setup === 'reset' ? undefined : pinned_setup - }; - - if (gearData.two_handed !== null) { - gearData.shield = null; - gearData.weapon = null; - } - - const preset = await prisma.gearPreset.upsert({ - where: { - user_id_name: { - user_id: user.id, - name - } - }, - update: { - ...gearData, - times_equipped: { - increment: 1 - } - }, - create: { - ...gearData, - name, - user_id: user.id, - times_equipped: 1 - } - }); - - return `Successfully ${isUpdating ? 'updated the' : 'made a new'} preset called \`${preset.name}\`.`; -} - -function makeSlotOption(slot: EquipmentSlot): CommandOption { - return { - type: ApplicationCommandOptionType.String, - name: slot, - description: `The item you want to put in the ${slot} slot in this gear setup.`, - required: false, - autocomplete: async (value: string) => { - const matchingItems = ( - value - ? allEquippableItems.filter(i => i.name.toLowerCase().includes(value.toLowerCase())) - : allEquippableItems - ) - .filter(i => i.equipment?.slot === slot) - .slice(0, 20) - .map(i => ({ name: i.name, value: i.name })); - if (!value || 'none'.includes(value.toLowerCase())) { - matchingItems.unshift({ name: 'None', value: 'none' }); - if (matchingItems.length > 20) matchingItems.pop(); - } - return matchingItems; - } - }; -} - -export const gearPresetsCommand: OSBMahojiCommand = { - name: 'gearpresets', - description: 'Manage, equip, unequip your gear presets.', - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'view', - description: 'View your gear setups.', - options: [ - { - ...gearPresetOption, - name: 'preset', - required: true - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'equip', - description: 'Equip an item or preset to one of your gear setups.', - options: [ - { - ...gearSetupOption, - required: true - }, - { - ...gearPresetOption, - name: 'preset', - required: true - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'create', - description: 'Create a new gear preset.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'name', - required: true, - description: 'The name to give this preset.' - }, - { - ...gearSetupOption, - required: false, - name: 'copy_setup', - description: 'Pick a setup to copy/use for this preset.' - }, - ...Object.values(EquipmentSlot).map(makeSlotOption), - { - type: ApplicationCommandOptionType.String, - required: false, - name: 'emoji', - description: 'Pick an emoji for the preset.' - }, - { - type: ApplicationCommandOptionType.String, - required: false, - name: 'pinned_setup', - description: 'Pick a setup to pin this setup too.', - choices: GearSetupTypes.map(i => ({ value: i, name: i })) - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'edit', - description: 'Edit an existing gear preset.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'gear_preset', - description: 'The gear preset you want to select.', - required: true, - autocomplete: async (value, user) => { - const presets = await prisma.gearPreset.findMany({ - where: { - user_id: user.id - }, - select: { - name: true - } - }); - return presets - .filter(i => (!value ? true : i.name.toLowerCase().includes(value.toLowerCase()))) - .map(i => ({ name: i.name, value: i.name })); - } - }, - ...Object.values(EquipmentSlot).map(makeSlotOption), - { - type: ApplicationCommandOptionType.String, - required: false, - name: 'emoji', - description: 'Pick an emoji for the preset.' - }, - { - type: ApplicationCommandOptionType.String, - required: false, - name: 'pinned_setup', - description: 'Pick a setup to pin this setup too.', - choices: [...GearSetupTypes, 'reset'].map(i => ({ value: i, name: i })) - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'delete', - description: 'Delete an existing gear preset.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'preset', - description: 'The gear preset you want to delete.', - required: false, - autocomplete: async (value, user) => { - const presets = await prisma.gearPreset.findMany({ - where: { - user_id: user.id - }, - select: { - name: true - } - }); - return presets - .filter(i => (!value ? true : i.name.toLowerCase().includes(value.toLowerCase()))) - .map(i => ({ name: i.name, value: i.name })); - } - } - ] - } - ], - run: async ({ - options, - userID, - interaction - }: CommandRunOptions<{ - equip?: { gear_setup: GearSetupType; preset: string }; - create?: InputGear & { copy_setup?: GearSetupType; name: string; emoji?: string; pinned_setup?: GearSetupType }; - edit?: InputGear & { gear_preset: string; emoji?: string; pinned_setup?: GearSetupType }; - delete?: { preset: string }; - view?: { preset: string }; - }>) => { - const user = await mUserFetch(userID); - if (options.create) { - return createOrEditGearSetup( - user, - options.create.copy_setup, - options.create.name, - false, - options.create, - options.create.emoji, - options.create.pinned_setup - ); - } - if (options.edit) { - return createOrEditGearSetup( - user, - undefined, - options.edit.gear_preset, - true, - options.edit, - options.edit.emoji, - options.edit.pinned_setup - ); - } - if (options.delete) { - const preset = await prisma.gearPreset.findFirst({ - where: { user_id: userID, name: options.delete.preset } - }); - if (!preset) { - return "You don't have a gear preset with that name."; - } - - await prisma.gearPreset.delete({ - where: { - user_id_name: { - user_id: userID, - name: preset.name - } - } - }); - - return `Successfully deleted your preset called \`${options.delete.preset}\`.`; - } - if (options.equip) { - return gearEquipCommand({ - interaction, - userID: user.id, - setup: options.equip.gear_setup, - item: undefined, - items: undefined, - preset: options.equip.preset, - quantity: undefined, - unEquippedItem: undefined, - auto: undefined - }); - } - if (options.view) { - const preset = - (await prisma.gearPreset.findFirst({ - where: { user_id: userID.toString(), name: options.view.preset } - })) || globalPresets.find(i => stringMatches(i.name, options.view?.preset ?? '')); - if (!preset) return "You don't have a preset with that name."; - const image = await generateGearImage(user, new Gear(preset), null, null); - return { files: [{ attachment: image, name: 'preset.jpg' }] }; - } - - return 'Invalid command.'; - } -}; diff --git a/src/mahoji/commands/gift.ts b/src/mahoji/commands/gift.ts deleted file mode 100644 index 6411618ff6..0000000000 --- a/src/mahoji/commands/gift.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { containsBlacklistedWord, mentionCommand, miniID, truncateString } from '@oldschoolgg/toolkit'; -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import type { MahojiUserOption } from '@oldschoolgg/toolkit'; -import { GiftBoxStatus } from '@prisma/client'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { Bank } from 'oldschooljs'; - -import type { ItemBank } from 'oldschooljs/dist/meta/types'; -import { BLACKLISTED_USERS } from '../../lib/blacklists'; -import { BOT_TYPE } from '../../lib/constants'; - -import { isSuperUntradeable, isValidNickname } from '../../lib/util'; -import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import itemIsTradeable from '../../lib/util/itemIsTradeable'; -import { makeBankImage } from '../../lib/util/makeBankImage'; -import { parseBank } from '../../lib/util/parseStringBank'; -import type { OSBMahojiCommand } from '../lib/util'; - -export const giftCommand: OSBMahojiCommand = { - name: 'gift', - description: 'Create gifts for other users, or open one you received.', - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'open', - description: 'Open one of the gifts you have.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'gift', - description: 'The gift to open.', - required: true, - autocomplete: async (input, user) => { - const gifts = await prisma.giftBox.findMany({ - where: { - owner_id: user.id, - status: GiftBoxStatus.Sent - } - }); - return gifts - .filter(g => { - if (!input) return true; - const str = g.name ?? g.id; - return str.toLowerCase().includes(input.toLowerCase()); - }) - .map(g => ({ name: g.name ? `${g.name} (${g.id})` : g.id, value: g.id })); - } - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'list', - description: 'List the boxes you have.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'create', - description: 'Create a gift.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'items', - description: 'The items to put in this gift.', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'name', - description: 'A name for your gift (optional).', - required: false - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'send', - description: 'Send someone a gift.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'gift', - description: 'The gift to send.', - required: true, - autocomplete: async (input, user) => { - const gifts = await prisma.giftBox.findMany({ - where: { - creator_id: user.id, - status: GiftBoxStatus.Created - } - }); - return gifts - .filter(g => { - if (!input) return true; - const str = g.name ?? g.id; - return str.toLowerCase().includes(input.toLowerCase()); - }) - .map(g => ({ name: g.name ? `${g.name} (${g.id})` : g.id, value: g.id })); - } - }, - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user to send it to.', - required: true - } - ] - } - ], - run: async ({ - options, - userID, - interaction - }: CommandRunOptions<{ - list?: {}; - create?: { - items: string; - name?: string; - }; - send?: { gift: string; user: MahojiUserOption }; - open?: { gift: string }; - }>) => { - const user = await mUserFetch(userID); - if (user.isIronman) { - return 'Ironmen cannot use this command.'; - } - - if (options.list) { - const giftsCreatedNotSent = await prisma.giftBox.findMany({ - where: { - creator_id: user.id, - status: GiftBoxStatus.Created - } - }); - - const giftsOwnedButNotOpened = await prisma.giftBox.findMany({ - where: { - owner_id: user.id, - status: GiftBoxStatus.Sent - } - }); - - return `**Gifts You Haven't Sent Yet:** -${giftsCreatedNotSent.map(g => `${g.name ? `${g.name} (${g.id})` : g.id}: ${new Bank(g.items as ItemBank)}`).join('\n')} - -**Gifts You Haven't Opened Yet:** -${truncateString(giftsOwnedButNotOpened.map(g => `${g.name ? `${g.name} (${g.id})` : g.id}`).join('\n'), 1000)}`; - } - - if (options.open) { - const gift = await prisma.giftBox.findFirst({ - where: { - id: options.open.gift, - owner_id: user.id, - status: GiftBoxStatus.Sent - } - }); - if (!gift) { - return 'Invalid gift box.'; - } - const loot = new Bank(gift.items as ItemBank); - await prisma.giftBox.update({ - where: { - id: gift.id - }, - data: { - status: GiftBoxStatus.Opened - } - }); - await user.addItemsToBank({ - items: loot, - collectionLog: false - }); - const image = await makeBankImage({ - bank: loot, - title: gift.name ?? 'Gift Box', - background: 33 - }); - return { - content: 'You opened the gift box!', - files: [image.file] - }; - } - - if (options.create) { - if ( - options.create.name && - (!isValidNickname(options.create.name) || containsBlacklistedWord(options.create.name)) - ) { - return 'That name cannot be used for your gift box; no special characters, less than 30 characters and no inappropriate words.'; - } - const items = parseBank({ - inputBank: user.bankWithGP, - inputStr: options.create.items, - maxSize: 30 - }); - - if (items.length === 0 || items.length > 20) { - return 'You must provide at least one item, and no more than 20.'; - } - - for (const [item] of items.items()) { - if (BOT_TYPE === 'OSB') { - if (!itemIsTradeable(item.id, true)) { - return `You cannot put ${item.name} in a gift box.`; - } - } - - if (isSuperUntradeable(item.id)) { - return `You cannot put ${item.name} in a gift box.`; - } - } - - if (!user.bankWithGP.has(items.bank)) { - return 'You do not have the required items to create this gift box.'; - } - - await handleMahojiConfirmation( - interaction, - `Are you sure you want to create this gift box? You will **lose these items**, and you **cannot get the items back**! - -${items}` - ); - - await user.removeItemsFromBank(items); - const gift = await prisma.giftBox.create({ - data: { - id: miniID(5), - creator_id: user.id, - items: items.bank, - name: options.create.name, - status: GiftBoxStatus.Created - } - }); - - return `You wrapped up your items into a gift box! You can send it to someone with ${mentionCommand( - globalClient, - 'gift', - 'send' - )}. This gift box has an id of ${gift.id}${gift.name ? `, and is called \`${gift.name}\`` : ''}.`; - } - - if (options.send) { - if (!options.send.gift) { - return 'You must provide a gift box to send.'; - } - if (!options.send.user || options.send.user.user.id === userID || options.send.user.user.bot) { - return 'You must provide a valid user to send the gift box to.'; - } - const recipient = await mUserFetch(options.send.user.user.id); - if (recipient.isIronman || BLACKLISTED_USERS.has(recipient.id)) { - return 'This person cannot receive gift boxes.'; - } - const giftBox = await prisma.giftBox.findFirst({ - where: { - id: options.send.gift, - creator_id: user.id, - status: GiftBoxStatus.Created - } - }); - if (!giftBox || giftBox.status !== GiftBoxStatus.Created) { - return 'Invalid gift box.'; - } - - await handleMahojiConfirmation( - interaction, - `Are you sure you want to send this gift box (${giftBox.id}) to ${recipient.badgedUsername}? You cannot get it back.` - ); - await prisma.giftBox.update({ - where: { - id: giftBox.id - }, - data: { - owner_id: recipient.id, - status: GiftBoxStatus.Sent - } - }); - - await prisma.economyTransaction.create({ - data: { - guild_id: interaction.guildId ? BigInt(interaction.guildId) : undefined, - sender: BigInt(user.id), - recipient: BigInt(recipient.id), - items_sent: giftBox.items as string, - items_received: undefined, - type: 'gift' - } - }); - return `You sent the gift box to ${recipient.badgedUsername}!`; - } - - return 'Invalid options.'; - } -}; diff --git a/src/mahoji/commands/giveaway.ts b/src/mahoji/commands/giveaway.ts deleted file mode 100644 index e2bae75446..0000000000 --- a/src/mahoji/commands/giveaway.ts +++ /dev/null @@ -1,286 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import type { Giveaway } from '@prisma/client'; -import { Duration } from '@sapphire/time-utilities'; -import type { BaseMessageOptions } from 'discord.js'; -import { - ActionRowBuilder, - AttachmentBuilder, - ButtonBuilder, - ButtonStyle, - ChannelType, - EmbedBuilder, - messageLink, - time -} from 'discord.js'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { Time, randInt } from 'e'; -import { Bank } from 'oldschooljs'; -import type { ItemBank } from 'oldschooljs/dist/meta/types'; - -import { Emoji, patronFeatures } from '../../lib/constants'; -import { marketPriceOfBank } from '../../lib/marketPrices'; - -import { channelIsSendable, isModOrAdmin, isSuperUntradeable, makeComponents, toKMB } from '../../lib/util'; -import { generateGiveawayContent } from '../../lib/util/giveaway'; -import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import itemIsTradeable from '../../lib/util/itemIsTradeable'; -import { logError } from '../../lib/util/logError'; -import { makeBankImage } from '../../lib/util/makeBankImage'; -import { parseBank } from '../../lib/util/parseStringBank'; -import { filterOption } from '../lib/mahojiCommandOptions'; -import type { OSBMahojiCommand } from '../lib/util'; -import { addToGPTaxBalance } from '../mahojiSettings'; - -function makeGiveawayButtons(giveawayID: number): BaseMessageOptions['components'] { - return [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId(`GIVEAWAY_ENTER_${giveawayID}`) - .setLabel('Join Giveaway') - .setStyle(ButtonStyle.Primary), - new ButtonBuilder() - .setCustomId(`GIVEAWAY_LEAVE_${giveawayID}`) - .setLabel('Leave Giveaway') - .setStyle(ButtonStyle.Secondary) - ]) - ]; -} - -function makeGiveawayRepeatButton(giveawayID: number) { - return new ButtonBuilder() - .setCustomId(`GIVEAWAY_REPEAT_${giveawayID}`) - .setLabel('Repeat This Giveaway') - .setEmoji('493286312854683654') - .setStyle(ButtonStyle.Danger); -} - -export const giveawayCommand: OSBMahojiCommand = { - name: 'giveaway', - description: 'Giveaway items from your ban to other players.', - attributes: { - requiresMinion: true, - examples: ['/giveaway items:10 trout, 5 coal time:1h'] - }, - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'start', - description: 'Start a giveaway.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'duration', - description: 'The duration of the giveaway (e.g. 1h, 1d).', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'items', - description: 'The items you want to giveaway.', - required: false - }, - filterOption, - { - type: ApplicationCommandOptionType.String, - name: 'search', - description: 'A search query for items in your bank to giveaway.', - required: false - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'list', - description: 'List giveaways active in this server.', - options: [] - } - ], - run: async ({ - options, - userID, - guildID, - interaction, - channelID, - user: apiUser - }: CommandRunOptions<{ - start?: { duration: string; items?: string; filter?: string; search?: string }; - list?: {}; - }>) => { - const user = await mUserFetch(userID); - if (user.isIronman) return 'You cannot do giveaways!'; - const channel = globalClient.channels.cache.get(channelID.toString()); - if (!channelIsSendable(channel)) return 'Invalid channel.'; - - if (options.start) { - const existingGiveaways = await prisma.giveaway.findMany({ - where: { - user_id: user.id, - completed: false - } - }); - if (existingGiveaways.length >= 10 && !isModOrAdmin(user)) { - return 'You cannot have more than 10 giveaways active at a time.'; - } - - if (!guildID) { - return 'You cannot make a giveaway outside a server.'; - } - - const bank = parseBank({ - inputStr: options.start.items, - inputBank: user.bankWithGP, - excludeItems: user.user.favoriteItems, - user, - search: options.start.search, - filters: [options.start.filter, 'tradeables'], - maxSize: 70 - }); - - if (bank.items().some(i => isSuperUntradeable(i[0]))) { - return 'You are trying to sell unsellable items.'; - } - - if (!user.bankWithGP.has(bank)) { - return "You don't own those items."; - } - - if (bank.items().some(i => !itemIsTradeable(i[0].id, true))) { - return "You can't giveaway untradeable items."; - } - - if (interaction) { - await handleMahojiConfirmation( - interaction, - `Are you sure you want to do a giveaway with these items? You cannot cancel the giveaway or retrieve the items back afterwards: ${bank}` - ); - } - - const duration = new Duration(options.start.duration); - const ms = duration.offset; - if (!ms || ms > Time.Day * 7 || ms < Time.Second * 5) { - return 'Your giveaway cannot last longer than 7 days, or be faster than 5 seconds.'; - } - - await user.sync(); - if (!user.bankWithGP.has(bank)) { - return "You don't own those items."; - } - - if (bank.length === 0) { - return 'You cannot have a giveaway with no items in it.'; - } - - const giveawayID = randInt(1, 500_000_000); - - const message = await channel.send({ - content: generateGiveawayContent(user.id, duration.fromNow, []), - files: [ - new AttachmentBuilder( - ( - await makeBankImage({ - bank, - title: `${apiUser?.username ?? user.rawUsername}'s Giveaway` - }) - ).file.attachment - ) - ], - components: makeGiveawayButtons(giveawayID) - }); - - try { - await user.removeItemsFromBank(bank); - } catch (err: any) { - return err instanceof Error ? err.message : err; - } - if (bank.has('Coins')) { - addToGPTaxBalance(user.id, bank.amount('Coins')); - } - - try { - await prisma.giveaway.create({ - data: { - id: giveawayID, - channel_id: channelID.toString(), - start_date: new Date(), - finish_date: duration.fromNow, - completed: false, - loot: bank.bank, - user_id: user.id, - duration: duration.offset, - message_id: message.id, - users_entered: [] - } - }); - } catch (err: any) { - logError(err, { - user_id: user.id, - giveaway: bank.toString() - }); - return 'Error starting giveaway.'; - } - - return { - content: 'Giveaway started.', - ephemeral: true, - components: makeComponents([makeGiveawayRepeatButton(giveawayID)]) - }; - } - - if (options.list) { - if (!guildID) { - return 'You cannot list giveaways outside a server.'; - } - const guild = globalClient.guilds.cache.get(guildID); - if (!guild) return; - - const textChannelsOfThisServer = guild.channels.cache - .filter(c => c.type === ChannelType.GuildText) - .map(i => i.id); - - const giveaways = await prisma.giveaway.findMany({ - where: { - channel_id: { - in: textChannelsOfThisServer - }, - completed: false - }, - orderBy: { - finish_date: 'asc' - } - }); - - if (giveaways.length === 0) { - return 'There are no active giveaways in this server.'; - } - - function getEmoji(giveaway: Giveaway) { - if (giveaway.user_id === user.id || giveaway.users_entered.includes(user.id)) { - return Emoji.Green; - } - return Emoji.RedX; - } - - return { - embeds: [ - new EmbedBuilder().setDescription( - giveaways - .map( - g => - `${ - user.perkTier() >= patronFeatures.ShowEnteredInGiveawayList.tier - ? `${getEmoji(g)} ` - : '' - }[${toKMB(marketPriceOfBank(new Bank(g.loot as ItemBank)))} giveaway ending ${time( - g.finish_date, - 'R' - )}](${messageLink(g.channel_id, g.message_id)})` - ) - .slice(0, 30) - .join('\n') - ) - ], - ephemeral: true - }; - } - } -}; diff --git a/src/mahoji/commands/help.ts b/src/mahoji/commands/help.ts deleted file mode 100644 index b11d54c7db..0000000000 --- a/src/mahoji/commands/help.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { ButtonStyle, ComponentType } from 'discord.js'; - -import { mahojiInformationalButtons } from '../../lib/constants'; -import type { OSBMahojiCommand } from '../lib/util'; - -export const helpCommand: OSBMahojiCommand = { - name: 'help', - description: 'Get information and help with the bot.', - options: [], - run: async () => { - return { - content: `🧑‍⚖️ **Rules:** You *must* follow our 5 simple rules, breaking any rule can result in a ban: - -<:patreonLogo:679334888792391703> **Patreon:** If you're able too, please consider supporting my work on Patreon, it's highly appreciated and helps me hugely ❤️ - -<:BSO:863823820435619890> **BSO:** I run a 2nd bot called BSO (Bot School Old), which you can also play, it has lots of fun and unique changes, like 5x XP and infinitely stacking clues. Invite it and read the wiki below. Ask for more information in the support server if you need! - -📢 **News:** Follow <#346210791747223554> for updates and news about the bot. -☺️ **Support:** <#668073484731154462> and <#970752140324790384> - -Please click the buttons below for important links.`, - embeds: [ - { - title: 'BSO', - description: - 'Bot School Old (BSO) is a copy of Old School Bot (OSB) that you can play alongside it, it has very fun changes like: 5x XP, 2x faster pvm, HUNDREDS of custom items, pets, monsters and bosses. It works exactly the same as OSB. Just invite it to your server using and `/minion buy` on it to get started!' - } - ], - components: [ - { - type: ComponentType.ActionRow, - components: mahojiInformationalButtons - }, - { - type: ComponentType.ActionRow, - components: [ - { - type: ComponentType.Button, - label: 'Invite BSO', - emoji: { id: '863823820435619890' }, - style: ButtonStyle.Link, - url: 'https://www.oldschool.gg/invite/bso' - }, - { - type: ComponentType.Button, - label: 'BSO Wiki', - emoji: { id: '863823820435619890' }, - style: ButtonStyle.Link, - url: 'https://bso-wiki.oldschool.gg/' - } - ] - } - ] - }; - } -}; diff --git a/src/mahoji/commands/hunt.ts b/src/mahoji/commands/hunt.ts index 57c79fadf6..16946301b3 100644 --- a/src/mahoji/commands/hunt.ts +++ b/src/mahoji/commands/hunt.ts @@ -9,7 +9,7 @@ import { HERBIBOAR_ID, RAZOR_KEBBIT_ID } from '../../lib/constants'; import type { UserFullGearSetup } from '../../lib/gear'; import { hasWildyHuntGearEquipped } from '../../lib/gear/functions/hasWildyHuntGearEquipped'; import { InventionID, inventionBoosts, inventionItemBoost } from '../../lib/invention/inventions'; -import { trackLoot } from '../../lib/lootTrack'; + import { monkeyTiers } from '../../lib/monkeyRumble'; import { soteSkillRequirements } from '../../lib/skilling/functions/questRequirements'; import creatures from '../../lib/skilling/skills/hunter/creatures'; @@ -21,7 +21,6 @@ import type { HunterActivityTaskOptions } from '../../lib/types/minions'; import { hasSkillReqs } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import type { OSBMahojiCommand } from '../lib/util'; import { userHasGracefulEquipped } from '../mahojiSettings'; @@ -406,19 +405,7 @@ export const huntCommand: OSBMahojiCommand = { result; await user.removeItemsFromBank(totalCost); - await updateBankSetting('hunter_cost', totalCost); - await trackLoot({ - id: creature.name, - totalCost, - type: 'Skilling', - changeType: 'cost', - users: [ - { - id: user.id, - cost: totalCost - } - ] - }); + await addSubTaskToActivityTask({ creatureName: creature.name, userID: user.id, diff --git a/src/mahoji/commands/ic.ts b/src/mahoji/commands/ic.ts deleted file mode 100644 index 51a7d207e0..0000000000 --- a/src/mahoji/commands/ic.ts +++ /dev/null @@ -1,308 +0,0 @@ -import { formatOrdinal } from '@oldschoolgg/toolkit'; -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ButtonBuilder, ButtonStyle, type ChatInputCommandInteraction } from 'discord.js'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { randArrItem, roll } from 'e'; -import { Bank, LootTable } from 'oldschooljs'; -import type { ItemBank } from 'oldschooljs/dist/meta/types'; - -import { itemContractResetTime } from '../../lib/MUser'; -import { PortentID, chargePortentIfHasCharges } from '../../lib/bso/divination'; -import { MysteryBoxes, PMBTable, allMbTables } from '../../lib/bsoOpenables'; -import { BitField, Emoji } from '../../lib/constants'; -import { AbyssalDragonLootTable } from '../../lib/minions/data/killableMonsters/custom/AbyssalDragon'; -import { Ignecarus } from '../../lib/minions/data/killableMonsters/custom/bosses/Ignecarus'; -import { kalphiteKingLootTable } from '../../lib/minions/data/killableMonsters/custom/bosses/KalphiteKing'; -import { VasaMagus } from '../../lib/minions/data/killableMonsters/custom/bosses/VasaMagus'; -import { BSOMonsters } from '../../lib/minions/data/killableMonsters/custom/customMonsters'; -import { nexLootTable } from '../../lib/nex'; -import { DragonTable } from '../../lib/simulation/grandmasterClue'; -import { allThirdAgeItems, runeAlchablesTable } from '../../lib/simulation/sharedTables'; -import { formatDuration, itemID, makeComponents } from '../../lib/util'; -import getOSItem from '../../lib/util/getOSItem'; -import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import resolveItems from '../../lib/util/resolveItems'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; -import { LampTable } from '../../lib/xpLamps'; -import type { OSBMahojiCommand } from '../lib/util'; -import { updateClientGPTrackSetting, userStatsBankUpdate } from '../mahojiSettings'; - -const contractTable = new LootTable() - .every('Coins', [1_000_000, 3_500_000]) - .tertiary(50, LampTable) - .tertiary(50, MysteryBoxes) - .add(DragonTable, [1, 2], 2) - .add(runeAlchablesTable, [1, 3], 3) - .every(runeAlchablesTable, [1, 3]) - .add( - new LootTable() - .add('Clue scroll (beginner)', 1, 50) - .add('Clue scroll (easy)', 1, 40) - .add('Clue scroll (medium)', 1, 30) - .add('Clue scroll (hard)', 1, 20) - .add('Clue scroll (elite)', 1, 10) - .add('Clue scroll (master)', 1, 5) - .add('Clue scroll (grandmaster)', 1, 2) - ); - -const itemContractItemsSet = new Set([ - ...allMbTables, - ...kalphiteKingLootTable.allItems.filter(i => i !== itemID('Baby kalphite king')), - ...AbyssalDragonLootTable.allItems, - ...VasaMagus.allItems, - ...Ignecarus.allItems.filter(i => i !== itemID('Dragon egg')), - ...nexLootTable.allItems, - ...PMBTable.allItems, - ...[ - ...BSOMonsters.FrostDragon.table.allItems, - ...BSOMonsters.GanodermicBeast.table.allItems, - ...BSOMonsters.Grifolaroo.table.allItems, - ...BSOMonsters.RumPumpedCrab.table.allItems, - ...BSOMonsters.QueenBlackDragon.table.allItems - ], - ...resolveItems([ - 'Untradeable mystery box', - 'Tradeable mystery box', - 'Pet mystery box', - 'Holiday mystery box', - 'Dwarven bar', - 'Dwarven ore' - ]) -]); - -const cantBeContract = resolveItems(['Coins']); -for (const id of cantBeContract) { - itemContractItemsSet.delete(id); -} - -const itemContractItems = Array.from(itemContractItemsSet); - -function pickItemContract(streak: number) { - let item = randArrItem(itemContractItems); - if (streak > 50) { - const fifties = Math.floor(streak / 50); - for (let i = 0; i < fifties; i++) { - if (roll(95 + i * 5)) { - item = randArrItem(allThirdAgeItems); - } - } - } - - return item; -} - -export function getItemContractDetails(mUser: MUser) { - const currentDate = Date.now(); - let lastDate = Number(mUser.user.last_item_contract_date); - if (lastDate === 0) lastDate = Date.now() - itemContractResetTime; - const difference = currentDate - lastDate; - const totalContracts = mUser.user.total_item_contracts; - const streak = mUser.user.item_contract_streak; - const currentItem = mUser.user.current_item_contract ? getOSItem(mUser.user.current_item_contract) : null; - const durationRemaining = Date.now() - (lastDate + itemContractResetTime); - const nextContractIsReady = difference >= itemContractResetTime; - const { bank } = mUser; - - const owns = currentItem ? bank.has(currentItem.id) : null; - return { - totalContracts, - streak, - currentItem, - nextContractIsReady, - durationRemaining, - differenceFromLastContract: difference, - owns, - canSkip: difference < itemContractResetTime, - lastDate, - infoStr: `**Current Contract:** ${ - currentItem ? `${currentItem.name} (ID: ${currentItem.id}) (You${owns ? '' : " don't"} own it)` : '*None*' - } -**Current Streak:** ${streak} -**Total Contracts Completed:** ${totalContracts} -${!currentItem ? `**Next Contract:** ${nextContractIsReady ? 'Ready now.' : formatDuration(durationRemaining)}` : ''}` - }; -} - -async function skip(interaction: ChatInputCommandInteraction, user: MUser) { - const { currentItem, differenceFromLastContract, streak, canSkip } = getItemContractDetails(user); - if (!currentItem) return "You don't have a contract to skip."; - if (canSkip) { - return `Your current contract is a ${currentItem.name} (ID:${ - currentItem.id - }), you can't skip it yet, you need to wait ${formatDuration( - itemContractResetTime - differenceFromLastContract - )}.`; - } - - await handleMahojiConfirmation( - interaction, - `Are you sure you want to skip your item contract? You won't be able to get another contract for ${formatDuration( - itemContractResetTime / 2 - )}.` - ); - - const newItem = pickItemContract(streak); - - await user.update({ - last_item_contract_date: Date.now() - itemContractResetTime / 2, - current_item_contract: newItem, - item_contract_streak: 0 - }); - - return `You skipped your item contract, your streak was reset, and your next contract will be available in ${formatDuration( - itemContractResetTime / 2 - )}.`; -} - -export async function handInContract(interaction: ChatInputCommandInteraction | null, user: MUser): Promise { - const { nextContractIsReady, durationRemaining, currentItem, owns, streak, totalContracts } = - getItemContractDetails(user); - - if (!nextContractIsReady) { - return `You have no item contract available at the moment. Come back in ${formatDuration(durationRemaining)}.`; - } - - if (!currentItem) { - await user.update({ - current_item_contract: pickItemContract(streak), - last_item_contract_date: Date.now() - }); - return handInContract(interaction, user); - } - - if (!owns) { - return `Your current contract is a ${currentItem.name} (Id: ${currentItem.id}), go get one!`; - } - const cost = new Bank().add(currentItem.id); - const newStreak = streak + 1; - - if (interaction) { - await handleMahojiConfirmation( - interaction, - `Are you sure you want to hand in ${cost} for your ${formatOrdinal(newStreak)} Item Contract?` - ); - } - - const loot = new Bank().add(contractTable.roll()); - let gotBonus = ''; - for (const [kc, rolls] of [ - [100, 25], - [50, 15], - [25, 10], - [10, 5] - ]) { - if (newStreak % kc === 0) { - gotBonus = `You got ${rolls} bonus rolls for your ${formatOrdinal(newStreak)} contract!`; - for (let i = 0; i < rolls; i++) { - loot.add(contractTable.roll()); - loot.add(runeAlchablesTable.roll(5)); - } - break; - } - } - - const { didCharge } = await chargePortentIfHasCharges({ - user, - portentID: PortentID.LuckyPortent, - charges: 1 - }); - if (didCharge) { - await userStatsBankUpdate(user.id, 'loot_from_lucky_portent', loot); - loot.multiply(2); - } - - await user.update({ - last_item_contract_date: Date.now(), - total_item_contracts: { - increment: 1 - }, - current_item_contract: pickItemContract(newStreak), - item_contract_bank: new Bank().add(currentItem.id).add(user.user.item_contract_bank as ItemBank).bank, - item_contract_streak: { - increment: 1 - } - }); - - await user.removeItemsFromBank(cost); - await user.addItemsToBank({ items: loot, collectionLog: false }); - - await Promise.all([ - updateBankSetting('item_contract_cost', cost), - updateBankSetting('item_contract_loot', loot), - updateClientGPTrackSetting('gp_ic', loot.amount('Coins')), - userStatsBankUpdate(user.id, 'ic_cost_bank', cost), - userStatsBankUpdate(user.id, 'ic_loot_bank', loot) - ]); - - let res = `You handed in a ${currentItem.name} and received ${loot}. You've completed ${ - totalContracts + 1 - } Item Contracts, and your streak is now at ${newStreak}.`; - if (gotBonus.length > 0) { - res += `\n\n${gotBonus}`; - } - if (didCharge) { - res += '\n\nYour Lucky Portent doubled your loot!'; - } - return res; -} - -export const icCommand: OSBMahojiCommand = { - name: 'ic', - description: 'Hand in random items for rewards.', - attributes: { - requiresMinion: true - }, - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'info', - description: 'View stats and info on your Item Contracts.' - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'send', - description: 'Hand in your contract and receive a new one.' - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'skip', - description: 'Skip your current contract.' - } - ], - run: async ({ options, userID, interaction }: CommandRunOptions<{ info?: {}; send?: {}; skip?: {} }>) => { - const user = await mUserFetch(userID); - const details = getItemContractDetails(user); - const components = - details.nextContractIsReady && - details.currentItem !== null && - !user.isIronman && - !user.bitfield.includes(BitField.NoItemContractDonations) - ? makeComponents([ - new ButtonBuilder() - .setStyle(ButtonStyle.Primary) - .setLabel('Donate IC') - .setEmoji('988422348434718812') - .setCustomId(`DONATE_IC_${user.id}`) - ]) - : undefined; - - if (options.info) { - if (!details.nextContractIsReady) { - return { - content: `${ - Emoji.ItemContract - } You have no item contract available at the moment. Come back in ${formatDuration( - details.durationRemaining - )}. - -${details.infoStr}` - }; - } - return { content: `${Emoji.ItemContract} ${details.infoStr}`, components }; - } - const res = options.skip ? await skip(interaction, user) : await handInContract(interaction, user); - - const nextIcDetails = getItemContractDetails(user); - return `${Emoji.ItemContract} ${res}\n\n${nextIcDetails.infoStr}`; - } -}; diff --git a/src/mahoji/commands/invention.ts b/src/mahoji/commands/invention.ts index c9ad2f6a0e..4ab78d0388 100644 --- a/src/mahoji/commands/invention.ts +++ b/src/mahoji/commands/invention.ts @@ -364,10 +364,6 @@ These Inventions are still not unlocked: ${locked } } - if (user.skillLevel(SkillsEnum.Crafting) < 90) { - return "Your minion isn't skilled enough to train Invention, you need level 90 Crafting."; - } - if (options.disassemble) { return disassembleCommand({ user, diff --git a/src/mahoji/commands/invite.ts b/src/mahoji/commands/invite.ts deleted file mode 100644 index c77fd8193e..0000000000 --- a/src/mahoji/commands/invite.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { OSBMahojiCommand } from '../lib/util'; - -export const inviteCommand: OSBMahojiCommand = { - name: 'invite', - description: 'Shows the invite link for the bot.', - options: [], - run: async () => { - return `**Old School Bot (OSB):** https://discord.com/application-directory/303730326692429825 -**Bot School Old (BSO):** https://discord.com/application-directory/729244028989603850`; - } -}; diff --git a/src/mahoji/commands/k.ts b/src/mahoji/commands/k.ts index b9686b058f..c22e053cd3 100644 --- a/src/mahoji/commands/k.ts +++ b/src/mahoji/commands/k.ts @@ -10,8 +10,7 @@ import { MOKTANG_ID } from '../../lib/minions/data/killableMonsters/custom/bosse import { Naxxus } from '../../lib/minions/data/killableMonsters/custom/bosses/Naxxus'; import { VasaMagus } from '../../lib/minions/data/killableMonsters/custom/bosses/VasaMagus'; import { NexMonster } from '../../lib/nex'; -import { returnStringOrFile } from '../../lib/util/smallUtils'; -import { minionKillCommand, monsterInfo } from '../lib/abstracted_commands/minionKill'; +import { minionKillCommand } from '../lib/abstracted_commands/minionKill'; import type { OSBMahojiCommand } from '../lib/util'; export const autocompleteMonsters = [ @@ -153,23 +152,11 @@ export const minionKCommand: OSBMahojiCommand = { required: false, choices: PVM_METHODS.map(i => ({ name: i, value: i })) }, - { - type: ApplicationCommandOptionType.Boolean, - name: 'show_info', - description: 'Show information on this monster.', - required: false - }, { type: ApplicationCommandOptionType.Boolean, name: 'wilderness', description: 'If you want to kill the monster in the wilderness.', required: false - }, - { - type: ApplicationCommandOptionType.Boolean, - name: 'solo', - description: 'Solo (if its a group boss)', - required: false } ], run: async ({ @@ -181,14 +168,10 @@ export const minionKCommand: OSBMahojiCommand = { name: string; quantity?: number; method?: PvMMethod; - show_info?: boolean; wilderness?: boolean; - solo?: boolean; }>) => { const user = await mUserFetch(userID); - if (options.show_info) { - return returnStringOrFile(await monsterInfo(user, options.name)); - } + return minionKillCommand( user, interaction, @@ -197,7 +180,7 @@ export const minionKCommand: OSBMahojiCommand = { options.quantity, options.method, options.wilderness, - options.solo + true ); } }; diff --git a/src/mahoji/commands/kc.ts b/src/mahoji/commands/kc.ts deleted file mode 100644 index 997bc61354..0000000000 --- a/src/mahoji/commands/kc.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { stringMatches, toTitleCase } from '@oldschoolgg/toolkit'; -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { Hiscores } from 'oldschooljs'; -import { bossNameMap, mappedBossNames } from 'oldschooljs/dist/constants'; -import type { BossRecords } from 'oldschooljs/dist/meta/types'; - -import type { OSBMahojiCommand } from '../lib/util'; - -export const kcCommand: OSBMahojiCommand = { - name: 'kc', - description: 'See your OSRS kc for a monster/boss.', - attributes: { - examples: ['/kc name:General Graardor'] - }, - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'rsn', - description: 'The runescape username to check', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'boss', - description: 'The boss you want to check', - required: true, - autocomplete: async (value: string) => { - return mappedBossNames - .filter(i => (!value ? true : (i[0] + i[1]).toLowerCase().includes(value.toLowerCase()))) - .map(i => ({ name: i[1], value: i[1] })); - } - } - ], - run: async ({ options }: CommandRunOptions<{ rsn: string; boss: string }>) => { - try { - const { bossRecords } = await Hiscores.fetch(options.rsn); - - for (const [boss, { rank, score }] of Object.entries(bossRecords)) { - if (stringMatches(boss, options.boss)) { - if (score === -1 || rank === -1) { - return `${toTitleCase(options.rsn)}'s has no recorded KC for that boss.`; - } - return `${toTitleCase(options.rsn)}'s ${bossNameMap.get( - boss as keyof BossRecords - )} KC is **${score.toLocaleString()}** (Rank ${rank.toLocaleString()})`; - } - } - - return `${toTitleCase(options.rsn)} doesn't have any recorded kills for that boss.`; - } catch (err: any) { - return err.message; - } - } -}; diff --git a/src/mahoji/commands/kibble.ts b/src/mahoji/commands/kibble.ts index e3a62f797b..065fcd9152 100644 --- a/src/mahoji/commands/kibble.ts +++ b/src/mahoji/commands/kibble.ts @@ -10,7 +10,6 @@ import type { KibbleOptions } from '../../lib/types/minions'; import { formatDuration, itemNameFromID, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import type { OSBMahojiCommand } from '../lib/util'; export const kibbleCommand: OSBMahojiCommand = { @@ -114,7 +113,6 @@ export const kibbleCommand: OSBMahojiCommand = { } await user.removeItemsFromBank(cost); - updateBankSetting('kibble_cost', cost); await addSubTaskToActivityTask({ userID: user.id, diff --git a/src/mahoji/commands/kill.ts b/src/mahoji/commands/kill.ts deleted file mode 100644 index 492d3bfc1f..0000000000 --- a/src/mahoji/commands/kill.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { toTitleCase } from '@oldschoolgg/toolkit'; -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { Bank, Monsters } from 'oldschooljs'; - -import { PerkTier } from '../../lib/constants'; -import { simulatedKillables } from '../../lib/simulation/simulatedKillables'; -import { stringMatches } from '../../lib/util'; -import { deferInteraction } from '../../lib/util/interactionReply'; -import { makeBankImage } from '../../lib/util/makeBankImage'; -import { Workers } from '../../lib/workers'; -import type { OSBMahojiCommand } from '../lib/util'; - -function determineKillLimit(user: MUser) { - const perkTier = user.perkTier(); - - if (perkTier >= PerkTier.Six) { - return 1_000_000; - } - - if (perkTier >= PerkTier.Five) { - return 600_000; - } - - if (perkTier >= PerkTier.Four) { - return 400_000; - } - - if (perkTier === PerkTier.Three) { - return 250_000; - } - - if (perkTier === PerkTier.Two) { - return 100_000; - } - - if (perkTier === PerkTier.One) { - return 50_000; - } - - return 10_000; -} - -export const killCommand: OSBMahojiCommand = { - name: 'kill', - description: 'Simulate killing monsters.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'name', - description: 'The monster you want to simulate killing.', - required: true, - autocomplete: async (value: string) => { - return [ - ...Monsters.map(i => ({ name: i.name, aliases: i.aliases })), - ...simulatedKillables.map(i => ({ name: i.name, aliases: [i.name] })) - ] - .filter(i => - !value ? true : i.aliases.some(alias => alias.toLowerCase().includes(value.toLowerCase())) - ) - .map(i => ({ - name: i.name, - value: i.name - })); - } - }, - { - type: ApplicationCommandOptionType.Integer, - name: 'quantity', - description: 'The quantity you want to simulate.', - required: true, - min_value: 1 - } - ], - run: async ({ options, userID, interaction }: CommandRunOptions<{ name: string; quantity: number }>) => { - const user = await mUserFetch(userID); - await deferInteraction(interaction); - const osjsMonster = Monsters.find(mon => mon.aliases.some(alias => stringMatches(alias, options.name))); - const simulatedKillable = simulatedKillables.find(i => stringMatches(i.name, options.name)); - - let limit = determineKillLimit(user); - if (osjsMonster?.isCustom) { - if (user.perkTier() < PerkTier.Four) { - return 'Simulating kills of custom monsters is a T3 perk!'; - } - limit /= 4; - } - - if (simulatedKillable?.isCustom) { - if (user.perkTier() < PerkTier.Four) { - return 'Simulating kills of custom monsters or raids is a T3 perk!'; - } - limit /= 4; - } - - const result = await Workers.kill({ - quantity: options.quantity, - bossName: options.name, - limit, - catacombs: false, - onTask: false, - lootTableTertiaryChanges: Array.from(user.buildTertiaryItemChanges().entries()) - }); - - if (result.error) { - return result.error; - } - - const bank = new Bank(result.bank?.bank); - const image = await makeBankImage({ - bank, - title: result.title ?? `Loot from ${options.quantity.toLocaleString()} ${toTitleCase(options.name)}`, - user - }); - - return { - files: [image.file], - content: result.content - }; - } -}; diff --git a/src/mahoji/commands/laps.ts b/src/mahoji/commands/laps.ts index 43ce63e912..cc83183530 100644 --- a/src/mahoji/commands/laps.ts +++ b/src/mahoji/commands/laps.ts @@ -11,7 +11,6 @@ import type { AgilityActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import type { OSBMahojiCommand } from '../lib/util'; const unlimitedFireRuneProviders = [ @@ -205,7 +204,6 @@ export const lapsCommand: OSBMahojiCommand = { await user.removeItemsFromBank(alchResult.bankToRemove); response += `\n\nYour minion is alching ${alchResult.maxCasts}x ${alchResult.itemToAlch.name} while training. Removed ${alchResult.bankToRemove} from your bank.`; - updateBankSetting('magic_cost_bank', alchResult.bankToRemove); } if (boosts.length > 0) { response += `\n**Boosts:** ${boosts.join(', ')}`; diff --git a/src/mahoji/commands/leaderboard.ts b/src/mahoji/commands/leaderboard.ts index a571c7b19e..296fb8f840 100644 --- a/src/mahoji/commands/leaderboard.ts +++ b/src/mahoji/commands/leaderboard.ts @@ -10,7 +10,6 @@ import { import { calcWhatPercent, chunk, isFunction } from 'e'; import type { ClueTier } from '../../lib/clues/clueTiers'; import { ClueTiers } from '../../lib/clues/clueTiers'; -import { masteryKey } from '../../lib/constants'; import { allClNames, getCollectionItems } from '../../lib/data/Collections'; import { allLeagueTasks } from '../../lib/leagues/leagues'; import { effectiveMonsters } from '../../lib/minions/data/killableMonsters'; @@ -143,7 +142,6 @@ async function kcLb( FROM user_stats ${ironmanOnly ? 'INNER JOIN "users" on "users"."id" = "user_stats"."user_id"::text' : ''} WHERE CAST("monster_scores"->>'${monster.id}' AS INTEGER) > 5 - ${ironmanOnly ? ' AND "users"."minion.ironman" = true ' : ''} ORDER BY score DESC LIMIT 2000;` ); @@ -158,17 +156,11 @@ async function kcLb( }); } -async function farmingContractLb( - interaction: ChatInputCommandInteraction, - user: MUser, - channelID: string, - ironmanOnly: boolean -) { +async function farmingContractLb(interaction: ChatInputCommandInteraction, user: MUser, channelID: string) { const list = await prisma.$queryRawUnsafe<{ id: string; count: number }[]>( `SELECT id, CAST("minion.farmingContract"->>'contractsCompleted' AS INTEGER) as count FROM users WHERE "minion.farmingContract" is not null and CAST ("minion.farmingContract"->>'contractsCompleted' AS INTEGER) >= 1 - ${ironmanOnly ? ' AND "minion.ironman" = true ' : ''} ORDER BY count DESC LIMIT 2000;` ); @@ -239,8 +231,7 @@ async function sacrificeLb( interaction: ChatInputCommandInteraction, user: MUser, channelID: string, - type: 'value' | 'unique', - ironmanOnly: boolean + type: 'value' | 'unique' ) { if (type === 'value') { const list = ( @@ -248,7 +239,6 @@ async function sacrificeLb( `SELECT "id", "sacrificedValue" FROM users WHERE "sacrificedValue" > 0 - ${ironmanOnly ? 'AND "minion.ironman" = true' : ''} ORDER BY "sacrificedValue" DESC LIMIT 2000;` ) @@ -276,7 +266,6 @@ async function sacrificeLb( `SELECT u.user_id::text AS id, u.sacbanklength FROM ( SELECT (SELECT COUNT(*)::int FROM JSONB_OBJECT_KEYS(sacrificed_bank)) sacbanklength, user_id FROM user_stats - ${ironmanOnly ? 'INNER JOIN users ON users.id::bigint = user_stats.user_id WHERE "minion.ironman" = true' : ''} ) u ORDER BY u.sacbanklength DESC LIMIT 10; ` @@ -390,7 +379,7 @@ async function clLb( } }); - const users = await fetchCLLeaderboard({ ironmenOnly, items, resultLimit: 200, userEvents: userEventOrders }); + const users = await fetchCLLeaderboard({ items, resultLimit: 200, userEvents: userEventOrders }); inputType = toTitleCase(inputType.toLowerCase()); return doMenuWrapper({ @@ -495,7 +484,6 @@ async function openLb( `SELECT user_id::text AS id, ("${key}"->>'${entityID}')::int as qty FROM user_stats ${ironmanOnly ? 'INNER JOIN users ON users.id::bigint = user_stats.user_id' : ''} WHERE ("${key}"->>'${entityID}')::int > 3 - ${ironmanOnly ? ' AND "minion.ironman" = true ' : ''} ORDER BY qty DESC LIMIT 30;` ); @@ -515,7 +503,6 @@ async function gpLb(interaction: ChatInputCommandInteraction, user: MUser, chann `SELECT "id", "GP" FROM users WHERE "GP" > 1000000 - ${ironmanOnly ? ' AND "minion.ironman" = true ' : ''} ORDER BY "GP" DESC LIMIT 100;` ) @@ -537,14 +524,12 @@ async function skillsLb( user: MUser, channelID: string, inputSkill: string, - type: 'xp' | 'level', - ironmanOnly: boolean + type: 'xp' | 'level' ) { let res = []; let overallUsers: { id: string; totalLevel: number; - ironman: boolean; totalXP: number; }[] = []; @@ -566,11 +551,9 @@ async function skillsLb( const query = `SELECT u.id, ${skillsVals.map(s => `"skills.${s.id}"`)}, - ${skillsVals.map(s => `"skills.${s.id}"::int8`).join(' + ')} as totalxp, - u."minion.ironman" + ${skillsVals.map(s => `"skills.${s.id}"::int8`).join(' + ')} as totalxp FROM users u - ${ironmanOnly ? ' WHERE "minion.ironman" = true ' : ''} ORDER BY totalxp DESC LIMIT 2000;`; res = await prisma.$queryRawUnsafe[]>(query); @@ -582,7 +565,6 @@ async function skillsLb( return { id: user.id, totalLevel, - ironman: user['minion.ironman'], totalXP: Number(user.totalxp!) }; }); @@ -615,10 +597,8 @@ async function skillsLb( if (!skill) return "That's not a valid skill."; const query = `SELECT - u."skills.${skill.id}", u.id, u."minion.ironman" FROM users u - ${ironmanOnly ? ' WHERE "minion.ironman" = true ' : ''} ORDER BY 1 DESC LIMIT 2000;`; @@ -713,7 +693,6 @@ async function cluesLb( FROM users WHERE "collectionLogBank"->>'${id}' IS NOT NULL AND ("collectionLogBank"->>'${id}')::int > 25 -${ironmanOnly ? 'AND "minion.ironman" = true ' : ''} ORDER BY ("collectionLogBank"->>'${id}')::int DESC LIMIT 50;` ) @@ -736,167 +715,11 @@ LIMIT 50;` return lbMsg('Clue Leaderboard', ironmanOnly); } -async function itemContractLb( - interaction: ChatInputCommandInteraction, - user: MUser, - channelID: string, - ironmanOnly?: boolean -) { - const results = await prisma.user.findMany({ - select: { - id: true, - item_contract_streak: true - }, - where: { - item_contract_streak: { - gte: 5 - }, - minion_ironman: ironmanOnly ? true : undefined - }, - orderBy: { - item_contract_streak: 'desc' - }, - take: 10 - }); - - doMenu( - interaction, - user, - channelID, - chunk(results, 10).map(subList => - subList - .map(({ id, item_contract_streak }) => `**${getUsernameSync(id)}:** ${item_contract_streak}`) - .join('\n') - ), - 'Item Contract Streak Leaderboard' - ); - return lbMsg('Item Contract Streak'); -} - -const globalLbTypes = ['xp', 'cl', 'mastery'] as const; -type GlobalLbType = (typeof globalLbTypes)[number]; -async function globalLb(interaction: ChatInputCommandInteraction, user: MUser, channelID: string, type: GlobalLbType) { - if (type === 'xp') { - const result = await roboChimpClient.$queryRaw< - { - id: string; - osb_total_xp: number; - bso_total_xp: number; - osb_xp_percent: number; - bso_xp_percent: number; - average_percentage: number; - }[] - >`SELECT id::text, osb_total_xp, bso_total_xp, - (osb_total_xp / (200000000.0 * 23) * 100) as osb_xp_percent, - (bso_total_xp / (5000000000.0 * 25) * 100) as bso_xp_percent, - (((osb_total_xp / (200000000.0 * 23) * 100) + (bso_total_xp / (5000000000.0 * 25) * 100)) / 2) as average_percentage -FROM public.user -WHERE osb_total_xp IS NOT NULL AND bso_total_xp IS NOT NULL -ORDER BY average_percentage DESC -LIMIT 10; -`; - doMenu( - interaction, - user, - channelID, - chunk(result, LB_PAGE_SIZE).map((subList, i) => - subList - .map( - ({ id, osb_xp_percent, bso_xp_percent }, j) => - `${getPos(i, j)}**${getUsernameSync(id)}:** ${osb_xp_percent.toFixed( - 2 - )}% OSB, ${bso_xp_percent.toFixed(2)}% BSO` - ) - .join('\n') - ), - 'Global (OSB+BSO) XP Leaderboard (% of the max XP)' - ); - return lbMsg('Global (OSB+BSO) XP Leaderboard'); - } - - if (type === 'mastery') { - const result = await roboChimpClient.$queryRaw< - { - id: string; - avg: number; - }[] - >`SELECT id::text, ((osb_mastery + bso_mastery) / 2) AS avg -FROM public.user -WHERE osb_mastery IS NOT NULL AND bso_mastery IS NOT NULL -ORDER BY avg DESC -LIMIT 10; -`; - doMenu( - interaction, - user, - channelID, - chunk(result, LB_PAGE_SIZE).map((subList, i) => - subList - .map(({ id, avg }, j) => `${getPos(i, j)}**${getUsernameSync(id)}:** ${avg.toFixed(2)}%`) - .join('\n') - ), - 'Global (OSB+BSO) Mastery Leaderboard' - ); - return lbMsg('Global Mastery Leaderboard'); - } - - const result = await roboChimpClient.$queryRaw<{ id: string; total_cl_percent: number }[]>`SELECT ((osb_cl_percent + bso_cl_percent) / 2) AS total_cl_percent, id::text AS id -FROM public.user -WHERE osb_cl_percent IS NOT NULL AND bso_cl_percent IS NOT NULL -ORDER BY total_cl_percent DESC -LIMIT 20;`; - - doMenu( - interaction, - user, - channelID, - chunk(result, LB_PAGE_SIZE).map((subList, i) => - subList - .map( - ({ id, total_cl_percent }, j) => - `${getPos(i, j)}**${getUsernameSync(id)}:** ${total_cl_percent.toLocaleString()}%` - ) - .join('\n') - ), - 'Global (OSB+BSO) CL Leaderboard' - ); - return lbMsg('Global (OSB+BSO) CL Leaderboard'); -} - -async function leaguesPointsLeaderboard(interaction: ChatInputCommandInteraction, user: MUser, channelID: string) { - const result = await roboChimpClient.user.findMany({ - where: { - leagues_points_total: { - gt: 0 - } - }, - orderBy: { - leagues_points_total: 'desc' - }, - take: 100 - }); - doMenu( - interaction, - user, - channelID, - chunk(result, 10).map(subList => - subList - .map( - ({ id, leagues_points_total }) => - `**${getUsernameSync(id)}:** ${leagues_points_total.toLocaleString()} Pts` - ) - .join('\n') - ), - 'Leagues Points Leaderboard' - ); - return lbMsg('Leagues Points'); -} - async function leastCompletedLeagueTasksLb() { - const taskCounts = await roboChimpClient.$queryRaw<{ task_id: number; qty: number }[]>`SELECT task_id, count(*) AS qty + const taskCounts = await prisma.$queryRaw<{ task_id: number; qty: number }[]>`SELECT task_id, count(*) AS qty FROM ( SELECT unnest(leagues_completed_tasks_ids) AS task_id - FROM public.user + FROM public.users ) sub GROUP BY 1 ORDER BY 2 ASC;`; @@ -942,7 +765,6 @@ async function compLeaderboard( FROM user_stats ${ironmanOnly ? 'INNER JOIN "users" on "users"."id" = "user_stats"."user_id"::text' : ''} WHERE ${key} IS NOT NULL - ${ironmanOnly ? ' AND "users"."minion.ironman" = true ' : ''} ORDER BY ${key} DESC LIMIT 100;` ); @@ -970,12 +792,17 @@ async function leaguesLeaderboard( channelID: string, type: 'points' | 'tasks' | 'hardest_tasks' ) { - if (type === 'points') return leaguesPointsLeaderboard(interaction, user, channelID); if (type === 'hardest_tasks') return leastCompletedLeagueTasksLb(); - const result: { id: number; tasks_completed: number }[] = await roboChimpClient.$queryRaw`SELECT id::text, COALESCE(cardinality(leagues_completed_tasks_ids), 0) AS tasks_completed - FROM public.user - ORDER BY tasks_completed DESC - LIMIT 100;`; + const result = await prisma.user.findMany({ + orderBy: { + leagues_completed_tasks_count: 'desc' + }, + take: 100, + select: { + id: true, + leagues_completed_tasks_count: true + } + }); doMenu( interaction, user, @@ -983,8 +810,8 @@ async function leaguesLeaderboard( chunk(result, 10).map(subList => subList .map( - ({ id, tasks_completed }) => - `**${getUsernameSync(id.toString())}:** ${tasks_completed.toLocaleString()} Tasks` + ({ id, leagues_completed_tasks_count }) => + `**${getUsernameSync(id.toString())}:** ${leagues_completed_tasks_count.toLocaleString()} Tasks` ) .join('\n') ), @@ -993,90 +820,6 @@ async function leaguesLeaderboard( return lbMsg('Leagues Tasks'); } -const gainersTypes = ['overall', 'top_250'] as const; -type GainersType = (typeof gainersTypes)[number]; -async function gainersLB(interaction: ChatInputCommandInteraction, user: MUser, channelID: string, type: GainersType) { - const result = await prisma.$queryRawUnsafe< - { - user_id: string; - cl_global_rank: number; - cl_completion_percentage: number; - cl_completion_count: number; - count_increase: number; - rank_difference: number; - percentage_difference: number; - }[] - >( - type === 'overall' - ? `WITH latest_count AS ( - SELECT user_id, cl_global_rank, cl_completion_percentage, cl_completion_count, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY date DESC) AS date_row - FROM historical_data - WHERE cl_global_rank != 0 -), -seven_days_ago_count AS ( - SELECT user_id, cl_global_rank, cl_completion_percentage, cl_completion_count, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY date DESC) AS date_row - FROM historical_data - WHERE date >= (CURRENT_DATE - INTERVAL '7 days') AND date < (CURRENT_DATE - INTERVAL '6 days') - AND cl_global_rank != 0 -) -SELECT lc.user_id::text, - lc.cl_global_rank, - lc.cl_completion_percentage, - lc.cl_completion_count, - (lc.cl_completion_count - sdac.cl_completion_count) AS count_increase, - (lc.cl_global_rank - sdac.cl_global_rank) AS rank_difference, - (lc.cl_completion_percentage - sdac.cl_completion_percentage) AS percentage_difference -FROM latest_count lc -JOIN seven_days_ago_count sdac ON lc.user_id = sdac.user_id AND lc.date_row = 1 AND sdac.date_row = 1 -ORDER BY count_increase DESC -LIMIT 10;` - : `WITH latest_count AS ( - SELECT user_id, cl_global_rank, cl_completion_percentage, cl_completion_count, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY date DESC) AS date_row - FROM historical_data - WHERE cl_global_rank <= 250 -), -seven_days_ago_count AS ( - SELECT user_id, cl_global_rank, cl_completion_percentage, cl_completion_count, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY date DESC) AS date_row - FROM historical_data - WHERE date >= (CURRENT_DATE - INTERVAL '7 days') AND date < (CURRENT_DATE - INTERVAL '6 days') - AND cl_global_rank <= 250 -) -SELECT CAST(lc.user_id AS TEXT) AS user_id, - lc.cl_global_rank, - lc.cl_completion_percentage, - lc.cl_completion_count, - (lc.cl_completion_count - sdac.cl_completion_count) AS count_increase, - (lc.cl_global_rank - sdac.cl_global_rank) AS rank_difference, - (lc.cl_completion_percentage - sdac.cl_completion_percentage) AS percentage_difference, - ((lc.cl_completion_count - sdac.cl_completion_count) * (1 + 0.1 * lc.cl_completion_count)) / (1 + ABS(lc.cl_global_rank - sdac.cl_global_rank)) AS score -FROM latest_count lc -JOIN seven_days_ago_count sdac ON lc.user_id = sdac.user_id AND lc.date_row = 1 AND sdac.date_row = 1 -ORDER BY score DESC -LIMIT 10; -` - ); - - doMenu( - interaction, - user, - channelID, - chunk(result, LB_PAGE_SIZE).map((subList, i) => - subList - .map( - ({ user_id, cl_completion_count, cl_global_rank, count_increase, rank_difference }, j) => - `${getPos(i, j)}**${getUsernameSync( - user_id - )}:** Gained ${count_increase} CL slots, from ${cl_completion_count} to ${ - cl_completion_count + count_increase - }, and their global rank went from ${cl_global_rank - rank_difference} to ${cl_global_rank}` - ) - .join('\n') - ), - 'Weekly Movers Leaderboard' - ); - return lbMsg('Weekly Movers Leaderboard'); -} - async function caLb(interaction: ChatInputCommandInteraction, user: MUser, channelID: string) { const users = ( await prisma.$queryRawUnsafe<{ id: string; qty: number }[]>( @@ -1105,35 +848,6 @@ LIMIT 50;` return lbMsg('Combat Achievements Leaderboard'); } -async function masteryLb(interaction: ChatInputCommandInteraction, user: MUser, channelID: string) { - const users = ( - await roboChimpClient.user.findMany({ - where: { - [masteryKey]: { not: null } - }, - orderBy: { - [masteryKey]: 'desc' - }, - take: 50, - select: { - id: true, - osb_mastery: true, - bso_mastery: true - } - }) - ).map(u => ({ id: u.id.toString(), score: u[masteryKey] ?? 0 })); - - return doMenuWrapper({ - interaction, - title: 'Mastery Leaderboard', - channelID, - ironmanOnly: false, - user, - users, - formatter: val => `${val.toFixed(3)}% mastery` - }); -} - const ironmanOnlyOption = { type: ApplicationCommandOptionType.Boolean, name: 'ironmen_only', @@ -1343,12 +1057,6 @@ export const leaderboardCommand: OSBMahojiCommand = { } ] }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'item_contract_streak', - description: 'The item contract streak leaderboard.', - options: [ironmanOnlyOption] - }, { type: ApplicationCommandOptionType.Subcommand, name: 'leagues', @@ -1359,7 +1067,7 @@ export const leaderboardCommand: OSBMahojiCommand = { name: 'type', description: 'The leagues lb you want to select.', required: true, - choices: ['points', 'tasks', 'hardest_tasks'].map(i => ({ name: i, value: i })) + choices: ['tasks', 'hardest_tasks'].map(i => ({ name: i, value: i })) } ] }, @@ -1378,34 +1086,6 @@ export const leaderboardCommand: OSBMahojiCommand = { ironmanOnlyOption ] }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'movers', - description: 'Check the movers leaderboards.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'type', - description: 'The type of movers you want to check.', - required: true, - choices: gainersTypes.map(i => ({ name: i, value: i })) - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'global', - description: 'Check the global (OSB+BSO) leaderboards.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'type', - description: 'The global leaderboard type you want to check.', - required: true, - choices: globalLbTypes.map(i => ({ name: i, value: i })) - } - ] - }, { type: ApplicationCommandOptionType.Subcommand, name: 'completion', @@ -1439,26 +1119,21 @@ export const leaderboardCommand: OSBMahojiCommand = { userID, interaction }: CommandRunOptions<{ - kc?: { monster: string; ironmen_only?: boolean }; + kc?: { monster: string }; farming_contracts?: { ironmen_only?: boolean }; inferno?: {}; challenges?: {}; - sacrifice?: { type: 'value' | 'unique'; ironmen_only?: boolean }; - minigames?: { minigame: string; ironmen_only?: boolean }; + sacrifice?: { type: 'value' | 'unique' }; + minigames?: { minigame: string }; hunter_catches?: { creature: string }; agility_laps?: { course: string }; gp?: { ironmen_only?: boolean }; - skills?: { skill: string; ironmen_only?: boolean; xp?: boolean }; - opens?: { openable: string; ironmen_only?: boolean }; - cl?: { cl: string; ironmen_only?: boolean; tames?: boolean }; - item_contract_streak?: { ironmen_only?: boolean }; - leagues?: { type: 'points' | 'tasks' | 'hardest_tasks' }; - clues?: { clue: ClueTier['name']; ironmen_only?: boolean }; - movers?: { type: GainersType }; - global?: { - type: GlobalLbType; - }; - completion?: { untrimmed?: boolean; ironmen_only?: boolean }; + skills?: { skill: string; xp?: boolean }; + opens?: { openable: string }; + cl?: { cl: string; tames?: boolean }; + leagues?: { type: 'tasks' | 'hardest_tasks' }; + clues?: { clue: ClueTier['name'] }; + completion?: { untrimmed?: boolean }; combat_achievements?: {}; mastery?: {}; }>) => { @@ -1477,56 +1152,32 @@ export const leaderboardCommand: OSBMahojiCommand = { gp, skills, cl, - item_contract_streak, leagues, clues, - movers, - global, completion, - combat_achievements, - mastery + combat_achievements } = options; - if (kc) return kcLb(interaction, user, channelID, kc.monster, Boolean(kc.ironmen_only)); + if (kc) return kcLb(interaction, user, channelID, kc.monster, true); if (farming_contracts) { - return farmingContractLb(interaction, user, channelID, Boolean(farming_contracts.ironmen_only)); + return farmingContractLb(interaction, user, channelID); } if (inferno) return infernoLb(); if (challenges) return bsoChallenge(interaction, user, channelID); - if (sacrifice) - return sacrificeLb(interaction, user, channelID, sacrifice.type, Boolean(sacrifice.ironmen_only)); + if (sacrifice) return sacrificeLb(interaction, user, channelID, sacrifice.type); if (minigames) return minigamesLb(interaction, user, channelID, minigames.minigame); if (hunter_catches) return creaturesLb(interaction, user, channelID, hunter_catches.creature); if (agility_laps) return lapsLb(interaction, user, channelID, agility_laps.course); if (gp) return gpLb(interaction, user, channelID, Boolean(gp.ironmen_only)); if (skills) { - return skillsLb( - interaction, - user, - channelID, - skills.skill, - skills.xp ? 'xp' : 'level', - Boolean(skills.ironmen_only) - ); + return skillsLb(interaction, user, channelID, skills.skill, skills.xp ? 'xp' : 'level'); } - if (opens) return openLb(interaction, user, channelID, opens.openable, Boolean(opens.ironmen_only)); - if (cl) return clLb(interaction, user, channelID, cl.cl, Boolean(cl.ironmen_only), Boolean(cl.tames)); - if (item_contract_streak) - return itemContractLb(interaction, user, channelID, item_contract_streak.ironmen_only); + if (opens) return openLb(interaction, user, channelID, opens.openable, true); + if (cl) return clLb(interaction, user, channelID, cl.cl, true, Boolean(cl.tames)); if (leagues) return leaguesLeaderboard(interaction, user, channelID, leagues.type); - if (clues) return cluesLb(interaction, user, channelID, clues.clue, Boolean(clues.ironmen_only)); - if (movers) return gainersLB(interaction, user, channelID, movers.type); - if (global) return globalLb(interaction, user, channelID, global.type); - if (completion) - return compLeaderboard( - interaction, - user, - Boolean(completion.untrimmed), - Boolean(completion.ironmen_only), - channelID - ); + if (clues) return cluesLb(interaction, user, channelID, clues.clue, true); + if (completion) return compLeaderboard(interaction, user, Boolean(completion.untrimmed), true, channelID); if (combat_achievements) return caLb(interaction, user, channelID); - if (mastery) return masteryLb(interaction, user, channelID); return 'Invalid input.'; } }; diff --git a/src/mahoji/commands/leagues.ts b/src/mahoji/commands/leagues.ts index 9312cea8f2..2a71b7f892 100644 --- a/src/mahoji/commands/leagues.ts +++ b/src/mahoji/commands/leagues.ts @@ -1,8 +1,6 @@ import { ApplicationCommandOptionType } from 'discord.js'; import { Time, calcWhatPercent } from 'e'; -import { production } from '../../config'; -import { PerkTier } from '../../lib/constants'; import { allLeagueTasks, generateLeaguesTasksTextFile, @@ -10,7 +8,6 @@ import { leaguesCheckUser, leaguesClaimCommand } from '../../lib/leagues/leagues'; -import { getUsersPerkTier } from '../../lib/perkTiers'; import { type CommandRunOptions, formatDuration } from '../../lib/util'; import { deferInteraction } from '../../lib/util/interactionReply'; import { Cooldowns } from '../lib/Cooldowns'; @@ -72,13 +69,8 @@ export const bsoLeaguesCommand: OSBMahojiCommand = { view_all_tasks?: { exclude_finished?: boolean }; }>) => { await deferInteraction(interaction); - const user = await mUserFetch(userID); - const cooldown = Cooldowns.get( - userID.toString(), - 'leagues', - getUsersPerkTier(user) >= PerkTier.Two ? Time.Second * 5 : Time.Second * 30 - ); - if (cooldown && production) { + const cooldown = Cooldowns.get(userID.toString(), 'leagues', Time.Second * 30); + if (cooldown) { return `This command is on cooldown, you can use it again in ${formatDuration(cooldown)}.`; } const { content, finished } = await leaguesCheckUser(userID.toString()); @@ -90,14 +82,14 @@ export const bsoLeaguesCommand: OSBMahojiCommand = { if (!group) return 'Invalid task.'; const task = group.tasks.find(t => t.id.toString() === options.view_task?.task); if (!task) return 'Invalid task.'; - const count = await roboChimpClient.user.count({ + const count = await prisma.user.count({ where: { leagues_completed_tasks_ids: { has: task.id } } }); - const totalUsers = await roboChimpClient.user.count({ where: { leagues_points_total: { gt: 0 } } }); + const totalUsers = await prisma.user.count({ where: { leagues_completed_tasks_count: { gt: 0 } } }); const percentCompleted = calcWhatPercent(count, totalUsers); return `**Description:** ${task.name} **Tier:** ${group.name} @@ -106,7 +98,7 @@ ${percentCompleted.toFixed(2)}% of users have finished this task, ${ }`; } if (options.claim) { - return leaguesClaimCommand(userID, finished); + return leaguesClaimCommand(await mUserFetch(userID), finished); } if (options.view_all_tasks) { return generateLeaguesTasksTextFile(finished, options.view_all_tasks.exclude_finished); diff --git a/src/mahoji/commands/loot.ts b/src/mahoji/commands/loot.ts deleted file mode 100644 index 4f867d3d35..0000000000 --- a/src/mahoji/commands/loot.ts +++ /dev/null @@ -1,105 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; - -import { PerkTier } from '../../lib/constants'; -import { getAllTrackedLootForUser, getDetailsOfSingleTrackedLoot } from '../../lib/lootTrack'; - -import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import type { OSBMahojiCommand } from '../lib/util'; - -export const lootCommand: OSBMahojiCommand = { - name: 'loot', - description: 'View your loot tracker data.', - attributes: { - examples: ['/loot view name:Nex'] - }, - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'view', - description: 'View your tracked loot for a certain thing.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'name', - description: 'The thing you want to view.', - required: true, - autocomplete: async (value: string, user) => { - return (await getAllTrackedLootForUser(user.id)) - .filter(i => (!value ? true : i.key.toLowerCase().includes(value.toLowerCase()))) - .map(i => ({ - name: i.key, - value: i.id - })); - } - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'reset', - description: 'Reset one of your loot trackers.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'name', - description: 'The thing you want to reset.', - required: true, - autocomplete: async (value: string, user) => { - return (await getAllTrackedLootForUser(user.id)) - .filter(i => (!value ? true : i.key.toLowerCase().includes(value.toLowerCase()))) - .map(i => ({ - name: i.key, - value: i.id - })); - } - } - ] - } - ], - run: async ({ - options, - userID, - interaction - }: CommandRunOptions<{ view?: { name: string }; reset?: { name: string } }>) => { - const user = await mUserFetch(userID); - const name = options.view?.name ?? options.reset?.name ?? ''; - if (user.perkTier() < PerkTier.Four) { - const res = await prisma.lootTrack.count({ - where: { - user_id: BigInt(userID) - } - }); - return `You need to be a Tier 3 Patron to use this feature. You have ${res}x loot trackers stored currently.`; - } - - const trackedLoot = await prisma.lootTrack - .findFirst({ - where: { - id: name, - user_id: BigInt(userID) - } - }) - .catch(() => null); - if (!trackedLoot) { - return "The name you specified doesn't exist."; - } - - if (options.view) { - return getDetailsOfSingleTrackedLoot(user, trackedLoot); - } - if (options.reset) { - await handleMahojiConfirmation(interaction, 'Are you sure you want to reset this loot tracker?'); - await prisma.lootTrack.delete({ - where: { - id: trackedLoot.id - } - }); - const current = await getDetailsOfSingleTrackedLoot(user, trackedLoot); - current.content = `**You reset this loot tracker:\n\n${current.content}`; - return current; - } - - return 'Invalid command.'; - } -}; diff --git a/src/mahoji/commands/lottery.ts b/src/mahoji/commands/lottery.ts deleted file mode 100644 index 869ec30f25..0000000000 --- a/src/mahoji/commands/lottery.ts +++ /dev/null @@ -1,444 +0,0 @@ -import { userMention } from '@discordjs/builders'; -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { calcWhatPercent, sumArr } from 'e'; -import { Bank } from 'oldschooljs'; -import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; - -import { mahojiUserSettingsUpdate } from '../../lib/MUser'; -import { ores, secondaries, seedsFilter } from '../../lib/data/filterables'; -import { Herb } from '../../lib/invention/groups/Herb'; - -import Firemaking from '../../lib/skilling/skills/firemaking'; -import Runecraft from '../../lib/skilling/skills/runecraft'; -import { assert, isSuperUntradeable } from '../../lib/util'; -import { mahojiClientSettingsFetch } from '../../lib/util/clientSettings'; -import getOSItem from '../../lib/util/getOSItem'; -import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import { makeBankImage } from '../../lib/util/makeBankImage'; -import { parseBank } from '../../lib/util/parseStringBank'; -import { filterOption } from '../lib/mahojiCommandOptions'; -import type { OSBMahojiCommand } from '../lib/util'; -import { mahojiUsersSettingsFetch } from '../mahojiSettings'; - -async function addToLotteryBank(userID: string, bankToAdd: Bank) { - const currentUserSettings = await mahojiUsersSettingsFetch(userID, { - lottery_input: true - }); - const current = currentUserSettings.lottery_input as ItemBank; - const newBank = new Bank(current).add(bankToAdd); - - const res = await mahojiUserSettingsUpdate(userID, { - lottery_input: newBank.bank - }); - return res; -} - -const specialPricesBeforeMultiplying = new Bank() - .add('Monkey egg', 4_000_000_000) - .add('Dragon egg', 9_500_000_000) - .add('Dwarven warhammer', 37_000_500_000) - .add('Twisted bow', 7_500_000_000) - .add('Ring of luck', 150_000_000) - .add('Dwarven ore', 75_000_000) - .add('Dwarven bar', 200_000_000) - .add('Volcanic shards', 300_000_000) - - .add('Death rune', 200) - .add('Elder rune', 70_000) - .add('Manta ray', 15_000) - .add('Coal', 2000) - .add('Raw shark', 6000) - .add('Magic logs', 1000) - .add('Yew logs', 300) - .add('Rocktail', 40_000) - .add('Runite ore', 15_000) - .add('Adamantite ore', 12_000) - .add('Mithril ore', 10_000) - .add('Gold ore', 5000) - .add('Pure essence', 1700) - .add('Daeyalt essence', 22_000) - .add('Amethyst', 15_000) - .add('Uncut diamond', 15_000) - .add('Uncut dragonstone', 25_000) - .add('Uncut sapphire', 5000) - .add('Uncut emerald', 7000) - .add('Uncut ruby', 10_000) - .add('Elder logs', 40_000) - .add('Elder plank', 60_000) - .add('Raw tuna', 1000) - .add('Raw lobster', 1500) - .add('Raw swordfish', 2000) - .add('Raw rocktail', 35_000) - - .add('Torstol', 25_000) - .add('Grimy torstol', 25_000) - .add('Torstol potion (unf)', 25_000) - - .add('Toadflax', 50_000) - .add('Grimy toadflax', 50_000) - .add('Toadflax potion (unf)', 50_000) - - .add('Lantadyme', 50_000) - .add('Grimy lantadyme', 50_000) - .add('Lantadyme potion (unf)', 50_000) - - .add('Clue scroll(beginner)', 50_000) - .add('Clue scroll(easy)', 250_000) - .add('Clue scroll(medium)', 500_000) - .add('Clue scroll(hard)', 300_000) - .add('Clue scroll(elite)', 3_500_000) - .add('Clue scroll(master)', 6_000_000) - .add('Reward casket(beginner)', 50_000) - .add('Reward casket(easy)', 100_000) - .add('Reward casket(medium)', 200_000) - .add('Reward casket(hard)', 500_000) - .add('Reward casket(elite)', 4_000_000) - .add('Reward casket(master)', 9_000_000) - .add('Clue scroll(grandmaster)', 250_000_000) - .add('Reward casket(grandmaster)', 140_000_000) - // Drygores - .add('Drygore longsword', 1_230_000_000) - .add('Offhand drygore longsword', 1_230_000_000) - .add('Drygore mace', 1_230_000_000) - .add('Offhand drygore mace', 1_230_000_000) - .add('Drygore rapier', 1_230_000_000) - .add('Offhand drygore rapier', 1_230_000_000) - // Nex - .add('Torva full helm', 800_000_000) - .add('Torva platebody', 400_000_000) - .add('Torva platelegs', 400_000_000) - .add('Torva boots', 200_000_000) - .add('Torva gloves', 200_000_000) - - .add('Pernix cowl', 800_000_000) - .add('Pernix body', 400_000_000) - .add('Pernix chaps', 400_000_000) - .add('Pernix boots', 200_000_000) - .add('Pernix gloves', 200_000_000) - - .add('Virtus mask', 800_000_000) - .add('Virtus robe top', 400_000_000) - .add('Virtus robe legs', 400_000_000) - .add('Virtus boots', 200_000_000) - .add('Virtus gloves', 200_000_000) - .add('Virtus wand', 500_000_000) - .add('Virtus book', 500_000_000) - - // Planks - .add('Mahogany plank', 15_000) - .add('Teak plank', 5000) - .add('Oak plank', 2000) - - // Misc - .add('Abyssal thread', 50_000_000) - .add('Magus scroll', 150_000_000) - .add('Abyssal cape', 750_000_000) - .add('Dwarven blessing', 250_000_000) - .add('Ent hide', 20_000_000) - .add('Tradeable mystery box', 7_500_000) - .add('Untradeable mystery box', 5_500_000) - - .add('Ignecarus dragonclaw', 50_000_000) - .add('Blood dye', 1_500_000_000) - .add('Ice dye', 1_500_000_000) - .add('Shadow dye', 1_500_000_000) - .add('Third age dye', 3_000_000_000) - .add('Holiday mystery box', 75_000_000) - .add('Saradomin brew(4)', 70_000) - .add('Super restore(4)', 30_000) - .add('Prayer potion(4)', 100_000) - .add('Heat res. brew', 350_000) - .add('Heat res. restore', 350_000) - .add('Hellfire arrow', 30_000) - .add('Mysterious seed', 5_000_000); - -for (const herb of Herb.items.flatMap(i => i.item)) { - if (!specialPricesBeforeMultiplying.has(herb.id)) { - specialPricesBeforeMultiplying.add(herb.id, 10_000); - } -} - -for (const seed of seedsFilter.map(getOSItem)) { - if (!specialPricesBeforeMultiplying.has(seed.id)) { - specialPricesBeforeMultiplying.add(seed.id, seed.price * 3.5); - } -} - -for (const seed of secondaries.map(getOSItem)) { - if (!specialPricesBeforeMultiplying.has(seed.id)) { - specialPricesBeforeMultiplying.add(seed.id, seed.price * 3.5); - } -} - -for (const seed of ores.map(getOSItem)) { - if (!specialPricesBeforeMultiplying.has(seed.id)) { - specialPricesBeforeMultiplying.add(seed.id, seed.price * 3.5); - } -} -for (const seed of Runecraft.Runes.map(i => getOSItem(i.id))) { - if (!specialPricesBeforeMultiplying.has(seed.id)) { - specialPricesBeforeMultiplying.add(seed.id, seed.price * 3.5); - } -} -for (const seed of Firemaking.Burnables.map(i => getOSItem(i.inputLogs))) { - if (!specialPricesBeforeMultiplying.has(seed.id)) { - specialPricesBeforeMultiplying.add(seed.id, seed.price * 3.5); - } -} - -const toDelete = ['Fire rune', 'Air rune', 'Water rune', 'Earth rune', 'Body rune', 'Mind rune', 'Eye of newt']; -for (const item of toDelete) { - specialPricesBeforeMultiplying.remove(item, specialPricesBeforeMultiplying.amount(item)); -} -const MULTIPLIER = 3; - -const parsedPriceBank = new Bank(); -for (const [item, qty] of specialPricesBeforeMultiplying.items()) { - parsedPriceBank.add(item.id, qty * MULTIPLIER); -} - -export async function isLotteryActive(): Promise { - const result = await mahojiClientSettingsFetch({ lottery_is_active: true }); - return result.lottery_is_active; -} - -function getPriceOfItem(item: Item) { - if (parsedPriceBank.has(item.id)) { - return Math.floor(parsedPriceBank.amount(item.id)); - } - return item.price; -} - -const LOTTERY_TICKET_ITEM = getOSItem('Bank lottery ticket'); -assert(LOTTERY_TICKET_ITEM.id === 5021); -const VALUE_PER_TICKET = 10_000_000; - -function calcTicketsOfUser(user: MUser | Bank) { - const input = user instanceof Bank ? user : new Bank(user.user.lottery_input as ItemBank); - - let totalPrice = 0; - for (const [item, quantity] of input.items()) { - totalPrice += getPriceOfItem(item) * quantity; - } - - const amountOfTickets = Math.floor(totalPrice / VALUE_PER_TICKET); - return { amountOfTickets, input }; -} - -export async function getLotteryBank() { - const res = ( - await prisma.$queryRawUnsafe<{ lottery_input: ItemBank; id: string }[]>( - "SELECT id, lottery_input FROM users WHERE lottery_input::text != '{}'::text;" - ) - ) - .map(u => ({ - id: u.id, - lotteryInput: new Bank(u.lottery_input) - })) - .map(u => ({ - ...u, - tickets: calcTicketsOfUser(u.lotteryInput).amountOfTickets - })) - .sort((a, b) => b.tickets - a.tickets); - const totalLoot = new Bank(); - for (const i of res) { - totalLoot.add(i.lotteryInput); - } - const totalTickets = sumArr(res.map(i => i.tickets)); - return { - totalLoot, - users: res, - totalTickets - }; -} - -export const lotteryCommand: OSBMahojiCommand = { - name: 'lottery', - description: 'Win big!', - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'buy_tickets', - description: 'Deposit items into the lottery to receive tickets.', - options: [ - { - type: ApplicationCommandOptionType.Integer, - name: 'quantity', - description: 'The number of tickets to buy', - required: true - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'deposit_items', - description: 'Deposit items into the lottery to receive tickets.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'items', - description: 'The items you want to put in.', - required: false - }, - filterOption, - { - type: ApplicationCommandOptionType.String, - name: 'search', - description: 'A search query for items in your bank to put in.', - required: false - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'info', - description: 'View the lottery loot/stats/info.' - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'prices', - description: 'View the custom prices.' - } - ], - run: async ({ - userID, - options, - interaction - }: CommandRunOptions<{ - info?: {}; - prices?: {}; - buy_tickets?: { quantity: number }; - deposit_items?: { items?: string; filter?: string; search?: string }; - }>) => { - const infoStr = ` -1. This is a regular Lottery (no special event or DC items) -2. There'll be 4 spins, each winner winning 1/4th of the loot. -3. You can win more than once. -4. 5% of the items will be deleted (item-sunk), based on a random unbiased roll, and the GP. -5. The Lottery will run for a month roughly, possibly longer. -6. Items/GP put into the Lottery are non-refundable and cannot be taken out. -7. It's possible that we change the custom prices of items (make them worth more/less), if you already put those items in, your ticket count will automatically update to reflect the new price.`; - const active = await isLotteryActive(); - if (!active) return 'There is no lottery currently going on.'; - const user = await mUserFetch(userID); - if (user.isIronman) return 'Ironmen cannot partake in the Lottery.'; - - if (options.prices) { - return { files: [(await makeBankImage({ bank: parsedPriceBank, title: 'Prices' })).file] }; - } - if (options.buy_tickets) { - const amountOfTickets = options.buy_tickets.quantity; - if (amountOfTickets < 1) { - return 'You need to buy at least one ticket.'; - } - const totalPrice = amountOfTickets * VALUE_PER_TICKET; - const bankToSell = new Bank().add('Coins', totalPrice); - - if (!user.owns(bankToSell)) return 'You do not have enough GP to buy these tickets.'; - - await handleMahojiConfirmation( - interaction, - `${user.mention}, are you sure you want to add ${bankToSell} to the bank lottery - you'll receive **${amountOfTickets} bank lottery tickets**. - -**WARNING:** ${infoStr}` - ); - - await user.sync(); - if (!user.owns(bankToSell)) return "You don't have enough GP to buy these tickets."; - await user.removeItemsFromBank(bankToSell); - - await addToLotteryBank(user.id, bankToSell); - - return `You put ${bankToSell} to the bank lottery, and received ${amountOfTickets}x bank lottery tickets.`; - } - if (options.deposit_items) { - const bankToSell = parseBank({ - inputStr: options.deposit_items.items, - inputBank: user.bankWithGP, - excludeItems: [...user.user.favoriteItems], - maxSize: 50, - search: options.deposit_items.search, - filters: [options.deposit_items.filter], - user - }); - bankToSell.filter(i => !isSuperUntradeable(i), true); - - if (bankToSell.items().some(i => isSuperUntradeable(i[0].id))) { - return 'You cannot put in super untradeable items.'; - } - - if (bankToSell.amount('Bank lottery ticket')) { - bankToSell.remove('Bank lottery ticket', bankToSell.amount('Bank lottery ticket')); - } - - let totalPrice = 0; - for (const [item, quantity] of bankToSell.items()) { - totalPrice += getPriceOfItem(item) * quantity; - } - - if (bankToSell.length === 0) return 'No items were given.'; - if (!user.owns(bankToSell)) return 'You do not own these items.'; - - const amountOfTickets = Math.floor(totalPrice / VALUE_PER_TICKET); - - if (amountOfTickets < 1) { - return "Those items aren't worth enough, your deposit needs to be enough to get you atleast 1 ticket."; - } - - const perItemTickets = []; - for (const [item, quantity] of bankToSell - .items() - .sort((a, b) => getPriceOfItem(b[0]) * b[1] - getPriceOfItem(a[0]) * a[1]) - .slice(0, 10)) { - perItemTickets.push( - `${((quantity * getPriceOfItem(item)) / VALUE_PER_TICKET).toFixed(1)} tickets for ${quantity} ${ - item.name - }` - ); - } - - await handleMahojiConfirmation( - interaction, - `${ - user.mention - }, are you sure you want to add ${bankToSell} to the bank lottery - you'll receive **${amountOfTickets} bank lottery tickets**. ${perItemTickets.join( - ', ' - )} - -**WARNING:** ${infoStr}` - ); - - await user.sync(); - if (!user.owns(bankToSell)) return 'You do not own these items.'; - await user.removeItemsFromBank(bankToSell); - - await addToLotteryBank(user.id, bankToSell); - - return `You put ${bankToSell} to the bank lottery, and received ${amountOfTickets}x bank lottery tickets.`; - } - - const { amountOfTickets, input } = calcTicketsOfUser(user); - const { totalLoot, totalTickets, users } = await getLotteryBank(); - - return { - content: `There have been ${totalTickets.toLocaleString()} purchased, you have ${amountOfTickets.toLocaleString()}x tickets, and a ${ - amountOfTickets === 0 ? 0 : calcWhatPercent(amountOfTickets, totalTickets).toFixed(4) - }% chance of winning (will fluctuate based on you/others buying tickets.) - -${infoStr} - -Top ticket holders: ${users - .slice(0, 10) - .map(i => `${userMention(i.id)} has ${i.tickets.toLocaleString()} tickets`) - .join(',')}`, - files: [ - (await makeBankImage({ bank: totalLoot, title: 'Lottery' })).file, - (await makeBankImage({ bank: input, title: 'Your Lottery Input' })).file - ], - allowedMentions: { - users: [] - } - }; - } -}; diff --git a/src/mahoji/commands/m.ts b/src/mahoji/commands/m.ts index aed390f57f..b53171ad05 100644 --- a/src/mahoji/commands/m.ts +++ b/src/mahoji/commands/m.ts @@ -7,7 +7,7 @@ export const mCommand: OSBMahojiCommand = { name: 'm', description: 'See your current minion status and helpful buttons.', options: [], - run: async ({ userID, channelID }: CommandRunOptions) => { - return minionStatusCommand(await mUserFetch(userID), channelID.toString()); + run: async ({ userID }: CommandRunOptions) => { + return minionStatusCommand(await mUserFetch(userID)); } }; diff --git a/src/mahoji/commands/mass.ts b/src/mahoji/commands/mass.ts deleted file mode 100644 index 8439cf7d20..0000000000 --- a/src/mahoji/commands/mass.ts +++ /dev/null @@ -1,190 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import type { TextChannel } from 'discord.js'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { Time, objectKeys } from 'e'; - -import killableMonsters from '../../lib/minions/data/killableMonsters'; -import calculateMonsterFood from '../../lib/minions/functions/calculateMonsterFood'; -import hasEnoughFoodForMonster from '../../lib/minions/functions/hasEnoughFoodForMonster'; -import removeFoodFromUser from '../../lib/minions/functions/removeFoodFromUser'; -import type { KillableMonster } from '../../lib/minions/types'; -import { setupParty } from '../../lib/party'; -import type { GroupMonsterActivityTaskOptions } from '../../lib/types/minions'; -import { channelIsSendable, formatDuration } from '../../lib/util'; -import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; -import calcDurQty from '../../lib/util/calcMassDurationQuantity'; -import findMonster from '../../lib/util/findMonster'; -import { deferInteraction } from '../../lib/util/interactionReply'; -import type { OSBMahojiCommand } from '../lib/util'; -import { hasMonsterRequirements } from '../mahojiSettings'; - -function checkReqs(users: MUser[], monster: KillableMonster, quantity: number) { - // Check if every user has the requirements for this monster. - for (const user of users) { - if (!user.user.minion_hasBought) { - return `${user.usernameOrMention} doesn't have a minion, so they can't join!`; - } - - if (user.minionIsBusy) { - return `${user.usernameOrMention} is busy right now and can't join!`; - } - - if (user.user.minion_ironman) { - return `${user.usernameOrMention} is an ironman, so they can't join!`; - } - - const [hasReqs, reason] = hasMonsterRequirements(user, monster); - if (!hasReqs) { - return `${user.usernameOrMention} doesn't have the requirements for this monster: ${reason}`; - } - - if (1 > 2 && !hasEnoughFoodForMonster(monster, user, quantity, users.length)) { - return `${ - users.length === 1 ? "You don't" : `${user.usernameOrMention} doesn't` - } have enough food. You need at least ${monster.healAmountNeeded! * quantity} HP in food to ${ - users.length === 1 ? 'start the mass' : 'enter the mass' - }.`; - } - } -} - -export const massCommand: OSBMahojiCommand = { - name: 'mass', - description: 'Arrange to mass bosses, killing them as a group.', - attributes: { - requiresMinion: true, - requiresMinionNotBusy: true, - examples: ['/mass name:General graardor'] - }, - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'monster', - description: 'The boss you want to mass.', - required: true, - autocomplete: async value => { - return killableMonsters - .filter(i => i.groupKillable) - .filter(i => (!value ? true : i.name.toLowerCase().includes(value.toLowerCase()))) - .map(i => ({ name: i.name, value: i.name })); - } - } - ], - run: async ({ interaction, options, userID, channelID }: CommandRunOptions<{ monster: string }>) => { - await deferInteraction(interaction); - const user = await mUserFetch(userID); - if (user.user.minion_ironman) return 'Ironmen cannot do masses.'; - const channel = globalClient.channels.cache.get(channelID.toString()); - if (!channel || !channelIsSendable(channel)) return 'Invalid channel.'; - const monster = findMonster(options.monster); - if (!monster) return "That monster doesn't exist!"; - if (!monster.groupKillable) return "This monster can't be killed in groups!"; - - const check = checkReqs([user], monster, 2); - if (check) return check; - - let users: MUser[] = []; - try { - users = await setupParty(channel as TextChannel, user, { - leader: user, - minSize: 2, - maxSize: 10, - ironmanAllowed: false, - message: `${user.badgedUsername} is doing a ${monster.name} mass! Use the buttons below to join/leave.`, - customDenier: async user => { - if (!user.user.minion_hasBought) { - return [true, "you don't have a minion."]; - } - if (user.minionIsBusy) { - return [true, 'your minion is busy.']; - } - const [hasReqs, reason] = hasMonsterRequirements(user, monster); - if (!hasReqs) { - return [true, `you don't have the requirements for this monster; ${reason}`]; - } - - if (1 > 2 && monster.healAmountNeeded) { - try { - calculateMonsterFood(monster, user); - } catch (err: any) { - return [true, err]; - } - - // Ensure people have enough food for at least 2 full KC - // This makes it so the users will always have enough food for any amount of KC - if (1 > 2 && !hasEnoughFoodForMonster(monster, user, 2)) { - return [ - true, - `You don't have enough food. You need at least ${ - monster.healAmountNeeded * 2 - } HP in food to enter the mass.` - ]; - } - } - - return [false]; - } - }); - } catch (err: any) { - return { - content: typeof err === 'string' ? err : 'Your mass failed to start.', - ephemeral: true - }; - } - const unchangedUsers = [...users]; - users = users.filter(i => !i.minionIsBusy); - const usersKickedForBusy = unchangedUsers.filter(i => !users.includes(i)); - - const durQtyRes = await calcDurQty(users, monster, undefined); - if (typeof durQtyRes === 'string') return durQtyRes; - const [quantity, duration, perKillTime, boostMsgs] = durQtyRes; - - const checkRes = checkReqs(users, monster, quantity); - if (checkRes) return checkRes; - - if (1 > 2 && monster.healAmountNeeded) { - for (const user of users) { - const [healAmountNeeded] = calculateMonsterFood(monster, user); - await removeFoodFromUser({ - user, - totalHealingNeeded: Math.ceil(healAmountNeeded / users.length) * quantity, - healPerAction: Math.ceil(healAmountNeeded / quantity), - activityName: monster.name, - attackStylesUsed: objectKeys(monster.minimumGearRequirements ?? {}) - }); - } - } - - await addSubTaskToActivityTask({ - mi: monster.id, - userID: user.id, - channelID: channelID.toString(), - q: quantity, - duration, - type: 'GroupMonsterKilling', - leader: user.id, - users: users.map(u => u.id) - }); - - let killsPerHr = `${Math.round((quantity / (duration / Time.Minute)) * 60).toLocaleString()} Kills/hr`; - - if (boostMsgs.length > 0) { - killsPerHr += `\n\n${boostMsgs.join(', ')}.`; - } - let str = `${user.usernameOrMention}'s party (${users - .map(u => u.usernameOrMention) - .join(', ')}) is now off to kill ${quantity}x ${monster.name}. Each kill takes ${formatDuration( - perKillTime - )} instead of ${formatDuration(monster.timeToFinish)}- the total trip will take ${formatDuration( - duration - )}. ${killsPerHr}`; - - if (usersKickedForBusy.length > 0) { - str += `\nThe following users were removed, because their minion became busy before the mass started: ${usersKickedForBusy - .map(i => i.usernameOrMention) - .join(', ')}.`; - } - - return str; - } -}; diff --git a/src/mahoji/commands/megaduck.ts b/src/mahoji/commands/megaduck.ts deleted file mode 100644 index 232504a9a6..0000000000 --- a/src/mahoji/commands/megaduck.ts +++ /dev/null @@ -1,272 +0,0 @@ -import { Canvas } from '@napi-rs/canvas'; -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { Time } from 'e'; -import { Bank } from 'oldschooljs'; - -import { Events } from '../../lib/constants'; -import { type MegaDuckLocation, defaultMegaDuckLocation } from '../../lib/minions/types'; -import { getUsernameSync } from '../../lib/util'; -import { loadAndCacheLocalImage } from '../../lib/util/canvasUtil'; -import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import { mahojiGuildSettingsUpdate } from '../guildSettings'; -import { type OSBMahojiCommand, resetCooldown } from '../lib/util'; - -const apeAtoll = [1059, 1226]; -const portSarim = [1418, 422]; -const karamja = [1293, 554]; -const flyer = [1358, 728]; -const teleportationLocations = [ - [ - { name: 'Port Sarim', coords: portSarim }, - { name: 'Karamja', coords: karamja } - ], - [ - { name: 'Gnome Flyer', coords: flyer }, - { name: 'Ape Atoll', coords: apeAtoll } - ] -]; - -function locationIsFinished(location: MegaDuckLocation) { - return location.x < 770 && location.y > 1011; -} - -function topFeeders(entries: any[]) { - return `Top 10 Feeders: ${[...entries] - .sort((a, b) => b[1] - a[1]) - .slice(0, 10) - .map(ent => `${getUsernameSync(ent[0])}. ${ent[1]}`) - .join(', ')}`; -} - -const directions = ['up', 'down', 'left', 'right'] as const; -type MegaduckDirection = (typeof directions)[number]; - -function applyDirection(location: MegaDuckLocation, direction: MegaduckDirection): MegaDuckLocation { - const newLocation = { ...location }; - switch (direction) { - case 'up': - newLocation.y--; - break; - case 'down': - newLocation.y++; - break; - case 'left': - newLocation.x--; - break; - case 'right': - newLocation.x++; - break; - } - return newLocation; -} - -function getPixel(x: number, y: number, data: any, width: number) { - const i = (width * Math.round(y) + Math.round(x)) * 4; - return [data[i], data[i + 1], data[i + 2], data[i + 3]]; -} - -async function makeImage(location: MegaDuckLocation) { - const { x, y, steps = [] } = location; - const mapImage = await loadAndCacheLocalImage('./src/lib/resources/images/megaduckmap.png'); - const noMoveImage = await loadAndCacheLocalImage('./src/lib/resources/images/megaducknomovemap.png'); - - const scale = 3; - const canvasSize = 250; - - const centerPosition = Math.floor(canvasSize / 2 / scale); - - const canvas = new Canvas(canvasSize, canvasSize); - const ctx = canvas.getContext('2d'); - ctx.imageSmoothingEnabled = false; - - ctx.scale(scale, scale); - ctx.drawImage(mapImage, 0 - x + centerPosition, 0 - y + centerPosition); - - // image.addImage(noMoveImage as any, 0 - x + centerPosition, 0 - y + centerPosition); - - ctx.font = '14px Arial'; - ctx.fillStyle = '#ffff00'; - ctx.fillRect(centerPosition, centerPosition, 1, 1); - - const noMoveCanvas = new Canvas(noMoveImage.width, noMoveImage.height); - const noMoveCanvasCtx = noMoveCanvas.getContext('2d'); - noMoveCanvasCtx.drawImage(noMoveImage, 0, 0); - - const currentColor = getPixel( - x, - y, - noMoveCanvasCtx.getImageData(0, 0, noMoveCanvasCtx.canvas.width, noMoveCanvasCtx.canvas.height).data, - noMoveCanvas.width - ); - - ctx.fillStyle = 'rgba(0,0,255,0.05)'; - for (const [_xS, _yS] of steps) { - const xS = _xS - x + centerPosition; - const yS = _yS - y + centerPosition; - ctx.fillRect(xS, yS, 1, 1); - } - - const buffer = await canvas.encode('png'); - - return { - image: buffer, - currentColor - }; -} - -export const megaDuckCommand: OSBMahojiCommand = { - name: 'megaduck', - description: 'Mega duck!.', - attributes: { - requiresMinion: true, - cooldown: 30 * Time.Second - }, - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'move', - description: 'Move megaduck in a direction.', - required: false, - choices: directions.map(i => ({ name: i, value: i })) - }, - { - type: ApplicationCommandOptionType.Boolean, - name: 'reset', - description: 'Reset megaduck back to falador? (admin only)', - required: false - } - ], - run: async ({ - options, - userID, - guildID, - interaction - }: CommandRunOptions<{ move?: MegaduckDirection; reset?: boolean }>) => { - const user = await mUserFetch(userID); - const withoutCooldown = (message: string) => { - resetCooldown(user, 'megaduck'); - return message; - }; - - const guild = guildID ? globalClient.guilds.cache.get(guildID.toString()) : null; - if (!guild) return withoutCooldown('You can only run this in a guild.'); - - const settings = await prisma.guild.upsert({ - where: { - id: guild.id - }, - update: {}, - create: { - id: guild.id - }, - select: { - mega_duck_location: true, - id: true - } - }); - const location: Readonly = { - ...((settings.mega_duck_location as any) || defaultMegaDuckLocation) - }; - - const direction = options.move; - - const member = guild.members.cache.get(userID.toString()); - if (options.reset && member && member.permissions.has('Administrator')) { - await handleMahojiConfirmation( - interaction, - 'Are you sure you want to reset your megaduck back to Falador Park? This will reset all data, and where its been, and who has contributed steps.' - ); - await mahojiGuildSettingsUpdate(guild, { - mega_duck_location: { - ...defaultMegaDuckLocation, - steps: location.steps - } - }); - } - - const { image } = await makeImage(location); - if (!direction) { - return { - content: `${user} Mega duck is at ${location.x}x ${location.y}y. You've moved it ${ - location.usersParticipated[user.id] ?? 0 - } times. ${topFeeders(Object.entries(location.usersParticipated))}`, - files: [{ attachment: image, name: 'megaduck.png' }] - }; - } - - const cost = new Bank().add('Breadcrumbs'); - if (!user.owns(cost)) { - return withoutCooldown(`${user} The Mega Duck won't move for you, it wants some food.`); - } - - let newLocation = applyDirection(location, direction); - const newLocationResult = await makeImage(newLocation); - if (newLocationResult.currentColor[3] !== 0) { - return "You can't move here."; - } - - if (newLocation.usersParticipated[user.id]) { - newLocation.usersParticipated[user.id]++; - } else { - newLocation.usersParticipated[user.id] = 1; - } - - await user.removeItemsFromBank(cost); - newLocation = { ...defaultMegaDuckLocation, ...newLocation }; - let str = ''; - for (const link of teleportationLocations) { - const [first, second] = link; - if (newLocation.x === first.coords[0] && newLocation.y === first.coords[1]) { - newLocation.x = second.coords[0]; - newLocation.y = second.coords[1]; - str += `\n\nYou teleported from ${first.name} to ${second.name}.`; - } else if (newLocation.x === second.coords[0] && newLocation.y === second.coords[1]) { - newLocation.x = first.coords[0]; - newLocation.y = first.coords[1]; - str += `\n\nYou teleported from ${second.name} to ${first.name}.`; - } - } - newLocation.steps.push([newLocation.x, newLocation.y]); - await mahojiGuildSettingsUpdate(guild, { - mega_duck_location: newLocation as any - }); - if ( - !locationIsFinished(location) && - locationIsFinished(newLocation) && - !newLocation.placesVisited.includes('ocean') - ) { - const loot = new Bank().add('Baby duckling'); - const entries = Object.entries(newLocation.usersParticipated).sort((a, b) => b[1] - a[1]); - for (const [id] of entries) { - try { - const user = await mUserFetch(id); - await user.addItemsToBank({ items: loot, collectionLog: true }); - } catch {} - } - const newT: MegaDuckLocation = { - ...newLocation, - usersParticipated: {}, - placesVisited: [...newLocation.placesVisited, 'ocean'] - }; - await mahojiGuildSettingsUpdate(guild, { - mega_duck_location: newT as any - }); - globalClient.emit( - Events.ServerNotification, - `The ${guild.name} server just returned Mega Duck into the ocean with Mrs Duck, ${ - Object.keys(newLocation.usersParticipated).length - } users received a Baby duckling pet. ${topFeeders(entries)}` - ); - return `Mega duck has arrived at his destination! ${ - Object.keys(newLocation.usersParticipated).length - } users received a Baby duckling pet. ${topFeeders(entries)}`; - } - return { - content: `${user} You moved Mega Duck ${direction}! You've moved him ${ - newLocation.usersParticipated[user.id] - } times. Removed ${cost} from your bank.${str}`, - files: location.steps?.length % 2 === 0 ? [{ attachment: image, name: 'megaduck.png' }] : [] - }; - } -}; diff --git a/src/mahoji/commands/minigames.ts b/src/mahoji/commands/minigames.ts index 316279fe20..52e8f15e08 100644 --- a/src/mahoji/commands/minigames.ts +++ b/src/mahoji/commands/minigames.ts @@ -7,8 +7,7 @@ import { agilityArenaBuyCommand, agilityArenaBuyables, agilityArenaCommand, - agilityArenaRecolorCommand, - agilityArenaXPCommand + agilityArenaRecolorCommand } from '../lib/abstracted_commands/agilityArenaCommand'; import { BarbBuyables, @@ -1362,10 +1361,6 @@ export const minigamesCommand: OSBMahojiCommand = { if (options.agility_arena?.recolor) { return agilityArenaRecolorCommand(user); } - if (options.agility_arena?.xp) { - return agilityArenaXPCommand(user, options.agility_arena.xp.quantity); - } - /** * * Trouble Brewing diff --git a/src/mahoji/commands/minion.ts b/src/mahoji/commands/minion.ts index af20471785..c8f7c97f89 100644 --- a/src/mahoji/commands/minion.ts +++ b/src/mahoji/commands/minion.ts @@ -1,17 +1,17 @@ -import { formatOrdinal, roboChimpCLRankQuery } from '@oldschoolgg/toolkit'; +import { formatOrdinal } from '@oldschoolgg/toolkit'; import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import type { MahojiUserOption } from '@oldschoolgg/toolkit'; import { bold } from 'discord.js'; import { ApplicationCommandOptionType } from 'discord.js'; import { notEmpty, randArrItem } from 'e'; import { BLACKLISTED_USERS } from '../../lib/blacklists'; import { - BitField, + type BitField, BitFieldData, FormattedCustomEmoji, MAX_LEVEL, PerkTier, + badges, minionActivityCache } from '../../lib/constants'; import { degradeableItems } from '../../lib/degradeableItems'; @@ -22,16 +22,14 @@ import type { AttackStyles } from '../../lib/minions/functions'; import { blowpipeCommand, blowpipeDarts } from '../../lib/minions/functions/blowpipeCommand'; import { degradeableItemsCommand } from '../../lib/minions/functions/degradeableItemsCommand'; import { allPossibleStyles, trainCommand } from '../../lib/minions/functions/trainCommand'; -import { roboChimpCache } from '../../lib/perkTier'; -import { roboChimpUserFetch } from '../../lib/roboChimp'; +import { randomizationMethods } from '../../lib/randomizer'; import { Minigames } from '../../lib/settings/minigames'; import Skills from '../../lib/skilling/skills'; import creatures from '../../lib/skilling/skills/hunter/creatures'; import { MUserStats } from '../../lib/structures/MUserStats'; import { convertLVLtoXP, isValidNickname } from '../../lib/util'; -import { findGroupOfUser } from '../../lib/util/findGroupOfUser'; import { getKCByName } from '../../lib/util/getKCByName'; -import getOSItem, { getItem } from '../../lib/util/getOSItem'; +import getOSItem from '../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { minionStatsEmbed } from '../../lib/util/minionStatsEmbed'; import { checkPeakTimes } from '../../lib/util/minionUtils'; @@ -39,13 +37,9 @@ import { achievementDiaryCommand, claimAchievementDiaryCommand } from '../lib/abstracted_commands/achievementDiaryCommand'; -import { bankBgCommand } from '../lib/abstracted_commands/bankBgCommand'; import { cancelTaskCommand } from '../lib/abstracted_commands/cancelTaskCommand'; -import { crackerCommand } from '../lib/abstracted_commands/crackerCommand'; import { dailyCommand } from '../lib/abstracted_commands/dailyCommand'; import { feedHammyCommand } from '../lib/abstracted_commands/hammyCommand'; -import { ironmanCommand } from '../lib/abstracted_commands/ironmanCommand'; -import { Lampables, lampCommand } from '../lib/abstracted_commands/lampCommand'; import { minionBuyCommand } from '../lib/abstracted_commands/minionBuyCommand'; import { minionStatusCommand } from '../lib/abstracted_commands/minionStatusCommand'; import { ownedItemOption, skillOption } from '../lib/mahojiCommandOptions'; @@ -64,16 +58,20 @@ const patMessages = [ const randomPatMessage = (minionName: string) => randArrItem(patMessages).replace('{name}', minionName); export async function getUserInfo(user: MUser) { - const roboChimpUser = await roboChimpUserFetch(user.id); - const leaguesRanking = await roboChimpClient.user.count({ + const leaguesRanking = await prisma.user.count({ where: { - leagues_points_total: { - gte: roboChimpUser.leagues_points_total + leagues_completed_tasks_count: { + gte: user.user.leagues_completed_tasks_count + } + } + }); + const clRank = await prisma.user.count({ + where: { + cl_percent: { + gte: user.user.cl_percent ?? 0 } } }); - const clRankRaw = await roboChimpClient.$queryRawUnsafe<{ count: number }[]>(roboChimpCLRankQuery(BigInt(user.id))); - const clRank = clRankRaw[0].count; const bitfields = `${(user.bitfield as BitField[]) .map(i => BitFieldData[i]) @@ -84,37 +82,25 @@ export async function getUserInfo(user: MUser) { const task = minionActivityCache.get(user.id); const taskText = task ? `${task.type}` : 'None'; + const userBadges = user.user.badges.map(i => badges[i]); + const result = { perkTier: user.perkTier(), isBlacklisted: BLACKLISTED_USERS.has(user.id), - badges: user.badgesString, - isIronman: user.isIronman, + badges: userBadges, bitfields, - currentTask: taskText, - patreon: roboChimpUser.patreon_id ? 'Yes' : 'None', - github: roboChimpUser.github_id ? 'Yes' : 'None' + currentTask: taskText }; - const globalCLPercent = (((roboChimpUser.bso_cl_percent ?? 0) + (roboChimpUser.osb_cl_percent ?? 0)) / 2).toFixed( - 2 - ); - - const roboCache = roboChimpCache.get(user.id); return { ...result, everythingString: `${user.badgedUsername}[${user.id}] **Current Trip:** ${taskText} -**Perk Tier:** ${roboCache?.perk_tier ?? 'None'} **Blacklisted:** ${result.isBlacklisted} -**Badges:** ${result.badges} -**Ironman:** ${result.isIronman} +**Badges:** ${result.badges.join(' ')} **Bitfields:** ${result.bitfields} -**Patreon Connected:** ${result.patreon} -**Github Connected:** ${result.github} -**Leagues:** ${roboChimpUser.leagues_completed_tasks_ids.length} tasks, ${ - roboChimpUser.leagues_points_total - } points (Rank ${leaguesRanking > 500 ? 'Unranked! Get more points!' : formatOrdinal(leaguesRanking)}) -**Global CL:** ${globalCLPercent}% (${clRank > 500 ? 'Unranked! Get more CL slots completed!' : formatOrdinal(clRank)}) +**Leagues:** ${user.user.leagues_completed_tasks_ids.length} tasks, (Rank ${leaguesRanking > 500 ? 'Unranked! Finish more tasks!' : formatOrdinal(leaguesRanking)}) +**CL:** ${user.user.cl_percent}% (${clRank > 500 ? 'Unranked! Get more CL slots completed!' : formatOrdinal(clRank)}) ` }; } @@ -129,10 +115,11 @@ export const minionCommand: OSBMahojiCommand = { description: 'Buy a minion so you can start playing the bot!', options: [ { - type: ApplicationCommandOptionType.Boolean, - name: 'ironman', - description: 'Do you want to be an ironman?', - required: false + type: ApplicationCommandOptionType.String, + name: 'randomization_method', + description: 'The randomization method you want to use.', + required: true, + choices: randomizationMethods.map(i => ({ name: i.name, value: i.name })) } ] }, @@ -141,19 +128,6 @@ export const minionCommand: OSBMahojiCommand = { name: 'status', description: 'View the status of your minion.' }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'cracker', - description: 'Use a Christmas Cracker on someone.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user you want to use the cracker on.', - required: true - } - ] - }, { type: ApplicationCommandOptionType.Subcommand, name: 'stats', @@ -179,85 +153,6 @@ export const minionCommand: OSBMahojiCommand = { } ] }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'bankbg', - description: 'Change your bank background.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'name', - description: 'The name of the bank background you want.', - autocomplete: async (value, user) => { - const mUser = await mUserFetch(user.id); - const isMod = mUser.bitfield.includes(BitField.isModerator); - const bankImages = bankImageGenerator.backgroundImages; - const allAccounts = await findGroupOfUser(mUser.id); - const owned = bankImages - .filter( - bg => - (bg.storeBitField && mUser.user.store_bitfield.includes(bg.storeBitField)) || - bg.owners?.some(i => allAccounts.includes(i)) - ) - .map(bg => bg.id); - return bankImages - .filter(bg => isMod || bg.available || owned.includes(bg.id)) - .filter(bg => (!value ? true : bg.name.toLowerCase().includes(value.toLowerCase()))) - .map(i => { - const name = i.perkTierNeeded - ? `${i.name} (Tier ${i.perkTierNeeded - 1} patrons)` - : i.name; - return { name, value: i.name }; - }); - } - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'lamp', - description: 'Use lamps to claim XP.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'item', - description: 'The item you want to use.', - autocomplete: async (value, user) => { - const mappedLampables = Lampables.map(i => i.items) - .flat(2) - .map(getItem) - .filter(notEmpty) - .map(i => ({ id: i.id, name: i.name })); - - const botUser = await mUserFetch(user.id); - - return botUser.bank - .items() - .filter(i => mappedLampables.map(l => l.id).includes(i[0].id)) - .filter(i => { - if (!value) return true; - return i[0].name.toLowerCase().includes(value.toLowerCase()); - }) - .map(i => ({ name: `${i[0].name} (${i[1]}x Owned)`, value: i[0].name.toLowerCase() })); - }, - required: true - }, - { - ...skillOption, - required: true, - name: 'skill', - description: 'The skill you want to use the item on.' - }, - { - type: ApplicationCommandOptionType.Integer, - name: 'quantity', - description: 'You quantity you want to use.', - required: false, - min_value: 1, - max_value: 100_000 - } - ] - }, { type: ApplicationCommandOptionType.Subcommand, name: 'cancel', @@ -318,19 +213,6 @@ export const minionCommand: OSBMahojiCommand = { } ] }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'ironman', - description: 'Become an ironman, or de-iron.', - options: [ - { - type: ApplicationCommandOptionType.Boolean, - name: 'permanent', - description: 'Do you want to become a permanent ironman?', - required: false - } - ] - }, { type: ApplicationCommandOptionType.Subcommand, name: 'charge', @@ -439,7 +321,6 @@ export const minionCommand: OSBMahojiCommand = { ], run: async ({ userID, - user: apiUser, options, interaction, channelID @@ -447,15 +328,12 @@ export const minionCommand: OSBMahojiCommand = { stats?: { stat?: string }; achievementdiary?: { diary?: string; claim?: boolean }; bankbg?: { name?: string }; - cracker?: { user: MahojiUserOption }; - lamp?: { item: string; quantity?: number; skill: string }; cancel?: {}; set_icon?: { icon: string }; set_name?: { name: string }; level?: { skill: string }; kc?: { name: string }; - buy?: { ironman?: boolean }; - ironman?: { permanent?: boolean }; + buy?: { randomization_method: string }; charge?: { item?: string; amount?: number }; daily?: {}; train?: { style: AttackStyles }; @@ -472,8 +350,22 @@ export const minionCommand: OSBMahojiCommand = { const user = await mUserFetch(userID); const perkTier = user.perkTier(); + if (options.buy || !user.hasMinion) { + const randMethod = randomizationMethods.find(i => i.name === options.buy?.randomization_method); + if (!randMethod) { + return 'Invalid method.'; + } + + await handleMahojiConfirmation( + interaction, + `You chose **${randMethod.name} (${randMethod.desc})**, are you sure? Changing this later requires you to reset.` + ); + + return minionBuyCommand(user, randMethod); + } + if (options.info) return (await getUserInfo(user)).everythingString; - if (options.status) return minionStatusCommand(user, channelID.toString()); + if (options.status) return minionStatusCommand(user); if (options.stats) { return { embeds: [await minionStatsEmbed(user)] }; @@ -486,22 +378,6 @@ export const minionCommand: OSBMahojiCommand = { return achievementDiaryCommand(user, options.achievementdiary.diary ?? ''); } - if (options.bankbg) { - return bankBgCommand(interaction, user, options.bankbg.name ?? ''); - } - if (options.cracker) { - return crackerCommand({ - ownerID: userID.toString(), - otherPersonID: options.cracker.user.user.id, - interaction, - otherPersonAPIUser: options.cracker.user.user - }); - } - - if (options.lamp) { - return lampCommand(user, options.lamp.item, options.lamp.skill, options.lamp.quantity); - } - if (options.cancel) return cancelTaskCommand(user, interaction); if (options.set_icon) { @@ -547,9 +423,6 @@ export const minionCommand: OSBMahojiCommand = { return `Your ${kcName} KC is: ${kcAmount}.`; } - if (options.buy) return minionBuyCommand(apiUser, user, Boolean(options.buy.ironman)); - if (options.ironman) return ironmanCommand(user, interaction); - if (options.charge) { return degradeableItemsCommand(interaction, user, options.charge.item, options.charge.amount); } diff --git a/src/mahoji/commands/mix.ts b/src/mahoji/commands/mix.ts index e885144ad4..0907d58c3d 100644 --- a/src/mahoji/commands/mix.ts +++ b/src/mahoji/commands/mix.ts @@ -10,7 +10,6 @@ import type { HerbloreActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import type { OSBMahojiCommand } from '../lib/util'; export const mixCommand: OSBMahojiCommand = { @@ -152,8 +151,6 @@ export const mixCommand: OSBMahojiCommand = { await user.removeItemsFromBank(finalCost); - updateBankSetting('herblore_cost_bank', finalCost); - await addSubTaskToActivityTask({ mixableID: mixableItem.item.id, userID: user.id, diff --git a/src/mahoji/commands/nursery.ts b/src/mahoji/commands/nursery.ts index 7aa478be66..5aeac8c223 100644 --- a/src/mahoji/commands/nursery.ts +++ b/src/mahoji/commands/nursery.ts @@ -5,15 +5,13 @@ import { ApplicationCommandOptionType } from 'discord.js'; import { randArrItem, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; -import { production } from '../../config'; -import { Events } from '../../lib/constants'; +import { Events, globalConfig } from '../../lib/constants'; import { SkillsEnum } from '../../lib/skilling/types'; import { type Nursery, type Species, TameSpeciesID, tameSpecies } from '../../lib/tames'; import { formatDuration, gaussianRandom, roll } from '../../lib/util'; import { getItem } from '../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import type { OSBMahojiCommand } from '../lib/util'; function makeTameNickname(species: Species) { @@ -92,7 +90,7 @@ async function view(user: MUser) { diff += specie.hatchTime / 2; } const timeRemaining = Math.max(0, specie.hatchTime - diff); - if (diff >= specie.hatchTime || !production) { + if (diff >= specie.hatchTime || !globalConfig.isProduction) { const newNursery: NonNullable = { egg: null, eggsHatched: nursery.eggsHatched + 1, @@ -133,7 +131,6 @@ async function fuelCommand(interaction: ChatInputCommandInteraction, user: MUser `Are you sure you want to use ${cost} to fuel your nursery? You need to provide fuel once per egg.` ); await user.removeItemsFromBank(cost); - updateBankSetting('construction_cost_bank', cost); const newNursery: NonNullable = { ...nursery, hasFuel: true @@ -157,7 +154,6 @@ async function buildCommand(user: MUser) { return `You need ${cost} to build a nursery.`; } await user.removeItemsFromBank(cost); - updateBankSetting('construction_cost_bank', cost); const newNursery: Nursery = { egg: null, eggsHatched: 0, diff --git a/src/mahoji/commands/offer.ts b/src/mahoji/commands/offer.ts index c86a40bd76..afb67921eb 100644 --- a/src/mahoji/commands/offer.ts +++ b/src/mahoji/commands/offer.ts @@ -177,11 +177,6 @@ export const offerCommand: OSBMahojiCommand = { } } - const xpStr = await user.addXP({ - skillName: SkillsEnum.Prayer, - amount: quantity * 100 - }); - const { previousCL, itemsAdded } = await user.transactItems({ collectionLog: true, itemsToAdd: loot, @@ -200,7 +195,7 @@ export const offerCommand: OSBMahojiCommand = { }); return { - content: `You offered ${quantity}x ${egg.name} to the Shrine and received the attached loot and ${xpStr}.`, + content: `You offered ${quantity}x ${egg.name} to the Shrine and received the attached loot.`, files: [file] }; } @@ -218,17 +213,10 @@ export const offerCommand: OSBMahojiCommand = { if (amountHas < quantity) { return `You don't have ${quantity}x ${specialBone.item.name}, you have ${amountHas}.`; } - const xp = quantity * specialBone.xp; - await Promise.all([ - user.addXP({ - skillName: SkillsEnum.Construction, - amount: xp - }), - user.removeItemsFromBank(new Bank().add(specialBone.item.id, quantity)) - ]); + await Promise.all([user.removeItemsFromBank(new Bank().add(specialBone.item.id, quantity))]); return `You handed over ${quantity} ${specialBone.item.name}${ quantity > 1 ? "'s" : '' - } to Barlak and received ${xp} Construction XP.`; + } to Barlak and received no XP, weird.`; } const speedMod = 1.5; diff --git a/src/mahoji/commands/open.ts b/src/mahoji/commands/open.ts index eb221230f5..52c38ad1f5 100644 --- a/src/mahoji/commands/open.ts +++ b/src/mahoji/commands/open.ts @@ -4,11 +4,7 @@ import { ApplicationCommandOptionType } from 'discord.js'; import { clamp } from 'e'; import { allOpenables, allOpenablesIDs } from '../../lib/openables'; import { deferInteraction } from '../../lib/util/interactionReply'; -import { - OpenUntilItems, - abstractedOpenCommand, - abstractedOpenUntilCommand -} from '../lib/abstracted_commands/openCommand'; +import { abstractedOpenCommand } from '../lib/abstracted_commands/openCommand'; import type { OSBMahojiCommand } from '../lib/util'; export const openCommand: OSBMahojiCommand = { @@ -33,8 +29,7 @@ export const openCommand: OSBMahojiCommand = { val.toLowerCase().includes(value.toLowerCase()) ); }) - .map(i => ({ name: `${i[0].name} (${i[1]}x Owned)`, value: i[0].name.toLowerCase() })) - .concat([{ name: 'All (Open Everything)', value: 'all' }]); + .map(i => ({ name: `${i[0].name} (${i[1]}x Owned)`, value: i[0].name.toLowerCase() })); } }, { @@ -43,20 +38,7 @@ export const openCommand: OSBMahojiCommand = { description: 'The quantity you want to open (defaults to one).', required: false, min_value: 1, - max_value: 100_000 - }, - { - type: ApplicationCommandOptionType.String, - name: 'open_until', - description: 'Keep opening items until you get this item.', - required: false, - autocomplete: async (value: string) => { - if (!value) return OpenUntilItems.map(i => ({ name: i.name, value: i.name })); - return OpenUntilItems.filter(i => i.name.toLowerCase().includes(value.toLowerCase())).map(i => ({ - name: i.name, - value: i.name - })); - } + max_value: 1000 } ], run: async ({ @@ -72,13 +54,7 @@ export const openCommand: OSBMahojiCommand = { 1950 )}.`; } - options.quantity = clamp(options.quantity ?? 1, 1, 100_000_000); - if (options.open_until) { - return abstractedOpenUntilCommand(user.id, options.name, options.open_until); - } - if (options.name.toLowerCase() === 'all') { - return abstractedOpenCommand(interaction, user.id, ['all'], 'auto'); - } + options.quantity = clamp(options.quantity ?? 1, 1, 1000); return abstractedOpenCommand(interaction, user.id, [options.name], options.quantity); } }; diff --git a/src/mahoji/commands/patreon.ts b/src/mahoji/commands/patreon.ts deleted file mode 100644 index 686f93245d..0000000000 --- a/src/mahoji/commands/patreon.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Emoji } from '../../lib/constants'; -import type { OSBMahojiCommand } from '../lib/util'; - -export const patreonCommand: OSBMahojiCommand = { - name: 'patreon', - description: 'Shows the patreon link for the bot, where you can donate.', - options: [], - run: async () => { - return `You can become a patron to support me or thank me if you're enjoying the bot, and receive some perks. It's highly appreciated. OR ${Emoji.PeepoOSBot}`; - } -}; diff --git a/src/mahoji/commands/pay.ts b/src/mahoji/commands/pay.ts deleted file mode 100644 index fd172a228d..0000000000 --- a/src/mahoji/commands/pay.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import type { MahojiUserOption } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { Bank } from 'oldschooljs'; - -import { BLACKLISTED_USERS } from '../../lib/blacklists'; -import { Events } from '../../lib/constants'; - -import { toKMB } from '../../lib/util'; -import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import { deferInteraction } from '../../lib/util/interactionReply'; -import { tradePlayerItems } from '../../lib/util/tradePlayerItems'; -import type { OSBMahojiCommand } from '../lib/util'; -import { addToGPTaxBalance, mahojiParseNumber } from '../mahojiSettings'; - -export const payCommand: OSBMahojiCommand = { - name: 'pay', - description: 'Send GP to another user.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user you want to send the GP too.', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'amount', - description: 'The amount you want to send. (e.g. 100k, 1m, 2.5b, 5*100m)', - required: true - } - ], - run: async ({ - options, - userID, - interaction, - guildID - }: CommandRunOptions<{ - user: MahojiUserOption; - amount: string; - }>) => { - await deferInteraction(interaction); - const user = await mUserFetch(userID.toString()); - const recipient = await mUserFetch(options.user.user.id); - const amount = mahojiParseNumber({ input: options.amount, min: 1, max: 500_000_000_000 }); - // Ensure the recipient's users row exists: - if (!amount) return "That's not a valid amount."; - const { GP } = user; - const isBlacklisted = BLACKLISTED_USERS.has(recipient.id); - if (isBlacklisted) return "Blacklisted players can't receive money."; - if (recipient.id === user.id) return "You can't send money to yourself."; - if (user.isIronman) return "Iron players can't send money."; - if (recipient.isIronman) return "Iron players can't receive money."; - if (GP < amount) return "You don't have enough GP."; - if (options.user.user.bot) return "You can't send money to a bot."; - if (recipient.isBusy) return 'That user is busy right now.'; - - if (amount > 500_000_000) { - await handleMahojiConfirmation( - interaction, - `Are you sure you want to pay ${options.user.user.username}#${options.user.user.discriminator} (ID: ${ - recipient.id - }) ${toKMB(amount)}?` - ); - } - - const bank = new Bank().add('Coins', amount); - - const { success, message } = await tradePlayerItems(user, recipient, bank); - if (!success) { - return message; - } - - await prisma.economyTransaction.create({ - data: { - guild_id: guildID ? BigInt(guildID) : undefined, - sender: BigInt(user.id), - recipient: BigInt(recipient.id), - items_sent: bank.bank, - items_received: undefined, - type: 'trade' - } - }); - - globalClient.emit(Events.EconomyLog, `${user.mention} paid ${amount} GP to ${recipient.mention}.`); - addToGPTaxBalance(user.id, amount); - - return `You sent ${amount.toLocaleString()} GP to ${recipient}.`; - } -}; diff --git a/src/mahoji/commands/poh.ts b/src/mahoji/commands/poh.ts index 306a89b093..8bdab17b40 100644 --- a/src/mahoji/commands/poh.ts +++ b/src/mahoji/commands/poh.ts @@ -11,7 +11,6 @@ import { pohDestroyCommand, pohListItemsCommand, pohMountItemCommand, - pohWallkitCommand, pohWallkits } from '../lib/abstracted_commands/pohCommand'; import { ownedItemOption } from '../lib/mahojiCommandOptions'; @@ -117,7 +116,6 @@ export const pohCommand: OSBMahojiCommand = { interaction }: CommandRunOptions<{ view?: { build_mode?: boolean }; - wallkit?: { name: string }; build?: { name: string }; destroy?: { name: string }; mount_item?: { name: string }; @@ -128,9 +126,7 @@ export const pohCommand: OSBMahojiCommand = { if (options.view) { return makePOHImage(user, options.view.build_mode); } - if (options.wallkit) { - return pohWallkitCommand(user, options.wallkit.name); - } + if (minionIsBusy(user.id)) return 'You cannot interact with your PoH, because your minion is busy.'; if (options.build) { return pohBuildCommand(interaction, user, options.build.name); diff --git a/src/mahoji/commands/poll.ts b/src/mahoji/commands/poll.ts deleted file mode 100644 index da58c55896..0000000000 --- a/src/mahoji/commands/poll.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; - -import { channelIsSendable } from '../../lib/util'; -import { deferInteraction } from '../../lib/util/interactionReply'; -import type { OSBMahojiCommand } from '../lib/util'; - -export const pollCommand: OSBMahojiCommand = { - name: 'poll', - description: 'Create a reaction poll.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'question', - description: 'The poll question.', - required: true - } - ], - run: async ({ interaction, options, user, channelID }: CommandRunOptions<{ question: string }>) => { - const channel = globalClient.channels.cache.get(channelID.toString()); - if (!channelIsSendable(channel)) return { ephemeral: true, content: 'Invalid channel.' }; - await deferInteraction(interaction); - try { - const message = await channel.send({ - content: `**Poll from ${user.username}:** ${options.question}`, - allowedMentions: { - parse: [] - } - }); - await message.react('380915244760825857'); - await message.react('380915244652036097'); - return 'Poll created. Users can click on the two reactions to vote.'; - } catch { - return 'There was an error making the poll.'; - } - } -}; diff --git a/src/mahoji/commands/price.ts b/src/mahoji/commands/price.ts deleted file mode 100644 index 847142a249..0000000000 --- a/src/mahoji/commands/price.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { EmbedBuilder } from 'discord.js'; -import { toKMB } from 'oldschooljs/dist/util'; - -import { getItem } from '../../lib/util/getOSItem'; -import { itemOption } from '../lib/mahojiCommandOptions'; -import type { OSBMahojiCommand } from '../lib/util'; -import { sellPriceOfItem } from './sell'; - -export const priceCommand: OSBMahojiCommand = { - name: 'price', - description: 'Looks up the price of an item.', - options: [ - { - ...itemOption(item => Boolean(item.tradeable_on_ge)), - name: 'item', - required: true - } - ], - run: async ({ options }: CommandRunOptions<{ item: string }>) => { - const item = getItem(options.item); - if (!item || item.customItemData?.isSecret) return "Couldn't find that item."; - - const { basePrice: priceOfItem } = sellPriceOfItem(item); - - const embed = new EmbedBuilder() - .setTitle(item.name) - .setColor(52_224) - .setThumbnail( - `https://raw.githubusercontent.com/runelite/static.runelite.net/gh-pages/cache/item/icon/${item.id}.png` - ) - .setDescription( - `**Price:** ${toKMB(priceOfItem)} -**Sell price:** ${sellPriceOfItem(item).price} -**Alch value:** ${toKMB(item.highalch ?? 0)}` - ); - - return { embeds: [embed.data] }; - } -}; diff --git a/src/mahoji/commands/raid.ts b/src/mahoji/commands/raid.ts index a1b72e02b1..ef1897fae3 100644 --- a/src/mahoji/commands/raid.ts +++ b/src/mahoji/commands/raid.ts @@ -3,7 +3,6 @@ import { Bank } from 'oldschooljs'; import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { ApplicationCommandOptionType } from 'discord.js'; -import { production } from '../../config'; import { DOANonUniqueTable } from '../../lib/bso/doa/doaLootTable'; import { doaStartCommand } from '../../lib/bso/doa/doaStartCommand'; import { doaMetamorphPets } from '../../lib/data/CollectionsExport'; @@ -233,32 +232,7 @@ export const raidCommand: OSBMahojiCommand = { type: ApplicationCommandOptionType.Subcommand, name: 'help', description: 'Shows helpful information and stats about DOA.' - }, - ...(production - ? [] - : [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'simulate', - description: 'Shows helpful information and stats about DOA.', - options: [ - { - type: ApplicationCommandOptionType.Boolean, - name: 'challenge_mode', - description: 'Try if you dare.', - required: false - }, - { - type: ApplicationCommandOptionType.Integer, - name: 'team_size', - description: 'Team size (1-5).', - required: false, - min_value: 1, - max_value: 5 - } - ] - } as any - ]) + } ] } ], diff --git a/src/mahoji/commands/randomizer.ts b/src/mahoji/commands/randomizer.ts new file mode 100644 index 0000000000..34a6fe6b36 --- /dev/null +++ b/src/mahoji/commands/randomizer.ts @@ -0,0 +1,229 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; + +import { isFunction } from 'e'; +import { Bank } from 'oldschooljs'; +import { getTotalCl } from '../../lib/data/Collections'; +import Buyables from '../../lib/data/buyables/buyables'; +import Createables from '../../lib/data/createables'; +import { RANDOMIZER_HELP, randomizationMethods, remapBank } from '../../lib/randomizer'; +import { MUserStats } from '../../lib/structures/MUserStats'; +import type { ItemBank } from '../../lib/types'; +import { getItem, itemNameFromID } from '../../lib/util'; +import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; +import { minionBuyCommand } from '../lib/abstracted_commands/minionBuyCommand'; +import { itemOption } from '../lib/mahojiCommandOptions'; +import type { OSBMahojiCommand } from '../lib/util'; + +export const randomizerCommand: OSBMahojiCommand = { + name: 'randomizer', + description: 'randomizer.', + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: 'info', + description: 'View your randomizer information.' + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'unlock_item_mapping', + description: 'Check the item mapping for a specific item.', + options: [ + { + ...itemOption(), + name: 'item', + description: 'The item', + required: true + } + ] + }, + // { + // type: ApplicationCommandOptionType.Subcommand, + // name: 'test_mapping', + // description: 'test_mapping', + // options: [ + // { + // type: ApplicationCommandOptionType.String, + // name: 'items', + // description: 'The items', + // required: true + // } + // ] + // }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'reset', + description: 'Entirely reset your account/seed.', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'randomization_method', + description: 'The randomization method you want to use.', + required: true, + choices: randomizationMethods.map(i => ({ name: i.name, value: i.name })) + } + ] + } + ], + run: async ({ + interaction, + options, + userID + }: CommandRunOptions<{ + info?: {}; + unlock_item_mapping?: { item: string }; + // test_mapping?: { items: string }; + reset?: { randomization_method: string }; + }>) => { + const user = await mUserFetch(userID); + const map = user.user.item_map as ItemBank; + const reverseMap = user.user.reverse_item_map as ItemBank; + + if (options.info) { + let str = RANDOMIZER_HELP(user); + str += `\n\nYou have tier ${user.perkTier() - 1} perks, unlock higher tiers by filling out more collection log slots.`; + + // let mapStr = 'FROM -> TO\n\n'; + // for (const [key, val] of Object.entries(map)) { + // mapStr += `${itemNameFromID(Number(key))} (${key}) -> ${itemNameFromID(Number(val))} (${val}) \n`; + // } + // const attachment = Buffer.from(mapStr); + + let buyableStr = '[Buy This] -> [Get This]\n'; + for (const b of Buyables) { + const singleOutput: Bank = + b.outputItems === undefined + ? new Bank().add(b.name) + : b.outputItems instanceof Bank + ? b.outputItems + : b.outputItems(user); + buyableStr += `${b.name} -> ${remapBank(user, singleOutput)}\n`; + } + const attachment2 = Buffer.from(buyableStr); + + let creatablesStr = '[Create This] -> [Get This]\n'; + for (const c of Createables) { + const outItems = new Bank(isFunction(c.outputItems) ? c.outputItems(user) : c.outputItems); + const inItems = isFunction(c.inputItems) ? c.inputItems(user) : new Bank(c.inputItems); + + creatablesStr += `${inItems} -> ${remapBank(user, outItems)}\n`; + } + const attachment3 = Buffer.from(creatablesStr); + + return { + content: str, + files: [ + { attachment: attachment2, name: 'buyables.txt' }, + { attachment: attachment3, name: 'creatables.txt' } + ] + }; + } + + if (options.unlock_item_mapping) { + const itemID = options.unlock_item_mapping.item; + const item = getItem(itemID); + if (!item) { + return 'Invalid item.'; + } + const [, clSlots] = await getTotalCl(user, 'collection', await MUserStats.fromID(user.id)); + const SLOTS_PER_MAPPING = 50; + const amountTheyCanMap = Math.ceil(clSlots / SLOTS_PER_MAPPING); + + if (amountTheyCanMap < 1) { + return `You can't map any items, you need to fill out at least ${SLOTS_PER_MAPPING} collection log slots to unlock your first mapping.`; + } + + if (Object.keys(user.user.unlocked_item_map as ItemBank).length >= amountTheyCanMap) { + return `You can only map ${amountTheyCanMap} items, you have already mapped ${Object.keys(user.user.unlocked_item_map as ItemBank).length}. You unlock a new mapping every ${SLOTS_PER_MAPPING} collection log slots you fill out.`; + } + + const mapStr = `${itemNameFromID(reverseMap[item.id])} [${reverseMap[item.id]}] -> ${itemNameFromID(item.id)} [${item.id}] -> ${itemNameFromID(map[item.id])} (${map[item.id]})`; + if ((user.user.unlocked_item_map as ItemBank)[item.id]) { + return `This item is already unlocked: ${mapStr}`; + } + + await user.update({ + unlocked_item_map: { + ...(user.user.unlocked_item_map as ItemBank), + [item.id]: true + } + }); + return mapStr; + } + + // if (options.test_mapping) { + // if (!options.test_mapping.items) { + // return "You didn't provide any items."; + // } + // const bank = parseBank({ + // inputStr: options.test_mapping.items, + // maxSize: 70 + // }); + // bank.bank[995] = 1; + // let str = ''; + // for (const id of Object.keys(bank.bank).map(i => Number(i))) { + // str += `${itemNameFromID(reverseMap[id])} -> ${itemNameFromID(id)} -> ${itemNameFromID(map[id])}\n`; + // } + // return str; + // } + + if (options.reset) { + const maxResets = 5; + const previousResets = await prisma.fullReset.findMany({ + where: { + discord_id: user.id + } + }); + if (previousResets.length > maxResets) { + return `You have already reset your account ${previousResets.length}x times.`; + } + + const randMethod = randomizationMethods.find(i => i.name === options.reset?.randomization_method); + if (!randMethod) { + return 'Invalid method.'; + } + + await handleMahojiConfirmation( + interaction, + `Are you sure you want to reset your account? This will reset your entire account and seed. **You can only do this ${maxResets - previousResets.length}x times**! + +You chose: **${randMethod.name} (${randMethod.desc})**` + ); + + const transactions = []; + transactions.push(prisma.$executeRaw`SET CONSTRAINTS ALL DEFERRED`); + transactions.push(prisma.portent.deleteMany({ where: { user_id: user.id } })); + transactions.push(prisma.slayerTask.deleteMany({ where: { user_id: user.id } })); + transactions.push(prisma.slayerTask.deleteMany({ where: { user_id: user.id } })); + transactions.push(prisma.newUser.deleteMany({ where: { id: user.id } })); + transactions.push(prisma.minigame.deleteMany({ where: { user_id: user.id } })); + transactions.push(prisma.playerOwnedHouse.deleteMany({ where: { user_id: user.id } })); + transactions.push(prisma.activity.deleteMany({ where: { user_id: BigInt(user.id) } })); + transactions.push(prisma.lastManStandingGame.deleteMany({ where: { user_id: BigInt(user.id) } })); + transactions.push(prisma.userStats.deleteMany({ where: { user_id: BigInt(user.id) } })); + transactions.push(prisma.stashUnit.deleteMany({ where: { user_id: BigInt(user.id) } })); + transactions.push(prisma.tameActivity.deleteMany({ where: { user_id: user.id } })); + transactions.push(prisma.tame.deleteMany({ where: { user_id: user.id } })); + transactions.push(prisma.user.deleteMany({ where: { id: user.id } })); + transactions.push(prisma.userEvent.deleteMany({ where: { user_id: user.id } })); + transactions.push(prisma.lastManStandingGame.deleteMany({ where: { user_id: BigInt(user.id) } })); + transactions.push( + prisma.fullReset.create({ + data: { + discord_id: user.id + } + }) + ); + + try { + await prisma.$transaction(transactions); + } catch (err) { + console.error(`Failed to reset ${user.id}`, err); + return 'Failed to reset your account, please try again later.'; + } + return `Your account/seed has been reset.\n${await minionBuyCommand(await mUserFetch(userID), randMethod)}`; + } + + return 'Invalid command.'; + } +}; diff --git a/src/mahoji/commands/rates.ts b/src/mahoji/commands/rates.ts deleted file mode 100644 index 0ed69d6cf7..0000000000 --- a/src/mahoji/commands/rates.ts +++ /dev/null @@ -1,900 +0,0 @@ -import { bold } from '@discordjs/builders'; -import { type CommandRunOptions, calcPerHour, convertBankToPerHourStats, formatDuration } from '@oldschoolgg/toolkit'; -import type { InteractionReplyOptions } from 'discord.js'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { Time, increaseNumByPercent, reduceNumByPercent, sumArr } from 'e'; -import { uniq } from 'lodash'; -import { Bank } from 'oldschooljs'; -import type { Item } from 'oldschooljs/dist/meta/types'; - -import { - BathhouseOres, - BathwaterMixtures, - bathHouseTiers, - calculateBathouseResult, - durationPerBaxBath -} from '../../lib/baxtorianBathhouses'; -import { calcAtomicEnergy, divinationEnergies, memoryHarvestTypes } from '../../lib/bso/divination'; -import { TuraelsTrialsMethods, calculateTuraelsTrialsInput } from '../../lib/bso/turaelsTrials'; -import { ClueTiers } from '../../lib/clues/clueTiers'; -import { GLOBAL_BSO_XP_MULTIPLIER, PeakTier } from '../../lib/constants'; -import { Eatables } from '../../lib/data/eatables'; -import { inventionBoosts } from '../../lib/invention/inventions'; -import { marketPriceOfBank } from '../../lib/marketPrices'; -import killableMonsters from '../../lib/minions/data/killableMonsters'; -import { stoneSpirits } from '../../lib/minions/data/stoneSpirits'; -import Agility from '../../lib/skilling/skills/agility'; -import { - calcGorajanShardChance, - calcMaxFloorUserCanDo, - numberOfGorajanOutfitsEquipped -} from '../../lib/skilling/skills/dung/dungDbFunctions'; -import { - mutatedSourceItems, - zygomiteFarmingSource, - zygomiteSeedMutChance -} from '../../lib/skilling/skills/farming/zygomites'; -import Hunter from '../../lib/skilling/skills/hunter/hunter'; -import Mining from '../../lib/skilling/skills/mining'; -import Smithing from '../../lib/skilling/skills/smithing'; -import { HunterTechniqueEnum } from '../../lib/skilling/types'; -import { Gear } from '../../lib/structures/Gear'; -import type { BathhouseTaskOptions } from '../../lib/types/minions'; -import { stringMatches, toKMB } from '../../lib/util'; -import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; -import { deferInteraction } from '../../lib/util/interactionReply'; -import itemID from '../../lib/util/itemID'; -import { itemNameFromID, returnStringOrFile } from '../../lib/util/smallUtils'; -import { calculateHunterResult } from '../../tasks/minions/HunterActivity/hunterActivity'; -import { calculateAgilityResult } from '../../tasks/minions/agilityActivity'; -import { calculateDungeoneeringResult } from '../../tasks/minions/bso/dungeoneeringActivity'; -import { memoryHarvestResult, totalTimePerRound } from '../../tasks/minions/bso/memoryHarvestActivity'; -import { calculateTuraelsTrialsResult } from '../../tasks/minions/bso/turaelsTrialsActivity'; -import { calculateMiningResult } from '../../tasks/minions/miningActivity'; -import { gearstatToSetup, gorajanBoosts } from '../lib/abstracted_commands/minionKill'; -import type { OSBMahojiCommand } from '../lib/util'; -import { calculateHunterInput } from './hunt'; -import { calculateMiningInput } from './mine'; -import { determineTameClueResult } from './tames'; - -export const ratesCommand: OSBMahojiCommand = { - name: 'rates', - description: 'Check rates of various skills/activities.', - options: [ - { - type: ApplicationCommandOptionType.SubcommandGroup, - name: 'minigames', - description: 'Check minigames rates.', - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'baxtorian_bathhouses', - description: 'baxtorian bathhouses', - options: [] - } - ] - }, - { - type: ApplicationCommandOptionType.SubcommandGroup, - name: 'tames', - description: 'Check tames rates.', - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'eagle', - description: 'Eagle tame.', - options: [] - } - ] - }, - { - type: ApplicationCommandOptionType.SubcommandGroup, - name: 'xphr', - description: 'Check XP/hr rates.', - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'divination_memory_harvesting', - description: 'Divination.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'agility', - description: 'agility.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'dungeoneering', - description: 'Dungeoneering.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'mining', - description: 'Mining.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'hunter', - description: 'XP/hr rates for Hunter.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'turaels_trials', - description: 'XP/hr rates for TT.', - options: [] - } - ] - }, - { - type: ApplicationCommandOptionType.SubcommandGroup, - name: 'monster', - description: 'Check monster loot rates.', - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'monster', - description: 'Check monster.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'name', - description: 'The name of the monster.', - required: true - } - ] - } - ] - }, - { - type: ApplicationCommandOptionType.SubcommandGroup, - name: 'misc', - description: 'Miscelleanous rates.', - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'zygomite_seeds', - description: 'Check zygomite seeds.' - } - ] - } - ], - run: async ({ - options, - userID, - interaction - }: CommandRunOptions<{ - xphr?: { - divination_memory_harvesting?: {}; - agility?: {}; - dungeoneering?: {}; - mining?: {}; - hunter?: {}; - turaels_trials?: {}; - }; - monster?: { monster?: { name: string } }; - tames?: { eagle?: {} }; - misc?: { zygomite_seeds?: {} }; - minigames?: { baxtorian_bathhouses?: {} }; - }>) => { - await deferInteraction(interaction); - const user = await mUserFetch(userID); - - if (options.minigames?.baxtorian_bathhouses) { - const results = []; - for (const tier of bathHouseTiers) { - for (const ore of BathhouseOres) { - for (const mixture of BathwaterMixtures) { - results.push({ - ...calculateBathouseResult({ - mixture: mixture.name, - ore: ore.item.id, - tier: tier.name, - minigameID: 'bax_baths', - quantity: 4 - } as BathhouseTaskOptions), - tier, - ore, - mixture - }); - } - } - } - - results.sort( - (a, b) => b.firemakingXP * GLOBAL_BSO_XP_MULTIPLIER - a.firemakingXP * GLOBAL_BSO_XP_MULTIPLIER - ); - const tableArr = [['Combo', 'FM XP/hr', 'Herb XP/hr'].join('\t')]; - for (const { tier, ore, mixture, firemakingXP, herbXP } of results) { - const duration = durationPerBaxBath * 4; - const totalFiremakingXP = firemakingXP * GLOBAL_BSO_XP_MULTIPLIER; - const totalHerbloreXP = herbXP * GLOBAL_BSO_XP_MULTIPLIER; - tableArr.push( - [ - `${tier.name} ${ore.item.name} ${mixture.name}`, - toKMB(calcPerHour(totalFiremakingXP, duration)), - toKMB(calcPerHour(totalHerbloreXP, duration)) - ].join('\t') - ); - } - return { - ...(returnStringOrFile(tableArr.join('\n'), true) as InteractionReplyOptions) - }; - } - if (options.misc?.zygomite_seeds) { - const mutationChancePerMinute = 1 / zygomiteSeedMutChance; - - const validZygomiteList = uniq(mutatedSourceItems.map(m => m.zygomite)); - // Returns an array containing [totalWeight, totalWeightedChance] for each Zygomite - const survivalChanceData = validZygomiteList.map(z => - mutatedSourceItems - .filter(m => m.zygomite === z) - .reduce( - (acc: [number, number, string], m) => { - acc[0] += m.weight; - acc[1] += m.weight * (1 / m.surivalChance); - acc[2] = m.zygomite; - return acc; - }, - [0, 0, ''] - ) - ); - const survivalChancePerMutation = - sumArr(survivalChanceData.map(d => d[1] / d[0])) / validZygomiteList.length; - const avgSurvivalChance = 1 / survivalChancePerMutation; - - const chancePerMinuteBoth = mutationChancePerMinute * survivalChancePerMutation; - const averageMinutesToGetBoth = 1 / chancePerMinuteBoth; - const averageHoursToGetBoth = averageMinutesToGetBoth / 60; - return `For every minute in any trip, a random, valid seed from your bank has a 1 in ${zygomiteSeedMutChance} chance of mutating, and then that mutated seed has a 1 in ${avgSurvivalChance.toFixed( - 2 - )} (weighted average) chance of surviving. ${averageHoursToGetBoth.toFixed(1)} hours on average to get a zygomite seed. - -${zygomiteFarmingSource - .map( - z => - `${bold(z.seedItem.name)} evolves from: ${ - !z.mutatedFromItems - ? 'No items' - : mutatedSourceItems - .filter(msi => msi.zygomite === z.name) - .map(i => i.item.name) - .join(', ') - }, drops these items: ${!z.lootTable ? 'Nothing' : z.lootTable.allItems.map(itemNameFromID).join(', ')}.` - ) - .join('\n\n')}`; - } - if (options.tames?.eagle) { - let results = `${['Support Level', 'Clue Tier', 'Clues/hr', 'Kibble/hr', 'GMC/Hr'].join('\t')}\n`; - for (const tameLevel of [50, 60, 70, 75, 80, 85, 90, 95, 100]) { - for (const clueTier of ClueTiers) { - const res = determineTameClueResult({ - tameGrowthLevel: 3, - clueTier, - extraTripLength: Time.Hour * 10, - supportLevel: tameLevel, - equippedArmor: itemID('Abyssal jibwings (e)'), - equippedPrimary: itemID('Divine ring') - }); - - results += [ - tameLevel, - clueTier.name, - calcPerHour(res.quantity, res.duration).toLocaleString(), - calcPerHour(res.cost.amount('Extraordinary kibble'), res.duration).toLocaleString(), - calcPerHour(res.cost.amount('Clue scroll (grandmaster)'), res.duration).toLocaleString() - ].join('\t'); - results += '\n'; - } - } - - return { - content: 'Assumes abyssal jibwings (e) and divine ring', - ...(returnStringOrFile(results, true) as InteractionReplyOptions) - }; - } - - if (options.monster?.monster) { - const monster = killableMonsters.find(m => stringMatches(m.name, options.monster!.monster!.name)); - if (!monster) { - return 'Invalid monster.'; - } - - let { timeToFinish } = monster; - // 10% for learning - timeToFinish = reduceNumByPercent(timeToFinish, 10); - - timeToFinish /= 2; - - if (monster.pohBoosts) { - const totalBoostPercent = sumArr(Object.values(monster.pohBoosts).map(val => Object.values(val)[0])); - timeToFinish = reduceNumByPercent(timeToFinish, totalBoostPercent); - } - - if (monster.itemInBankBoosts) { - const boosts = sumArr(monster.itemInBankBoosts.map(b => Object.values(b)[0])); - timeToFinish = reduceNumByPercent(timeToFinish, boosts); - } - - if (!monster.wildy) { - timeToFinish = reduceNumByPercent(timeToFinish, 40); - } else if (monster.wildy) { - timeToFinish /= 3; - } - - timeToFinish = reduceNumByPercent(timeToFinish, 15); - - if (monster.equippedItemBoosts) { - for (const boostSet of monster.equippedItemBoosts) { - const equippedInThisSet = boostSet.items[0]; - if (equippedInThisSet) { - timeToFinish = reduceNumByPercent(timeToFinish, equippedInThisSet.boostPercent); - } - } - } - - const hasBlessing = true; - const hasZealotsAmulet = true; - if (hasZealotsAmulet && hasBlessing) { - timeToFinish *= 0.75; - } else if (hasBlessing) { - timeToFinish *= 0.8; - } - if (monster.wildy && hasZealotsAmulet) { - timeToFinish *= 0.95; - } - - const allGorajan = gorajanBoosts.every(e => user.gear[e[1]].hasEquipped(e[0], true)); - for (const [outfit, setup] of gorajanBoosts) { - if ( - allGorajan || - (gearstatToSetup.get(monster.attackStyleToUse) === setup && - user.gear[setup].hasEquipped(outfit, true)) - ) { - timeToFinish *= 0.9; - break; - } - } - - timeToFinish *= 0.85; - - timeToFinish *= 0.9; - - const bestFood = Eatables.filter(e => e.pvmBoost !== undefined).sort((a, b) => b.pvmBoost! - a.pvmBoost!)[0] - .pvmBoost; - timeToFinish = reduceNumByPercent(timeToFinish, bestFood!); - - const sampleSize = 100_000; - const boostedSize = increaseNumByPercent(sampleSize, 25); - - const loot = monster.table.kill(boostedSize, {}); - const totalTime = timeToFinish * sampleSize; - - let str = `${monster.name}\n`; - - const results: { item: Item; qty: number; perHour: number; valuePerHour: number }[] = []; - for (const [item, qty] of loot.items()) { - const perHour = calcPerHour(qty, totalTime); - const valuePerHour = calcPerHour(marketPriceOfBank(new Bank().add(item, qty)), totalTime); - results.push({ item, qty, perHour, valuePerHour }); - } - results.sort((a, b) => b.valuePerHour - a.valuePerHour); - str += '\nTop 10 most valuable item drops:\n'; - for (const { item, perHour, valuePerHour } of results.slice(0, 10)) { - str += `${item.name}: ${perHour.toFixed(2)}/hr ${toKMB(valuePerHour)} GP/hr\n`; - } - - str += '\n'; - - str += `Each kill takes ${formatDuration(timeToFinish)}, killing ${calcPerHour(sampleSize, totalTime)}/hr.`; - str += '\n'; - - str += `Based on market/bot prices, this monster produces ${toKMB( - calcPerHour(marketPriceOfBank(loot), totalTime) - )}/hr in GP.`; - - str += '\n\nAssumes max learning, poh boosts, all item boosts, DWWH/HFB, Ori.'; - return { - content: str, - files: [ - { - attachment: Buffer.from(`${results.map(i => [i.item.name, i.qty, i.perHour]).join('\n')}`), - name: `result-${monster.name}.txt` - } - ] - }; - } - if (options.xphr?.hunter) { - let results = `${[ - 'Creature', - 'XP/Hr', - 'Portent', - 'Quick Trap', - 'Max Learning', - 'Stam&Hunt Pots', - 'Successful qty/hr', - 'QuantityHunted' - ].join('\t')}\n`; - - for (const creature of Hunter.Creatures) { - for (const hasPortent of [true, false]) { - for (const _hasQuickTrap of [true, false]) { - for (const usingStamAndHunterPotions of [true, false]) { - for (const hasMaxLearning of [true, false]) { - const hasQuickTrap = - _hasQuickTrap && creature.huntTechnique === HunterTechniqueEnum.BoxTrapping; - if (creature.huntTechnique !== HunterTechniqueEnum.BoxTrapping && _hasQuickTrap) { - continue; - } - const duration = Time.Hour * 5; - const skillsAsLevels = { - ...user.skillsAsLevels, - hunter: 120, - fishing: 120, - agility: 120, - mining: 120, - woodcutting: 120, - prayer: 120, - herblore: 120 - }; - - const creatureScores = hasMaxLearning ? { [creature.id]: 100_000_000 } : {}; - - const gear = new Gear(); - gear.equip('Beginner rumble greegree'); - gear.equip('Masori body (f)'); - gear.equip('Masori chaps (f)'); - gear.equip('Dragon boots'); - gear.equip('Fire cape'); - - const bank = new Bank() - .add('Hunter potion(4)', 1000) - .add('Simple kibble', 100_000) - .add('Delicious kibble', 100_000) - .add('Stamina potion(4)', 1000) - .add('Eastern ferret', 10_000) - .add('Saradomin brew(4)', 1000) - .add('Super restore(4)', 1000) - .add('Banana', 100_000) - .add('Magic box', 50_000) - .add('Butterfly jar', 10_000); - - const fullSetup = { - wildy: gear - } as any; - - const inputResult = calculateHunterInput({ - creatureScores, - creature, - skillsAsLevels, - isUsingHunterPotion: usingStamAndHunterPotions, - shouldUseStaminaPotions: usingStamAndHunterPotions, - bank, - quantityInput: undefined, - allGear: fullSetup, - hasHunterMasterCape: true, - maxTripLength: duration, - isUsingQuickTrap: hasQuickTrap, - quickTrapMessages: '', - QP: 5000, - hasGraceful: true, - isUsingWebshooter: hasQuickTrap, - webshooterMessages: '', - user - }); - - if (typeof inputResult === 'string') { - console.log(inputResult); - continue; - } - const result = calculateHunterResult({ - creature, - allItemsOwned: new Bank(), - skillsAsLevels, - usingHuntPotion: usingStamAndHunterPotions, - usingStaminaPotion: true, - bank, - quantity: inputResult.quantity, - duration, - creatureScores, - allGear: user.gear, - collectionLog: new Bank(), - equippedPet: itemID('Sandy'), - skillsAsXP: skillsAsLevels, - hasHunterMasterCape: true, - wildyPeakTier: PeakTier.Low, - isUsingArcaneHarvester: false, - arcaneHarvesterMessages: undefined, - portentResult: hasPortent - ? { didCharge: true, portent: { charges_remaining: 1000 } as any } - : { didCharge: false }, - invincible: true, - noRandomness: true, - graceful: true, - experienceScore: 10 - }); - results += [ - creature.name, - Math.floor( - calcPerHour(result.totalHunterXP * GLOBAL_BSO_XP_MULTIPLIER, duration) - ).toLocaleString(), - hasPortent ? 'Has Portent' : 'No Portent', - hasQuickTrap ? 'Has QT/WS' : 'No QT/WS', - hasMaxLearning ? 'Max Learning' : 'No Max Learning', - usingStamAndHunterPotions ? 'Has Pots' : 'No Pots', - calcPerHour(result.successfulQuantity, duration).toLocaleString(), - inputResult.quantity - ].join('\t'); - results += '\n'; - } - } - } - } - } - return { - ...(returnStringOrFile(results, true) as InteractionReplyOptions), - content: 'Assumes: Hunter master cape, level 120 Hunter, full Graceful, Sandy pet equipped.' - }; - } - - if (options.xphr?.turaels_trials) { - let results = `${[ - 'Method', - 'Slayer XP/Hr', - 'Melee XP/Hr', - 'Range XP/Hr', - 'Mage XP/Hr', - 'Loot/hr', - 'Cost/hr' - ].join('\t')}\n`; - const maxTripLength = Time.Hour; - - for (const method of TuraelsTrialsMethods) { - const input = calculateTuraelsTrialsInput({ maxTripLength, method, isUsingBloodFury: true }); - const result = calculateTuraelsTrialsResult({ quantity: input.quantity, method }); - const { duration } = input; - if (input.chargeBank.amount('scythe_of_vitur_charges') !== 0) { - input.cost.add('Scythe of vitur', input.chargeBank.amount('scythe_of_vitur_charges')); - } - if (input.chargeBank.amount('blood_fury_charges') !== 0) { - input.cost.add('Scythe of vitur', input.chargeBank.amount('blood_fury_charges')); - } - if (input.hpHealingNeeded !== 0) { - input.cost.add('Rocktail', Math.ceil(input.hpHealingNeeded / 26)); - } - - let slayerXP = result.xpBank.amount('slayer') * GLOBAL_BSO_XP_MULTIPLIER; - slayerXP = increaseNumByPercent(slayerXP, 8); - results += [ - method, - Math.floor(calcPerHour(slayerXP, duration)).toLocaleString(), - Math.floor( - calcPerHour(result.xpBank.amount('attack') * GLOBAL_BSO_XP_MULTIPLIER, duration) - ).toLocaleString(), - Math.floor( - calcPerHour(result.xpBank.amount('ranged') * GLOBAL_BSO_XP_MULTIPLIER, duration) - ).toLocaleString(), - Math.floor( - calcPerHour(result.xpBank.amount('magic') * GLOBAL_BSO_XP_MULTIPLIER, duration) - ).toLocaleString(), - convertBankToPerHourStats(result.loot, duration).join(', '), - convertBankToPerHourStats(input.cost, duration).join(', ') - ].join('\t'); - results += '\n'; - } - - return { - ...(returnStringOrFile(results, true) as InteractionReplyOptions), - content: 'Assumes: Slayer master cape (8% boost to slayer xp)' - }; - } - - if (options.xphr?.mining) { - let results = `${[ - 'Ore', - 'XP/Hr', - 'Powermining', - 'Portent', - 'S. Inferno Adze', - 'Volcanic Pickaxe', - 'Smithing XP', - 'Loot', - 'Cost' - ].join('\t')}\n`; - const duration = Number(Time.Hour); - for (const ore of Mining.Ores) { - for (const isPowerminingInput of [true, false]) { - for (const shouldTryUseSpirits of [true, false]) { - for (const shouldUsePortent of [true, false]) { - for (const usingAdze of [true, false]) { - for (const hasOffhandVolcPick of [true, false]) { - if (shouldUsePortent && !shouldTryUseSpirits) continue; - - const smeltedOre = Smithing.Bars.find( - o => - o.inputOres.bank[ore.id] && - o.inputOres.items().filter(i => i[0].name !== 'Coal').length === 1 - ); - if (usingAdze && (!smeltedOre || isPowerminingInput)) continue; - - const miningLevel = 120; - const fakeGear = user.gear.skilling.clone(); - fakeGear.equip('Volcanic pickaxe'); - fakeGear.equip('Varrock armour 4'); - fakeGear.equip('Mining master cape'); - fakeGear.equip('Amulet of glory'); - fakeGear.equip('Prospector helmet'); - fakeGear.equip('Prospector jacket'); - fakeGear.equip('Prospector legs'); - fakeGear.equip('Prospector boots'); - - const secondaryGearSetup = fakeGear.clone(); - if (usingAdze) { - secondaryGearSetup.equip('Superior inferno adze'); - } - - const fullSetup = { - skilling: fakeGear, - misc: secondaryGearSetup - } as any; - - const result = calculateMiningInput({ - nameInput: ore.name, - quantityInput: undefined, - isPowermining: isPowerminingInput, - gear: fullSetup, - hasSOTFQuest: true, - qp: 500, - miningLevel, - craftingLevel: 120, - strengthLevel: 120, - maxTripLength: duration, - user - }); - if (typeof result === 'string') continue; - const spiritOre = stoneSpirits.find(t => t.ore.id === ore.id); - const amountOfSpiritsToUse = - spiritOre !== undefined && shouldTryUseSpirits - ? Math.min(result.newQuantity, 100_000) - : 0; - - if (shouldTryUseSpirits && !spiritOre) continue; - const { totalMiningXPToAdd, smithingXPFromAdze, loot, totalCost } = - calculateMiningResult({ - duration, - isPowermining: result.isPowermining, - isUsingObsidianPickaxe: hasOffhandVolcPick, - quantity: result.newQuantity, - hasMiningMasterCape: true, - ore, - allGear: fullSetup, - miningLevel, - disabledInventions: [], - equippedPet: null, - amountOfSpiritsToUse, - spiritOre, - portentResult: !shouldUsePortent - ? { didCharge: false } - : ({ didCharge: true, portent: { charges_remaining: 1000 } } as any), - collectionLog: new Bank(), - miningXP: 500_000_000 - }); - - results += [ - ore.name, - Math.floor( - calcPerHour(totalMiningXPToAdd * GLOBAL_BSO_XP_MULTIPLIER, duration) - ).toLocaleString(), - result.isPowermining ? 'Powermining' : 'NOT Powermining', - shouldUsePortent ? 'Has Portent' : 'No Portent', - usingAdze ? 'Has Adze' : 'No Adze', - hasOffhandVolcPick ? 'Has Volc Pick' : 'No Volc Pick', - calcPerHour(smithingXPFromAdze * GLOBAL_BSO_XP_MULTIPLIER, duration), - convertBankToPerHourStats(loot, duration).join(', '), - convertBankToPerHourStats(totalCost, duration).join(', ') - ].join('\t'); - results += '\n'; - } - } - } - } - } - } - return { - ...(returnStringOrFile(results, true) as InteractionReplyOptions), - content: - 'Assumes: Mining master cape, full Prospector, Glory, Varrock armour 4, 120 mining, Volcanic pickaxe.' - }; - } - - if (options.xphr?.divination_memory_harvesting) { - let results = `${[ - 'Type', - 'Method', - 'Wisp-buster', - 'Cache Boost', - 'Divine Hand', - 'Divination Potion', - 'Boon', - 'Pet Time (Hours)', - 'XP/Hr', - 'Memories/HR', - 'GMC/hr', - 'MC/hr', - 'EnergyLoot/hr', - 'EnergyCost/hr', - 'Energy per memory', - 'Hours for Boon', - 'Atomic energy/hr' - ].join('\t')}\n`; - for (const energy of divinationEnergies) { - for (const harvestMethod of memoryHarvestTypes) { - for (const hasCacheBoost of [true, false]) { - for (const hasPotAndBoon of [true, false]) { - for (const hasWispBuster of [true, false]) { - for (const hasDivineHand of [true, false]) { - if (hasDivineHand && hasWispBuster) continue; - const duration = Time.Hour; - const totalSeconds = Math.round(duration / Time.Second); - const rounds = Math.floor(totalSeconds / totalTimePerRound); - const res = memoryHarvestResult({ - duration, - energy, - hasBoon: hasPotAndBoon, - harvestMethod: harvestMethod.id, - hasGuthixianBoost: hasCacheBoost, - hasDivineHand, - hasWispBuster, - isUsingDivinationPotion: hasPotAndBoon, - hasMasterCape: false, - rounds - }); - - const energyReceived = res.loot.amount(energy.item.id); - const energyPerHour = calcPerHour(energyReceived, Time.Hour); - - const nextEnergy = divinationEnergies[divinationEnergies.indexOf(energy) + 1]; - let timeToGetBoon = 0; - if ( - nextEnergy?.boonEnergyCost && - energyPerHour > 0 && - res.loot.has(energy.item.id) - ) { - timeToGetBoon = nextEnergy.boonEnergyCost / energyPerHour; - } - - const atomicEnergyPerHour = - energyReceived === 0 - ? '0' - : calcPerHour(energyReceived * calcAtomicEnergy(energy), duration).toFixed( - 1 - ); - - results += [ - energy.type, - harvestMethod.name, - hasWispBuster ? 'Has Wisp-buster' : 'No Wisp-buster', - hasCacheBoost ? 'Has Cache Boost' : 'No Cache Boost', - hasDivineHand ? 'Has Divine Hand' : 'No Divine Hand', - hasPotAndBoon ? 'Has Pot' : 'No Pot', - hasPotAndBoon ? 'Has Boon' : 'No Boon', - res.avgPetTime / Time.Hour, - res.totalDivinationXP * GLOBAL_BSO_XP_MULTIPLIER, - calcPerHour(res.totalMemoriesHarvested, Time.Hour), - calcPerHour(res.loot.amount('Clue scroll (grandmaster)'), Time.Hour), - calcPerHour(res.loot.amount('Clue scroll (master)'), Time.Hour), - energyPerHour, - calcPerHour(res.cost.amount(energy.item.id), Time.Hour), - res.energyPerMemory, - timeToGetBoon, - atomicEnergyPerHour - ].join('\t'); - results += '\n'; - } - } - } - } - } - } - - return returnStringOrFile(results); - } - - if (options.xphr?.agility) { - let results = `${[ - 'Course', - 'XP/Hr', - 'Marks/hr', - 'Agility Level', - 'Silver Hawk Boots', - 'Portent', - 'Harry' - ].join('\t')}\n`; - for (const course of Agility.Courses) { - for (const usingSilverHawks of [true, false]) { - for (const usingHarry of [true, false]) { - for (const usingPortent of [true, false]) { - let timePerLap = course.lapTime * Time.Second; - if (usingSilverHawks) { - timePerLap = Math.floor( - timePerLap / inventionBoosts.silverHawks.agilityBoostMultiplier - ); - } - const quantity = Math.floor(Time.Hour / timePerLap); - const duration = quantity * timePerLap; - const agilityLevel = 120; - const result = calculateAgilityResult({ - quantity, - course, - agilityLevel, - duration, - hasDiaryBonus: true, - usingHarry, - hasAgilityPortent: usingPortent - }); - const xpHr = calcPerHour(result.xpReceived * GLOBAL_BSO_XP_MULTIPLIER, duration); - results += [ - course.name, - Math.round(xpHr), - calcPerHour(result.loot.amount('Mark of grace'), duration).toFixed(1), - agilityLevel, - usingSilverHawks ? 'Yes' : 'No', - usingPortent ? 'Yes' : 'No', - usingHarry ? 'Yes' : 'No' - ].join('\t'); - results += '\n'; - } - } - } - } - return returnStringOrFile(results, true); - } - - if (options.xphr?.dungeoneering) { - let results = `${['Floor', 'XP/Hr', 'Dung. Level', 'Tokens/hr', 'Portent', 'G. Shard Time'].join('\t')}\n`; - for (const floor of [1, 2, 3, 4, 5, 6, 7]) { - for (const hasPortent of [true, false]) { - const dungeonLength = Time.Minute * 5 * (floor / 2); - const quantity = Math.floor(calcMaxTripLength(user, 'Dungeoneering') / dungeonLength); - const duration = quantity * dungeonLength; - const dungeoneeringLevel = 120; - const goraShardChance = calcGorajanShardChance({ - dungLevel: dungeoneeringLevel, - hasMasterCape: user.hasEquipped('Dungeoneering master cape'), - hasRingOfLuck: user.hasEquipped('Ring of luck') - }); - const result = calculateDungeoneeringResult({ - floor, - quantity, - duration, - dungeoneeringLevel, - gorajanShardChance: goraShardChance.chance, - maxFloorUserCanDo: calcMaxFloorUserCanDo(user), - hasScrollOfMystery: true, - gorajanEquipped: numberOfGorajanOutfitsEquipped(user), - hasDungeonPortent: hasPortent - }); - const xpHr = calcPerHour(result.xp * GLOBAL_BSO_XP_MULTIPLIER, duration); - results += [ - floor, - Math.round(xpHr), - dungeoneeringLevel, - calcPerHour(result.tokens, duration), - hasPortent ? 'Has Portent' : 'No Portent', - floor >= 5 ? `${formatDuration(result.goraShardChanceX * duration)}` : 'N/A' - ].join('\t'); - results += '\n'; - } - } - return { - ...(returnStringOrFile(results, true) as InteractionReplyOptions), - content: 'Assumes: 120 Dungeoneering, Ring of luck, master cape (For gora shard chance)' - }; - } - return 'No option selected.'; - } -}; diff --git a/src/mahoji/commands/redeem.ts b/src/mahoji/commands/redeem.ts deleted file mode 100644 index 70e8467cd4..0000000000 --- a/src/mahoji/commands/redeem.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { ProductID, products } from '@oldschoolgg/toolkit'; -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType, bold } from 'discord.js'; - -import { Time, notEmpty } from 'e'; -import { BOT_TYPE } from '../../lib/constants'; -import { addToDoubleLootTimer } from '../../lib/doubleLoot'; -import { roboChimpSyncData } from '../../lib/roboChimp'; -import type { OSBMahojiCommand } from '../lib/util'; - -export const redeemCommand: OSBMahojiCommand = { - name: 'redeem', - description: 'Redeem a code you received.', - attributes: { - cooldown: 10 - }, - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'code', - description: 'The code to redeem.', - required: true - } - ], - run: async ({ options, userID }: CommandRunOptions<{ code: string }>) => { - const user = await mUserFetch(userID); - const code = await roboChimpClient.storeCode.findFirst({ - where: { - code: options.code - } - }); - if (!code) { - return 'That code is invalid.'; - } - if (code.redeemed_at) { - return 'That code has already been redeemed.'; - } - - const product = products.find(p => p.id === code.product_id); - if (!product) { - throw new Error('Invalid product ID.'); - } - await roboChimpSyncData(user); - if (product.type === 'bit' && user.user.store_bitfield.includes(product.id)) { - return 'You already have this, redeeming it again would be a waste!'; - } - - if (BOT_TYPE === 'OSB') { - if (product.type === 'active') { - switch (product.id) { - case ProductID.OneHourDoubleLoot: { - return 'You cannot redeem this on OSB.'; - } - case ProductID.ThreeHourDoubleLoot: { - return 'You cannot redeem this on OSB.'; - } - } - } - } - - await roboChimpClient.$transaction( - [ - roboChimpClient.storeCode.update({ - where: { - code: options.code - }, - data: { - redeemed_at: new Date(), - redeemed_by_user_id: user.id - } - }), - 'bit' in product - ? roboChimpClient.user.update({ - where: { - id: BigInt(userID) - }, - data: { - store_bitfield: { - push: product.bit - } - } - }) - : undefined - ].filter(notEmpty) - ); - - if (BOT_TYPE === 'BSO') { - if (product.type === 'active') { - switch (product.id) { - case ProductID.OneHourDoubleLoot: { - await addToDoubleLootTimer(Time.Hour, `Purchased by ${user}`); - break; - } - case ProductID.ThreeHourDoubleLoot: { - await addToDoubleLootTimer(Time.Hour * 3, `Purchased by ${user}`); - break; - } - } - } - } - - await roboChimpSyncData(user); - - return `You have redeemed: ${bold(product.name)}!`; - } -}; diff --git a/src/mahoji/commands/relic.ts b/src/mahoji/commands/relic.ts new file mode 100644 index 0000000000..53f41dc270 --- /dev/null +++ b/src/mahoji/commands/relic.ts @@ -0,0 +1,64 @@ +import type { CommandRunOptions } from '@oldschoolgg/toolkit'; +import { ApplicationCommandOptionType } from 'discord.js'; + +import { relics } from '../../lib/randomizer'; +import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; +import type { OSBMahojiCommand } from '../lib/util'; + +export const relicCommand: OSBMahojiCommand = { + name: 'relic', + description: 'Pick/view relics.', + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: 'pick', + description: 'Pick one starter relic.', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'relic', + required: true, + description: 'The relic you want to pick.', + choices: relics.map(i => ({ name: i.name, value: i.name })) + } + ] + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'view', + description: 'View relics.' + } + ], + run: async ({ + interaction, + options, + userID + }: CommandRunOptions<{ + pick?: { relic?: string }; + view?: {}; + }>) => { + const user = await mUserFetch(userID); + if (options.pick) { + if (user.user.relics.length === 0) { + const pickedRelic = relics.find(r => r.name === options.pick?.relic); + if (!pickedRelic) { + return 'Invalid relic.'; + } + await handleMahojiConfirmation( + interaction, + `Are you sure you want to pick the ${pickedRelic.name} relic? You cannot switch.` + ); + await user.update({ + relics: { + push: pickedRelic.id + } + }); + return `You picked the ${pickedRelic.name} relic.`; + } + } + + return relics + .map(r => `${r.name}: ${r.desc} ${user.user.relics.includes(r.id) ? '(Owned)' : '(Not Owned)'}`) + .join('\n'); + } +}; diff --git a/src/mahoji/commands/roll.ts b/src/mahoji/commands/roll.ts deleted file mode 100644 index 6b66ef3291..0000000000 --- a/src/mahoji/commands/roll.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; - -import { cryptoRand } from '../../lib/util'; -import type { OSBMahojiCommand } from '../lib/util'; - -export const rollCommand: OSBMahojiCommand = { - name: 'roll', - description: 'Roll a random number from 1, up to a limit.', - options: [ - { - type: ApplicationCommandOptionType.Integer, - name: 'limit', - description: 'The upper limit of the roll. Defaults to 10.', - required: false, - min_value: 1, - max_value: 1_000_000_000 - } - ], - run: async ({ options, user }: CommandRunOptions<{ limit?: number }>) => { - const limit = options.limit ?? 10; - return `**${user.username}** rolled a random number from 1 to ${limit}...\n\n**${cryptoRand( - 1, - limit - ).toString()}**`; - } -}; diff --git a/src/mahoji/commands/rp.ts b/src/mahoji/commands/rp.ts index dfc0331879..0feba73ee3 100644 --- a/src/mahoji/commands/rp.ts +++ b/src/mahoji/commands/rp.ts @@ -1,199 +1,31 @@ -import { dateFm, toTitleCase } from '@oldschoolgg/toolkit'; import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import type { MahojiUserOption } from '@oldschoolgg/toolkit'; -import { type Prisma, UserEventType, xp_gains_skill_enum } from '@prisma/client'; -import { DiscordSnowflake } from '@sapphire/snowflake'; -import { Duration } from '@sapphire/time-utilities'; -import { SnowflakeUtil, codeBlock } from 'discord.js'; +import { codeBlock } from 'discord.js'; import { ApplicationCommandOptionType } from 'discord.js'; -import { Time, objectValues, randArrItem, sumArr } from 'e'; -import { Bank } from 'oldschooljs'; -import type { Item } from 'oldschooljs/dist/meta/types'; +import { randArrItem } from 'e'; -import { ADMIN_IDS, OWNER_IDS, SupportServer, production } from '../../config'; -import { analyticsTick } from '../../lib/analytics'; import { calculateCompCapeProgress } from '../../lib/bso/calculateCompCapeProgress'; -import { BitField, Channel } from '../../lib/constants'; -import { allCollectionLogsFlat } from '../../lib/data/Collections'; -import type { GearSetupType } from '../../lib/gear/types'; -import { GrandExchange } from '../../lib/grandExchange'; -import { marketPricemap } from '../../lib/marketPrices'; -import { unEquipAllCommand } from '../../lib/minions/functions/unequipAllCommand'; -import { unequipPet } from '../../lib/minions/functions/unequipPet'; -import { premiumPatronTime } from '../../lib/premiumPatronTime'; - -import { TeamLoot } from '../../lib/simulation/TeamLoot'; -import { SkillsEnum } from '../../lib/skilling/types'; -import type { ItemBank } from '../../lib/types'; -import { isValidDiscordSnowflake, returnStringOrFile } from '../../lib/util'; -import getOSItem from '../../lib/util/getOSItem'; -import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; +import { globalConfig } from '../../lib/constants'; import { deferInteraction } from '../../lib/util/interactionReply'; -import itemIsTradeable from '../../lib/util/itemIsTradeable'; import { makeBankImage } from '../../lib/util/makeBankImage'; -import { migrateUser } from '../../lib/util/migrateUser'; -import { parseBank } from '../../lib/util/parseStringBank'; -import { insertUserEvent } from '../../lib/util/userEvents'; -import { sendToChannelID } from '../../lib/util/webhook'; -import { cancelUsersListings } from '../lib/abstracted_commands/cancelGEListingCommand'; -import { gearSetupOption } from '../lib/mahojiCommandOptions'; import type { OSBMahojiCommand } from '../lib/util'; import { gifs } from './admin'; -import { getUserInfo } from './minion'; -import { sellPriceOfItem } from './sell'; - -const itemFilters = [ - { - name: 'Tradeable', - filter: (item: Item) => itemIsTradeable(item.id, true) - } -]; - -async function usernameSync() { - const roboChimpUsersToCache = ( - await roboChimpClient.user.findMany({ - where: { - OR: [ - { - osb_cl_percent: { - gte: 80 - } - }, - { - bso_total_level: { - gte: 80 - } - }, - { - osb_total_level: { - gte: 1500 - } - }, - { - bso_total_level: { - gte: 1500 - } - }, - { - leagues_points_total: { - gte: 20_000 - } - } - ] - }, - select: { - id: true - } - }) - ).map(i => i.id.toString()); - - const orConditions: Prisma.UserWhereInput[] = []; - for (const skill of objectValues(SkillsEnum)) { - orConditions.push({ - [`skills_${skill}`]: { - gte: 15_000_000 - } - }); - } - const usersToCache = ( - await prisma.user.findMany({ - where: { - OR: [ - ...orConditions, - { - last_command_date: { - gt: new Date(Date.now() - Number(Time.Month)) - } - } - ], - id: { - notIn: roboChimpUsersToCache - } - }, - select: { - id: true - } - }) - ).map(i => i.id); - - const response: string[] = []; - const allNewUsers = await prisma.newUser.findMany({ - where: { - username: { - not: null - }, - id: { - in: [...usersToCache, ...roboChimpUsersToCache] - } - }, - select: { - id: true, - username: true - } - }); - - response.push(`Cached ${allNewUsers.length} usernames.`); - return response.join(', '); -} - -function isProtectedAccount(user: MUser) { - const botAccounts = ['303730326692429825', '729244028989603850', '969542224058654790']; - if ([...ADMIN_IDS, ...OWNER_IDS, ...botAccounts].includes(user.id)) return true; - if ([BitField.isModerator, BitField.isContributor].some(bf => user.bitfield.includes(bf))) return true; - return false; -} export const rpCommand: OSBMahojiCommand = { name: 'rp', description: 'Admin tools second set', - guildID: SupportServer, + guildID: globalConfig.mainServerID, options: [ { type: ApplicationCommandOptionType.SubcommandGroup, name: 'action', description: 'Action tools', options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'validate_ge', - description: 'Validate the g.e.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'patreon_reset', - description: 'Reset all patreon data.', - options: [] - }, { type: ApplicationCommandOptionType.Subcommand, name: 'force_comp_update', description: 'Force the top 100 completionist users to update their completion percentage.', options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'view_all_items', - description: 'View all item IDs present in banks/cls.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'analytics_tick', - description: 'analyticsTick.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'networth_sync', - description: 'networth_sync.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'redis_sync', - description: 'redis sync.', - options: [] } ] }, @@ -202,38 +34,6 @@ export const rpCommand: OSBMahojiCommand = { name: 'player', description: 'Player manipulation tools', options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'givetgb', - description: 'Give em a tgb', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user', - required: true - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'set_buy_date', - description: 'Set the minion buy date of a user.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user.', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'message_id', - description: 'The message id when they bought their minion.', - required: true - } - ] - }, { type: ApplicationCommandOptionType.Subcommand, name: 'viewbank', @@ -252,279 +52,6 @@ export const rpCommand: OSBMahojiCommand = { required: false } ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'add_patron_time', - description: 'Give user temporary patron time.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user.', - required: true - }, - { - type: ApplicationCommandOptionType.Integer, - name: 'tier', - description: 'The tier to give.', - required: true, - choices: [1, 2, 3, 4, 5, 6].map(i => ({ name: i.toString(), value: i })) - }, - { - type: ApplicationCommandOptionType.String, - name: 'time', - description: 'The time.', - required: true - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'unequip_all_items', - description: 'Force unequip all items from a user.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user.', - required: true - }, - { - ...gearSetupOption, - required: false - }, - { - name: 'all', - description: 'Unequip all gear slots', - type: ApplicationCommandOptionType.Boolean, - required: false - }, - { - name: 'pet', - description: 'Unequip pet also?', - type: ApplicationCommandOptionType.Boolean, - required: false - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'steal_items', - description: 'Steal items from a user', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'items', - description: 'The items to take', - required: false - }, - { - type: ApplicationCommandOptionType.String, - name: 'item_filter', - description: 'A preconfigured item filter.', - required: false, - choices: itemFilters.map(i => ({ name: i.name, value: i.name })) - }, - { - type: ApplicationCommandOptionType.String, - name: 'reason', - description: 'The reason' - }, - { - type: ApplicationCommandOptionType.Boolean, - name: 'delete', - description: 'To delete the items instead' - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'view_user', - description: 'View a users info', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user', - required: true - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'migrate_user', - description: "Migrate a user's minion profile across Discord accounts", - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'source', - description: 'Source account with data to preserve and migrate', - required: true - }, - { - type: ApplicationCommandOptionType.User, - name: 'dest', - description: 'Destination account (any existing data on this account will be deleted)', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'reason', - description: 'The reason' - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'list_trades', - description: 'Show trades between users', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user', - required: true - }, - { - type: ApplicationCommandOptionType.User, - name: 'partner', - description: 'Optional second user, will only show trades between the users', - required: false - }, - { - type: ApplicationCommandOptionType.String, - name: 'guild_id', - description: 'Optional - Restrict search to this guild.', - required: false - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'ge_cancel', - description: 'Cancel GE Listings', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user', - required: true - } - ] - } - ] - }, - { - type: ApplicationCommandOptionType.SubcommandGroup, - name: 'user_event', - description: 'Manage user events.', - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'cl_completion', - description: 'CL Completion', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user that completed the cl.', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'cl_name', - description: 'The cl the user completed', - required: true, - autocomplete: async val => { - return allCollectionLogsFlat - .map(c => c.name) - .filter(c => (!val ? true : c.toLowerCase().includes(val.toLowerCase()))) - .map(val => ({ name: val, value: val })); - } - }, - { - type: ApplicationCommandOptionType.String, - name: 'message_id', - description: 'The message id of when they got it', - required: true - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'max_total', - description: 'Set max total level or total xp', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user that reached max total xp or level', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'type', - description: 'Did they reach max total level or max total xp', - required: true, - choices: [ - { name: UserEventType.MaxTotalLevel, value: UserEventType.MaxTotalLevel }, - { name: UserEventType.MaxTotalXP, value: UserEventType.MaxTotalXP } - ] - }, - { - type: ApplicationCommandOptionType.String, - name: 'message_id', - description: 'The message id of when they got it', - required: true - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'max', - description: 'Set max level/xp, e.g. lvl 99 or 200m in one skill', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user that reached max total xp or level', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'type', - description: 'Did they reach max level or max xp', - required: true, - choices: [ - { name: UserEventType.MaxXP, value: UserEventType.MaxXP }, - { name: UserEventType.MaxLevel, value: UserEventType.MaxLevel } - ] - }, - { - type: ApplicationCommandOptionType.String, - name: 'skill', - description: 'What skill?', - required: true, - autocomplete: async val => { - return Object.values(xp_gains_skill_enum) - .filter(s => (!val ? true : s.includes(val.toLowerCase()))) - .map(s => ({ name: s, value: s })); - } - }, - { - type: ApplicationCommandOptionType.String, - name: 'message_id', - description: 'The message id of when they got it', - required: true - } - ] } ] } @@ -532,140 +59,28 @@ export const rpCommand: OSBMahojiCommand = { run: async ({ options, userID, - interaction, - guildID + interaction }: CommandRunOptions<{ - user_event?: { - cl_completion?: { user: MahojiUserOption; cl_name: string; message_id: string }; - max_total?: { user: MahojiUserOption; type: UserEventType; message_id: string }; - max?: { user: MahojiUserOption; type: UserEventType; skill: xp_gains_skill_enum; message_id: string }; - }; action?: { - validate_ge?: {}; - patreon_reset?: {}; force_comp_update?: {}; - view_all_items?: {}; - analytics_tick?: {}; - networth_sync?: {}; - redis_sync?: {}; }; player?: { - givetgb?: { user: MahojiUserOption }; viewbank?: { user: MahojiUserOption; json?: boolean }; - add_patron_time?: { user: MahojiUserOption; tier: number; time: string }; - steal_items?: { - user: MahojiUserOption; - items?: string; - item_filter?: string; - reason?: string; - delete?: boolean; - }; - unequip_all_items?: { - user: MahojiUserOption; - gear_setup?: string; - all?: boolean; - pet?: boolean; - }; - set_buy_date?: { - user: MahojiUserOption; - message_id: string; - }; view_user?: { user: MahojiUserOption }; - migrate_user?: { source: MahojiUserOption; dest: MahojiUserOption; reason?: string }; - list_trades?: { - user: MahojiUserOption; - partner?: MahojiUserOption; - guild_id?: string; - }; - ge_cancel?: { user: MahojiUserOption }; }; }>) => { await deferInteraction(interaction); - const adminUser = await mUserFetch(userID); - const isOwner = OWNER_IDS.includes(userID.toString()); - const isAdmin = ADMIN_IDS.includes(userID); - const isMod = isOwner || isAdmin || adminUser.bitfield.includes(BitField.isModerator); - const isContrib = isMod || adminUser.bitfield.includes(BitField.isContributor); - const isTrusted = [BitField.IsWikiContributor, BitField.isContributor].some(bit => - adminUser.bitfield.includes(bit) - ); - if (!guildID || (production && guildID.toString() !== SupportServer)) return randArrItem(gifs); - if (!isAdmin && !isMod && !isTrusted) return randArrItem(gifs); - - if (options.user_event) { - const messageId = - options.user_event.cl_completion?.message_id ?? - options.user_event.max?.message_id ?? - options.user_event.max_total?.message_id; - if (!messageId || !isValidDiscordSnowflake(messageId)) return null; - - const snowflake = DiscordSnowflake.timestampFrom(messageId); - const date = new Date(snowflake); - const userId = - options.user_event.cl_completion?.user.user.id ?? - options.user_event.max?.user.user.id ?? - options.user_event.max_total?.user.user.id; - if (!userId) return null; - const targetUser = await mUserFetch(userId); - let type: UserEventType = UserEventType.CLCompletion; - let skill = undefined; - let collectionLogName = undefined; - - let confirmationStr = `Please confirm: -User: ${targetUser.rawUsername} -Date: ${dateFm(date)}`; - if (options.user_event.cl_completion) { - confirmationStr += `\nCollection log: ${options.user_event.cl_completion.cl_name}`; - type = UserEventType.CLCompletion; - collectionLogName = options.user_event.cl_completion.cl_name; - } - if (options.user_event.max) { - confirmationStr += `\nSkill: ${options.user_event.max.skill}`; - type = options.user_event.max.type; - skill = options.user_event.max.skill; - } - if (options.user_event.max_total) { - type = options.user_event.max_total.type; - } - await handleMahojiConfirmation(interaction, confirmationStr); - await insertUserEvent({ - userID: targetUser.id, - type, - skill, - collectionLogName, - date - }); - await sendToChannelID(Channel.BotLogs, { - content: `${adminUser.logName} created userevent for ${targetUser.logName}: ${type} ${dateFm(date)} ${ - skill ?? '' - }` - }); - return `Done: ${confirmationStr.replace('Please confirm:', '')}`; - } - - if (!isMod) return randArrItem(gifs); - - if (!guildID || !isContrib || (production && guildID.toString() !== SupportServer)) return randArrItem(gifs); - // Contributor+ only commands: - if (options.player?.givetgb) { - const user = await mUserFetch(options.player?.givetgb.user.user.id); - if (user.id === adminUser.id) { - return randArrItem(gifs); - } - await user.addItemsToBank({ items: new Bank().add('Tester gift box'), collectionLog: true }); - return `Gave 1x Tester gift box to ${user}.`; - } + const mods = [ + '794368001856110594', + '604278562320810009', + '425134194436341760', + '157797566833098752', + '507686806624534529' + ]; + if (!mods.includes(userID)) return randArrItem(gifs); - if (!isMod) return randArrItem(gifs); // Mod+ only commands: - if (options.action?.validate_ge) { - const isValid = await GrandExchange.extensiveVerification(); - if (isValid) { - return 'No issues found.'; - } - return 'Something was invalid. Check logs!'; - } if (options.action?.force_comp_update) { const usersToUpdate = await prisma.userStats.findMany({ where: { @@ -684,63 +99,6 @@ Date: ${dateFm(date)}`; return 'Done.'; } - if (options.action?.analytics_tick) { - await analyticsTick(); - return 'Finished.'; - } - if (options.action?.redis_sync) { - const result = await usernameSync(); - return result; - } - if (options.action?.networth_sync) { - const users = await prisma.user.findMany({ - where: { - GP: { - gt: 10_000_000_000 - } - }, - take: 20, - orderBy: { - GP: 'desc' - }, - select: { - id: true - } - }); - for (const { id } of users) { - const user = await mUserFetch(id); - await user.update({ - cached_networth_value: (await user.calculateNetWorth()).value - }); - } - return 'Done.'; - } - if (options.action?.view_all_items) { - const result = await prisma.$queryRawUnsafe<{ item_id: number }[]>(`SELECT DISTINCT json_object_keys(bank)::int AS item_id -FROM users -UNION -SELECT DISTINCT jsonb_object_keys("collectionLogBank")::int AS item_id -FROM users -ORDER BY item_id ASC;`); - return returnStringOrFile(`[${result.map(i => i.item_id).join(',')}]`); - } - - if (options.player?.set_buy_date) { - const userToCheck = await mUserFetch(options.player.set_buy_date.user.user.id); - const res = SnowflakeUtil.deconstruct(options.player.set_buy_date.message_id); - const date = new Date(Number(res.timestamp)); - - await handleMahojiConfirmation( - interaction, - `Are you sure you want to set the buy date of ${userToCheck.usernameOrMention} to ${dateFm(date)}?` - ); - await sendToChannelID(Channel.BotLogs, { - content: `${adminUser.logName} set minion buy date of ${userToCheck.logName} to ${dateFm(date)}` - }); - await userToCheck.update({ minion_bought_date: date }); - return `Set minion buy date of ${userToCheck.usernameOrMention} to ${dateFm(date)}.`; - } - if (options.player?.viewbank) { const userToCheck = await mUserFetch(options.player.viewbank.user.user.id); const bank = userToCheck.allItemsOwned; @@ -754,257 +112,6 @@ ORDER BY item_id ASC;`); return { files: [(await makeBankImage({ bank, title: userToCheck.usernameOrMention })).file] }; } - if (options.player?.add_patron_time) { - const { tier, time, user: userToGive } = options.player.add_patron_time; - const duration = new Duration(time); - if (![1, 2, 3, 4, 5, 6].includes(tier)) return 'Invalid input.'; - const ms = duration.offset; - if (ms < Time.Second || ms > Time.Year * 3) return 'Invalid input.'; - const res = await premiumPatronTime(ms, tier, await mUserFetch(userToGive.user.id), interaction); - return res; - } - - // Unequip Items - if (options.player?.unequip_all_items) { - if (!isOwner && !isAdmin) { - return randArrItem(gifs); - } - const allGearSlots = ['melee', 'range', 'mage', 'misc', 'skilling', 'other', 'wildy', 'fashion']; - const opts = options.player.unequip_all_items; - const targetUser = await mUserFetch(opts.user.user.id); - const warningMsgs: string[] = []; - if (targetUser.minionIsBusy) warningMsgs.push("User's minion is busy."); - const gearSlot = opts.all - ? 'all' - : opts.gear_setup && allGearSlots.includes(opts.gear_setup) - ? opts.gear_setup - : undefined; - if (gearSlot === undefined) { - return 'No gear slot specified.'; - } - await handleMahojiConfirmation( - interaction, - `Unequip ${gearSlot} gear from ${targetUser.usernameOrMention}?${ - warningMsgs.length > 0 ? warningMsgs.join('\n') : '' - }` - ); - const slotsToUnequip = gearSlot === 'all' ? allGearSlots : [gearSlot]; - - for (const gear of slotsToUnequip) { - const result = await unEquipAllCommand(targetUser.id, gear as GearSetupType, true); - if (!result.endsWith('setup.')) return result; - } - - let petResult = ''; - if (opts.pet) { - petResult = await unequipPet(targetUser); - } - return `Successfully removed ${gearSlot} gear.${opts.pet ? ` ${petResult}` : ''}`; - } - - // Steal Items - if (options.player?.steal_items) { - if (!isOwner && !isAdmin) { - return randArrItem(gifs); - } - const toDelete = options.player.steal_items.delete ?? false; - const actionMsg = toDelete ? 'delete' : 'steal'; - const actionMsgPast = toDelete ? 'deleted' : 'stole'; - - const userToStealFrom = await mUserFetch(options.player.steal_items.user.user.id); - - const items = new Bank(); - if (options.player.steal_items.item_filter) { - const filter = itemFilters.find(i => i.name === options.player?.steal_items?.item_filter); - if (!filter) return 'Invalid item filter.'; - for (const [item, qty] of userToStealFrom.bank.items()) { - if (filter.filter(item)) { - items.add(item.id, qty); - } - } - } else { - items.add( - parseBank({ - inputStr: options.player.steal_items.items, - noDuplicateItems: true, - inputBank: userToStealFrom.bankWithGP - }) - ); - } - await handleMahojiConfirmation( - interaction, - `Are you sure you want to ${actionMsg} ${items.toString().slice(0, 500)} from ${ - userToStealFrom.usernameOrMention - }?` - ); - let missing = new Bank(); - if (!userToStealFrom.owns(items)) { - missing = items.clone().remove(userToStealFrom.bankWithGP); - return `${userToStealFrom.mention} doesn't have all items. Missing: ${missing - .toString() - .slice(0, 500)}`; - } - - await sendToChannelID(Channel.BotLogs, { - content: `${adminUser.logName} ${actionMsgPast} \`${items.toString().slice(0, 500)}\` from ${ - userToStealFrom.logName - } for ${options.player.steal_items.reason ?? 'No reason'}`, - files: [{ attachment: Buffer.from(items.toString()), name: 'items.txt' }] - }); - - await userToStealFrom.removeItemsFromBank(items); - if (!toDelete) await adminUser.addItemsToBank({ items, collectionLog: false }); - return `${toTitleCase(actionMsgPast)} ${items.toString().slice(0, 500)} from ${userToStealFrom.mention}`; - } - - if (options.player?.view_user) { - const userToView = await mUserFetch(options.player.view_user.user.user.id); - return (await getUserInfo(userToView)).everythingString; - } - - if (options.player?.migrate_user) { - if (!isOwner && !isAdmin) { - return randArrItem(gifs); - } - - const { source, dest, reason } = options.player.migrate_user; - - if (source.user.id === dest.user.id) { - return 'Destination cannot be the same as the source!'; - } - const sourceUser = await mUserFetch(source.user.id); - const destUser = await mUserFetch(dest.user.id); - - if (isProtectedAccount(destUser)) return 'You cannot clobber that account.'; - const sourceXp = sumArr(Object.values(sourceUser.skillsAsXP)); - const destXp = sumArr(Object.values(destUser.skillsAsXP)); - if (destXp > sourceXp) { - await handleMahojiConfirmation( - interaction, - `The target user, ${destUser.logName}, has more XP than the source user; are you really sure the names aren't backwards?` - ); - } - await handleMahojiConfirmation( - interaction, - `Are you 1000%, totally, **REALLY** sure that \`${sourceUser.logName}\` is the account you want to preserve, and \`${destUser.logName}\` is the new account that will have ALL existing data destroyed?` - ); - const result = await migrateUser(sourceUser, destUser); - if (result === true) { - await sendToChannelID(Channel.BotLogs, { - content: `${adminUser.logName} migrated ${sourceUser.logName} to ${destUser.logName}${ - reason ? `, for ${reason}` : '' - }` - }); - return 'Done'; - } - return result; - } - if (options.player?.list_trades) { - const baseSql = - 'SELECT date, sender::text as sender_id, recipient::text as recipient_id, s.username as sender, r.username as recipient, items_sent, items_received, type, guild_id::text from economy_transaction e inner join new_users s on sender = s.id::bigint inner join new_users r on recipient = r.id::bigint'; - const where: string[] = []; - if (options.player.list_trades.partner) { - const inUsers = `(${options.player.list_trades.partner.user.id}, ${options.player.list_trades.user.user.id})`; - where.push(`(sender IN ${inUsers} AND recipient IN ${inUsers})`); - } else { - where.push( - `(sender = ${options.player.list_trades.user.user.id} OR recipient = ${options.player.list_trades.user.user.id})` - ); - } - if (options.player.list_trades.guild_id) { - where.push(`guild_id = ${options.player.list_trades.guild_id}`); - } - - const sql = `${`${baseSql} WHERE ${where.join(' AND ')}`} ORDER BY date DESC`; - - let report = - 'date\tguild_id\tsender_id\trecipient_id\tsender\trecipient\tsent_bank\trcvd_bank\tsent_value\trcvd_value\tsent_value_last_100\trcvd_value_last_100\n'; - - const totalsSent = new TeamLoot(); - const totalsRcvd = new TeamLoot(); - const result: { - date: Date; - sender_id: string; - recipient_id: string; - sender: string; - recipient: string; - items_sent: ItemBank; - items_received: ItemBank; - type: 'gri' | 'trade' | 'giveaway'; - guild_id: string; - }[] = await prisma.$queryRawUnsafe(sql); - for (const row of result) { - const sentBank = new Bank(row.items_sent); - const recvBank = new Bank(row.items_received); - - // Calculate values of the traded banks: - let sentValueGuide = 0; - let sentValueLast100 = 0; - let recvValueGuide = 0; - let recvValueLast100 = 0; - - // We use Object.entries(bank) instead of bank.items() so we can filter out deleted/broken items: - for (const [itemId, qty] of Object.entries(sentBank.bank)) { - try { - const item = getOSItem(Number(itemId)); - const marketData = marketPricemap.get(item.id); - if (marketData) { - sentValueGuide += marketData.guidePrice * qty; - sentValueLast100 += marketData.averagePriceLast100 * qty; - } else { - const { price } = sellPriceOfItem(item, 0); - sentValueGuide += price * qty; - sentValueLast100 += price * qty; - } - } catch (e) { - // This means item doesn't exist at this point in time. - delete sentBank.bank[itemId]; - } - } - for (const [itemId, qty] of Object.entries(recvBank.bank)) { - try { - const item = getOSItem(Number(itemId)); - const marketData = marketPricemap.get(item.id); - if (marketData) { - recvValueGuide += marketData.guidePrice * qty; - recvValueLast100 += marketData.averagePriceLast100 * qty; - } else { - const { price } = sellPriceOfItem(item, 0); - recvValueGuide += price * qty; - recvValueLast100 += price * qty; - } - } catch (e) { - // This means item doesn't exist at this point in time. - delete recvBank.bank[itemId]; - } - } - totalsSent.add(row.sender_id, 'Coins', sentValueLast100); - totalsRcvd.add(row.sender_id, 'Coins', recvValueLast100); - totalsSent.add(row.recipient_id, 'Coins', recvValueLast100); - totalsRcvd.add(row.recipient_id, 'Coins', sentValueLast100); - - // Add report row: - report += `${row.date.toLocaleString('en-us')}\t${row.guild_id}\t${row.sender_id}\t${ - row.recipient_id - }\t${row.sender}\t${ - row.recipient - }\t${sentBank}\t${recvBank}\t${sentValueGuide}\t${recvValueGuide}\t${sentValueLast100}\t${recvValueLast100}\n`; - } - report += '\n\n'; - report += 'User ID\tTotal Sent\tTotal Received\n'; - for (const [userId, bank] of totalsSent.entries()) { - report += `${userId}\t${bank}\t${totalsRcvd.get(userId)}\n`; - } - - return { files: [{ attachment: Buffer.from(report), name: 'trade_report.txt' }] }; - } - - if (options.player?.ge_cancel) { - const targetUser = await mUserFetch(options.player.ge_cancel.user.user.id); - await cancelUsersListings(targetUser); - return `Cancelled listings for ${targetUser}`; - } - return 'Invalid command.'; } }; diff --git a/src/mahoji/commands/runecraft.ts b/src/mahoji/commands/runecraft.ts index feff23763c..964fec52c3 100644 --- a/src/mahoji/commands/runecraft.ts +++ b/src/mahoji/commands/runecraft.ts @@ -14,7 +14,6 @@ import { formatDuration, formatSkillRequirements, itemID, stringMatches } from ' import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { determineRunes } from '../../lib/util/determineRunes'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { tiaraRunecraftCommand } from '../lib/abstracted_commands/tiaraRunecraftCommand'; import type { OSBMahojiCommand } from '../lib/util'; import { calcMaxRCQuantity, userHasGracefulEquipped } from '../mahojiSettings'; @@ -339,7 +338,6 @@ export const runecraftCommand: OSBMahojiCommand = { if (!user.owns(totalCost)) return `You don't own: ${totalCost}.`; await user.removeItemsFromBank(totalCost); - updateBankSetting('runecraft_cost', totalCost); await addSubTaskToActivityTask({ runeID: runeObj.id, diff --git a/src/mahoji/commands/sacrifice.ts b/src/mahoji/commands/sacrifice.ts index 9e5b5713a7..2e47f91ed6 100644 --- a/src/mahoji/commands/sacrifice.ts +++ b/src/mahoji/commands/sacrifice.ts @@ -3,7 +3,8 @@ import type { Item } from 'oldschooljs/dist/meta/types'; import type { CommandRunOptions } from '@oldschoolgg/toolkit'; import { ApplicationCommandOptionType } from 'discord.js'; -import { Emoji, Events } from '../../lib/constants'; +import { clamp } from 'e'; +import { Emoji, Events, ONE_TRILLION } from '../../lib/constants'; import { cats } from '../../lib/growablePets'; import minionIcons from '../../lib/minions/data/minionIcons'; import type { ItemBank } from '../../lib/types'; @@ -12,17 +13,13 @@ import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmatio import { deferInteraction } from '../../lib/util/interactionReply'; import { parseBank } from '../../lib/util/parseStringBank'; import resolveItems from '../../lib/util/resolveItems'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { filterOption } from '../lib/mahojiCommandOptions'; import type { OSBMahojiCommand } from '../lib/util'; import { userStatsBankUpdate } from '../mahojiSettings'; import { sellPriceOfItem } from './sell'; async function trackSacBank(user: MUser, bank: Bank) { - await Promise.all([ - updateBankSetting('economyStats_sacrificedBank', bank), - userStatsBankUpdate(user, 'sacrificed_bank', bank) - ]); + await Promise.all([userStatsBankUpdate(user.id, 'sacrificed_bank', bank)]); const stats = await user.fetchStats({ sacrificed_bank: true }); return new Bank(stats.sacrificed_bank as ItemBank); } @@ -176,20 +173,15 @@ export const sacrificeCommand: OSBMahojiCommand = { totalPrice = Math.floor(totalPrice * 1.3); } - let hammyCount = 0; + const hammyLoot = new Bank(); for (let i = 0; i < Math.floor(totalPrice / 51_530_000); i++) { if (roll(140)) { - hammyCount++; + hammyLoot.add('Hammy'); } } - if (hammyCount) { - await user.addItemsToBank({ items: new Bank().add('Hammy', hammyCount), collectionLog: true }); - } await user.update({ - sacrificedValue: { - increment: totalPrice - } + sacrificedValue: clamp(Number(user.user.sacrificedValue) + totalPrice, 0, ONE_TRILLION) }); const newValue = user.user.sacrificedValue; @@ -213,13 +205,11 @@ export const sacrificeCommand: OSBMahojiCommand = { } } - if (hammyCount) { - str += - '\n\n<:Hamstare:685036648089780234> A small hamster called Hammy has crawled into your bank and is now staring intensely into your eyes.'; - if (hammyCount > 1) { - str += ` **(x${hammyCount})**`; - } + if (hammyLoot.length > 0) { + await user.addItemsToBank({ items: hammyLoot, collectionLog: true }); + str += `\n\n<:Hamstare:685036648089780234> A small hamster called Hammy has crawled into your bank and is now staring intensely into your eyes, you received: ${hammyLoot}`; } + if (hasSkipper) { str += '\n<:skipper:755853421801766912> Skipper has negotiated with the bank and gotten you +30% extra value from your sacrifice.'; diff --git a/src/mahoji/commands/sell.ts b/src/mahoji/commands/sell.ts index 531caec9f5..34879748d9 100644 --- a/src/mahoji/commands/sell.ts +++ b/src/mahoji/commands/sell.ts @@ -1,5 +1,4 @@ import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import type { Prisma } from '@prisma/client'; import { ApplicationCommandOptionType } from 'discord.js'; import { calcPercentOfNum, clamp, reduceNumByPercent } from 'e'; import { Bank } from 'oldschooljs'; @@ -9,13 +8,11 @@ import { MAX_INT_JAVA } from '../../lib/constants'; import { customPrices } from '../../lib/customItems/util'; import { NestBoxesTable } from '../../lib/simulation/misc'; -import { itemID, returnStringOrFile, toKMB } from '../../lib/util'; +import { itemID, toKMB } from '../../lib/util'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { parseBank } from '../../lib/util/parseStringBank'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { filterOption } from '../lib/mahojiCommandOptions'; import type { OSBMahojiCommand } from '../lib/util'; -import { updateClientGPTrackSetting, userStatsBankUpdate, userStatsUpdate } from '../mahojiSettings'; /** * - Hardcoded prices @@ -229,8 +226,6 @@ export const sellCommand: OSBMahojiCommand = { taxRatePercent -= 5; } - const botItemSellData: Prisma.BotItemSellCreateManyInput[] = []; - for (const [item, qty] of bankToSell.items()) { const specialPrice = specialSoldItems.get(item.id); let pricePerStack = -1; @@ -243,12 +238,6 @@ export const sellCommand: OSBMahojiCommand = { pricePerStack = Math.floor(price * qty); } totalPrice += pricePerStack; - botItemSellData.push({ - item_id: item.id, - quantity: qty, - gp_received: pricePerStack, - user_id: user.id - }); } await handleMahojiConfirmation( @@ -269,33 +258,6 @@ export const sellCommand: OSBMahojiCommand = { itemsToRemove: bankToSell }); - await Promise.all([ - updateClientGPTrackSetting('gp_sell', totalPrice), - updateBankSetting('sold_items_bank', bankToSell), - userStatsBankUpdate(user, 'items_sold_bank', bankToSell), - userStatsUpdate( - user.id, - { - sell_gp: { - increment: totalPrice - } - }, - {} - ), - prisma.botItemSell.createMany({ data: botItemSellData }) - ]); - - if (user.isIronman) { - return `Sold ${bankToSell} for **${totalPrice.toLocaleString()}gp (${toKMB(totalPrice)})**`; - } - return returnStringOrFile( - `Sold ${bankToSell} for **${totalPrice.toLocaleString()}gp (${toKMB( - totalPrice - )})** (${taxRatePercent}% below market price). ${ - hasSkipper - ? '\n\n<:skipper:755853421801766912> Skipper has negotiated with the bank and you were charged less tax on the sale!' - : '' - }` - ); + return `Sold ${bankToSell} for **${totalPrice.toLocaleString()}gp (${toKMB(totalPrice)})**`; } }; diff --git a/src/mahoji/commands/simulate.ts b/src/mahoji/commands/simulate.ts deleted file mode 100644 index 2146f8d8b1..0000000000 --- a/src/mahoji/commands/simulate.ts +++ /dev/null @@ -1,242 +0,0 @@ -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import type { CommandResponse } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { randInt, roll } from 'e'; -import { Bank } from 'oldschooljs'; -import { ChambersOfXeric } from 'oldschooljs/dist/simulation/misc'; -import { toKMB } from 'oldschooljs/dist/util'; - -import { PerkTier } from '@oldschoolgg/toolkit'; -import { ColosseumWaveBank, startColosseumRun } from '../../lib/colosseum'; -import pets from '../../lib/data/pets'; -import { assert, averageBank, formatDuration } from '../../lib/util'; -import { deferInteraction } from '../../lib/util/interactionReply'; -import { makeBankImage } from '../../lib/util/makeBankImage'; -import type { OSBMahojiCommand } from '../lib/util'; - -function determineCoxLimit(user: MUser) { - const perkTier = user.perkTier(); - - if (perkTier >= PerkTier.Three) { - return 2000; - } - - if (perkTier === PerkTier.Two) { - return 1000; - } - - if (perkTier === PerkTier.One) { - return 100; - } - - return 10; -} - -function simulateColosseumRuns(samples = 100) { - const totalSimulations = samples; - let totalAttempts = 0; - let totalDeaths = 0; - const totalLoot = new Bank(); - const finishAttemptAmounts = []; - let totalDuration = 0; - - for (let i = 0; i < totalSimulations; i++) { - let attempts = 0; - let deaths = 0; - let done = false; - const kcBank = new ColosseumWaveBank(); - const runLoot = new Bank(); - - while (!done) { - attempts++; - const result = startColosseumRun({ - kcBank, - hasScythe: true, - hasTBow: true, - hasVenBow: true, - hasBF: false, - hasClaws: true, - hasSGS: true, - hasTorture: true - }); - totalDuration += result.realDuration; - kcBank.add(result.addedWaveKCBank); - if (result.diedAt === null) { - if (result.loot) runLoot.add(result.loot); - done = true; - } else { - deaths++; - } - } - assert(kcBank.amount(12) > 0); - finishAttemptAmounts.push(attempts); - totalAttempts += attempts; - totalDeaths += deaths; - totalLoot.add(runLoot); - } - - const averageAttempts = totalAttempts / totalSimulations; - const averageDeaths = totalDeaths / totalSimulations; - - finishAttemptAmounts.sort((a, b) => a - b); - - const result = `Results from the simulation of ${totalSimulations}x people completing the Colosseum: -**Average duration to beat wave 12 for first time:** ${formatDuration(totalDuration / totalSimulations)} -**Average deaths before beating wave 12:** ${averageDeaths} -**Average loot:** ${averageBank(totalLoot, totalSimulations)} -**Fastest completion trips:** ${finishAttemptAmounts[0]} -**Mean completion trips:** ${finishAttemptAmounts[Math.floor(finishAttemptAmounts.length / 2)]} -**Average trips to beat wave 12:** ${averageAttempts}. -**Longest completion trips:** ${finishAttemptAmounts[finishAttemptAmounts.length - 1]}`; - return result; -} - -async function coxCommand(user: MUser, quantity: number, cm = false, points = 25_000, teamSize = 4): CommandResponse { - const limit = determineCoxLimit(user); - if (quantity > limit) { - return `The quantity provided is over your limit of ${limit}. You can increase your limit up to 2000 by becoming a patron: `; - } - - const team = [ - { - id: user.id, - personalPoints: points - } - ]; - while (team.length < Math.min(100, teamSize)) { - team.push({ id: `${randInt(1, 10_000_000)}`, personalPoints: points }); - } - - const loot = new Bank(); - for (let i = 0; i < quantity; i++) { - const singleRaidLoot = ChambersOfXeric.complete({ - team, - challengeMode: cm, - timeToComplete: 1 - }); - - for (const lootBank of Object.values(singleRaidLoot)) { - loot.add(lootBank); - } - } - const image = await makeBankImage({ - bank: loot, - title: `Loot from ${quantity} ${cm ? 'challenge mode ' : ''}raids` - }); - - return { - content: `Personal Loot from ${quantity}x raids, with ${team.length} people, each with ${toKMB( - points - )} points.`, - files: [image.file] - }; -} - -export const simulateCommand: OSBMahojiCommand = { - name: 'simulate', - description: 'Simulate various OSRS related things.', - attributes: { - examples: ['/simulate cox quantity:1'] - }, - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'cox', - description: 'Simulate Chambers of Xeric.', - options: [ - { - type: ApplicationCommandOptionType.Integer, - name: 'quantity', - description: 'The amount of raids to simulate.', - min_value: 1, - required: true - }, - { - type: ApplicationCommandOptionType.Integer, - name: 'points', - description: 'How many points to have (default 25k).', - min_value: 1, - max_value: 1_000_000, - required: false - }, - { - type: ApplicationCommandOptionType.Integer, - name: 'team_size', - description: 'The size of your team (default 4).', - min_value: 1, - required: false - }, - { - type: ApplicationCommandOptionType.Boolean, - name: 'challenge_mode', - description: 'Challenge mode raids? (default false)', - required: false - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'petroll', - description: 'Simulate rolls at every pet at once.', - options: [ - { - type: ApplicationCommandOptionType.Integer, - name: 'quantity', - description: 'The amount of rolls.', - min_value: 1, - max_value: 100, - required: true - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'colosseum', - description: 'Simulate colosseum.' - } - ], - run: async ({ - interaction, - options, - userID - }: CommandRunOptions<{ - cox?: { - quantity: number; - points?: number; - team_size?: number; - challenge_mode?: boolean; - }; - petroll?: { - quantity: number; - }; - colosseum?: {}; - }>) => { - await deferInteraction(interaction); - const user = await mUserFetch(userID.toString()); - if (options.colosseum) { - return simulateColosseumRuns(); - } - if (options.cox) { - return coxCommand( - user, - options.cox.quantity, - options.cox.challenge_mode, - options.cox.points, - options.cox.team_size - ); - } - if (options.petroll) { - const received = []; - - for (let i = 0; i < options.petroll.quantity; i++) { - for (const pet of pets) { - if (roll(pet.chance)) received.push(pet.emoji); - } - } - - if (received.length === 0) return "You didn't get any pets!"; - return received.join(' '); - } - return 'Invalid command.'; - } -}; diff --git a/src/mahoji/commands/smelt.ts b/src/mahoji/commands/smelt.ts index 5637c17333..e5eba69f16 100644 --- a/src/mahoji/commands/smelt.ts +++ b/src/mahoji/commands/smelt.ts @@ -10,7 +10,6 @@ import type { SmeltingActivityTaskOptions } from '../../lib/types/minions'; import { formatDuration, formatSkillRequirements, itemID, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import type { OSBMahojiCommand } from '../lib/util'; import { userHasGracefulEquipped } from '../mahojiSettings'; @@ -171,7 +170,6 @@ export const smeltingCommand: OSBMahojiCommand = { } await transactItems({ userID: user.id, itemsToRemove: cost }); - updateBankSetting('smithing_cost', cost); await addSubTaskToActivityTask({ barID: bar.id, diff --git a/src/mahoji/commands/smith.ts b/src/mahoji/commands/smith.ts index 1dd168fef0..048e8cd2b5 100644 --- a/src/mahoji/commands/smith.ts +++ b/src/mahoji/commands/smith.ts @@ -14,7 +14,6 @@ import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import resolveItems from '../../lib/util/resolveItems'; import { pluraliseItemName } from '../../lib/util/smallUtils'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import type { OSBMahojiCommand } from '../lib/util'; export const smithCommand: OSBMahojiCommand = { @@ -172,7 +171,6 @@ export const smithCommand: OSBMahojiCommand = { } await transactItems({ userID: user.id, itemsToRemove: cost }); - updateBankSetting('smithing_cost', cost); await addSubTaskToActivityTask({ smithedBarID: smithedItem.id, diff --git a/src/mahoji/commands/steal.ts b/src/mahoji/commands/steal.ts index bab255868c..dc56e0602f 100644 --- a/src/mahoji/commands/steal.ts +++ b/src/mahoji/commands/steal.ts @@ -12,10 +12,9 @@ import { formatDuration } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { logError } from '../../lib/util/logError'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { calcLootXPPickpocketing } from '../../tasks/minions/pickpocketActivity'; import type { OSBMahojiCommand } from '../lib/util'; -import { rogueOutfitPercentBonus, userStatsBankUpdate } from '../mahojiSettings'; +import { rogueOutfitPercentBonus } from '../mahojiSettings'; export const stealCommand: OSBMahojiCommand = { name: 'steal', @@ -170,10 +169,6 @@ export const stealCommand: OSBMahojiCommand = { attackStylesUsed: [] }); - await Promise.all([ - userStatsBankUpdate(user.id, 'steal_loot_bank', foodRemoved), - updateBankSetting('economyStats_thievingCost', foodRemoved) - ]); str += ` Removed ${foodRemoved}.`; } else { // Up to 5% fail chance, random diff --git a/src/mahoji/commands/tames.ts b/src/mahoji/commands/tames.ts index ef3ec08614..e8782be2d8 100644 --- a/src/mahoji/commands/tames.ts +++ b/src/mahoji/commands/tames.ts @@ -2,10 +2,9 @@ import { readFileSync } from 'node:fs'; import { readFile } from 'node:fs/promises'; import { bold, time } from '@discordjs/builders'; import { Canvas, type Image, type SKRSContext2D, loadImage } from '@napi-rs/canvas'; -import { mentionCommand } from '@oldschoolgg/toolkit'; +import { mentionCommand, toTitleCase } from '@oldschoolgg/toolkit'; import type { CommandResponse, CommandRunOptions } from '@oldschoolgg/toolkit'; import { type Tame, tame_growth } from '@prisma/client'; -import { toTitleCase } from '@sapphire/utilities'; import { ApplicationCommandOptionType, type ChatInputCommandInteraction, type User } from 'discord.js'; import { Time, @@ -24,11 +23,11 @@ import { type ClueTier, ClueTiers } from '../../lib/clues/clueTiers'; import { PerkTier, badges } from '../../lib/constants'; import { Eatables } from '../../lib/data/eatables'; import { getSimilarItems } from '../../lib/data/similarItems'; -import { trackLoot } from '../../lib/lootTrack'; + import { Planks } from '../../lib/minions/data/planks'; import getUserFoodFromBank from '../../lib/minions/functions/getUserFoodFromBank'; -import { getUsersPerkTier } from '../../lib/perkTiers'; +import { RelicID } from '../../lib/relics'; import Tanning from '../../lib/skilling/skills/crafting/craftables/tanning'; import { SkillsEnum } from '../../lib/skilling/types'; import { @@ -72,7 +71,6 @@ import { tameHasBeenFed, tameName } from '../../lib/util/tameUtil'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import { arbitraryTameActivities } from '../../tasks/tames/tameTasks'; import { collectables } from '../lib/collectables'; import type { OSBMahojiCommand } from '../lib/util'; @@ -636,8 +634,6 @@ export async function removeRawFood({ } }); - updateBankSetting('economyStats_PVMCost', itemCost); - return { success: true, str: `${itemCost} from ${user.rawUsername}${foodBoosts.length > 0 ? `(${foodBoosts.join(', ')})` : ''}`, @@ -747,7 +743,6 @@ async function mergeCommand(user: MUser, interaction: ChatInputCommandInteractio ); await user.removeItemsFromBank(mergingCost); - updateBankSetting('tame_merging_cost', mergingCost); // Set the merged tame activities to the tame that is consuming it await prisma.tameActivity.updateMany({ @@ -961,6 +956,11 @@ async function killCommand(user: MUser, channelID: string, str: string) { const boosts = []; + if (user.hasRelic(RelicID.Speed)) { + speed = reduceNumByPercent(speed, 10); + boosts.push('Your relic of speed granted you a 30% speed boost'); + } + // Apply calculated boost: const combatLevelChange = reduceNumByPercent(speed, combatLevelBoost); boosts.push( @@ -980,7 +980,7 @@ async function killCommand(user: MUser, channelID: string, str: string) { const patronBoost = patronMaxTripBonus(user) * 2; if (patronBoost > 0) { maxTripLength += patronBoost; - boosts.push(`+${formatDuration(patronBoost, true)} trip length for T${getUsersPerkTier(user) - 1} patron`); + boosts.push(`+${formatDuration(patronBoost, true)} trip length for T${user.perkTier()} patron`); } if (isWeekend()) { @@ -1029,21 +1029,6 @@ async function killCommand(user: MUser, channelID: string, str: string) { } const fakeDuration = Math.floor(quantity * speed); - - await trackLoot({ - id: monster.name, - changeType: 'cost', - type: 'Monster', - totalCost: foodRes.removed, - suffix: 'tame', - users: [ - { - id: user.id, - cost: foodRes.removed - } - ] - }); - const kcs = await getIgneTameKC(tame); const deathChance = monster.deathChance ? monster.deathChance({ tame, kc: kcs.idBank[monster.id] ?? 0 }) : 0; let realDuration: number = fakeDuration; @@ -1266,19 +1251,7 @@ async function monkeyMagicHandler( } await user.removeItemsFromBank(finalCost); - await trackLoot({ - id: `${spellOptions.spell.name}`, - changeType: 'cost', - type: 'Skilling', - totalCost: finalCost, - suffix: 'tame', - users: [ - { - id: user.id, - cost: finalCost - } - ] - }); + await prisma.tame.update({ where: { id: tame.id @@ -1287,7 +1260,6 @@ async function monkeyMagicHandler( total_cost: new Bank(tame.total_cost as ItemBank).add(finalCost).bank } }); - await updateBankSetting('economyStats_PVMCost', finalCost); const duration = Math.floor(quantity * speed); @@ -1547,7 +1519,7 @@ async function tameUnequipCommand(user: MUser, itemName: string) { } const loot = new Bank().add(equippable.item.id); - await user.addItemsToBank({ items: loot, collectionLog: false, dontAddToTempCL: true }); + await user.transactItems({ itemsToAdd: loot, collectionLog: false, dontAddToTempCL: true, shouldRemap: false }); await prisma.tame.update({ where: { @@ -1681,7 +1653,6 @@ async function tameClueCommand(user: MUser, channelID: string, inputName: string await user.removeItemsFromBank(cost); await tame.addToStatsBank('total_cost', cost); - await updateBankSetting('economyStats_PVMCost', cost); if (costSavedByDemonicJibwings) { await tame.addToStatsBank('demonic_jibwings_saved_cost', costSavedByDemonicJibwings); } diff --git a/src/mahoji/commands/testershop.ts b/src/mahoji/commands/testershop.ts deleted file mode 100644 index f44559921d..0000000000 --- a/src/mahoji/commands/testershop.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Time } from 'e'; -import { Bank } from 'oldschooljs'; - -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { premiumPatronTime } from '../../lib/premiumPatronTime'; -import { roboChimpUserFetch } from '../../lib/roboChimp'; -import type { OSBMahojiCommand } from '../lib/util'; - -const shop = [ - { - name: 'Tester gift box', - cost: 30 - }, - { - name: '1 Month T1', - cost: 300 - }, - { - name: 'Double loot token', - cost: 80 - } -] as const; - -export const testerShopCommand: OSBMahojiCommand = { - name: 'testershop', - description: 'Buy things using your testing points.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'name', - description: 'The item to buy.', - required: true, - choices: shop.map(i => ({ - name: `${i.name} (${i.cost} points)`, - value: i.name - })) - }, - { - type: ApplicationCommandOptionType.Integer, - name: 'quantity', - description: 'The quantity (defaults to 1).', - required: false, - min_value: 1 - } - ], - run: async ({ options, userID }: CommandRunOptions<{ name: string; quantity?: number }>) => { - const user = await mUserFetch(userID); - const robochimpUser = await roboChimpUserFetch(userID); - const item = shop.find(i => i.name === options.name); - if (!item) return 'Invalid item.'; - const quantity = options.quantity ?? 1; - const cost = item.cost * quantity; - if (robochimpUser.testing_points_balance < cost) { - return `You don't have enough points to buy ${quantity}x ${item.name}.`; - } - await roboChimpClient.user.update({ - data: { - testing_points_balance: { - decrement: cost - } - }, - where: { - id: BigInt(userID) - } - }); - - debugLog(`Tester shop: ${user.id} bought ${quantity}x ${item.name} for ${cost} points.`); - switch (item.name) { - case 'Tester gift box': { - const loot = new Bank().add('Tester gift box', quantity); - await user.addItemsToBank({ items: loot, collectionLog: true }); - return `You bought ${loot}!`; - } - case '1 Month T1': { - const res = await premiumPatronTime(Time.Day * 31, 3, user, null); - return res; - } - case 'Double loot token': { - const loot = new Bank().add('Double loot token', quantity); - await user.addItemsToBank({ items: loot, collectionLog: true }); - return `You bought ${loot}!`; - } - } - } -}; diff --git a/src/mahoji/commands/testpotato.ts b/src/mahoji/commands/testpotato.ts index e43c9ee156..6228d110f3 100644 --- a/src/mahoji/commands/testpotato.ts +++ b/src/mahoji/commands/testpotato.ts @@ -3,14 +3,13 @@ import { Bank, Items } from 'oldschooljs'; import { convertLVLtoXP, itemID, toKMB } from 'oldschooljs/dist/util'; import { type Prisma, activity_type_enum, tame_growth, xp_gains_skill_enum } from '@prisma/client'; -import { ApplicationCommandOptionType, type User } from 'discord.js'; +import { ApplicationCommandOptionType } from 'discord.js'; import { Time, noOp } from 'e'; -import { production } from '../../config'; import { mahojiUserSettingsUpdate } from '../../lib/MUser'; import { BathhouseOres, BathwaterMixtures } from '../../lib/baxtorianBathhouses'; import { allStashUnitTiers, allStashUnitsFlat } from '../../lib/clues/stashUnits'; import { CombatAchievements } from '../../lib/combat_achievements/combatAchievements'; -import { BitField, MAX_INT_JAVA, MAX_XP } from '../../lib/constants'; +import { BitField, MAX_INT_JAVA, MAX_XP, globalConfig } from '../../lib/constants'; import { expertCapesCL, gorajanArcherOutfit, @@ -36,6 +35,8 @@ import { MAX_QP } from '../../lib/minions/data/quests'; import { allOpenables } from '../../lib/openables'; import { Minigames } from '../../lib/settings/minigames'; +import type { ItemBank } from 'oldschooljs/dist/meta/types'; +import { relics } from '../../lib/randomizer'; import { getFarmingInfo } from '../../lib/skilling/functions/getFarmingInfo'; import Skills from '../../lib/skilling/skills'; import Farming from '../../lib/skilling/skills/farming'; @@ -64,11 +65,9 @@ import { getUsersTame } from '../../lib/util/tameUtil'; import { userEventToStr } from '../../lib/util/userEvents'; import { getPOH } from '../lib/abstracted_commands/pohCommand'; import { allUsableItems } from '../lib/abstracted_commands/useCommand'; -import { BingoManager } from '../lib/bingo/BingoManager'; import { gearSetupOption } from '../lib/mahojiCommandOptions'; import type { OSBMahojiCommand } from '../lib/util'; import { userStatsUpdate } from '../mahojiSettings'; -import { fetchBingosThatUserIsInvolvedIn } from './bingo'; import { generateNewTame } from './nursery'; import { tameEquippables, tameImage } from './tames'; @@ -107,8 +106,6 @@ const thingsToReset = [ await prisma.slayerTask.deleteMany({ where: { user_id: user.id } }).catch(noOp); await prisma.activity.deleteMany({ where: { user_id: BigInt(user.id) } }).catch(noOp); await prisma.commandUsage.deleteMany({ where: { user_id: BigInt(user.id) } }).catch(noOp); - await prisma.gearPreset.deleteMany({ where: { user_id: user.id } }).catch(noOp); - await prisma.giveaway.deleteMany({ where: { user_id: user.id } }).catch(noOp); await prisma.lastManStandingGame.deleteMany({ where: { user_id: BigInt(user.id) } }).catch(noOp); await prisma.minigame.deleteMany({ where: { user_id: user.id } }).catch(noOp); await prisma.newUser.deleteMany({ where: { id: user.id } }).catch(noOp); @@ -321,12 +318,26 @@ const thingsToWipe = [ 'trips' ] as const; -export const testPotatoCommand: OSBMahojiCommand | null = production +export const testPotatoCommand: OSBMahojiCommand | null = globalConfig.isProduction ? null : { name: 'testpotato', description: 'Commands for making testing easier and faster.', options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: 'spawnrelic', + description: 'spawn relic.', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'relic', + description: 'relic.', + required: true, + choices: relics.map(i => ({ name: i.name, value: i.name })) + } + ] + }, { type: ApplicationCommandOptionType.Subcommand, name: 'wipe', @@ -648,27 +659,6 @@ export const testPotatoCommand: OSBMahojiCommand | null = production } ] }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'bingo_tools', - description: 'Bingo tools', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'start_bingo', - description: 'Make your bingo start now.', - required: true, - autocomplete: async (value: string, user: User) => { - const bingos = await fetchBingosThatUserIsInvolvedIn(user.id); - return bingos - .map(i => new BingoManager(i)) - .filter(b => b.creatorID === user.id || b.organizers.includes(user.id)) - .filter(bingo => (!value ? true : bingo.id.toString() === value)) - .map(bingo => ({ name: bingo.title, value: bingo.id.toString() })); - } - } - ] - }, { type: ApplicationCommandOptionType.Subcommand, name: 'setslayertask', @@ -749,14 +739,25 @@ export const testPotatoCommand: OSBMahojiCommand | null = production bingo_tools?: { start_bingo: string }; setslayertask?: { master: string; monster: string; quantity: number }; events?: {}; + spawnrelic?: { relic: string }; }>) => { await deferInteraction(interaction); - if (production) { + if (globalConfig.isProduction) { logError('Test command ran in production', { userID: userID.toString() }); return 'This will never happen...'; } const user = await mUserFetch(userID.toString()); + if (options.spawnrelic) { + const relic = relics.find(r => r.name === options.spawnrelic?.relic); + if (!relic) return 'Invalid relic'; + await user.update({ + relics: { + push: relic.id + } + }); + return `Spawned relic: ${relic.name}`; + } if (options.settamelvl) { const tame = await getUsersTame(user); if (!tame.tame) return 'no tame selected'; @@ -771,13 +772,6 @@ export const testPotatoCommand: OSBMahojiCommand | null = production return tameImage(user); } - if (options.refreshic) { - await mahojiUserSettingsUpdate(user.id, { - last_item_contract_date: 0 - }); - return 'reset your last contract date'; - } - if (options.events) { const events = await prisma.userEvent.findMany({ where: { @@ -789,26 +783,6 @@ export const testPotatoCommand: OSBMahojiCommand | null = production }); return events.map(userEventToStr).join('\n'); } - if (options.bingo_tools) { - if (options.bingo_tools.start_bingo) { - const bingo = await prisma.bingo.findFirst({ - where: { - id: Number(options.bingo_tools.start_bingo), - creator_id: user.id - } - }); - if (!bingo) return 'Invalid bingo.'; - await prisma.bingo.update({ - where: { - id: bingo.id - }, - data: { - start_date: new Date() - } - }); - return 'Your bingo start date has been set to this moment, so it has just started.'; - } - } if (options.check) { if (options.check.monster_droprates) { @@ -852,13 +826,6 @@ ${droprates.join('\n')}`), return 'Finished all CA tasks.'; } } - if (options.irontoggle) { - const current = user.isIronman; - await user.update({ - minion_ironman: !current - }); - return `You now ${!current ? 'ARE' : 'ARE NOT'} an ironman.`; - } if (options.wipe) { const { thing } = options.wipe; if (thing === 'trips') { @@ -869,29 +836,12 @@ ${droprates.join('\n')}`), }); return 'Deleted all your trips.'; } - if (thing === 'mt') { - await user.update({ - bso_mystery_trail_current_step_id: null, - collectionLogBank: {}, - bank: {}, - bitfield: user.bitfield.filter(i => i !== BitField.HasUnlockedYeti) - }); - return 'MT + cl + bank reset.'; - } if (thing === 'kc') { await userStatsUpdate(user.id, { monster_scores: {} }); return 'Reset all your KCs.'; } - if (thing === 'buypayout') { - await prisma.botItemSell.deleteMany({ - where: { - user_id: user.id - } - }); - return 'Deleted all your buy payout records, so you have no tax rate accumulated.'; - } if (thing === 'bank') { await mahojiUserSettingsUpdate(user.id, { bank: {} @@ -907,8 +857,7 @@ ${droprates.join('\n')}`), } if (thing === 'cl') { await mahojiUserSettingsUpdate(user.id, { - collectionLogBank: {}, - temp_cl: {} + collectionLogBank: {} }); await prisma.userStats.update({ where: { @@ -980,20 +929,6 @@ ${droprates.join('\n')}`), pool: 29_241 } }); - await roboChimpClient.user.upsert({ - where: { - id: BigInt(user.id) - }, - create: { - id: BigInt(user.id), - leagues_points_balance_osb: 25_000 - }, - update: { - leagues_points_balance_osb: { - increment: 25_000 - } - } - }); const loot = new Bank() .add('Rune pouch') .add('Blood rune', 100_000_000) @@ -1120,7 +1055,7 @@ Spawned an adult of each tame, fed them all applicable items, and spawned ALL th return setXP(user, options.setxp.skill, options.setxp.xp); } if (options.spawn) { - const { preset, collectionlog, item, items } = options.spawn; + const { preset, item, items } = options.spawn; const bankToGive = new Bank(); if (preset) { const actualPreset = spawnPresets.find(i => i[0] === preset); @@ -1167,7 +1102,10 @@ Spawned an adult of each tame, fed them all applicable items, and spawned ALL th return `Gave you ${loot}, ${bBank}, 200m invention xp, unlocked all blueprints.`; } - await user.addItemsToBank({ items: bankToGive, collectionLog: Boolean(collectionlog) }); + const currentBank = user.user.bank as ItemBank; + await user.update({ + bank: new Bank(currentBank).add(bankToGive).bank + }); return `Spawned: ${bankToGive.toString().slice(0, 500)}.`; } if (options.setmonsterkc) { diff --git a/src/mahoji/commands/tokkulshop.ts b/src/mahoji/commands/tokkulshop.ts index a3d853ccb7..e2957b740a 100644 --- a/src/mahoji/commands/tokkulshop.ts +++ b/src/mahoji/commands/tokkulshop.ts @@ -11,7 +11,6 @@ import { formatDuration, stringMatches } from '../../lib/util'; import addSubTaskToActivityTask from '../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../lib/util/calcMaxTripLength'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import { updateBankSetting } from '../../lib/util/updateBankSetting'; import type { OSBMahojiCommand } from '../lib/util'; const { TzTokJad } = Monsters; @@ -102,7 +101,7 @@ export const tksCommand: OSBMahojiCommand = { const [hasKaramjaDiary] = await userhasDiaryTier(user, KaramjaDiary.easy); const item = TokkulShopItems.find(i => stringMatches(i.name, options.buy?.name ?? options.sell?.name ?? '')); const hasKilledJad: boolean = (await user.getKC(TzTokJad.id)) >= 1; - const isIronman = !!user.user.minion_ironman; + const isIronman = true; // If the user is buying an invalid item if (!item) return "That's not a valid item."; @@ -185,7 +184,6 @@ export const tksCommand: OSBMahojiCommand = { // Remove the cost, and update bank settings await transactItems({ userID: user.id, itemsToRemove: cost }); - await updateBankSetting('tks_cost', cost); // Tokkul shop activity await addSubTaskToActivityTask({ diff --git a/src/mahoji/commands/tools.ts b/src/mahoji/commands/tools.ts index e7dac3176c..f727c04df1 100644 --- a/src/mahoji/commands/tools.ts +++ b/src/mahoji/commands/tools.ts @@ -1,23 +1,11 @@ -import { - type CommandResponse, - type CommandRunOptions, - type MahojiUserOption, - PerkTier, - asyncGzip -} from '@oldschoolgg/toolkit'; +import { type CommandResponse, type CommandRunOptions, PerkTier, asyncGzip } from '@oldschoolgg/toolkit'; import type { Activity, User } from '@prisma/client'; -import { ApplicationCommandOptionType, ChannelType, EmbedBuilder, userMention } from 'discord.js'; -import { Time } from 'e'; +import { ApplicationCommandOptionType, ChannelType } from 'discord.js'; import { Bank } from 'oldschooljs'; import type { Item, ItemBank } from 'oldschooljs/dist/meta/types'; import { ToBUniqueTable } from 'oldschooljs/dist/simulation/misc/TheatreOfBlood'; -import { ADMIN_IDS, OWNER_IDS, production } from '../../config.example'; -import { giveBoxResetTime, mahojiUserSettingsUpdate, spawnLampResetTime } from '../../lib/MUser'; -import { MysteryBoxes, spookyTable } from '../../lib/bsoOpenables'; import { ClueTiers } from '../../lib/clues/clueTiers'; import { allStashUnitsFlat } from '../../lib/clues/stashUnits'; -import { BitField, Channel, Emoji } from '../../lib/constants'; -import { allCLItems, allDroppedItems } from '../../lib/data/Collections'; import { anglerOutfit, evilChickenOutfit, @@ -26,36 +14,16 @@ import { shadesOfMorttonCL, toaCL } from '../../lib/data/CollectionsExport'; -import pets from '../../lib/data/pets'; -import { addToDoubleLootTimer } from '../../lib/doubleLoot'; -import killableMonsters, { effectiveMonsters, NightmareMonster } from '../../lib/minions/data/killableMonsters'; -import { getUsersPerkTier } from '../../lib/perkTiers'; +import { NightmareMonster } from '../../lib/minions/data/killableMonsters'; import type { MinigameName } from '../../lib/settings/minigames'; import { Minigames } from '../../lib/settings/minigames'; import { convertStoredActivityToFlatActivity } from '../../lib/settings/prisma'; -import Skills from '../../lib/skilling/skills'; -import { - formatDuration, - generateXPLevelQuestion, - getUsername, - isGroupActivity, - isNexActivity, - isRaidsActivity, - isTOBOrTOAActivity, - itemID, - itemNameFromID, - roll, - stringMatches -} from '../../lib/util'; -import { findGroupOfUser } from '../../lib/util/findGroupOfUser'; -import { getItem } from '../../lib/util/getOSItem'; +import { formatDuration, isGroupActivity, isNexActivity, isRaidsActivity, isTOBOrTOAActivity } from '../../lib/util'; import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; import { deferInteraction } from '../../lib/util/interactionReply'; import { makeBankImage } from '../../lib/util/makeBankImage'; import { repairBrokenItemsFromUser } from '../../lib/util/repairBrokenItems'; import resolveItems from '../../lib/util/resolveItems'; -import { LampTable } from '../../lib/xpLamps'; -import { Cooldowns } from '../lib/Cooldowns'; import { getParsedStashUnits, stashUnitBuildAllCommand, @@ -63,48 +31,14 @@ import { stashUnitUnfillCommand, stashUnitViewCommand } from '../lib/abstracted_commands/stashUnitsCommand'; -import { dataPoints, statsCommand } from '../lib/abstracted_commands/statCommand'; -import { buttonUserPicker } from '../lib/buttonUserPicker'; -import { itemOption, monsterOption, skillOption } from '../lib/mahojiCommandOptions'; +import { dataPoints } from '../lib/abstracted_commands/statCommand'; import type { OSBMahojiCommand } from '../lib/util'; import { patronMsg } from '../mahojiSettings'; -const INTERVAL_DAY = 'day'; -const INTERVAL_WEEK = 'week'; -const INTERVAL_MONTH = 'month'; -const skillsVals = Object.values(Skills); - function dateDiff(first: number, second: number) { return Math.round((second - first) / (1000 * 60 * 60 * 24)); } -async function giveBox(mahojiUser: MUser, _recipient: MahojiUserOption) { - if (!_recipient) return 'You need to specify a user to give a box to.'; - const recipient = await mUserFetch(_recipient.user.id); - - const currentDate = Date.now(); - const lastDate = Number(mahojiUser.user.lastGivenBoxx); - const difference = currentDate - lastDate; - const isOwner = OWNER_IDS.includes(mahojiUser.id); - - // If no user or not an owner and can not send one yet, show time till next box. - if (difference < giveBoxResetTime && !isOwner) { - return `You can give another box in ${formatDuration(giveBoxResetTime - difference)}`; - } - - if (recipient.id === mahojiUser.id) return "You can't give boxes to yourself!"; - if (recipient.isIronman) return "You can't give boxes to ironmen!"; - await mahojiUserSettingsUpdate(mahojiUser.id, { - lastGivenBoxx: currentDate - }); - - const boxToReceive = new Bank().add(roll(10) ? MysteryBoxes.roll() : itemID('Mystery box')); - - await recipient.addItemsToBank({ items: boxToReceive, collectionLog: false }); - - return `Gave **${boxToReceive}** to ${recipient}.`; -} - const whereInMassClause = (id: string) => `OR (group_activity = true AND data::jsonb ? 'users' AND data->>'users'::text LIKE '%${id}%')`; @@ -169,290 +103,6 @@ ${whereInMassClause(id)};`) `; } -async function clueGains(interval: string, tier?: string, ironmanOnly?: boolean) { - let tierFilter = ''; - let title = ''; - let intervalValue = ''; - - switch (interval.toLowerCase()) { - case INTERVAL_DAY: - intervalValue = 'day'; - break; - case INTERVAL_WEEK: - intervalValue = 'week'; - break; - case INTERVAL_MONTH: - intervalValue = 'month'; - break; - default: - return 'Invalid time interval.'; - } - if (tier) { - const clueTier = ClueTiers.find(t => t.name.toLowerCase() === tier.toLowerCase()); - if (!clueTier) return 'Invalid clue scroll tier.'; - const tierId = clueTier.id; - tierFilter = `AND (a."data"->>'ci')::int = ${tierId}`; - title = `Highest ${clueTier.name} clue scroll completions in the past ${interval}`; - } else { - title = `Highest All clue scroll completions in the past ${interval}`; - } - - const query = `SELECT a.user_id::text, SUM((a."data"->>'q')::int) AS qty, MAX(a.finish_date) AS lastDate - FROM activity a - JOIN users u ON a.user_id::text = u.id - WHERE a.type = 'ClueCompletion' - AND a.finish_date >= now() - interval '1 ${intervalValue}' AND a.completed = true - ${ironmanOnly ? ' AND u."minion.ironman" = true' : ''} - ${tierFilter} - GROUP BY a.user_id - ORDER BY qty DESC, lastDate ASC - LIMIT 10`; - - const res = await prisma.$queryRawUnsafe<{ user_id: string; qty: number }[]>(query); - - if (res.length === 0) { - return 'No results found.'; - } - - let place = 0; - const embed = new EmbedBuilder() - .setTitle(title) - .setDescription( - ( - await Promise.all( - res.map( - async (i: any) => - `${++place}. **${await getUsername(i.user_id)}**: ${Number(i.qty).toLocaleString()}` - ) - ) - ).join('\n') - ); - - return { embeds: [embed] }; -} - -interface XPRecord { - user: string; - total_xp: number; - lastDate: string; -} - -async function executeXPGainsQuery( - intervalValue: string, - skillId: string | undefined, - ironmanOnly: boolean -): Promise { - const query = ` - SELECT - x.user_id::text AS user, - sum(x.xp) AS total_xp, - max(x.date) AS lastDate - FROM - xp_gains AS x - INNER JOIN - users AS u ON u.id = x.user_id::text - WHERE - x.date > now() - INTERVAL '1 ${intervalValue}' - ${skillId ? `AND x.skill = '${skillId}'` : ''} - ${ironmanOnly ? ' AND u."minion.ironman" = true' : ''} - GROUP BY - x.user_id - ORDER BY - total_xp DESC, - lastDate ASC - LIMIT 10; - `; - - const result = await prisma.$queryRawUnsafe(query); - return result; -} - -async function xpGains(interval: string, skill?: string, ironmanOnly?: boolean) { - let intervalValue = ''; - - switch (interval.toLowerCase()) { - case INTERVAL_DAY: - intervalValue = 'day'; - break; - case INTERVAL_WEEK: - intervalValue = 'week'; - break; - case INTERVAL_MONTH: - intervalValue = 'month'; - break; - default: - return 'Invalid time interval.'; - } - - const skillObj = skill - ? skillsVals.find(_skill => _skill.aliases.some(name => stringMatches(name, skill))) - : undefined; - - const xpRecords = await executeXPGainsQuery(intervalValue, skillObj?.id, Boolean(ironmanOnly)); - - if (xpRecords.length === 0) { - return 'No results found.'; - } - - let place = 0; - const embed = new EmbedBuilder() - .setTitle(`Highest ${skillObj ? skillObj.name : 'Overall'} XP Gains in the past ${interval}`) - .setDescription( - ( - await Promise.all( - xpRecords.map( - async record => - `${++place}. **${await getUsername(record.user)}**: ${Number(record.total_xp).toLocaleString()} XP` - ) - ) - ).join('\n') - ); - - return { embeds: [embed.data] }; -} - -async function kcGains(interval: string, monsterName: string, ironmanOnly?: boolean): CommandResponse { - let intervalValue = ''; - - switch (interval.toLowerCase()) { - case INTERVAL_DAY: - intervalValue = 'day'; - break; - case INTERVAL_WEEK: - intervalValue = 'week'; - break; - case INTERVAL_MONTH: - intervalValue = 'month'; - break; - default: - return 'Invalid time interval.'; - } - const monster = killableMonsters.find( - k => stringMatches(k.name, monsterName) || k.aliases.some(a => stringMatches(a, monsterName)) - ); - - if (!monster) { - return 'Invalid monster.'; - } - - const query = ` - SELECT a.user_id::text, SUM((a."data"->>'q')::int) AS qty, MAX(a.finish_date) AS lastDate - FROM activity a - JOIN users u ON a.user_id::text = u.id - WHERE a.type = 'MonsterKilling' AND (a."data"->>'mi')::int = ${monster.id} - AND a.finish_date >= now() - interval '1 ${intervalValue}' -- Corrected interval usage - AND a.completed = true - ${ironmanOnly ? ' AND u."minion.ironman" = true' : ''} - GROUP BY a.user_id - ORDER BY qty DESC, lastDate ASC - LIMIT 10`; - const res = await prisma.$queryRawUnsafe<{ user_id: string; qty: number }[]>(query); - - if (res.length === 0) { - return 'No results found.'; - } - - let place = 0; - const embed = new EmbedBuilder() - .setTitle(`Highest ${monster.name} KC gains in the past ${interval}`) - .setDescription( - ( - await Promise.all( - res.map( - async (i: any) => - `${++place}. **${await getUsername(i.user_id)}**: ${Number(i.qty).toLocaleString()}` - ) - ) - ).join('\n') - ); - - return { embeds: [embed.data] }; -} - -export function spawnLampIsReady(user: MUser, channelID: string): [true] | [false, string] { - if (production && ![Channel.BSOChannel, Channel.General, Channel.BSOGeneral].includes(channelID)) { - return [false, "You can't use spawnlamp in this channel."]; - } - - const perkTier = user.perkTier(); - const isPatron = perkTier >= PerkTier.Four || user.bitfield.includes(BitField.HasPermanentSpawnLamp); - if (!isPatron) { - return [false, 'You need to be a T3 patron or higher to use this command.']; - } - const currentDate = Date.now(); - const lastDate = Number(user.user.lastSpawnLamp); - const difference = currentDate - lastDate; - - const cooldown = spawnLampResetTime(user); - - if (difference < cooldown) { - const duration = formatDuration(Date.now() - (lastDate + cooldown)); - return [false, `You can spawn another lamp in ${duration}.`]; - } - return [true]; -} -async function spawnLampCommand(user: MUser, channelID: string): CommandResponse { - const isAdmin = OWNER_IDS.includes(user.id) || ADMIN_IDS.includes(user.id); - const [lampIsReady, reason] = isAdmin ? [true, ''] : spawnLampIsReady(user, channelID); - if (!lampIsReady && reason) return reason; - - const group = await findGroupOfUser(user.id); - await prisma.user.updateMany({ - where: { - id: { - in: group - } - }, - data: { - lastSpawnLamp: Date.now() - } - }); - - const { answers, question, explainAnswer } = generateXPLevelQuestion(); - - const winnerID = await buttonUserPicker({ - channelID, - str: `<:Huge_lamp:988325171498721290> ${userMention(user.id)} spawned a Lamp: ${question}`, - ironmenAllowed: false, - answers, - creator: user.id, - creatorGetsTwoGuesses: true - }); - if (!winnerID) return `Nobody got it. ${explainAnswer}`; - const winner = await mUserFetch(winnerID); - const loot = LampTable.roll(); - await winner.addItemsToBank({ items: loot, collectionLog: false }); - return `${winner} got it, and won **${loot}**! ${explainAnswer}`; -} -async function spawnBoxCommand(user: MUser, channelID: string): CommandResponse { - const perkTier = user.perkTier(); - if (perkTier < PerkTier.Four && !user.bitfield.includes(BitField.HasPermanentEventBackgrounds)) { - return 'You need to be a T3 patron or higher to use this command.'; - } - if (production && ![Channel.BSOChannel, Channel.General, Channel.BSOGeneral].includes(channelID.toString())) { - return "You can't use spawnbox in this channel."; - } - const isOnCooldown = Cooldowns.get(user.id, 'SPAWN_BOX', Time.Minute * 45); - if (isOnCooldown !== null) { - return `This command is on cooldown for you for ${formatDuration(isOnCooldown)}.`; - } - const { answers, question, explainAnswer } = generateXPLevelQuestion(); - - const winnerID = await buttonUserPicker({ - channelID, - str: `${Emoji.MysteryBox} ${userMention(user.id)} spawned a Mystery Box: ${question}`, - ironmenAllowed: false, - answers, - creator: user.id - }); - if (!winnerID) return `Nobody got it. ${explainAnswer}`; - const winner = await mUserFetch(winnerID); - - const loot = new Bank().add(MysteryBoxes.roll()); - await winner.addItemsToBank({ items: loot, collectionLog: false }); - return `Congratulations, ${winner}! You received: **${loot}**. ${explainAnswer}`; -} - const clueItemsOnlyDroppedInOneTier = ClueTiers.flatMap(i => i.table.allItems.filter(itemID => ClueTiers.filter(i => i.table.allItems.includes(itemID)).length === 1) ); @@ -542,11 +192,10 @@ export const dryStreakEntities: DrystreakEntity[] = [ 'Gastly ghost cape', 'Spooky box' ]), - run: async ({ item, ironmanOnly }) => { + run: async ({ item }) => { const result = await prisma.$queryRawUnsafe<{ id: string; val: number }[]>(`SELECT user_id::text AS id, COUNT(1) as val FROM activity WHERE user_id IN (SELECT id::bigint FROM users WHERE "collectionLogBank"->'${item.id}' IS NULL -${ironmanOnly ? ' AND "minion.ironman" = TRUE' : ''}) AND type = 'HalloweenMiniMinigame' GROUP BY user_id ORDER BY val DESC LIMIT 10`); return result; @@ -570,13 +219,13 @@ ORDER BY val DESC LIMIT 10`); 'Twisted bow', 'Olmlet' ]), - run: async ({ item, ironmanOnly }) => { + run: async ({ item }) => { const result = await prisma.$queryRawUnsafe<{ id: string; points: number; raids_total_kc: number }[]>(`SELECT "users"."id", "user_stats".total_cox_points AS points, "minigames"."raids" + "minigames"."raids_challenge_mode" AS raids_total_kc FROM user_stats INNER JOIN "users" on "users"."id" = "user_stats"."user_id"::text INNER JOIN "minigames" on "minigames"."user_id" = "user_stats"."user_id"::text WHERE "collectionLogBank"->>'${item.id}' IS NULL -${ironmanOnly ? ' AND "minion.ironman" = true' : ''} + ORDER BY "user_stats".total_cox_points DESC LIMIT 10;`); return result.map(i => ({ @@ -598,7 +247,7 @@ LIMIT 10;`); 'Volatile orb', 'Harmonised orb' ]), - run: async ({ item, ironmanOnly }) => { + run: async ({ item }) => { const result = await prisma.$queryRawUnsafe< { id: string; val: number }[] >(`SELECT "id", ("monster_scores"->>'${NightmareMonster.id}')::int AS val @@ -606,7 +255,7 @@ LIMIT 10;`); INNER JOIN "user_stats" ON "user_stats"."user_id"::text = "users"."id" WHERE "collectionLogBank"->>'${item.id}' IS NULL AND "monster_scores"->>'${NightmareMonster.id}' IS NOT NULL - ${ironmanOnly ? 'AND "minion.ironman" = true' : ''} + ORDER BY ("monster_scores"->>'${NightmareMonster.id}')::int DESC LIMIT 10;`); return result; @@ -617,13 +266,13 @@ LIMIT 10;`); { name: 'Barbarian Assault (Pet penance queen)', items: resolveItems(['Pet penance queen']), - run: async ({ item, ironmanOnly }) => { + run: async ({ item }) => { const result = await prisma.$queryRawUnsafe<{ id: string; val: number }[]>(`SELECT "id", high_gambles AS val FROM users INNER JOIN "user_stats" ON "user_stats"."user_id"::text = "users"."id" WHERE "collectionLogBank"->>'${item.id}' IS NULL AND high_gambles > 0 - ${ironmanOnly ? 'AND "minion.ironman" = true' : ''} + ORDER BY high_gambles DESC LIMIT 10;`); return result; @@ -633,12 +282,12 @@ LIMIT 10;`); { name: 'Guardians of the Rift', items: guardiansOfTheRiftCL, - run: async ({ item, ironmanOnly }) => { + run: async ({ item }) => { const result = await prisma.$queryRawUnsafe<{ id: string; val: number }[]>(`SELECT users.id, gotr_rift_searches AS val FROM users INNER JOIN "user_stats" "userstats" on "userstats"."user_id"::text = "users"."id" WHERE "collectionLogBank"->>'${item.id}' IS NULL - ${ironmanOnly ? ' AND "minion.ironman" = true' : ''} + ORDER BY gotr_rift_searches DESC LIMIT 10;`); return result; @@ -648,7 +297,7 @@ LIMIT 10;`); { name: 'Evil Chicken Outfit', items: evilChickenOutfit, - run: async ({ item, ironmanOnly }) => { + run: async ({ item }) => { const result = await prisma.$queryRawUnsafe<{ id: string; val: number }[]>(` SELECT * FROM @@ -660,7 +309,7 @@ LIMIT 10;`); FROM users INNER JOIN "user_stats" "userstats" on "userstats"."user_id"::text = "users"."id" WHERE "collectionLogBank"->>'${item.id}' IS NULL - ${ironmanOnly ? ' AND "minion.ironman" = true' : ''} + GROUP BY users.id ORDER BY val DESC LIMIT 10 @@ -674,7 +323,7 @@ LIMIT 10;`); { name: 'Random Events', items: resolveItems(['Stale baguette']), - run: async ({ ironmanOnly }) => { + run: async () => { const result = await prisma.$queryRawUnsafe< { id: string; mbox_opens: number; baguettes_received: number }[] >(`SELECT id, (openable_scores->>'6199')::int AS mbox_opens, ("collectionLogBank"->>'6961')::int AS baguettes_received, @@ -688,7 +337,7 @@ AND "collectionLogBank"->>'6961' IS NOT NULL AND "collectionLogBank"->>'20590' IS NULL AND openable_scores->>'6199' IS NOT NULL AND (openable_scores->>'6199')::int > 3 -${ironmanOnly ? 'AND "minion.ironman" = true' : ''} + ORDER BY factor DESC LIMIT 10;`); return result.map(i => ({ @@ -701,7 +350,7 @@ LIMIT 10;`); { name: 'Clue Scrolls', items: clueItemsOnlyDroppedInOneTier, - run: async ({ ironmanOnly, item }) => { + run: async ({ item }) => { const clueTierWithItem = ClueTiers.filter(t => t.allItems.includes(item.id)); if (clueTierWithItem.length !== 1) { return 'You can only check items which are dropped by only 1 clue scroll tier.'; @@ -715,7 +364,6 @@ INNER JOIN "user_stats" ON "user_stats"."user_id"::text = "users"."id" WHERE "collectionLogBank"->>'${item.id}' IS NULL AND openable_scores->>'${clueTier.id}' IS NOT NULL AND (openable_scores->>'${clueTier.id}')::int > 100 -${ironmanOnly ? 'AND "minion.ironman" = true' : ''} ORDER BY opens DESC LIMIT 10;`); return result.map(i => ({ @@ -728,12 +376,12 @@ LIMIT 10;`); { name: 'Superior Slayer Creatures', items: resolveItems(['Imbued heart', 'Eternal gem']), - run: async ({ ironmanOnly, item }) => { + run: async ({ item }) => { const result = await prisma.$queryRawUnsafe<{ id: string; slayer_superior_count: number }[]>(`SELECT id, slayer_superior_count FROM users INNER JOIN "user_stats" ON "user_stats"."user_id"::text = "users"."id" WHERE "collectionLogBank"->>'${item.id}' IS NULL -${ironmanOnly ? 'AND "minion.ironman" = true' : ''} + ORDER BY slayer_superior_count DESC LIMIT 10;`); return result.map(i => ({ @@ -748,7 +396,7 @@ for (const minigame of dryStreakMinigames) { dryStreakEntities.push({ name: minigame.name, items: minigame.items, - run: async ({ item, ironmanOnly }) => { + run: async ({ item }) => { const minigameObj = Minigames.find(i => i.column === minigame.key)!; const result = await prisma.$queryRawUnsafe<{ id: string; val: number }[]>(`SELECT users.id, "minigame"."${ minigameObj.column @@ -756,7 +404,7 @@ for (const minigame of dryStreakMinigames) { FROM users INNER JOIN "minigames" "minigame" on "minigame"."user_id" = "users"."id"::text WHERE "collectionLogBank"->>'${item.id}' IS NULL -${ironmanOnly ? ' AND "minion.ironman" = true' : ''} + ORDER BY "minigame"."${minigameObj.column}" DESC LIMIT 10;`); return result; @@ -765,99 +413,6 @@ LIMIT 10;`); }); } -async function dryStreakCommand(monsterName: string, itemName: string, ironmanOnly: boolean) { - const item = getItem(itemName); - if (!item) return 'Invalid item.'; - const entity = dryStreakEntities.find(i => stringMatches(i.name, monsterName)); - if (entity) { - if (!entity.items.includes(item.id)) { - return `That's not a valid item dropped for this thing, valid items are: ${entity.items - .map(itemNameFromID) - .join(', ')}.`; - } - - const result = await entity.run({ item, ironmanOnly }); - if (result.length === 0) return 'No results found.'; - if (typeof result === 'string') return result; - - return `**Dry Streaks for ${item.name} from ${entity.name}:**\n${( - await Promise.all( - result.map(async ({ id, val }) => `${await getUsername(id)}: ${entity.format(val || -1)}`) - ) - ).join('\n')}`; - } - - const mon = effectiveMonsters.find(mon => mon.aliases.some(alias => stringMatches(alias, monsterName))); - if (!mon) { - return "That's not a valid monster or minigame."; - } - - const ironmanPart = ironmanOnly ? 'AND "minion.ironman" = true' : ''; - const key = 'monster_scores'; - const { id } = mon; - const query = `SELECT id, "${key}"->>'${id}' AS "KC" - FROM users - INNER JOIN "user_stats" ON "user_stats"."user_id"::text = "users"."id" - WHERE "collectionLogBank"->>'${item.id}' IS NULL - AND "${key}"->>'${id}' IS NOT NULL - ${ironmanPart} - ORDER BY ("${key}"->>'${id}')::int DESC - LIMIT 10;`; - - const result = - await prisma.$queryRawUnsafe< - { - id: string; - KC: string; - }[] - >(query); - - if (result.length === 0) return 'No results found.'; - - return `**Dry Streaks for ${item.name} from ${mon.name}:**\n${( - await Promise.all( - result.map( - async ({ id, KC }) => `${(await getUsername(id)) as string}: ${Number.parseInt(KC).toLocaleString()}` - ) - ) - ).join('\n')}`; -} - -async function mostDrops(user: MUser, itemName: string, filter: string) { - const item = getItem(itemName); - const ironmanPart = - filter === 'Irons Only' - ? 'AND "minion.ironman" = true' - : filter === 'Mains Only' - ? 'AND "minion.ironman" = false' - : ''; - if (!item) return "That's not a valid item."; - if (!allDroppedItems.includes(item.id) && !user.bitfield.includes(BitField.isModerator)) { - return "You can't check this item, because it's not on any collection log."; - } - - const query = `SELECT "id", "collectionLogBank"->>'${item.id}' AS "qty" FROM users WHERE "collectionLogBank"->>'${item.id}' IS NOT NULL ${ironmanPart} ORDER BY ("collectionLogBank"->>'${item.id}')::int DESC LIMIT 10;`; - - const result = - await prisma.$queryRawUnsafe< - { - id: string; - qty: string; - }[] - >(query); - - if (result.length === 0) return 'No results found.'; - - return `**Most '${item.name}' received:**\n${( - await Promise.all( - result.map( - async ({ id, qty }) => - `${result.length < 10 ? '(Anonymous)' : await getUsername(id)}: ${Number.parseInt(qty).toLocaleString()}` - ) - ) - ).join('\n')}`; -} - async function checkMassesCommand(guildID: string | undefined) { if (!guildID) return 'This can only be used in a server.'; const guild = globalClient.guilds.cache.get(guildID.toString()); @@ -905,53 +460,6 @@ async function checkMassesCommand(guildID: string | undefined) { ${massStr}`.slice(0, 1999); } -function calcTime(perkTier: PerkTier | 0) { - for (const [bit, dur] of [ - [PerkTier.Seven, Time.Minute * 90], - [PerkTier.Six, Time.Minute * 40], - [PerkTier.Five, Time.Minute * 20] - ] as const) { - if (perkTier === bit) return dur; - } - throw new Error('User is not a Tier 4+ Patron'); -} - -export const PATRON_DOUBLE_LOOT_COOLDOWN = Time.Day * 31; -async function patronTriggerDoubleLoot(user: MUser) { - const perkTier = getUsersPerkTier(user); - if (perkTier < PerkTier.Five) { - return 'Only T4, T5 or T6 patrons can use this command.'; - } - - const lastTime = user.user.last_patron_double_time_trigger; - const differenceSinceLastUsage = lastTime ? Date.now() - lastTime.getTime() : null; - if (differenceSinceLastUsage && differenceSinceLastUsage < PATRON_DOUBLE_LOOT_COOLDOWN) { - return `This command is still on cooldown, you can use it again in: ${formatDuration( - PATRON_DOUBLE_LOOT_COOLDOWN - differenceSinceLastUsage - )}.`; - } - - const time = calcTime(perkTier); - - const group = await findGroupOfUser(user.id); - await prisma.user.updateMany({ - where: { - id: { - in: group - } - }, - data: { - last_patron_double_time_trigger: new Date() - } - }); - - await addToDoubleLootTimer( - time, - `${userMention(user.id)} used their monthly Tier ${perkTier - 1} double loot time` - ); - return `Added ${formatDuration(time)} of double loot.`; -} - export const toolsCommand: OSBMahojiCommand = { name: 'tools', description: 'Various tools and miscellaneous commands.', @@ -961,129 +469,6 @@ export const toolsCommand: OSBMahojiCommand = { description: 'Tools that only patrons can use.', type: ApplicationCommandOptionType.SubcommandGroup, options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'clue_gains', - description: "Show's who has the highest clue scroll completions for a given time period.", - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'time', - description: 'The time period.', - required: true, - choices: ['day', 'week', 'month'].map(i => ({ name: i, value: i })) - }, - { - type: ApplicationCommandOptionType.String, - name: 'tier', - description: 'The tier of clue scroll.', - required: false, - autocomplete: async value => { - return [...ClueTiers.map(i => ({ name: i.name, value: i }))] - .filter(i => (!value ? true : i.name.toLowerCase().includes(value.toLowerCase()))) - .map(i => ({ name: i.name, value: i.name })); - } - }, - { - type: ApplicationCommandOptionType.Boolean, - name: 'ironman', - description: 'Only check ironmen accounts.', - required: false - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'kc_gains', - description: "Show's who has the highest KC gains for a given time period.", - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'time', - description: 'The time period.', - required: true, - choices: ['day', 'week', 'month'].map(i => ({ name: i, value: i })) - }, - monsterOption, - { - type: ApplicationCommandOptionType.Boolean, - name: 'ironman', - description: 'Only check ironmen accounts.', - required: false - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'xp_gains', - description: "Show's who has the highest XP gains for a given time period.", - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'time', - description: 'The time period.', - required: true, - choices: ['day', 'week', 'month'].map(i => ({ name: i, value: i })) - }, - skillOption, - { - type: ApplicationCommandOptionType.Boolean, - name: 'ironman', - description: 'Only check ironmen accounts.', - required: false - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'drystreak', - description: "Show's the biggest drystreaks for certain drops from a certain monster.", - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'monster', - description: 'The monster you want to pick.', - required: true, - autocomplete: async value => { - return [ - ...dryStreakEntities.map(i => ({ name: i.name, value: i })), - ...effectiveMonsters - ] - .filter(i => (!value ? true : i.name.toLowerCase().includes(value.toLowerCase()))) - .map(i => ({ name: i.name, value: i.name })); - } - }, - { - ...itemOption(item => [...allCLItems, ...spookyTable.allItems].includes(item.id)), - required: true - }, - { - type: ApplicationCommandOptionType.Boolean, - name: 'ironman', - description: 'Only check ironmen accounts.', - required: false - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'mostdrops', - description: - "Show's which players have received the most drops of an item, based on their collection log.", - options: [ - { - ...itemOption(), - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'filter', - description: 'Filter by account type.', - required: false, - choices: ['Both', 'Irons Only', 'Mains Only'].map(i => ({ name: i, value: i })) - } - ] - }, { type: ApplicationCommandOptionType.Subcommand, name: 'sacrificed_bank', @@ -1108,34 +493,6 @@ export const toolsCommand: OSBMahojiCommand = { name: 'minion_stats', description: 'Shows statistics about your minion.' }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'give_box', - description: 'Allows you to give a mystery box to a friend.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user you want to give a box too.', - required: true - } - ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'spawnlamp', - description: 'Allows you to spawn a lamp.' - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'spawnbox', - description: 'Allows you to spawn a mystery box.' - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'activity_export', - description: 'Export all your activities (For advanced users).' - }, { type: ApplicationCommandOptionType.Subcommand, name: 'stats', @@ -1157,11 +514,6 @@ export const toolsCommand: OSBMahojiCommand = { required: true } ] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'doubleloot', - description: 'Add double loot time.' } ] }, @@ -1170,25 +522,6 @@ export const toolsCommand: OSBMahojiCommand = { description: 'Various tools for yourself.', type: ApplicationCommandOptionType.SubcommandGroup, options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'mypets', - description: 'See the chat pets you have.', - options: [] - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'temp_cl', - description: 'Manage and view your temporary CL.', - options: [ - { - type: ApplicationCommandOptionType.Boolean, - name: 'reset', - description: 'Reset your temporary CL.', - required: false - } - ] - }, { type: ApplicationCommandOptionType.Subcommand, name: 'checkmasses', @@ -1270,49 +603,19 @@ export const toolsCommand: OSBMahojiCommand = { options, userID, interaction, - channelID, guildID }: CommandRunOptions<{ patron?: { - clue_gains?: { - time: 'day' | 'week' | 'month'; - tier?: string; - ironman?: boolean; - }; - kc_gains?: { - time: 'day' | 'week' | 'month'; - monster: string; - ironman?: boolean; - }; - xp_gains?: { - time: 'day' | 'week' | 'month'; - skill?: string; - ironman?: boolean; - }; - drystreak?: { - monster: string; - item: string; - ironman?: boolean; - }; - mostdrops?: { - item: string; - filter?: string; - }; sacrificed_bank?: {}; cl_bank?: { format?: 'bank' | 'json'; }; minion_stats?: {}; - give_box?: { - user: MahojiUserOption; - }; activity_export?: {}; - spawnlamp?: {}; - spawnbox?: {}; stats?: { stat: string }; doubleloot?: {}; }; - user?: { mypets?: {}; temp_cl: { reset?: boolean }; checkmasses?: {}; fixbank?: {} }; + user?: { checkmasses?: {}; fixbank?: {} }; stash_units?: { view?: { unit?: string; not_filled?: boolean }; build_all?: {}; @@ -1325,30 +628,6 @@ export const toolsCommand: OSBMahojiCommand = { if (options.patron) { const { patron } = options; - if (patron.clue_gains) { - if (mahojiUser.perkTier() < PerkTier.Four) return patronMsg(PerkTier.Four); - return clueGains(patron.clue_gains.time, patron.clue_gains.tier, Boolean(patron.clue_gains.ironman)); - } - if (patron.kc_gains) { - if (mahojiUser.perkTier() < PerkTier.Four) return patronMsg(PerkTier.Four); - return kcGains(patron.kc_gains.time, patron.kc_gains.monster, Boolean(patron.kc_gains.ironman)); - } - if (patron.xp_gains) { - if (mahojiUser.perkTier() < PerkTier.Four) return patronMsg(PerkTier.Four); - return xpGains(patron.xp_gains.time, patron.xp_gains.skill, patron.xp_gains.ironman); - } - if (patron.drystreak) { - if (mahojiUser.perkTier() < PerkTier.Four) return patronMsg(PerkTier.Four); - return dryStreakCommand( - patron.drystreak.monster, - patron.drystreak.item, - Boolean(patron.drystreak.ironman) - ); - } - if (patron.mostdrops) { - if (mahojiUser.perkTier() < PerkTier.Four) return patronMsg(PerkTier.Four); - return mostDrops(mahojiUser, patron.mostdrops.item, String(patron.mostdrops.filter)); - } if (patron.sacrificed_bank) { if (mahojiUser.perkTier() < PerkTier.Two) return patronMsg(PerkTier.Two); const sacBank = await mahojiUser.fetchStats({ sacrificed_bank: true }); @@ -1382,10 +661,6 @@ export const toolsCommand: OSBMahojiCommand = { if (mahojiUser.perkTier() < PerkTier.Four) return patronMsg(PerkTier.Four); return minionStats(mahojiUser.user); } - if (patron.give_box) { - if (getUsersPerkTier(mahojiUser) < PerkTier.Two) return patronMsg(PerkTier.Two); - return giveBox(mahojiUser, patron.give_box.user); - } if (patron.activity_export) { if (mahojiUser.perkTier() < PerkTier.Four) return patronMsg(PerkTier.Four); const promise = activityExport(mahojiUser.user); @@ -1396,31 +671,6 @@ export const toolsCommand: OSBMahojiCommand = { const result = await promise; return result; } - if (patron.spawnlamp) { - return spawnLampCommand(mahojiUser, channelID); - } - if (patron.spawnbox) return spawnBoxCommand(mahojiUser, channelID); - if (patron.stats) { - return statsCommand(mahojiUser, patron.stats.stat); - } - if (patron.doubleloot) { - return patronTriggerDoubleLoot(mahojiUser); - } - } - if (options.user) { - if (options.user.mypets) { - const b = new Bank(); - for (const [pet, qty] of Object.entries(mahojiUser.user.pets as ItemBank)) { - const petObj = pets.find(i => i.id === Number(pet)); - if (!petObj) continue; - b.add(petObj.name, qty); - } - return { - files: [ - (await makeBankImage({ bank: b, title: `Your Chat Pets (${b.length}/${pets.length})` })).file - ] - }; - } } if (options.stash_units) { @@ -1437,34 +687,7 @@ export const toolsCommand: OSBMahojiCommand = { return stashUnitUnfillCommand(mahojiUser, options.stash_units.unfill.unit); } } - if (options.user?.temp_cl) { - if (options.user.temp_cl.reset === true) { - await handleMahojiConfirmation( - interaction, - 'Are you sure you want to reset your temporary CL? If you are participating in a Bingo, this will reset your progress.' - ); - await mahojiUser.update({ - temp_cl: {}, - last_temp_cl_reset: new Date() - }); - return 'Reset your temporary CL.'; - } - const lastReset = await prisma.user.findUnique({ - where: { - id: mahojiUser.id - }, - select: { - last_temp_cl_reset: true - } - }); - return `You can view your temporary CL using, for example, \`/cl name:PvM type:Temp\`. -You last reset your temporary CL: ${ - lastReset?.last_temp_cl_reset - ? `` - : 'Never' - }`; - } if (options.user?.checkmasses) { return checkMassesCommand(guildID); } diff --git a/src/mahoji/commands/trade.ts b/src/mahoji/commands/trade.ts deleted file mode 100644 index 3a958d2e9a..0000000000 --- a/src/mahoji/commands/trade.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { discrimName, mentionCommand, truncateString } from '@oldschoolgg/toolkit'; -import type { CommandRunOptions } from '@oldschoolgg/toolkit'; -import type { MahojiUserOption } from '@oldschoolgg/toolkit'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { Bank } from 'oldschooljs'; - -import { BLACKLISTED_USERS } from '../../lib/blacklists'; -import { Events } from '../../lib/constants'; - -import { handleMahojiConfirmation } from '../../lib/util/handleMahojiConfirmation'; -import { deferInteraction } from '../../lib/util/interactionReply'; -import itemIsTradeable from '../../lib/util/itemIsTradeable'; -import { parseBank } from '../../lib/util/parseStringBank'; -import { tradePlayerItems } from '../../lib/util/tradePlayerItems'; -import { filterOption } from '../lib/mahojiCommandOptions'; -import type { OSBMahojiCommand } from '../lib/util'; -import { addToGPTaxBalance, mahojiParseNumber } from '../mahojiSettings'; - -export const tradeCommand: OSBMahojiCommand = { - name: 'trade', - description: 'Allows you to trade items with other players.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'user', - description: 'The user you want to trade items with.', - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: 'send', - description: 'The items you want to send to the other player.', - required: false - }, - { - type: ApplicationCommandOptionType.String, - name: 'receive', - description: 'The items you want to receieve from the other player.', - required: false - }, - { - type: ApplicationCommandOptionType.String, - name: 'price', - description: 'A shortcut for adding GP to the received items.', - required: false - }, - filterOption, - { - type: ApplicationCommandOptionType.String, - name: 'search', - description: 'An optional search of items by name.', - required: false - } - ], - run: async ({ - interaction, - userID, - guildID, - options, - user - }: CommandRunOptions<{ - user: MahojiUserOption; - send?: string; - receive?: string; - price?: string; - filter?: string; - search?: string; - }>) => { - await deferInteraction(interaction); - if (!guildID) return 'You can only run this in a server.'; - const senderUser = await mUserFetch(userID); - const senderAPIUser = user; - const recipientUser = await mUserFetch(options.user.user.id); - const recipientAPIUser = options.user.user; - - const isBlacklisted = BLACKLISTED_USERS.has(recipientUser.id); - if (isBlacklisted) return "Blacklisted players can't buy items."; - if (senderUser.user.minion_ironman || recipientUser.user.minion_ironman) { - return "Iron players can't trade items."; - } - if (recipientUser.id === senderUser.id) return "You can't trade yourself."; - if (recipientAPIUser.bot) return "You can't trade a bot."; - if (recipientUser.isBusy) return 'That user is busy right now.'; - - const itemsSent = - !options.search && !options.filter && !options.send - ? new Bank() - : parseBank({ - inputBank: senderUser.bankWithGP, - inputStr: options.send, - maxSize: 70, - flags: { tradeables: 'tradeables' }, - filters: [options.filter], - search: options.search, - noDuplicateItems: true - }).filter(i => itemIsTradeable(i.id, true)); - const itemsReceived = parseBank({ - inputStr: options.receive, - maxSize: 70, - flags: { tradeables: 'tradeables' }, - noDuplicateItems: true - }).filter(i => itemIsTradeable(i.id, true)); - - if (options.price) { - const gp = mahojiParseNumber({ input: options.price, min: 1 }); - if (gp) { - itemsReceived.add('Coins', gp); - } - } - - const allItems = new Bank().add(itemsSent).add(itemsReceived); - if (allItems.items().some(i => !itemIsTradeable(i[0].id, true))) { - return "You're trying to trade untradeable items."; - } - - if (itemsSent.length === 0 && itemsReceived.length === 0) return "You can't make an empty trade."; - if (!senderUser.owns(itemsSent)) return "You don't own those items."; - - await handleMahojiConfirmation( - interaction, - `**${senderUser}** is giving: ${truncateString(itemsSent.toString(), 950)} -**${recipientUser}** is giving: ${truncateString(itemsReceived.toString(), 950)} - -Both parties must click confirm to make the trade.`, - [recipientUser.id, senderUser.id] - ); - - await senderUser.sync(); - await recipientUser.sync(); - if (!recipientUser.owns(itemsReceived)) return "They don't own those items."; - if (!senderUser.owns(itemsSent)) return "You don't own those items."; - - const { success, message } = await tradePlayerItems(senderUser, recipientUser, itemsSent, itemsReceived); - if (!success) { - return `Trade failed because: ${message}`; - } - await prisma.economyTransaction.create({ - data: { - guild_id: BigInt(guildID), - sender: BigInt(senderUser.id), - recipient: BigInt(recipientUser.id), - items_sent: itemsSent.bank, - items_received: itemsReceived.bank, - type: 'trade' - } - }); - globalClient.emit( - Events.EconomyLog, - `${senderUser.mention} sold ${itemsSent} to ${recipientUser.mention} for ${itemsReceived}.` - ); - if (itemsReceived.has('Coins')) { - await addToGPTaxBalance(recipientUser.id, itemsReceived.amount('Coins')); - } - if (itemsSent.has('Coins')) { - await addToGPTaxBalance(senderUser.id, itemsSent.amount('Coins')); - } - - return `${discrimName(senderAPIUser)} sold ${itemsSent} to ${discrimName( - recipientAPIUser - )} in return for ${itemsReceived}. - -You can now buy/sell items in the Grand Exchange: ${mentionCommand(globalClient, 'ge')}`; - } -}; diff --git a/src/mahoji/commands/trivia.ts b/src/mahoji/commands/trivia.ts deleted file mode 100644 index 59735cb4e9..0000000000 --- a/src/mahoji/commands/trivia.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { TextChannel } from 'discord.js'; -import { userMention } from 'discord.js'; -import { ApplicationCommandOptionType } from 'discord.js'; -import { shuffleArr, uniqueArr } from 'e'; - -import type { CommandRunOptions, MahojiUserOption } from '@oldschoolgg/toolkit'; -import { DynamicButtons } from '../../lib/DynamicButtons'; -import { getRandomTriviaQuestions } from '../../lib/roboChimp'; -import { deferInteraction } from '../../lib/util/interactionReply'; -import type { OSBMahojiCommand } from '../lib/util'; - -export const triviaCommand: OSBMahojiCommand = { - name: 'trivia', - description: 'Try to answer a random trivia question!', - attributes: { - examples: ['/trivia', '/trivia duel:@Magnaboy'] - }, - options: [ - { - type: ApplicationCommandOptionType.User, - name: 'duel', - description: 'A user to duel in answering the question fastest.', - required: false - } - ], - run: async ({ - interaction, - userID, - channelID, - options - }: CommandRunOptions<{ - duel?: MahojiUserOption; - }>) => { - await deferInteraction(interaction); - const [question, ...fakeQuestions] = await getRandomTriviaQuestions(); - const channel = globalClient.channels.cache.get(channelID.toString()); - const users = [userID.toString()]; - if (options.duel) users.push(options.duel.user.id); - - let correctUser: string | null = null; - const buttons = new DynamicButtons({ - channel: channel as TextChannel, - usersWhoCanInteract: users, - deleteAfterConfirm: true - }); - for (const q of uniqueArr(shuffleArr([question, ...fakeQuestions].map(i => i.answers[0])))) { - buttons.add({ - name: q, - fn: ({ interaction }) => { - if (question.answers.includes(q)) { - correctUser = interaction.user.id; - } - }, - cantBeBusy: false - }); - } - - const allMention = users.map(userMention).join(' '); - - await buttons.render({ - messageOptions: { content: `${allMention} ${question.question}` }, - isBusy: false - }); - - if (users.length > 1) { - if (!correctUser) return `${allMention}, neither of you got it - sad!`; - return `${userMention(correctUser)} won the trivia duel!`; - } - return { - content: `You answered ${correctUser !== null ? 'correctly' : 'incorrectly'}!` - }; - } -}; diff --git a/src/mahoji/guildSettings.ts b/src/mahoji/guildSettings.ts index 4ff7301811..ab8fa49691 100644 --- a/src/mahoji/guildSettings.ts +++ b/src/mahoji/guildSettings.ts @@ -1,8 +1,8 @@ -import type { Guild, Prisma } from '@prisma/client'; +import type { Guild } from '@prisma/client'; import type { Guild as DJSGuild } from 'discord.js'; import { LRUCache } from 'lru-cache'; -type CachedGuild = Pick; +type CachedGuild = Pick; export const untrustedGuildSettingsCache = new LRUCache({ max: 1000 }); export async function mahojiGuildSettingsFetch(guild: string | DJSGuild) { @@ -17,24 +17,9 @@ export async function mahojiGuildSettingsFetch(guild: string | DJSGuild) { }, select: { disabledCommands: true, - id: true, - petchannel: true, - staffOnlyChannels: true + id: true } }); untrustedGuildSettingsCache.set(id, result); return result; } - -export async function mahojiGuildSettingsUpdate(guild: string | DJSGuild, data: Prisma.GuildUpdateArgs['data']) { - const guildID = typeof guild === 'string' ? guild : guild.id; - - const newGuild = await prisma.guild.update({ - data, - where: { - id: guildID - } - }); - untrustedGuildSettingsCache.set(newGuild.id, newGuild); - return { newGuild }; -} diff --git a/src/mahoji/lib/abstracted_commands/agilityArenaCommand.ts b/src/mahoji/lib/abstracted_commands/agilityArenaCommand.ts index 5a9d20c3c7..f573a9b267 100644 --- a/src/mahoji/lib/abstracted_commands/agilityArenaCommand.ts +++ b/src/mahoji/lib/abstracted_commands/agilityArenaCommand.ts @@ -166,29 +166,3 @@ export async function agilityArenaRecolorCommand(user: MUser) { head: 'izzy' }); } - -export async function agilityArenaXPCommand(user: MUser, qty: number): CommandResponse { - const amountTicketsHas = user.bank.amount('Agility arena ticket'); - - if (!(qty in ticketQuantities)) { - return `You can only redeem tickets for XP at the following quantities: ${Object.keys(ticketQuantities).join( - ', ' - )}.`; - } - if (amountTicketsHas < qty) { - return "You don't have enough Agility arena tickets."; - } - const [hasKaramjaMed] = await userhasDiaryTier(user, KaramjaDiary.medium); - const xpToGive = determineXPFromTickets(qty, user, hasKaramjaMed); - let str = `Redeemed ${qty}x Agility arena tickets for ${xpToGive.toLocaleString()} Agility XP. (${(xpToGive / qty).toFixed(2)} ea)`; - await transactItems({ userID: user.id, itemsToRemove: new Bank().add('Agility arena ticket', qty) }); - await user.addXP({ - skillName: SkillsEnum.Agility, - amount: xpToGive, - artificial: true - }); - if (hasKaramjaMed) { - str += '\n\nYou received 10% extra XP for the Karamja Medium Diary.'; - } - return str; -} diff --git a/src/mahoji/lib/abstracted_commands/alchCommand.ts b/src/mahoji/lib/abstracted_commands/alchCommand.ts index 0ef0c7f859..e3117e4019 100644 --- a/src/mahoji/lib/abstracted_commands/alchCommand.ts +++ b/src/mahoji/lib/abstracted_commands/alchCommand.ts @@ -11,7 +11,6 @@ import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { getItem } from '../../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; const unlimitedFireRuneProviders = resolveItems([ 'Staff of fire', @@ -98,7 +97,6 @@ export async function alchCommand( ); } await user.removeItemsFromBank(consumedItems); - await updateBankSetting('magic_cost_bank', consumedItems); await addSubTaskToActivityTask({ itemID: osItem.id, diff --git a/src/mahoji/lib/abstracted_commands/bankBgCommand.ts b/src/mahoji/lib/abstracted_commands/bankBgCommand.ts deleted file mode 100644 index 2948b8b2bf..0000000000 --- a/src/mahoji/lib/abstracted_commands/bankBgCommand.ts +++ /dev/null @@ -1,142 +0,0 @@ -import type { ChatInputCommandInteraction } from 'discord.js'; -import { Bank } from 'oldschooljs'; - -import { resolveItems } from 'oldschooljs/dist/util/util'; -import { BitField } from '../../../lib/constants'; -import { formatSkillRequirements, stringMatches, toKMB } from '../../../lib/util'; -import { findGroupOfUser } from '../../../lib/util/findGroupOfUser'; -import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; - -export async function bankBgCommand(interaction: ChatInputCommandInteraction, user: MUser, name: string) { - const bankImages = bankImageGenerator.backgroundImages; - const selectedImage = bankImages.find(img => stringMatches(img.name, name)); - - if (!selectedImage) { - return `The following bank images exist: ${bankImages.map(img => img.name).join(', ')}`; - } - - if (user.user.bankBackground === selectedImage.id) { - return 'This is already your bank background.'; - } - - const owners = selectedImage.owners ?? []; - const allAccounts = await findGroupOfUser(user.id); - if (user.bitfield.includes(BitField.isModerator) || allAccounts.some(a => owners.includes(a))) { - await user.update({ - bankBackground: selectedImage.id - }); - return `Your bank background is now **${selectedImage.name}**!`; - } - - if (selectedImage.storeBitField && user.user.store_bitfield.includes(selectedImage.storeBitField)) { - await user.update({ - bankBackground: selectedImage.id - }); - return `Your bank background is now **${selectedImage.name}**!`; - } - - if (selectedImage.sacValueRequired) { - const sac = Number(user.user.sacrificedValue); - if (sac < selectedImage.sacValueRequired) { - return `You have to have sacrificed atleast ${toKMB( - selectedImage.sacValueRequired - )} GP worth of items to use this background.`; - } - } - - if (selectedImage.skillsNeeded) { - const meets = user.hasSkillReqs(selectedImage.skillsNeeded); - if (!meets) { - return `You don't meet the skill requirements to use this background, you need: ${formatSkillRequirements( - selectedImage.skillsNeeded - )}.`; - } - } - - if (!selectedImage.available) { - return 'This image is not currently available.'; - } - - if (selectedImage.bitfield && !user.bitfield.includes(selectedImage.bitfield)) { - return "You're not elligible to use this bank background."; - } - - // Check they have required collection log items. - if (selectedImage.collectionLogItemsNeeded && !user.cl.has(selectedImage.collectionLogItemsNeeded)) { - return `You're not worthy to use this background. You need these items in your Collection Log: ${new Bank( - selectedImage.collectionLogItemsNeeded - )}`; - } - - // Check they have the required perk tier. - if (selectedImage.perkTierNeeded && user.perkTier() < selectedImage.perkTierNeeded) { - return `This background is only available for Tier ${Number(selectedImage.perkTierNeeded) - 1} patrons.`; - } - - if (selectedImage.name === 'Pets') { - const { cl } = user; - const hasPet = resolveItems(['Rocky', 'Bloodhound', 'Giant squirrel', 'Baby chinchompa']).some(id => - cl.has(id) - ); - if (!hasPet) { - return 'You need to have one of these pets to purchase the Pets background: Rocky, Bloodhound, Giant squirrel, Baby chinchompa.'; - } - } - - /** - * If this bank image has a gp or item cost, confirm and charge. - */ - const economyCost = new Bank(); - if (selectedImage.gpCost || selectedImage.itemCost) { - const userBank = user.bank; - - // Ensure they have the required items. - if (selectedImage.itemCost && !userBank.has(selectedImage.itemCost)) { - return `You don't have the required items to purchase this background. You need: ${new Bank( - selectedImage.itemCost - )}, you're missing: ${new Bank(selectedImage.itemCost).remove(userBank)}.`; - } - - // Ensure they have the required GP. - if (selectedImage.gpCost && user.GP < selectedImage.gpCost) { - return `You need ${selectedImage.gpCost.toLocaleString()} GP to purchase this background.`; - } - - // Start building a string to show to the user. - let str = `${user}, please confirm that you want to buy the **${selectedImage.name}** bank background for: `; - - // If theres an item cost or GP cost, add it to the string to show users the cost. - if (selectedImage.itemCost) { - str += new Bank(selectedImage.itemCost).toString(); - if (selectedImage.gpCost) { - str += `, ${selectedImage.gpCost.toLocaleString()} GP.`; - } - } else if (selectedImage.gpCost) { - str += `${selectedImage.gpCost.toLocaleString()} GP.`; - } - - str += - " **Note:** You'll have to pay this cost again if you switch to another background and want this one again."; - - await handleMahojiConfirmation(interaction, str); - - if (selectedImage.itemCost) { - economyCost.add(selectedImage.itemCost); - await user.removeItemsFromBank(new Bank(selectedImage.itemCost)); - } - - if (selectedImage.gpCost) { - economyCost.add(selectedImage.gpCost); - await user.removeItemsFromBank(new Bank().add('Coins', selectedImage.gpCost)); - } - } - - await user.update({ - bankBackground: selectedImage.id - }); - - updateBankSetting('economyStats_bankBgCostBank', economyCost); - - return `Your bank background is now **${selectedImage.name}**!`; -} diff --git a/src/mahoji/lib/abstracted_commands/barbAssault.ts b/src/mahoji/lib/abstracted_commands/barbAssault.ts index bc8971853d..c1361f9fd8 100644 --- a/src/mahoji/lib/abstracted_commands/barbAssault.ts +++ b/src/mahoji/lib/abstracted_commands/barbAssault.ts @@ -139,12 +139,13 @@ export async function barbAssaultBuyCommand( const { item, cost } = buyable; const stats = await user.fetchStats({ honour_points: true }); const balance = stats.honour_points; + const loot = new Bank().add(item.id, quantity); if (balance < cost * quantity) { - return `You don't have enough Honour Points to buy ${quantity.toLocaleString()}x ${item.name}. You need ${(cost * quantity).toLocaleString()}, but you have only ${balance.toLocaleString()}.`; + return `You don't have enough Honour Points to buy ${loot}. You need ${(cost * quantity).toLocaleString()}, but you have only ${balance.toLocaleString()}.`; } await handleMahojiConfirmation( interaction, - `Are you sure you want to buy ${quantity.toLocaleString()}x ${item.name}, for ${(cost * quantity).toLocaleString()} honour points?` + `Are you sure you want to buy ${loot}, for ${(cost * quantity).toLocaleString()} honour points?` ); await userStatsUpdate( user.id, @@ -156,9 +157,9 @@ export async function barbAssaultBuyCommand( {} ); - await user.addItemsToBank({ items: new Bank().add(item.id, quantity), collectionLog: true }); + const res = await user.addItemsToBank({ items: loot, collectionLog: true }); - return `Successfully purchased ${quantity.toLocaleString()}x ${item.name} for ${(cost * quantity).toLocaleString()} Honour Points.`; + return `Successfully purchased ${res.itemsAdded} for ${(cost * quantity).toLocaleString()} Honour Points.`; } export async function barbAssaultGambleCommand( diff --git a/src/mahoji/lib/abstracted_commands/birdhousesCommand.ts b/src/mahoji/lib/abstracted_commands/birdhousesCommand.ts index 172dfaaedc..b3cf2f88aa 100644 --- a/src/mahoji/lib/abstracted_commands/birdhousesCommand.ts +++ b/src/mahoji/lib/abstracted_commands/birdhousesCommand.ts @@ -7,7 +7,6 @@ import defaultBirdhouseTrap, { type BirdhouseData } from '../../../lib/skilling/ import type { BirdhouseActivityTaskOptions } from '../../../lib/types/minions'; import { birdhouseLimit } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { mahojiUsersSettingsFetch, userHasGracefulEquipped } from '../../mahojiSettings'; interface BirdhouseDetails { @@ -148,7 +147,6 @@ export async function birdhouseHarvestCommand(user: MUser, channelID: string, in } if (!user.owns(removeBank)) return `You don't own: ${removeBank}.`; - await updateBankSetting('farming_cost_bank', removeBank); await transactItems({ userID: user.id, itemsToRemove: removeBank }); // If user does not have something already placed, just place the new birdhouses. diff --git a/src/mahoji/lib/abstracted_commands/bonanzaCommand.ts b/src/mahoji/lib/abstracted_commands/bonanzaCommand.ts index b4746e84ae..5b06254694 100644 --- a/src/mahoji/lib/abstracted_commands/bonanzaCommand.ts +++ b/src/mahoji/lib/abstracted_commands/bonanzaCommand.ts @@ -1,6 +1,5 @@ import { Time } from 'e'; -import { production } from '../../../config'; import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { formatDuration, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; @@ -9,7 +8,7 @@ export async function bonanzaCommand(user: MUser, channelID: string) { if (user.minionIsBusy) return 'Your minion is busy.'; const lastPlayedDate = Number(user.user.last_bonanza_date); const difference = Date.now() - lastPlayedDate; - if (difference < Time.Day * 7 && production) { + if (difference < Time.Day * 7) { const duration = formatDuration(Date.now() - (lastPlayedDate + Time.Day * 7)); return `You can only participate in Balthazar's Big Bonanza once per week, you can do it again in ${duration}.`; } diff --git a/src/mahoji/lib/abstracted_commands/butlerCommand.ts b/src/mahoji/lib/abstracted_commands/butlerCommand.ts index 255027bf10..5b2910205f 100644 --- a/src/mahoji/lib/abstracted_commands/butlerCommand.ts +++ b/src/mahoji/lib/abstracted_commands/butlerCommand.ts @@ -8,7 +8,6 @@ import type { ButlerActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, itemNameFromID, stringMatches, toKMB } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; const unlimitedEarthRuneProviders = resolveItems([ 'Staff of earth', @@ -133,8 +132,6 @@ export async function butlerCommand(user: MUser, plankName: string, quantity: nu const costBank = new Bank(consumedItems).add('Coins', cost).add(plank?.inputItem, quantity); await user.removeItemsFromBank(costBank); - await updateBankSetting('construction_cost_bank', new Bank().add('Coins', cost)); - await addSubTaskToActivityTask({ type: 'Butler', duration, diff --git a/src/mahoji/lib/abstracted_commands/cancelGEListingCommand.ts b/src/mahoji/lib/abstracted_commands/cancelGEListingCommand.ts deleted file mode 100644 index e8a1dd14ba..0000000000 --- a/src/mahoji/lib/abstracted_commands/cancelGEListingCommand.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { UserError } from '@oldschoolgg/toolkit'; -import { Bank } from 'oldschooljs'; - -import { GrandExchange } from '../../../lib/grandExchange'; - -import { makeTransactFromTableBankQueries } from '../../../lib/tableBank'; -import { logError } from '../../../lib/util/logError'; - -export async function cancelUsersListings(user: MUser) { - const activeListings = await prisma.gEListing.findMany({ - where: { - user_id: user.id, - quantity_remaining: { - gt: 0 - }, - fulfilled_at: null, - cancelled_at: null - }, - include: { - buyTransactions: true, - sellTransactions: true - }, - orderBy: { - created_at: 'desc' - } - }); - // Return early if no active listings. - if (activeListings.length === 0) { - return true; - } - // Let's terminate all listings: - for (const listing of activeListings) { - const result = await cancelGEListingCommand(user, listing.userfacing_id); - if (!result.startsWith('Successfully cancelled your listing')) { - const err = `Failed to cancel ${user.usernameOrMention}'s listings: ${result}`; - logError(new Error(err)); - throw new UserError(err); - } - } - return true; -} - -export async function cancelGEListingCommand(user: MUser, idToCancel: string) { - if (GrandExchange.locked) { - return 'The Grand Exchange is currently closed, please try again later.'; - } - return GrandExchange.queue.add(async () => { - const listing = await prisma.gEListing.findFirst({ - where: { - user_id: user.id, - userfacing_id: idToCancel, - cancelled_at: null, - fulfilled_at: null, - quantity_remaining: { - gt: 0 - } - } - }); - if (!listing) { - return 'You do not have a listing with that ID.'; - } - if (listing.fulfilled_at || listing.quantity_remaining === 0) { - return 'You cannot cancel a listing that has already been fulfilled.'; - } - if (listing.cancelled_at) { - return 'You cannot cancel a listing that has already been cancelled.'; - } - - const refundBank = new Bank(); - if (listing.type === 'Buy') { - refundBank.add('Coins', Number(listing.asking_price_per_item) * listing.quantity_remaining); - } else { - refundBank.add(listing.item_id, listing.quantity_remaining); - } - - const geBank = await GrandExchange.fetchOwnedBank(); - if (!geBank.has(refundBank)) { - const error = new Error(`GE doesn't have ${refundBank} to refund ${user.id}, listing ${listing.id}`); - logError(error); - await GrandExchange.lockGE(error.message); - return 'Something went wrong, please try again later.'; - } - - await Promise.all([ - prisma.$transaction([ - prisma.gEListing.update({ - where: { - id: listing.id - }, - data: { - cancelled_at: new Date() - } - }), - ...makeTransactFromTableBankQueries({ bankToRemove: refundBank }) - ]), - user.addItemsToBank({ - items: refundBank, - collectionLog: false, - dontAddToTempCL: true, - neverUpdateHistory: true - }) - ]); - - return `Successfully cancelled your listing, you have been refunded ${refundBank}.`; - }); -} diff --git a/src/mahoji/lib/abstracted_commands/castCommand.ts b/src/mahoji/lib/abstracted_commands/castCommand.ts index 2466abe113..f7a90f21e6 100644 --- a/src/mahoji/lib/abstracted_commands/castCommand.ts +++ b/src/mahoji/lib/abstracted_commands/castCommand.ts @@ -7,7 +7,6 @@ import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { determineRunes } from '../../../lib/util/determineRunes'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { userHasGracefulEquipped } from '../../mahojiSettings'; export async function castCommand(channelID: string, user: MUser, name: string, quantity: number | undefined) { @@ -108,7 +107,6 @@ export async function castCommand(channelID: string, user: MUser, name: string, } await user.removeItemsFromBank(cost); - await updateBankSetting('magic_cost_bank', cost); await addSubTaskToActivityTask({ spellID: spell.id, diff --git a/src/mahoji/lib/abstracted_commands/collectCommand.ts b/src/mahoji/lib/abstracted_commands/collectCommand.ts index 6caac97fd6..24d25d82e5 100644 --- a/src/mahoji/lib/abstracted_commands/collectCommand.ts +++ b/src/mahoji/lib/abstracted_commands/collectCommand.ts @@ -7,7 +7,6 @@ import type { CollectingOptions } from '../../../lib/types/minions'; import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { collectables } from '../collectables'; import { getPOH } from './pohCommand'; @@ -83,8 +82,6 @@ export async function collectCommand( return `You don't have the items needed for this trip, you need: ${cost}.`; } await transactItems({ userID: user.id, itemsToRemove: cost }); - - await updateBankSetting('collecting_cost', cost); } await addSubTaskToActivityTask({ diff --git a/src/mahoji/lib/abstracted_commands/coxCommand.ts b/src/mahoji/lib/abstracted_commands/coxCommand.ts index b8a0d38e24..a824a2189b 100644 --- a/src/mahoji/lib/abstracted_commands/coxCommand.ts +++ b/src/mahoji/lib/abstracted_commands/coxCommand.ts @@ -18,7 +18,7 @@ import { inventionBoosts, inventionItemBoost } from '../../../lib/invention/inventions'; -import { trackLoot } from '../../../lib/lootTrack'; + import { setupParty } from '../../../lib/party'; import { getMinigameScore } from '../../../lib/settings/minigames'; import type { MakePartyOptions } from '../../../lib/types'; @@ -26,7 +26,6 @@ import type { RaidsOptions } from '../../../lib/types/minions'; import { channelIsSendable, formatDuration, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { mahojiParseNumber } from '../../mahojiSettings'; const uniques = [ @@ -96,8 +95,6 @@ export async function coxCommand( return 'Specify your team setup for Chambers of Xeric, either solo or mass.'; } - const minigameID = isChallengeMode ? 'raids_challenge_mode' : 'raids'; - if (isChallengeMode) { const normalKC = await getMinigameScore(user.id, 'raids'); if (normalKC < 200) { @@ -206,7 +203,7 @@ export async function coxCommand( }) ); - const costResult = await Promise.all([ + await Promise.all([ ...users.map(async u => { const supplies = (await calcCoxInput(u, isSolo)).multiply(quantity); await u.removeItemsFromBank(supplies); @@ -237,19 +234,6 @@ export async function coxCommand( }) ]); - updateBankSetting('cox_cost', totalCost); - - await trackLoot({ - id: minigameID, - totalCost, - type: 'Minigame', - changeType: 'cost', - users: costResult.map(i => ({ - id: i.userID, - cost: i.itemsRemoved - })) - }); - await addSubTaskToActivityTask({ userID: user.id, channelID: channelID.toString(), diff --git a/src/mahoji/lib/abstracted_commands/crackerCommand.ts b/src/mahoji/lib/abstracted_commands/crackerCommand.ts deleted file mode 100644 index be047d8cf9..0000000000 --- a/src/mahoji/lib/abstracted_commands/crackerCommand.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { ChatInputCommandInteraction, User } from 'discord.js'; -import { shuffleArr } from 'e'; -import { Bank, LootTable } from 'oldschooljs'; - -import { Emoji } from '../../../lib/constants'; -import { partyHatTableRoll } from '../../../lib/data/holidayItems'; -import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; - -const JunkTable = new LootTable() - .add('Chocolate bar', 1, 1 / 5.2) - .add('Silver bar', 1, 1 / 7.6) - .add('Spinach roll', 1, 1 / 8) - .add('Chocolate cake', 1, 1 / 8.6) - .add('Holy symbol', 1, 1 / 11.7) - .add('Silk', 1, 1 / 12.2) - .add('Gold ring', 1, 1 / 13.9) - .add('Black dagger', 1, 1 / 24.3) - .add('Law rune', 1, 1 / 25.3); - -export async function crackerCommand({ - ownerID, - otherPersonID, - interaction, - otherPersonAPIUser -}: { - otherPersonAPIUser: User; - ownerID: string; - otherPersonID: string; - interaction: ChatInputCommandInteraction; -}) { - const otherPerson = await mUserFetch(otherPersonID); - const owner = await mUserFetch(ownerID); - if (owner.isIronman && owner.id === otherPerson.id) { - if (!owner.owns('Christmas cracker')) { - return "You don't have any Christmas crackers!"; - } - await owner.removeItemsFromBank(new Bank().add('Christmas cracker', 1)); - const loot = partyHatTableRoll(); - await owner.addItemsToBank({ items: loot, collectionLog: true }); - return `${Emoji.ChristmasCracker} ${owner} pulled a Christmas cracker with... yourself? You received ${loot}.`; - } - - if (otherPerson.isIronman) return 'That person is an ironman, they stand alone.'; - if (otherPersonAPIUser.bot) return "Bot's don't have hands."; - if (otherPerson.id === owner.id) return 'Nice try.'; - - if (!owner.bank.has('Christmas cracker')) { - return "You don't have any Christmas crackers."; - } - - await handleMahojiConfirmation( - interaction, - `${Emoji.ChristmasCracker} Are you sure you want to use your cracker on them? Either person could get the partyhat! Please confirm if you understand and wish to use it.` - ); - - await owner.removeItemsFromBank(new Bank().add('Christmas cracker', 1)); - const winnerLoot = partyHatTableRoll(); - const loserLoot = JunkTable.roll(); - const [winner, loser] = shuffleArr([otherPerson, owner]); - await winner.addItemsToBank({ items: winnerLoot, collectionLog: true }); - await loser.addItemsToBank({ items: loserLoot, collectionLog: true }); - - return `${Emoji.ChristmasCracker} ${owner} pulled a Christmas cracker with ${otherPerson} and....\n\n ${winner} received ${winnerLoot}, ${loser} received ${loserLoot}.`; -} diff --git a/src/mahoji/lib/abstracted_commands/dailyCommand.ts b/src/mahoji/lib/abstracted_commands/dailyCommand.ts index 4d505fe344..5bdacfceb6 100644 --- a/src/mahoji/lib/abstracted_commands/dailyCommand.ts +++ b/src/mahoji/lib/abstracted_commands/dailyCommand.ts @@ -1,18 +1,14 @@ import type { CommandResponse } from '@oldschoolgg/toolkit'; -import type { ChatInputCommandInteraction, TextChannel } from 'discord.js'; -import { shuffleArr, uniqueArr } from 'e'; +import type { ChatInputCommandInteraction } from 'discord.js'; import { Bank } from 'oldschooljs'; -import { SupportServer } from '../../../config'; -import { DynamicButtons } from '../../../lib/DynamicButtons'; import { dailyResetTime } from '../../../lib/MUser'; import { COINS_ID, Emoji } from '../../../lib/constants'; -import { getRandomTriviaQuestions } from '../../../lib/roboChimp'; import dailyRoll from '../../../lib/simulation/dailyTable'; import { channelIsSendable, formatDuration, isWeekend, roll } from '../../../lib/util'; import { deferInteraction } from '../../../lib/util/interactionReply'; import { makeBankImage } from '../../../lib/util/makeBankImage'; -import { updateClientGPTrackSetting, userStatsUpdate } from '../../mahojiSettings'; +import { userStatsUpdate } from '../../mahojiSettings'; export async function isUsersDailyReady( user: MUser @@ -31,9 +27,6 @@ export async function isUsersDailyReady( } async function reward(user: MUser, triviaCorrect: boolean): CommandResponse { - const guild = globalClient.guilds.cache.get(SupportServer); - const member = await guild?.members.fetch(user.id).catch(() => null); - const loot = dailyRoll(3, triviaCorrect); const bonuses = []; @@ -43,11 +36,6 @@ async function reward(user: MUser, triviaCorrect: boolean): CommandResponse { bonuses.push(Emoji.MoneyBag); } - if (member) { - loot.bank[COINS_ID] = Math.floor(loot.bank[COINS_ID] * 1.5); - bonuses.push(Emoji.OSBot); - } - if (user.user.minion_hasBought) { loot.bank[COINS_ID] /= 1.5; } @@ -95,9 +83,7 @@ async function reward(user: MUser, triviaCorrect: boolean): CommandResponse { '\n<:skipper:755853421801766912> Skipper has negotiated with Diango and gotten you 50% extra GP from your daily!'; } - if (loot.bank[COINS_ID] > 0) { - updateClientGPTrackSetting('gp_daily', loot.bank[COINS_ID]); - } else { + if (!loot.bank[COINS_ID]) { delete loot.bank[COINS_ID]; } @@ -138,32 +124,5 @@ export async function dailyCommand( {} ); - const [question, ...fakeQuestions] = await getRandomTriviaQuestions(); - - let correctUser: string | null = null; - const buttons = new DynamicButtons({ - channel: channel as TextChannel, - usersWhoCanInteract: [user.id], - deleteAfterConfirm: true - }); - const allAnswers = uniqueArr(shuffleArr([question, ...fakeQuestions].map(q => q.answers[0]))); - for (const answer of allAnswers) { - buttons.add({ - name: answer, - fn: ({ interaction }) => { - if (question.answers.includes(answer)) { - correctUser = interaction.user.id; - } - }, - cantBeBusy: false - }); - } - - await buttons.render({ - messageOptions: { - content: `**${Emoji.Diango} Diango asks ${user.badgedUsername}...** ${question.question}` - }, - isBusy: false - }); - return reward(user, correctUser !== null); + return reward(user, true); } diff --git a/src/mahoji/lib/abstracted_commands/diceCommand.ts b/src/mahoji/lib/abstracted_commands/diceCommand.ts deleted file mode 100644 index ba4137eb5d..0000000000 --- a/src/mahoji/lib/abstracted_commands/diceCommand.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { ChatInputCommandInteraction } from 'discord.js'; -import { Bank, Util } from 'oldschooljs'; - -import { cryptoRand, percentChance } from '../../../lib/util'; -import { deferInteraction } from '../../../lib/util/interactionReply'; -import { - mahojiParseNumber, - updateClientGPTrackSetting, - updateGPTrackSetting, - userStatsUpdate -} from '../../mahojiSettings'; - -export async function diceCommand(user: MUser, interaction: ChatInputCommandInteraction, diceamount?: string) { - await deferInteraction(interaction); - const roll = cryptoRand(1, 100); - const amount = mahojiParseNumber({ input: diceamount, min: 1, max: 500_000_000_000 }); - - if (!diceamount) { - return `You rolled **${roll}** on the percentile dice.`; - } - - if (!amount) { - return `You rolled **${roll}** on the percentile dice.`; - } - if (user.isIronman) return "You're an ironman and you cant play dice."; - - if (amount > 10_000_000_000) { - return 'You can only dice up to 10b at a time!'; - } - - if (amount < 1_000_000) { - return 'You have to dice atleast 1,000,000.'; - } - - const gp = user.GP; - if (amount > gp) return "You don't have enough GP."; - const won = roll >= 55; - const amountToAdd = won ? amount : -amount; - - await updateClientGPTrackSetting('gp_dice', amountToAdd); - await updateGPTrackSetting('gp_dice', amountToAdd, user); - - if (won) { - await userStatsUpdate( - user.id, - { - dice_wins: { increment: 1 } - }, - {} - ); - await user.addItemsToBank({ items: new Bank().add('Coins', amount) }); - } else { - await userStatsUpdate( - user.id, - { - dice_losses: { increment: 1 } - }, - {} - ); - await user.removeItemsFromBank(new Bank().add('Coins', amount)); - } - - if (amount >= 100_000_000 && won && percentChance(3)) { - await user.addItemsToBank({ items: new Bank().add('Gamblers bag'), collectionLog: true }); - return `${user.usernameOrMention} rolled **${roll}** on the percentile dice, and you won ${Util.toKMB( - amountToAdd - )} GP.\n\nYou received a **Gamblers Bag**.`; - } - - return `${user.usernameOrMention} rolled **${roll}** on the percentile dice, and you ${ - won ? 'won' : 'lost' - } ${Util.toKMB(amountToAdd)} GP.`; -} diff --git a/src/mahoji/lib/abstracted_commands/duelCommand.ts b/src/mahoji/lib/abstracted_commands/duelCommand.ts deleted file mode 100644 index 7612279817..0000000000 --- a/src/mahoji/lib/abstracted_commands/duelCommand.ts +++ /dev/null @@ -1,183 +0,0 @@ -import type { MahojiUserOption } from '@oldschoolgg/toolkit'; -import type { ChatInputCommandInteraction } from 'discord.js'; -import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; -import { Time, noOp, sleep } from 'e'; -import { Bank, Util } from 'oldschooljs'; - -import { MUserClass } from '../../../lib/MUser'; -import { BLACKLISTED_USERS } from '../../../lib/blacklists'; -import { Emoji, Events } from '../../../lib/constants'; - -import { awaitMessageComponentInteraction, channelIsSendable } from '../../../lib/util'; -import { deferInteraction } from '../../../lib/util/interactionReply'; -import { mahojiParseNumber, userStatsUpdate } from '../../mahojiSettings'; - -async function checkBal(user: MUser, amount: number) { - return user.GP >= amount; -} - -export async function duelCommand( - user: MUser, - interaction: ChatInputCommandInteraction, - duelUser: MUser, - targetAPIUser: MahojiUserOption, - duelAmount?: string -) { - await deferInteraction(interaction); - - const duelSourceUser = user; - const duelTargetUser = duelUser; - - const amount = mahojiParseNumber({ input: duelAmount, min: 1, max: 500_000_000_000 }); - if (!amount) { - const winner = Math.random() >= 0.5 ? duelSourceUser : duelTargetUser; - return `${winner} won the duel against ${ - winner.id === duelSourceUser.id ? duelTargetUser : duelSourceUser - } with ${Math.floor(Math.random() * 30 + 1)} HP remaining.`; - } - - if (duelSourceUser.isIronman) return "You can't duel someone as an ironman."; - if (duelTargetUser.isIronman) return "You can't duel someone who is an ironman."; - if (duelSourceUser.id === duelTargetUser.id) return 'You cant duel yourself.'; - if (!(duelTargetUser instanceof MUserClass)) return "You didn't mention a user to duel."; - if (BLACKLISTED_USERS.has(duelTargetUser.id)) return 'Target user is blacklisted.'; - if (targetAPIUser.user.bot) return 'You cant duel a bot.'; - - if (!(await checkBal(duelSourceUser, amount))) { - return 'You dont have have enough GP to duel that much.'; - } - - if (!(await checkBal(duelTargetUser, amount))) { - return "That person doesn't have enough GP to duel that much."; - } - - const channel = globalClient.channels.cache.get(interaction.channelId); - if (!channelIsSendable(channel)) throw new Error('Channel for confirmation not found.'); - const duelMessage = await channel.send({ - content: `${duelTargetUser}, do you accept the duel for ${Util.toKMB(amount)} GP?`, - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder({ - label: 'Accept', - style: ButtonStyle.Primary, - customId: 'CONFIRM' - }), - new ButtonBuilder({ - label: 'Decline', - style: ButtonStyle.Secondary, - customId: 'CANCEL' - }) - ]) - ] - }); - - function cancel() { - duelMessage.delete().catch(noOp); - return "Duel cancelled, user didn't accept in time."; - } - - async function confirm(amount: number) { - duelMessage.edit({ components: [] }).catch(noOp); - await duelSourceUser.sync(); - await duelTargetUser.sync(); - if (!(await checkBal(duelSourceUser, amount)) || !(await checkBal(duelTargetUser, amount))) { - duelMessage.delete().catch(noOp); - return 'User appears to be less wealthy than expected (they lost some money before accepting...).'; - } - - const b = new Bank().add('Coins', amount); - await duelSourceUser.removeItemsFromBank(b); - await duelTargetUser.removeItemsFromBank(b); - - await duelMessage - .edit(`${duelTargetUser.badgedUsername} accepted the duel. You both enter the duel arena...`) - .catch(noOp); - - await sleep(2000); - await duelMessage - .edit(`${duelSourceUser.badgedUsername} and ${duelTargetUser.badgedUsername} begin fighting...`) - .catch(noOp); - - const [winner, loser] = - Math.random() > 0.5 ? [duelSourceUser, duelTargetUser] : [duelTargetUser, duelSourceUser]; - - await sleep(2000); - await duelMessage.edit('The fight is almost over...').catch(noOp); - await sleep(2000); - - const winningAmount = amount * 2; - - await userStatsUpdate( - winner.id, - { - duel_wins: { - increment: 1 - } - }, - {} - ); - await userStatsUpdate( - loser.id, - { - duel_losses: { - increment: 1 - } - }, - {} - ); - - await winner.addItemsToBank({ items: new Bank().add('Coins', winningAmount), collectionLog: false }); - await prisma.economyTransaction.create({ - data: { - guild_id: interaction.guildId ? BigInt(interaction.guildId) : null, - sender: BigInt(loser.id), - recipient: BigInt(winner.id), - items_sent: new Bank().add('Coins', Math.floor(amount)).bank, - type: 'duel' - } - }); - - if (amount >= 1_000_000_000) { - globalClient.emit( - Events.ServerNotification, - `${Emoji.MoneyBag} **${winner.badgedUsername}** just won a **${Util.toKMB( - winningAmount - )}** GP duel against ${loser.badgedUsername}.` - ); - } - - globalClient.emit( - Events.EconomyLog, - `${winner.mention} won ${winningAmount} GP in a duel with ${loser.mention}.` - ); - - duelMessage.edit( - `Congratulations ${winner.usernameOrMention}! You won ${Util.toKMB(winningAmount)}, and paid 0 tax.` - ); - - return `Duel finished, ${winner} won.`; - } - - try { - const selection = await awaitMessageComponentInteraction({ - message: duelMessage, - filter: i => { - if (i.user.id !== (duelTargetUser.id ?? interaction.user.id).toString()) { - i.reply({ ephemeral: true, content: 'This is not your confirmation message.' }); - return false; - } - return true; - }, - time: Time.Second * 10 - }); - if (selection.customId === 'CANCEL') { - return cancel(); - } - if (selection.customId === 'CONFIRM') { - return await confirm(amount); - } - } catch (err) { - return cancel(); - } - return cancel(); -} diff --git a/src/mahoji/lib/abstracted_commands/enchantCommand.ts b/src/mahoji/lib/abstracted_commands/enchantCommand.ts index 13aa62ac5d..c69018e707 100644 --- a/src/mahoji/lib/abstracted_commands/enchantCommand.ts +++ b/src/mahoji/lib/abstracted_commands/enchantCommand.ts @@ -10,7 +10,6 @@ import { itemNameFromID } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { determineRunes } from '../../../lib/util/determineRunes'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; export async function enchantCommand(user: MUser, channelID: string, name: string, quantity?: number) { const enchantable = Enchantables.find( @@ -64,8 +63,6 @@ export async function enchantCommand(user: MUser, channelID: string, name: strin } await transactItems({ userID: user.id, itemsToRemove: cost }); - updateBankSetting('magic_cost_bank', cost); - await addSubTaskToActivityTask({ itemID: enchantable.id, userID: user.id, diff --git a/src/mahoji/lib/abstracted_commands/farmingCommand.ts b/src/mahoji/lib/abstracted_commands/farmingCommand.ts index 6f54f8d39f..80629b01f9 100644 --- a/src/mahoji/lib/abstracted_commands/farmingCommand.ts +++ b/src/mahoji/lib/abstracted_commands/farmingCommand.ts @@ -19,7 +19,6 @@ import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { farmingPatchNames, findPlant, isPatchName } from '../../../lib/util/farmingHelpers'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { userHasGracefulEquipped, userStatsBankUpdate } from '../../mahojiSettings'; function treeCheck(plant: Plant, wcLevel: number, bal: number, quantity: number): string | null { @@ -298,7 +297,6 @@ export async function farmingPlantCommand({ if (!user.owns(cost)) return `You don't own ${cost}.`; await transactItems({ userID: user.id, itemsToRemove: cost }); - updateBankSetting('farming_cost_bank', cost); // If user does not have something already planted, just plant the new seeds. if (!patchType.patchPlanted) { infoStr.unshift(`${user.minionName} is now planting ${quantity}x ${plant.name}.`); @@ -315,19 +313,7 @@ export async function farmingPlantCommand({ ); } - const inserted = await prisma.farmedCrop.create({ - data: { - user_id: user.id, - date_planted: new Date(), - item_id: plant.id, - quantity_planted: quantity, - was_autofarmed: autoFarmed, - paid_for_protection: didPay, - upgrade_type: upgradeType - } - }); - - await userStatsBankUpdate(user, 'farming_plant_cost_bank', cost); + await userStatsBankUpdate(user.id, 'farming_plant_cost_bank', cost); await addSubTaskToActivityTask({ plantsName: plant.name, @@ -341,8 +327,7 @@ export async function farmingPlantCommand({ duration, currentDate, type: 'Farming', - autoFarmed, - pid: inserted.id + autoFarmed }); return `${infoStr.join(' ')} diff --git a/src/mahoji/lib/abstracted_commands/fightCavesCommand.ts b/src/mahoji/lib/abstracted_commands/fightCavesCommand.ts index 50691facbf..cb5f95f070 100644 --- a/src/mahoji/lib/abstracted_commands/fightCavesCommand.ts +++ b/src/mahoji/lib/abstracted_commands/fightCavesCommand.ts @@ -10,7 +10,6 @@ import type { FightCavesActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { newChatHeadImage } from '../../../lib/util/chatHeadImage'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; export const fightCavesCost = new Bank({ 'Prayer potion(4)': 10, @@ -161,8 +160,6 @@ export async function fightCavesCommand(user: MUser, channelID: string): Command fakeDuration }); - updateBankSetting('economyStats_fightCavesCost', fightCavesCost); - const totalDeathChance = (((100 - preJadDeathChance) * (100 - jadDeathChance)) / 100).toFixed(1); return { diff --git a/src/mahoji/lib/abstracted_commands/fishingContestCommand.ts b/src/mahoji/lib/abstracted_commands/fishingContestCommand.ts index bcb1f55502..62a6dc4e54 100644 --- a/src/mahoji/lib/abstracted_commands/fishingContestCommand.ts +++ b/src/mahoji/lib/abstracted_commands/fishingContestCommand.ts @@ -8,12 +8,11 @@ import { getUsersFishingContestDetails, getValidLocationsForFishType } from '../../../lib/fishingContest'; -import { trackLoot } from '../../../lib/lootTrack'; + import { getMinigameScore } from '../../../lib/settings/minigames'; import type { FishingContestOptions } from '../../../lib/types/minions'; import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; export async function fishingContestStartCommand(user: MUser, channelID: string, loc: string | undefined) { const currentFishType = getCurrentFishType(); @@ -76,7 +75,6 @@ export async function fishingContestStartCommand(user: MUser, channelID: string, return `You need ${cost} to bait fish at ${fishingLocation.name}.`; } await user.removeItemsFromBank(cost); - await updateBankSetting('fc_cost', cost); await addSubTaskToActivityTask({ userID: user.id, @@ -88,19 +86,6 @@ export async function fishingContestStartCommand(user: MUser, channelID: string, location: fishingLocation.id }); - await trackLoot({ - totalCost: cost, - id: 'fishing_contest', - type: 'Minigame', - changeType: 'cost', - users: [ - { - id: user.id, - cost - } - ] - }); - return { content: `${user.minionName} is now off to catch ${quantity === 1 ? 'a' : quantity} fish at ${ fishingLocation.name diff --git a/src/mahoji/lib/abstracted_commands/gearCommands.ts b/src/mahoji/lib/abstracted_commands/gearCommands.ts index 3cb1fb6c54..be14de4c59 100644 --- a/src/mahoji/lib/abstracted_commands/gearCommands.ts +++ b/src/mahoji/lib/abstracted_commands/gearCommands.ts @@ -1,194 +1,43 @@ -import { PerkTier, toTitleCase } from '@oldschoolgg/toolkit'; +import { toTitleCase } from '@oldschoolgg/toolkit'; import type { CommandResponse } from '@oldschoolgg/toolkit'; -import type { GearPreset } from '@prisma/client'; import type { ChatInputCommandInteraction } from 'discord.js'; -import { objectValues } from 'e'; import { Bank } from 'oldschooljs'; -import { MAX_INT_JAVA, PATRON_ONLY_GEAR_SETUP } from '../../../lib/constants'; +import { MAX_INT_JAVA } from '../../../lib/constants'; import { generateAllGearImage, generateGearImage } from '../../../lib/gear/functions/generateGearImage'; import type { GearSetup, GearSetupType } from '../../../lib/gear/types'; import { GearStat } from '../../../lib/gear/types'; import getUserBestGearFromBank from '../../../lib/minions/functions/getUserBestGearFromBank'; import { unEquipAllCommand } from '../../../lib/minions/functions/unequipAllCommand'; - -import { Gear, defaultGear, globalPresets } from '../../../lib/structures/Gear'; +import { Gear, defaultGear } from '../../../lib/structures/Gear'; import { assert, formatSkillRequirements, isValidGearSetup, stringMatches } from '../../../lib/util'; import calculateGearLostOnDeathWilderness from '../../../lib/util/calculateGearLostOnDeathWilderness'; -import { gearEquipMultiImpl } from '../../../lib/util/equipMulti'; import { getItem } from '../../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; import { minionIsBusy } from '../../../lib/util/minionIsBusy'; import { mahojiParseNumber } from '../../mahojiSettings'; -async function gearPresetEquipCommand(user: MUser, gearSetup: string, presetName: string): CommandResponse { - if (user.minionIsBusy) { - return `${user.minionName} is currently out on a trip, so you can't change their gear!`; - } - - if (!presetName) return "You didn't supply a preset name."; - if (!gearSetup) return "You didn't supply a setup."; - - if (!isValidGearSetup(gearSetup)) { - return "That's not a valid gear setup."; - } - - const userPreset = await prisma.gearPreset.findFirst({ where: { user_id: user.id, name: presetName } }); - const globalPreset = globalPresets.find(i => i.name === presetName); - if (!userPreset && !globalPreset) { - return "You don't have a gear preset with that name."; - } - const preset = (userPreset ?? globalPreset) as GearPreset; - if (preset.two_handed !== null) { - preset.weapon = null; - preset.shield = null; - } - - // Checks the preset to make sure the user has the required stats for every item in the preset - for (const gearItemId of Object.values(preset)) { - if (gearItemId !== null) { - const itemToEquip = getItem(gearItemId); - if (itemToEquip?.equipment?.requirements && !user.hasSkillReqs(itemToEquip.equipment.requirements)) { - return `You can't equip this preset because ${ - itemToEquip.name - } requires these stats: ${formatSkillRequirements(itemToEquip.equipment.requirements)}.`; - } - } - } - - const toRemove = new Bank(); - function gearItem(val: null | number) { - if (val === null) return null; - toRemove.add(val); - return { - item: val, - quantity: 1 - }; - } - - const newGear = { ...defaultGear }; - newGear.head = gearItem(preset.head); - newGear.neck = gearItem(preset.neck); - newGear.body = gearItem(preset.body); - newGear.legs = gearItem(preset.legs); - newGear.cape = gearItem(preset.cape); - newGear['2h'] = gearItem(preset.two_handed); - newGear.hands = gearItem(preset.hands); - newGear.feet = gearItem(preset.feet); - newGear.shield = gearItem(preset.shield); - newGear.weapon = gearItem(preset.weapon); - newGear.ring = gearItem(preset.ring); - - if (preset.ammo) { - newGear.ammo = { item: preset.ammo, quantity: preset.ammo_qty! }; - toRemove.add(preset.ammo, preset.ammo_qty!); - } - - const userBankWithEquippedItems = user.bank.clone(); - for (const e of objectValues(user.gear[gearSetup].raw())) { - if (e) userBankWithEquippedItems.add(e.item, Math.max(e.quantity, 1)); - } - - if (!userBankWithEquippedItems.has(toRemove.bank)) { - return `You don't have the items in this preset. You're missing: ${toRemove.remove(user.bank)}.`; - } - - await unEquipAllCommand(user.id, gearSetup); - - await user.removeItemsFromBank(toRemove); - - await user.update({ - [`gear_${gearSetup}`]: newGear - }); - const updatedGear = user.gear[gearSetup]; - const image = await generateGearImage(user, updatedGear, gearSetup, user.user.minion_equippedPet); - - return { - content: `You equipped the ${preset.name} preset in your ${gearSetup} setup.`, - files: [{ name: 'gear.jpg', attachment: image }] - }; -} - -async function gearEquipMultiCommand( - user: MUser, - interaction: ChatInputCommandInteraction, - setup: string, - items: string -) { - if (!isValidGearSetup(setup)) return 'Invalid gear setup.'; - if (setup === 'wildy') { - await handleMahojiConfirmation( - interaction, - "You're trying to equip items into your *wildy* setup. ANY item in this setup can potentially be lost if doing Wilderness activities. Please confirm you understand this." - ); - } - - // We must update the user after any confirmation because the bank/gear could change from something else. - await user.sync(); - const { - success: resultSuccess, - failMsg, - skillFailBank, - equippedGear, - equipBank, - unequipBank - } = gearEquipMultiImpl(user, setup, items); - if (!resultSuccess) return failMsg!; - - const dbKey = `gear_${setup}` as const; - const { newUser } = await user.update({ - [dbKey]: equippedGear - }); - await transactItems({ - userID: user.id, - filterLoot: false, - itemsToRemove: equipBank, - itemsToAdd: unequipBank - }); - - const image = await generateGearImage(user, newUser[dbKey] as GearSetup, setup, user.user.minion_equippedPet); - let content = `You equipped ${equipBank} on your ${setup} setup, and unequipped ${unequipBank}.`; - if (skillFailBank!.length > 0) { - content += `\nThese items failed to be equipped as you don't have the requirements: ${skillFailBank}.`; - } - return { - content, - files: [{ name: 'gear.jpg', attachment: image }] - }; -} - export async function gearEquipCommand(args: { interaction: ChatInputCommandInteraction; userID: string; setup: string; item: string | undefined; - items: string | undefined; - preset: string | undefined; quantity: number | undefined; unEquippedItem: Bank | undefined; auto: string | undefined; }): CommandResponse { - const { interaction, userID, setup, item, items, preset, quantity: _quantity, auto } = args; + const { interaction, userID, setup, item, quantity: _quantity, auto } = args; if (!isValidGearSetup(setup)) return 'Invalid gear setup.'; const user = await mUserFetch(userID); if (minionIsBusy(user.id)) { return `${user.minionName} is currently out on a trip, so you can't change their gear!`; } - if (items) { - return gearEquipMultiCommand(user, interaction, setup, items); - } - if (setup === 'other' && user.perkTier() < PerkTier.Four) { - return PATRON_ONLY_GEAR_SETUP; - } - if (preset) { - return gearPresetEquipCommand(user, setup, preset); - } if (auto) { return autoEquipCommand(user, setup, auto); } const itemToEquip = getItem(item); - if (!itemToEquip) return "You didn't supply the name of an item or preset you want to equip."; + if (!itemToEquip) return "You didn't supply the name of an item you want to equip."; const quantity = mahojiParseNumber({ input: _quantity ?? 1, min: 1, max: MAX_INT_JAVA }) ?? 1; if (!itemToEquip.equipable_by_player || !itemToEquip.equipment) return "This item isn't equipable."; @@ -275,11 +124,11 @@ export async function gearUnequipCommand( const newGear = { ...currentGear }; newGear[slot] = null; - await user.addItemsToBank({ - items: { - [equippedInThisSlot!.item]: equippedInThisSlot!.quantity - }, - collectionLog: false + const loot = new Bank().add(equippedInThisSlot!.item, equippedInThisSlot!.quantity); + await user.transactItems({ + itemsToAdd: loot, + collectionLog: false, + shouldRemap: false }); await user.update({ [`gear_${gearSetup}`]: newGear @@ -294,10 +143,6 @@ export async function gearUnequipCommand( } async function autoEquipCommand(user: MUser, gearSetup: GearSetupType, equipmentType: string): CommandResponse { - if (gearSetup === 'other' && user.perkTier() < PerkTier.Four) { - return PATRON_ONLY_GEAR_SETUP; - } - if (!Object.values(GearStat).includes(equipmentType as any)) { return 'Invalid gear stat.'; } @@ -435,10 +280,6 @@ export async function gearSwapCommand( ); } - if ([first, second].includes('other') && user.perkTier() < PerkTier.Four) { - return PATRON_ONLY_GEAR_SETUP; - } - const { gear } = user; await user.update({ diff --git a/src/mahoji/lib/abstracted_commands/giantsFoundryCommand.ts b/src/mahoji/lib/abstracted_commands/giantsFoundryCommand.ts index 7648cc6db1..7709bd8d1b 100644 --- a/src/mahoji/lib/abstracted_commands/giantsFoundryCommand.ts +++ b/src/mahoji/lib/abstracted_commands/giantsFoundryCommand.ts @@ -3,7 +3,7 @@ import { Bank } from 'oldschooljs'; import type { ChatInputCommandInteraction } from 'discord.js'; import { Time, calcWhatPercent, reduceNumByPercent } from 'e'; import { TOTAL_GIANT_WEAPONS } from '../../../lib/giantsFoundry'; -import { trackLoot } from '../../../lib/lootTrack'; + import { getMinigameEntity } from '../../../lib/settings/minigames'; import Smithing from '../../../lib/skilling/skills/smithing'; import { SkillsEnum } from '../../../lib/skilling/types'; @@ -12,8 +12,7 @@ import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; -import { userStatsBankUpdate, userStatsUpdate } from '../../mahojiSettings'; +import { userStatsUpdate } from '../../mahojiSettings'; import type { GiantsFoundryBank } from './../../../lib/giantsFoundry'; export const giantsFoundryAlloys = [ @@ -225,20 +224,6 @@ export async function giantsFoundryStartCommand( } await user.removeItemsFromBank(totalCost); - updateBankSetting('gf_cost', totalCost); - await trackLoot({ - id: 'giants_foundry', - type: 'Minigame', - totalCost, - changeType: 'cost', - users: [ - { - id: user.id, - cost: totalCost - } - ] - }); - await userStatsBankUpdate(user, 'gf_cost', totalCost); await addSubTaskToActivityTask({ quantity, diff --git a/src/mahoji/lib/abstracted_commands/gnomeRestaurantCommand.ts b/src/mahoji/lib/abstracted_commands/gnomeRestaurantCommand.ts index ffe7ac84fd..4af994d532 100644 --- a/src/mahoji/lib/abstracted_commands/gnomeRestaurantCommand.ts +++ b/src/mahoji/lib/abstracted_commands/gnomeRestaurantCommand.ts @@ -9,7 +9,6 @@ import type { GnomeRestaurantActivityTaskOptions } from '../../../lib/types/mini import { formatDuration, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { userHasGracefulEquipped } from '../../mahojiSettings'; import { getPOH } from './pohCommand'; @@ -103,7 +102,6 @@ export async function gnomeRestaurantCommand(user: MUser, channelID: string) { await user.removeItemsFromBank(itemsToRemove); - await updateBankSetting('gnome_res_cost', itemsToRemove); await addSubTaskToActivityTask({ userID: user.id, channelID: channelID.toString(), diff --git a/src/mahoji/lib/abstracted_commands/guardiansOfTheRiftCommand.ts b/src/mahoji/lib/abstracted_commands/guardiansOfTheRiftCommand.ts index eecc0116ce..d24d19da69 100644 --- a/src/mahoji/lib/abstracted_commands/guardiansOfTheRiftCommand.ts +++ b/src/mahoji/lib/abstracted_commands/guardiansOfTheRiftCommand.ts @@ -1,7 +1,6 @@ import { Time } from 'e'; import { Bank } from 'oldschooljs'; -import { trackLoot } from '../../../lib/lootTrack'; import { pickaxes, varrockArmours } from '../../../lib/skilling/functions/miningBoosts'; import Runecraft from '../../../lib/skilling/skills/runecraft'; import { SkillsEnum } from '../../../lib/skilling/types'; @@ -10,7 +9,6 @@ import { formatDuration, itemID, itemNameFromID, randomVariation } from '../../. import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { determineRunes } from '../../../lib/util/determineRunes'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { userHasGracefulEquipped } from '../../mahojiSettings'; export async function guardiansOfTheRiftStartCommand( @@ -157,19 +155,6 @@ export async function guardiansOfTheRiftStartCommand( rolls += 2; boosts.push('Extra 2 rolls for Combination runecrafting'); await user.removeItemsFromBank(removeRunesAndNecks); - updateBankSetting('gotr_cost', removeRunesAndNecks); - await trackLoot({ - id: 'guardians_of_the_rift', - type: 'Minigame', - totalCost: removeRunesAndNecks, - changeType: 'cost', - users: [ - { - id: user.id, - cost: removeRunesAndNecks - } - ] - }); } // 5.5 rolls, 120 is average mined essences, 14 is averge created guardians/barriers per game at max efficiency diff --git a/src/mahoji/lib/abstracted_commands/hotColdCommand.ts b/src/mahoji/lib/abstracted_commands/hotColdCommand.ts deleted file mode 100644 index b7ecc647ba..0000000000 --- a/src/mahoji/lib/abstracted_commands/hotColdCommand.ts +++ /dev/null @@ -1,146 +0,0 @@ -import type { CommandResponse } from '@oldschoolgg/toolkit'; -import type { ChatInputCommandInteraction } from 'discord.js'; -import { EmbedBuilder } from 'discord.js'; -import { LootTable } from 'oldschooljs'; -import { toKMB } from 'oldschooljs/dist/util'; - -import { resolveItems } from 'oldschooljs/dist/util/util'; -import { mahojiClientSettingsUpdate } from '../../../lib/util/clientSettings'; -import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; -import { mahojiParseNumber, userStatsUpdate } from '../../mahojiSettings'; - -export const flowerTable = new LootTable() - .add('Red flowers', 1, 150) - .add('Yellow flowers', 1, 150) - .add('Orange flowers', 1, 150) - - .add('Blue flowers', 1, 150) - .add('Purple flowers', 1, 150) - .add('Assorted flowers', 1, 150) - - .add('Mixed flowers', 1, 150) - - .add('Black flowers', 1, 2) - .add('White flowers', 1, 1); - -const hot = resolveItems(['Red flowers', 'Yellow flowers', 'Orange flowers']); -const cold = resolveItems(['Blue flowers', 'Purple flowers', 'Assorted flowers']); -const blackAndWhite = resolveItems(['Black flowers', 'White flowers']); - -const explanation = - "Hot and Cold Rules: You pick hot (red, yellow, orange) or cold (purple, blue, assorted), and if you guess right, you win. If it's mixed, you lose. If its black or white, you win **5x** your bet."; - -export async function hotColdCommand( - interaction: ChatInputCommandInteraction, - user: MUser, - choice: 'hot' | 'cold' | undefined, - _amount: string | undefined -) { - if (user.isIronman) return 'Ironmen cannot gamble.'; - const amount = mahojiParseNumber({ input: _amount, min: 1 }); - if (!amount || !choice || !['hot', 'cold'].includes(choice) || !Number.isInteger(amount)) return explanation; - if (amount < 10_000_000 || amount > 500_000_000) return 'You must gamble between 10m and 500m.'; - if (user.GP < amount) return "You can't afford to gamble that much."; - const flowerLoot = flowerTable.roll(); - const flower = flowerLoot.items()[0][0]; - - await handleMahojiConfirmation( - interaction, - `Are you sure you want to gamble ${toKMB(amount)}? You might lose it all, you might win a lot. -${explanation}` - ); - - await user.sync(); - if (user.GP < amount) return "You can't afford to gamble that much."; - await transactItems({ - userID: user.id, - itemsToAdd: flowerLoot, - collectionLog: true - }); - - const embed = new EmbedBuilder() - .setTitle(`You picked ${choice} and got '${flower.name}'!`) - .setThumbnail(`https://chisel.weirdgloop.org/static/img/osrs-sprite/${flower.id}.png`) - .setFooter({ - text: `You received ${flowerLoot}` - }); - const response: Awaited = { - embeds: [embed.data] - }; - - // You get 5x if you roll a black/white flower - if (blackAndWhite.includes(flower.id)) { - const amountWon = amount * 5; - await user.update({ - GP: { - increment: amountWon - } - }); - await userStatsUpdate( - user.id, - { - gp_hotcold: { - increment: amountWon - } - }, - {} - ); - await mahojiClientSettingsUpdate({ - gp_hotcold: { - decrement: amountWon - } - }); - embed - .setDescription( - `You rolled a special flower, and received 5x of your bet! You received ${toKMB(amountWon)}` - ) - .setColor(6_875_960); - return response; - } - - await user.update({ - GP: { - decrement: amount - } - }); - const arrToCheck = choice === 'hot' ? hot : cold; - const playerDidWin = flower.name !== 'Mixed flowers' && arrToCheck.includes(flower.id); - const key = playerDidWin ? 'increment' : 'decrement'; - await mahojiClientSettingsUpdate({ - gp_hotcold: { - [key]: amount - } - }); - - if (playerDidWin) { - const amountWon = amount * 2; - await user.update({ - GP: { - increment: amount * 2 - } - }); - await userStatsUpdate( - user.id, - { - gp_hotcold: { - increment: amount - } - }, - {} - ); - embed.setDescription(`You **won** ${toKMB(amountWon)}!`).setColor(6_875_960); - return response; - } - await userStatsUpdate( - user.id, - { - gp_hotcold: { - decrement: amount - } - }, - {} - ); - - embed.setDescription(`You lost ${toKMB(amount)}.`).setColor(15_417_396); - return response; -} diff --git a/src/mahoji/lib/abstracted_commands/igneCommand.ts b/src/mahoji/lib/abstracted_commands/igneCommand.ts index 4b5b8a3927..518a265d93 100644 --- a/src/mahoji/lib/abstracted_commands/igneCommand.ts +++ b/src/mahoji/lib/abstracted_commands/igneCommand.ts @@ -74,7 +74,6 @@ export async function igneCommand( mostImportantStat: 'attack_crush', ignoreStats: ['attack_ranged', 'attack_magic'], food: () => new Bank(), - settingsKeys: ['ignecarus_cost', 'ignecarus_loot'], channel, activity: 'Ignecarus', massText: `${user.usernameOrMention} is assembling a team to fight Ignecarus! Use the buttons below to join/leave.`, diff --git a/src/mahoji/lib/abstracted_commands/infernoCommand.ts b/src/mahoji/lib/abstracted_commands/infernoCommand.ts index cb1251b620..69dcfffbae 100644 --- a/src/mahoji/lib/abstracted_commands/infernoCommand.ts +++ b/src/mahoji/lib/abstracted_commands/infernoCommand.ts @@ -22,7 +22,6 @@ import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask import { newChatHeadImage } from '../../../lib/util/chatHeadImage'; import getOSItem from '../../../lib/util/getOSItem'; import resolveItems from '../../../lib/util/resolveItems'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; const minimumRangeItems = [ 'Amulet of fury', @@ -688,7 +687,6 @@ export async function infernoStartCommand(user: MUser, channelID: string, emerge diedEmergedZuk }); - updateBankSetting('inferno_cost', realCost); const emergedZukDeathMsg = emerged ? `**Emerged Zuk Death Chance:** ${emergedZukDeathChance.value.toFixed( 1 diff --git a/src/mahoji/lib/abstracted_commands/ironmanCommand.ts b/src/mahoji/lib/abstracted_commands/ironmanCommand.ts deleted file mode 100644 index b61a157ad4..0000000000 --- a/src/mahoji/lib/abstracted_commands/ironmanCommand.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { mentionCommand } from '@oldschoolgg/toolkit'; -import type { Prisma } from '@prisma/client'; -import type { ChatInputCommandInteraction } from 'discord.js'; -import type { ItemBank } from 'oldschooljs/dist/meta/types'; - -import { BitField } from '../../../lib/constants'; -import { roboChimpUserFetch } from '../../../lib/roboChimp'; - -import { assert } from '../../../lib/util'; -import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; -import { minionIsBusy } from '../../../lib/util/minionIsBusy'; - -export async function ironmanCommand(user: MUser, interaction: ChatInputCommandInteraction | null) { - if (minionIsBusy(user.id)) return 'Your minion is busy.'; - if (user.isIronman) { - return 'You are already an ironman.'; - } - - const existingGiveaways = await prisma.giveaway.findMany({ - where: { - user_id: user.id, - completed: false - } - }); - - if (existingGiveaways.length !== 0) { - return "You can't become an ironman because you have active giveaways."; - } - - const bingos = await prisma.bingo.count({ - where: { - creator_id: user.id - } - }); - - if (bingos !== 0) { - return "You can't become an ironman because you have active bingos."; - } - - const activeListings = await prisma.gEListing.findMany({ - where: { - user_id: user.id, - quantity_remaining: { - gt: 0 - }, - fulfilled_at: null, - cancelled_at: null - }, - include: { - buyTransactions: true, - sellTransactions: true - }, - orderBy: { - created_at: 'desc' - } - }); - // Return early if no active listings. - if (activeListings.length !== 0) { - return `You can't become an ironman because you have active Grand Exchange listings. Cancel them and try again: ${mentionCommand( - globalClient, - 'ge', - 'cancel' - )}`; - } - - if (interaction) { - await handleMahojiConfirmation( - interaction, - `Are you sure you want to start over and play as an ironman? -:warning: **Read the following text before confirming. This is your only warning. ** :warning: -The following things will be COMPLETELY reset/wiped from your account, with no chance of being recovered: Your entire bank, collection log, GP/Coins, QP/Quest Points, Clue Scores, Monster Scores, all XP. If you type \`confirm\`, they will all be wiped. -After becoming an ironman: - - You will no longer be able to receive GP from \`=daily\` - - You will no longer be able to use \`=pay\`, \`=duel\`, \`=sellto\`, \`=sell\`, \`=dice\`, \`=gri\` - - You **cannot** de-iron, it is PERMANENT. - - Your entire BSO account, EVERYTHING, will be reset. -Type \`confirm permanent ironman\` if you understand the above information, and want to become an ironman now.` - ); - } - - const mUser = (await mUserFetch(user.id)).user; - - type KeysThatArentReset = - | 'bank_bg_hex' - | 'bank_sort_weightings' - | 'bank_sort_method' - | 'minion_bought_date' - | 'id' - | 'pets' - | 'RSN' - | 'bitfield'; - - const bitFieldsToKeep: BitField[] = [ - BitField.IsPatronTier1, - BitField.IsPatronTier2, - BitField.IsPatronTier3, - BitField.IsPatronTier4, - BitField.IsPatronTier5, - BitField.isModerator, - BitField.isContributor, - BitField.BypassAgeRestriction, - BitField.HasPermanentEventBackgrounds, - BitField.HasPermanentTierOne, - BitField.DisabledRandomEvents, - BitField.AlwaysSmallBank, - BitField.IsWikiContributor, - BitField.IsPatronTier6 - ]; - - const createOptions: Required> = { - id: user.id, - bank_bg_hex: mUser.bank_bg_hex, - bank_sort_method: mUser.bank_sort_method, - bank_sort_weightings: mUser.bank_sort_weightings as ItemBank, - minion_bought_date: mUser.minion_bought_date, - RSN: mUser.RSN, - pets: mUser.pets as ItemBank, - bitfield: bitFieldsToKeep.filter(i => user.bitfield.includes(i)) - }; - - // Delete tables with foreign keys first: - await prisma.historicalData.deleteMany({ where: { user_id: user.id } }); - await prisma.botItemSell.deleteMany({ where: { user_id: user.id } }); - await prisma.pinnedTrip.deleteMany({ where: { user_id: user.id } }); - await prisma.farmedCrop.deleteMany({ where: { user_id: user.id } }); - await prisma.portent.deleteMany({ where: { user_id: user.id } }); - // Now we can delete the user - await prisma.user.deleteMany({ - where: { id: user.id } - }); - await prisma.user.create({ - data: createOptions - }); - await prisma.slayerTask.deleteMany({ where: { user_id: user.id } }); - await prisma.playerOwnedHouse.deleteMany({ where: { user_id: user.id } }); - await prisma.minigame.deleteMany({ where: { user_id: user.id } }); - await prisma.xPGain.deleteMany({ where: { user_id: BigInt(user.id) } }); - await prisma.newUser.deleteMany({ where: { id: user.id } }); - await prisma.activity.deleteMany({ where: { user_id: BigInt(user.id) } }); - await prisma.stashUnit.deleteMany({ where: { user_id: BigInt(user.id) } }); - await prisma.userEvent.deleteMany({ where: { user_id: user.id } }); - await prisma.userStats.deleteMany({ where: { user_id: BigInt(user.id) } }); - await prisma.tameActivity.deleteMany({ where: { user_id: user.id } }); - await prisma.tame.deleteMany({ where: { user_id: user.id } }); - await prisma.fishingContestCatch.deleteMany({ where: { user_id: BigInt(user.id) } }); - await prisma.buyCommandTransaction.deleteMany({ where: { user_id: BigInt(user.id) } }); - - // Refund the leagues points they spent - const roboChimpUser = await roboChimpUserFetch(user.id); - if (roboChimpUser.leagues_points_total >= 0) { - await roboChimpClient.user.update({ - where: { - id: BigInt(user.id) - }, - data: { - leagues_points_balance_bso: roboChimpUser.leagues_points_balance_bso - } - }); - } - - const { newUser } = await user.update({ - minion_ironman: true, - minion_hasBought: true - }); - assert(!newUser.GP && !newUser.QP && !newUser.skills_woodcutting, `Ironman sanity check - ID: ${newUser.id}`); - return 'You are now an ironman.'; -} diff --git a/src/mahoji/lib/abstracted_commands/kgCommand.ts b/src/mahoji/lib/abstracted_commands/kgCommand.ts index e8d72bdd05..e23961f303 100644 --- a/src/mahoji/lib/abstracted_commands/kgCommand.ts +++ b/src/mahoji/lib/abstracted_commands/kgCommand.ts @@ -62,7 +62,6 @@ export async function kgCommand( mostImportantStat: 'attack_slash', ignoreStats: ['attack_ranged', 'attack_magic'], food: () => new Bank(), - settingsKeys: ['kg_cost', 'kg_loot'], channel, activity: 'KingGoldemar', massText: `${user.usernameOrMention} is assembling a team to fight King Goldemar! Use the buttons below to join/leave.`, diff --git a/src/mahoji/lib/abstracted_commands/kkCommand.ts b/src/mahoji/lib/abstracted_commands/kkCommand.ts index c1f6a84135..3611e45dc3 100644 --- a/src/mahoji/lib/abstracted_commands/kkCommand.ts +++ b/src/mahoji/lib/abstracted_commands/kkCommand.ts @@ -4,7 +4,7 @@ import { Bank } from 'oldschooljs'; import { calcBossFood } from '../../../lib/bso/calcBossFood'; import { gorajanWarriorOutfit, torvaOutfit } from '../../../lib/data/CollectionsExport'; -import { trackLoot } from '../../../lib/lootTrack'; + import { KalphiteKingMonster } from '../../../lib/minions/data/killableMonsters/custom/bosses/KalphiteKing'; import { calculateMonsterFood } from '../../../lib/minions/functions'; import type { KillableMonster } from '../../../lib/minions/types'; @@ -17,7 +17,6 @@ import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask import calcDurQty from '../../../lib/util/calcMassDurationQuantity'; import { getKalphiteKingGearStats } from '../../../lib/util/getKalphiteKingGearStats'; import { deferInteraction } from '../../../lib/util/interactionReply'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { hasMonsterRequirements } from '../../mahojiSettings'; async function checkReqs(users: MUser[], monster: KillableMonster, quantity: number): Promise { @@ -299,17 +298,6 @@ export async function kkCommand( foodString += `${foodRemoved.join(', ')}.`; - await trackLoot({ - changeType: 'cost', - totalCost, - id: KalphiteKingMonster.name, - type: 'Monster', - users: removeResult.map(i => ({ - id: i.id, - cost: i.cost - })) - }); - await addSubTaskToActivityTask({ userID: user.id, channelID: channelID.toString(), @@ -319,8 +307,6 @@ export async function kkCommand( users: users.map(u => u.id) }); - updateBankSetting('kk_cost', totalCost); - let str = `${partyOptions.leader.usernameOrMention}'s party (${users .map(u => u.usernameOrMention) .join(', ')}) is now off to kill ${quantity}x ${KalphiteKingMonster.name}. Each kill takes ${formatDuration( diff --git a/src/mahoji/lib/abstracted_commands/lampCommand.ts b/src/mahoji/lib/abstracted_commands/lampCommand.ts deleted file mode 100644 index eb81771dab..0000000000 --- a/src/mahoji/lib/abstracted_commands/lampCommand.ts +++ /dev/null @@ -1,323 +0,0 @@ -import { clamp, objectValues } from 'e'; -import { Bank } from 'oldschooljs'; - -import type { Item } from 'oldschooljs/dist/meta/types'; -import { SkillsEnum } from '../../../lib/skilling/types'; -import type { ItemBank, Skills } from '../../../lib/types'; -import { assert, isValidSkill, itemID } from '../../../lib/util'; -import { getItem } from '../../../lib/util/getOSItem'; -import resolveItems from '../../../lib/util/resolveItems'; -import { userStatsUpdate } from '../../mahojiSettings'; - -interface IXPLamp { - itemID: number; - amount: number; - name: string; - minimumLevel: number; - allowedSkills?: SkillsEnum[]; -} - -export const XPLamps: IXPLamp[] = [ - { - itemID: 11_137, - amount: 2500, - name: 'Antique lamp 1', - minimumLevel: 1 - }, - { - itemID: 11_139, - amount: 7500, - name: 'Antique lamp 2', - minimumLevel: 30 - }, - { - itemID: 11_141, - amount: 15_000, - name: 'Antique lamp 3', - minimumLevel: 40 - }, - { - itemID: 11_185, - amount: 50_000, - name: 'Antique lamp 4', - minimumLevel: 70 - }, - { - itemID: 28_409, - amount: 100_000, - name: 'Ancient lamp', - minimumLevel: 60, - allowedSkills: [ - SkillsEnum.Attack, - SkillsEnum.Strength, - SkillsEnum.Defence, - SkillsEnum.Hitpoints, - SkillsEnum.Ranged, - SkillsEnum.Magic, - SkillsEnum.Prayer - ] - }, - // BSO Lamps - { - itemID: 6796, - amount: 20_000, - name: 'Tiny lamp', - minimumLevel: 1 - }, - { - itemID: 21_642, - amount: 50_000, - name: 'Small lamp', - minimumLevel: 1 - }, - { - itemID: 23_516, - amount: 100_000, - name: 'Average lamp', - minimumLevel: 1 - }, - { - itemID: 22_320, - amount: 1_000_000, - name: 'Large lamp', - minimumLevel: 1 - }, - { - itemID: 11_157, - amount: 5_000_000, - name: 'Huge lamp', - minimumLevel: 1 - }, - { - itemID: 28_587, - amount: 30_000, - name: 'Magic lamp (strength)', - minimumLevel: 1, - allowedSkills: [SkillsEnum.Strength] - }, - { - itemID: 28_588, - amount: 20_000, - name: 'Magic lamp (slayer)', - minimumLevel: 1, - allowedSkills: [SkillsEnum.Slayer] - }, - { - itemID: 28_589, - amount: 5000, - name: 'Magic lamp (thieving)', - minimumLevel: 1, - allowedSkills: [SkillsEnum.Thieving] - }, - { - itemID: 28_590, - amount: 500, - name: 'Magic lamp (magic)', - minimumLevel: 1, - allowedSkills: [SkillsEnum.Magic] - }, - /* Needs OSJS Update - { - itemID: 28_820, - amount: 5000, - name: 'Antique lamp (defender of varrock)', - minimumLevel: 1 - },*/ - { - itemID: itemID('Antique lamp (easy ca)'), - amount: 5000, - name: 'Antique lamp (easy ca)', - minimumLevel: 20 - }, - { - itemID: itemID('Antique lamp (medium ca)'), - amount: 10_000, - name: 'Antique lamp (medium ca)', - minimumLevel: 30 - }, - { - itemID: itemID('Antique lamp (hard ca)'), - amount: 15_000, - name: 'Antique lamp (hard ca)', - minimumLevel: 40 - }, - { - itemID: itemID('Antique lamp (elite ca)'), - amount: 25_000, - name: 'Antique lamp (elite ca)', - minimumLevel: 50 - }, - { - itemID: itemID('Antique lamp (master ca)'), - amount: 35_000, - name: 'Antique lamp (master ca)', - minimumLevel: 60 - }, - { - itemID: itemID('Antique lamp (grandmaster ca)'), - amount: 50_000, - name: 'Antique lamp (grandmaster ca)', - minimumLevel: 70 - } -]; - -interface IFunctionData { - user: MUser; - item: Item; - quantity: number; -} - -interface IXPObject { - items: number[]; - function: (data: IFunctionData) => [Skills, Skills | undefined]; -} - -export const Lampables: IXPObject[] = [ - { - items: resolveItems(['Dark relic']), - function: data => { - const skills: Skills = {}; - for (const skill of objectValues(SkillsEnum)) { - skills[skill] = - data.user.skillLevel(skill) * - ([ - SkillsEnum.Mining, - SkillsEnum.Woodcutting, - SkillsEnum.Herblore, - SkillsEnum.Farming, - SkillsEnum.Hunter, - SkillsEnum.Cooking, - SkillsEnum.Fishing, - SkillsEnum.Thieving, - SkillsEnum.Firemaking, - SkillsEnum.Agility, - SkillsEnum.Dungeoneering - ].includes(skill) - ? 150 - : 50) * - data.quantity; - } - return [skills, undefined]; - } - }, - { - items: resolveItems(['Genie lamp']), - function: data => { - const skills: Skills = {}; - for (const skill of objectValues(SkillsEnum)) { - skills[skill] = data.user.skillLevel(skill) * 10 * data.quantity; - } - return [skills, undefined]; - } - }, - { - items: resolveItems(['Book of knowledge']), - function: data => { - const skills: Skills = {}; - for (const skill of objectValues(SkillsEnum)) { - skills[skill] = data.user.skillLevel(skill) * 15 * data.quantity; - } - return [skills, undefined]; - } - }, - { - items: XPLamps.map(i => i.itemID), - function: data => { - const lamp = XPLamps.find(l => l.itemID === data.item.id)!; - const skills: Skills = {}; - const requirements: Skills = {}; - for (const skill of objectValues(SkillsEnum)) { - if (lamp.allowedSkills && !lamp.allowedSkills.includes(skill)) continue; - skills[skill] = lamp.amount * data.quantity; - requirements[skill] = lamp.minimumLevel; - } - return [skills, requirements]; - } - }, - { - items: resolveItems(['Book of arcane knowledge']), - function: data => { - const skills: Skills = {}; - for (const skill of objectValues(SkillsEnum)) { - if (skill !== SkillsEnum.Magic && skill !== SkillsEnum.Runecraft) { - continue; - } - skills[skill] = - data.user.skillLevel(skill) * ([SkillsEnum.Magic].includes(skill) ? 11 : 4) * data.quantity; - } - return [skills, undefined]; - } - }, - { - items: resolveItems(['Training manual']), - function: data => { - const skills: Skills = {}; - for (const skill of objectValues(SkillsEnum)) { - if ( - ![SkillsEnum.Attack, SkillsEnum.Strength, SkillsEnum.Defence, SkillsEnum.Hitpoints].includes(skill) - ) { - continue; - } - skills[skill] = - Math.round(Number(Math.pow(data.user.skillLevel(skill), 2)) / 4 + 7 * data.user.skillLevel(skill)) * - data.quantity; - } - return [skills, undefined]; - } - } -]; - -export async function lampCommand(user: MUser, itemToUse: string, skill: string, _quantity: number | undefined) { - const item = getItem(itemToUse); - if (!item) return "That's not a valid item."; - - const xpObject = Lampables.find(x => x.items.includes(item.id)); - if (!xpObject) return "That's not a valid item to use."; - - if (!isValidSkill(skill)) return "That's not a valid skill."; - if (skill === SkillsEnum.Invention || skill === SkillsEnum.Divination) { - return 'A magic force prevents you from using lamps on this skill.'; - } - - const qty = !_quantity ? 1 : clamp(_quantity, 1, 1000); - const toRemoveFromBank = new Bank().add(item.id, qty); - if (!user.owns(toRemoveFromBank)) { - return `You don't have **${toRemoveFromBank}** in your bank.`; - } - - let skillsToReceive: Skills = {}; - let skillsRequirements: Skills | undefined = undefined; - - [skillsToReceive, skillsRequirements] = xpObject.function({ - user, - quantity: qty, - item - }); - - if (!skillsToReceive[skill]) { - return 'This is not a valid skill for this item.'; - } - - if (skillsRequirements && user.skillLevel(skill) < skillsRequirements[skill]!) { - return `You are not skilled enough to receive this reward. You need level **${skillsRequirements[ - skill - ]!}** in ${skill} to receive it.`; - } - - const amount = skillsToReceive[skill]!; - assert(typeof amount === 'number' && amount > 0); - const stats = await user.fetchStats({ lamped_xp: true }); - const newLampedXp = { - ...(stats.lamped_xp as ItemBank) - }; - if (!newLampedXp[skill]) newLampedXp[skill] = amount; - else newLampedXp[skill] += amount; - userStatsUpdate(user.id, { - lamped_xp: newLampedXp - }); - - await user.removeItemsFromBank(toRemoveFromBank); - const xpStr = await user.addXP({ skillName: skill, amount, artificial: true, multiplier: false }); - - return { content: `You used ${toRemoveFromBank}. ${xpStr}` }; -} diff --git a/src/mahoji/lib/abstracted_commands/luckyPickCommand.ts b/src/mahoji/lib/abstracted_commands/luckyPickCommand.ts deleted file mode 100644 index e6712eb792..0000000000 --- a/src/mahoji/lib/abstracted_commands/luckyPickCommand.ts +++ /dev/null @@ -1,202 +0,0 @@ -import type { BaseMessageOptions, ButtonInteraction, CacheType, ChatInputCommandInteraction } from 'discord.js'; -import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; -import { Time, chunk, noOp, roll, shuffleArr } from 'e'; -import { Bank } from 'oldschooljs'; -import { toKMB } from 'oldschooljs/dist/util'; - -import { SILENT_ERROR } from '../../../lib/constants'; -import { awaitMessageComponentInteraction, channelIsSendable } from '../../../lib/util'; -import { handleMahojiConfirmation, silentButtonAck } from '../../../lib/util/handleMahojiConfirmation'; -import { deferInteraction } from '../../../lib/util/interactionReply'; -import { logError } from '../../../lib/util/logError'; -import { mahojiParseNumber, updateClientGPTrackSetting, updateGPTrackSetting } from '../../mahojiSettings'; - -export async function luckyPickCommand(user: MUser, luckypickamount: string, interaction: ChatInputCommandInteraction) { - const amount = mahojiParseNumber({ input: luckypickamount, min: 1_000_000, max: 3_000_000_000 }); - - if (!amount) { - return 'amount must be between 1000000 and 3000000000 exclusively.'; - } - - await deferInteraction(interaction); - - interface Button { - name: string; - mod: (qty: number) => number; - emoji?: string; - } - - const buttons: Button[] = [ - { - name: '0', - mod: () => 0 - }, - { - name: '1.5x', - mod: (qty: number) => qty * 1.5 - }, - { - name: '2x', - mod: (qty: number) => qty * 2 - }, - { - name: '3x', - mod: (qty: number) => qty * 3 - }, - { - name: '5x', - mod: (qty: number) => qty * 5 - }, - { - name: '10x', - mod: (qty: number) => qty * 10 - } - ]; - - function getButtons(): ButtonInstance[] { - const buttonsToShow = [ - '0', - '0', - '0', - '2x', - '1.5x', - '0', - '1.5x', - '0', - '1.5x', - '1.5x', - '2x', - '0', - '3x', - '2x', - '0', - '0', - '2x', - '0' - ]; - - buttonsToShow.push(roll(10) ? '10x' : '0'); - buttonsToShow.push(roll(10) ? '5x' : '0'); - return shuffleArr(buttonsToShow.map(n => buttons.find(i => i.name === n)!)).map((item, index) => ({ - ...item, - picked: false, - id: `LP_${index}` - })); - } - - interface ButtonInstance extends Button { - id: string; - picked: boolean; - } - if (user.isIronman) { - return "Ironmen can't gamble! Go pickpocket some men for GP."; - } - - await handleMahojiConfirmation( - interaction, - `Are you sure you want to gamble ${toKMB(amount)}? You might lose it all, you might win a lot.` - ); - await user.sync(); - const currentBalance = user.GP; - if (currentBalance < amount) { - return "You don't have enough GP to make this bet."; - } - await user.removeItemsFromBank(new Bank().add('Coins', amount)); - const buttonsToShow = getButtons(); - function getCurrentButtons({ showTrueNames }: { showTrueNames: boolean }): BaseMessageOptions['components'] { - const chunkedButtons = chunk(buttonsToShow, 5); - return chunkedButtons.map(c => - new ActionRowBuilder().addComponents( - c.map(b => { - const button = new ButtonBuilder() - - .setCustomId(b.id.toString()) - .setStyle( - b.picked - ? b.name !== '0' - ? ButtonStyle.Success - : ButtonStyle.Danger - : ButtonStyle.Secondary - ); - - if (showTrueNames) { - button.setLabel(b.name); - } - if (!showTrueNames) { - button.setEmoji('680783258488799277'); - } - if (b.name === '10x' && !b.picked && showTrueNames) { - button.setStyle(ButtonStyle.Primary); - } - return button; - }) - ) - ); - } - - const channel = globalClient.channels.cache.get(interaction.channelId); - if (!channelIsSendable(channel)) throw new Error('Channel for confirmation not found.'); - const sentMessage = await channel.send({ - content: `${user}, Pick *one* button!`, - components: getCurrentButtons({ showTrueNames: false }) - }); - - const finalize = async ({ - button - }: { - button: ButtonInstance; - }) => { - const amountReceived = Math.floor(button.mod(amount)); - if (amountReceived > 0) { - await user.addItemsToBank({ items: new Bank().add('Coins', amountReceived) }); - } - await updateClientGPTrackSetting('gp_luckypick', amountReceived - amount); - await updateGPTrackSetting('gp_luckypick', amountReceived - amount, user); - await sentMessage.edit({ components: getCurrentButtons({ showTrueNames: true }) }).catch(noOp); - return amountReceived === 0 - ? `${user} picked the wrong button and lost ${toKMB(amount)}!` - : `${user} won ${toKMB(amountReceived)}!`; - }; - - const cancel = async () => { - await sentMessage.delete(); - if (!buttonsToShow.some(b => b.picked)) { - await user.addItemsToBank({ items: new Bank().add('Coins', amount) }); - return `You didn't pick any buttons in time, so you were refunded ${toKMB(amount)} GP.`; - } - throw new Error(SILENT_ERROR); - }; - - try { - const selection = await awaitMessageComponentInteraction({ - message: sentMessage, - filter: i => { - if (i.user.id !== (user.id ?? interaction.user.id).toString()) { - i.reply({ ephemeral: true, content: 'This is not your confirmation message.' }); - return false; - } - return true; - }, - time: Time.Second * 10 - }); - sentMessage.delete().catch(noOp); - - const pickedButton = buttonsToShow.find(b => b.id === selection.customId)!; - const index = Number.parseInt(pickedButton.id.split('_')[1]); - buttonsToShow[index].picked = true; - - try { - await silentButtonAck(selection as ButtonInteraction); - const result = await finalize({ button: pickedButton }); - return { - content: result, - components: getCurrentButtons({ showTrueNames: true }) - }; - } catch (err) { - logError(err); - return 'Error.'; - } - } catch (err) { - return cancel(); - } -} diff --git a/src/mahoji/lib/abstracted_commands/mageArena2Command.ts b/src/mahoji/lib/abstracted_commands/mageArena2Command.ts index aa6560ee3a..0559944e08 100644 --- a/src/mahoji/lib/abstracted_commands/mageArena2Command.ts +++ b/src/mahoji/lib/abstracted_commands/mageArena2Command.ts @@ -6,7 +6,6 @@ import removeFoodFromUser from '../../../lib/minions/functions/removeFoodFromUse import type { ActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { formatDuration, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; export async function mageArena2Command(user: MUser, channelID: string) { if (user.skillLevel(SkillsEnum.Magic) < 75) { @@ -42,8 +41,6 @@ export async function mageArena2Command(user: MUser, channelID: string) { const totalCost = itemsNeeded.clone().add(foodRemoved); - await updateBankSetting('mage_arena_cost', totalCost); - await addSubTaskToActivityTask({ userID: user.id, channelID: channelID.toString(), diff --git a/src/mahoji/lib/abstracted_commands/mageArenaCommand.ts b/src/mahoji/lib/abstracted_commands/mageArenaCommand.ts index 5fafa6565a..85aed0c071 100644 --- a/src/mahoji/lib/abstracted_commands/mageArenaCommand.ts +++ b/src/mahoji/lib/abstracted_commands/mageArenaCommand.ts @@ -6,7 +6,6 @@ import removeFoodFromUser from '../../../lib/minions/functions/removeFoodFromUse import type { ActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { formatDuration, randomVariation } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; export async function mageArenaCommand(user: MUser, channelID: string) { if (user.skillLevel(SkillsEnum.Magic) < 60) { @@ -37,8 +36,6 @@ export async function mageArenaCommand(user: MUser, channelID: string) { await user.removeItemsFromBank(itemsNeeded); - updateBankSetting('mage_arena_cost', totalCost); - await addSubTaskToActivityTask({ userID: user.id, channelID: channelID.toString(), diff --git a/src/mahoji/lib/abstracted_commands/mageTrainingArenaCommand.ts b/src/mahoji/lib/abstracted_commands/mageTrainingArenaCommand.ts index 1ee8cb5719..65494b7342 100644 --- a/src/mahoji/lib/abstracted_commands/mageTrainingArenaCommand.ts +++ b/src/mahoji/lib/abstracted_commands/mageTrainingArenaCommand.ts @@ -9,7 +9,6 @@ import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { determineRunes } from '../../../lib/util/determineRunes'; import getOSItem from '../../../lib/util/getOSItem'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { pizazzPointsPerHour } from '../../../tasks/minions/minigames/mageTrainingArenaActivity'; const RuneTable = new LootTable() @@ -99,9 +98,10 @@ export async function mageTrainingArenaBuyCommand(user: MUser, input = '') { } }); - await user.addItemsToBank({ items: { [item.id]: 1 }, collectionLog: true }); + const loot = new Bank().add(item.id); + await user.addItemsToBank({ items: loot, collectionLog: true }); - return `Successfully purchased 1x ${item.name} for ${cost} Pizazz Points.`; + return `Successfully purchased ${loot} for ${cost} Pizazz Points.`; } export async function mageTrainingArenaPointsCommand(user: MUser) { @@ -131,8 +131,6 @@ export async function mageTrainingArenaStartCommand(user: MUser, channelID: stri await transactItems({ userID: user.id, itemsToRemove: cost }); - await updateBankSetting('mta_cost', cost); - await addSubTaskToActivityTask({ userID: user.id, channelID: channelID.toString(), diff --git a/src/mahoji/lib/abstracted_commands/mahoganyHomesCommand.ts b/src/mahoji/lib/abstracted_commands/mahoganyHomesCommand.ts index 678772b0cf..0b355f76e9 100644 --- a/src/mahoji/lib/abstracted_commands/mahoganyHomesCommand.ts +++ b/src/mahoji/lib/abstracted_commands/mahoganyHomesCommand.ts @@ -10,7 +10,6 @@ import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import getOSItem from '../../../lib/util/getOSItem'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; interface IContract { name: string; @@ -169,8 +168,6 @@ export async function mahoganyHomesBuildCommand(user: MUser, channelID: string, } await user.removeItemsFromBank(itemsNeeded); - updateBankSetting('construction_cost_bank', itemsNeeded); - await addSubTaskToActivityTask({ userID: user.id, channelID: channelID.toString(), diff --git a/src/mahoji/lib/abstracted_commands/minionBuyCommand.ts b/src/mahoji/lib/abstracted_commands/minionBuyCommand.ts index 58cab597a9..516905712b 100644 --- a/src/mahoji/lib/abstracted_commands/minionBuyCommand.ts +++ b/src/mahoji/lib/abstracted_commands/minionBuyCommand.ts @@ -1,69 +1,15 @@ -import { type CommandResponse, isAtleastThisOld } from '@oldschoolgg/toolkit'; -import { ComponentType, type User } from 'discord.js'; -import { Time } from 'e'; -import { Bank } from 'oldschooljs'; -import { mahojiInformationalButtons } from '../../../lib/constants'; +import { RANDOMIZER_HELP, type randomizationMethods, updateUsersRandomizerMap } from '../../../lib/randomizer'; -export async function minionBuyCommand(apiUser: User, user: MUser, ironman: boolean): CommandResponse { +export async function minionBuyCommand(user: MUser, method: (typeof randomizationMethods)[number]): Promise { if (user.user.minion_hasBought) return 'You already have a minion!'; await user.update({ minion_hasBought: true, minion_bought_date: new Date(), - minion_ironman: Boolean(ironman) + randomize_method: method.id }); - const starter = isAtleastThisOld(apiUser.createdAt, Time.Year * 2) - ? new Bank({ - Shark: 300, - 'Saradomin brew(4)': 50, - 'Super restore(4)': 20, - 'Anti-dragon shield': 1, - 'Tiny lamp': 5, - 'Small lamp': 2, - 'Tradeable mystery box': 5, - 'Untradeable Mystery box': 5, - 'Dragon bones': 50, - Coins: 50_000_000, - 'Clue scroll (beginner)': 10, - 'Equippable mystery box': 1, - 'Pet Mystery box': 1 - }) - : null; + await updateUsersRandomizerMap(user, method); - if (starter) { - await user.addItemsToBank({ items: starter, collectionLog: false }); - } - // Ensure user has a userStats row - await prisma.userStats.upsert({ - where: { - user_id: BigInt(user.id) - }, - create: { - user_id: BigInt(user.id) - }, - update: {} - }); - - return { - content: `You have successfully got yourself a minion, and you're ready to use the bot now! Please check out the links below for information you should read. - -<:ironman:626647335900020746> You can make your new minion an Ironman by using the command: \`/minion ironman\`. - -🧑‍⚖️ **Rules:** You *must* follow our 5 simple rules, breaking any rule can result in a permanent ban - and "I didn't know the rules" is not a valid excuse, read them here: - -<:patreonLogo:679334888792391703> **Patreon:** If you're able too, please consider supporting my work on Patreon, it's highly appreciated and helps me hugely ❤️ - -<:BSO:863823820435619890> **BSO:** I run a 2nd bot called BSO (Bot School Old), which you can also play, it has lots of fun and unique changes, like 5x XP and infinitely stacking clues. Type \`/help\` for more information. - -Please click the buttons below for important links. - -${starter !== null ? `**You received these starter items:** ${starter}.` : ''}`, - components: [ - { - type: ComponentType.ActionRow, - components: mahojiInformationalButtons - } - ] - }; + return RANDOMIZER_HELP(user); } diff --git a/src/mahoji/lib/abstracted_commands/minionKill.ts b/src/mahoji/lib/abstracted_commands/minionKill.ts index c4f6bd9847..a044e9b91e 100644 --- a/src/mahoji/lib/abstracted_commands/minionKill.ts +++ b/src/mahoji/lib/abstracted_commands/minionKill.ts @@ -1,4 +1,3 @@ -import type { GearSetupType, Prisma } from '@prisma/client'; import { type ChatInputCommandInteraction, type InteractionReplyOptions, bold } from 'discord.js'; import { Time, @@ -16,15 +15,15 @@ import { Bank, Monsters } from 'oldschooljs'; import { MonsterAttribute } from 'oldschooljs/dist/meta/monsterData'; import { itemID } from 'oldschooljs/dist/util'; -import { BitField, PeakTier, type PvMMethod, YETI_ID } from '../../../lib/constants'; +import { BitField, PeakTier, type PvMMethod } from '../../../lib/constants'; import { gorajanArcherOutfit, gorajanOccultOutfit, gorajanWarriorOutfit } from '../../../lib/data/CollectionsExport'; import { Eatables } from '../../../lib/data/eatables'; import { getSimilarItems } from '../../../lib/data/similarItems'; import { checkUserCanUseDegradeableItem, degradeItem, degradeablePvmBoostItems } from '../../../lib/degradeableItems'; -import { userhasDiaryTier } from '../../../lib/diaries'; -import { type GearStat, maxOffenceStats } from '../../../lib/gear'; +import { type GearSetupType, type GearStat, maxOffenceStats } from '../../../lib/gear'; import { InventionID, canAffordInventionBoost, inventionItemBoost } from '../../../lib/invention/inventions'; -import { trackLoot } from '../../../lib/lootTrack'; + +import { userhasDiaryTier } from '../../../lib/diaries'; import type { CombatOptionsEnum } from '../../../lib/minions/data/combatConstants'; import { SlayerActivityConstants, @@ -50,6 +49,7 @@ import reducedTimeFromKC from '../../../lib/minions/functions/reducedTimeFromKC' import removeFoodFromUser from '../../../lib/minions/functions/removeFoodFromUser'; import type { Consumable } from '../../../lib/minions/types'; import { calcPOHBoosts } from '../../../lib/poh'; +import { RelicID } from '../../../lib/relics'; import { SkillsEnum } from '../../../lib/skilling/types'; import { SlayerTaskUnlocksEnum } from '../../../lib/slayer/slayerUnlocks'; import { determineBoostChoice, getUsersCurrentSlayerInfo } from '../../../lib/slayer/slayerUtil'; @@ -62,10 +62,7 @@ import { convertAttackStyleToGearSetup, convertPvmStylesToGearSetup, formatDuration, - formatItemBoosts, formatItemCosts, - formatItemReqs, - formatPohBoosts, isWeekend, itemNameFromID, randomVariation, @@ -80,10 +77,8 @@ import findMonster from '../../../lib/util/findMonster'; import getOSItem from '../../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; import resolveItems from '../../../lib/util/resolveItems'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { sendToChannelID } from '../../../lib/util/webhook'; -import { hasMonsterRequirements, resolveAvailableItemBoosts, userStatsUpdate } from '../../mahojiSettings'; -import { findBingosWithUserParticipating } from '../bingo/BingoManager'; +import { hasMonsterRequirements, resolveAvailableItemBoosts } from '../../mahojiSettings'; import { igneCommand } from './igneCommand'; import { kgCommand } from './kgCommand'; import { kkCommand } from './kkCommand'; @@ -815,9 +810,7 @@ export async function minionKillCommand( } quantity = Math.max(1, quantity); - if (!user.bitfield.includes(BitField.HasUnlockedYeti) && monster.id === YETI_ID) { - quantity = 1; - } + if (quantity > 1 && duration > maxTripLength) { return `${minionName} can't go on PvM trips longer than ${formatDuration( maxTripLength @@ -980,51 +973,17 @@ export async function minionKillCommand( duration *= 0.9; } + if (user.hasRelic(RelicID.Speed)) { + boosts.push('Your Relic of Speed granted you a 30% speed boost.'); + duration *= 0.7; + } + if (hasBlessing && dwarvenBlessingPotsNeeded) { const prayerPotsBank = new Bank().add(dwarvenBlessingItem, dwarvenBlessingPotsNeeded); lootToRemove.add(prayerPotsBank); } - const rangeSetup = { ...user.gear.range.raw() }; - let usedDart = false; - if (rangeSetup.weapon?.item === itemID('Deathtouched dart')) { - const bingos = await findBingosWithUserParticipating(user.id); - if (bingos.some(bingo => bingo.isActive())) { - return 'You cannot use Deathtouched darts while in an active Bingo.'; - } - duration = 1; - if (rangeSetup.weapon.quantity > 1) { - rangeSetup.weapon.quantity--; - } else { - rangeSetup.weapon = null; - } - await user.update({ - gear_range: rangeSetup as Prisma.InputJsonObject - }); - if (monster.name === 'Koschei the deathless') { - return ( - 'You send your minion off to fight Koschei with a Deathtouched dart, they stand a safe distance and throw the dart - Koschei immediately locks' + - ' eyes with your minion and grabs the dart mid-air, and throws it back, killing your minion instantly.' - ); - } - if (monster.name === 'Solis') { - return 'The dart melts into a crisp dust before coming into contact with Solis.'; - } - if (monster.name === 'Celestara') { - return 'Your minion threw the dart at the moon, it did not reach.'; - } - if (monster.name === 'Yeti') { - return 'You send your minion off to fight Yeti with a Deathtouched dart, they stand a safe distance and throw the dart - the cold, harsh wind blows it out of the air. Your minion runs back to you in fear.'; - } - if ([BSOMonsters.Akumu.id, BSOMonsters.Venatrix.id].includes(monster.id)) { - return 'This monster is temporarily unable to be killed with a Deathtouched dart.'; - } - usedDart = true; - await userStatsUpdate(user.id, { - death_touched_darts_used: { - increment: 1 - } - }); - } + const usedDart = false; + if (monster.name === 'Koschei the deathless') { return 'You send your minion off to fight Koschei, before they even get close, they feel an immense, powerful fear and return back.'; } @@ -1176,26 +1135,10 @@ export async function minionKillCommand( // Remove items after food calc to prevent losing items if the user doesn't have the right amount of food. Example: Mossy key if (lootToRemove.length > 0) { - await updateBankSetting('economyStats_PVMCost', lootToRemove); - await user.specialRemoveItems(lootToRemove, { wildy: !!isInWilderness }); + await user.specialRemoveItems(lootToRemove, { wildy: isInWilderness }); totalCost.add(lootToRemove); } - if (totalCost.length > 0) { - await trackLoot({ - id: monster.name, - totalCost, - type: 'Monster', - changeType: 'cost', - users: [ - { - id: user.id, - cost: totalCost - } - ] - }); - } - await addSubTaskToActivityTask({ mi: monster.id, userID: user.id, @@ -1244,204 +1187,3 @@ export async function minionKillCommand( return response; } - -export async function monsterInfo(user: MUser, name: string): Promise { - const monster = findMonster(name); - - if (stringMatches(name, 'nightmare')) { - return 'The Nightmare is not supported by this command due to the complexity of the fight.'; - } - - if (!monster) { - return "That's not a valid monster"; - } - const osjsMon = Monsters.get(monster.id); - const [, , attackStyles] = resolveAttackStyles(user, { - monsterID: monster.id - }); - - const userKc = await user.getKC(monster.id); - let [timeToFinish, percentReduced] = reducedTimeFromKC(monster, userKc); - - // item boosts - const ownedBoostItems = []; - let totalItemBoost = 0; - for (const [itemID, boostAmount] of Object.entries(resolveAvailableItemBoosts(user, monster))) { - timeToFinish *= (100 - boostAmount) / 100; - totalItemBoost += boostAmount; - ownedBoostItems.push(itemNameFromID(Number.parseInt(itemID))); - } - - let isDragon = false; - if (monster.name.toLowerCase() !== 'vorkath' && osjsMon?.data?.attributes?.includes(MonsterAttribute.Dragon)) { - isDragon = true; - if ( - user.hasEquippedOrInBank('Dragon hunter lance') && - !attackStyles.includes(SkillsEnum.Ranged) && - !attackStyles.includes(SkillsEnum.Magic) - ) { - timeToFinish = reduceNumByPercent(timeToFinish, 20); - ownedBoostItems.push('Dragon hunter lance'); - totalItemBoost += 20; - } else if (user.hasEquippedOrInBank('Dragon hunter crossbow') && attackStyles.includes(SkillsEnum.Ranged)) { - timeToFinish = reduceNumByPercent(timeToFinish, 20); - ownedBoostItems.push('Dragon hunter crossbow'); - totalItemBoost += 20; - } - } - // poh boosts - if (monster.pohBoosts) { - const [boostPercent, messages] = calcPOHBoosts(await getPOH(user.id), monster.pohBoosts); - if (boostPercent > 0) { - timeToFinish = reduceNumByPercent(timeToFinish, boostPercent); - const boostString = messages.join(' ').replace(/[0-9]{2}% for /, ''); - ownedBoostItems.push(`${boostString}`); - totalItemBoost += boostPercent; - } - } - // combat stat boosts - const skillTotal = sumArr(attackStyles.map(s => user.skillLevel(s))); - - let percent = round(calcWhatPercent(skillTotal, attackStyles.length * 99), 2); - - const str = [`**${monster.name}**\n`]; - - let skillString = ''; - - if (percent < 50) { - percent = 50 - percent; - skillString = `Skills boost: -${percent.toFixed(2)}% for your skills.\n`; - timeToFinish = increaseNumByPercent(timeToFinish, percent); - } else { - percent = Math.min(15, percent / 6.5); - skillString = `Skills boost: ${percent.toFixed(2)}% for your skills.\n`; - timeToFinish = reduceNumByPercent(timeToFinish, percent); - } - let hpString = ''; - // Find best eatable boost and add 1% extra - const noFoodBoost = Math.floor(Math.max(...Eatables.map(eatable => eatable.pvmBoost ?? 0)) + 1); - if (monster.healAmountNeeded) { - const [hpNeededPerKill] = calculateMonsterFood(monster, user); - if (hpNeededPerKill === 0) { - timeToFinish = reduceNumByPercent(timeToFinish, noFoodBoost); - hpString = `${noFoodBoost}% boost for no food`; - } - } - const maxCanKillSlay = Math.floor(calcMaxTripLength(user, 'MonsterKilling') / reduceNumByPercent(timeToFinish, 15)); - const maxCanKill = Math.floor(calcMaxTripLength(user, 'MonsterKilling') / timeToFinish); - - const { QP } = user; - - str.push(`**Barrage/Burst**: ${monster.canBarrage ? 'Yes' : 'No'}`); - str.push( - `**Cannon**: ${monster.canCannon ? `Yes, ${monster.cannonMulti ? 'multi' : 'single'} combat area` : 'No'}\n` - ); - - if (monster.qpRequired) { - str.push(`${monster.name} requires **${monster.qpRequired}qp** to kill, and you have ${QP}qp.\n`); - } - - const itemRequirements = []; - if (monster.itemsRequired && monster.itemsRequired.length > 0) { - itemRequirements.push(`**Items Required:** ${formatItemReqs(monster.itemsRequired)}\n`); - } - if (monster.itemCost) { - itemRequirements.push( - `**Item Cost per Trip:** ${formatItemCosts(monster.itemCost, timeToFinish * maxCanKill)}\n` - ); - } - - if (monster.healAmountNeeded) { - const [hpNeededPerKill, gearStats] = calculateMonsterFood(monster, user); - const gearReductions = gearStats.replace(/: Reduced from (?:[0-9]+?), /, '\n').replace('), ', ')\n'); - if (hpNeededPerKill > 0) { - itemRequirements.push( - `**Healing Required:** ${gearReductions}\nYou require ${ - hpNeededPerKill * maxCanKill - } hp for a full trip\n` - ); - } else { - itemRequirements.push(`**Healing Required:** ${gearReductions}\n**Food boost**: ${hpString}\n`); - } - } - str.push(`${itemRequirements.join('')}`); - const totalBoost = []; - if (isDragon) { - totalBoost.push('15% for Dragon hunter lance OR 15% for Dragon hunter crossbow'); - } - if (monster.itemInBankBoosts) { - totalBoost.push(`${formatItemBoosts(monster.itemInBankBoosts)}`); - } - if (monster.equippedItemBoosts) { - for (const boostSet of monster.equippedItemBoosts) { - totalBoost.push( - `${boostSet.items - .map(i => `${i.boostPercent}% for ${itemNameFromID(i.itemID)}`) - .join(' OR ')}, equipped in ${boostSet.gearSetup} setup` - ); - } - } - if (monster.pohBoosts) { - totalBoost.push( - `${formatPohBoosts(monster.pohBoosts) - .replace(/(Pool:)/, '') - .replace(')', '') - .replace('(', '') - .replace('\n', '')}` - ); - } - if (totalBoost.length > 0) { - str.push( - `**Boosts**\nAvailable Boosts: ${totalBoost.join(',')}\n${ - ownedBoostItems.length > 0 ? `Your boosts: ${ownedBoostItems.join(', ')} for ${totalItemBoost}%` : '' - }\n${skillString}` - ); - } else { - str.push(`**Boosts**\n${skillString}`); - } - str.push('**Trip info**'); - - str.push( - `Maximum trip length: ${formatDuration( - calcMaxTripLength(user, 'MonsterKilling') - )}\nNormal kill time: ${formatDuration( - monster.timeToFinish - )}. You can kill up to ${maxCanKill} per trip (${formatDuration(timeToFinish)} per kill).` - ); - str.push( - `If you were on a slayer task: ${maxCanKillSlay} per trip (${formatDuration( - reduceNumByPercent(timeToFinish, 15) - )} per kill).` - ); - const kcForOnePercent = Math.ceil((Time.Hour * 5) / monster.timeToFinish); - - str.push( - `Every ${kcForOnePercent}kc you will gain a 1% (upto 10%).\nYou currently recieve a ${percentReduced}% boost with your ${userKc}kc.\n` - ); - - const min = timeToFinish * maxCanKill * 1.01; - const max = timeToFinish * maxCanKill * 1.2; - str.push( - `Due to the random variation of an added 1-20% duration, ${maxCanKill}x kills can take between (${formatDuration( - min - )}) and (${formatDuration(max)})\nIf the Weekend boost is active, it takes: (${formatDuration( - min * 0.9 - )}) to (${formatDuration(max * 0.9)}) to finish.\n` - ); - - if (monster.degradeableItemUsage) { - for (const item of monster.degradeableItemUsage) { - str.push( - `${item.items.map(i => `${itemNameFromID(i.itemID)} (${i.boostPercent}% boost)`).join(' OR ')} ${ - item.required ? 'must' : 'can' - } be equipped in your ${item.gearSetup} setup, needs to be charged using /minion charge.` - ); - } - } - - const response: InteractionReplyOptions = { - content: str.join('\n') - }; - - return response; -} diff --git a/src/mahoji/lib/abstracted_commands/minionStatusCommand.ts b/src/mahoji/lib/abstracted_commands/minionStatusCommand.ts index 52842e7a32..896ada1866 100644 --- a/src/mahoji/lib/abstracted_commands/minionStatusCommand.ts +++ b/src/mahoji/lib/abstracted_commands/minionStatusCommand.ts @@ -1,12 +1,10 @@ -import { toTitleCase } from '@oldschoolgg/toolkit'; import type { BaseMessageOptions } from 'discord.js'; -import { ButtonBuilder, ButtonStyle, ComponentType } from 'discord.js'; -import { roll, stripNonAlphanumeric } from 'e'; +import { ButtonBuilder, ButtonStyle } from 'discord.js'; import { ClueTiers } from '../../../lib/clues/clueTiers'; -import { BitField, Emoji, PerkTier, minionBuyButton } from '../../../lib/constants'; +import { BitField, Emoji, PerkTier } from '../../../lib/constants'; import { getUsersFishingContestDetails } from '../../../lib/fishingContest'; -import { roboChimpSyncData, roboChimpUserFetch } from '../../../lib/roboChimp'; +import { roboChimpSyncData } from '../../../lib/roboChimp'; import { makeComponents } from '../../../lib/util'; import { @@ -17,75 +15,23 @@ import { import { minionStatus } from '../../../lib/util/minionStatus'; import { makeRepeatTripButtons } from '../../../lib/util/repeatStoredTrip'; import { getUsersTame, shortTameTripDesc, tameLastFinishedActivity } from '../../../lib/util/tameUtil'; -import { getItemContractDetails } from '../../commands/ic'; -import { spawnLampIsReady } from '../../commands/tools'; import { calculateBirdhouseDetails } from './birdhousesCommand'; import { isUsersDailyReady } from './dailyCommand'; import { canRunAutoContract } from './farmingContractCommand'; -async function fetchFavoriteGearPresets(userID: string) { - const pinnedPresets = await prisma.gearPreset.findMany({ - where: { user_id: userID, pinned_setup: { not: null } }, - orderBy: { times_equipped: 'desc' }, - take: 5 - }); - - if (pinnedPresets.length === 0) return []; - - return pinnedPresets.map(i => - new ButtonBuilder() - .setStyle(ButtonStyle.Secondary) - .setCustomId(`GPE_${i.pinned_setup}_${stripNonAlphanumeric(i.name)}`) - .setLabel(`Equip '${toTitleCase(i.name).replace(/_/g, ' ')}' to ${i.pinned_setup}`) - .setEmoji(i.emoji_id ?? Emoji.Gear) - ); -} - -async function fetchPinnedTrips(userID: string) { - const pinnedPresets = await prisma.pinnedTrip.findMany({ - where: { user_id: userID }, - take: 5 - }); - - if (pinnedPresets.length === 0) return []; - - return pinnedPresets.map(i => - new ButtonBuilder() - .setStyle(ButtonStyle.Secondary) - .setCustomId(`PTR_${i.id}`) - .setLabel(`Repeat ${i.custom_name ?? i.activity_type}`) - .setEmoji(i.emoji_id ?? '🔁') - ); -} - -export async function minionStatusCommand(user: MUser, channelID: string): Promise { +export async function minionStatusCommand(user: MUser): Promise { const { minionIsBusy } = user; - const birdhouseDetails = minionIsBusy ? { isReady: false } : calculateBirdhouseDetails(user); - const [roboChimpUser, gearPresetButtons, pinnedTripButtons, fishingResult, dailyIsReady] = await Promise.all([ - roboChimpUserFetch(user.id), - minionIsBusy ? [] : fetchFavoriteGearPresets(user.id), - minionIsBusy ? [] : fetchPinnedTrips(user.id), + const [birdhouseDetails, fishingResult, dailyIsReady] = await Promise.all([ + minionIsBusy ? { isReady: false } : calculateBirdhouseDetails(user), getUsersFishingContestDetails(user), isUsersDailyReady(user) ]); await roboChimpSyncData(user); - if (user.user.cached_networth_value === null || roll(100)) { - await user.update({ - cached_networth_value: (await user.calculateNetWorth()).value - }); - } if (!user.user.minion_hasBought) { return { - content: - "You haven't bought a minion yet! Click the button below to buy a minion and start playing the bot.", - components: [ - { - components: [minionBuyButton], - type: ComponentType.ActionRow - } - ] + content: `You haven't bought a minion yet! Use /minion buy.` }; } @@ -173,7 +119,7 @@ export async function minionStatusCommand(user: MUser, channelID: string): Promi } const perkTier = user.perkTier(); - if (perkTier >= PerkTier.Two) { + if (perkTier >= 2) { const { tame, species, activity } = await getUsersTame(user); if (tame && !activity) { const lastTameAct = await tameLastFinishedActivity(user); @@ -189,45 +135,6 @@ export async function minionStatusCommand(user: MUser, channelID: string): Promi } } - const [spawnLampReady] = spawnLampIsReady(user, channelID); - if (spawnLampReady) { - buttons.push( - new ButtonBuilder() - .setCustomId('SPAWN_LAMP') - .setLabel('Spawn Lamp') - .setEmoji('988325171498721290') - .setStyle(ButtonStyle.Secondary) - ); - } - - const icDetails = getItemContractDetails(user); - if (perkTier >= PerkTier.Two && icDetails.currentItem && icDetails.owns) { - buttons.push( - new ButtonBuilder() - .setCustomId('ITEM_CONTRACT_SEND') - .setLabel(`IC: ${icDetails.currentItem.name.slice(0, 20)}`) - .setEmoji('988422348434718812') - .setStyle(ButtonStyle.Secondary) - ); - } - - if (roboChimpUser.leagues_points_total === 0) { - buttons.push( - new ButtonBuilder() - .setLabel('OSB/BSO Leagues') - .setEmoji('660333438016028723') - .setStyle(ButtonStyle.Link) - .setURL('https://bso-wiki.oldschool.gg/leagues') - ); - } - - if (gearPresetButtons.length > 0) { - buttons.push(...gearPresetButtons); - } - if (pinnedTripButtons.length > 0) { - buttons.push(...pinnedTripButtons); - } - return { content: status, components: makeComponents(buttons) diff --git a/src/mahoji/lib/abstracted_commands/moktangCommand.ts b/src/mahoji/lib/abstracted_commands/moktangCommand.ts index 3de0a3908d..4f8c03e5d3 100644 --- a/src/mahoji/lib/abstracted_commands/moktangCommand.ts +++ b/src/mahoji/lib/abstracted_commands/moktangCommand.ts @@ -3,7 +3,7 @@ import { Time } from 'e'; import { Bank } from 'oldschooljs'; import { dwarvenOutfit } from '../../../lib/data/CollectionsExport'; -import { trackLoot } from '../../../lib/lootTrack'; + import { SkillsEnum } from '../../../lib/skilling/types'; import { PercentCounter } from '../../../lib/structures/PercentCounter'; import type { MoktangTaskOptions } from '../../../lib/types/minions'; @@ -11,7 +11,6 @@ import { formatDuration, itemNameFromID } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import resolveItems from '../../../lib/util/resolveItems'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; const requiredPickaxes = resolveItems(['Crystal pickaxe', 'Volcanic pickaxe', 'Dwarven pickaxe', 'Dragon pickaxe']); @@ -56,19 +55,6 @@ export async function moktangCommand(user: MUser, channelID: string, inputQuanti } await user.removeItemsFromBank(cost); - await updateBankSetting('moktang_cost', cost); - await trackLoot({ - changeType: 'cost', - totalCost: cost, - id: 'moktang', - type: 'Monster', - users: [ - { - id: user.id, - cost - } - ] - }); await addSubTaskToActivityTask({ userID: user.id, diff --git a/src/mahoji/lib/abstracted_commands/monkeyRumbleCommand.ts b/src/mahoji/lib/abstracted_commands/monkeyRumbleCommand.ts index 98ec87fd31..55e78a5b04 100644 --- a/src/mahoji/lib/abstracted_commands/monkeyRumbleCommand.ts +++ b/src/mahoji/lib/abstracted_commands/monkeyRumbleCommand.ts @@ -20,7 +20,6 @@ import { formatDuration } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import { mahojiChatHead } from '../../../lib/util/chatHeadImage'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; export async function monkeyRumbleStatsCommand(user: MUser) { const tier = monkeyTiers.find(t => t.id === monkeyTierOfUser(user))!; @@ -112,7 +111,6 @@ export async function monkeyRumbleCommand(user: MUser, channelID: string): Comma } const cost = new Bank().add(eatable.item.id, foodRequired); await user.removeItemsFromBank(cost); - updateBankSetting('mr_cost', cost); await addSubTaskToActivityTask({ userID: user.id, diff --git a/src/mahoji/lib/abstracted_commands/naxxusCommand.ts b/src/mahoji/lib/abstracted_commands/naxxusCommand.ts index fb5afcb031..9f88f8f5a0 100644 --- a/src/mahoji/lib/abstracted_commands/naxxusCommand.ts +++ b/src/mahoji/lib/abstracted_commands/naxxusCommand.ts @@ -5,7 +5,7 @@ import type { Item } from 'oldschooljs/dist/meta/types'; import { checkUserCanUseDegradeableItem, degradeItem, degradeablePvmBoostItems } from '../../../lib/degradeableItems'; import type { GearStats } from '../../../lib/gear'; -import { trackLoot } from '../../../lib/lootTrack'; + import { NAXXUS_HP, Naxxus } from '../../../lib/minions/data/killableMonsters/custom/bosses/Naxxus'; import { Gear } from '../../../lib/structures/Gear'; import type { ActivityTaskOptionsWithQuantity } from '../../../lib/types/minions'; @@ -13,7 +13,6 @@ import { formatDuration, isWeekend } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import getOSItem from '../../../lib/util/getOSItem'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { hasMonsterRequirements } from '../../mahojiSettings'; const bisMageGear = new Gear({ @@ -233,19 +232,6 @@ export async function naxxusCommand(user: MUser, channelID: string, quantity: nu await user.removeItemsFromBank(foodBank); - await trackLoot({ - changeType: 'cost', - totalCost: foodBank, - id: Naxxus.name, - type: 'Monster', - users: [ - { - id: user.id, - cost: foodBank - } - ] - }); - await addSubTaskToActivityTask({ userID: user.id, channelID: channelID.toString(), @@ -254,8 +240,6 @@ export async function naxxusCommand(user: MUser, channelID: string, quantity: nu type: 'Naxxus' }); - updateBankSetting('naxxus_cost', foodBank); - const embed = new EmbedBuilder() .setDescription( `**Supplies**: ${foodBank.toString()}. diff --git a/src/mahoji/lib/abstracted_commands/nexCommand.ts b/src/mahoji/lib/abstracted_commands/nexCommand.ts index a29a95fa20..c1ab37b684 100644 --- a/src/mahoji/lib/abstracted_commands/nexCommand.ts +++ b/src/mahoji/lib/abstracted_commands/nexCommand.ts @@ -5,7 +5,7 @@ import type { ChatInputCommandInteraction } from 'discord.js'; import { Time, increaseNumByPercent, reduceNumByPercent, round } from 'e'; import { calcBossFood } from '../../../lib/bso/calcBossFood'; import { gorajanArcherOutfit, pernixOutfit } from '../../../lib/data/CollectionsExport'; -import { trackLoot } from '../../../lib/lootTrack'; + import { calculateMonsterFood } from '../../../lib/minions/functions'; import type { KillableMonster } from '../../../lib/minions/types'; import { NexMonster } from '../../../lib/nex'; @@ -16,7 +16,6 @@ import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask import calcDurQty from '../../../lib/util/calcMassDurationQuantity'; import { getNexGearStats } from '../../../lib/util/getNexGearStats'; import { deferInteraction } from '../../../lib/util/interactionReply'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { hasMonsterRequirements } from '../../mahojiSettings'; async function checkReqs(users: MUser[], monster: KillableMonster, quantity: number): Promise { @@ -277,17 +276,6 @@ export async function nexCommand( const totalCost = new Bank(); for (const u of removeResult) totalCost.add(u.cost); - await trackLoot({ - changeType: 'cost', - totalCost, - id: NexMonster.name, - type: 'Monster', - users: removeResult.map(i => ({ - id: i.id, - cost: i.cost - })) - }); - foodString += `${foodRemoved.join(', ')}.`; await addSubTaskToActivityTask({ @@ -299,8 +287,6 @@ export async function nexCommand( users: users.map(u => u.id) }); - updateBankSetting('nex_cost', totalCost); - let str = type === 'solo' ? `Your minion is now attempting to kill ${quantity}x Nex. ${foodString} The trip will take ${formatDuration(duration)}.` diff --git a/src/mahoji/lib/abstracted_commands/nightmareCommand.ts b/src/mahoji/lib/abstracted_commands/nightmareCommand.ts index 17963a9cd0..41a5ac3c31 100644 --- a/src/mahoji/lib/abstracted_commands/nightmareCommand.ts +++ b/src/mahoji/lib/abstracted_commands/nightmareCommand.ts @@ -4,7 +4,7 @@ import { Bank } from 'oldschooljs'; import { BitField, PHOSANI_NIGHTMARE_ID, ZAM_HASTA_CRUSH } from '../../../lib/constants'; import { degradeItem } from '../../../lib/degradeableItems'; -import { trackLoot } from '../../../lib/lootTrack'; + import { NightmareMonster } from '../../../lib/minions/data/killableMonsters'; import { calculateMonsterFood } from '../../../lib/minions/functions'; import removeFoodFromUser from '../../../lib/minions/functions/removeFoodFromUser'; @@ -17,7 +17,6 @@ import calcDurQty from '../../../lib/util/calcMassDurationQuantity'; import { getNightmareGearStats } from '../../../lib/util/getNightmareGearStats'; import getOSItem from '../../../lib/util/getOSItem'; import resolveItems from '../../../lib/util/resolveItems'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { hasMonsterRequirements } from '../../mahojiSettings'; async function soloMessage(user: MUser, duration: number, quantity: number, isPhosani: boolean) { @@ -287,20 +286,6 @@ export async function nightmareCommand(user: MUser, channelID: string, name: str } } - await updateBankSetting('nightmare_cost', totalCost); - await trackLoot({ - id: 'nightmare', - totalCost, - type: 'Monster', - changeType: 'cost', - users: [ - { - id: user.id, - cost: totalCost - } - ] - }); - await addSubTaskToActivityTask({ userID: user.id, channelID: channelID.toString(), diff --git a/src/mahoji/lib/abstracted_commands/nightmareZoneCommand.ts b/src/mahoji/lib/abstracted_commands/nightmareZoneCommand.ts index f8861fbd0c..08a0ec7073 100644 --- a/src/mahoji/lib/abstracted_commands/nightmareZoneCommand.ts +++ b/src/mahoji/lib/abstracted_commands/nightmareZoneCommand.ts @@ -3,7 +3,7 @@ import { Time, calcWhatPercent, reduceNumByPercent, round, sumArr } from 'e'; import { Bank } from 'oldschooljs'; import type { NMZStrategy } from '../../../lib/constants'; -import { trackLoot } from '../../../lib/lootTrack'; + import { MAX_QP } from '../../../lib/minions/data/quests'; import { resolveAttackStyles } from '../../../lib/minions/functions'; import { getMinigameEntity } from '../../../lib/settings/minigames'; @@ -14,7 +14,6 @@ import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import getOSItem from '../../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import type { NightmareZoneActivityTaskOptions } from './../../../lib/types/minions'; const itemBoosts = [ @@ -371,19 +370,6 @@ export async function nightmareZoneStartCommand(user: MUser, strategy: NMZStrate } await user.removeItemsFromBank(totalCost); - updateBankSetting('nmz_cost', totalCost); - await trackLoot({ - id: 'nmz', - type: 'Minigame', - totalCost, - changeType: 'cost', - users: [ - { - id: user.id, - cost: totalCost - } - ] - }); await addSubTaskToActivityTask({ quantity, @@ -413,10 +399,6 @@ export async function nightmareZoneShopCommand( return `You currently have ${currentUserPoints.toLocaleString()} Nightmare Zone points.`; } - if (user.user.minion_ironman) { - return `${user.usernameOrMention} is an ironman, so they can't buy anything from this shop!`; - } - const shopItem = nightmareZoneBuyables.find( i => stringMatches(item, i.name) || i.aliases.some(alias => stringMatches(alias, item)) ); @@ -456,7 +438,7 @@ export async function nightmareZoneShopCommand( } }); - return `You successfully bought **${quantity.toLocaleString()}x ${shopItem.name}** for ${(costPerItem * quantity).toLocaleString()} Nightmare Zone points.\nYou now have ${currentUserPoints - cost} Nightmare Zone points left.`; + return `You successfully bought **${loot}** for ${(costPerItem * quantity).toLocaleString()} Nightmare Zone points.\nYou now have ${currentUserPoints - cost} Nightmare Zone points left.`; } export async function nightmareZoneImbueCommand(user: MUser, input = '') { diff --git a/src/mahoji/lib/abstracted_commands/odsCommand.ts b/src/mahoji/lib/abstracted_commands/odsCommand.ts index 8c830881a7..cac74c73dc 100644 --- a/src/mahoji/lib/abstracted_commands/odsCommand.ts +++ b/src/mahoji/lib/abstracted_commands/odsCommand.ts @@ -4,14 +4,13 @@ import { Bank } from 'oldschooljs'; import { randomVariation } from 'oldschooljs/dist/util'; import { Emoji } from '../../../lib/constants'; -import { trackLoot } from '../../../lib/lootTrack'; + import { getMinigameEntity } from '../../../lib/settings/minigames'; import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import { formatDuration, stringMatches } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; import getOSItem from '../../../lib/util/getOSItem'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; export const OuraniaBuyables = [ { @@ -63,9 +62,10 @@ export async function odsBuyCommand(user: MUser, name: string, qty: number): Com } }); - await user.addItemsToBank({ items: { [item.id]: qty }, collectionLog: true }); + const loot = new Bank().add(item.id, qty); + await user.addItemsToBank({ items: loot, collectionLog: true }); - return `Successfully purchased ${qty.toLocaleString()}x ${item.name} for ${cost.toLocaleString()} Ourania Tokens.`; + return `Successfully purchased ${loot} for ${cost.toLocaleString()} Ourania Tokens.`; } export async function odsStartCommand(klasaUser: MUser, channelID: string) { @@ -95,7 +95,6 @@ export async function odsStartCommand(klasaUser: MUser, channelID: string) { } await klasaUser.removeItemsFromBank(cost); - updateBankSetting('ods_cost', cost); let str = `${ klasaUser.minionName @@ -107,19 +106,6 @@ export async function odsStartCommand(klasaUser: MUser, channelID: string) { str += `\n\n**Boosts:** ${boosts.join(', ')}.`; } - await trackLoot({ - changeType: 'cost', - totalCost: cost, - id: 'ourania_delivery_service', - type: 'Monster', - users: [ - { - id: klasaUser.id, - cost - } - ] - }); - await addSubTaskToActivityTask({ userID: klasaUser.id, channelID: channelID.toString(), diff --git a/src/mahoji/lib/abstracted_commands/openCommand.ts b/src/mahoji/lib/abstracted_commands/openCommand.ts index 036cb0ffef..2b80a677da 100644 --- a/src/mahoji/lib/abstracted_commands/openCommand.ts +++ b/src/mahoji/lib/abstracted_commands/openCommand.ts @@ -1,21 +1,20 @@ import { type CommandResponse, PerkTier, stringMatches } from '@oldschoolgg/toolkit'; import type { ButtonBuilder, ChatInputCommandInteraction } from 'discord.js'; -import { noOp, notEmpty, percentChance, randArrItem, shuffleArr, uniqueArr } from 'e'; +import { notEmpty, percentChance, uniqueArr } from 'e'; import { Bank } from 'oldschooljs'; import { ClueTiers } from '../../../lib/clues/clueTiers'; import { buildClueButtons } from '../../../lib/clues/clueUtils'; import { BitField, Emoji } from '../../../lib/constants'; import { type UnifiedOpenable, allOpenables, getOpenableLoot } from '../../../lib/openables'; -import { roboChimpUserFetch } from '../../../lib/roboChimp'; -import { assert, itemNameFromID, makeComponents } from '../../../lib/util'; +import { assert, makeComponents } from '../../../lib/util'; import { checkElderClueRequirements } from '../../../lib/util/elderClueRequirements'; import getOSItem, { getItem } from '../../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; import itemID from '../../../lib/util/itemID'; import { makeBankImage } from '../../../lib/util/makeBankImage'; import resolveItems from '../../../lib/util/resolveItems'; -import { addToOpenablesScores, patronMsg, updateClientGPTrackSetting, userStatsBankUpdate } from '../../mahojiSettings'; +import { addToOpenablesScores, patronMsg, userStatsBankUpdate } from '../../mahojiSettings'; const regex = /^(.*?)( \([0-9]+x Owned\))?$/; @@ -58,14 +57,12 @@ export async function abstractedOpenUntilCommand(userID: string, name: string, o const loot = new Bank(); let amountOpened = 0; const max = Math.min(100, amountOfThisOpenableOwned); - const totalLeaguesPoints = (await roboChimpUserFetch(user.id)).leagues_points_total; for (let i = 0; i < max; i++) { cost.add(openable.openedItem.id); const thisLoot = await getOpenableLoot({ openable, quantity: 1, - user, - totalLeaguesPoints + user }); loot.add(thisLoot.bank); amountOpened++; @@ -131,7 +128,6 @@ async function finalizeOpening({ if (hasSmokey || hasOcto) { const bonuses = []; - const totalLeaguesPoints = (await roboChimpUserFetch(user.id)).leagues_points_total; for (const openable of openables) { if (!openable.smokeyApplies) continue; const bonusChancePercent = hasSmokey ? 10 : 8; @@ -152,8 +148,7 @@ async function finalizeOpening({ await getOpenableLoot({ user, openable, - quantity: smokeyBonus, - totalLeaguesPoints + quantity: smokeyBonus }) ).bank ); @@ -170,50 +165,15 @@ async function finalizeOpening({ return `You don't own: ${cost}.`; } await transactItems({ userID: user.id, itemsToRemove: cost }); - const { previousCL } = await user.addItemsToBank({ + const { previousCL, itemsAdded } = await user.addItemsToBank({ items: loot, collectionLog: true, filterLoot: false, dontAddToTempCL: openables.some(i => itemsThatDontAddToTempCL.includes(i.id)) }); - const fakeTrickedLoot = loot.clone(); - - const openableWithTricking = openables.find(i => 'trickableItems' in i); - if ( - openableWithTricking && - 'trickableItems' in openableWithTricking && - openableWithTricking.trickableItems !== undefined - ) { - const activeTrick = await prisma.mortimerTricks.findFirst({ - where: { - target_id: user.id, - completed: false - } - }); - if (activeTrick) { - // Pick a random item not in CL, or just a random one if all are in CL - const trickedItem = - shuffleArr(openableWithTricking.trickableItems).find(i => !user.cl.has(i)) ?? - randArrItem(openableWithTricking.trickableItems); - fakeTrickedLoot.add(trickedItem); - const trickster = await globalClient.users.fetch(activeTrick.trickster_id).catch(noOp); - trickster - ?.send(`You just tricked ${user.rawUsername} into thinking they got a ${itemNameFromID(trickedItem)}!`) - .catch(noOp); - await prisma.mortimerTricks.update({ - where: { - id: activeTrick.id - }, - data: { - completed: true - } - }); - } - } - const image = await makeBankImage({ - bank: fakeTrickedLoot, + bank: itemsAdded, title: openables.length === 1 ? `Loot from ${cost.amount(openables[0].openedItem.id)}x ${openables[0].name}` @@ -223,10 +183,6 @@ async function finalizeOpening({ mahojiFlags: user.bitfield.includes(BitField.DisableOpenableNames) ? undefined : ['show_names'] }); - if (loot.has('Coins')) { - await updateClientGPTrackSetting('gp_open', loot.amount('Coins')); - } - const openedStr = openables .map(({ openedItem }) => `${newOpenableScores.amount(openedItem.id)}x ${openedItem.name}`) .join(', '); @@ -295,8 +251,6 @@ export async function abstractedOpenCommand( const loot = new Bank(); const messages: string[] = []; - const totalLeaguesPoints = (await roboChimpUserFetch(user.id)).leagues_points_total; - for (const openable of openables) { const { openedItem } = openable; const quantity = typeof _quantity === 'string' ? user.bank.amount(openedItem.id) : _quantity; @@ -310,8 +264,7 @@ export async function abstractedOpenCommand( const thisLoot = await getOpenableLoot({ openable, quantity, - user, - totalLeaguesPoints + user }); loot.add(thisLoot.bank); if (thisLoot.message) messages.push(thisLoot.message); diff --git a/src/mahoji/lib/abstracted_commands/pohCommand.ts b/src/mahoji/lib/abstracted_commands/pohCommand.ts index 069f60b8f4..215cde1020 100644 --- a/src/mahoji/lib/abstracted_commands/pohCommand.ts +++ b/src/mahoji/lib/abstracted_commands/pohCommand.ts @@ -2,7 +2,6 @@ import { stringMatches } from '@oldschoolgg/toolkit'; import type { ChatInputCommandInteraction } from 'discord.js'; import { Bank } from 'oldschooljs'; -import { BitField } from '../../../lib/constants'; import { GroupedPohObjects, PoHObjects, getPOHObject, itemsNotRefundable } from '../../../lib/poh'; import { pohImageGenerator } from '../../../lib/pohImage'; @@ -10,18 +9,12 @@ import { SkillsEnum } from '../../../lib/skilling/types'; import { formatSkillRequirements, itemNameFromID } from '../../../lib/util'; import getOSItem from '../../../lib/util/getOSItem'; import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; export const pohWallkits = [ { bitfield: null, name: 'Default', imageID: 1 - }, - { - bitfield: BitField.HasHosidiusWallkit, - name: 'Hosidius', - imageID: 2 } ]; @@ -36,46 +29,6 @@ export async function makePOHImage(user: MUser, showSpaces = false) { return { files: [{ attachment: buffer, name: 'image.jpg' }] }; } -export async function pohWallkitCommand(user: MUser, input: string) { - const poh = await getPOH(user.id); - const currentWallkit = pohWallkits.find(i => i.imageID === poh.background_id)!; - const selectedKit = pohWallkits.find(i => stringMatches(i.name, input)); - - if (!input || !selectedKit) { - return `Your current wallkit is the '${currentWallkit.name}' wallkit. The available wallkits are: ${pohWallkits - .map(i => i.name) - .join(', ')}.`; - } - - if (currentWallkit.imageID === selectedKit.imageID) { - return 'This is already your wallkit.'; - } - - const { bitfield } = user; - const userBank = user.bank; - if (selectedKit.bitfield && !bitfield.includes(BitField.HasHosidiusWallkit)) { - if (selectedKit.imageID === 2 && userBank.has('Hosidius blueprints')) { - await user.removeItemsFromBank(new Bank().add('Hosidius blueprints')); - await user.update({ - bitfield: { - push: selectedKit.bitfield - } - }); - } else { - return `You haven't unlocked the ${selectedKit.name} wallkit!`; - } - } - await prisma.playerOwnedHouse.update({ - where: { - user_id: user.id - }, - data: { - background_id: selectedKit.imageID - } - }); - return makePOHImage(user); -} - export async function pohBuildCommand(interaction: ChatInputCommandInteraction, user: MUser, name: string) { const poh = await getPOH(user.id); @@ -118,7 +71,6 @@ export async function pohBuildCommand(interaction: ChatInputCommandInteraction, } await handleMahojiConfirmation(interaction, str); await user.removeItemsFromBank(obj.itemCost); - updateBankSetting('construction_cost_bank', obj.itemCost); } let refunded: Bank | null = null; @@ -216,10 +168,11 @@ export async function pohDestroyCommand(user: MUser, name: string) { [obj.slot]: null } }); - await user.addItemsToBank({ items: { [inPlace!]: 1 }, collectionLog: false }); + const loot = new Bank().add(inPlace!); + await user.addItemsToBank({ items: loot, collectionLog: false }); return { ...(await makePOHImage(user)), - content: `You removed a ${obj.name} from your house, and were refunded 1x ${itemNameFromID(inPlace!)}.` + content: `You removed a ${obj.name} from your house, and were refunded ${loot}.` }; } if (inPlace !== obj.id) { diff --git a/src/mahoji/lib/abstracted_commands/roguesDenCommand.ts b/src/mahoji/lib/abstracted_commands/roguesDenCommand.ts index 32656a6844..b61a8a427d 100644 --- a/src/mahoji/lib/abstracted_commands/roguesDenCommand.ts +++ b/src/mahoji/lib/abstracted_commands/roguesDenCommand.ts @@ -6,7 +6,6 @@ import { SkillsEnum } from '../../../lib/skilling/types'; import type { MinigameActivityTaskOptionsWithNoChanges } from '../../../lib/types/minions'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; export async function roguesDenCommand(user: MUser, channelID: string) { if (user.minionIsBusy) return `${user.minionName} is busy.`; @@ -47,7 +46,6 @@ export async function roguesDenCommand(user: MUser, channelID: string) { if (staminasToRemove.length > 0) { await user.removeItemsFromBank(staminasToRemove); - await updateBankSetting('rogues_den_cost', staminasToRemove); } await addSubTaskToActivityTask({ diff --git a/src/mahoji/lib/abstracted_commands/sawmillCommand.ts b/src/mahoji/lib/abstracted_commands/sawmillCommand.ts index 8a70b8b874..770c5d967e 100644 --- a/src/mahoji/lib/abstracted_commands/sawmillCommand.ts +++ b/src/mahoji/lib/abstracted_commands/sawmillCommand.ts @@ -6,7 +6,6 @@ import type { SawmillActivityTaskOptions } from '../../../lib/types/minions'; import { formatDuration, itemNameFromID, stringMatches, toKMB } from '../../../lib/util'; import addSubTaskToActivityTask from '../../../lib/util/addSubTaskToActivityTask'; import { calcMaxTripLength } from '../../../lib/util/calcMaxTripLength'; -import { updateBankSetting } from '../../../lib/util/updateBankSetting'; import { userHasGracefulEquipped } from '../../mahojiSettings'; export async function sawmillCommand( @@ -81,8 +80,6 @@ export async function sawmillCommand( const costBank = new Bank().add('Coins', cost).add(plank!.inputItem, quantity); await user.removeItemsFromBank(costBank); - await updateBankSetting('construction_cost_bank', new Bank().add('Coins', cost)); - await addSubTaskToActivityTask({ type: 'Sawmill', duration, diff --git a/src/mahoji/lib/abstracted_commands/slayerTaskCommand.ts b/src/mahoji/lib/abstracted_commands/slayerTaskCommand.ts index bfe56def4f..99acc53ba4 100644 --- a/src/mahoji/lib/abstracted_commands/slayerTaskCommand.ts +++ b/src/mahoji/lib/abstracted_commands/slayerTaskCommand.ts @@ -300,6 +300,11 @@ export async function slayerNewTaskCommand({ await userStatsUpdate(user.id, { [taskStreakKey]: 0 }, {}); const newSlayerTask = await assignNewSlayerTask(user, slayerMaster); + if (typeof newSlayerTask === 'string') { + interactionReply(interaction, newSlayerTask); + return; + } + const commonName = getCommonTaskName(newSlayerTask.assignedTask.monster); const returnMessage = `Your task has been skipped.\n\n ${slayerMaster.name}` + @@ -350,6 +355,10 @@ export async function slayerNewTaskCommand({ } const newSlayerTask = await assignNewSlayerTask(user, slayerMaster); + if (typeof newSlayerTask === 'string') { + interactionReply(interaction, newSlayerTask); + return; + } let commonName = getCommonTaskName(newSlayerTask.assignedTask.monster); if (commonName === 'TzHaar') { diff --git a/src/mahoji/lib/abstracted_commands/slotsCommand.ts b/src/mahoji/lib/abstracted_commands/slotsCommand.ts deleted file mode 100644 index 6ebe0f0d64..0000000000 --- a/src/mahoji/lib/abstracted_commands/slotsCommand.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { SimpleTable } from '@oldschoolgg/toolkit'; -import type { CommandResponse } from '@oldschoolgg/toolkit'; -import type { BaseMessageOptions, ChatInputCommandInteraction } from 'discord.js'; -import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; -import { chunk, noOp, randInt, shuffleArr, sleep } from 'e'; -import { Bank } from 'oldschooljs'; -import { toKMB } from 'oldschooljs/dist/util'; - -import { channelIsSendable } from '../../../lib/util'; -import { handleMahojiConfirmation } from '../../../lib/util/handleMahojiConfirmation'; -import { deferInteraction } from '../../../lib/util/interactionReply'; -import { mahojiParseNumber, updateClientGPTrackSetting, updateGPTrackSetting } from '../../mahojiSettings'; - -interface Button { - name: string; - emoji: string; - mod: (qty: number) => number; -} - -interface ButtonInstance extends Button { - id: string; -} - -const buttonsData: Button[] = [ - { - name: 'Peky', - mod: (qty: number) => qty * 2, - emoji: '886284972263084133' - }, - { - name: 'Wintertoad', - mod: (qty: number) => qty * 3, - emoji: '886284972141457439' - }, - { - name: 'Flappy', - mod: (qty: number) => qty * 5, - emoji: '884799334737129513' - }, - { - name: 'Smokey', - mod: (qty: number) => qty * 15, - emoji: '886284971914969149' - } -]; - -const buttonTable = new SimpleTable