diff --git a/.data/gamevals-binary/max-ids.toml b/.data/gamevals-binary/max-ids.toml index 48464519c..de9f9b4fc 100644 --- a/.data/gamevals-binary/max-ids.toml +++ b/.data/gamevals-binary/max-ids.toml @@ -6,6 +6,7 @@ revision = 239 [max_ids] +component = 63438864 dbrow = 16939 dbtable = 258 interface = 968 diff --git a/.data/gamevals/area.rscm b/.data/gamevals/area.rscm index 92e34494c..aa8372e84 100644 --- a/.data/gamevals/area.rscm +++ b/.data/gamevals/area.rscm @@ -54,6 +54,7 @@ greatconch=53 charred_island_dungeon=54 revenant_caves=55 mining_guild=56 +rune_essence_mine=57 minor_wilderness_bosses_dungeons=32757 ferox_enclave=32758 diff --git a/.data/raw-cache/map/area/rune_essence_mine.toml b/.data/raw-cache/map/area/rune_essence_mine.toml new file mode 100644 index 000000000..e74c8411c --- /dev/null +++ b/.data/raw-cache/map/area/rune_essence_mine.toml @@ -0,0 +1,12 @@ +[[area]] +name = "Rune Essence Mine" +area_id = "area.rune_essence_mine" +levels = [0] + +[[area.polygons]] +vertices = [ + [2882, 4806], + [2931, 4806], + [2931, 4858], + [2882, 4858], +] diff --git a/.data/raw-cache/server/loc/loc.toml b/.data/raw-cache/server/loc/loc.toml index 595ef9415..3484086e6 100644 --- a/.data/raw-cache/server/loc/loc.toml +++ b/.data/raw-cache/server/loc/loc.toml @@ -389,6 +389,140 @@ contentGroup = "content.tree" "param.skill_xp" = 250 "param.next_loc_stage" = "loc.deadtree2_stump_dark" +[[object]] +id = "loc.castledoubledoorl" +inherit = "loc.castledoubledoorl" +contentGroup = "content.closed_left_door" + +[object.params] +"param.next_loc_stage" = "loc.opencastledoubledoorl" + +[[object]] +id = "loc.opencastledoubledoorl" +inherit = "loc.opencastledoubledoorl" +contentGroup = "content.opened_left_door" + +[object.params] +"param.next_loc_stage" = "loc.castledoubledoorl" + +[[object]] +id = "loc.castledoubledoorr" +inherit = "loc.castledoubledoorr" +contentGroup = "content.closed_right_door" + +[object.params] +"param.next_loc_stage" = "loc.opencastledoubledoorr" + +[[object]] +id = "loc.opencastledoubledoorr" +inherit = "loc.opencastledoubledoorr" +contentGroup = "content.opened_right_door" + +[object.params] +"param.next_loc_stage" = "loc.castledoubledoorr" + +[[object]] +id = "loc.poordoor" +inherit = "loc.poordoor" +contentGroup = "content.closed_single_door" + +[object.params] +"param.next_loc_stage" = "loc.poordooropen" + +[[object]] +id = "loc.poordooropen" +inherit = "loc.poordooropen" +contentGroup = "content.opened_single_door" + +[object.params] +"param.next_loc_stage" = "loc.poordoor" + +[[object]] +id = "loc.fai_wiztower_poor_door" +inherit = "loc.fai_wiztower_poor_door" +contentGroup = "content.closed_single_door" + +[object.params] +"param.next_loc_stage" = "loc.fai_wiztower_poor_door_open" + +[[object]] +id = "loc.fai_wiztower_poor_door_open" +inherit = "loc.fai_wiztower_poor_door_open" +contentGroup = "content.opened_single_door" + +[object.params] +"param.next_loc_stage" = "loc.fai_wiztower_poor_door" + +[[object]] +id = "loc.poshdoor" +inherit = "loc.poshdoor" +contentGroup = "content.closed_single_door" + +[object.params] +"param.next_loc_stage" = "loc.poshdooropen" +"param.opensound" = "synth.nicedoor_open" + +[[object]] +id = "loc.poshdooropen" +inherit = "loc.poshdooropen" +contentGroup = "content.opened_single_door" + +[object.params] +"param.closesound" = "synth.nicedoor_close" +"param.next_loc_stage" = "loc.poshdoor" + +[[object]] +id = "loc.elfdoor" +inherit = "loc.elfdoor" +contentGroup = "content.closed_single_door" + +[object.params] +"param.next_loc_stage" = "loc.elfdooropen" + +[[object]] +id = "loc.elfdooropen" +inherit = "loc.elfdooropen" +contentGroup = "content.opened_single_door" + +[object.params] +"param.next_loc_stage" = "loc.elfdoor" + +[[object]] +id = "loc.fencegate_l" +inherit = "loc.fencegate_l" +contentGroup = "content.closed_left_picketgate" + +[object.params] +"param.next_loc_stage" = "loc.openfencegate_l" +"param.opensound" = "synth.picketgate_open" + +[[object]] +id = "loc.openfencegate_l" +inherit = "loc.openfencegate_l" +contentGroup = "content.opened_left_picketgate" + +[object.params] +"param.closesound" = "synth.picketgate_close" +"param.next_loc_stage" = "loc.fencegate_l" + +[[object]] +id = "loc.fencegate_r" +inherit = "loc.fencegate_r" +contentGroup = "content.closed_right_picketgate" + +[object.params] +"param.next_loc_stage" = "loc.openfencegate_r" +"param.opensound" = "synth.picketgate_open" + +[[object]] +id = "loc.openfencegate_r" +inherit = "loc.openfencegate_r" +contentGroup = "content.opened_right_picketgate" + +[object.params] +"param.closesound" = "synth.picketgate_close" +"param.next_loc_stage" = "loc.fencegate_r" + [[object]] id = "loc.achey_tree" inherit = "loc.achey_tree" @@ -910,6 +1044,29 @@ id = "loc.ladder_from_cellar" inherit = "loc.ladder_from_cellar" contentGroup = "content.dungeonladder_up" +[[object]] +id = "loc.fai_wiztower_spiralstairs" +inherit = "loc.fai_wiztower_spiralstairs" +contentGroup = "content.spiralstaircase_up" + +[[object]] +id = "loc.fai_wiztower_spiralstairs_middle" +inherit = "loc.fai_wiztower_spiralstairs_middle" +contentGroup = "content.spiralstaircase_option" + +[[object]] +id = "loc.fai_wiztower_spiralstairstop" +inherit = "loc.fai_wiztower_spiralstairstop" +contentGroup = "content.spiralstaircase_down" + +[[object]] +id = "loc.wizards_tower_laddertop" +inherit = "loc.wizards_tower_laddertop" + +[[object]] +id = "loc.wizards_tower_ladder" +inherit = "loc.wizards_tower_ladder" + [[object]] id = "loc.aide_bankbooth" inherit = "loc.aide_bankbooth" diff --git a/content/areas/city/lumbridge/build.gradle.kts b/content/areas/city/lumbridge/build.gradle.kts index 2c82bf53e..fc0645dfe 100644 --- a/content/areas/city/lumbridge/build.gradle.kts +++ b/content/areas/city/lumbridge/build.gradle.kts @@ -5,4 +5,5 @@ plugins { dependencies { implementation(projects.api.pluginCommons) implementation(projects.api.scriptAdvanced) + implementation(projects.content.quest) } diff --git a/content/areas/city/lumbridge/src/main/kotlin/org/rsmod/content/areas/city/lumbridge/npcs/DonieAndGee.kt b/content/areas/city/lumbridge/src/main/kotlin/org/rsmod/content/areas/city/lumbridge/npcs/DonieAndGee.kt index e677c482a..2374cdf48 100644 --- a/content/areas/city/lumbridge/src/main/kotlin/org/rsmod/content/areas/city/lumbridge/npcs/DonieAndGee.kt +++ b/content/areas/city/lumbridge/src/main/kotlin/org/rsmod/content/areas/city/lumbridge/npcs/DonieAndGee.kt @@ -343,12 +343,18 @@ private suspend fun Dialogue.dukeStrangeTalismanQuest() { "Lumbridge Castle.", ) // Note: dialogue should be different when the quest has been started but not completed. + // varp.runemysteries uses real stages (endstate=6 when complete). val progress = vars["varp.runemysteries"] - if (progress == 0) { - chatPlayer(quiz, "Sounds mysterious. I may just do that. Thanks.") - } else { - chatPlayer(happy, "Yes, I have already solved the rune mysteries.") - chatNpc(happy, "Ah excellent. Thank you very much adventurer.") + when { + progress == 0 -> chatPlayer(quiz, "Sounds mysterious. I may just do that. Thanks.") + progress >= 6 -> { + chatPlayer(happy, "Yes, I have already solved the rune mysteries.") + chatNpc(happy, "Ah excellent. Thank you very much adventurer.") + } + else -> { + chatPlayer(happy, "I'm already looking into that for the Duke.") + chatNpc(happy, "Good luck with it.") + } } } diff --git a/content/areas/city/lumbridge/src/main/kotlin/org/rsmod/content/areas/city/lumbridge/npcs/DukeHoracio.kt b/content/areas/city/lumbridge/src/main/kotlin/org/rsmod/content/areas/city/lumbridge/npcs/DukeHoracio.kt new file mode 100644 index 000000000..65b4a7145 --- /dev/null +++ b/content/areas/city/lumbridge/src/main/kotlin/org/rsmod/content/areas/city/lumbridge/npcs/DukeHoracio.kt @@ -0,0 +1,288 @@ +package org.rsmod.content.areas.city.lumbridge.npcs + +import jakarta.inject.Inject +import org.rsmod.api.config.Constants +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.vars.intVarp +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.content.quest.area.lumbridge.RuneMysteriesQuest +import org.rsmod.content.quest.area.lumbridge.rmTalisman +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +class DukeHoracio @Inject constructor(private val runeMysteries: RuneMysteriesQuest) : PluginScript() { + + private val quest + get() = runeMysteries.quest + + private var Player.dragonQuest by intVarp("varp.dragonquest") + + override fun ScriptContext.startup() { + onOpNpc1("npc.duke_of_lumbridge") { startDukeDialogue(it.npc) } + } + + private suspend fun ProtectedAccess.startDukeDialogue(npc: Npc) { + startDialogue(npc) { dukeDialogue(npc) } + } + + private suspend fun Dialogue.dukeDialogue(npc: Npc) { + chatNpc(happy, "Greetings. Welcome to my castle.") + + val hasShield = player.inv.contains(ANTI_DRAGON_SHIELD) + val showShieldOption = !hasShield && player.dragonQuest >= 1 + val stage = quest.getQuestStage(player) + val questOption = + when { + stage == RuneMysteriesQuest.STAGE_TALISMAN -> "What did you want me to do again?" + else -> "Have you any quests for me?" + } + + val choice = + if (showShieldOption) { + choice3( + "I seek a shield that will protect me from dragonbreath.", + 1, + questOption, + 2, + "Where can I find money?", + 3, + ) + } else { + choice2( + questOption, + 2, + "Where can I find money?", + 3, + ) + } + + when (choice) { + 1 -> dukeDragonShield(npc) + 2 -> dukeQuestBranch(npc) + 3 -> { + chatPlayer(quiz, "Where can I find money?") + chatNpc( + happy, + "I've heard that the blacksmiths are prosperous amongst the peasantry. " + + "Maybe you could try your hand at that?", + ) + } + } + } + + private suspend fun Dialogue.dukeQuestBranch(npc: Npc) { + val stage = quest.getQuestStage(player) + when { + stage == 0 -> dukeStartQuest(npc) + stage == RuneMysteriesQuest.STAGE_TALISMAN && !hasAirTalisman(player) -> + dukeReplaceTalisman(npc) + stage == RuneMysteriesQuest.STAGE_TALISMAN -> { + chatPlayer(quiz, "What did you want me to do again?") + chatNpc( + happy, + "Take that talisman I gave you to Sedridor at the Wizards' Tower. You'll find " + + "it south west of here, across the bridge from Draynor Village.", + ) + chatPlayer(happy, "Okay, will do.") + } + stage in RuneMysteriesQuest.STAGE_TALISMAN_GIVEN until RuneMysteriesQuest.STAGE_COMPLETE -> { + chatPlayer(quiz, "Have you any quests for me?") + chatNpc(happy, "The only job I had was the delivery of that talisman, so I'm afraid not.") + } + else -> { + chatPlayer(quiz, "Have you any quests for me?") + chatNpc(happy, "No, all is well for me.") + } + } + } + + private suspend fun Dialogue.dukeStartQuest(npc: Npc) { + chatPlayer(quiz, "Have you any quests for me?") + chatNpc( + quiz, + "Well, I wouldn't describe it as a quest, but there is something I could use some help with.", + ) + chatPlayer(quiz, "What is it?") + chatNpc( + happy, + "We were recently sorting through some of the things stored down in the cellar, and we " + + "found this old talisman.", + ) + mesbox("The Duke shows you a talisman.") + chatNpc( + happy, + "The Order of Wizards over at the Wizards' Tower have been on the hunt for magical " + + "artefacts recently. I wonder if this might be just the kind of thing they're after.", + ) + chatNpc(quiz, "Would you be willing to take it to them for me?") + + when ( + choice2( + "Sure, no problem.", + 1, + "Not right now.", + 2, + title = "Start the Rune Mysteries quest?", + ) + ) { + 1 -> { + chatPlayer(happy, "Sure, no problem.") + if (access.invAdd(access.inv, RuneMysteriesQuest.AIR_TALISMAN).failure) { + chatNpc( + sad, + "You don't seem to have any free inventory space. Come back when you do.", + ) + return + } + player.rmTalisman = true + quest.advanceQuestStage(access) + chatNpc( + happy, + "Thank you very much. You'll find the Wizards' Tower south west of here, " + + "across the bridge from Draynor Village. When you arrive, look for " + + "Sedridor. He is the Archmage of the wizards there.", + ) + objbox(RuneMysteriesQuest.AIR_TALISMAN, "The Duke hands you the talisman.") + } + 2 -> { + chatPlayer(sad, "Not right now.") + chatNpc(sad, "As you wish. Hopefully I can find someone else to help.") + } + } + } + + private suspend fun Dialogue.dukeReplaceTalisman(npc: Npc) { + chatPlayer(quiz, "What did you want me to do again?") + chatNpc(quiz, "Did you take that talisman to Sedridor?") + chatPlayer(sad, "No, I lost it.") + chatNpc( + happy, + "Ah, well that explains things. One of my servants found it outside, and it seemed " + + "too much of a coincidence that another would suddenly show up.", + ) + if (access.invAdd(access.inv, RuneMysteriesQuest.AIR_TALISMAN).failure) { + chatNpc(sad, "You don't have space for it right now. Come back when you do.") + return + } + player.rmTalisman = true + chatNpc( + happy, + "Here, take it to the Wizards' Tower, south west of here. Please try not to lose it this time.", + ) + objbox(RuneMysteriesQuest.AIR_TALISMAN, "The Duke hands you the talisman.") + } + + private suspend fun Dialogue.dukeDragonShield(npc: Npc) { + chatPlayer(quiz, "I seek a shield that will protect me from dragonbreath.") + chatNpc(quiz, "A knight going on a dragon quest, hmm? What dragon do you intend to slay?") + + when ( + choice2( + "Elvarg, the dragon of Crandor island!", + 1, + "Oh, no dragon in particular.", + 2, + ) + ) { + 1 -> { + chatPlayer(happy, "Elvarg, the dragon of Crandor island!") + chatNpc(shocked, "Elvarg? Are you sure?") + when (choice2("Yes.", 1, "I'd better leave that dragon alone.", 2, title = "Well, are you sure?")) { + 1 -> { + chatPlayer(happy, "Yes.") + val gender = + if (player.appearance.bodyType == Constants.bodytype_a) "man" else "woman" + chatNpc(happy, "Well, you're a braver $gender than I!") + chatPlayer(quiz, "Why is everyone so scared of this dragon?") + chatNpc( + sad, + "Back in my father's day, Crandor was an important city-state. " + + "Politically it was as important as Falador and Varrock and its ships " + + "traded with every port.", + ) + chatNpc( + sad, + "But, one day, when I was little, all contact was lost. The trading ships " + + "and the diplomatic envoys just stopped coming.", + ) + chatNpc( + sad, + "I remember my father being very scared. He posted lookouts on the roof " + + "to warn if the dragon was approaching. All the city rulers worried " + + "that Elvarg would devastate the whole continent.", + ) + when ( + choice2( + "So, are you going to give me the shield or not?", + 1, + "I'd better leave that dragon alone.", + 2, + ) + ) { + 1 -> giveAntiDragonShield(finishedDragonSlayer = false) + 2 -> leaveDragonAlone() + } + } + 2 -> leaveDragonAlone() + } + } + 2 -> { + chatPlayer(quiz, "Oh, no dragon in particular. I just feel like killing a dragon.") + if (player.dragonQuest >= 2) { + chatNpc( + happy, + "Of course. Now you've slain Elvarg, you've earned the right to call the " + + "shield your own!", + ) + giveAntiDragonShield(finishedDragonSlayer = true) + } else { + chatNpc( + angry, + "I don't have an infinite supply of these shields, you know. I'll only give " + + "one for a truly worthy cause.", + ) + } + } + } + } + + private suspend fun Dialogue.leaveDragonAlone() { + chatPlayer(sad, "I'd better leave that dragon alone.") + chatNpc( + happy, + "That's a relief. I would hate to see such a promising adventurer cut down in " + + "${if (player.appearance.bodyType == Constants.bodytype_a) "his" else "her"} prime.", + ) + } + + private suspend fun Dialogue.giveAntiDragonShield(finishedDragonSlayer: Boolean) { + if (!finishedDragonSlayer) { + chatPlayer(quiz, "So, are you going to give me the shield or not?") + chatNpc( + happy, + "If you really think you're up to it then perhaps you are the one who can kill this dragon.", + ) + } + if (!player.inv.contains(ANTI_DRAGON_SHIELD)) { + if (access.invAdd(access.inv, ANTI_DRAGON_SHIELD).failure) { + chatNpc(sad, "You don't have enough inventory space for the shield.") + return + } + } + mesbox("The Duke hands you a heavy orange shield.") + if (!finishedDragonSlayer) { + chatNpc(worried, "Take care out there. If you kill it...") + chatNpc(angry, "If you kill it, for Saradomin's sake make sure it's really dead!") + } + } + + private fun hasAirTalisman(player: Player): Boolean = + player.inv.contains(RuneMysteriesQuest.AIR_TALISMAN) + + private companion object { + const val ANTI_DRAGON_SHIELD = "obj.antidragonbreathshield" + } +} diff --git a/content/areas/city/varrock/build.gradle.kts b/content/areas/city/varrock/build.gradle.kts index 91319a398..0aa93d3d0 100644 --- a/content/areas/city/varrock/build.gradle.kts +++ b/content/areas/city/varrock/build.gradle.kts @@ -7,6 +7,8 @@ dependencies { implementation(projects.api.pluginCommons) implementation(projects.api.repo) implementation(projects.api.script) + implementation(projects.content.quest) + implementation(projects.content.skills.runecrafting) implementation(projects.engine.game) implementation(projects.engine.map) } diff --git a/content/areas/city/varrock/src/main/kotlin/org/rsmod/content/areas/city/varrock/npcs/Aubury.kt b/content/areas/city/varrock/src/main/kotlin/org/rsmod/content/areas/city/varrock/npcs/Aubury.kt new file mode 100644 index 000000000..4c9895cfb --- /dev/null +++ b/content/areas/city/varrock/src/main/kotlin/org/rsmod/content/areas/city/varrock/npcs/Aubury.kt @@ -0,0 +1,244 @@ +package org.rsmod.content.areas.city.varrock.npcs + +import jakarta.inject.Inject +import org.rsmod.api.config.Constants +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.output.UpdateRun +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.api.script.onOpNpc3 +import org.rsmod.api.script.onOpNpc4 +import org.rsmod.api.shops.Shops +import org.rsmod.content.quest.area.lumbridge.RuneMysteriesQuest +import org.rsmod.content.quest.area.lumbridge.rmNotes +import org.rsmod.content.skills.runecrafting.essence.teleportToRuneEssenceMine +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +class Aubury @Inject constructor( + private val shops: Shops, + private val runeMysteries: RuneMysteriesQuest, +) : PluginScript() { + + private val quest + get() = runeMysteries.quest + + override fun ScriptContext.startup() { + onOpNpc1("npc.aubury") { startAuburyDialogue(it.npc) } + onOpNpc3("npc.aubury") { player.openAuburyShop(it.npc) } + onOpNpc4("npc.aubury") { teleportToRuneEssenceMine(it.npc) } + } + + private suspend fun ProtectedAccess.startAuburyDialogue(npc: Npc) { + startDialogue(npc) { auburyDialogue(npc) } + } + + private suspend fun Dialogue.auburyDialogue(npc: Npc) { + val stage = quest.getQuestStage(player) + when { + stage in RuneMysteriesQuest.STAGE_PACKAGE..RuneMysteriesQuest.STAGE_AWAITING_NOTES -> + auburyDeliverPackage(npc) + stage == RuneMysteriesQuest.STAGE_NOTES -> auburyNotesFollowUp(npc) + quest.isQuestCompleted(player) -> auburyShopDialogue(npc, includeTeleport = true) + else -> auburyShopDialogue(npc, includeTeleport = false) + } + } + + private suspend fun Dialogue.auburyDeliverPackage(npc: Npc) { + chatNpc(quiz, "Do you want to buy some runes?") + when ( + choice3( + "Yes please!", + 1, + "I've been sent here with a package for you.", + 2, + "Oh, it's a rune shop. No thank you, then.", + 3, + ) + ) { + 1 -> { + chatPlayer(happy, "Yes please!") + player.openAuburyShop(npc) + } + 2 -> { + chatPlayer(happy, "I've been sent here with a package for you.") + chatNpc(quiz, "A package? From who?") + chatPlayer(happy, "From Sedridor at the Wizards' Tower.") + chatNpc( + shocked, + "From Sedridor? But... surely, he can't have? Please, let me have it. It must be " + + "extremely important for him to have sent a stranger.", + ) + if (!player.inv.contains(RuneMysteriesQuest.RESEARCH_PACKAGE)) { + chatPlayer(sad, "Uh... yeah... about that... I kind of don't have it with me...") + chatNpc( + angry, + "What kind of person says they have a delivery for me, but not with them? Honestly.", + ) + chatNpc(angry, "Come back when you have it.") + return + } + if (access.invDel(access.inv, RuneMysteriesQuest.RESEARCH_PACKAGE).failure) { + chatNpc(angry, "Come back when you have it.") + return + } + mesbox("You hand the package to Aubury.") + mesbox("Aubury goes through the package of research notes.") + chatNpc(happy, "Now, let's have a look...") + chatNpc(shocked, "This... this is incredible.") + chatNpc( + happy, + "My gratitude to you adventurer for bringing me these research notes. Thanks to " + + "you, I think we finally have it.", + ) + chatPlayer(quiz, "You mean the incantation?") + chatNpc(quiz, "Well when we combine my own research with this latest discovery, I think we might just...") + chatNpc( + happy, + "No, no, I'm getting ahead of myself. The signs are promising, but let's not jump " + + "to any conclusions just yet.", + ) + chatNpc( + happy, + "Here, take these notes back to Sedridor. They should hopefully give him " + + "everything he needs.", + ) + if (access.invAdd(access.inv, RuneMysteriesQuest.RESEARCH_NOTES).failure) { + if (quest.getQuestStage(player) == RuneMysteriesQuest.STAGE_PACKAGE) { + quest.advanceQuestStage(access) + } + chatNpc(sad, "You don't have space for the notes. Come back when you do.") + return + } + player.rmNotes = true + // STAGE_PACKAGE -> STAGE_NOTES (skip awaiting if notes given immediately) + val stage = quest.getQuestStage(player) + when (stage) { + RuneMysteriesQuest.STAGE_PACKAGE -> + quest.advanceQuestStage( + access, + RuneMysteriesQuest.STAGE_NOTES - RuneMysteriesQuest.STAGE_PACKAGE, + ) + RuneMysteriesQuest.STAGE_AWAITING_NOTES -> quest.advanceQuestStage(access) + } + mesbox("Aubury hands you some research notes.") + chatNpc(happy, "Before you leave, why not have a cup of tea?") + when (choice2("I'd love a cup of tea.", 1, "No, thank you.", 2)) { + 1 -> { + chatPlayer(happy, "I'd love a cup of tea.") + player.runEnergy = Constants.run_max_energy + UpdateRun.energy(player, player.runEnergy) + chatPlayer(happy, "Aaah, nothing like a nice cuppa tea!") + } + 2 -> chatPlayer(quiz, "No, thank you.") + } + } + 3 -> { + chatPlayer(bored, "Oh, it's a rune shop. No thank you, then.") + chatNpc(sad, "Well, if you find someone who does want runes, please send them my way.") + } + } + } + + private suspend fun Dialogue.auburyNotesFollowUp(npc: Npc) { + chatNpc(quiz, "Hello. Did you take those notes back to Sedridor?") + if (!player.inv.contains(RuneMysteriesQuest.RESEARCH_NOTES)) { + chatPlayer(sad, "Sorry, but I lost them.") + if (access.invAdd(access.inv, RuneMysteriesQuest.RESEARCH_NOTES).failure) { + chatNpc(sad, "You don't have space for another copy right now.") + return + } + player.rmNotes = true + chatNpc( + happy, + "Well, luckily I have duplicates. It's a good thing they are written in code. I " + + "wouldn't want the wrong kind of person to get access to the information " + + "contained within.", + ) + mesbox("Aubury hands you some research notes.") + } else { + chatPlayer(happy, "I'm still working on it.") + chatNpc( + happy, + "Don't take too long. He'll be eager to see if this is indeed the breakthrough we " + + "were hoping for.", + ) + chatNpc(quiz, "Now, did you want to buy some runes?") + when ( + choice2( + "Yes please!", + 1, + "No thank you.", + 2, + ) + ) { + 1 -> { + chatPlayer(happy, "Yes please!") + player.openAuburyShop(npc) + } + 2 -> { + chatPlayer(quiz, "No thank you.") + chatNpc(sad, "Well, if you find someone who does want runes, please send them my way.") + } + } + } + } + + private suspend fun Dialogue.auburyShopDialogue(npc: Npc, includeTeleport: Boolean) { + chatNpc(quiz, "Do you want to buy some runes?") + if (includeTeleport) { + when ( + choice3( + "Yes please!", + 1, + "No thank you.", + 2, + "Can you teleport me to the Rune Essence?", + 3, + ) + ) { + 1 -> { + chatPlayer(happy, "Yes please!") + player.openAuburyShop(npc) + } + 2 -> { + chatPlayer(quiz, "No thank you.") + chatNpc(sad, "Well, if you find someone who does want runes, please send them my way.") + } + 3 -> { + chatPlayer(quiz, "Can you teleport me to the Rune Essence?") + chatNpc( + happy, + "Of course. By the way, if you end up making any runes from the essence you " + + "mine, I'll happily buy them from you.", + ) + teleportToRuneEssenceMine() + } + } + } else { + when ( + choice2( + "Yes please!", + 1, + "Oh, it's a rune shop. No thank you, then.", + 2, + ) + ) { + 1 -> { + chatPlayer(happy, "Yes please!") + player.openAuburyShop(npc) + } + 2 -> { + chatPlayer(bored, "Oh, it's a rune shop. No thank you, then.") + chatNpc(sad, "Well, if you find someone who does want runes, please send them my way.") + } + } + } + } + + private fun Player.openAuburyShop(npc: Npc) { + shops.open(this, npc, "Aubury's Rune Shop.", "inv.runeshop") + } +} diff --git a/content/areas/city/varrock/src/main/kotlin/org/rsmod/content/areas/city/varrock/npcs/shops/MagicShop.kt b/content/areas/city/varrock/src/main/kotlin/org/rsmod/content/areas/city/varrock/npcs/shops/MagicShop.kt deleted file mode 100644 index 19c11708b..000000000 --- a/content/areas/city/varrock/src/main/kotlin/org/rsmod/content/areas/city/varrock/npcs/shops/MagicShop.kt +++ /dev/null @@ -1,89 +0,0 @@ -package org.rsmod.content.areas.city.varrock.npcs.shops - -import jakarta.inject.Inject -import org.rsmod.api.player.dialogue.Dialogue -import org.rsmod.api.player.hasAtLeast99s -import org.rsmod.api.player.protect.ProtectedAccess -import org.rsmod.api.player.stat.runecraftingLvl -import org.rsmod.api.script.onOpNpc1 -import org.rsmod.api.script.onOpNpc3 -import org.rsmod.api.shops.Shops -import org.rsmod.game.entity.Npc -import org.rsmod.game.entity.Player -import org.rsmod.plugin.scripts.PluginScript -import org.rsmod.plugin.scripts.ScriptContext - -class MagicShop @Inject constructor(private val shops: Shops) : PluginScript() { - - override fun ScriptContext.startup() { - onOpNpc1("npc.aubury") { shopDialogue(it.npc) } - onOpNpc3("npc.aubury") { player.openMagicShop(it.npc) } - } - - private fun Player.openMagicShop(npc: Npc) { - val shopInv = if (this.runecraftingLvl == 99) { - "inv.runeshop_skillcape" - } else if (this.hasAtLeast99s(2)) { - "inv.runeshop_skillcape_trimmed" - } else { - "inv.runeshop" - } - - shops.open(this, npc, "Aubury's Rune Shop", shopInv) - } - - private suspend fun ProtectedAccess.shopDialogue(npc: Npc) = - startDialogue(npc) { shopKeeper(npc) } - - private suspend fun Dialogue.shopKeeper(npc: Npc) { - chatNpc(happy, "Do you want to buy some runes?") - - val choice = choice3( - "Can you tell me about your cape?", 1, - "Yes please!", 2, - "Oh, it's a rune shop. No thank you, then.", 3, - ) - - when (choice) { - 1 -> skillcape(npc) - 2 -> player.openMagicShop(npc) - 3 -> { - chatPlayer(neutral, "No thank you.") - chatNpc( - neutral, - "Well, if you find someone who does want runes, please send them my way.", - ) - } - } - } - - private suspend fun Dialogue.skillcape(npc: Npc) { - chatNpc( - happy, - "Certainly! Skillcapes are a symbol of achievement. " + - "Only people who have mastered a skill and reached level 99 can get their hands " + - "on them and gain the benefits they carry.", - ) - chatNpc( - happy, - "The Cape of Runecrafting has been upgraded with each talisman, allowing you " + - "to access all Runecrafting altars. Is there anything else I can help you with?", - ) - - val choice = choice2( - "I'd like to view your store please.", 1, - "No thank you.", 2, - ) - - when (choice) { - 1 -> player.openMagicShop(npc) - 2 -> { - chatPlayer(neutral, "No thank you.") - chatNpc( - neutral, - "Well, if you find someone who does want runes, please send them my way.", - ) - } - } - } -} diff --git a/content/areas/misc/wizards_tower/build.gradle.kts b/content/areas/misc/wizards_tower/build.gradle.kts new file mode 100644 index 000000000..20db6ec0b --- /dev/null +++ b/content/areas/misc/wizards_tower/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.pluginCommons) + implementation(projects.content.quest) + implementation(projects.content.skills.runecrafting) +} diff --git a/content/areas/misc/wizards_tower/src/main/kotlin/org/rsmod/content/areas/misc/wizards_tower/WizardTowerLadderScript.kt b/content/areas/misc/wizards_tower/src/main/kotlin/org/rsmod/content/areas/misc/wizards_tower/WizardTowerLadderScript.kt new file mode 100644 index 000000000..1499c6ce0 --- /dev/null +++ b/content/areas/misc/wizards_tower/src/main/kotlin/org/rsmod/content/areas/misc/wizards_tower/WizardTowerLadderScript.kt @@ -0,0 +1,33 @@ +package org.rsmod.content.areas.misc.wizards_tower + +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +class WizardTowerLadderScript : PluginScript() { + override fun ScriptContext.startup() { + onOpLoc1("loc.wizards_tower_laddertop") { climbDown() } + onOpLoc1("loc.wizards_tower_ladder") { climbUp() } + } + + private suspend fun ProtectedAccess.climbDown() { + arriveDelay() + anim("seq.human_pickupfloor") + delay(1) + telejump(Basement) + } + + private suspend fun ProtectedAccess.climbUp() { + arriveDelay() + anim("seq.human_reachforladder") + delay(1) + telejump(GroundFloor) + } + + private companion object { + private val Basement = CoordGrid(3104, 9576) + private val GroundFloor = CoordGrid(3105, 3162) + } +} diff --git a/content/areas/misc/wizards_tower/src/main/kotlin/org/rsmod/content/areas/misc/wizards_tower/npcs/ArchmageSedridor.kt b/content/areas/misc/wizards_tower/src/main/kotlin/org/rsmod/content/areas/misc/wizards_tower/npcs/ArchmageSedridor.kt new file mode 100644 index 000000000..4a64983c8 --- /dev/null +++ b/content/areas/misc/wizards_tower/src/main/kotlin/org/rsmod/content/areas/misc/wizards_tower/npcs/ArchmageSedridor.kt @@ -0,0 +1,545 @@ +package org.rsmod.content.areas.misc.wizards_tower.npcs + +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.api.script.onOpNpc3 +import org.rsmod.content.quest.area.lumbridge.RuneMysteriesQuest +import org.rsmod.content.quest.area.lumbridge.rmBackstory +import org.rsmod.content.quest.area.lumbridge.rmKnowName +import org.rsmod.content.quest.area.lumbridge.rmKnowOthers +import org.rsmod.content.quest.area.lumbridge.rmNotesGiven +import org.rsmod.content.quest.area.lumbridge.rmOwedTalisman +import org.rsmod.content.quest.area.lumbridge.rmPackage +import org.rsmod.content.quest.area.lumbridge.rmTalismanGiven +import org.rsmod.content.skills.runecrafting.essence.teleportToRuneEssenceMine +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +class ArchmageSedridor @Inject constructor(private val runeMysteries: RuneMysteriesQuest) : PluginScript() { + + private val quest + get() = runeMysteries.quest + + private val doubtedIdentity + get() = runeMysteries.doubtedIdentity + + override fun ScriptContext.startup() { + onOpNpc1("npc.head_wizard") { startSedridorDialogue(it.npc) } + onOpNpc3("npc.head_wizard") { teleportToRuneEssenceMine(it.npc) } + } + + private suspend fun ProtectedAccess.startSedridorDialogue(npc: Npc) { + startDialogue(npc) { + sedridorDialogue(npc) + } + } + + private suspend fun Dialogue.sedridorDialogue(npc: Npc) { + when { + quest.isQuestCompleted(player) -> sedridorAfterQuest(npc) + quest.getQuestStage(player) == 0 -> sedridorBeforeQuest(npc) + else -> sedridorDuringQuest(npc) + } + } + + private suspend fun Dialogue.sedridorBeforeQuest(npc: Npc) { + chatNpc( + happy, + "Welcome adventurer, to the world renowned Wizards' Tower, home to the Order of " + + "Wizards. How may I help you?", + ) + chatPlayer(quiz, "I'm just looking around.") + chatNpc( + happy, + "Well, take care adventurer. You stand on the ruins of the old destroyed Wizards' " + + "Tower. Strange and powerful magicks lurk here.", + ) + } + + private suspend fun Dialogue.sedridorDuringQuest(npc: Npc) { + val stage = quest.getQuestStage(player) + when { + stage == RuneMysteriesQuest.STAGE_TALISMAN -> sedridorReceiveTalisman(npc) + stage == RuneMysteriesQuest.STAGE_TALISMAN_GIVEN -> sedridorOfferPackage(npc) + stage == RuneMysteriesQuest.STAGE_PACKAGE -> sedridorPackageFollowUp(npc) + stage == RuneMysteriesQuest.STAGE_AWAITING_NOTES -> { + chatNpc( + quiz, + "Ah, ${player.displayName}. Have you taken my research to Aubury yet?", + ) + chatPlayer(sad, "I delivered the package, but I still need to collect his notes.") + chatNpc(happy, "Then return to Aubury in south-east Varrock and collect them.") + } + stage >= RuneMysteriesQuest.STAGE_NOTES -> sedridorReceiveNotes(npc) + else -> sedridorReceiveTalisman(npc) + } + } + + private suspend fun Dialogue.sedridorReceiveTalisman(npc: Npc) { + val returningWithoutTalisman = + quest.getQuestStage(player) == RuneMysteriesQuest.STAGE_TALISMAN && !hasAirTalisman(player) + + if (returningWithoutTalisman) { + chatNpc(quiz, "Welcome back, adventurer. Do you have that talisman now?") + chatPlayer(sad, "Not yet.") + chatNpc(happy, "Well come back when you have it.") + return + } + + chatNpc( + happy, + "Welcome adventurer, to the world renowned Wizards' Tower, home to the Order of " + + "Wizards. We are the oldest and most prestigious group of wizards around. Now, " + + "how may I help you?", + ) + + chatPlayer(quiz, "Are you Sedridor?") + chatNpc(quiz, "Sedridor? What is it you want with him?") + chatPlayer( + happy, + "The Duke of Lumbridge sent me to find him. I have this talisman he found. He said " + + "Sedridor would be interested in it.", + ) + chatNpc(quiz, "Did he now? Well hand it over then, and we'll see what all the hubbub is about.") + + when ( + choice3( + "Okay, here you are.", + 1, + "No, I'll only give it to Sedridor.", + 2, + "No, I don't think you are Sedridor.", + 3, + ) + ) { + 1 -> handTalismanToSedridor(doubted = false) + 2 -> { + chatPlayer(angry, "No, I'll only give it to Sedridor.") + chatNpc( + happy, + "Well good news, for I am Sedridor! Now, hand it over and let me have a proper " + + "look at it, hmm?", + ) + handTalismanToSedridor(doubted = false) + } + 3 -> { + chatPlayer(angry, "No, I don't think you are Sedridor.") + chatNpc( + quiz, + "Hmm... Well, I admire your caution adventurer. Perhaps I can prove myself? I " + + "will use my mental powers to discover...", + ) + chatNpc(happy, "Your name is... ${player.displayName}!") + chatPlayer(shocked, "You're right! How did you know that?") + chatNpc( + happy, + "Well I am the Archmage you know! You don't get to my position without learning " + + "a few tricks along the way!", + ) + chatNpc(quiz, "So now that I have proved myself to you, why don't you hand over that talisman, hmm?") + chatPlayer(happy, "Okay, here you are.") + doubtedIdentity.set(player, true) + handTalismanToSedridor(doubted = true) + } + } + } + + private suspend fun Dialogue.handTalismanToSedridor(doubted: Boolean) { + if (access.invDel(access.inv, RuneMysteriesQuest.AIR_TALISMAN).failure) { + chatPlayer(happy, "Okay, here you are.") + chatNpc(confused, "...") + chatPlayer(confused, "...") + chatNpc(angry, "Well?") + chatPlayer(sad, "I don't seem to have it with me.") + chatNpc(confused, "Hmm? You are a very odd person. Come back again when you have it.") + return + } + quest.advanceQuestStage(access) + player.rmTalismanGiven = true + mesbox("You hand the talisman to Sedridor.") + mesbox("Sedridor murmurs some sort of incantation and the talisman glows slightly.") + chatNpc( + quiz, + "Hmm... Doesn't seem to be anything too special. Just a normal air talisman by the " + + "looks of things. Still, looks can be deceiving. Let me take a closer look...", + ) + chatNpc( + happy, + "How interesting... It would appear I spoke too soon. There's more to this talisman " + + "than meets the eye. In fact, it may well be the last piece of the puzzle.", + ) + chatPlayer(quiz, "Puzzle?") + chatNpc( + happy, + "Indeed! The lost legacy of the first tower. This talisman may in fact be key to " + + "finding the forgotten essence mine!", + ) + chatPlayer(confused, "First tower? Forgotten essence mine? What are you on about?") + chatNpc(happy, "Ah, my apologies, adventurer. Allow me to fill you in.") + + when ( + choice2( + "Go ahead.", + 1, + "Actually, I'm not interested.", + 2, + ) + ) { + 1 -> sedridorLoreThenPackage(doubted) + 2 -> { + chatPlayer(bored, "Actually, I'm not interested.") + chatNpc( + sad, + "Oh... Well I guess the short of it is that this talisman could be key to " + + "helping us rediscover an important teleportation incantation.", + ) + chatNpc( + happy, + "With it, we'll be able to access a hidden essence mine, our lost source of " + + "rune essence.", + ) + player.rmBackstory = true + sedridorAskDeliverPackage(doubted) + } + } + } + + private suspend fun Dialogue.sedridorLoreThenPackage(doubted: Boolean) { + player.rmBackstory = true + chatPlayer(happy, "Go ahead.") + chatNpc(happy, "As you are likely aware, when we cast spells, we do so using the power of runes.") + chatNpc( + happy, + "These runes are crafted from a highly unique material, and then imbued with magical " + + "power from various runic altars. Different altars create different runes with " + + "different magical effects.", + ) + chatNpc( + happy, + "The process of imbuing runes is called runecrafting. Legend has it that this was once " + + "a common art, but the secrets of how to do it were lost until just under two " + + "hundred years ago.", + ) + chatNpc( + happy, + "The rediscovery of runecrafting had such a large impact on the world, that it marked " + + "the dawn of the Fifth Age. It also resulted in the birth of our order, and the " + + "construction of the first Wizards' Tower.", + ) + chatPlayer(quiz, "If it was the first tower, I'm guessing it doesn't exist anymore? What happened?") + chatNpc( + sad, + "It was burnt down by traitorous members of our own order. They followed the evil god " + + "of chaos, Zamorak, and they wished to claim our magical discoveries in his name.", + ) + chatNpc( + sad, + "When the tower burnt down, much was lost, including an important incantation. A spell " + + "that could be used to teleport to a hidden essence mine.", + ) + chatPlayer(quiz, "The essence mine you mentioned earlier, I assume?") + chatNpc( + happy, + "Precisely. Rune essence is the material used to make runes, but it is incredibly rare. " + + "That essence mine was the only place it could be found that our order knew of.", + ) + chatNpc(sad, "Since the incantation was lost, we have struggled to maintain our stocks of rune essence.") + chatNpc( + sad, + "There are seemingly those out there that still know where to find some, but while they " + + "have been willing to sell essence to us, they have refused to share knowledge on " + + "how to find it ourselves.", + ) + chatPlayer( + quiz, + "I'm starting to see why this is so important. So you think this talisman can help you " + + "rediscover that incantation?", + ) + chatNpc( + happy, + "I do! All magic leaves traces, and from what I can tell, this talisman was used heavily " + + "during the time of the first tower.", + ) + chatNpc( + happy, + "It would have been taken to the essence mine many times, and the magical energies there " + + "will have left an imprint on it. To think that it was hidden in Lumbridge all this time!", + ) + chatPlayer(quiz, "So what happens now?") + sedridorAskDeliverPackage(doubted) + } + + private suspend fun Dialogue.sedridorAskDeliverPackage(doubted: Boolean) { + chatNpc( + happy, + "It is critical I share this discovery with my associate, Aubury, as soon as possible. " + + "He's not much of a wizard, but he's an expert on runecrafting, and his insight will " + + "be essential.", + ) + chatNpc( + quiz, + "Would you be willing to visit him for me? I would go myself, but I wish to study this " + + "talisman some more.", + ) + when (choice2("Yes, certainly.", 1, "No, I'm busy.", 2)) { + 1 -> giveResearchPackage(doubted) + 2 -> { + chatPlayer(sad, "No, I'm busy.") + chatNpc( + sad, + "As you wish adventurer. I will continue to study this talisman you have brought " + + "me. Return here if you find yourself with some spare time to help me.", + ) + } + } + } + + private suspend fun Dialogue.sedridorOfferPackage(npc: Npc) { + chatPlayer(quiz, "So is that talisman of any use to you?") + sedridorAskDeliverPackage(doubtedIdentity.get(player)) + } + + private suspend fun Dialogue.giveResearchPackage(doubted: Boolean) { + chatPlayer(happy, "Yes, certainly.") + if (access.invAdd(access.inv, RuneMysteriesQuest.RESEARCH_PACKAGE).failure) { + chatNpc( + sad, + "You don't have enough inventory space for the package. Come back when you do.", + ) + return + } + if (quest.getQuestStage(player) == RuneMysteriesQuest.STAGE_TALISMAN_GIVEN) { + quest.advanceQuestStage(access) + } + player.rmPackage = true + chatNpc( + happy, + "He runs a rune shop in the south east of Varrock. Please, take this package of research " + + "notes to him. If all goes well, the secrets of the essence mine may soon be ours " + + "once more!", + ) + mesbox("Sedridor hands you a package.") + chatNpc(happy, "Best of luck, ${player.displayName}.") + if (!doubted) { + player.rmKnowName = true + chatPlayer(confused, "I don't remember telling you my name... How do you know it?") + chatNpc(happy, "Really now? I am the Archmage you know.") + } + } + + private suspend fun Dialogue.sedridorPackageFollowUp(npc: Npc) { + chatNpc(quiz, "Hello again, adventurer. Did you take that package to Aubury?") + if (!player.inv.contains(RuneMysteriesQuest.RESEARCH_PACKAGE)) { + chatPlayer(sad, "I lost it. Could I have another?") + if (access.invAdd(access.inv, RuneMysteriesQuest.RESEARCH_PACKAGE).failure) { + chatNpc(sad, "You don't have space for another right now.") + return + } + player.rmPackage = true + chatNpc(happy, "Well it's a good job I have copies of everything.") + mesbox("Sedridor hands you a package.") + chatNpc(happy, "Best of luck, ${player.displayName}.") + if (!doubtedIdentity.get(player)) { + player.rmKnowName = true + chatPlayer(confused, "I don't remember telling you my name... How do you know it?") + chatNpc(happy, "Really now? I am the Archmage you know.") + } + } else { + chatPlayer(sad, "Not yet.") + chatNpc( + happy, + "He runs a rune shop in the south east of Varrock. Please deliver it to him soon.", + ) + } + } + + private suspend fun Dialogue.sedridorReceiveNotes(npc: Npc) { + chatNpc( + happy, + "Ah, ${player.displayName}. How goes your quest? Have you delivered my research to Aubury yet?", + ) + chatPlayer(happy, "Yes, I have. He gave me some notes to give to you.") + chatNpc(happy, "Wonderful! Let's have a look then.") + + if (access.invDel(access.inv, RuneMysteriesQuest.RESEARCH_NOTES).failure) { + chatPlayer(sad, "Err, you're not going to believe this...") + chatNpc(angry, "What?") + chatPlayer(sad, "I don't have them.") + chatNpc( + angry, + "Right... You're rather careless aren't you. I suggest you go and speak to Aubury " + + "once more. With luck he will have made copies.", + ) + return + } + + player.rmNotesGiven = true + mesbox("You hand the notes to Sedridor.") + chatNpc(happy, "Alright, let's see what Aubury has for us...") + chatNpc(happy, "Yes, this is it! The lost incantation!") + chatPlayer(quiz, "So you'll be able to access that essence mine now?") + chatNpc( + happy, + "That's right! Because of you, our order finally has a proper source of rune essence " + + "again! Thank you, friend.", + ) + chatNpc( + happy, + "If you ever want to access the essence mine yourself, just let me know. It's the least " + + "I can do.", + ) + chatNpc( + happy, + "I will also share the incantation with others, including Aubury. When I do, I'll let " + + "them know that you are to be given unlimited access to the mine.", + ) + chatNpc( + happy, + "Oh, and you can have this air talisman back as well. I have no further need of it, and " + + "I'm sure you will find it useful.", + ) + if (access.invAdd(access.inv, RuneMysteriesQuest.AIR_TALISMAN).failure) { + player.rmOwedTalisman = true + chatNpc( + sad, + "Ah - your inventory is full. Take care of that and come speak to me again for the talisman.", + ) + } else { + player.rmOwedTalisman = false + } + chatNpc( + happy, + "In case you didn't know, the talisman can be used to craft air runes. Just take it to " + + "the Air Altar south of Falador along with some rune essence.", + ) + chatNpc( + happy, + "Don't worry if you can't find the altar. The talisman can guide you there. You may " + + "find talismans for other altars as well while adventuring. They'll let you craft " + + "other types of rune.", + ) + chatPlayer(happy, "Great! Thanks!") + chatNpc(happy, "My pleasure!") + + // Advance remaining stages up to completion (notes stage -> complete). + val remaining = quest.maxSteps - quest.getQuestStage(player) + if (remaining > 0) { + quest.advanceQuestStage(access, remaining) + } + + when (choice2("I'd better get going.", 1, "Can you teleport me to the Rune Essence Mine?", 2)) { + 1 -> chatPlayer(happy, "I'd better get going.") + 2 -> teleportToRuneEssenceMine() + } + } + + private suspend fun Dialogue.sedridorAfterQuest(npc: Npc) { + chatPlayer(happy, "Hello there.") + chatNpc(happy, "Hello again, ${player.displayName}. What can I do for you?") + if (player.rmOwedTalisman) { + giveOwedAirTalisman() + } + sedridorAfterQuestOptions() + } + + private suspend fun Dialogue.giveOwedAirTalisman() { + chatNpc( + happy, + "Ah, before I forget - I still have that air talisman for you.", + ) + if (access.invAdd(access.inv, RuneMysteriesQuest.AIR_TALISMAN).failure) { + chatNpc( + sad, + "Your inventory is still full. Free up some space and speak to me again.", + ) + return + } + player.rmOwedTalisman = false + objbox(RuneMysteriesQuest.AIR_TALISMAN, "Sedridor hands you an air talisman.") + chatNpc(happy, "There you are. I'm sure you will find it useful.") + } + + private suspend fun Dialogue.sedridorAfterQuestOptions() { + when ( + choice4( + "Can you teleport me to the Rune Essence Mine?", + 1, + "Who else knows the teleport to the Rune Essence Mine?", + 2, + "Could you tell me about the old Wizards' Tower?", + 3, + "Nothing thanks, I'm just looking around.", + 4, + ) + ) { + 1 -> teleportToRuneEssenceMine() + 2 -> { + chatPlayer(quiz, "Who else knows the teleport to the Rune Essence Mine?") + player.rmKnowOthers = true + chatNpc( + happy, + "Apart from myself, there's also Aubury in Varrock, Wizard Cromperty in East " + + "Ardougne, Brimstail in the Tree Gnome Stronghold and Wizard Distentor in " + + "Yanille's Wizards' Guild.", + ) + sedridorAfterQuestOptions() + } + 3 -> { + chatPlayer(quiz, "Could you tell me about the old Wizards' Tower?") + chatNpc( + happy, + "Of course. The first Wizards' Tower was built at the same time the Order of " + + "Wizards was founded. It was at the dawn of the Fifth Age, when the secrets " + + "of runecrafting were rediscovered.", + ) + chatNpc( + happy, + "For years, the tower was a hub of magical research. Wizards of all races and " + + "religions were welcomed into our order.", + ) + chatNpc( + sad, + "Alas, that openness is what ultimately led to disaster. The wizards who served " + + "Zamorak, the evil god of chaos, tried to claim our magical discoveries in " + + "his name.", + ) + chatNpc( + sad, + "They failed, but in retaliation, they burnt the entire tower to the ground. " + + "Years of work was destroyed.", + ) + chatNpc( + happy, + "The tower was soon rebuilt of course, but even now we are still trying to regain " + + "knowledge that was lost.", + ) + chatNpc( + happy, + "That's why I spend my time down here, in fact. This basement is all that is left " + + "of the old tower, and I believe there are still some secrets to discover here.", + ) + chatNpc( + happy, + "Of course, one secret I am no longer looking for is the teleportation " + + "incantation to the Rune Essence Mine. We have you to thank for that.", + ) + sedridorAfterQuestOptions() + } + 4 -> { + chatPlayer(quiz, "Nothing thanks, I'm just looking around.") + chatNpc( + happy, + "Well, take care. You stand on the ruins of the old destroyed Wizards' Tower. " + + "Strange and powerful magicks lurk here.", + ) + } + } + } + + private fun hasAirTalisman(player: Player): Boolean = + player.inv.contains(RuneMysteriesQuest.AIR_TALISMAN) +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/RuneMysteries.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/RuneMysteries.kt new file mode 100644 index 000000000..d184ff556 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/RuneMysteries.kt @@ -0,0 +1,112 @@ +package org.rsmod.content.quest.area.lumbridge + +import jakarta.inject.Singleton +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.content.quest.manager.ItemRewardDisplay +import org.rsmod.content.quest.manager.QuestProgressState +import org.rsmod.content.quest.manager.QuestScript +import org.rsmod.content.quest.manager.rewards +import org.rsmod.plugin.scripts.ScriptContext + +@Singleton +class RuneMysteriesQuest : QuestScript( + "quest_runemysteries", "varp.runemysteries", rewards { + extra("Access to the Rune Essence Mine") }, + ItemRewardDisplay(AIR_TALISMAN) +) { + + val doubtedIdentity = quest.attribute(name = "DOUBTED_IDENTITY", default = false) + + override fun ScriptContext.init() { + + } + + override fun subTitle(): String = + "talking to