From efcb35077facc2207cf388de903a50f580603edf Mon Sep 17 00:00:00 2001 From: GregHib Date: Sat, 21 Feb 2026 19:43:00 +0000 Subject: [PATCH 01/20] Remove imports --- .../kotlin/content/entity/player/modal/tab/QuestJournals.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/game/src/main/kotlin/content/entity/player/modal/tab/QuestJournals.kt b/game/src/main/kotlin/content/entity/player/modal/tab/QuestJournals.kt index a26762d51f..06d53a3539 100644 --- a/game/src/main/kotlin/content/entity/player/modal/tab/QuestJournals.kt +++ b/game/src/main/kotlin/content/entity/player/modal/tab/QuestJournals.kt @@ -2,10 +2,15 @@ package content.entity.player.modal.tab import com.github.michaelbull.logging.InlineLogger import content.quest.refreshQuestJournal +import world.gregs.voidps.cache.definition.data.InterfaceDefinition import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.clearCamera +import world.gregs.voidps.engine.client.sendInterfaceSettings +import world.gregs.voidps.engine.client.sendScript import world.gregs.voidps.engine.client.ui.InterfaceApi import world.gregs.voidps.engine.data.definition.QuestDefinitions +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.queue.softQueue import world.gregs.voidps.engine.timer.Timer class QuestJournals(val questDefinitions: QuestDefinitions) : Script { From de955847dd2b46b1a1f8bcadd301a1e9e5708579 Mon Sep 17 00:00:00 2001 From: GregHib Date: Sat, 21 Feb 2026 21:49:10 +0000 Subject: [PATCH 02/20] Formatting --- .../kotlin/content/entity/player/modal/tab/QuestJournals.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/game/src/main/kotlin/content/entity/player/modal/tab/QuestJournals.kt b/game/src/main/kotlin/content/entity/player/modal/tab/QuestJournals.kt index 06d53a3539..a26762d51f 100644 --- a/game/src/main/kotlin/content/entity/player/modal/tab/QuestJournals.kt +++ b/game/src/main/kotlin/content/entity/player/modal/tab/QuestJournals.kt @@ -2,15 +2,10 @@ package content.entity.player.modal.tab import com.github.michaelbull.logging.InlineLogger import content.quest.refreshQuestJournal -import world.gregs.voidps.cache.definition.data.InterfaceDefinition import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.clearCamera -import world.gregs.voidps.engine.client.sendInterfaceSettings -import world.gregs.voidps.engine.client.sendScript import world.gregs.voidps.engine.client.ui.InterfaceApi import world.gregs.voidps.engine.data.definition.QuestDefinitions -import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.queue.softQueue import world.gregs.voidps.engine.timer.Timer class QuestJournals(val questDefinitions: QuestDefinitions) : Script { From f5a8b39862c12d8f99ccf23074c795e53fd380f7 Mon Sep 17 00:00:00 2001 From: GregHib Date: Sat, 21 Feb 2026 21:49:39 +0000 Subject: [PATCH 03/20] Start adding wise old man dialogues and tasks --- .../wise_old_man/wise_old_man.enums.toml | 113 +++++ .../wise_old_man/wise_old_man.varbits.toml | 4 + .../wise_old_man/wise_old_man.vars.toml | 11 + .../misthalin/draynor_village/WiseOldMan.kt | 397 ++++++++++++++++++ 4 files changed, 525 insertions(+) create mode 100644 data/area/misthalin/draynor/wise_old_man/wise_old_man.enums.toml create mode 100644 data/area/misthalin/draynor/wise_old_man/wise_old_man.varbits.toml create mode 100644 data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml create mode 100644 game/src/main/kotlin/content/area/misthalin/draynor_village/WiseOldMan.kt diff --git a/data/area/misthalin/draynor/wise_old_man/wise_old_man.enums.toml b/data/area/misthalin/draynor/wise_old_man/wise_old_man.enums.toml new file mode 100644 index 0000000000..ab71984f40 --- /dev/null +++ b/data/area/misthalin/draynor/wise_old_man/wise_old_man.enums.toml @@ -0,0 +1,113 @@ +[wise_old_man_tasks] +key = "items" +type = "string" +values = { + anchovies = "I could do with some freshly cooked anchovies for a salad I'm planning.", + beer_glass = "My glassware got damaged when I moved here, so I need some new beer glasses.", + bones = "My plans for today require some sets of bones, the normal-sized sort you get from goblins.", + bronze_arrow = "I'm short of ammunition, so I could do with some bronze arrows. That way I can shoot at goblins through my window.", + bronze_bar = "I need some bronze bars.", + bronze_dagger = "I need a few bronze daggers to keep the goblins at bay.", + bronze_hatchet = "The head fell off my hatchet when I was chopping wood last week, so I need some more bronze hachets.", + bronze_mace = "I'd like some maces made of bronze.", + bronze_med_helm = "I could do with some medium-sized helmets. I'd like bronze ones.", + bronze_spear = "I need some bronze spears.", + bronze_sword = "My weapons collection isn't all it used to be. Some short swords made of bronze would be much appreciated.", + beer = "Strange as it may seem, I want a lot of beer!", + cadava_berries = "I've found a use for cadava berries.", + cooked_chicken = "I'm running short of food, so I'd like some cooked chickens.", + cooked_meat = "I'm a bit hungry. I'd like some cooked meat, not chicken or any sort of bird. And definitely not camel or rabbit either!", + copper_ore = "I need a few lumps of copper ore.", + cowhide = "I'd like a few cowhides.", + egg = "I'm going to make an omelette, so I'll need some eggs.", + feather = "I need a handful of feathers to stick in my beard.", + grain = "I'd like a bit of grain.", + iron_bar = "I need some iron bars.", + iron_mace = "Some iron maces would be useful.", + iron_ore = "A few lumps of iron ore would be nice.", + soft_clay = "I'll need some clay that's been softened so I can craft it.", + leather_gloves = "It's a bit nippy in this house, and my hands are cold. I need some leather gloves.", + logs = "This house is a bit cold, so I could do with some normal logs to burn.", + molten_glass = "Some chunks of molten glass would be the ideal patch for my cracked window.", + potato = "I need some potatoes, if you'd be so kind.", + raw_rat_meat = "I hear pet cats are getting popular these days. I'd like some raw rat in case I get one.", + rune_essence = "I'd like to study the rune essence that the wizards have been talking about recently, so I need a few pieces.", + shrimps = "I could do with some freshly cooked shrimps for a salad I'm planning.", + silk = "My undergarments are getting a bit worn out. I'll be needing some sheets of silk to patch them.", + leather = "I'll be needing a few pieces of soft leather.", + tin_ore = "I need a few lumps of tin ore.", + ball_of_wool = "I saw an interesting bed in Karamja called a 'hammock'. It seemed to be made out of string, and I'd like some balls of wool so I can make my own.", + bow_string = "My shortbow's string is getting a bit worn out, so could you fetch me some bow strings?", + bread = "I don't have a decent larder here, so I can't store food very well. Now I'm out of loaves of bread.", + bronze_arrowtips = "I need a few bronze arrowheads to complete some arrows I was making.", + bronze_knife = "I'd like some bronze knives to throw at goblins.", + bronze_warhammer = "Could you fetch me some big warhammers made of bronze?", + bronze_wire = "I need some lengths of bronze wire to repair something.", + headless_arrow = "I want to make some arrows, so I'll need some headless arrows that have the feathers attached.", + swamp_paste = "My roof is leaking, so I need some swamp paste to fix it.", + iron_arrowtips = "I need some iron arrowheads to put on the arrows I was making.", + iron_knife = "I could do with some iron throwing knives to keep the goblins away from my house.", + iron_warhammer = "I'd like some chunky iron warhammers.", + leather_cowl = "The hail can be very heavy here, so I'd like a few leather cowls.", + pot_of_flour = "I'm out of flour, so I could do with a few pots of that.", + unfired_pie_dish = "Strange as this may seem, I need some unfired pie dishes.", + unfired_pot = "Believe it or not, I need a few unfired clay pots.", + leather_boots = "My footwear is getting a bit worn out, so I need a few pairs of leather boots.", +} + +[wise_old_man_hints] +key = "items" +type = "string" +values = { + anchovies = "Use a small fishing net to get anchovies from the sea south of here. Then cook them on a fire or range.", + beer_glass = "If you get some seaweed and a bucket of sand, you'll be able to mix them at a furnace to make molten glass. Then use a glassblowing pipe to make beer glasses. Most of what you need can be found on Entrana.", + bones = "You'll find bones easily enough if you fight creatures.", + bronze_arrow = "You can make them by chopping a log into arrowshafts and adding feathers to them, then smithing a bronze bar into arrowheads to complete the arrows.", + bronze_bar = "Mine some copper ore and tin ore. The furnace in Lumbridge can smelt them into bars of bronze.", + bronze_dagger = "Try smelting some copper and tin ore to make bronze. Then hammer the bronze on an anvil to make daggers.", + bronze_hatchet = "Try smelting some copper and tin ore to make bronze. Then hammer the bronze on an anvil to make hatchets.", + bronze_mace = "Try smelting some copper and tin ore to make bronze. Then hammer the bronze on an anvil to make maces.", + bronze_med_helm = "Try smelting some copper and tin ore to make bronze. Then hammer the bronze on an anvil to make helmets.", + bronze_spear = "If you kill some of those pesky goblins, you're bound to get some bronze spears.", + bronze_sword = "Try smelting some copper and tin ore to make bronze. Then hammer the bronze on an anvil to make short swords.", + beer = "There's a pub not far from here that will sell you plenty of that. Go west to Port Sarim.", + cadava_berries = "Just look around the woods south-west of Varrock.", + cooked_chicken = "You could try killing some chickens, then cooking their meat.", + cooked_meat = "Just cook some beef, rat or bear.", + copper_ore = "It should be easy enough for you to mine some copper ore.", + cowhide = "If you slaughter some cows, you'll get cowhides easily enough.", + egg = "Eggs are usually found where chickens are farmed.", + feather = "The cheapest way to get feathers is to kill chickens.", + grain = "There's a field full of it north of here.", + iron_bar = "Mine some iron ore. The furnace in Lumbridge can smelt them into bars of iron.", + iron_mace = "Try smelting some iron. Then hammer the bronze on an anvil to make maces.", + iron_ore = "It should be easy enough for you to mine some iron ore.", + soft_clay = "If you mine some clay, you can use containers of water on it to soften it.", + leather_gloves = "If you get the tanner in Al Kharid to turn some cowhides into leather, you'll be able to sew the gloves yourself. Buy a needle & thread from the crafting shop in Al Kharid.", + logs = "I suggest you take a hatchet and chop down some standard trees.", + molten_glass = "Use a bucket of sand with some soda ash on a furnace.", + potato = "There's a field of those north of Lumbridge.", + raw_rat_meat = "You should find some big rats south-east of here in the swamp.", + rune_essence = "Ask the Archmage in the tower south of here to teleport you to the Rune Essence mine. Then you can mine me some pieces.", + shrimps = "Use a small fishing net to get shrimps from the sea south of here. Then cook them on a fire or range.", + silk = "There's a man in Al Kharid who sells it very cheaply. If you ever take any to Ardougne you'll get a good profit.", + leather = "There's a tanner in Al Kharid who will turn cowhides into leather.", + tin_ore = "You shouldn't have any trouble mining tin ore.", + ball_of_wool = "You can buy shears from the general store east of here. Shear some sheep, then spin the wool into balls on the spinning wheel in Lumbridge Castle.", + bow_string = "If you pick some flax, you can use the spinning wheel in Lumbridge Castle to spin it into bowstrings.", + bread = "Mix some flour and water to make bread dough, then bake it on a cooking range.", + bronze_arrowtips = "Try smelting some copper and tin ore to make bronze. Then hammer the bronze on an anvil to make arrowtips.", + bronze_knife = "Try smelting some copper and tin ore to make bronze. Then hammer the bronze on an anvil to make knives.", + bronze_warhammer = "Try smelting some copper and tin ore to make bronze. Then hammer the bronze on an anvil to make warhammers.", + bronze_wire = "Try smelting some copper and tin ore to make bronze. Then hammer the bronze on an anvil to make wire.", + headless_arrow = "Use a knife to chop some logs into arrowshafts. Then add a feather to each arrowshaft.", + swamp_paste = "Swamp tar is found south-east of here. Add it to a pot of flour and cook it on an open fire to make the paste.", + iron_arrowtips = "Try smelting some iron. Then hammer the bronze on an anvil to make arrowtips.", + iron_knife = "Try smelting some iron. Then hammer the bronze on an anvil to make knives.", + iron_warhammer = "Try smelting some iron. Then hammer the bronze on an anvil to make warhammers.", + leather_cowl = "If you get the tanner in Al Kharid to turn some cowhides into leather, you'll be able to make the cowls yourself. Buy needles & thread from the crafting shop in Al Kharid if you need any.", + pot_of_flour = "There's a windmill north of here. You can use it to grind grain into flour. Then use an empty pot to collect the flour.", + unfired_pie_dish = "Mine some clay. Then use a container full of water to soften it. In the Barbarian Village you'll be able to form this into pie dishes. Just don't bake them.", + unfired_pot = "Mine some clay. Then use a container full of water to soften it. In the Barbarian Village you'll be able to form this into pots. Just don't bake them.", + leather_boots = "If you get the tanner in Al Kharid to turn some cowhides into leather, you'll be able to make the boots yourself. Buy needles & thread from the crafting shop in Al Kharid if you need any.", +} \ No newline at end of file diff --git a/data/area/misthalin/draynor/wise_old_man/wise_old_man.varbits.toml b/data/area/misthalin/draynor/wise_old_man/wise_old_man.varbits.toml new file mode 100644 index 0000000000..7a353e395c --- /dev/null +++ b/data/area/misthalin/draynor/wise_old_man/wise_old_man.varbits.toml @@ -0,0 +1,4 @@ +[wise_old_man_met] +id = 596 +persist = true +format = "boolean" \ No newline at end of file diff --git a/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml b/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml new file mode 100644 index 0000000000..eacc40f3d3 --- /dev/null +++ b/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml @@ -0,0 +1,11 @@ +[wise_old_man_task] +format = "string" +persist = true + +[wise_old_man_amount] +format = "int" +persist = true + +[wise_old_man_remaining] +format = "int" +persist = true diff --git a/game/src/main/kotlin/content/area/misthalin/draynor_village/WiseOldMan.kt b/game/src/main/kotlin/content/area/misthalin/draynor_village/WiseOldMan.kt new file mode 100644 index 0000000000..d478ddb727 --- /dev/null +++ b/game/src/main/kotlin/content/area/misthalin/draynor_village/WiseOldMan.kt @@ -0,0 +1,397 @@ +package content.area.misthalin.draynor_village + +import content.entity.player.dialogue.Bored +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Laugh +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Quiz +import content.entity.player.dialogue.Sad +import content.entity.player.dialogue.Shifty +import content.entity.player.dialogue.Shock +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player +import content.quest.questCompleted +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.data.definition.EnumDefinitions +import world.gregs.voidps.engine.data.definition.ItemDefinitions +import world.gregs.voidps.engine.entity.World +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.name +import world.gregs.voidps.type.random + +class WiseOldMan( + val enums: EnumDefinitions, +) : Script { + init { + npcOperate("Talk-to", "wise_old_man") { + npc("Greetings, $name.") + if (get("wise_old_man_met", false)) { + choice("What would you like to say?") { + if (contains("wise_old_man_task")) { + option("What did you ask me to do?") { + checkTask() + } + } else { + option("Is there anything I can do for you?") { + task() + } + } + option("Could you check my items for junk, please?") { + choice { + option("Could you check my bank for junk, please?") { + npc("Certainly, but I should warn you that I don't know about all items.") + // TODO add junk search + npc("There doesn't seem to be any junk in your bank at all.") + } + option("Could you check my inventory for junk, please?") { + npc("Certainly, but I should warn you that I don't know about all items.") + // TODO add junk search + npc("There doesn't seem to be any junk in your inventory at all.") + } +// if (follower != null) { // TODO and has BoB +// option("Could you check my beast of burden for junk, please?") +// } + } + } + option("I'd just like to ask you something.") + } + return@npcOperate + } + intro() + } + } + + private suspend fun Player.intro() { + player("So you're a wise old man, huh?") + npc("Less of the 'old' man, if you please!") + npc("But yes, I suppose you could say that. I prefer to think of myself as a sage.") + player("So what's a sage doing here?") + npc("I've spent most of my life studying this world in which we live. I've strode through the depths of the deadliest dungeons, roamed the murky jungles of Karamja, meditated on the glories of Saradomin on Entrana,") + npc("and read dusty tomes in the Library of Varrock.") + npc("Now I'm not as young as I used to be, I'm settling here where it's peaceful.") + if (!questCompleted("vampire_slayer")) { + npc("It's a pity about that vampyre that keeps attacking the village. At least Saradomin protects me.") + } + player("That's quite an exciting life you've had.") + npc("Exciting? Yes, I suppose so.") + npc("Now I'm here, perhaps I could offer you the benefit of my experience and wisdom?") + player("Thanks! So how can you help me?") + set("wise_old_man_met", true) + npc("Well, I imagine you've gathered up quite a lot of stuff on your travels. Things you used for quests a long time ago that you don't need now.") + npc("If you like, I can look through your bank and see if there's anything you can chuck away.") + npc("Alternatively, you can bring items here and show them to me. If I see that it's something you don't need, I'll let you know. I might even be willing to buy it.") + player("So you'll help me clear junk out of my bank?") + npc("Yes, that's right. Or I'd be happy to chat with you about the wonders of this world!") + choice("What would you like to say?") { + option("Could I have some free stuff, please?") { + npc("Deary deary me...") + if (!World.members) { + npc("I'm not giving out free money, but if you log into a members' world I'd be glad to reward you if you'd do a little job for me.") + return@option + } + npc("I'm not giving out free money, but I'd be happy to reward you if you'll do a little job for me.") + choice("What would you like to say?") { + option("Ok, what do you want me to do?") + option("Thanks, maybe some other time.") { + npc("As you wish. Farewell, $name.") + } + } + } + option("I'd just like to ask you something.") { + npc("Please do!") + topic() + } + option("Is there anything I can do for you?") + option("Could you check my items for junk, please?") + option("Thanks, maybe some other time.") + } + } + + private suspend fun Player.topic() { + choice("Pick a topic") { + option("Distant lands") { + choice("Pick a topic") { + option("The Wilderness") { + player("Could you tell me about the Wilderness, please?") + npc("If Entrana is a land dedicated to the glory of Saradomin, the Wilderness is surely the land of Zamorak.") + npc("It's a dangerous place, where adventurers such as yourself may attack each other, using all their combat skills in the struggle for survival.") + npc("The Wilderness has different levels. In a low level area, you can only fight adventurers whose combat level is close to yours.") + npc("But if you venture into the high level areas in the far north, you can be attacked by adventurers who are significantly stronger than you.") + npc("Of course, you'd be able to attack considerably weaker people too, so it can be worth the risk.") + npc("If you dare to go to the far north-west of the Wilderness, there's a building called the Mage Arena where you can learn to summon the power of Saradomin himself!") + npc("Is there anything else you'd like to ask?") + anythingElse() + } + option("Misty jungles") { + player("What can you tell me about jungles?") + npc("If it's jungle you want, look no further than the southern regions of Karamja.") + npc("Once you get south of Brimhaven, the whole island is pretty much covered in exotic trees, creepers and shrubs.") + npc("There's a small settlement called Tai Bwo Wannai Village in the middle of the island. It's a funny place; the chieftain's an unfriendly chap and his sons are barking mad.") + npc("Honestly, one of them asked me to stuff a dead monkey with seaweed so he could EAT it!") + npc("Further south you'll find Shilo Village. It's been under attack by terrifying zombies in recent months, if my sources are correct.") + npc("The jungle's filled with nasty creatures. There are vicious spiders that you can hardly see before they try to bite your legs off, and great big jungle ogres.") + npc("Is there anything else you'd like to ask?") + anythingElse() + } + option("Underground domains") { + player("Tell me about what's underground.") + npc("Oh, the dwarven realms?") + npc("Yes, there was a time, back in the Fourth Age, when we humans wouldn't have been able to venture underground. That was before we had magic; the dwarves were quite a threat.") + npc("Still, it's much more friendly now. You can visit the vast dwarven mine if you like; the entrance is on the mountain north of Falador.") + npc("If you go further west you may be able to visit the dwarven city of Keldagrim. But they were a bit cautious about letting humans in, last time I asked.") + npc("On the other hand, if you go west of Brimhaven, you'll find a huge underground labyrinth full of giants, demons, dogs and dragons to fight. It's even bigger than the caves under Taverley, although the Taverley") + npc("dungeon's pretty good for training your combat skills.") + npc("Is there anything else you'd like to ask?") + anythingElse() + } + option("Mystical realms") { + player("What mystical realms can I visit?") + npc("The fabled Lost City of Zanaris has an entrance somewhere near here. Perhaps some day you'll go there.") + npc("Also, in my research I came across ancient references to some kind of Abyss. Demons from the Abyss have already escaped into this land; Saradomin be thanked that they are very rare!") + npc("Is there anything else you'd like to ask?") + anythingElse() + } + } + } + option("Strange beasts") { + choice("Pick a topic") { + option("Biggest & Baddest") { + player("What's the biggest monster in the world?") + npc("There's a mighty fire-breathing dragon living underground in the deep Wilderness, known as the King Black Dragon. It's a fearsome beast, with a breath that can poison you, freeze you to the ground or") + npc("incinerate you where you stand.") + npc("But even more deadly is the Queen of the Kalphites. As if her giant mandibles of death were not enough, she also throws her spines at her foes with deadly force. She can even cast rudimentary spells.") + npc("Some dark power must be protecting her, for she can block attacks using prayer just as humans do.") + npc("Another beast that's worthy of a special mention is the Shaikahan. It dwells in the eastern reaches of Karamja, and is almost impossible to kill except with specially prepared weapons.") + npc("Is there anything else you'd like to ask?") + anythingElse() + } + option("Poison and how to survive it") { + player("What does poison do?") + npc("Many monsters use poison against their foes. If you get poisoned, you will not feel it at the time, but later you will begin to suffer its effects, and your life will drain slowly from you.") + } + option("Wealth through slaughter") { + player("What monsters drop good items?") + npc("As a general rule, tougher monsters drop more valuable items. But even a lowly hobgoblin can drop valuable gems; it just does this extremely rarely.") + npc("If you can persuade the Slayer Masters to train you as a Slayer, you will be able to fight certain monsters that drop valuable items far more often.") + npc("You might care to invest in an enchanted dragonstone ring. These are said to make a monster drop its most valuable items a little more often.") + npc("Is there anything else you'd like to ask?") + anythingElse() + } + option("Random events") { + player("What are these strange monsters that keep appearing out of nowhere and attacking me when I'm training?") + npc("Ah, I imagine you see a lot of those.") + npc("Creatures such as the rock golem, river troll and tree spirit dwell in places where adventurers frequently go to train their skills. While you're training you will often disturb one by accident. It will then get angry and") + npc("attack you. The safest way to deal with them is to run away immediately, but they sometimes drop valuable items if you kill them.") + npc("Is there anything else you'd like to ask?") + anythingElse() + } + } + } + option("Days gone by") { + choice("Pick a topic") { + option("Heroic figures") { + player("Tell me about valiant heroes!") + npc("Ha ha ha... There are plenty of heroes. Always have been, always will be, until the fall of the world.") + npc("If you'd do a few more quests, you'd soon become a fairly noted adventurer yourself.") + npc("But I suppose I could tell you of a couple...") + npc("Yes, there was a man called Arrav. No-one knew where he came from, but he was a fearsome fighter, a skillful hunter and a remarkable farmer. He lived in the ancient settlement of Avarrocka, defending it from") + npc("goblins, until he went forth in search of some strange artefact long desired by the dreaded Mahjarrat.") + npc("Perhaps some day I shall be able to tell you what became of him.") + npc("But do not let your head be turned by heroics. Randas was another great man, but he let himself be beguiled into turning to serve Zamorak, and they say he is now a mindless creature deep in the Underground Pass that") + npc("leads to Isafdar.") + npc("Is there anything else you'd like to ask?") + anythingElse() + } + option("The origin of magic") { + player("Where did humans learn to use magic?") + npc("Ah, that was quite a discovery! It revolutionised our way of life and jolted us into this Fifth Age of the world.") + npc("They say a traveller in the north discovered the key, although no records state exactly what he found. From this he was able to summon the magic of the four elements, using magic as a tool and a weapon.") + npc("He and his followers then learnt how to bind the power into runes so that others could use it.") + npc("In the land south of here they constructed an immense tower where the power could be studied, but followers of Zamorak destroyed it with fire many years ago, and much of the knowledge was lost.") + npc("Perhaps one day those lost secrets will be uncovered once more.") + npc("Is there anything else you'd like to ask?") + anythingElse() + } + option("Settlements") { + player("I suppose you'd know about the history of today's cities?") + npc("Yes, there are fairly good records of the formation of the cities from primitive settlements.") + npc("In the early part of the Fourth Age, of course, there were no permanent settlements. Tribes wandered the lands, staying where they could until the resources were exhausted.") + npc("This changed as people learnt to grow crops and breed animals, and now there are very few of the old nomadic tribes. There's at least one tribe roaming between the Troll Stronghold and Rellekka, though.") + npc("One settlement was Avarrocka, a popular trading centre.") + npc("In the west, Ardougne gradually formed under the leadership of the Carnillean family, despite the threat of the Mahjarrat warlord Hazeel who dwelt in that area until his downfall.") + npc("Is there anything else you'd like to ask?") + anythingElse() + } + option("The Wise Old Man of Draynor Village") { + player("Tell me about yourself, old man.") + npc("Ah, so you want to know about me, eh?") + npc("Mmm... what could I say about myself? Let's see what I've done...") + npc("I've delved into the dungeon west of Brimhaven and heard the terrifying CRASH of the steel dragons battling each other for territory.") + npc("I spent some years on Entrana, where I learnt the techniques of pure meditation.") + npc("I've wandered through the vast desert that lies south of Al Kharid and seen the great walls of Menaphos and Sophanem.") + npc("Apart from all that, I've spent many a happy hour in dusty libraries, searching through ancient scrolls and texts for the wisdom of those who have passed on.") + npc("Plus plenty of other adventures, quests, journeys... Is there anything else you'd like to know?") + anythingElse() + } + } + } + option("Gods and demons") { + choice("Pick a topic") { + option("Three gods?") + option("The wars of the gods") { + player("I wanna know about the wars of the gods!") + npc("Ah, that was a terrible time. The armies of Saradomin fought gloriously against the minions of Zamorak, but many brave warriors and noble cities were overthrown and destroyed utterly.") + player("How did it all end?") + npc("Before the Zamorakian forces could be utterly routed, Lord Saradomin took pity on them and the battle- scarred world, and allowed a truce.") + npc("Is there anything else you'd like to ask?") + anythingElse() + } + option("The Mahjarrat") { + player("What are the Mahjarrat?") + npc("Very little is written about the tribe of the Mahjarrat. They are believed to be from the realm of Freneskae, or Frenaskrae - the spelling in this tongue is only approximate.") + npc("One of them, the foul Zamorak, has achieved godhood, although none knows how this came about.") + } + option("Wielding the power of the gods") { + player("Can I wield the power of Saradomin myself?") + npc("If you travel to the Mage Arena in the north-west reaches of the Wilderness, the battle mage Kolodion may be willing to let you learn to summon the power of Saradomin, should you be able to pass his test.") + npc("Is there anything else you'd like to ask?") + anythingElse() + } + } + } + option("Your hat!") { + player("I want to ask you about your hat.") + npc("Why, thank you! I rather like it myself.") + choice("What would you like to say?") { + option("Where did you get it?") { + npc("Oh, I saw it on the floor when I was out for my morning stroll.") + player("You found a PARTY HAT on the floor?!") + npc("Yes, that's right. Would you like to ask me about something else?") + choice("What would you like to say?") { + option("How can I get a hat like that?") { + npc("You could buy one off another player, or wait until they're next made available by the Council.") + } + option("Yes please.") + option("Thanks, maybe some other time.") + } + } + option("How can I get a hat like that?") + } + } + } + } + + private suspend fun Player.anythingElse() { + choice("What would you like to say?") { + option("Yes please.") + option("Thanks, maybe some other time.") + } + } + + val tasks = setOf( + "anchovies", + "beer_glass", + "bones", + "bronze_arrow", + "bronze_bar", + "bronze_dagger", + "bronze_axe", + "bronze_mace", + "bronze_med_helm", + "bronze_spear", + "bronze_sword", + "beer", + "cadava_berries", + "cooked_chicken", + "cooked_meat", + "copper_ore", + "cowhide", + "egg", + "feather", + "grain", + "iron_bar", + "iron_mace", + "iron_ore", + "soft_clay", + "leather_gloves", + "logs", + "molten_glass", + "potato", + "raw_rat_meat", + "rune_essence", + "shrimps", + "silk", + "leather", + "tin_ore", + "ball_of_wool", + "bow_string", + "bread", + "bronze_arrowtips", + "bronze_knife", + "bronze_warhammer", + "bronze_wire", + "headless_arrow", + "swamp_paste", + "iron_arrowtips", + "iron_knife", + "iron_warhammer", + "leather_cowl", + "pot_of_flour", + "unfired_pie_dish", + "unfired_pot", + "leather_boots", + ) + + suspend fun Player.checkTask() { + val item: String = get("wise_old_man_task") ?: return + val remaining: Int = get("wise_old_man_remaining") ?: return + val id = ItemDefinitions.get(item).id + val intro = enums.get("wise_old_man_tasks").getString(id) + npc("$intro. I still need $remaining.") + hint(id) + } + + suspend fun Player.task() { + npc("I'm sure I can think of a few little jobs. This won't be a quest, mind you, just a little favour...") + for (item in tasks) { + if (!ItemDefinitions.contains(item)) { + println("Doesn't exist: $item") + } + } + if (random.nextInt(100) == 2) { + set("wise_old_man_task", "thing_under_the_bed") + set("wise_old_man_amount", 1) + npc("Well, this is rather embarrassing, but I think there's some kind of monster in my house. Could you go upstairs and get rid of it, please?") + choice("What would you like to say?") { + option("Where do I need to go?") { + npc("I think it's somewhere upstairs. It kept me awake all last night.") + player("Right, I'll see you later.") + } + option("Right, I'll see you later.") + } + return + } + // TODO letters + val amount = random.nextInt(3, 16) + val item = tasks.random(random) // TODO requirements + set("wise_old_man_task", item) + set("wise_old_man_amount", amount) + set("wise_old_man_remaining", amount) + val id = ItemDefinitions.get(item).id + val intro = enums.get("wise_old_man_tasks").getString(id) + npc("$intro. Please bring me $amount.") + hint(id) + } + + private suspend fun Player.hint(id: Int) { + choice("What would you like to say?") { + option("Where can I get that?") { + val hint = enums.get("wise_old_man_hints").getString(id) + npc(hint) + player("Right, I'll see you later.") + } + option("Right, I'll see you later.") + } + } +} From d494450c7e1f73ce2b51eade18170f1fc06681e8 Mon Sep 17 00:00:00 2001 From: GregHib Date: Wed, 25 Feb 2026 18:00:38 +0000 Subject: [PATCH 04/20] Add support for custom enums --- .../cache/definition/data/EnumDefinition.kt | 4 + .../voidps/cache/definition/data/EnumTypes.kt | 65 +++++++++++++++ data/client/{enums.toml => all.enums.toml} | 0 .../engine/data/definition/EnumDefinitions.kt | 83 +++++++++++++++---- game/src/main/kotlin/Main.kt | 5 +- game/src/test/kotlin/WorldTest.kt | 3 +- .../gregs/voidps/tools/EnumDefinitions.kt | 12 ++- 7 files changed, 151 insertions(+), 21 deletions(-) create mode 100644 cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/EnumTypes.kt rename data/client/{enums.toml => all.enums.toml} (100%) diff --git a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/EnumDefinition.kt b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/EnumDefinition.kt index c2aa55a2e3..813e1d736c 100644 --- a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/EnumDefinition.kt +++ b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/EnumDefinition.kt @@ -23,6 +23,10 @@ data class EnumDefinition( fun getString(id: Int) = map?.get(id) as? String ?: defaultString + override fun toString(): String { + return "EnumDefinition(id=$id, keyType=${EnumTypes.name(keyType)}, valueType=${EnumTypes.name(valueType)}, defaultString=$defaultString, defaultInt=$defaultInt, length=$length, map=$map, stringId=$stringId, extras=$extras)" + } + companion object { val EMPTY = EnumDefinition() } diff --git a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/EnumTypes.kt b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/EnumTypes.kt new file mode 100644 index 0000000000..a15d94bbf6 --- /dev/null +++ b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/EnumTypes.kt @@ -0,0 +1,65 @@ +package world.gregs.voidps.cache.definition.data + +object EnumTypes { + const val STRING = 's' + const val INT = 'i' + const val STRUCT = 'J' + const val JINGLE = 'j' + const val ITEM = 'o' + const val ITEM_2 = 'O' + const val SPRITE = 'd' + const val MODEL = 'm' + const val ID_KIT = 'K' + const val COMPONENT = 'I' + const val MAP_AREA = '`' + const val SKILL = 'S' + const val TILE = 'C' + const val CHAT_TYPE = 'c' + const val ANIM = 'A' + const val NPC = 'n' + const val ENUM = 'g' + const val INV = 'v' + + fun name(char: Char) = when (char) { + STRING -> "string" + INT -> "int" + STRUCT -> "struct" + JINGLE -> "jingle" + ITEM -> "item" + ITEM_2 -> "item" + SPRITE -> "sprite" + MODEL -> "model" + ID_KIT -> "idkit" + COMPONENT -> "interface" + MAP_AREA -> "map_area" + SKILL -> "skill" + TILE -> "tile" + CHAT_TYPE -> "chat type" + ANIM -> "anim" + NPC -> "npc" + ENUM -> "enum" + INV -> "inv" + else -> "null" + } + + fun char(name: String) = when (name) { + "string" -> STRING + "int" -> INT + "struct" -> STRUCT + "jingle" -> JINGLE + "item" -> ITEM + "sprite" -> SPRITE + "model" -> MODEL + "id_kit" -> ID_KIT + "interface" -> COMPONENT + "map_area" -> MAP_AREA + "skill" -> SKILL + "tile" -> TILE + "chat_type" -> CHAT_TYPE + "anim" -> ANIM + "npc" -> NPC + "enum" -> ENUM + "inv" -> INV + else -> null + } +} \ No newline at end of file diff --git a/data/client/enums.toml b/data/client/all.enums.toml similarity index 100% rename from data/client/enums.toml rename to data/client/all.enums.toml diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt index 5625cbb4b6..9a6941b433 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt @@ -6,7 +6,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import org.jetbrains.annotations.TestOnly import world.gregs.config.Config import world.gregs.voidps.cache.definition.data.EnumDefinition -import world.gregs.voidps.engine.data.definition.ItemDefinitions.loaded +import world.gregs.voidps.cache.definition.data.EnumTypes import world.gregs.voidps.engine.timedLoad /** @@ -18,6 +18,8 @@ object EnumDefinitions : DefinitionsDecoder { override var ids: Map = emptyMap() + var loaded = false + fun init(definitions: Array): EnumDefinitions { this.definitions = definitions loaded = true @@ -55,26 +57,74 @@ object EnumDefinitions : DefinitionsDecoder { return StructDefinitions.get(struct)[param, default] } - fun load(path: String): EnumDefinitions { + fun load(list: List): EnumDefinitions { timedLoad("enum extra") { + require(ItemDefinitions.loaded) { "Item definitions must be loaded before enum definitions" } val ids = Object2IntOpenHashMap(definitions.size, Hash.VERY_FAST_LOAD_FACTOR) - Config.fileReader(path, 50) { - while (nextSection()) { - val stringId = section() - var id = 0 - val extras = Object2ObjectOpenHashMap(2, Hash.VERY_FAST_LOAD_FACTOR) - while (nextPair()) { - when (val key = key()) { - "id" -> { - id = int() - require(!ids.containsKey(stringId)) { "Duplicate enum id found '$stringId' at $path." } - ids[stringId] = id - definitions[id].stringId = stringId + for (path in list) { + Config.fileReader(path, 250) { + while (nextSection()) { + val stringId = section() + var id = -1 + var keyType: Char = 0.toChar() + var valueType: Char = 0.toChar() + var defaultString = "null" + var defaultInt = 0 + val extras = Object2ObjectOpenHashMap(2, Hash.VERY_FAST_LOAD_FACTOR) + val map = mutableMapOf() + while (nextPair()) { + when (val key = key()) { + "id" -> { + id = int() + require(!ids.containsKey(stringId)) { "Duplicate enum id found '$stringId' at $path." } + ids[stringId] = id + definitions[id].stringId = stringId + } + "keyType" -> { + val string = string() + keyType = EnumTypes.char(string) ?: error("Unknown enum type: $string") + } + "valueType" -> { + val string = string() + valueType = EnumTypes.char(string) ?: error("Unknown enum type: $string") + } + "defaultString" -> defaultString = string() + "defaultInt" -> defaultInt = int() + "values" -> while (nextEntry()) { + val key = key() + val keyInt = when (keyType) { + EnumTypes.ITEM, EnumTypes.ITEM_2 -> ItemDefinitions.get(key).id + else -> key.toInt() + } + map[keyInt] = value() + } + else -> extras[key] = value() + } + } + if (id == -1 && map.isNotEmpty()) { + val index = definitions.size + definitions = Array(index + 1) { + if (it == index) { + EnumDefinition( + it, + keyType = keyType, + valueType = valueType, + defaultString = defaultString, + defaultInt = defaultInt, + length = map.size, + map = map, + extras = extras, + stringId = stringId, + ) + } else { + definitions[it] + } } - else -> extras[key] = value() + ids[stringId] = index + } else { + definitions[id].extras = extras } } - definitions[id].extras = extras } } this.ids = ids @@ -84,4 +134,5 @@ object EnumDefinitions : DefinitionsDecoder { } override fun empty() = EnumDefinition.EMPTY + } diff --git a/game/src/main/kotlin/Main.kt b/game/src/main/kotlin/Main.kt index 60b4553e4b..53a2986cb2 100644 --- a/game/src/main/kotlin/Main.kt +++ b/game/src/main/kotlin/Main.kt @@ -124,7 +124,10 @@ object Main { single(createdAtStart = true) { NPCDefinitions.init(NPCDecoder(members, get()).load(cache)).load(files.list(Settings["definitions.npcs"]), get()) } single(createdAtStart = true) { ItemDefinitions.init(ItemDecoder(get()).load(cache)).load(files.list(Settings["definitions.items"])) } single(createdAtStart = true) { AnimationDefinitions(AnimationDecoder().load(cache)).load(files.list(Settings["definitions.animations"])) } - single(createdAtStart = true) { EnumDefinitions.init(EnumDecoder().load(cache)).load(files.find(Settings["definitions.enums"])) } + single(createdAtStart = true) { + get() + EnumDefinitions.init(EnumDecoder().load(cache)).load(files.list(Settings["definitions.enums"])) + } single(createdAtStart = true) { GraphicDefinitions(GraphicDecoder().load(cache)).load(files.list(Settings["definitions.graphics"])) } single(createdAtStart = true) { InterfaceDefinitions.init(InterfaceDecoder().load(cache)).load(files.list(Settings["definitions.interfaces"]), files.find(Settings["definitions.interfaces.types"])) } single(createdAtStart = true) { diff --git a/game/src/test/kotlin/WorldTest.kt b/game/src/test/kotlin/WorldTest.kt index c40b576641..7377fdbe6c 100644 --- a/game/src/test/kotlin/WorldTest.kt +++ b/game/src/test/kotlin/WorldTest.kt @@ -357,7 +357,8 @@ abstract class WorldTest : KoinTest { EnumDecoder().load(cache) } private val enumIds: Map by lazy { - EnumDefinitions.init(EnumDecoder().load(cache)).load(configFiles.find(Settings["definitions.enums"])) + itemIds + EnumDefinitions.init(EnumDecoder().load(cache)).load(configFiles.list(Settings["definitions.enums"])) EnumDefinitions.ids } private val objectCollisionAdd: GameObjectCollisionAdd by lazy { GameObjectCollisionAdd() } diff --git a/tools/src/main/kotlin/world/gregs/voidps/tools/EnumDefinitions.kt b/tools/src/main/kotlin/world/gregs/voidps/tools/EnumDefinitions.kt index 1810dd7bf8..08415ec594 100644 --- a/tools/src/main/kotlin/world/gregs/voidps/tools/EnumDefinitions.kt +++ b/tools/src/main/kotlin/world/gregs/voidps/tools/EnumDefinitions.kt @@ -3,7 +3,11 @@ package world.gregs.voidps.tools import world.gregs.voidps.cache.Cache import world.gregs.voidps.cache.CacheDelegate import world.gregs.voidps.cache.definition.decoder.EnumDecoder +import world.gregs.voidps.cache.definition.decoder.ItemDecoder import world.gregs.voidps.engine.data.Settings +import world.gregs.voidps.engine.data.configFiles +import world.gregs.voidps.engine.data.definition.EnumDefinitions +import world.gregs.voidps.engine.data.definition.ItemDefinitions object EnumDefinitions { @@ -11,9 +15,11 @@ object EnumDefinitions { fun main(args: Array) { Settings.load() val cache: Cache = CacheDelegate(Settings["storage.cache.path"]) - val decoder = EnumDecoder().load(cache) - for (i in decoder.indices) { - val def = decoder.getOrNull(i) ?: continue + val files = configFiles() + ItemDefinitions.init(ItemDecoder().load(cache)).load(files.list(Settings["definitions.items"])) + val definitions = EnumDefinitions.init(EnumDecoder().load(cache)).load(files.list(Settings["definitions.enums"])) + for (i in definitions.definitions.indices) { + val def = definitions.getOrNull(i) ?: continue println("$i $def") } } From 2be0fea255624d806bea8e54b64169241f1679e9 Mon Sep 17 00:00:00 2001 From: GregHib Date: Wed, 25 Feb 2026 20:56:50 +0000 Subject: [PATCH 05/20] Add enum helpers --- .../cache/definition/data/EnumDefinition.kt | 7 +-- .../data/QuickChatPhraseDefinition.kt | 6 +-- .../client/instruction/InterfaceHandler.kt | 2 +- .../voidps/engine/client/ui/Interfaces.kt | 4 +- .../engine/data/definition/EnumDefinitions.kt | 52 +++++++++++++++++-- .../kotlin/content/achievement/TaskSystem.kt | 4 +- .../main/kotlin/content/achievement/Tasks.kt | 4 +- .../area/asgarnia/falador/Hairdresser.kt | 4 +- .../area/asgarnia/falador/MakeoverMage.kt | 4 +- .../area/fremennik_province/rellekka/Yrsa.kt | 4 +- .../lumbridge/church/GravestoneShop.kt | 4 +- .../area/misthalin/varrock/Thessalia.kt | 4 +- .../varrock/grand_exchange/CommonItemCosts.kt | 2 +- .../content/entity/npc/shop/stock/ItemInfo.kt | 4 +- .../content/entity/npc/shop/stock/Price.kt | 8 +-- .../entity/player/modal/CharacterCreation.kt | 8 +-- .../content/entity/world/music/Music.kt | 4 +- .../kotlin/content/skill/farming/Farmer.kt | 2 +- .../content/skill/summoning/ShardSwapping.kt | 4 +- .../content/skill/summoning/Summoning.kt | 4 +- .../skill/summoning/SummoningCrafting.kt | 8 +-- .../trade/exchange/GrandExchangeItemSets.kt | 4 +- 22 files changed, 96 insertions(+), 51 deletions(-) diff --git a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/EnumDefinition.kt b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/EnumDefinition.kt index 813e1d736c..d3d2f6af55 100644 --- a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/EnumDefinition.kt +++ b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/EnumDefinition.kt @@ -2,6 +2,7 @@ package world.gregs.voidps.cache.definition.data import world.gregs.voidps.cache.Definition import world.gregs.voidps.cache.definition.Extra +import world.gregs.voidps.type.random data class EnumDefinition( override var id: Int = -1, @@ -17,11 +18,11 @@ data class EnumDefinition( Extra { fun getKey(value: Any) = map?.filterValues { it == value }?.keys?.lastOrNull() ?: -1 - fun getInt(id: Int) = map?.get(id) as? Int ?: defaultInt + fun int(id: Int) = map?.get(id) as? Int ?: defaultInt - fun randomInt() = map?.values?.random() as? Int ?: defaultInt + fun randomInt() = map?.values?.random(random) as? Int ?: defaultInt - fun getString(id: Int) = map?.get(id) as? String ?: defaultString + fun string(id: Int) = map?.get(id) as? String ?: defaultString override fun toString(): String { return "EnumDefinition(id=$id, keyType=${EnumTypes.name(keyType)}, valueType=${EnumTypes.name(valueType)}, defaultString=$defaultString, defaultInt=$defaultInt, length=$length, map=$map, stringId=$stringId, extras=$extras)" diff --git a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/QuickChatPhraseDefinition.kt b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/QuickChatPhraseDefinition.kt index c9e7085a1f..a42da22d3f 100644 --- a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/QuickChatPhraseDefinition.kt +++ b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/QuickChatPhraseDefinition.kt @@ -30,12 +30,12 @@ data class QuickChatPhraseDefinition( else -> 0 } val string = when (type) { - QuickChatType.MultipleChoice -> enums[ids[index].first()].getString(key) + QuickChatType.MultipleChoice -> enums[ids[index].first()].string(key) QuickChatType.AllItems, QuickChatType.TradeItems -> items[key].name QuickChatType.SlayerAssignment -> { - enums[ids[index].first()].getString(key) + enums[ids[index].first()].string(key) } - QuickChatType.ClanRank, QuickChatType.SkillExperience -> enums[ids[index].first()].getString(key) + QuickChatType.ClanRank, QuickChatType.SkillExperience -> enums[ids[index].first()].string(key) else -> key.toString() } append(string) diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InterfaceHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InterfaceHandler.kt index 3a9b1583db..b6d3c74c4d 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InterfaceHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InterfaceHandler.kt @@ -24,7 +24,7 @@ class InterfaceHandler( id.startsWith("summoning_") && id.endsWith("_creation") -> item = Item(ItemDefinitions.get(itemId).stringId) id == "summoning_trade_in" -> item = Item(ItemDefinitions.get(itemId).stringId) id == "exchange_item_sets" -> { - val expected = EnumDefinitions.get("exchange_item_sets").getInt(itemSlot + 1) + val expected = EnumDefinitions.get("exchange_item_sets").int(itemSlot + 1) if (expected != itemId) { logger.info { "Exchange item sets don't match [$player, expected=$expected, actual=$itemId]" } return null diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/ui/Interfaces.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/ui/Interfaces.kt index da9a396545..5b0a309f1f 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/ui/Interfaces.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/ui/Interfaces.kt @@ -281,8 +281,8 @@ fun Player.closeInterfaces(): Boolean { } fun Player.playTrack(trackIndex: Int) { - playMusicTrack(EnumDefinitions.get("music_tracks").getInt(trackIndex)) - val name = EnumDefinitions.get("music_track_names").getString(trackIndex) + playMusicTrack(EnumDefinitions.get("music_tracks").int(trackIndex)) + val name = EnumDefinitions.get("music_track_names").string(trackIndex) interfaces.sendText("music_player", "currently_playing", name) this["playing_song"] = true this["current_track"] = trackIndex diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt index 9a6941b433..2717da1b37 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt @@ -5,9 +5,11 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import org.jetbrains.annotations.TestOnly import world.gregs.config.Config +import world.gregs.voidps.cache.config.data.StructDefinition import world.gregs.voidps.cache.definition.data.EnumDefinition import world.gregs.voidps.cache.definition.data.EnumTypes import world.gregs.voidps.engine.timedLoad +import world.gregs.voidps.type.Tile /** * Also known as DataMap in cs2 or tables @@ -39,21 +41,63 @@ object EnumDefinitions : DefinitionsDecoder { loaded = false } + private fun key(keyType: Char, key: String) = when (keyType) { + EnumTypes.ITEM -> ItemDefinitions.get(key).id + EnumTypes.COMPONENT -> InterfaceDefinitions.get(key).id + EnumTypes.INV -> InventoryDefinitions.get(key).id + EnumTypes.NPC -> NPCDefinitions.get(key).id + EnumTypes.STRUCT -> StructDefinitions.get(key).id + else -> error("Unsupported enum type: $keyType") + } + + fun string(enum: String, key: String): String { + val definition = get(enum) + val key = key(definition.keyType, key) + return definition.string(key) + } + + fun int(enum: String, key: String): Int { + val definition = get(enum) + val key = key(definition.keyType, key) + return definition.int(key) + } + + fun item(enum: String, key: String): String { + val definition = get(enum) + assert(definition.valueType == EnumTypes.ITEM || definition.valueType == EnumTypes.ITEM_2) { "Enum $enum value type not Item, found: ${EnumTypes.name(definition.valueType)}" } + val key = key(definition.keyType, key) + return ItemDefinitions.get(definition.int(key)).stringId + } + + fun tile(enum: String, key: String): Tile { + val definition = get(enum) + assert(definition.valueType == EnumTypes.TILE) { "Enum $enum value type not Tile, found: ${EnumTypes.name(definition.valueType)}" } + val key = key(definition.keyType, key) + return Tile(definition.int(key)) + } + + fun struct(enum: String, key: String): StructDefinition { + val definition = get(enum) + assert(definition.valueType == EnumTypes.TILE) { "Enum $enum value type not Tile, found: ${EnumTypes.name(definition.valueType)}" } + val key = key(definition.keyType, key) + return StructDefinitions.get(definition.int(key)) + } + fun getStruct(id: String, index: Int, param: String): T { val enum = get(id) - val struct = enum.getInt(index) + val struct = enum.int(index) return StructDefinitions.get(struct)[param] } fun getStructOrNull(id: String, index: Int, param: String): T? { val enum = get(id) - val struct = enum.getInt(index) + val struct = enum.int(index) return StructDefinitions.getOrNull(struct)?.getOrNull(param) } fun getStruct(id: String, index: Int, param: String, default: T): T { val enum = get(id) - val struct = enum.getInt(index) + val struct = enum.int(index) return StructDefinitions.get(struct)[param, default] } @@ -93,7 +137,7 @@ object EnumDefinitions : DefinitionsDecoder { "values" -> while (nextEntry()) { val key = key() val keyInt = when (keyType) { - EnumTypes.ITEM, EnumTypes.ITEM_2 -> ItemDefinitions.get(key).id + EnumTypes.ITEM, EnumTypes.ITEM_2 -> ItemDefinitions.getOrNull(key)?.id ?: error("Unknown item '$key' ${exception()}") else -> key.toInt() } map[keyInt] = value() diff --git a/game/src/main/kotlin/content/achievement/TaskSystem.kt b/game/src/main/kotlin/content/achievement/TaskSystem.kt index 18c3f401d5..de4c577ba3 100644 --- a/game/src/main/kotlin/content/achievement/TaskSystem.kt +++ b/game/src/main/kotlin/content/achievement/TaskSystem.kt @@ -206,8 +206,8 @@ class TaskSystem( player["task_popup"] = index val difficulty = definition["task_difficulty", 0] val area = definition["task_area", 61] - val areaName = EnumDefinitions.get("task_area_names").getString(area) - val difficultyName = EnumDefinitions.get("task_difficulties").getString(difficulty) + val areaName = EnumDefinitions.get("task_area_names").string(area) + val difficultyName = EnumDefinitions.get("task_difficulties").string(difficulty) if (areaName.isNotBlank() && difficultyName.isNotBlank()) { player.message("You have completed the Task '${definition["task_name", ""]}' in the $difficultyName $areaName set!") } else { diff --git a/game/src/main/kotlin/content/achievement/Tasks.kt b/game/src/main/kotlin/content/achievement/Tasks.kt index a83afa95e9..d3d94ba09e 100644 --- a/game/src/main/kotlin/content/achievement/Tasks.kt +++ b/game/src/main/kotlin/content/achievement/Tasks.kt @@ -37,11 +37,11 @@ object Tasks { } fun forEach(areaId: Int, block: TaskIterator.() -> R?): R? { - var next = EnumDefinitions.get("task_area_start_indices").getInt(areaId) + var next = EnumDefinitions.get("task_area_start_indices").int(areaId) val structs = EnumDefinitions.get("task_structs") val iterator = TaskIterator() while (next != 4091 && next != 450 && next != 4094) { - val struct = structs.getInt(next) + val struct = structs.int(next) iterator.definition = StructDefinitions.getOrNull(struct) ?: break iterator.index = next iterator.skip = false diff --git a/game/src/main/kotlin/content/area/asgarnia/falador/Hairdresser.kt b/game/src/main/kotlin/content/area/asgarnia/falador/Hairdresser.kt index f63aa2d296..b69d2e25b0 100644 --- a/game/src/main/kotlin/content/area/asgarnia/falador/Hairdresser.kt +++ b/game/src/main/kotlin/content/area/asgarnia/falador/Hairdresser.kt @@ -56,7 +56,7 @@ class Hairdresser : Script { val type = if (beard) "beard" else "hair" val key = "look_${type}_$sex" val value = if (beard) { - EnumDefinitions.get(key).getInt(itemSlot / 2) + EnumDefinitions.get(key).int(itemSlot / 2) } else { EnumDefinitions.getStruct(key, itemSlot / 2, "body_look_id") } @@ -64,7 +64,7 @@ class Hairdresser : Script { } interfaceOption(id = "hairdressers_salon:colours") { (_, itemSlot) -> - set("makeover_colour_hair", EnumDefinitions.get("colour_hair").getInt(itemSlot / 2)) + set("makeover_colour_hair", EnumDefinitions.get("colour_hair").int(itemSlot / 2)) } interfaceClosed("hairdressers_salon") { diff --git a/game/src/main/kotlin/content/area/asgarnia/falador/MakeoverMage.kt b/game/src/main/kotlin/content/area/asgarnia/falador/MakeoverMage.kt index 375260e5f1..eb2336c5d9 100644 --- a/game/src/main/kotlin/content/area/asgarnia/falador/MakeoverMage.kt +++ b/game/src/main/kotlin/content/area/asgarnia/falador/MakeoverMage.kt @@ -69,7 +69,7 @@ class MakeoverMage : Script { } interfaceOption(id = "skin_colour:colour_*") { - set("makeover_colour_skin", EnumDefinitions.get("character_skin").getInt(it.component.removePrefix("colour_").toInt())) + set("makeover_colour_skin", EnumDefinitions.get("character_skin").int(it.component.removePrefix("colour_").toInt())) } interfaceOption("Confirm", "skin_colour:confirm") { @@ -214,6 +214,6 @@ class MakeoverMage : Script { val old = EnumDefinitions.get("look_${name}_${if (male) "female" else "male"}") val new = EnumDefinitions.get("look_${name}_${if (male) "male" else "female"}") val key = old.getKey(player.body.getLook(bodyPart)) - player.body.setLook(bodyPart, new.getInt(key)) + player.body.setLook(bodyPart, new.int(key)) } } diff --git a/game/src/main/kotlin/content/area/fremennik_province/rellekka/Yrsa.kt b/game/src/main/kotlin/content/area/fremennik_province/rellekka/Yrsa.kt index b5d90f9e9b..6e506e8408 100644 --- a/game/src/main/kotlin/content/area/fremennik_province/rellekka/Yrsa.kt +++ b/game/src/main/kotlin/content/area/fremennik_province/rellekka/Yrsa.kt @@ -53,12 +53,12 @@ class Yrsa : Script { } interfaceOption(id = "yrsas_shoe_store:styles") { (_, itemSlot) -> - val value = EnumDefinitions.get("look_shoes_$sex").getInt(itemSlot / 2) + val value = EnumDefinitions.get("look_shoes_$sex").int(itemSlot / 2) set("makeover_shoes", value) } interfaceOption(id = "yrsas_shoe_store:colours") { (_, itemSlot) -> - set("makeover_colour_shoes", EnumDefinitions.get("colour_shoes").getInt(itemSlot / 2)) + set("makeover_colour_shoes", EnumDefinitions.get("colour_shoes").int(itemSlot / 2)) } interfaceOption("Confirm", "yrsas_shoe_store:confirm") { diff --git a/game/src/main/kotlin/content/area/misthalin/lumbridge/church/GravestoneShop.kt b/game/src/main/kotlin/content/area/misthalin/lumbridge/church/GravestoneShop.kt index 107467c706..617eaf62f4 100644 --- a/game/src/main/kotlin/content/area/misthalin/lumbridge/church/GravestoneShop.kt +++ b/game/src/main/kotlin/content/area/misthalin/lumbridge/church/GravestoneShop.kt @@ -41,12 +41,12 @@ class GravestoneShop : Script { } interfaceOption(id = "gravestone_shop:button") { (_, itemSlot) -> - val name = EnumDefinitions.get("gravestone_names").getString(itemSlot) + val name = EnumDefinitions.get("gravestone_names").string(itemSlot) val id = name.replace(" ", "_").lowercase() if (get("gravestone_current", "memorial_plaque") == id) { return@interfaceOption } - val cost = EnumDefinitions.get("gravestone_price").getInt(itemSlot) + val cost = EnumDefinitions.get("gravestone_price").int(itemSlot) if (cost > 0 && !inventory.remove("coins", cost)) { notEnough("coins") return@interfaceOption diff --git a/game/src/main/kotlin/content/area/misthalin/varrock/Thessalia.kt b/game/src/main/kotlin/content/area/misthalin/varrock/Thessalia.kt index ca9d1bc591..a5cff5e8b7 100644 --- a/game/src/main/kotlin/content/area/misthalin/varrock/Thessalia.kt +++ b/game/src/main/kotlin/content/area/misthalin/varrock/Thessalia.kt @@ -78,7 +78,7 @@ class Thessalia : Script { if ((part == "arms" || part == "wrists") && previous) { return@interfaceOption } - val value = EnumDefinitions.get("look_${part}_$sex").getInt(itemSlot / 2) + val value = EnumDefinitions.get("look_${part}_$sex").int(itemSlot / 2) if (part == "top") { val current = fullBodyChest(value, male) if (previous && !current) { @@ -100,7 +100,7 @@ class Thessalia : Script { "legs" -> "makeover_colour_legs" else -> return@interfaceOption } - set(colour, EnumDefinitions.get("colour_$part").getInt(itemSlot / 2)) + set(colour, EnumDefinitions.get("colour_$part").int(itemSlot / 2)) } interfaceOption("Confirm", "thessalias_makeovers:confirm") { diff --git a/game/src/main/kotlin/content/area/misthalin/varrock/grand_exchange/CommonItemCosts.kt b/game/src/main/kotlin/content/area/misthalin/varrock/grand_exchange/CommonItemCosts.kt index d830f10135..562c20f072 100644 --- a/game/src/main/kotlin/content/area/misthalin/varrock/grand_exchange/CommonItemCosts.kt +++ b/game/src/main/kotlin/content/area/misthalin/varrock/grand_exchange/CommonItemCosts.kt @@ -18,7 +18,7 @@ class CommonItemCosts( val enum = EnumDefinitions.get("exchange_items_$type") var index = 1 for (i in 0 until enum.length) { - val item = enum.getInt(i) + val item = enum.int(i) val definition = ItemDefinitions.get(item) val price = exchange.history.marketPrice(definition.stringId) sendScript("send_common_item_price", index, i, "${price.toDigitGroupString()} gp") diff --git a/game/src/main/kotlin/content/entity/npc/shop/stock/ItemInfo.kt b/game/src/main/kotlin/content/entity/npc/shop/stock/ItemInfo.kt index f377dea49a..f5b7c12f6e 100644 --- a/game/src/main/kotlin/content/entity/npc/shop/stock/ItemInfo.kt +++ b/game/src/main/kotlin/content/entity/npc/shop/stock/ItemInfo.kt @@ -38,7 +38,7 @@ object ItemInfo { private fun setRequirements(player: Player, def: ItemDefinition) { val quest = def["quest_info", -1] if (def.contains("equip_req") || def.contains("skillcape_skill") || quest != -1) { - player["item_info_requirement_title"] = EnumDefinitions.get("item_info_requirement_titles").getString(def.slot.index) + player["item_info_requirement_title"] = EnumDefinitions.get("item_info_requirement_titles").string(def.slot.index) val builder = StringBuilder() val requirements = def.getOrNull>("equip_req") ?: emptyMap() for ((skill, level) in requirements) { @@ -57,7 +57,7 @@ object ItemInfo { } player["item_info_requirement"] = builder.toString() } else { - player["item_info_requirement_title"] = EnumDefinitions.get("item_info_titles").getString(def.slot.index) + player["item_info_requirement_title"] = EnumDefinitions.get("item_info_titles").string(def.slot.index) player["item_info_requirement"] = "" } } diff --git a/game/src/main/kotlin/content/entity/npc/shop/stock/Price.kt b/game/src/main/kotlin/content/entity/npc/shop/stock/Price.kt index b93c7254ad..18a969093d 100644 --- a/game/src/main/kotlin/content/entity/npc/shop/stock/Price.kt +++ b/game/src/main/kotlin/content/entity/npc/shop/stock/Price.kt @@ -18,11 +18,11 @@ object Price { fun getPrice(player: Player, item: String, index: Int, amount: Int): Int { val itemId = getRealItem(item) - var price = EnumDefinitions.get("price_runes").getInt(itemId) + var price = EnumDefinitions.get("price_runes").int(itemId) if (player["shop_currency", "coins"] == "tokkul" && price != -1 && price > 0) { return price } - price = EnumDefinitions.get("price_garden").getInt(itemId) + price = EnumDefinitions.get("price_garden").int(itemId) if (price != -1 && price > 0) { return price } @@ -57,11 +57,11 @@ object Price { val itemId = getRealItem(item) val koin = KoinPlatformTools.defaultContext().getOrNull() if (koin != null) { - var price = EnumDefinitions.get("price_runes").getInt(itemId) + var price = EnumDefinitions.get("price_runes").int(itemId) if (currency == "tokkul" && price != -1 && price > 0) { return price } - price = EnumDefinitions.get("price_garden").getInt(itemId) + price = EnumDefinitions.get("price_garden").int(itemId) if (price != -1 && price > 0) { return price } diff --git a/game/src/main/kotlin/content/entity/player/modal/CharacterCreation.kt b/game/src/main/kotlin/content/entity/player/modal/CharacterCreation.kt index 0097d0aa56..83797be689 100644 --- a/game/src/main/kotlin/content/entity/player/modal/CharacterCreation.kt +++ b/game/src/main/kotlin/content/entity/player/modal/CharacterCreation.kt @@ -44,7 +44,7 @@ class CharacterCreation : Script { } interfaceOption(id = "character_creation:skin_colour") { (_, itemSlot) -> - set("makeover_colour_skin", EnumDefinitions.get("character_skin").getInt(itemSlot)) + set("makeover_colour_skin", EnumDefinitions.get("character_skin").int(itemSlot)) } interfaceOption(id = "character_creation:style_*") { @@ -68,7 +68,7 @@ class CharacterCreation : Script { if (part == "beard") { part = "hair" } - set("makeover_colour_$part", EnumDefinitions.get("character_$part").getInt(itemSlot)) + set("makeover_colour_$part", EnumDefinitions.get("character_$part").int(itemSlot)) } interfaceOption("Choose My Colour", "character_creation:choose_colour") { @@ -83,7 +83,7 @@ class CharacterCreation : Script { val value = if (part == "hair") { EnumDefinitions.getStruct("character_${part}_styles_$sex", itemSlot, "body_look_id") } else { - EnumDefinitions.get("character_${part}_styles_$sex").getInt(itemSlot) + EnumDefinitions.get("character_${part}_styles_$sex").int(itemSlot) } if (part == "top") { onStyle(value) { @@ -182,7 +182,7 @@ class CharacterCreation : Script { player["character_creation_female"] = female val hairStyle = player["character_creation_hair_style", 0] val hair: Int = EnumDefinitions.getStruct("character_hair_styles_${if (female) "female" else "male"}", hairStyle, "body_look_id") - val beard: Int = if (female) -1 else EnumDefinitions.get("character_beard_styles_male").getInt(hairStyle / 2) + val beard: Int = if (female) -1 else EnumDefinitions.get("character_beard_styles_male").int(hairStyle / 2) player["makeover_hair"] = hair player["makeover_beard"] = beard player["character_creation_sub_style"] = 1 diff --git a/game/src/main/kotlin/content/entity/world/music/Music.kt b/game/src/main/kotlin/content/entity/world/music/Music.kt index a0c574e64c..b97148a681 100644 --- a/game/src/main/kotlin/content/entity/world/music/Music.kt +++ b/game/src/main/kotlin/content/entity/world/music/Music.kt @@ -215,14 +215,14 @@ class Music(val tracks: MusicTracks) : Script { } fun Player.hasUnlocked(musicIndex: Int): Boolean { - val name = EnumDefinitions.get("music_track_names").getString(musicIndex) + val name = EnumDefinitions.get("music_track_names").string(musicIndex) return containsVarbit("unlocked_music_${musicIndex / 32}", toIdentifier(name)) } fun autoPlay(player: Player, track: MusicTracks.Track) { val index = track.index if (player.addVarbit("unlocked_music_${index / 32}", track.name)) { - player.message("You have unlocked a new music track: ${EnumDefinitions.get("music_track_names").getString(index)}.") + player.message("You have unlocked a new music track: ${EnumDefinitions.get("music_track_names").string(index)}.") } if (!player["playing_song", false]) { player.playTrack(index) diff --git a/game/src/main/kotlin/content/skill/farming/Farmer.kt b/game/src/main/kotlin/content/skill/farming/Farmer.kt index 0fd5ed8b0b..d54656e44d 100644 --- a/game/src/main/kotlin/content/skill/farming/Farmer.kt +++ b/game/src/main/kotlin/content/skill/farming/Farmer.kt @@ -169,7 +169,7 @@ class Farmer : Script { } val def = ObjectDefinitions.get("${value.substringBeforeLast("_")}_fullygrown") val item: String = def.getOrNull("harvest") ?: return - val harvest = EnumDefinitions.get("farming_protection").getString(ItemDefinitions.get(item).id).substringAfter(":") + val harvest = EnumDefinitions.get("farming_protection").string(ItemDefinitions.get(item).id).substringAfter(":") npc("If you like, but I want $harvest for that.") val (required, noted) = requiredItems(item) if (!inventory.remove(required) && (noted.isEmpty() || !inventory.remove(noted))) { diff --git a/game/src/main/kotlin/content/skill/summoning/ShardSwapping.kt b/game/src/main/kotlin/content/skill/summoning/ShardSwapping.kt index 137f8e0e9d..52a3d89759 100644 --- a/game/src/main/kotlin/content/skill/summoning/ShardSwapping.kt +++ b/game/src/main/kotlin/content/skill/summoning/ShardSwapping.kt @@ -39,7 +39,7 @@ class ShardSwapping : Script { val itemType = id.substringAfter(":").removeSuffix("_trade_in") if (item.id.endsWith("_u")) { - val actualItemId = EnumDefinitions.get("summoning_${itemType}_ids_1").getInt(enumIndex) + val actualItemId = EnumDefinitions.get("summoning_${itemType}_ids_1").int(enumIndex) actualItem = Item(ItemDefinitions.get(actualItemId).stringId) } @@ -52,7 +52,7 @@ class ShardSwapping : Script { val itemType = id.substringAfter(":").removeSuffix("_trade_in") if (item.id.endsWith("_u")) { - val actualItemId = EnumDefinitions.get("summoning_${itemType}_ids_1").getInt(enumIndex) + val actualItemId = EnumDefinitions.get("summoning_${itemType}_ids_1").int(enumIndex) actualItem = Item(ItemDefinitions.get(actualItemId).stringId) sendValueMessage(this, actualItem, itemType) return@interfaceOption diff --git a/game/src/main/kotlin/content/skill/summoning/Summoning.kt b/game/src/main/kotlin/content/skill/summoning/Summoning.kt index 53b89180d9..1344a44c04 100644 --- a/game/src/main/kotlin/content/skill/summoning/Summoning.kt +++ b/game/src/main/kotlin/content/skill/summoning/Summoning.kt @@ -173,8 +173,8 @@ class Summoning : Script { init { itemOption("Summon", "*_pouch") { option -> - val familiarLevel = EnumDefinitions.get("summoning_pouch_levels").getInt(option.item.def.id) - val familiarId = EnumDefinitions.get("summoning_familiar_ids").getInt(option.item.def.id) + val familiarLevel = EnumDefinitions.get("summoning_pouch_levels").int(option.item.def.id) + val familiarId = EnumDefinitions.get("summoning_familiar_ids").int(option.item.def.id) val summoningXp = option.item.def["summon_experience", 0.0] val familiar = NPCDefinitions.get(familiarId) if (!has(Skill.Summoning, familiarLevel)) { diff --git a/game/src/main/kotlin/content/skill/summoning/SummoningCrafting.kt b/game/src/main/kotlin/content/skill/summoning/SummoningCrafting.kt index dac0f72ad1..f30898f04d 100644 --- a/game/src/main/kotlin/content/skill/summoning/SummoningCrafting.kt +++ b/game/src/main/kotlin/content/skill/summoning/SummoningCrafting.kt @@ -103,7 +103,7 @@ class SummoningCrafting : Script { * @param amount: The amount of pouches the player is attempting to craft */ fun infusePouches(player: Player, enumIndex: Int, amount: Int) { - val pouchItemId = EnumDefinitions.get("summoning_pouch_ids_1").getInt(enumIndex) + val pouchItemId = EnumDefinitions.get("summoning_pouch_ids_1").int(enumIndex) val pouchItem = Item(ItemDefinitions.get(pouchItemId).stringId) val shards = getShards(pouchItem) @@ -138,7 +138,7 @@ class SummoningCrafting : Script { * @param amount: The amount of pouches the player is attempting to turn into scrolls */ fun transformScrolls(player: Player, enumIndex: Int, amount: Int) { - val scrollItemId = EnumDefinitions.get("summoning_scroll_ids_1").getInt(enumIndex) + val scrollItemId = EnumDefinitions.get("summoning_scroll_ids_1").int(enumIndex) val scrollItem = Item(ItemDefinitions.get(scrollItemId).stringId) val pouchId = EnumDefinitions.get("summoning_scroll_ids_2").getKey(scrollItemId) @@ -262,8 +262,8 @@ class SummoningCrafting : Script { * @param enumIndex: The index of the clicked pouch in the "summoning_pouch_ids_1" enum. */ fun sendIngredientMessage(player: Player, enumIndex: Int) { - val realPouchId = EnumDefinitions.get("summoning_pouch_ids_1").getInt(enumIndex) - val ingredientString = EnumDefinitions.get("summoning_pouch_crafting_ingredient_strings").getString(realPouchId) + val realPouchId = EnumDefinitions.get("summoning_pouch_ids_1").int(enumIndex) + val ingredientString = EnumDefinitions.get("summoning_pouch_crafting_ingredient_strings").string(realPouchId) player.message(ingredientString) } diff --git a/game/src/main/kotlin/content/social/trade/exchange/GrandExchangeItemSets.kt b/game/src/main/kotlin/content/social/trade/exchange/GrandExchangeItemSets.kt index 70a9a31b40..dbfc0acf2e 100644 --- a/game/src/main/kotlin/content/social/trade/exchange/GrandExchangeItemSets.kt +++ b/game/src/main/kotlin/content/social/trade/exchange/GrandExchangeItemSets.kt @@ -35,7 +35,7 @@ class GrandExchangeItemSets : Script { interfaceOption("Components", "exchange_item_sets:sets") { (item) -> val descriptions = EnumDefinitions.get("exchange_set_descriptions") - message(descriptions.getString(item.def.id)) + message(descriptions.string(item.def.id)) } interfaceOption("Exchange", "exchange_item_sets:sets") { (item) -> @@ -73,7 +73,7 @@ class GrandExchangeItemSets : Script { interfaceOption("Components", "exchange_sets_side:items") { (item) -> val descriptions = EnumDefinitions.get("exchange_set_descriptions") - val text = descriptions.getString(item.def.id) + val text = descriptions.string(item.def.id) if (text != "shop_dummy") { message(text) } else { From 4ec63e8815806845207b2f71dce6ab7feaf49e82 Mon Sep 17 00:00:00 2001 From: GregHib Date: Wed, 25 Feb 2026 20:57:06 +0000 Subject: [PATCH 06/20] Use enums with wise old man --- data/area/misthalin/draynor/draynor.npcs.toml | 2 +- .../wise_old_man/wise_old_man.enums.toml | 48 ++- .../wise_old_man/wise_old_man.vars.toml | 5 + .../misthalin/draynor_village/WiseOldMan.kt | 310 +++++++++++------- 4 files changed, 238 insertions(+), 127 deletions(-) diff --git a/data/area/misthalin/draynor/draynor.npcs.toml b/data/area/misthalin/draynor/draynor.npcs.toml index ba11f0e1f3..54043da8d5 100644 --- a/data/area/misthalin/draynor/draynor.npcs.toml +++ b/data/area/misthalin/draynor/draynor.npcs.toml @@ -225,7 +225,7 @@ examine = "The hat's a dead giveaway." [chicken_draynor_2] id = 288 -[wise_old_man_2_2] +[wise_old_man_draynor] id = 2253 [wise_old_man_2_3] diff --git a/data/area/misthalin/draynor/wise_old_man/wise_old_man.enums.toml b/data/area/misthalin/draynor/wise_old_man/wise_old_man.enums.toml index ab71984f40..fc40cce3ca 100644 --- a/data/area/misthalin/draynor/wise_old_man/wise_old_man.enums.toml +++ b/data/area/misthalin/draynor/wise_old_man/wise_old_man.enums.toml @@ -1,6 +1,6 @@ -[wise_old_man_tasks] -key = "items" -type = "string" +[wise_old_man_items] +keyType = "item" +valueType = "string" values = { anchovies = "I could do with some freshly cooked anchovies for a salad I'm planning.", beer_glass = "My glassware got damaged when I moved here, so I need some new beer glasses.", @@ -29,7 +29,7 @@ values = { leather_gloves = "It's a bit nippy in this house, and my hands are cold. I need some leather gloves.", logs = "This house is a bit cold, so I could do with some normal logs to burn.", molten_glass = "Some chunks of molten glass would be the ideal patch for my cracked window.", - potato = "I need some potatoes, if you'd be so kind.", + raw_potato = "I need some potatoes, if you'd be so kind.", raw_rat_meat = "I hear pet cats are getting popular these days. I'd like some raw rat in case I get one.", rune_essence = "I'd like to study the rune essence that the wizards have been talking about recently, so I need a few pieces.", shrimps = "I could do with some freshly cooked shrimps for a salad I'm planning.", @@ -37,7 +37,7 @@ values = { leather = "I'll be needing a few pieces of soft leather.", tin_ore = "I need a few lumps of tin ore.", ball_of_wool = "I saw an interesting bed in Karamja called a 'hammock'. It seemed to be made out of string, and I'd like some balls of wool so I can make my own.", - bow_string = "My shortbow's string is getting a bit worn out, so could you fetch me some bow strings?", + bowstring = "My shortbow's string is getting a bit worn out, so could you fetch me some bow strings?", bread = "I don't have a decent larder here, so I can't store food very well. Now I'm out of loaves of bread.", bronze_arrowtips = "I need a few bronze arrowheads to complete some arrows I was making.", bronze_knife = "I'd like some bronze knives to throw at goblins.", @@ -55,9 +55,9 @@ values = { leather_boots = "My footwear is getting a bit worn out, so I need a few pairs of leather boots.", } -[wise_old_man_hints] -key = "items" -type = "string" +[wise_old_man_item_hints] +keyType = "item" +valueType = "string" values = { anchovies = "Use a small fishing net to get anchovies from the sea south of here. Then cook them on a fire or range.", beer_glass = "If you get some seaweed and a bucket of sand, you'll be able to mix them at a furnace to make molten glass. Then use a glassblowing pipe to make beer glasses. Most of what you need can be found on Entrana.", @@ -86,7 +86,7 @@ values = { leather_gloves = "If you get the tanner in Al Kharid to turn some cowhides into leather, you'll be able to sew the gloves yourself. Buy a needle & thread from the crafting shop in Al Kharid.", logs = "I suggest you take a hatchet and chop down some standard trees.", molten_glass = "Use a bucket of sand with some soda ash on a furnace.", - potato = "There's a field of those north of Lumbridge.", + raw_potato = "There's a field of those north of Lumbridge.", raw_rat_meat = "You should find some big rats south-east of here in the swamp.", rune_essence = "Ask the Archmage in the tower south of here to teleport you to the Rune Essence mine. Then you can mine me some pieces.", shrimps = "Use a small fishing net to get shrimps from the sea south of here. Then cook them on a fire or range.", @@ -94,7 +94,7 @@ values = { leather = "There's a tanner in Al Kharid who will turn cowhides into leather.", tin_ore = "You shouldn't have any trouble mining tin ore.", ball_of_wool = "You can buy shears from the general store east of here. Shear some sheep, then spin the wool into balls on the spinning wheel in Lumbridge Castle.", - bow_string = "If you pick some flax, you can use the spinning wheel in Lumbridge Castle to spin it into bowstrings.", + bowstring = "If you pick some flax, you can use the spinning wheel in Lumbridge Castle to spin it into bowstrings.", bread = "Mix some flour and water to make bread dough, then bake it on a cooking range.", bronze_arrowtips = "Try smelting some copper and tin ore to make bronze. Then hammer the bronze on an anvil to make arrowtips.", bronze_knife = "Try smelting some copper and tin ore to make bronze. Then hammer the bronze on an anvil to make knives.", @@ -110,4 +110,32 @@ values = { unfired_pie_dish = "Mine some clay. Then use a container full of water to soften it. In the Barbarian Village you'll be able to form this into pie dishes. Just don't bake them.", unfired_pot = "Mine some clay. Then use a container full of water to soften it. In the Barbarian Village you'll be able to form this into pots. Just don't bake them.", leather_boots = "If you get the tanner in Al Kharid to turn some cowhides into leather, you'll be able to make the boots yourself. Buy needles & thread from the crafting shop in Al Kharid if you need any.", +} + +[wise_old_man_npcs] +keyType = "npc" +valueType = "string" +values = { + father_aereck = "Father Aereck in Lumbridge is sure to reward you if you take a note to him for me.", + high_priest_entrana = "Could you please take a message to the High Priest of Entrana for me?", + reldo = "Reldo, the librarian in Varrock Palace, wrote to me recently about something. I need you to take my reply to him.", + thurgo = "Thurgo the dwarf is from an ancient tribe. I've found some of its history that he was asking about, and I need you to deliver the information to him.", + father_lawrence = "I hear my old friend Fr Lawrence in Varrock is drinking a bit too much. I'd like to get a letter to him about it.", + abbot_langley = "I've written a letter to my old friend Abbot Langley that I'd like you to deliver.", + oracle = "I'd like to get a note up to the Oracle on Ice Mountain.", + thing_under_the_bed = "Well, this is rather embarrassing, but I think there's some kind of monster in my house. Could you go upstairs and get rid of it, please?" +} + +[wise_old_man_npc_hints] +keyType = "npc" +valueType = "string" +values = { + father_aereck = "Just head eastwards to Lumbridge. He'll be in the church.", + high_priest_entrana = "There are some monks in Port Sarim, west of here, who will take you there so long as you're not carrying any weapons or armour.", + reldo = "The library is on the ground floor of the Palace in Varrock.", + thurgo = "Go west to Port Sarim, then southwards to some cliffs. He lives on the beach there.", + father_lawrence = "Varrock is a fairly long way north-east of here. The church is at the eastern side of the town, near the Palace.", + abbot_langley = "Walk northwards, past Draynor Manor. When you reach the Barbarian Village, go north-west until you find his Monastery. He'll be downstairs.", + oracle = "Walk northwards, past Draynor Manor. When you reach the Barbarian Village, go north-west until you see the Ice Mountain. It'll be at the top.", + thing_under_the_bed = "I think it's somewhere upstairs. It kept me awake all last night.", } \ No newline at end of file diff --git a/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml b/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml index eacc40f3d3..c437ef61be 100644 --- a/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml +++ b/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml @@ -2,6 +2,11 @@ format = "string" persist = true +[wise_old_man_npc] +format = "list" +persist = true +values = [ "father_aereck", "high_priest_entrana", "reldo", "thurgo", "father_lawrence", "abbot_langley", "oracle", "thing_under_the_bed" ] + [wise_old_man_amount] format = "int" persist = true diff --git a/game/src/main/kotlin/content/area/misthalin/draynor_village/WiseOldMan.kt b/game/src/main/kotlin/content/area/misthalin/draynor_village/WiseOldMan.kt index d478ddb727..8bff730974 100644 --- a/game/src/main/kotlin/content/area/misthalin/draynor_village/WiseOldMan.kt +++ b/game/src/main/kotlin/content/area/misthalin/draynor_village/WiseOldMan.kt @@ -1,5 +1,6 @@ package content.area.misthalin.draynor_village +import content.entity.player.bank.ownsItem import content.entity.player.dialogue.Bored import content.entity.player.dialogue.Happy import content.entity.player.dialogue.Laugh @@ -8,53 +9,31 @@ import content.entity.player.dialogue.Quiz import content.entity.player.dialogue.Sad import content.entity.player.dialogue.Shifty import content.entity.player.dialogue.Shock +import content.entity.player.dialogue.type.ChoiceOption import content.entity.player.dialogue.type.choice import content.entity.player.dialogue.type.npc import content.entity.player.dialogue.type.player import content.quest.questCompleted import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.data.definition.EnumDefinitions -import world.gregs.voidps.engine.data.definition.ItemDefinitions import world.gregs.voidps.engine.entity.World import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.name +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.type.random -class WiseOldMan( - val enums: EnumDefinitions, -) : Script { +class WiseOldMan : Script { init { - npcOperate("Talk-to", "wise_old_man") { + npcOperate("Talk-to", "wise_old_man_draynor") { npc("Greetings, $name.") if (get("wise_old_man_met", false)) { choice("What would you like to say?") { - if (contains("wise_old_man_task")) { - option("What did you ask me to do?") { - checkTask() - } - } else { - option("Is there anything I can do for you?") { - task() - } - } - option("Could you check my items for junk, please?") { - choice { - option("Could you check my bank for junk, please?") { - npc("Certainly, but I should warn you that I don't know about all items.") - // TODO add junk search - npc("There doesn't seem to be any junk in your bank at all.") - } - option("Could you check my inventory for junk, please?") { - npc("Certainly, but I should warn you that I don't know about all items.") - // TODO add junk search - npc("There doesn't seem to be any junk in your inventory at all.") - } -// if (follower != null) { // TODO and has BoB -// option("Could you check my beast of burden for junk, please?") -// } - } - } - option("I'd just like to ask you something.") + anyHelp(this@choice) + findJunk() + ask() } return@npcOperate } @@ -98,16 +77,52 @@ class WiseOldMan( } } } - option("I'd just like to ask you something.") { - npc("Please do!") - topic() - } - option("Is there anything I can do for you?") - option("Could you check my items for junk, please?") + ask() + anyHelp(this@choice) + findJunk() option("Thanks, maybe some other time.") } } + private fun ChoiceOption.ask() { + option("I'd just like to ask you something.") { + npc("Please do!") + topic() + } + } + + private fun Player.anyHelp(option: ChoiceOption) { + if (contains("wise_old_man_task")) { + option.option("What did you ask me to do?") { + checkTask() + } + } else { + option.option("Is there anything I can do for you?") { + task() + } + } + } + + private fun ChoiceOption.findJunk() { + option("Could you check my items for junk, please?") { + choice { + option("Could you check my bank for junk, please?") { + npc("Certainly, but I should warn you that I don't know about all items.") + // TODO add junk search + npc("There doesn't seem to be any junk in your bank at all.") + } + option("Could you check my inventory for junk, please?") { + npc("Certainly, but I should warn you that I don't know about all items.") + // TODO add junk search + npc("There doesn't seem to be any junk in your inventory at all.") + } + // if (follower != null) { // TODO and has BoB + // option("Could you check my beast of burden for junk, please?") + // } + } + } + } + private suspend fun Player.topic() { choice("Pick a topic") { option("Distant lands") { @@ -280,6 +295,15 @@ class WiseOldMan( } } } + + itemOnNPCOperate("old_mans_message", "wise_old_man_draynor") { + //Do you think I need to keep this? + if (contains("wise_old_man_npc")) { +// Yes, you're meant to be delivering it for me! + } else { + // I asked you to deliver that for me. But I may as well take it back now. + } + } } private suspend fun Player.anythingElse() { @@ -289,109 +313,163 @@ class WiseOldMan( } } - val tasks = setOf( - "anchovies", - "beer_glass", - "bones", - "bronze_arrow", - "bronze_bar", - "bronze_dagger", - "bronze_axe", - "bronze_mace", - "bronze_med_helm", - "bronze_spear", - "bronze_sword", - "beer", - "cadava_berries", - "cooked_chicken", - "cooked_meat", - "copper_ore", - "cowhide", - "egg", - "feather", - "grain", - "iron_bar", - "iron_mace", - "iron_ore", - "soft_clay", - "leather_gloves", - "logs", - "molten_glass", - "potato", - "raw_rat_meat", - "rune_essence", - "shrimps", - "silk", - "leather", - "tin_ore", - "ball_of_wool", - "bow_string", - "bread", - "bronze_arrowtips", - "bronze_knife", - "bronze_warhammer", - "bronze_wire", - "headless_arrow", - "swamp_paste", - "iron_arrowtips", - "iron_knife", - "iron_warhammer", - "leather_cowl", - "pot_of_flour", - "unfired_pie_dish", - "unfired_pot", - "leather_boots", - ) suspend fun Player.checkTask() { + val npc: String? = get("wise_old_man_npc") + if (npc != null) { + val intro = EnumDefinitions.string("wise_old_man_npcs", npc) + npc(intro) + if (npc != "thing_under_the_bed" && !ownsItem("old_mans_message")) { + npc("You seem to have mislaid my letter, so here's another copy.") + if (!inventory.add("old_mans_message")) { + // TODO + } + } + hintNpc(npc) + } val item: String = get("wise_old_man_task") ?: return val remaining: Int = get("wise_old_man_remaining") ?: return - val id = ItemDefinitions.get(item).id - val intro = enums.get("wise_old_man_tasks").getString(id) + val intro = EnumDefinitions.string("wise_old_man_items", item) npc("$intro. I still need $remaining.") - hint(id) + hintItem(item) } suspend fun Player.task() { npc("I'm sure I can think of a few little jobs. This won't be a quest, mind you, just a little favour...") - for (item in tasks) { - if (!ItemDefinitions.contains(item)) { - println("Doesn't exist: $item") - } - } - if (random.nextInt(100) == 2) { - set("wise_old_man_task", "thing_under_the_bed") - set("wise_old_man_amount", 1) - npc("Well, this is rather embarrassing, but I think there's some kind of monster in my house. Could you go upstairs and get rid of it, please?") - choice("What would you like to say?") { - option("Where do I need to go?") { - npc("I think it's somewhere upstairs. It kept me awake all last night.") - player("Right, I'll see you later.") + if (random.nextInt(100) < 16) { + val npc = setOf( + "father_aereck", "high_priest_entrana", "reldo", + "thurgo", "father_lawrence", "abbot_langley", + "oracle", "thing_under_the_bed" + ).random(random) + val intro = EnumDefinitions.string("wise_old_man_npcs", npc) + npc(intro) + set("wise_old_man_npc", npc) + if (npc != "thing_under_the_bed") { + npc("Here's the letter") + if (!inventory.add("old_mans_message")) { + // TODO } - option("Right, I'll see you later.") } + hintNpc(npc) return } - // TODO letters val amount = random.nextInt(3, 16) - val item = tasks.random(random) // TODO requirements + val item = tasks().random(random) set("wise_old_man_task", item) set("wise_old_man_amount", amount) set("wise_old_man_remaining", amount) - val id = ItemDefinitions.get(item).id - val intro = enums.get("wise_old_man_tasks").getString(id) + val intro = EnumDefinitions.string("wise_old_man_items", item) npc("$intro. Please bring me $amount.") - hint(id) + hintItem(item) + } + + private suspend fun Player.hintNpc(npc: String) { + choice("What would you like to say?") { + option("Where do I need to go?") { + npc(EnumDefinitions.string("wise_old_man_npc_hints", npc)) + player("Right, I'll see you later.") + } + option("Right, I'll see you later.") + } } - private suspend fun Player.hint(id: Int) { + private suspend fun Player.hintItem(item: String) { choice("What would you like to say?") { option("Where can I get that?") { - val hint = enums.get("wise_old_man_hints").getString(id) - npc(hint) + npc(EnumDefinitions.string("wise_old_man_item_hints", item)) player("Right, I'll see you later.") } option("Right, I'll see you later.") } } + + private fun Player.tasks(): MutableSet { + val tasks = mutableSetOf( + "beer_glass", + "bones", + "bronze_arrow", + "bronze_bar", + "bronze_dagger", + "bronze_hatchet", + "beer", + "cadava_berries", + "cooked_chicken", + "cooked_meat", + "copper_ore", + "cowhide", + "egg", + "feather", + "grain", + "soft_clay", + "leather_gloves", + "logs", + "molten_glass", + "raw_potato", + "raw_rat_meat", + "shrimps", + "silk", + "leather", + "tin_ore", + "ball_of_wool", + "bowstring", + "bread", + "headless_arrow", + "swamp_paste", + "pot_of_flour", + "unfired_pot", + ) + if (has(Skill.Fishing, 15)) { + tasks.add("anchovies") + } + if (has(Skill.Smithing, 2)) { + tasks.add("bronze_mace") + } + if (has(Skill.Smithing, 3)) { + tasks.add("bronze_med_helm") + } + if (has(Skill.Smithing, 4)) { + tasks.add("bronze_wire") + } + if (has(Skill.Smithing, 5)) { + tasks.add("bronze_spear") + tasks.add("bronze_sword") + tasks.add("bronze_arrowtips") + } + if (has(Skill.Smithing, 7)) { + tasks.add("bronze_knife") + } + if (has(Skill.Smithing, 9)) { + tasks.add("bronze_warhammer") + } + if (has(Skill.Smithing, 15)) { + tasks.add("iron_bar") + } + if (has(Skill.Smithing, 17)) { + tasks.add("iron_mace") + } + if (has(Skill.Smithing, 20)) { + tasks.add("iron_arrowtips") + } + if (has(Skill.Smithing, 20)) { + tasks.add("iron_knife") + } + if (has(Skill.Smithing, 24)) { + tasks.add("iron_warhammer") + } + if (has(Skill.Mining, 17)) { + tasks.add("iron_ore") + } + if (has(Skill.Crafting, 7)) { + tasks.add("unfired_pie_dish") + tasks.add("leather_boots") + } + if (has(Skill.Crafting, 9)) { + tasks.add("leather_cowl") + } + if (questCompleted("rune_mysteries")) { + tasks.add("rune_essence") + } + return tasks + } } From a666eed280c19cddcdc51a9de9a586172cab9ff5 Mon Sep 17 00:00:00 2001 From: GregHib Date: Thu, 26 Feb 2026 16:07:46 +0000 Subject: [PATCH 07/20] EnumDefinitions dependencies --- .../voidps/cache/definition/decoder/NPCDecoder.kt | 2 +- .../voidps/engine/data/definition/EnumDefinitions.kt | 8 ++++++++ .../engine/data/definition/InterfaceDefinitions.kt | 6 ++++++ .../engine/data/definition/InventoryDefinitions.kt | 6 ++++++ .../voidps/engine/data/definition/ItemDefinitions.kt | 1 + .../voidps/engine/data/definition/NPCDefinitions.kt | 9 ++++++++- .../engine/data/definition/StructDefinitions.kt | 3 +++ game/src/main/kotlin/Main.kt | 4 ++++ game/src/test/kotlin/WorldTest.kt | 4 ++++ .../world/gregs/voidps/tools/EnumDefinitions.kt | 12 ++++++++++++ 10 files changed, 53 insertions(+), 2 deletions(-) diff --git a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/decoder/NPCDecoder.kt b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/decoder/NPCDecoder.kt index ba28683e40..f96f6e92e3 100644 --- a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/decoder/NPCDecoder.kt +++ b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/decoder/NPCDecoder.kt @@ -7,7 +7,7 @@ import world.gregs.voidps.cache.definition.Parameters import world.gregs.voidps.cache.definition.data.NPCDefinition class NPCDecoder( - val member: Boolean, + val member: Boolean = true, private val parameters: Parameters = Parameters.EMPTY, ) : DefinitionDecoder(NPCS) { diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt index 2717da1b37..fe3e265007 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt @@ -104,6 +104,10 @@ object EnumDefinitions : DefinitionsDecoder { fun load(list: List): EnumDefinitions { timedLoad("enum extra") { require(ItemDefinitions.loaded) { "Item definitions must be loaded before enum definitions" } + require(InterfaceDefinitions.loaded) { "Interface definitions must be loaded before enum definitions" } + require(InventoryDefinitions.loaded) { "Inventory definitions must be loaded before enum definitions" } + require(NPCDefinitions.loaded) { "NPC definitions must be loaded before enum definitions" } + require(StructDefinitions.loaded) { "Struct definitions must be loaded before enum definitions" } val ids = Object2IntOpenHashMap(definitions.size, Hash.VERY_FAST_LOAD_FACTOR) for (path in list) { Config.fileReader(path, 250) { @@ -138,6 +142,10 @@ object EnumDefinitions : DefinitionsDecoder { val key = key() val keyInt = when (keyType) { EnumTypes.ITEM, EnumTypes.ITEM_2 -> ItemDefinitions.getOrNull(key)?.id ?: error("Unknown item '$key' ${exception()}") + EnumTypes.COMPONENT -> InterfaceDefinitions.getOrNull(key)?.id ?: error("Unknown interface '$key' ${exception()}") + EnumTypes.INV -> InventoryDefinitions.getOrNull(key)?.id ?: error("Unknown inventory '$key' ${exception()}") + EnumTypes.NPC -> NPCDefinitions.getOrNull(key)?.id ?: error("Unknown npc '$key' ${exception()}") + EnumTypes.STRUCT -> StructDefinitions.getOrNull(key)?.id ?: error("Unknown struct '$key' ${exception()}") else -> key.toInt() } map[keyInt] = value() diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/InterfaceDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/InterfaceDefinitions.kt index a9f6d77e24..ff7c0913d4 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/InterfaceDefinitions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/InterfaceDefinitions.kt @@ -18,8 +18,12 @@ object InterfaceDefinitions : DefinitionsDecoder { override var ids: Map = emptyMap() var componentIds: Map = emptyMap() + var loaded = false + private set + fun init(definitions: Array): InterfaceDefinitions { this.definitions = definitions + loaded = true return this } @@ -28,12 +32,14 @@ object InterfaceDefinitions : DefinitionsDecoder { this.definitions = definitions this.ids = ids this.componentIds = components + loaded = true } fun clear() { this.definitions = emptyArray() this.ids = emptyMap() this.componentIds = emptyMap() + loaded = false } fun getComponentId(id: String, component: String) = componentIds["$id:$component"] diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/InventoryDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/InventoryDefinitions.kt index 432802989b..08b4f93b27 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/InventoryDefinitions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/InventoryDefinitions.kt @@ -13,8 +13,12 @@ object InventoryDefinitions : DefinitionsDecoder { override var definitions: Array = emptyArray() override var ids: Map = emptyMap() + var loaded = false + private set + fun init(definitions: Array): InventoryDefinitions { this.definitions = definitions + loaded = true return this } @@ -22,11 +26,13 @@ object InventoryDefinitions : DefinitionsDecoder { fun set(definitions: Array, ids: Map) { this.definitions = definitions this.ids = ids + loaded = true } fun clear() { this.definitions = emptyArray() this.ids = emptyMap() + loaded = false } override fun empty() = InventoryDefinition.EMPTY diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/ItemDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/ItemDefinitions.kt index 885774626f..935b1c42e4 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/ItemDefinitions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/ItemDefinitions.kt @@ -21,6 +21,7 @@ object ItemDefinitions : DefinitionsDecoder { override var definitions: Array = emptyArray() var loaded = false + private set val size: Int get() = definitions.size diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/NPCDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/NPCDefinitions.kt index e05becff88..52cf181c81 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/NPCDefinitions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/NPCDefinitions.kt @@ -20,8 +20,12 @@ object NPCDefinitions : DefinitionsDecoder { override lateinit var definitions: Array + var loaded = false + private set + fun init(definitions: Array): NPCDefinitions { this.definitions = definitions + loaded = true return this } @@ -29,17 +33,19 @@ object NPCDefinitions : DefinitionsDecoder { fun set(definitions: Array, map: Map) { this.definitions = definitions this.ids = map + loaded = true } fun clear() { definitions = emptyArray() ids = emptyMap() + loaded = false } fun load( paths: List, dropTables: DropTables? = null, - ) { + ): NPCDefinitions { timedLoad("npc extra") { val ids = Object2IntOpenHashMap() val refs = Object2IntOpenHashMap() @@ -97,5 +103,6 @@ object NPCDefinitions : DefinitionsDecoder { this.ids = ids ids.size } + return this } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/StructDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/StructDefinitions.kt index 7fe408ea95..cd19221652 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/StructDefinitions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/StructDefinitions.kt @@ -18,6 +18,9 @@ object StructDefinitions : DefinitionsDecoder { override var ids: Map = emptyMap() + var loaded = false + private set + fun init(definitions: Array): StructDefinitions { this.definitions = definitions loaded = true diff --git a/game/src/main/kotlin/Main.kt b/game/src/main/kotlin/Main.kt index 53a2986cb2..33a2d583d7 100644 --- a/game/src/main/kotlin/Main.kt +++ b/game/src/main/kotlin/Main.kt @@ -126,6 +126,10 @@ object Main { single(createdAtStart = true) { AnimationDefinitions(AnimationDecoder().load(cache)).load(files.list(Settings["definitions.animations"])) } single(createdAtStart = true) { get() + get() + get() + get() + get() EnumDefinitions.init(EnumDecoder().load(cache)).load(files.list(Settings["definitions.enums"])) } single(createdAtStart = true) { GraphicDefinitions(GraphicDecoder().load(cache)).load(files.list(Settings["definitions.graphics"])) } diff --git a/game/src/test/kotlin/WorldTest.kt b/game/src/test/kotlin/WorldTest.kt index 7377fdbe6c..a358d0c62d 100644 --- a/game/src/test/kotlin/WorldTest.kt +++ b/game/src/test/kotlin/WorldTest.kt @@ -358,6 +358,10 @@ abstract class WorldTest : KoinTest { } private val enumIds: Map by lazy { itemIds + interfaceIds + inventoryIds + npcIds + structIds EnumDefinitions.init(EnumDecoder().load(cache)).load(configFiles.list(Settings["definitions.enums"])) EnumDefinitions.ids } diff --git a/tools/src/main/kotlin/world/gregs/voidps/tools/EnumDefinitions.kt b/tools/src/main/kotlin/world/gregs/voidps/tools/EnumDefinitions.kt index 08415ec594..1a2fe95320 100644 --- a/tools/src/main/kotlin/world/gregs/voidps/tools/EnumDefinitions.kt +++ b/tools/src/main/kotlin/world/gregs/voidps/tools/EnumDefinitions.kt @@ -2,12 +2,20 @@ package world.gregs.voidps.tools import world.gregs.voidps.cache.Cache import world.gregs.voidps.cache.CacheDelegate +import world.gregs.voidps.cache.config.decoder.InventoryDecoder +import world.gregs.voidps.cache.config.decoder.StructDecoder import world.gregs.voidps.cache.definition.decoder.EnumDecoder +import world.gregs.voidps.cache.definition.decoder.InterfaceDecoder import world.gregs.voidps.cache.definition.decoder.ItemDecoder +import world.gregs.voidps.cache.definition.decoder.NPCDecoder import world.gregs.voidps.engine.data.Settings import world.gregs.voidps.engine.data.configFiles import world.gregs.voidps.engine.data.definition.EnumDefinitions +import world.gregs.voidps.engine.data.definition.InterfaceDefinitions +import world.gregs.voidps.engine.data.definition.InventoryDefinitions import world.gregs.voidps.engine.data.definition.ItemDefinitions +import world.gregs.voidps.engine.data.definition.NPCDefinitions +import world.gregs.voidps.engine.data.definition.StructDefinitions object EnumDefinitions { @@ -17,6 +25,10 @@ object EnumDefinitions { val cache: Cache = CacheDelegate(Settings["storage.cache.path"]) val files = configFiles() ItemDefinitions.init(ItemDecoder().load(cache)).load(files.list(Settings["definitions.items"])) + InterfaceDefinitions.init(InterfaceDecoder().load(cache)).load(files.list(Settings["definitions.interfaces"]), files.find(Settings["definitions.interfaces.types"])) + InventoryDefinitions.init(InventoryDecoder().load(cache)).load(files.list(Settings["definitions.inventories"]), files.list(Settings["definitions.shops"])) + NPCDefinitions.init(NPCDecoder().load(cache)).load(files.list(Settings["definitions.npcs"])) + StructDefinitions.init(StructDecoder().load(cache)).load(files.find(Settings["definitions.structs"])) val definitions = EnumDefinitions.init(EnumDecoder().load(cache)).load(files.list(Settings["definitions.enums"])) for (i in definitions.definitions.indices) { val def = definitions.getOrNull(i) ?: continue From 30e4cf19a7158ec77257131cd344c9c0c3574037 Mon Sep 17 00:00:00 2001 From: GregHib Date: Thu, 26 Feb 2026 16:08:35 +0000 Subject: [PATCH 08/20] Rename drop table typo --- .../world/gregs/voidps/engine/entity/item/drop/DropTable.kt | 2 +- .../gregs/voidps/engine/entity/item/drop/DropTableTest.kt | 4 ++-- .../kotlin/content/area/kandarin/ourania/OuraniaAltar.kt | 2 +- game/src/main/kotlin/content/entity/death/NPCDeath.kt | 2 +- .../kotlin/content/entity/player/command/DropCommands.kt | 2 +- .../content/minigame/sorceress_garden/SorceressGarden.kt | 4 ++-- game/src/main/kotlin/content/quest/Quest.kt | 5 ++--- .../src/main/kotlin/content/skill/thieving/Pickpocketing.kt | 6 +++--- game/src/main/kotlin/content/skill/thieving/Stalls.kt | 2 +- game/src/main/kotlin/content/skill/thieving/TrapChests.kt | 2 +- game/src/main/kotlin/content/skill/woodcutting/BirdsNest.kt | 2 +- .../main/kotlin/content/skill/woodcutting/Woodcutting.kt | 2 +- 12 files changed, 17 insertions(+), 18 deletions(-) diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/item/drop/DropTable.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/item/drop/DropTable.kt index e651b1d8ca..507937fd2c 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/item/drop/DropTable.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/item/drop/DropTable.kt @@ -26,7 +26,7 @@ data class DropTable( * @param list optional list to add the drop to * @param player the player for [ItemDrop.predicate]'s */ - fun role(maximumRoll: Int = -1, list: MutableList = mutableListOf(), player: Player? = null): MutableList { + fun roll(maximumRoll: Int = -1, list: MutableList = mutableListOf(), player: Player? = null): MutableList { collect(list, maximumRoll, player, random(maximumRoll)) return list } diff --git a/engine/src/test/kotlin/world/gregs/voidps/engine/entity/item/drop/DropTableTest.kt b/engine/src/test/kotlin/world/gregs/voidps/engine/entity/item/drop/DropTableTest.kt index c1d0a494f6..36074defdc 100644 --- a/engine/src/test/kotlin/world/gregs/voidps/engine/entity/item/drop/DropTableTest.kt +++ b/engine/src/test/kotlin/world/gregs/voidps/engine/entity/item/drop/DropTableTest.kt @@ -90,7 +90,7 @@ internal class DropTableTest { val item2 = drop("2", 1) val root = DropTable(TableType.First, 5, listOf(item1, item2), 1) - val list = root.role() + val list = root.roll() assertEquals(listOf(item2), list) } @@ -104,7 +104,7 @@ internal class DropTableTest { val item2 = drop("2", 1) val root = DropTable(TableType.First, -1, listOf(item1, item2), 1) - val list = root.role(maximumRoll = 10) + val list = root.roll(maximumRoll = 10) assertTrue(list.isEmpty()) } diff --git a/game/src/main/kotlin/content/area/kandarin/ourania/OuraniaAltar.kt b/game/src/main/kotlin/content/area/kandarin/ourania/OuraniaAltar.kt index b341e589c9..5e1acd0b2c 100644 --- a/game/src/main/kotlin/content/area/kandarin/ourania/OuraniaAltar.kt +++ b/game/src/main/kotlin/content/area/kandarin/ourania/OuraniaAltar.kt @@ -34,7 +34,7 @@ class OuraniaAltar(val drops: DropTables) : Script { } val runes = mutableListOf() for (i in 0 until essence) { - table.role(list = runes) + table.roll(list = runes) } for (drop in runes) { val item = drop.toItem() diff --git a/game/src/main/kotlin/content/entity/death/NPCDeath.kt b/game/src/main/kotlin/content/entity/death/NPCDeath.kt index db08a814f0..eef288de08 100644 --- a/game/src/main/kotlin/content/entity/death/NPCDeath.kt +++ b/game/src/main/kotlin/content/entity/death/NPCDeath.kt @@ -102,7 +102,7 @@ class NPCDeath( is NPC -> killer.def.combat else -> -1 } - val drops = table.role(maximumRoll = if (combatLevel > 0) combatLevel * 10 else -1, player = killer as? Player) + val drops = table.roll(maximumRoll = if (combatLevel > 0) combatLevel * 10 else -1, player = killer as? Player) .filterNot { it.id == "nothing" } .reversed() .map { it.toItem() } diff --git a/game/src/main/kotlin/content/entity/player/command/DropCommands.kt b/game/src/main/kotlin/content/entity/player/command/DropCommands.kt index 5bee38ae3f..9b3d8b5380 100644 --- a/game/src/main/kotlin/content/entity/player/command/DropCommands.kt +++ b/game/src/main/kotlin/content/entity/player/command/DropCommands.kt @@ -79,7 +79,7 @@ class DropCommands(val tables: DropTables) : Script { val temp = Inventory.debug(capacity = 100) val list = InventoryDelegate(temp) for (i in numbers) { - table.role(list = list, player = player) + table.roll(list = list, player = player) } temp } diff --git a/game/src/main/kotlin/content/minigame/sorceress_garden/SorceressGarden.kt b/game/src/main/kotlin/content/minigame/sorceress_garden/SorceressGarden.kt index a884e37dcc..20bc91155a 100644 --- a/game/src/main/kotlin/content/minigame/sorceress_garden/SorceressGarden.kt +++ b/game/src/main/kotlin/content/minigame/sorceress_garden/SorceressGarden.kt @@ -106,8 +106,8 @@ class SorceressGarden(val dropTables: DropTables) : Script { val table = dropTables.get("${type}_herbs_drop_table") if (table != null) { val drops = mutableListOf() - table.role(list = drops) - table.role(list = drops) + table.roll(list = drops) + table.roll(list = drops) inventory.transaction { for (drop in drops) { add(drop.id, 1) diff --git a/game/src/main/kotlin/content/quest/Quest.kt b/game/src/main/kotlin/content/quest/Quest.kt index 7a1c8c6278..5aae63dbc3 100644 --- a/game/src/main/kotlin/content/quest/Quest.kt +++ b/game/src/main/kotlin/content/quest/Quest.kt @@ -77,13 +77,12 @@ fun Player.letterScroll(name: String, lines: List) { } } -fun Player.wiseOldManScroll(name: String, lines: List) { +fun Player.wiseOldManScroll(lines: List) { if (!interfaces.open("wise_old_man_scroll")) { return } - interfaces.sendText("wise_old_man_scroll", "title", name) for (i in 0..16) { - interfaces.sendText("wise_old_man_scroll", "line${i + 1}", lines.getOrNull(i) ?: "") + interfaces.sendText("wise_old_man_scroll", "line${i}", lines.getOrNull(i) ?: "") } } diff --git a/game/src/main/kotlin/content/skill/thieving/Pickpocketing.kt b/game/src/main/kotlin/content/skill/thieving/Pickpocketing.kt index d32876748e..60bd35e197 100644 --- a/game/src/main/kotlin/content/skill/thieving/Pickpocketing.kt +++ b/game/src/main/kotlin/content/skill/thieving/Pickpocketing.kt @@ -87,16 +87,16 @@ class Pickpocketing(val combatDefinitions: CombatDefinitions, val dropTables: Dr fun getLoot(target: NPC, table: String?): List? { var id = dropTables.get("${table}_pickpocket") if (id != null) { - return id.role() + return id.roll() } id = dropTables.get("${target.id}_pickpocket") if (id != null) { - return id.role() + return id.roll() } for (category in target.categories) { id = dropTables.get("${category}_pickpocket") if (id != null) { - return id.role() + return id.roll() } } return null diff --git a/game/src/main/kotlin/content/skill/thieving/Stalls.kt b/game/src/main/kotlin/content/skill/thieving/Stalls.kt index 982a4ad729..a74d15befb 100644 --- a/game/src/main/kotlin/content/skill/thieving/Stalls.kt +++ b/game/src/main/kotlin/content/skill/thieving/Stalls.kt @@ -60,7 +60,7 @@ class Stalls(val drops: DropTables) : Script { } val table = drops.get("${target.id}_drop_table") if (table != null) { - val drops = table.role().map { it.toItem() } + val drops = table.roll().map { it.toItem() } inventory.transaction { for (item in drops) { add(item.id, item.amount) diff --git a/game/src/main/kotlin/content/skill/thieving/TrapChests.kt b/game/src/main/kotlin/content/skill/thieving/TrapChests.kt index 2bc1f99f3a..7c4cb10247 100644 --- a/game/src/main/kotlin/content/skill/thieving/TrapChests.kt +++ b/game/src/main/kotlin/content/skill/thieving/TrapChests.kt @@ -70,7 +70,7 @@ class TrapChests(val tables: DropTables) : Script { delay(1) val table = tables.get("${target.id}_drop_table") if (table != null) { - val drops = table.role().map { it.toItem() } + val drops = table.roll().map { it.toItem() } inventory.transaction { for (item in drops) { add(item.id, item.amount) diff --git a/game/src/main/kotlin/content/skill/woodcutting/BirdsNest.kt b/game/src/main/kotlin/content/skill/woodcutting/BirdsNest.kt index b97bce78b6..04d4354677 100644 --- a/game/src/main/kotlin/content/skill/woodcutting/BirdsNest.kt +++ b/game/src/main/kotlin/content/skill/woodcutting/BirdsNest.kt @@ -26,7 +26,7 @@ class BirdsNest(val drops: DropTables) : Script { val table = drops.get(tableId) ?: return@itemOption val items = mutableListOf() - table.role(list = items) + table.roll(list = items) val drop = items.firstOrNull() ?: return@itemOption val itemId = drop.id diff --git a/game/src/main/kotlin/content/skill/woodcutting/Woodcutting.kt b/game/src/main/kotlin/content/skill/woodcutting/Woodcutting.kt index 06bdde308a..860e4bdf71 100644 --- a/game/src/main/kotlin/content/skill/woodcutting/Woodcutting.kt +++ b/game/src/main/kotlin/content/skill/woodcutting/Woodcutting.kt @@ -105,7 +105,7 @@ class Woodcutting(val drops: DropTables) : Script { val hasRabbitFoot = player.equipment.contains("strung_rabbit_foot") val totalWeight = if (hasRabbitFoot) 95 else 100 - val drop = table.role(totalWeight).firstOrNull() ?: return + val drop = table.roll(totalWeight).firstOrNull() ?: return val source = if (ivy) "ivy" else "tree" player.message("A bird's nest falls out of the $source!") From db3e1e887c25e9209c05dfbf316182923187e138 Mon Sep 17 00:00:00 2001 From: GregHib Date: Thu, 26 Feb 2026 16:47:51 +0000 Subject: [PATCH 09/20] Start adding wise old man letters --- .../wise_old_man/wise_old_man.drops.toml | 68 +++++++++++ .../wise_old_man/OldMansMessage.kt | 113 ++++++++++++++++++ .../{ => wise_old_man}/WiseOldMan.kt | 25 ++-- .../lumbridge/church/FatherAereck.kt | 56 ++++++++- 4 files changed, 252 insertions(+), 10 deletions(-) create mode 100644 data/area/misthalin/draynor/wise_old_man/wise_old_man.drops.toml create mode 100644 game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/OldMansMessage.kt rename game/src/main/kotlin/content/area/misthalin/draynor_village/{ => wise_old_man}/WiseOldMan.kt (97%) diff --git a/data/area/misthalin/draynor/wise_old_man/wise_old_man.drops.toml b/data/area/misthalin/draynor/wise_old_man/wise_old_man.drops.toml new file mode 100644 index 0000000000..a57a0daf96 --- /dev/null +++ b/data/area/misthalin/draynor/wise_old_man/wise_old_man.drops.toml @@ -0,0 +1,68 @@ +[wise_old_man_gems] +roll = 25 +drops = [ + { id = "uncut_red_topaz", chance = 2 }, + { id = "uncut_sapphire", chance = 4 }, + { id = "uncut_opal", chance = 5 }, + { id = "uncut_emerald", chance = 4 }, + { id = "uncut_ruby", chance = 4 }, + { id = "uncut_jade", chance = 5 }, + { id = "uncut_diamond", chance = 1 }, +] + +[wise_old_man_runes] +roll = 9 +drops = [ + { id = "air_rune", amount = 3 }, + { id = "water_rune", amount = 2 }, + { id = "earth_rune", amount = 2 }, + { id = "body_rune", amount = 2 }, + { id = "mind_rune", amount = 2 }, + { id = "fire_rune", amount = 1 }, + { id = "law_rune", amount = 1 }, + { id = "nature_rune", amount = 1 }, + { id = "chaos_rune", amount = 1 }, +] + +[wise_old_man_herbs] +drops = [ + { id = "grimy_guam_noted" }, + { id = "grimy_marrentill_noted" }, + { id = "grimy_tarromin_noted" }, + { id = "grimy_ranarr_noted" }, + { id = "grimy_harralander_noted" }, +] + +[wise_old_man_seeds] +roll = 67 +drops = [ + { id = "potato_seed", chance = 4 }, + { id = "onion_seed", chance = 4 }, + { id = "tomato_seed", chance = 4 }, + { id = "cabbage_seed", chance = 4 }, + { id = "marigold_seed", chance = 4 }, + { id = "yanillian_seed", chance = 2 }, + { id = "cactus_seed", chance = 2 }, + { id = "cadavaberry_seed", chance = 2 }, + { id = "hammerstone_seed", chance = 2 }, + { id = "jangerberry_seed", chance = 2 }, + { id = "barley_seed", chance = 2 }, + { id = "nasturtium_seed", chance = 2 }, + { id = "strawberry_seed", chance = 2 }, + { id = "jute_seed", chance = 2 }, + { id = "woad_seed", chance = 2 }, + { id = "guam_seed", chance = 2 }, + { id = "marrentill_seed", chance = 2 }, + { id = "tarromin_seed", chance = 2 }, + { id = "toadflax_seed", chance = 2 }, + { id = "harralander_seed", chance = 2 }, + { id = "watermelon_seed", chance = 2 }, + { id = "rosemary_seed", chance = 2 }, + { id = "redberry_seed", chance = 2 }, + { id = "dwellberry_seed", chance = 2 }, + { id = "asgarnian_seed", chance = 2 }, + { id = "sweetcorn_seed", chance = 2 }, + { id = "whiteberry_seed", chance = 2 }, + { id = "wildblood_seed", chance = 2 }, + { id = "limpwurt_seed", chance = 1 }, +] \ No newline at end of file diff --git a/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/OldMansMessage.kt b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/OldMansMessage.kt new file mode 100644 index 0000000000..aaeef23083 --- /dev/null +++ b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/OldMansMessage.kt @@ -0,0 +1,113 @@ +package content.area.misthalin.draynor_village.wise_old_man + +import content.quest.wiseOldManScroll +import world.gregs.voidps.engine.Script + +class OldMansMessage : Script { + init { + itemOption("Read", "old_mans_message") { + when (get("wise_old_man_npc", "")) { + "father_aereck" -> wiseOldManScroll( + listOf( + "", + "To Aereck, resident priest of Lumbridge,", + "greetings:", + "", + "I am pleased to inform you that, following a", + "careful search, the staff of the seminary have", + "located your pyjamas.", + "", + "Before returning them to you, however, they", + "would be very interested to know exactly how", + "these garments came to be in the graveyard.", + "", + "Please pass on my regards to Urhney.", + "*D", + "", + "", + ) + ) + "abbot_langley" -> wiseOldManScroll( + listOf( + "", + "To Langley, Abbot of the Monastery of", + "Saradomin, greetings:", + "", + "Long has it been since our last meeting, my", + "friend, too long. Truly we are living in", + "tumultuous times, and the foul works of Zamorak", + "can be seen across the lands. Indeed, I hear a", + "whisper from the south that the power of the", + "Terrible One has been rediscovered!", + "But be of good cheer, my friend, for we are all", + "in the hands of Lord Saradomin.", + "", + "Until our next meeting, then,", + "*D", + "", + ) + ) + "high_priest_entrana" -> wiseOldManScroll( + listOf( + "To the High Priest of Entrana, greetings:", + "", + "In an effort to respond to your recent questions", + "about the effects of summoning the power of", + "Saradomin, I have spent some time searching", + "through the scrolls of Kolodion the Battle Mage.", + "He records that a bolt of lightning falls from", + "above, accompanied by a resounding crash, and", + "the victim loses up to 20 points of health.", + "However, he believed that this could be increased", + "by 50% should one be wearing the Cape of", + "Saradomin and be Charged when casting the", + "spell.", + "", + "Fare thee well, my young friend, - *D", + "", + ) + ) + "father_lawrence" -> wiseOldManScroll( + listOf( + "", + "To Lawrence, resident priest of Varrock,", + "greetings:", + "", + "Despite our recent conversation on this matter, I", + "hear that you are still often found in a less than", + "sober condition. I am forced to repeat the", + "warning I gave you at the time: if you continue", + "to indulge yourself in this manner, the Council", + "will have no choice but to transfer you to", + "Entrana where you can be supervised more", + "carefully.", + "", + "I trust you will heed this message.", + "*D", + "", + ) + ) + "thurgo" -> wiseOldManScroll( + listOf( + "", + "To Thurgo, master blacksmith, greetings:", + "", + "Following your request, I have spent some time", + "re-reading the relevant scrolls in the Library of", + "Varrock. It appears that when your forefathers", + "encountered that adventurer, he was on a quest", + "to find a mysterious shield.", + "", + "Many thanks for the recipe you sent me; I shall", + "certainly try this 'redberry pie' of which you", + "speak so highly.", + "", + "Regards,", + "*D", + "", + ) + ) + } + } + } +} \ No newline at end of file diff --git a/game/src/main/kotlin/content/area/misthalin/draynor_village/WiseOldMan.kt b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt similarity index 97% rename from game/src/main/kotlin/content/area/misthalin/draynor_village/WiseOldMan.kt rename to game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt index 8bff730974..d8dff5ba5a 100644 --- a/game/src/main/kotlin/content/area/misthalin/draynor_village/WiseOldMan.kt +++ b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt @@ -1,7 +1,8 @@ -package content.area.misthalin.draynor_village +package content.area.misthalin.draynor_village.wise_old_man import content.entity.player.bank.ownsItem import content.entity.player.dialogue.Bored +import content.entity.player.dialogue.Confused import content.entity.player.dialogue.Happy import content.entity.player.dialogue.Laugh import content.entity.player.dialogue.Neutral @@ -15,6 +16,7 @@ import content.entity.player.dialogue.type.npc import content.entity.player.dialogue.type.player import content.quest.questCompleted import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.ui.InterfaceApi.Companion.option import world.gregs.voidps.engine.data.definition.EnumDefinitions import world.gregs.voidps.engine.entity.World import world.gregs.voidps.engine.entity.character.player.Player @@ -23,6 +25,7 @@ import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has import world.gregs.voidps.engine.inv.add import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.inv.remove import world.gregs.voidps.type.random class WiseOldMan : Script { @@ -297,13 +300,15 @@ class WiseOldMan : Script { } itemOnNPCOperate("old_mans_message", "wise_old_man_draynor") { - //Do you think I need to keep this? + player("Do you think I need to keep this?") if (contains("wise_old_man_npc")) { -// Yes, you're meant to be delivering it for me! + npc("Yes, you're meant to be delivering it for me!") } else { - // I asked you to deliver that for me. But I may as well take it back now. + inventory.remove("old_mans_message") + npc("I asked you to deliver that for me. But I may as well take it back now.") } } + } private suspend fun Player.anythingElse() { @@ -322,7 +327,8 @@ class WiseOldMan : Script { if (npc != "thing_under_the_bed" && !ownsItem("old_mans_message")) { npc("You seem to have mislaid my letter, so here's another copy.") if (!inventory.add("old_mans_message")) { - // TODO + npc("Please make room in your inventory to carry the letter.") + return } } hintNpc(npc) @@ -343,12 +349,13 @@ class WiseOldMan : Script { "oracle", "thing_under_the_bed" ).random(random) val intro = EnumDefinitions.string("wise_old_man_npcs", npc) - npc(intro) + npc(intro) set("wise_old_man_npc", npc) if (npc != "thing_under_the_bed") { - npc("Here's the letter") + npc("Here's the letter") if (!inventory.add("old_mans_message")) { - // TODO + npc("Please make room in your inventory to carry the letter.") + return } } hintNpc(npc) @@ -472,4 +479,4 @@ class WiseOldMan : Script { } return tasks } -} +} \ No newline at end of file diff --git a/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt b/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt index 3bcabfcc52..67a7cd9911 100644 --- a/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt +++ b/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt @@ -2,20 +2,29 @@ package content.area.misthalin.lumbridge.church import content.entity.player.dialogue.* import content.entity.player.dialogue.type.* +import content.entity.player.inv.item.addOrDrop import content.quest.quest import content.quest.refreshQuestJournal import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.ui.open import world.gregs.voidps.engine.data.Settings import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.exp.exp +import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has +import world.gregs.voidps.engine.entity.item.drop.DropTables +import world.gregs.voidps.engine.entity.item.drop.ItemDrop +import world.gregs.voidps.engine.inv.add import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.replace +import world.gregs.voidps.type.random -class FatherAereck : Script { +class FatherAereck(val drops: DropTables) : Script { init { npcOperate("Talk-to", "father_aereck") { + wiseOldManLetter() when (quest("the_restless_ghost")) { "unstarted" -> { npc("Welcome to the church of holy Saradomin.") @@ -52,6 +61,51 @@ class FatherAereck : Script { } } + private suspend fun Player.wiseOldManLetter() { + if (get("wise_old_man_npc", "") != "father_aereck") { + return + } + player("The Wise Old Man of Draynor Village said you might reward me if I brought you this.") + npc("Oh, did he?") + if (inventory.isFull()) { + npc("I'd give you a reward, but you don't seem to have any space for it. Come back when you do.") + return + } + val easy = false + val chance = random.nextInt(16) + if (chance < 1) { // 6.25% + npc("I suppose gems are always acceptable rewards!") + + } else if (chance < 3) { // 12.5% + npc("Well, maybe you'll have a use for these?") + val range = if (easy) 1..10 else 1..25 + val count = range.random(random) + val table = drops.getValue("wise_old_man_runes") + val drops = mutableListOf() + for (i in 0 until count) { + table.roll(list = drops) + } + for (drop in drops) { + val item = drop.toItem() + addOrDrop(item.id, item.amount) + } + } else if (chance < 5) { // 12.5% + npc("") // TODO herbs + val table = drops.getValue("wise_old_man_gems") + table.roll() + } else if (chance < 7) { // 12.5% + npc("Well, maybe you'll find a use for these seeds?") + } else if (has(Skill.Prayer, 3) && chance < 14) { // 43.75% + exp(Skill.Prayer, 10.0) + clear("wise_old_man_npc") + levelUp(Skill.Prayer, "Faether Aereck blesses you.
You gain some Prayer xp.") + npc("Well, it's still nice of you to bring the message here.
Here, I shall bless you...") + } else { // 12.5% or 56.25% depending on prayer level + val range = if (easy) 185..215 else 990..1020 + inventory.add("coins", range.random(random)) + } + } + suspend fun Player.started() { npc("Have you got rid of the ghost yet?") player("I can't find Father Urhney at the moment.") From ebd3f9a4957c37cf051079cdc10b9036bb5b15e0 Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 27 Feb 2026 12:49:12 +0000 Subject: [PATCH 10/20] Add support for colour wrapping on text split --- .../cache/definition/data/FontDefinition.kt | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/FontDefinition.kt b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/FontDefinition.kt index 6da33a75ff..102328c07e 100644 --- a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/FontDefinition.kt +++ b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/data/FontDefinition.kt @@ -148,6 +148,7 @@ data class FontDefinition( var wordStart = 0 var tagStart = -1 var lastChar = -1 + var colour: String? = null for (index in input.indices) { var current: Int = charToByte(input[index]) and 0xff var extraWidth = 0 @@ -194,7 +195,12 @@ data class FontDefinition( "euro" -> addKernelWidth(8364) "copy" -> addKernelWidth(169) "reg" -> addKernelWidth(174) - else -> { + "blue", "orange", "green", "red", "red_orange", "yellow", "lime", "gold", "white", + "black", "navy", "maroon", "purple", "brown", "violet", "dark_green", "dark_red", + -> colour = tag + else -> if (tag.startsWith("col=")) { + colour = tag + } else { totalWidth += spriteWidth(tag, icons) ?: continue lastChar = -1 } @@ -215,13 +221,21 @@ data class FontDefinition( } if (totalWidth > widths[if (widths.size > output.size) output.size else widths.size - 1]) { if (lineLength >= 0) { - output.add(input.substring(lineStart, lineLength + 1 - wordStart)) + if (colour != null && output.isNotEmpty()) { + output.add("<${colour}>${input.substring(lineStart, lineLength + 1 - wordStart)}") + } else { + output.add(input.substring(lineStart, lineLength + 1 - wordStart)) + } lineStart = lineLength + 1 lastChar = -1 lineLength = -1 totalWidth -= wordWidth } else { - output.add(input.substring(lineStart, currentWidth)) + if (colour != null && output.isNotEmpty()) { + output.add("<${colour}>${input.substring(lineStart, currentWidth)}") + } else { + output.add(input.substring(lineStart, currentWidth)) + } lineStart = currentWidth lastChar = -1 lineLength = -1 @@ -235,7 +249,11 @@ data class FontDefinition( } } if (lineStart < input.length) { - output.add(input.substring(lineStart)) + if (colour != null && output.isNotEmpty()) { + output.add("<${colour}>${input.substring(lineStart)}") + } else { + output.add(input.substring(lineStart)) + } } return output } From a9015c648fdd40d2d6726cce12671bbba25fede5 Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 27 Feb 2026 17:51:14 +0000 Subject: [PATCH 11/20] Add edgeville monastery, and other npcs with letter dialogues --- .../misthalin/edgeville/edgeville.teles.toml | 4 +- .../edgeville/monastery/monastery.gfx.toml | 3 + .../edgeville/monastery/monastery.objs.toml | 3 + .../edgeville/monastery/monastery.sounds.toml | 2 + .../player/dialogue/dialogue.ifaces.toml | 3 + .../area/asgarnia/entrana/HighPriest.kt | 54 +++++++++ .../area/asgarnia/port_sarim/Thurgo.kt | 37 ++++++ .../wise_old_man/OldMansMessage.kt | 90 ++++++++++++++- .../area/misthalin/edgeville/Oracle.kt | 89 +++++++++++++++ .../edgeville/monastery/AbbotLangley.kt | 83 ++++++++++++++ .../edgeville/monastery/BrotherJared.kt | 106 ++++++++++++++++++ .../edgeville/monastery/EdgevilleMonastery.kt | 42 +++++++ .../edgeville/monastery/MonasteryMonk.kt | 35 ++++++ .../lumbridge/church/FatherAereck.kt | 63 ++++------- .../area/misthalin/varrock/FatherLawrence.kt | 61 ++++++++++ .../area/misthalin/varrock/palace/Reldo.kt | 38 +++++++ .../area/wilderness/WildernessLevers.kt | 3 +- .../entity/player/dialogue/type/ItemBox.kt | 9 ++ .../entity/player/inv/item/ItemExtensions.kt | 4 +- 19 files changed, 676 insertions(+), 53 deletions(-) create mode 100644 data/area/misthalin/edgeville/monastery/monastery.gfx.toml create mode 100644 data/area/misthalin/edgeville/monastery/monastery.objs.toml create mode 100644 data/area/misthalin/edgeville/monastery/monastery.sounds.toml create mode 100644 game/src/main/kotlin/content/area/asgarnia/entrana/HighPriest.kt create mode 100644 game/src/main/kotlin/content/area/misthalin/edgeville/Oracle.kt create mode 100644 game/src/main/kotlin/content/area/misthalin/edgeville/monastery/AbbotLangley.kt create mode 100644 game/src/main/kotlin/content/area/misthalin/edgeville/monastery/BrotherJared.kt create mode 100644 game/src/main/kotlin/content/area/misthalin/edgeville/monastery/EdgevilleMonastery.kt create mode 100644 game/src/main/kotlin/content/area/misthalin/edgeville/monastery/MonasteryMonk.kt create mode 100644 game/src/main/kotlin/content/area/misthalin/varrock/FatherLawrence.kt diff --git a/data/area/misthalin/edgeville/edgeville.teles.toml b/data/area/misthalin/edgeville/edgeville.teles.toml index 74256b1b70..0bb286a41b 100644 --- a/data/area/misthalin/edgeville/edgeville.teles.toml +++ b/data/area/misthalin/edgeville/edgeville.teles.toml @@ -114,7 +114,7 @@ option = "Climb-down" tile = { x = 3069, y = 3517, level = 1 } delta = { level = -1 } -[2641] +[monastery_ladder_up] option = "Climb-up" tile = { x = 3057, y = 3483 } delta = { level = 1 } @@ -124,7 +124,7 @@ option = "Climb-down" tile = { x = 3057, y = 3483, level = 1 } delta = { level = -1 } -[2641] +[monastery_ladder_up] option = "Climb-up" tile = { x = 3046, y = 3483 } delta = { level = 1 } diff --git a/data/area/misthalin/edgeville/monastery/monastery.gfx.toml b/data/area/misthalin/edgeville/monastery/monastery.gfx.toml new file mode 100644 index 0000000000..a75c750230 --- /dev/null +++ b/data/area/misthalin/edgeville/monastery/monastery.gfx.toml @@ -0,0 +1,3 @@ +[heal] +id = 84 +height = 120 diff --git a/data/area/misthalin/edgeville/monastery/monastery.objs.toml b/data/area/misthalin/edgeville/monastery/monastery.objs.toml new file mode 100644 index 0000000000..f8a0a4040c --- /dev/null +++ b/data/area/misthalin/edgeville/monastery/monastery.objs.toml @@ -0,0 +1,3 @@ +[monastery_ladder_up] +id = 2641 +examine = "I can climb this." \ No newline at end of file diff --git a/data/area/misthalin/edgeville/monastery/monastery.sounds.toml b/data/area/misthalin/edgeville/monastery/monastery.sounds.toml new file mode 100644 index 0000000000..76161d9aa7 --- /dev/null +++ b/data/area/misthalin/edgeville/monastery/monastery.sounds.toml @@ -0,0 +1,2 @@ +[heal] +id = 166 diff --git a/data/entity/player/dialogue/dialogue.ifaces.toml b/data/entity/player/dialogue/dialogue.ifaces.toml index 47708b4c43..87d21a2c32 100644 --- a/data/entity/player/dialogue/dialogue.ifaces.toml +++ b/data/entity/player/dialogue/dialogue.ifaces.toml @@ -961,6 +961,9 @@ id = 0 [.line2] id = 1 +[.line3] +id = 2 + [.continue] id = 3 diff --git a/game/src/main/kotlin/content/area/asgarnia/entrana/HighPriest.kt b/game/src/main/kotlin/content/area/asgarnia/entrana/HighPriest.kt new file mode 100644 index 0000000000..f836a694d1 --- /dev/null +++ b/game/src/main/kotlin/content/area/asgarnia/entrana/HighPriest.kt @@ -0,0 +1,54 @@ +package content.area.asgarnia.entrana + +import content.area.misthalin.draynor_village.wise_old_man.OldMansMessage +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.type.item +import content.entity.player.dialogue.type.items +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player +import net.pearx.kasechange.toLowerSpaceCase +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.inv.carriesItem + +class HighPriest : Script { + init { + npcOperate("Talk-to", "high_priest_entrana") { + npc("Many greetings. Welcome to our fair island.") + wiseOldManLetter() + npc("Enjoy your stay here. May it be spiritually uplifting!") + } + + // TODO message on npc? + itemOnNPCOperate(npc = "high_priest_entrana") { + npc("No thank you, I am not accepting donations for the church at this time.") + } + } + + private suspend fun Player.wiseOldManLetter() { + if (get("wise_old_man_npc", "") != "high_priest_entrana" || !carriesItem("old_mans_message")) { + return + } + player("I've got a message for you from the Wise Old Man in Draynor Village.") + npc("How kind of you to bring me a message to this remote island!") + val reward = OldMansMessage.reward(this) ?: return + when (reward) { + "runes" -> items("nature_rune", "water_rune", "The High Priest gives you some runes.") // TODO proper message + "herbs" -> npc("Here, let me give you some herbs.") + "seeds" -> item("potato_seed", 400, "The High Priest gives you some seeds.") // TODO proper message + "prayer" -> { + item(167, "The High Priest blesses you.
You gain some Prayer xp.") + npc("In the name of Saradomin I shall bless you...") + } + "coins" -> { + item("coins_8", 400, "The High Priest gives you some coins.") + npc("I don't have much in the way of wealth, but I can spare you a few coins for your trouble.") + } + else -> { + item(reward, 400, "The High Priest gives you an ${reward.toLowerSpaceCase()}!") + npc("The beasts which dwell under this island occasionally drop gems when they die - please take this one as a sign of my gratitude.") + } + } + } +} diff --git a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Thurgo.kt b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Thurgo.kt index d930c2fa08..6d3509faaf 100644 --- a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Thurgo.kt +++ b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Thurgo.kt @@ -1,8 +1,10 @@ package content.area.asgarnia.port_sarim +import content.area.misthalin.draynor_village.wise_old_man.OldMansMessage import content.entity.player.dialogue.* import content.entity.player.dialogue.type.* import content.quest.quest +import net.pearx.kasechange.toSentenceCase import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.item.Item @@ -22,6 +24,7 @@ class Thurgo : Script { init { npcOperate("Talk-to", "thurgo") { + wiseOldManLetter() when (quest("the_knights_sword")) { "started", "find_thurgo" -> menu() "happy_thurgo" -> menuSword() @@ -40,6 +43,40 @@ class Thurgo : Script { } } + private suspend fun Player.wiseOldManLetter() { + if (get("wise_old_man_npc", "") != "thurgo" || !carriesItem("old_mans_message")) { + return + } + player("The Wise Old Man says he's got the information you wanted. Here's his message.") + npc("Ooh, thanks. I've been hoping he'd send me that information soon, although I wouldn't mind if he'd send me a pie instead!") + val reward = OldMansMessage.reward(this) ?: return + when (reward) { + "runes" -> { + items("nature_rune", "water_rune", "Thurgo gives you some runes.") // TODO check always same runes or not? + npc("Magic isn't really my thing. Here, take these.") + } + "herbs" -> { + item("grimy_tarromin", 400, "Thurgo gives you some banknotes that can be exchanged for herbs.") + npc("Some guy died down in the hole just north of my house, and he dropped these herbs. Here, take them.") + } + "seeds" -> { + item("potato_seed", 400, "Thurgo gives you some seeds.") + npc("I'm not the gardening type. Here, take these.") + } + "prayer" -> { + item(167, "Thurgo blesses you.
You gain some Prayer xp.") // TODO proper message + } + "coins" -> { + item("coins_8", 400, "Thurgo gives you some coins.") + npc("Here's some cash for your time.") + } + else -> { + item(reward, 400, "Thurgo gives you an ${reward.toSentenceCase()}!") + npc("I found this while I was mining. Hope you like it.") + } + } + } + suspend fun Player.menuReplacementSword() { choice { if (carriesItem("blurite_sword")) { diff --git a/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/OldMansMessage.kt b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/OldMansMessage.kt index aaeef23083..7a1d12d790 100644 --- a/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/OldMansMessage.kt +++ b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/OldMansMessage.kt @@ -1,7 +1,22 @@ package content.area.misthalin.draynor_village.wise_old_man +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.statement +import content.entity.player.inv.item.addOrDrop import content.quest.wiseOldManScroll import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.exp.exp +import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has +import world.gregs.voidps.engine.entity.item.drop.DropTables +import world.gregs.voidps.engine.entity.item.drop.ItemDrop +import world.gregs.voidps.engine.get +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.inv.remove +import world.gregs.voidps.type.random class OldMansMessage : Script { init { @@ -25,7 +40,7 @@ class OldMansMessage : Script { "*D", "", "", - ) + ), ) "abbot_langley" -> wiseOldManScroll( listOf( @@ -45,7 +60,7 @@ class OldMansMessage : Script { "Until our next meeting, then,", "*D", "", - ) + ), ) "high_priest_entrana" -> wiseOldManScroll( listOf( @@ -65,7 +80,7 @@ class OldMansMessage : Script { "", "Fare thee well, my young friend, - *D", "", - ) + ), ) "father_lawrence" -> wiseOldManScroll( listOf( @@ -85,7 +100,7 @@ class OldMansMessage : Script { "I trust you will heed this message.", "*D", "", - ) + ), ) "thurgo" -> wiseOldManScroll( listOf( @@ -105,9 +120,72 @@ class OldMansMessage : Script { "Regards,", "*D", "", - ) + ), ) } } } -} \ No newline at end of file + + companion object { + suspend fun reward(player: Player): String? { + if (player.inventory.isFull()) { + player.npc("I'd give you a reward, but you don't seem to have any space for it. Come back when you do.") + return null + } + player.inventory.remove("old_mans_message") + player.clear("wise_old_man_npc") + val drops: DropTables = get() + val easy = false + val chance = random.nextInt(16) + if (chance < 1) { // 6.25% + val drops = drops.getValue("wise_old_man_gems").roll() + give(player, drops) + return drops.first().id + } else if (chance < 3) { // 12.5% + if (!repeat(player, "wise_old_man_runes", if (easy) 10 else 25)) { + player.statement("Unfortunately you don't have space for all the runes.") + } + return "runes" + } else if (chance < 5) { // 12.5% + if (!repeat(player, "wise_old_man_herbs", if (easy) 3 else 10)) { + player.statement("Unfortunately you don't have space for all the herbs.") + } + return "herbs" + } else if (chance < 7) { // 12.5% + if (!repeat(player, "wise_old_man_herbs", if (easy) 3 else 10)) { + player.statement("Unfortunately you don't have space for all the seeds.") + } + return "seeds" + } else if (player.has(Skill.Prayer, 3) && chance < 14) { // 43.75% + player.exp(Skill.Prayer, 10.0) + return "prayer" + } else { // 12.5% or 56.25% depending on prayer level + val range = if (easy) 185..215 else 990..1020 + player.inventory.add("coins", range.random(random)) + return "coins" + } + } + + private fun repeat(player: Player, table: String, max: Int): Boolean { + val tables: DropTables = get() + val table = tables.getValue(table) + val drops = mutableListOf() + val count = random.nextInt(1, max + 1) + for (i in 0 until count) { + table.roll(list = drops) + } + return give(player, drops) + } + + private fun give(player: Player, drops: List): Boolean { + var dropped = false + for (drop in drops) { + val item = drop.toItem() + if (!player.addOrDrop(item.id, item.amount)) { + dropped = true + } + } + return dropped + } + } +} diff --git a/game/src/main/kotlin/content/area/misthalin/edgeville/Oracle.kt b/game/src/main/kotlin/content/area/misthalin/edgeville/Oracle.kt new file mode 100644 index 0000000000..abe3c92882 --- /dev/null +++ b/game/src/main/kotlin/content/area/misthalin/edgeville/Oracle.kt @@ -0,0 +1,89 @@ +package content.area.misthalin.edgeville + +import content.area.misthalin.draynor_village.wise_old_man.OldMansMessage +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Quiz +import content.entity.player.dialogue.Sad +import content.entity.player.dialogue.Shifty +import content.entity.player.dialogue.type.item +import content.entity.player.dialogue.type.items +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player +import net.pearx.kasechange.toSentenceCase +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.inv.carriesItem +import world.gregs.voidps.type.random + +class Oracle : Script { + init { + npcOperate("Talk-to", "oracle") { + wiseOldManLetter() + player("Can you impart your wise knowledge to me, O Oracle?") + when (random.nextInt(0, 30)) { + 0 -> npc("They say that ham does not mix well with other kinds of meat.") + 1 -> npc("Capes are always in fashion!") + 2 -> npc("The goblins will never make up their minds on their own.") + 3 -> npc("No. I'm not in the mood.") + 4 -> npc("An answer is unimportant; it is the question that matters.") + 5 -> npc("Nothing like a tasty fish.") + 6 -> npc("Is it time to wake up? I am not sure...") + 7 -> npc("There are no crisps at the party.") + 8 -> npc("Don't judge a book by its cover - judge it on its' grammar and, punctuation.") + 9 -> npc("Pies...they're great, aren't they?") + 10 -> npc("It's not you; it's me.") + 11 -> npc("Help wanted? Enquire within.") + 12 -> npc("Jas left a stone behind.") + 13 -> npc("Too many cooks spoil the anchovy pizza.") + 14 -> npc("Do not fear the dragons...fear their kin.") + 15 -> npc("A bird in the hand can make a tasty snack.") + 16 -> npc("Sometimes you get lucky, sometimes you don't.") + 17 -> npc("The God Wars are over...as long as the thing they were fighting over remains hidden.") + 18 -> npc("Everyone you know will one day be dead.") + 19 -> npc("If a tree falls in the forest and no one is around, then nobody gets Woodcutting xp.") + 20 -> npc("The chicken came before the egg.") + 21 -> npc("A woodchuck does not chuck wood.") + 22 -> npc("When in Asgarnia, do as the Asgarnians do.") + 23 -> npc("Many secrets are buried under this land.") + 24 -> npc("The light at the end of the tunnel is the demon-infested lava pit.") + 25 -> npc("Yes, I can. But I'm not going to.") + 26 -> npc("The great snake of Guthix guards more than she knows.") + 27 -> npc("Beware the cabbage: it is both green AND leafy.") + 28 -> npc("He who uses the power of custard mixes it with his tears.") + 29 -> npc("Who guards the guardsmen?") + } + } + } + + private suspend fun Player.wiseOldManLetter() { + if (get("wise_old_man_npc", "") != "oracle" || !carriesItem("old_mans_message")) { + return + } + player("I've got a message for you from the Wise Old Man who lives in Draynor Village.") + npc("Many do my wisdom seek; few do their own wisdom to me send!") + val reward = OldMansMessage.reward(this) ?: return + when (reward) { + "runes" -> { + items("nature_rune", "water_rune", "The Oracle gives you some runes.") // TODO proper message + } + "herbs" -> { + item("grimy_tarromin", 400, "The Oracle gives you some herbs.") // TODO proper message + } + "seeds" -> { + item("potato_seed", 400, "The Oracle gives you some seeds.") + npc("New life from these shall perchance spring!") + } + "prayer" -> { + item(167, "The Oracle blesses you.
You gain some Prayer xp.") // TODO proper message + } + "coins" -> { + item("coins_8", 400, "The Oracle gives you some coins.") // TODO proper message + } + else -> { + item(reward, 400, "The Oracle gives you an ${reward.toSentenceCase()}!") // TODO proper message + npc("I found this while I was mining. Hope you like it.") + } + } + } +} diff --git a/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/AbbotLangley.kt b/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/AbbotLangley.kt new file mode 100644 index 0000000000..a6561399fd --- /dev/null +++ b/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/AbbotLangley.kt @@ -0,0 +1,83 @@ +package content.area.misthalin.edgeville.monastery + +import content.area.misthalin.draynor_village.wise_old_man.OldMansMessage +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Quiz +import content.entity.player.dialogue.Sad +import content.entity.player.dialogue.type.* +import net.pearx.kasechange.toSentenceCase +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.entity.character.areaSound +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.equip.has +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has +import world.gregs.voidps.engine.inv.carriesItem + +class AbbotLangley : Script { + init { + npcOperate("Talk-to", "abbot_langley") { + npc("Greetings traveller.") + wiseOldManLetter() + choice { + option("Can you heal me? I'm injured.") { + npc("Ok.") + message("You feel a little better.") + gfx("heal") + areaSound("heal", tile, radius = 10) + levels.restore(Skill.Constitution, -levels.getOffset(Skill.Constitution)) + statement("Abbot Langley places his hands on your head. You feel a little better.") + } + option("Isn't this place built a bit out of the way?") { + npc("We like it that way actually! We get disturbed less. We still get rather a large amount of travellers looking for sanctuary and healing here as it is!") + } + if (!get("edgeville_monastery_order_member", false)) { + option("How do I get further into the monastery?") { + npc("I'm sorry but only members of our order are allowed in the second level of the monastery.") + choice { + option("Well can I join your order?") { + canIJoin() + } + option("Oh, sorry.") + } + } + } + } + } + } + + private suspend fun Player.canIJoin() { + if (!has(Skill.Prayer, 31)) { + npc("No. I am sorry, but I feel you are not devout enough.") + message("You need a prayer level of 31 to join the order.") + return + } + npc("Ok, I see you are someone suitable for our order. You may join.") + set("edgeville_monastery_order_member", true) + } + + private suspend fun Player.wiseOldManLetter() { + if (get("wise_old_man_npc", "") != "abbot_langley" || !carriesItem("old_mans_message")) { + return + } + player("I've got a message for you from your friend in Draynor Village.") + npc("Gosh, you are very kind to bring a message to my remote monastery!") + val reward = OldMansMessage.reward(this) ?: return + when (reward) { + "runes" -> items("nature_rune", "water_rune", "Abbot Langley gives you some runes.") // TODO proper message + "herbs" -> { + item("grimy_tarromin", 400, "Abbot Langley gives you some banknotes that can be exchanged for herbs.") + npc("I grow a few herbs in my little cabbage patch; please take some as a sign of my gratitude.") + } + "seeds" -> item("potato_seed", 400, "Abbot Langley gives you some seeds.") // TODO proper message + "prayer" -> { + item(167, "Abbot Langley blesses you.
You gain some Prayer xp.") + npc("Allow me to bestow on you Saradomin's blessings...") + } + "coins" -> item("coins_8", 400, "Abbot Langley gives you some coins.") + else -> item(reward, 400, "Abbot Langley gives you an ${reward.toSentenceCase()}!") // TODO proper message + } + } +} diff --git a/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/BrotherJared.kt b/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/BrotherJared.kt new file mode 100644 index 0000000000..feba61565b --- /dev/null +++ b/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/BrotherJared.kt @@ -0,0 +1,106 @@ +package content.area.misthalin.edgeville.monastery + +import com.github.michaelbull.logging.InlineLogger +import content.entity.player.dialogue.* +import content.entity.player.dialogue.type.* +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.level.Level +import world.gregs.voidps.engine.entity.character.player.skill.level.Level.hasMax +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.inv.replace +import world.gregs.voidps.engine.inv.transact.TransactionError +import world.gregs.voidps.engine.inv.transact.operation.AddItem.add +import world.gregs.voidps.engine.inv.transact.operation.RemoveItem.remove + +class BrotherJared : Script { + init { + npcOperate("Talk-to", "brother_jared") { (target) -> + choice { + bold() + option("Praise be to Saradomin!") { + npc("Yes! Praise he who brings life to this world.") + } + } + } + } + + private val logger = InlineLogger() + + private fun ChoiceOption.bold() { + option("What can you do to help a bold adventurer like myself?") { + val hasStar = inventory.contains("unblessed_symbol") + if (hasStar) { + npc("Well I can bless that star of Saradomin you have, or I could tell you about the Skillcape of Prayer!") + } else if (hasMax(Skill.Prayer, 99)) { + skillcape() + return@option + } else { + npc("I can tell you about holy symbols or the Skillcape of Prayer.") + } + choice { + if (hasStar) { + option("Bless star, please.") { + player("Yes please.") + inventory.replace("unblessed_symbol", "holy_symbol") + item("holy_symbol", 400, "You give Jered the symbol. Jered closes his eyes and places his hand on the symbol. He softly chants. Jered passes you the holy symbol.") + } + } else { + option("Tell me about holy symbols.") { + npc("If you have a silver star, which is the holy symbol of Saradomin, then I can bless it. Then if you are wearing it, it will help you when you are praying.") + } + } + option("Tell me about the Skillcape of Prayer.") { + npc("The Skillcape of Prayer is the hardest of all the skillcapes to get; it requires much devotion to acquire but also imbues the wearer with the ability to briefly fly!") + npc("The Cape of Prayer also increases the amount of Prayer points restored from drinking potions when it is equipped. Is there something else I can do for you?") + choice { + bold() + option("No, thank you.") { + player("No thank you.") + } + } + } + } + } + } + + private suspend fun Player.skillcape() { + npc("Well, seeing as you are so devout in praising the gods, I could sell you a Skillcape of Prayer, which increases the amount of Prayer points restored when drinking potions.") + choice { + option("Yes, please. So few people have Skillcapes of Prayer!") { + npc("One as pious as you has certainly earned the right to wear one, but the monastery requires a donation of 99000 coins for the privilege.") + choice { + option("I'm afraid I can't afford that.") { + noThanks() + } + option("I am always happy to contribute towards the monastery's upkeep.") { + inventory.transaction { + val trimmed = Skill.entries.any { it != Skill.Prayer && levels.getMax(it) >= Level.MAX_LEVEL } + remove("coins", 99_000) + add("prayer_cape${if (trimmed) "_t" else ""}") + add("prayer_hood") + } + when (inventory.transaction.error) { + is TransactionError.Deficient -> { + player("But, unfortunately, I don't have enough money with me.") + npc("Well, come back and see me when you do.") + } + is TransactionError.Full -> npc("Unfortunately all Skillcapes are only available with a free hood, it's part of a skill promotion deal; buy one get one free, you know. So you'll need to free up some inventory space before I can sell you one.") + TransactionError.None -> npc("Excellent! Wear that cape with pride my friend.") + else -> logger.debug { "Error buying prayer skillcape: ${inventory.transaction.error}." } + } + } + } + } + option("No thanks, I can't afford one of those.") { + noThanks() + } + } + } + + private suspend fun Player.noThanks() { + npc("No thanks, I can't afford one of those.") + npc("I am sorry to hear that. If you should find yourself in wealthier times come back and see me.") + } +} diff --git a/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/EdgevilleMonastery.kt b/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/EdgevilleMonastery.kt new file mode 100644 index 0000000000..1dbc210b1d --- /dev/null +++ b/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/EdgevilleMonastery.kt @@ -0,0 +1,42 @@ +package content.area.misthalin.edgeville.monastery + +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Sad +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.client.variable.start +import world.gregs.voidps.engine.entity.character.player.Teleport +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has +import world.gregs.voidps.engine.queue.queue + +class EdgevilleMonastery : Script { + init { + objTeleportTakeOff("Climb-up", "monastery_ladder_up") { _, _ -> + if (!get("edgeville_monastery_order_member", false)) { + queue("edgeville_monastery_member_dialogue") { + npc("abbot_langley", "I'm sorry but only members of our order are allowed in the second level of the monastery.") + choice { + option("Well can I join your order?") { + if (!has(Skill.Prayer, 31)) { + npc("abbot_langley", "No. I am sorry, but I feel you are not devout enough.") + message("You need a prayer level of 31 to join the order.") + return@option + } + npc("abbot_langley", "Ok, I see you are someone suitable for our order. You may join.") + set("edgeville_monastery_order_member", true) + } + option("Oh, sorry.") + } + } + return@objTeleportTakeOff Teleport.CANCEL + } + anim("climb_up") + start("teleport_delay", 2) + return@objTeleportTakeOff Teleport.CONTINUE + } + } +} diff --git a/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/MonasteryMonk.kt b/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/MonasteryMonk.kt new file mode 100644 index 0000000000..de47f23a9d --- /dev/null +++ b/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/MonasteryMonk.kt @@ -0,0 +1,35 @@ +package content.area.misthalin.edgeville.monastery + +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Quiz +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.entity.character.areaSound +import world.gregs.voidps.engine.entity.character.player.skill.Skill + +class MonasteryMonk : Script { + init { + npcOperate("Talk-to", "monk_edgeville") { (target) -> + npc("Greetings traveller.") + choice { + option("Can you heal me? I'm injured.") { + npc("Ok.") + message("You feel a little better.") + gfx("heal") + areaSound("heal", tile, radius = 10) + levels.restore(Skill.Constitution, levels.getOffset(Skill.Constitution)) + } + option("Isn't this place built a bit out of the way?") { + npc("We like it that way actually! We get disturbed less. We still get rather a large amount of travellers looking for sanctuary and healing here as it is!") + } + if (!get("edgeville_monastery_order_member", false)) { + option("How do I get further into the monastery?") { + npc("You'll need to talk to Abbot Langley about that. He's usually to be found walking the halls of the monastery.") + } + } + } + } + } +} diff --git a/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt b/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt index 67a7cd9911..8ac9b8f78e 100644 --- a/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt +++ b/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt @@ -1,24 +1,19 @@ package content.area.misthalin.lumbridge.church +import content.area.misthalin.draynor_village.wise_old_man.OldMansMessage import content.entity.player.dialogue.* import content.entity.player.dialogue.type.* -import content.entity.player.inv.item.addOrDrop import content.quest.quest import content.quest.refreshQuestJournal +import net.pearx.kasechange.toSentenceCase import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.ui.open import world.gregs.voidps.engine.data.Settings import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.entity.character.player.skill.Skill -import world.gregs.voidps.engine.entity.character.player.skill.exp.exp -import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has import world.gregs.voidps.engine.entity.item.drop.DropTables -import world.gregs.voidps.engine.entity.item.drop.ItemDrop -import world.gregs.voidps.engine.inv.add import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.replace -import world.gregs.voidps.type.random class FatherAereck(val drops: DropTables) : Script { @@ -62,47 +57,31 @@ class FatherAereck(val drops: DropTables) : Script { } private suspend fun Player.wiseOldManLetter() { - if (get("wise_old_man_npc", "") != "father_aereck") { + if (get("wise_old_man_npc", "") != "father_aereck" || !carriesItem("old_mans_message")) { return } player("The Wise Old Man of Draynor Village said you might reward me if I brought you this.") npc("Oh, did he?") - if (inventory.isFull()) { - npc("I'd give you a reward, but you don't seem to have any space for it. Come back when you do.") - return - } - val easy = false - val chance = random.nextInt(16) - if (chance < 1) { // 6.25% - npc("I suppose gems are always acceptable rewards!") - - } else if (chance < 3) { // 12.5% - npc("Well, maybe you'll have a use for these?") - val range = if (easy) 1..10 else 1..25 - val count = range.random(random) - val table = drops.getValue("wise_old_man_runes") - val drops = mutableListOf() - for (i in 0 until count) { - table.roll(list = drops) + val reward = OldMansMessage.reward(this) ?: return + when (reward) { + "runes" -> { + items("nature_rune", "water_rune", "Faether Aereck gives you some runes.") + npc("Well, maybe you'll have a use for these?") + } + "herbs" -> item("grimy_tarromin", 400, "Faether Aereck gives you some herbs.") // TODO proper message + "seeds" -> { + item("potato_seed", 400, "Faether Aereck gives you some seeds.") + npc("Well, maybe you'll find a use for these seeds?") + } + "prayer" -> { + item(167, "Father Aereck blesses you.
You gain some Prayer xp.") + npc("Well, it's still nice of you to bring the message here. Here, I shall bless you...") } - for (drop in drops) { - val item = drop.toItem() - addOrDrop(item.id, item.amount) + "coins" -> item("coins_8", 400, "Faether Aereck gives you some coins.") + else -> { + item(reward, 400, "Father Aereck gives you an ${reward.toSentenceCase()}.") + npc("I suppose gems are always acceptable rewards!") } - } else if (chance < 5) { // 12.5% - npc("") // TODO herbs - val table = drops.getValue("wise_old_man_gems") - table.roll() - } else if (chance < 7) { // 12.5% - npc("Well, maybe you'll find a use for these seeds?") - } else if (has(Skill.Prayer, 3) && chance < 14) { // 43.75% - exp(Skill.Prayer, 10.0) - clear("wise_old_man_npc") - levelUp(Skill.Prayer, "Faether Aereck blesses you.
You gain some Prayer xp.") - npc("Well, it's still nice of you to bring the message here.
Here, I shall bless you...") - } else { // 12.5% or 56.25% depending on prayer level - val range = if (easy) 185..215 else 990..1020 - inventory.add("coins", range.random(random)) } } diff --git a/game/src/main/kotlin/content/area/misthalin/varrock/FatherLawrence.kt b/game/src/main/kotlin/content/area/misthalin/varrock/FatherLawrence.kt new file mode 100644 index 0000000000..8dcc6e1a33 --- /dev/null +++ b/game/src/main/kotlin/content/area/misthalin/varrock/FatherLawrence.kt @@ -0,0 +1,61 @@ +package content.area.misthalin.varrock + +import content.area.misthalin.draynor_village.wise_old_man.OldMansMessage +import content.entity.player.dialogue.Drunk +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Shifty +import content.entity.player.dialogue.type.item +import content.entity.player.dialogue.type.items +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player +import net.pearx.kasechange.toSentenceCase +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.inv.carriesItem + +class FatherLawrence : Script { + init { + npcOperate("Talk-to", "father_lawrence") { + wiseOldManLetter() + npc("Oh, to be a father in the times of whiskey! I sing and I drink and I wake up in gutters.") + player("Good morning.") + npc("Top of the morning to you.") + } + } + + private suspend fun Player.wiseOldManLetter() { + if (get("wise_old_man_npc", "") != "father_lawrence" || !carriesItem("old_mans_message")) { + return + } + player("The Wise Old Man of Draynor Village sent you this message about your drinking habits!") + npc("mssge? wha messsge?") + npc("oh, msesesge for me.") + message("Oh dear, he doesn't look like he's going to be able to read the message!") + val reward = OldMansMessage.reward(this) ?: return + when (reward) { + "runes" -> { + items("nature_rune", "water_rune", "Father Lawrence gives you some runes.") + npc("Whee! Mmmgic power, kazamm...") + } + "herbs" -> { + item("grimy_tarromin", 400, "Father Lawrence gives you some herbs.") // TODO proper message + } + "seeds" -> { + item("potato_seed", 400, "Father Lawrence gives you some seeds.") // TODO proper message + } + "prayer" -> { + item(167, "Father lawrence blesses you.
You gain some Prayer xp.") + npc("in nomine saradomini, blah blah blah...") + } + "coins" -> { + item("coins_8", 400, "Father Lawrence gives you some coins.") + npc("here, hve som munny.") + } + else -> { + item(reward, 400, "Father Lawrence gives you an ${reward.toSentenceCase()}!") // TODO proper message + npc("I found this while I was mining. Hope you like it.") + } + } + } +} diff --git a/game/src/main/kotlin/content/area/misthalin/varrock/palace/Reldo.kt b/game/src/main/kotlin/content/area/misthalin/varrock/palace/Reldo.kt index 864e6b76a6..5014a2cb39 100644 --- a/game/src/main/kotlin/content/area/misthalin/varrock/palace/Reldo.kt +++ b/game/src/main/kotlin/content/area/misthalin/varrock/palace/Reldo.kt @@ -1,12 +1,17 @@ package content.area.misthalin.varrock.palace +import content.area.misthalin.draynor_village.wise_old_man.OldMansMessage +import content.entity.player.dialogue.Happy import content.entity.player.dialogue.Idle import content.entity.player.dialogue.Laugh import content.entity.player.dialogue.Quiz import content.entity.player.dialogue.Shifty import content.entity.player.dialogue.type.* import content.quest.quest +import net.pearx.kasechange.toSentenceCase import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.inv.carriesItem class Reldo : Script { @@ -14,6 +19,11 @@ class Reldo : Script { npcOperate("Talk-to", "reldo*") { npc("Hello stranger.") choice { + if (get("wise_old_man_npc", "") == "reldo" && carriesItem("old_mans_message")) { + option("The Wise Old Man of Draynor Village sent you this message.") { + wiseOldManLetter() + } + } anythingToTrade() whatDoYouDo() val stage = quest("the_knights_sword") @@ -24,6 +34,34 @@ class Reldo : Script { } } + private suspend fun Player.wiseOldManLetter() { + npc("Ah, I am always delighted to hear from him. You would not imagine the depths of his wisdom!") + val reward = OldMansMessage.reward(this) ?: return + when (reward) { + "runes" -> { + items("nature_rune", "water_rune", "Reldo gives you some runes.") + npc("My old friend Aubury sent me some runes in return for some books he wanted. Perhaps you'd like them?") + } + "herbs" -> { + item("grimy_tarromin", 400, "Reldo gives you some herbs.") // TODO proper message + } + "seeds" -> { + item("potato_seed", 400, "Reldo gives you some seeds.") + npc("These little things seem to be everywhere these days! Perhaps you'd like them?") + } + "prayer" -> { + item(167, "Reldo blesses you.
You gain some Prayer xp.") // TODO proper message + } + "coins" -> { + item("coins_8", 400, "Reldo gives you some coins.") + npc("King Roald has been very generous with my salary, so I can spare you some coins for your trouble.") + } + else -> { + item(reward, 400, "Reldo gives you an ${reward.toSentenceCase()}!") // TODO proper message + } + } + } + fun ChoiceOption.anythingToTrade() = option("Do you have anything to trade?") { npc("Only knowledge.") player("How much do you want for that then?") diff --git a/game/src/main/kotlin/content/area/wilderness/WildernessLevers.kt b/game/src/main/kotlin/content/area/wilderness/WildernessLevers.kt index 7f50ccde62..a13b5bbbfb 100644 --- a/game/src/main/kotlin/content/area/wilderness/WildernessLevers.kt +++ b/game/src/main/kotlin/content/area/wilderness/WildernessLevers.kt @@ -13,14 +13,13 @@ import world.gregs.voidps.engine.entity.character.player.chat.ChatType import world.gregs.voidps.engine.entity.character.sound import world.gregs.voidps.engine.entity.obj.GameObject import world.gregs.voidps.engine.queue.queue -import world.gregs.voidps.engine.queue.strongQueue class WildernessLevers(val teleports: ObjectTeleports) : Script { init { objTeleportTakeOff("Pull", "lever_*") { target, option -> if (target.def(this).stringId == "lever_ardougne_edgeville" && get("wilderness_lever_warning", true)) { - strongQueue("wilderness_lever_warning") { + queue("wilderness_lever_warning") { statement("Warning! Pulling the lever will teleport you deep into the Wilderness.") choice("Are you sure you wish to pull it?") { option("Yes I'm brave.") { diff --git a/game/src/main/kotlin/content/entity/player/dialogue/type/ItemBox.kt b/game/src/main/kotlin/content/entity/player/dialogue/type/ItemBox.kt index 7c159d07d1..a6fb43f2a8 100644 --- a/game/src/main/kotlin/content/entity/player/dialogue/type/ItemBox.kt +++ b/game/src/main/kotlin/content/entity/player/dialogue/type/ItemBox.kt @@ -24,6 +24,15 @@ suspend fun Player.item(item: String, zoom: Int, text: String, sprite: Int? = nu close(ITEM_INTERFACE_ID) } +suspend fun Player.item(sprite: Int, text: String) { + check(open(ITEM_INTERFACE_ID)) { "Unable to open item dialogue for $this" } + interfaces.sendSprite(ITEM_INTERFACE_ID, "sprite", sprite) + val lines = if (text.contains("\n")) text.trimIndent().replace("\n", "
") else get().get("q8_full").splitLines(text, 380).joinToString("
") + interfaces.sendText(ITEM_INTERFACE_ID, "line1", lines) + ContinueSuspension.get(this) + close(ITEM_INTERFACE_ID) +} + suspend fun Player.items(item1: String, item2: String, text: String) { check(open(DOUBLE_ITEM_INTERFACE_ID)) { "Unable to open item dialogue for $this" } interfaces.sendItem(DOUBLE_ITEM_INTERFACE_ID, "model1", ItemDefinitions.get(item1).id) diff --git a/game/src/main/kotlin/content/entity/player/inv/item/ItemExtensions.kt b/game/src/main/kotlin/content/entity/player/inv/item/ItemExtensions.kt index f66b499fb9..e6aa62a0b1 100644 --- a/game/src/main/kotlin/content/entity/player/inv/item/ItemExtensions.kt +++ b/game/src/main/kotlin/content/entity/player/inv/item/ItemExtensions.kt @@ -10,8 +10,10 @@ import world.gregs.voidps.engine.inv.inventory val Item.tradeable: Boolean get() = def["tradeable", true] -fun Player.addOrDrop(id: String, amount: Int = 1, inventory: Inventory = this.inventory, revealTicks: Int = 100, disappearTicks: Int = 200) { +fun Player.addOrDrop(id: String, amount: Int = 1, inventory: Inventory = this.inventory, revealTicks: Int = 100, disappearTicks: Int = 200): Boolean { if (!inventory.add(id, amount)) { FloorItems.add(tile, id, amount, revealTicks = revealTicks, disappearTicks = disappearTicks, owner = this) + return false } + return true } From 686bcc0b78dbda67bc14cdf69a8a3f524258f78d Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 27 Feb 2026 17:51:32 +0000 Subject: [PATCH 12/20] Formatting --- .../draynor_village/wise_old_man/WiseOldMan.kt | 15 +++++++++------ game/src/main/kotlin/content/quest/Quest.kt | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt index d8dff5ba5a..edd4c41875 100644 --- a/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt +++ b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt @@ -308,7 +308,6 @@ class WiseOldMan : Script { npc("I asked you to deliver that for me. But I may as well take it back now.") } } - } private suspend fun Player.anythingElse() { @@ -318,7 +317,6 @@ class WiseOldMan : Script { } } - suspend fun Player.checkTask() { val npc: String? = get("wise_old_man_npc") if (npc != null) { @@ -344,9 +342,14 @@ class WiseOldMan : Script { npc("I'm sure I can think of a few little jobs. This won't be a quest, mind you, just a little favour...") if (random.nextInt(100) < 16) { val npc = setOf( - "father_aereck", "high_priest_entrana", "reldo", - "thurgo", "father_lawrence", "abbot_langley", - "oracle", "thing_under_the_bed" + "father_aereck", + "high_priest_entrana", + "reldo", + "thurgo", + "father_lawrence", + "abbot_langley", + "oracle", + "thing_under_the_bed", ).random(random) val intro = EnumDefinitions.string("wise_old_man_npcs", npc) npc(intro) @@ -479,4 +482,4 @@ class WiseOldMan : Script { } return tasks } -} \ No newline at end of file +} diff --git a/game/src/main/kotlin/content/quest/Quest.kt b/game/src/main/kotlin/content/quest/Quest.kt index 5aae63dbc3..2b957e95e0 100644 --- a/game/src/main/kotlin/content/quest/Quest.kt +++ b/game/src/main/kotlin/content/quest/Quest.kt @@ -82,7 +82,7 @@ fun Player.wiseOldManScroll(lines: List) { return } for (i in 0..16) { - interfaces.sendText("wise_old_man_scroll", "line${i}", lines.getOrNull(i) ?: "") + interfaces.sendText("wise_old_man_scroll", "line$i", lines.getOrNull(i) ?: "") } } From 88039dc1794ca9fd98a6f612cce319ff210277a7 Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 27 Feb 2026 18:33:11 +0000 Subject: [PATCH 13/20] Add port sarim shop dialogues and bot navigation --- .../asgarnia/port_sarim/port_sarim.areas.toml | 16 ++- .../port_sarim/port_sarim.nav-edges.toml | 136 ++++++++++++++++++ .../content/area/asgarnia/port_sarim/Betty.kt | 26 ++++ .../content/area/asgarnia/port_sarim/Brian.kt | 23 +++ .../area/asgarnia/port_sarim/Gerrant.kt | 21 +++ .../content/area/asgarnia/port_sarim/Grum.kt | 25 ++++ .../content/area/asgarnia/port_sarim/Wydin.kt | 36 +++++ 7 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 data/area/asgarnia/port_sarim/port_sarim.nav-edges.toml create mode 100644 game/src/main/kotlin/content/area/asgarnia/port_sarim/Betty.kt create mode 100644 game/src/main/kotlin/content/area/asgarnia/port_sarim/Brian.kt create mode 100644 game/src/main/kotlin/content/area/asgarnia/port_sarim/Gerrant.kt create mode 100644 game/src/main/kotlin/content/area/asgarnia/port_sarim/Grum.kt create mode 100644 game/src/main/kotlin/content/area/asgarnia/port_sarim/Wydin.kt diff --git a/data/area/asgarnia/port_sarim/port_sarim.areas.toml b/data/area/asgarnia/port_sarim/port_sarim.areas.toml index adc54f2774..8f60c4e11d 100644 --- a/data/area/asgarnia/port_sarim/port_sarim.areas.toml +++ b/data/area/asgarnia/port_sarim/port_sarim.areas.toml @@ -3,7 +3,19 @@ x = [3008, 3064] y = [3171, 3263] [greater_port_sarim] -x = [2962,2962,2909,2909,3071,3071] -y = [3136,3187,3187,3263,3263,3136] +x = [2962, 2962, 2909, 2909, 3071, 3071] +y = [3136, 3187, 3187, 3263, 3263, 3136] tags = ["penguin_area"] hint = "near Port Sarim." + +[gerrants_fish_shop] +x = [3011, 3017] +y = [3223, 3229] + +[brians_battleaxe_shop] +x = [3023, 3030] +y = [3245, 3253] + +[betties_magic_shop] +x = [3011, 3016] +y = [3256, 3261] diff --git a/data/area/asgarnia/port_sarim/port_sarim.nav-edges.toml b/data/area/asgarnia/port_sarim/port_sarim.nav-edges.toml new file mode 100644 index 0000000000..7785d6bff7 --- /dev/null +++ b/data/area/asgarnia/port_sarim/port_sarim.nav-edges.toml @@ -0,0 +1,136 @@ +edges = [ + { from = { x = 3071, y = 3266 }, to = { x = 3071, y = 3276 } }, + { from = { x = 3071, y = 3276 }, to = { x = 3069, y = 3276 } }, + { from = { x = 3069, y = 3276 }, to = { x = 3066, y = 3269 } }, + { from = { x = 3066, y = 3269 }, to = { x = 3063, y = 3260 } }, + { from = { x = 3066, y = 3269 }, to = { x = 3057, y = 3265 } }, + { from = { x = 3057, y = 3265 }, to = { x = 3051, y = 3265 } }, + { from = { x = 3063, y = 3260 }, to = { x = 3060, y = 3254 } }, + { from = { x = 3063, y = 3260 }, to = { x = 3053, y = 3261 } }, + { from = { x = 3060, y = 3254 }, to = { x = 3053, y = 3247 } }, + { from = { x = 3053, y = 3247 }, to = { x = 3042, y = 3247 } }, + { from = { x = 3053, y = 3261 }, to = { x = 3051, y = 3265 } }, + { from = { x = 3051, y = 3265 }, to = { x = 3041, y = 3263 } }, + { from = { x = 3041, y = 3263 }, to = { x = 3036, y = 3255 } }, + { from = { x = 3041, y = 3263 }, to = { x = 3032, y = 3265 } }, + { from = { x = 3032, y = 3265 }, to = { x = 3027, y = 3268 } }, + { from = { x = 3027, y = 3268 }, to = { x = 3019, y = 3263 } }, + { from = { x = 3027, y = 3268 }, to = { x = 3038, y = 3276 } }, + { from = { x = 3038, y = 3276 }, to = { x = 3055, y = 3276 } }, + { from = { x = 3055, y = 3276 }, to = { x = 3069, y = 3276 } }, + { from = { x = 3055, y = 3276 }, to = { x = 3057, y = 3265 } }, + { from = { x = 3069, y = 3276 }, to = { x = 3071, y = 3276 } }, + { from = { x = 3019, y = 3263 }, to = { x = 3019, y = 3258 } }, + { from = { x = 3053, y = 3261 }, to = { x = 3057, y = 3265 } }, + { from = { x = 3042, y = 3247 }, to = { x = 3035, y = 3248 } }, + { from = { x = 3042, y = 3247 }, to = { x = 3036, y = 3255 } }, + { from = { x = 3036, y = 3255 }, to = { x = 3026, y = 3256 } }, + { from = { x = 3026, y = 3256 }, to = { x = 3019, y = 3258 } }, + { from = { x = 3019, y = 3258 }, to = { x = 3017, y = 3258 } }, + { from = { x = 3017, y = 3258 }, to = { x = 3016, y = 3258 }, + actions = [ + { object = { + option = "Open", + id = "door_668_closed", + x = 3017, + y = 3259, + success = { object = { id = "door_668_opened", x = 3016, y = 3259 } } + } }, + { tile = { x = 3016, y = 3258, radius = 1 } } + ] + }, + { from = { x = 3016, y = 3258 }, to = { x = 3017, y = 3258 }, + actions = [ + { object = { + option = "Open", + id = "door_668_closed", + x = 3017, + y = 3259, + success = { object = { id = "door_668_opened", x = 3016, y = 3259 } } + } }, + { tile = { x = 3017, y = 3258, radius = 1 } } + ] + }, + { from = { x = 3019, y = 3258 }, to = { x = 3019, y = 3247 } }, + { from = { x = 3035, y = 3248 }, to = { x = 3031, y = 3248 } }, + { from = { x = 3035, y = 3248 }, to = { x = 3026, y = 3242 } }, + { from = { x = 3031, y = 3248 }, to = { x = 3026, y = 3242 } }, + { from = { x = 3031, y = 3248 }, to = { x = 3030, y = 3248 }, + actions = [ + { object = { + option = "Open", + id = "door_668_closed", + x = 3031, + y = 3248, + success = { object = { id = "door_668_opened", x = 3030, y = 3248 } } + } }, + { tile = { x = 3030, y = 3248, radius = 1 } } + ] + }, + { from = { x = 3030, y = 3248 }, to = { x = 3031, y = 3248 }, + actions = [ + { object = { + option = "Open", + id = "door_668_closed", + x = 3031, + y = 3248, + success = { object = { id = "door_668_opened", x = 3030, y = 3248 } } + } }, + { tile = { x = 3031, y = 3248, radius = 1 } } + ] + }, + { from = { x = 3030, y = 3248 }, to = { x = 3026, y = 3245 } }, + { from = { x = 3026, y = 3242 }, to = { x = 3026, y = 3244 } }, + { from = { x = 3026, y = 3242 }, to = { x = 3019, y = 3247 } }, + { from = { x = 3026, y = 3242 }, to = { x = 3021, y = 3237 } }, + { from = { x = 3026, y = 3244 }, to = { x = 3026, y = 3245 }, + actions = [ + { object = { + option = "Open", + id = "door_668_closed", + x = 3026, + y = 3244, + success = { object = { id = "door_668_opened", x = 3026, y = 3245 } } + } }, + { tile = { x = 3026, y = 3245, radius = 1 } } + ] + }, + { from = { x = 3026, y = 3245 }, to = { x = 3026, y = 3244 }, + actions = [ + { object = { + option = "Open", + id = "door_668_closed", + x = 3026, + y = 3244, + success = { object = { id = "door_668_opened", x = 3026, y = 3245 } } + } }, + { tile = { x = 3026, y = 3244, radius = 1 } } + ] + }, + { from = { x = 3019, y = 3247 }, to = { x = 3021, y = 3237 } }, + { from = { x = 3021, y = 3237 }, to = { x = 3019, y = 3224 } }, + { from = { x = 3019, y = 3224 }, to = { x = 3016, y = 3217 } }, + { from = { x = 3016, y = 3217 }, to = { x = 3013, y = 3219 } }, + { from = { x = 3013, y = 3219 }, to = { x = 3013, y = 3220 }, + actions = [ + { object = { + option = "Open", + id = "door_668_closed", + x = 3013, + y = 3219, + success = { object = { id = "door_668_opened", x = 3013, y = 3220 } } + } }, + { tile = { x = 3013, y = 3220, radius = 1 } } + ] + }, + { from = { x = 3013, y = 3220 }, to = { x = 3013, y = 3219 }, actions = [ + { object = { + option = "Open", + id = "door_668_closed", + x = 3013, + y = 3219, + success = { object = { id = "door_668_opened", x = 3013, y = 3220 } } + } }, + { tile = { x = 3013, y = 3219, radius = 1 } } + ] }, +] \ No newline at end of file diff --git a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Betty.kt b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Betty.kt new file mode 100644 index 0000000000..ceee50bba6 --- /dev/null +++ b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Betty.kt @@ -0,0 +1,26 @@ +package content.area.asgarnia.port_sarim + +import content.entity.npc.shop.openShop +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Quiz +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import world.gregs.voidps.engine.Script + +class Betty : Script { + init { + npcOperate("Talk-to", "betty_port_sarim") { (target) -> + npc("Hello there. Welcome to my magic emporium.") + choice { + option("Can I see your wares?") { + npc("Of course.") + openShop(target.def["shop"]) + } + option("Sorry, I'm not into magic.") { + npc("Well, if you see anyone who is, please send them my way.") + } + } + } + } +} \ No newline at end of file diff --git a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Brian.kt b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Brian.kt new file mode 100644 index 0000000000..a8124a8725 --- /dev/null +++ b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Brian.kt @@ -0,0 +1,23 @@ +package content.area.asgarnia.port_sarim + +import content.entity.npc.shop.openShop +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import world.gregs.voidps.engine.Script + +class Brian : Script { + init { + npcOperate("Talk-to", "brian_port_sarim") { (target) -> + choice { + option("So, are you selling something?") { + npc("Yep, take a look at these great axes!") + openShop(target.def["shop"]) + } + option("'Ello.") { + npc("'Ello!") + } + } + } + } +} \ No newline at end of file diff --git a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Gerrant.kt b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Gerrant.kt new file mode 100644 index 0000000000..a688efc4e2 --- /dev/null +++ b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Gerrant.kt @@ -0,0 +1,21 @@ +package content.area.asgarnia.port_sarim + +import content.entity.npc.shop.openShop +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import world.gregs.voidps.engine.Script + +class Gerrant : Script { + init { + npcOperate("Talk-to", "gerrant*") { (target) -> + npc("Welcome! You can buy fishing equipment at my store. We'll also buy anything you catch off you.") + choice { + option("Let's see what you've got then.") { + openShop(target.def["shop"]) + } + option("Sorry, I'm not interested.") + } + } + } +} \ No newline at end of file diff --git a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Grum.kt b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Grum.kt new file mode 100644 index 0000000000..88aa605e67 --- /dev/null +++ b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Grum.kt @@ -0,0 +1,25 @@ +package content.area.asgarnia.port_sarim + +import content.entity.npc.shop.openShop +import content.entity.player.dialogue.Angry +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import world.gregs.voidps.engine.Script + +class Grum : Script { + init { + npcOperate("Talk-to", "grum") { (target) -> + npc("Would you like to buy or sell some gold jewellery?") + choice { + option("Yes please.") { + openShop(target.def["shop"]) + } + option("No, I'm not that rich.") { + npc("Get out then! We don't want any riff-raff in here.") + } + } + } + } +} \ No newline at end of file diff --git a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Wydin.kt b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Wydin.kt new file mode 100644 index 0000000000..29a68ed916 --- /dev/null +++ b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Wydin.kt @@ -0,0 +1,36 @@ +package content.area.asgarnia.port_sarim + +import content.entity.npc.shop.openShop +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Idle +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Quiz +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import world.gregs.voidps.engine.Script + +class Wydin : Script { + init { + npcOperate("Talk-to", "wydin") { (target) -> + npc("Welcome to my food store! Would you like to buy anything?") + choice { + option("Yes please.") { + openShop(target.def["shop"]) + } + option("No, thank you.") + option("What can you recommend?") { + npc("We have this really exotic fruit all the way from Karamja. It's called a banana.") + choice { + option("Hmm, I think I'll try one.") { + npc("Great. You might as well take a look at the rest of my wares as well.") + openShop(target.def["shop"]) + } + option("I don't like the sound of that.") { + npc("Well, it's your choice, but I do recommend them.") + } + } + } + } + } + } +} \ No newline at end of file From fe20f449134b0acb05fa027bc1363d22f6d66be6 Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 27 Feb 2026 18:33:48 +0000 Subject: [PATCH 14/20] Fix draynor bank bot spawns --- data/area/misthalin/draynor/draynor.areas.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/area/misthalin/draynor/draynor.areas.toml b/data/area/misthalin/draynor/draynor.areas.toml index c643e90bb3..94a960e087 100644 --- a/data/area/misthalin/draynor/draynor.areas.toml +++ b/data/area/misthalin/draynor/draynor.areas.toml @@ -25,8 +25,8 @@ x = [3075, 3085] y = [3265, 3274] [draynor_bank] -x = [3088, 3097] -y = [3240, 3246] +x = [3092, 3095] +y = [3241, 3245] tags = ["bank"] [draynor_fishing_area] From e73260b7559e0f923a190702c4702dd9d655ee8b Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 27 Feb 2026 18:35:11 +0000 Subject: [PATCH 15/20] Formatting --- game/src/main/kotlin/content/area/asgarnia/port_sarim/Betty.kt | 2 +- game/src/main/kotlin/content/area/asgarnia/port_sarim/Brian.kt | 2 +- .../main/kotlin/content/area/asgarnia/port_sarim/Gerrant.kt | 2 +- game/src/main/kotlin/content/area/asgarnia/port_sarim/Grum.kt | 3 +-- game/src/main/kotlin/content/area/asgarnia/port_sarim/Wydin.kt | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Betty.kt b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Betty.kt index ceee50bba6..ffe8c8da7a 100644 --- a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Betty.kt +++ b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Betty.kt @@ -23,4 +23,4 @@ class Betty : Script { } } } -} \ No newline at end of file +} diff --git a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Brian.kt b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Brian.kt index a8124a8725..141042f6d2 100644 --- a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Brian.kt +++ b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Brian.kt @@ -20,4 +20,4 @@ class Brian : Script { } } } -} \ No newline at end of file +} diff --git a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Gerrant.kt b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Gerrant.kt index a688efc4e2..888cc4c193 100644 --- a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Gerrant.kt +++ b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Gerrant.kt @@ -18,4 +18,4 @@ class Gerrant : Script { } } } -} \ No newline at end of file +} diff --git a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Grum.kt b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Grum.kt index 88aa605e67..2085df00ca 100644 --- a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Grum.kt +++ b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Grum.kt @@ -3,7 +3,6 @@ package content.area.asgarnia.port_sarim import content.entity.npc.shop.openShop import content.entity.player.dialogue.Angry import content.entity.player.dialogue.Happy -import content.entity.player.dialogue.Neutral import content.entity.player.dialogue.type.choice import content.entity.player.dialogue.type.npc import world.gregs.voidps.engine.Script @@ -22,4 +21,4 @@ class Grum : Script { } } } -} \ No newline at end of file +} diff --git a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Wydin.kt b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Wydin.kt index 29a68ed916..138d59ce5a 100644 --- a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Wydin.kt +++ b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Wydin.kt @@ -33,4 +33,4 @@ class Wydin : Script { } } } -} \ No newline at end of file +} From 905d330b33b5b928f47f53b1efb91c7a2b479cf8 Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 27 Feb 2026 18:38:49 +0000 Subject: [PATCH 16/20] Add missing vars --- data/area/misthalin/edgeville/monastery/monastery.vars.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 data/area/misthalin/edgeville/monastery/monastery.vars.toml diff --git a/data/area/misthalin/edgeville/monastery/monastery.vars.toml b/data/area/misthalin/edgeville/monastery/monastery.vars.toml new file mode 100644 index 0000000000..9e49151923 --- /dev/null +++ b/data/area/misthalin/edgeville/monastery/monastery.vars.toml @@ -0,0 +1,3 @@ +[edgeville_monastery_order_member] +format = "boolean" +persist = true From 0c6f0fbf0f13e55b43267b98a69354f880497e44 Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 27 Feb 2026 18:39:42 +0000 Subject: [PATCH 17/20] Remove resolved todo --- game/src/main/kotlin/content/area/asgarnia/entrana/HighPriest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/game/src/main/kotlin/content/area/asgarnia/entrana/HighPriest.kt b/game/src/main/kotlin/content/area/asgarnia/entrana/HighPriest.kt index f836a694d1..b650127db0 100644 --- a/game/src/main/kotlin/content/area/asgarnia/entrana/HighPriest.kt +++ b/game/src/main/kotlin/content/area/asgarnia/entrana/HighPriest.kt @@ -20,7 +20,6 @@ class HighPriest : Script { npc("Enjoy your stay here. May it be spiritually uplifting!") } - // TODO message on npc? itemOnNPCOperate(npc = "high_priest_entrana") { npc("No thank you, I am not accepting donations for the church at this time.") } From 4121e99998725ea8556830995759f153bd06b72c Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 27 Feb 2026 20:01:57 +0000 Subject: [PATCH 18/20] Add thing under the bed fight --- data/area/misthalin/draynor/draynor.npcs.toml | 19 --- .../wise_old_man/wise_old_man.anims.toml | 8 ++ .../wise_old_man/wise_old_man.combat.toml | 11 ++ .../wise_old_man/wise_old_man.jingles.toml | 2 + .../wise_old_man/wise_old_man.npcs.toml | 20 +++ .../wise_old_man/wise_old_man.sounds.toml | 8 ++ .../wise_old_man/wise_old_man.vars.toml | 12 ++ data/entity/player/player.sounds.toml | 3 + .../engine/data/definition/EnumDefinitions.kt | 45 ++++--- .../entity/character/mode/move/Movement.kt | 2 +- .../move/target/NPCCharacterTargetStrategy.kt | 28 +++-- .../area/asgarnia/entrana/HighPriest.kt | 2 +- .../area/asgarnia/port_sarim/Thurgo.kt | 2 +- .../wise_old_man/OldMansMessage.kt | 22 ++-- .../wise_old_man/ThingUnderTheBed.kt | 44 +++++++ .../wise_old_man/WiseOldMan.kt | 116 +++++++++++++++++- .../area/misthalin/edgeville/Oracle.kt | 2 +- .../edgeville/monastery/AbbotLangley.kt | 2 +- .../lumbridge/church/FatherAereck.kt | 2 +- .../area/misthalin/varrock/FatherLawrence.kt | 2 +- .../area/misthalin/varrock/palace/Reldo.kt | 2 +- 21 files changed, 284 insertions(+), 70 deletions(-) create mode 100644 data/area/misthalin/draynor/wise_old_man/wise_old_man.anims.toml create mode 100644 data/area/misthalin/draynor/wise_old_man/wise_old_man.combat.toml create mode 100644 data/area/misthalin/draynor/wise_old_man/wise_old_man.jingles.toml create mode 100644 data/area/misthalin/draynor/wise_old_man/wise_old_man.npcs.toml create mode 100644 data/area/misthalin/draynor/wise_old_man/wise_old_man.sounds.toml create mode 100644 game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/ThingUnderTheBed.kt diff --git a/data/area/misthalin/draynor/draynor.npcs.toml b/data/area/misthalin/draynor/draynor.npcs.toml index 54043da8d5..46c96c9e42 100644 --- a/data/area/misthalin/draynor/draynor.npcs.toml +++ b/data/area/misthalin/draynor/draynor.npcs.toml @@ -152,9 +152,6 @@ attack_bonus = 9 respawn_delay = 50 examine = "He guards the Draynor Market stalls from thieves." -[bed_draynor] -id = 2254 - [bank_guard_draynor] id = 2574 examine = "He's guarding the bank." @@ -168,9 +165,6 @@ id = 3299 pickpocket = { level = 38, stun_ticks = 8, stun_hit = 30, xp = 43.0, chance_min = 90, chance_max = 240, table = "master_farmer" } examine = "A master at gardening." -[wise_old_man_2] -id = 3820 - [fortunato] id = 3671 shop = "fortunatos_fine_wine" @@ -225,25 +219,12 @@ examine = "The hat's a dead giveaway." [chicken_draynor_2] id = 288 -[wise_old_man_draynor] -id = 2253 - -[wise_old_man_2_3] -id = 11569 - [ava_2] id = 5198 [scout_draynor_2] id = 5569 -[thing_under_the_bed] -id = 2255 -hitpoints = 250 -def = 5 -style = "melee" -max_hit_melee = 10 - [fishing_spot_small_net_bait_draynor] id = 327 fishing_net = { items = ["small_fishing_net"], bait = { none = ["raw_shrimps", "raw_anchovies"] } } diff --git a/data/area/misthalin/draynor/wise_old_man/wise_old_man.anims.toml b/data/area/misthalin/draynor/wise_old_man/wise_old_man.anims.toml new file mode 100644 index 0000000000..b8209a90a9 --- /dev/null +++ b/data/area/misthalin/draynor/wise_old_man/wise_old_man.anims.toml @@ -0,0 +1,8 @@ +[bed_block] +id = 2168 + +[bed_attack] +id = 2167 + +[bed_death] +id = 2169 diff --git a/data/area/misthalin/draynor/wise_old_man/wise_old_man.combat.toml b/data/area/misthalin/draynor/wise_old_man/wise_old_man.combat.toml new file mode 100644 index 0000000000..12ab5a8ebb --- /dev/null +++ b/data/area/misthalin/draynor/wise_old_man/wise_old_man.combat.toml @@ -0,0 +1,11 @@ +[thing_under_the_bed] +attack_speed = 4 +defend_anim = "bed_defend" +defend_sound = "bed_defend" +death_anim = "bed_death" +death_sound = "bed_death" + +[thing_under_the_bed.attack] +anim = "bed_attack" +target_sound = "bed_attack" +target_hit = { offense = "melee", max = 10 } diff --git a/data/area/misthalin/draynor/wise_old_man/wise_old_man.jingles.toml b/data/area/misthalin/draynor/wise_old_man/wise_old_man.jingles.toml new file mode 100644 index 0000000000..ec309170cb --- /dev/null +++ b/data/area/misthalin/draynor/wise_old_man/wise_old_man.jingles.toml @@ -0,0 +1,2 @@ +[wise_old_man] +id = 201 diff --git a/data/area/misthalin/draynor/wise_old_man/wise_old_man.npcs.toml b/data/area/misthalin/draynor/wise_old_man/wise_old_man.npcs.toml new file mode 100644 index 0000000000..ac9d44433b --- /dev/null +++ b/data/area/misthalin/draynor/wise_old_man/wise_old_man.npcs.toml @@ -0,0 +1,20 @@ +[wise_old_man_draynor] +id = 2253 + +[wise_old_man_2_3] +id = 11569 + +[wise_old_man_2] +id = 3820 + +[bed_draynor] +id = 2254 +hitpoints = 250 +def = 5 +combat_def = "thing_under_the_bed" +respawn_delay = 0 +examine = "It's a fairly ordinary bed, but..." + +[thing_under_the_bed] +id = 2255 +examine = "It's just like that dream I use to have when I was little." \ No newline at end of file diff --git a/data/area/misthalin/draynor/wise_old_man/wise_old_man.sounds.toml b/data/area/misthalin/draynor/wise_old_man/wise_old_man.sounds.toml new file mode 100644 index 0000000000..98140359df --- /dev/null +++ b/data/area/misthalin/draynor/wise_old_man/wise_old_man.sounds.toml @@ -0,0 +1,8 @@ +[bed_attack] +id = 1371 + +[bed_block] +id = 1373 + +[bed_death] +id = 1372 \ No newline at end of file diff --git a/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml b/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml index c437ef61be..25d76018f3 100644 --- a/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml +++ b/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml @@ -14,3 +14,15 @@ persist = true [wise_old_man_remaining] format = "int" persist = true + +[wise_old_man_tasks_completed] +format = "int" +persist = true + +[wise_old_man_letters_completed] +format = "int" +persist = true + +[wise_old_man_bed_kills] +format = "int" +persist = true diff --git a/data/entity/player/player.sounds.toml b/data/entity/player/player.sounds.toml index df988d2c1e..241885bf0f 100644 --- a/data/entity/player/player.sounds.toml +++ b/data/entity/player/player.sounds.toml @@ -36,3 +36,6 @@ id = 2393 [shearing] id = 761 + +[unarmed_kick] +id = 2565 diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt index fe3e265007..2dfa65a117 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt @@ -109,6 +109,7 @@ object EnumDefinitions : DefinitionsDecoder { require(NPCDefinitions.loaded) { "NPC definitions must be loaded before enum definitions" } require(StructDefinitions.loaded) { "Struct definitions must be loaded before enum definitions" } val ids = Object2IntOpenHashMap(definitions.size, Hash.VERY_FAST_LOAD_FACTOR) + val custom = mutableListOf() for (path in list) { Config.fileReader(path, 250) { while (nextSection()) { @@ -154,31 +155,37 @@ object EnumDefinitions : DefinitionsDecoder { } } if (id == -1 && map.isNotEmpty()) { - val index = definitions.size - definitions = Array(index + 1) { - if (it == index) { - EnumDefinition( - it, - keyType = keyType, - valueType = valueType, - defaultString = defaultString, - defaultInt = defaultInt, - length = map.size, - map = map, - extras = extras, - stringId = stringId, - ) - } else { - definitions[it] - } - } - ids[stringId] = index + custom.add( + EnumDefinition( + keyType = keyType, + valueType = valueType, + defaultString = defaultString, + defaultInt = defaultInt, + length = map.size, + map = map, + extras = extras, + stringId = stringId, + ) + ) } else { definitions[id].extras = extras } } } } + if (custom.isNotEmpty()) { + val index = definitions.size + definitions = Array(index + custom.size) { i -> + if (i >= index) { + custom[i - index].also { + ids[it.stringId] = i + it.id = i + } + } else { + definitions[i] + } + } + } this.ids = ids ids.size } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt index ff8bf46cf5..128918b72d 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt @@ -46,7 +46,7 @@ open class Movement( if (character is Player && !tile.noCollision) { val route = pathFinder.findPath(character, strategy, shape) character.steps.queueRoute(route, tile, tile.noCollision, tile.noRun) - } else { + } else if (tile != Tile.EMPTY) { character.steps.queueStep(tile, tile.noCollision, tile.noRun) } needsCalculation = false diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/target/NPCCharacterTargetStrategy.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/target/NPCCharacterTargetStrategy.kt index 4e346499cf..8949d8f40a 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/target/NPCCharacterTargetStrategy.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/target/NPCCharacterTargetStrategy.kt @@ -2,6 +2,7 @@ package world.gregs.voidps.engine.entity.character.mode.move.target import org.rsmod.game.pathfinder.PathFinder import world.gregs.voidps.engine.entity.character.Character +import world.gregs.voidps.engine.entity.character.npc.NPC import world.gregs.voidps.type.Tile data class NPCCharacterTargetStrategy( @@ -21,14 +22,21 @@ data class NPCCharacterTargetStrategy( override val sizeY: Int get() = character.size - override fun destination(source: Character) = Tile(PathFinder.naiveDestination( - sourceX = source.tile.x, - sourceZ = source.tile.y, - sourceWidth = source.size, - sourceHeight = source.size, - targetX = character.tile.x, - targetZ = character.tile.y, - targetWidth = character.size, - targetHeight = character.size - ).packed) + override fun destination(source: Character): Tile { + if (source is NPC && source.id == "bed_draynor") { + return Tile.EMPTY + } + return Tile( + PathFinder.naiveDestination( + sourceX = source.tile.x, + sourceZ = source.tile.y, + sourceWidth = source.size, + sourceHeight = source.size, + targetX = character.tile.x, + targetZ = character.tile.y, + targetWidth = character.size, + targetHeight = character.size + ).packed + ) + } } diff --git a/game/src/main/kotlin/content/area/asgarnia/entrana/HighPriest.kt b/game/src/main/kotlin/content/area/asgarnia/entrana/HighPriest.kt index b650127db0..fd491a8e47 100644 --- a/game/src/main/kotlin/content/area/asgarnia/entrana/HighPriest.kt +++ b/game/src/main/kotlin/content/area/asgarnia/entrana/HighPriest.kt @@ -31,7 +31,7 @@ class HighPriest : Script { } player("I've got a message for you from the Wise Old Man in Draynor Village.") npc("How kind of you to bring me a message to this remote island!") - val reward = OldMansMessage.reward(this) ?: return + val reward = OldMansMessage.rewardLetter(this) ?: return when (reward) { "runes" -> items("nature_rune", "water_rune", "The High Priest gives you some runes.") // TODO proper message "herbs" -> npc("Here, let me give you some herbs.") diff --git a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Thurgo.kt b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Thurgo.kt index 6d3509faaf..a8136a4c33 100644 --- a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Thurgo.kt +++ b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Thurgo.kt @@ -49,7 +49,7 @@ class Thurgo : Script { } player("The Wise Old Man says he's got the information you wanted. Here's his message.") npc("Ooh, thanks. I've been hoping he'd send me that information soon, although I wouldn't mind if he'd send me a pie instead!") - val reward = OldMansMessage.reward(this) ?: return + val reward = OldMansMessage.rewardLetter(this) ?: return when (reward) { "runes" -> { items("nature_rune", "water_rune", "Thurgo gives you some runes.") // TODO check always same runes or not? diff --git a/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/OldMansMessage.kt b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/OldMansMessage.kt index 7a1d12d790..2991e492b5 100644 --- a/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/OldMansMessage.kt +++ b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/OldMansMessage.kt @@ -127,40 +127,46 @@ class OldMansMessage : Script { } companion object { - suspend fun reward(player: Player): String? { + suspend fun rewardLetter(player: Player): String? { if (player.inventory.isFull()) { player.npc("I'd give you a reward, but you don't seem to have any space for it. Come back when you do.") return null } player.inventory.remove("old_mans_message") player.clear("wise_old_man_npc") + player.inc("wise_old_man_letters_completed") + return reward(player, true) + } + + suspend fun reward(player: Player, hard: Boolean): String { val drops: DropTables = get() - val easy = false val chance = random.nextInt(16) if (chance < 1) { // 6.25% val drops = drops.getValue("wise_old_man_gems").roll() give(player, drops) return drops.first().id } else if (chance < 3) { // 12.5% - if (!repeat(player, "wise_old_man_runes", if (easy) 10 else 25)) { + if (!repeat(player, "wise_old_man_runes", if (hard) 25 else 10)) { player.statement("Unfortunately you don't have space for all the runes.") } return "runes" } else if (chance < 5) { // 12.5% - if (!repeat(player, "wise_old_man_herbs", if (easy) 3 else 10)) { + if (!repeat(player, "wise_old_man_herbs", if (hard) 10 else 3)) { player.statement("Unfortunately you don't have space for all the herbs.") } return "herbs" } else if (chance < 7) { // 12.5% - if (!repeat(player, "wise_old_man_herbs", if (easy) 3 else 10)) { + if (!repeat(player, "wise_old_man_herbs", if (hard) 10 else 3)) { player.statement("Unfortunately you don't have space for all the seeds.") } return "seeds" } else if (player.has(Skill.Prayer, 3) && chance < 14) { // 43.75% - player.exp(Skill.Prayer, 10.0) + val range = if (hard) 215..430 else 185..370 + val amount = range.random(random) + player.exp(Skill.Prayer, amount.toDouble()) return "prayer" } else { // 12.5% or 56.25% depending on prayer level - val range = if (easy) 185..215 else 990..1020 + val range = if (hard) 990..1020 else 185..215 player.inventory.add("coins", range.random(random)) return "coins" } @@ -185,7 +191,7 @@ class OldMansMessage : Script { dropped = true } } - return dropped + return !dropped } } } diff --git a/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/ThingUnderTheBed.kt b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/ThingUnderTheBed.kt new file mode 100644 index 0000000000..0120f9b9fb --- /dev/null +++ b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/ThingUnderTheBed.kt @@ -0,0 +1,44 @@ +package content.area.misthalin.draynor_village.wise_old_man + +import content.entity.combat.killer +import content.entity.effect.transform +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.type.player +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.instruction.handle.interactPlayer +import world.gregs.voidps.engine.entity.character.jingle +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.sound + +class ThingUnderTheBed : Script { + init { + npcOperate("Kick", "bed_draynor") { (target) -> + watch(target) + anim("unarmed_kick") + sound("unarmed_kick") + delay(1) + target.anim("bed_block") + clearWatch() + if (get("wise_old_man_npc", "") != "thing_under_the_bed") { + return@npcOperate + } + if (get("wise_old_man_remaining", 0) == 0) { + player("I think it's already dead. Maybe the Wise Old Man will reward me now?") + return@npcOperate + } + target.say("Gurrhh!") + jingle("wise_old_man") + delay(1) + target.transform("thing_under_the_bed") + target.interactPlayer(this, "Attack") + } + + npcDeath("bed_draynor") { + val killer = killer + if (killer is Player) { + killer.dec("wise_old_man_remaining") + killer.inc("wise_old_man_bed_kills") + } + } + } +} diff --git a/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt index edd4c41875..4d36991429 100644 --- a/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt +++ b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt @@ -12,27 +12,34 @@ import content.entity.player.dialogue.Shifty import content.entity.player.dialogue.Shock import content.entity.player.dialogue.type.ChoiceOption import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.item +import content.entity.player.dialogue.type.items import content.entity.player.dialogue.type.npc import content.entity.player.dialogue.type.player import content.quest.questCompleted +import net.pearx.kasechange.toSentenceCase import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.client.ui.InterfaceApi.Companion.option import world.gregs.voidps.engine.data.definition.EnumDefinitions import world.gregs.voidps.engine.entity.World +import world.gregs.voidps.engine.entity.character.npc.NPC import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.name import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.exp.exp import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has import world.gregs.voidps.engine.inv.add import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove +import world.gregs.voidps.engine.inv.removeToLimit import world.gregs.voidps.type.random class WiseOldMan : Script { init { - npcOperate("Talk-to", "wise_old_man_draynor") { + npcOperate("Talk-to", "wise_old_man_draynor") { (target) -> npc("Greetings, $name.") if (get("wise_old_man_met", false)) { + checkTaskNpcs(target) + checkTaskItems() choice("What would you like to say?") { anyHelp(this@choice) findJunk() @@ -44,6 +51,81 @@ class WiseOldMan : Script { } } + suspend fun Player.checkTaskNpcs(npc: NPC) { + if (get("wise_old_man_npc", "") != "thing_under_the_bed") { + return + } + if (get("wise_old_man_remaining", 0) != 0) { + return + } + player("I've killed a creature that was under your bed.") + npc("Ah, thank you very much! Now I shall be able to sleep in peace.") + npc("Allow me to offer you an appropriate reward for your assistance...") + npc.anim("bind") + clear("wise_old_man_npc") + clear("wise_old_man_remaining") + exp(Skill.Constitution, (280..300).random(random).toDouble()) + } + + suspend fun Player.checkTaskItems() { + val item: String = get("wise_old_man_task") ?: return + if (!inventory.contains(item)) { + if (inventory.contains("${item}_noted")) { + player("Here, I've got the items you wanted.") + npc("Those are banknotes! I can't use those!") + } + return + } + val remaining: Int = get("wise_old_man_remaining") ?: return + if (inventory.count(item) < remaining) { + player("I've got some of the stuff you wanted.") + } else { + player("I've got all the stuff you asked me to fetch.") + } + val removed = inventory.removeToLimit(item, remaining) + if (removed != remaining) { + set("wise_old_man_remaining", remaining - removed) + npc("Ahh, you are very kind.") + player("I'll come back when I've got the rest.") + return + } + clear("wise_old_man_remaining") + clear("wise_old_man_task") + inc("wise_old_man_tasks_completed") + when (val reward = OldMansMessage.reward(this, hard.contains(item))) { + "runes" -> { + items("nature_rune", "water_rune", "The Wise Old Man gives you some runes.") + npc("Thank you, thank you! Please take these runes as a sign of my gratitude.") + } + "herbs" -> { + item("grimy_tarromin", 400, "The Wise Old Man gives you some backnotes that can be exchanged for herbs.") + npc("Thank you, thank you! Please take these herbs as a sign of my gratitude.") + } + "seeds" -> { + item("potato_seed", 400, "The Wise Old Man gives you some seeds.") + npc("Thank you, thank you! Please take these seeds as a sign of my gratitude.") + } + "prayer" -> { + item(167, "The Wise Old Man blesses you.
You gain some Prayer xp.") + npc("Thank you, thank you! In thanks, I shall bestow on you a simple blessing.") + } + "coins" -> { + item("coins_8", 400, "The Wise Old Man gives you some coins.") + npc("Thank you, thank you! Please take this money as a sign of my gratitude.") + } + else -> item( + reward, + 400, + "The Wise Old Man gives you an ${reward.toSentenceCase()}${ + when { + reward.endsWith("diamond") || reward.endsWith("ruby") || reward.endsWith("emerald") -> "!" + else -> "." + } + }", + ) + } + } + private suspend fun Player.intro() { player("So you're a wise old man, huh?") npc("Less of the 'old' man, if you please!") @@ -119,9 +201,9 @@ class WiseOldMan : Script { // TODO add junk search npc("There doesn't seem to be any junk in your inventory at all.") } - // if (follower != null) { // TODO and has BoB - // option("Could you check my beast of burden for junk, please?") - // } + // if (follower != null) { // TODO and has BoB + // option("Could you check my beast of burden for junk, please?") + // } } } } @@ -334,7 +416,7 @@ class WiseOldMan : Script { val item: String = get("wise_old_man_task") ?: return val remaining: Int = get("wise_old_man_remaining") ?: return val intro = EnumDefinitions.string("wise_old_man_items", item) - npc("$intro. I still need $remaining.") + npc("$intro I still need $remaining.") hintItem(item) } @@ -360,6 +442,8 @@ class WiseOldMan : Script { npc("Please make room in your inventory to carry the letter.") return } + } else { + set("wise_old_man_remaining", 1) } hintNpc(npc) return @@ -394,6 +478,26 @@ class WiseOldMan : Script { } } + private val hard = setOf( + "ball_of_wool", + "bowstring", + "bread", + "bronze_arrowtips", + "bronze_knife", + "bronze_warhammer", + "bronze_wire", + "headless_arrow", + "swamp_paste", + "iron_arrowtips", + "iron_knife", + "iron_warhammer", + "leather_cowl", + "pot_of_flour", + "unfired_pie_dish", + "unfired_pot", + "leather_boots", + ) + private fun Player.tasks(): MutableSet { val tasks = mutableSetOf( "beer_glass", diff --git a/game/src/main/kotlin/content/area/misthalin/edgeville/Oracle.kt b/game/src/main/kotlin/content/area/misthalin/edgeville/Oracle.kt index abe3c92882..5dcb60bfd3 100644 --- a/game/src/main/kotlin/content/area/misthalin/edgeville/Oracle.kt +++ b/game/src/main/kotlin/content/area/misthalin/edgeville/Oracle.kt @@ -62,7 +62,7 @@ class Oracle : Script { } player("I've got a message for you from the Wise Old Man who lives in Draynor Village.") npc("Many do my wisdom seek; few do their own wisdom to me send!") - val reward = OldMansMessage.reward(this) ?: return + val reward = OldMansMessage.rewardLetter(this) ?: return when (reward) { "runes" -> { items("nature_rune", "water_rune", "The Oracle gives you some runes.") // TODO proper message diff --git a/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/AbbotLangley.kt b/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/AbbotLangley.kt index a6561399fd..a6cea073e2 100644 --- a/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/AbbotLangley.kt +++ b/game/src/main/kotlin/content/area/misthalin/edgeville/monastery/AbbotLangley.kt @@ -64,7 +64,7 @@ class AbbotLangley : Script { } player("I've got a message for you from your friend in Draynor Village.") npc("Gosh, you are very kind to bring a message to my remote monastery!") - val reward = OldMansMessage.reward(this) ?: return + val reward = OldMansMessage.rewardLetter(this) ?: return when (reward) { "runes" -> items("nature_rune", "water_rune", "Abbot Langley gives you some runes.") // TODO proper message "herbs" -> { diff --git a/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt b/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt index 8ac9b8f78e..a6fbdf0a80 100644 --- a/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt +++ b/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt @@ -62,7 +62,7 @@ class FatherAereck(val drops: DropTables) : Script { } player("The Wise Old Man of Draynor Village said you might reward me if I brought you this.") npc("Oh, did he?") - val reward = OldMansMessage.reward(this) ?: return + val reward = OldMansMessage.rewardLetter(this) ?: return when (reward) { "runes" -> { items("nature_rune", "water_rune", "Faether Aereck gives you some runes.") diff --git a/game/src/main/kotlin/content/area/misthalin/varrock/FatherLawrence.kt b/game/src/main/kotlin/content/area/misthalin/varrock/FatherLawrence.kt index 8dcc6e1a33..6ce4db4e0a 100644 --- a/game/src/main/kotlin/content/area/misthalin/varrock/FatherLawrence.kt +++ b/game/src/main/kotlin/content/area/misthalin/varrock/FatherLawrence.kt @@ -32,7 +32,7 @@ class FatherLawrence : Script { npc("mssge? wha messsge?") npc("oh, msesesge for me.") message("Oh dear, he doesn't look like he's going to be able to read the message!") - val reward = OldMansMessage.reward(this) ?: return + val reward = OldMansMessage.rewardLetter(this) ?: return when (reward) { "runes" -> { items("nature_rune", "water_rune", "Father Lawrence gives you some runes.") diff --git a/game/src/main/kotlin/content/area/misthalin/varrock/palace/Reldo.kt b/game/src/main/kotlin/content/area/misthalin/varrock/palace/Reldo.kt index 5014a2cb39..e3190142f7 100644 --- a/game/src/main/kotlin/content/area/misthalin/varrock/palace/Reldo.kt +++ b/game/src/main/kotlin/content/area/misthalin/varrock/palace/Reldo.kt @@ -36,7 +36,7 @@ class Reldo : Script { private suspend fun Player.wiseOldManLetter() { npc("Ah, I am always delighted to hear from him. You would not imagine the depths of his wisdom!") - val reward = OldMansMessage.reward(this) ?: return + val reward = OldMansMessage.rewardLetter(this) ?: return when (reward) { "runes" -> { items("nature_rune", "water_rune", "Reldo gives you some runes.") From a78dcdfc1d24841a4d8ee6581326174dc6bd6bec Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 27 Feb 2026 21:13:54 +0000 Subject: [PATCH 19/20] Add integration tests --- .../wise_old_man/wise_old_man.vars.toml | 6 +- .../engine/data/definition/EnumDefinitions.kt | 2 +- .../wise_old_man/WiseOldMan.kt | 7 +- game/src/test/kotlin/WorldTest.kt | 8 +- .../wise_old_man/WiseOldManTest.kt | 73 +++++++++++++++++++ 5 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 game/src/test/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldManTest.kt diff --git a/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml b/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml index 25d76018f3..663d44293a 100644 --- a/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml +++ b/data/area/misthalin/draynor/wise_old_man/wise_old_man.vars.toml @@ -5,11 +5,7 @@ persist = true [wise_old_man_npc] format = "list" persist = true -values = [ "father_aereck", "high_priest_entrana", "reldo", "thurgo", "father_lawrence", "abbot_langley", "oracle", "thing_under_the_bed" ] - -[wise_old_man_amount] -format = "int" -persist = true +values = [ -1, "father_aereck", "high_priest_entrana", "reldo", "thurgo", "father_lawrence", "abbot_langley", "oracle", "thing_under_the_bed" ] [wise_old_man_remaining] format = "int" diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt index 2dfa65a117..5c166e306b 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/EnumDefinitions.kt @@ -47,7 +47,7 @@ object EnumDefinitions : DefinitionsDecoder { EnumTypes.INV -> InventoryDefinitions.get(key).id EnumTypes.NPC -> NPCDefinitions.get(key).id EnumTypes.STRUCT -> StructDefinitions.get(key).id - else -> error("Unsupported enum type: $keyType") + else -> error("Unsupported enum type: ${keyType.code}") } fun string(enum: String, key: String): String { diff --git a/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt index 4d36991429..e78ba3f9cd 100644 --- a/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt +++ b/game/src/main/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldMan.kt @@ -436,14 +436,14 @@ class WiseOldMan : Script { val intro = EnumDefinitions.string("wise_old_man_npcs", npc) npc(intro) set("wise_old_man_npc", npc) - if (npc != "thing_under_the_bed") { + if (npc == "thing_under_the_bed") { + set("wise_old_man_remaining", 1) + } else { npc("Here's the letter") if (!inventory.add("old_mans_message")) { npc("Please make room in your inventory to carry the letter.") return } - } else { - set("wise_old_man_remaining", 1) } hintNpc(npc) return @@ -451,7 +451,6 @@ class WiseOldMan : Script { val amount = random.nextInt(3, 16) val item = tasks().random(random) set("wise_old_man_task", item) - set("wise_old_man_amount", amount) set("wise_old_man_remaining", amount) val intro = EnumDefinitions.string("wise_old_man_items", item) npc("$intro. Please bring me $amount.") diff --git a/game/src/test/kotlin/WorldTest.kt b/game/src/test/kotlin/WorldTest.kt index a358d0c62d..49688b3373 100644 --- a/game/src/test/kotlin/WorldTest.kt +++ b/game/src/test/kotlin/WorldTest.kt @@ -354,15 +354,17 @@ abstract class WorldTest : KoinTest { private val weaponStyleDefinitions: WeaponStyleDefinitions by lazy { WeaponStyleDefinitions().load(configFiles.find(Settings["definitions.weapons.styles"])) } private val weaponAnimationDefinitions: WeaponAnimationDefinitions by lazy { WeaponAnimationDefinitions().load(configFiles.find(Settings["definitions.weapons.animations"])) } private val enumDefinitions: Array by lazy { - EnumDecoder().load(cache) - } - private val enumIds: Map by lazy { itemIds interfaceIds inventoryIds npcIds structIds EnumDefinitions.init(EnumDecoder().load(cache)).load(configFiles.list(Settings["definitions.enums"])) + EnumDefinitions.definitions + } + + private val enumIds: Map by lazy { + enumDefinitions EnumDefinitions.ids } private val objectCollisionAdd: GameObjectCollisionAdd by lazy { GameObjectCollisionAdd() } diff --git a/game/src/test/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldManTest.kt b/game/src/test/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldManTest.kt new file mode 100644 index 0000000000..1fd9627b43 --- /dev/null +++ b/game/src/test/kotlin/content/area/misthalin/draynor_village/wise_old_man/WiseOldManTest.kt @@ -0,0 +1,73 @@ +package content.area.misthalin.draynor_village.wise_old_man + +import FakeRandom +import WorldTest +import dialogueContinue +import dialogueOption +import npcOption +import org.junit.jupiter.api.Test +import world.gregs.voidps.engine.client.ui.closeDialogue +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.type.Tile +import world.gregs.voidps.type.setRandom +import kotlin.test.assertEquals + +class WiseOldManTest : WorldTest() { + + @Test + fun `Complete letter task`() { + setRandom(object : FakeRandom() { + override fun nextInt(until: Int) = if (until == 16) 12 else 0 + override fun nextInt(from: Int, until: Int) = from + }) + val player = createPlayer(Tile(3088, 3254)) + player.levels.set(Skill.Prayer, 3) + player["wise_old_man_met"] = true + val wom = createNPC("wise_old_man_draynor", Tile(3088, 3255)) + player.npcOption(wom, "Talk-to") + tick() + player.dialogueContinue() + player.dialogueOption("line1") + player.dialogueContinue(4) + assertEquals("father_aereck", player["wise_old_man_npc", ""]) + assertEquals(1, player.inventory.count("old_mans_message")) + val father = createNPC("father_aereck", Tile(3088, 3255)) + player.npcOption(father, "Talk-to") + tick() + player.dialogueContinue(4) + assertEquals("", player["wise_old_man_npc", ""]) + assertEquals(1, player["wise_old_man_letters_completed", 0]) + assertEquals(0, player.inventory.count("old_mans_message")) + assertEquals(215.0, player.experience.get(Skill.Prayer)) + } + + @Test + fun `Complete basic task`() { + setRandom(object : FakeRandom() { + override fun nextInt(until: Int) = if (until == 100) 20 else 0 + override fun nextInt(from: Int, until: Int) = from + }) + val player = createPlayer(Tile(3088, 3254)) + player["wise_old_man_met"] = true + val wom = createNPC("wise_old_man_draynor", Tile(3088, 3255)) + player.npcOption(wom, "Talk-to") + tick() + player.dialogueContinue() + player.dialogueOption("line1") + player.dialogueContinue(3) + player.closeDialogue() + assertEquals("beer_glass", player["wise_old_man_task", ""]) + assertEquals(3, player["wise_old_man_remaining", 0]) + player.inventory.add("beer_glass", 3) + player.npcOption(wom, "Talk-to") + tick() + player.dialogueContinue(3) + assertEquals(0, player.inventory.count("beer_glass")) + assertEquals(1, player.inventory.count("uncut_red_topaz")) + assertEquals("", player["wise_old_man_task", ""]) + assertEquals(0, player["wise_old_man_remaining", 0]) + assertEquals(1, player["wise_old_man_tasks_completed", 0]) + } +} From 13cbf85a7a71a17cf8ffd72605d9244d248b624a Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 27 Feb 2026 21:20:37 +0000 Subject: [PATCH 20/20] Fix test --- .../voidps/engine/entity/character/mode/move/MovementTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/src/test/kotlin/world/gregs/voidps/engine/entity/character/mode/move/MovementTest.kt b/engine/src/test/kotlin/world/gregs/voidps/engine/entity/character/mode/move/MovementTest.kt index 42ed837eb1..efd894ea4b 100644 --- a/engine/src/test/kotlin/world/gregs/voidps/engine/entity/character/mode/move/MovementTest.kt +++ b/engine/src/test/kotlin/world/gregs/voidps/engine/entity/character/mode/move/MovementTest.kt @@ -53,7 +53,7 @@ internal class MovementTest : KoinMock() { @Test fun `Player queues smart route`() { player.tile = Tile(10, 10) - val movement = Movement(player, TileTargetStrategy(Tile.EMPTY)) + val movement = Movement(player, TileTargetStrategy(Tile(1, 1))) movement.calculate() assertTrue(player.steps.isNotEmpty()) } @@ -61,7 +61,7 @@ internal class MovementTest : KoinMock() { @Test fun `Npc queues step`() { val npc = NPC(tile = Tile(10, 10)) - val movement = Movement(npc, TileTargetStrategy(Tile.EMPTY)) + val movement = Movement(npc, TileTargetStrategy(Tile(1, 1))) movement.calculate() assertTrue(npc.steps.isNotEmpty()) verify(exactly = 0) {