From 7860d8a27f8969de12e44c7e787aa50c6e30253b Mon Sep 17 00:00:00 2001 From: aidan Date: Wed, 21 Jan 2026 07:54:06 -0600 Subject: [PATCH 01/45] structure --- .../quest/elementalworkshop2/BeatenBook.kt | 4 + .../quest/elementalworkshop2/EW2Listeners.kt | 33 ++++++++ .../elementalworkshop2/ElementalWorkshop2.kt | 77 +++++++++++++++++++ .../JunctionBoxInterface.kt | 4 + 4 files changed, 118 insertions(+) create mode 100644 Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt create mode 100644 Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt create mode 100644 Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt create mode 100644 Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt new file mode 100644 index 000000000..481d92f11 --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt @@ -0,0 +1,4 @@ +package content.region.kandarin.seers.quest.elementalworkshop2 + +class BeatenBook { +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt new file mode 100644 index 000000000..866cf47aa --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -0,0 +1,33 @@ +package content.region.kandarin.seers.quest.elementalworkshop2 + +import content.data.Quests +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2Varbit +import core.api.* +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.skill.Skills +import org.rs09.consts.* + +class EW2Listeners : InteractionListener { + + override fun defineListeners() { + + // Bookcase at digsite where you start quest. + on(Scenery.BOOKCASE_17382, IntType.SCENERY, "search") { player, _ -> + if (isQuestComplete(player, Quests.ELEMENTAL_WORKSHOP_I) && + !inInventory(player, Items.BEATEN_BOOK_9717) && + hasLevelStat(player, Skills.MAGIC, 20) && + hasLevelStat(player, Skills.SMITHING, 30) + ) { + sendItemDialogue(player, Items.BEATEN_BOOK_9717, "You find an old-looking book.") + addItemOrDrop(player, Items.BEATEN_BOOK_9717) + //setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 1) + setVarbit(player, EW2Varbit, 1, true) + } else { + sendMessage(player, "You search the books...") + sendMessage(player, "You find nothing of interest.") + } + return@on true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt new file mode 100644 index 000000000..5a37ce5b9 --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt @@ -0,0 +1,77 @@ +package content.region.kandarin.seers.quest.elementalworkshop2 + +import content.data.Quests +import core.api.* +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.quest.Quest +import core.game.node.entity.skill.Skills +import org.rs09.consts.* + +/** + * ELEMENTAL WORKSHOP II QUEST + * + * Requirements: 20 Magic, 30 Smithing, completion of Elemental Workshop I + * Primary Sources: https://www.youtube.com/watch?v=YKSWisRgGZk, https://www.youtube.com/watch?v=xoDkqRhus4Y + */ + +class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Varbit, 0, 1, 11) { + + companion object { + const val EW2Varbit = 2639 + } + + /** + * Quest Stages and Varp values: + * https://chisel.weirdgloop.org/varbs/display?varbit=2639 + * + * val stage desc. + * 0 0 unstarted + * 1 1 found book + * 2 2 read book + * 3 3 + * 4 4 + * 5 5 + * 6 6 + * 7 7 + * 8 8 + * 9 9 + * 10 10 + * 11 100 quest complete + */ + override fun drawJournal(player: Player, stage: Int) { + super.drawJournal(player, stage) + var line = 12 + var stage = getStage(player) + + var started = getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) > 0 + + // journal here + } + + override fun reset(player: Player?) { + super.reset(player) + } + + override fun finish(player: Player) { + var ln = 10 + super.finish(player) + player.packetDispatch.sendString("You have completed the Elemental Workshop II quest!", 277, 4) + player.packetDispatch.sendItemZoomOnInterface(Items.ELEMENTAL_HELMET_9729, 230, 277, 5) + + drawReward(player, "1 Quest Point", ln++) + drawReward(player, "7,500 Smithing XP,", ln++) + drawReward(player, "7,500 Crafting XP,", ln++) + drawReward(player, "Ability to make and equip", ln++) + drawReward(player, "elemental mind equipment", ln) + + setVarbit(player, EW2Varbit, 11, true) + rewardXP(player, Skills.SMITHING, 7500.0) + rewardXP(player, Skills.CRAFTING, 7500.0) + + // remove attributes + } + + override fun newInstance(`object`: Any?): Quest { + return this + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt new file mode 100644 index 000000000..bb6916ba1 --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt @@ -0,0 +1,4 @@ +package content.region.kandarin.seers.quest.elementalworkshop2 + +class JunctionBoxInterface { +} \ No newline at end of file From 118f995bef53a19a988502b2f3abfd316c9707f1 Mon Sep 17 00:00:00 2001 From: aidan Date: Wed, 21 Jan 2026 15:00:50 -0600 Subject: [PATCH 02/45] lever logic --- .../quest/elementalworkshop/EWListeners.kt | 163 +++++++--- .../quest/elementalworkshop2/BeatenBook.kt | 225 +++++++++++++- .../quest/elementalworkshop2/EW2Listeners.kt | 285 +++++++++++++++++- .../elementalworkshop2/ElementalWorkshop2.kt | 135 +++++++-- 4 files changed, 752 insertions(+), 56 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt index f43ac6bed..acc48c6f6 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt @@ -215,8 +215,7 @@ class EWListeners : InteractionListener { /* * * * * * * * * * * * * * * * * Workshop Area listeners * * * * * * * * * * * * * * * */ - // Center hatch, inaccessible - // NOTE: REMOVE ME/ADD CORRECT CHECKS FOR ELEMENTAL WORKSHOP II + // Center hatch, inaccessible until unlocked during EW2 on(Scenery.HATCH_18595, IntType.SCENERY, "open") { player, _ -> sendDialogue(player, "You're unable to open the locked hatch.") return@on true @@ -258,41 +257,135 @@ class EWListeners : InteractionListener { } // Workbenches/Anvil + // Before completing EW1 you can only smith and elemental shield. During EW2 you have the option to make a shield, claw, or helm. onUseWith(IntType.SCENERY, Items.ELEMENTAL_METAL_2893, Scenery.WORKBENCH_3402) { player, used, workbench -> - // Warn player their smithing level is too low to make the shield - if (player.skills.getLevel(Skills.SMITHING) < 20) { - sendMessage(player, "You need a smithing level of 20 to create an elemental shield.") - return@onUseWith true - } - // Warn player they don't have the required book in their inventory - if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.SLASHED_BOOK_9715, Items.BATTERED_BOOK_2886))) { - sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") - return@onUseWith true - } - // Warn player they don't have a hammer in their inventory - if (!inInventory(player, Items.HAMMER_2347)) { - sendDialogue(player, "You don't have a tool available to shape the metal.") - return@onUseWith true - } - // Sanity error check (Should never get thrown) - if (!player.inventory.containsAll(*elementalShieldReqItems)) { - log(this::class.java, Log.ERR, "${player.username} tried to forge an elemental shield without all the required items.") - return@onUseWith false - } - // Successfully smith the elemental shield - lock(player, (animationDuration(smithElementalShield) + 2)) - runTask(player) { - face(player, workbench) - animate(player, smithElementalShield) - playAudio(player, smithElementalShieldSFX) - replaceSlot(player, used.asItem().slot, elementalShield) - rewardXP(player, Skills.SMITHING, 20.0) - sendMessage(player, "Following the instructions in the book you make an elemental shield.") - } - // Check to see if the quest is completed, if not, complete the quest - if (!player.questRepository.getQuest(Quests.ELEMENTAL_WORKSHOP_I).isCompleted(player)) { - player.questRepository.getQuest(Quests.ELEMENTAL_WORKSHOP_I).finish(player) + // if elemental workshop II is in progress, display a dialogue. otherwise, revert to smithing the elemental shield only. + if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) >= 5) { + sendDialogueOptions(player, "What would you like to make...", "An elemental shield.", "An elemental claw.", "An elemental helm.") + addDialogueAction(player) { _, buttonId -> + when (buttonId) { + 2 -> { // ele shield + // Warn player their smithing level is too low to make the shield + if (player.skills.getLevel(Skills.SMITHING) < 20) { + sendMessage(player, "You need a smithing level of 20 to create an elemental shield.") + return@addDialogueAction + } + // Warn player they don't have the required book in their inventory + if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.SLASHED_BOOK_9715, Items.BATTERED_BOOK_2886))) { + sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") + return@addDialogueAction + } + // Warn player they don't have a hammer in their inventory + if (!inInventory(player, Items.HAMMER_2347)) { + sendDialogue(player, "You don't have a tool available to shape the metal.") + return@addDialogueAction + } + // Successfully smith the elemental shield + lock(player, (animationDuration(smithElementalShield) + 2)) + runTask(player) { + face(player, workbench) + animate(player, smithElementalShield) + playAudio(player, smithElementalShieldSFX) + replaceSlot(player, used.asItem().slot, elementalShield) + rewardXP(player, Skills.SMITHING, 20.0) + sendMessage(player, "Following the instructions in the book you make an elemental shield.") + } + } + 3 -> { // ele claw + // Warn player their smithing level is too low to make the shield + if (player.skills.getLevel(Skills.SMITHING) < 20) { + sendMessage(player, "You need a smithing level of 20 to create an elemental claw.") + return@addDialogueAction + } + // Warn player they don't have the required book in their inventory + if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.CRANE_SCHEMATIC_9718))) { + sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") + return@addDialogueAction + } + // Warn player they don't have a hammer in their inventory + if (!inInventory(player, Items.HAMMER_2347)) { + sendDialogue(player, "You don't have a tool available to shape the metal.") + return@addDialogueAction + } + // Successfully smith the elemental shield + lock(player, (animationDuration(smithElementalShield) + 2)) + runTask(player) { + face(player, workbench) + animate(player, smithElementalShield) + playAudio(player, smithElementalShieldSFX) + replaceSlot(player, used.asItem().slot, Item(Items.CRANE_CLAW_9720)) + rewardXP(player, Skills.SMITHING, 20.0) + sendMessage(player, "Following the instructions from the schematic you make an elemental claw.") + } + } + 4 -> { // ele helm + // Warn player their smithing level is too low to make the helm + if (player.skills.getLevel(Skills.SMITHING) < 30) { + sendMessage(player, "You need a smithing level of 30 to create an elemental helm.") + return@addDialogueAction + } + // Warn player they don't have the required book in their inventory + if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.BEATEN_BOOK_9717))) { + sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") + return@addDialogueAction + } + // Warn player they don't have a hammer in their inventory + if (!inInventory(player, Items.HAMMER_2347)) { + sendDialogue(player, "You don't have a tool available to shape the metal.") + return@addDialogueAction + } + // Successfully smith the elemental helm + lock(player, (animationDuration(smithElementalShield) + 2)) + runTask(player) { + face(player, workbench) + animate(player, smithElementalShield) + playAudio(player, smithElementalShieldSFX) + replaceSlot(player, used.asItem().slot, Item(Items.ELEMENTAL_HELMET_9729)) + rewardXP(player, Skills.SMITHING, 20.0) + sendMessage(player, "Following the instructions in the book you make an elemental helm.") + } + } + } + } + + // EW1 fallback + } else { + // Warn player their smithing level is too low to make the shield + if (player.skills.getLevel(Skills.SMITHING) < 20) { + sendMessage(player, "You need a smithing level of 20 to create an elemental shield.") + return@onUseWith true + } + // Warn player they don't have the required book in their inventory + if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.SLASHED_BOOK_9715, Items.BATTERED_BOOK_2886))) { + sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") + return@onUseWith true + } + // Warn player they don't have a hammer in their inventory + if (!inInventory(player, Items.HAMMER_2347)) { + sendDialogue(player, "You don't have a tool available to shape the metal.") + return@onUseWith true + } + // Sanity error check (Should never get thrown) + if (!player.inventory.containsAll(*elementalShieldReqItems)) { + log(this::class.java, Log.ERR, "${player.username} tried to forge an elemental shield without all the required items.") + return@onUseWith false + } + // Successfully smith the elemental shield + lock(player, (animationDuration(smithElementalShield) + 2)) + runTask(player) { + face(player, workbench) + animate(player, smithElementalShield) + playAudio(player, smithElementalShieldSFX) + replaceSlot(player, used.asItem().slot, elementalShield) + rewardXP(player, Skills.SMITHING, 20.0) + sendMessage(player, "Following the instructions in the book you make an elemental shield.") + } + // Check to see if the quest is completed, if not, complete the quest + if (!player.questRepository.getQuest(Quests.ELEMENTAL_WORKSHOP_I).isCompleted(player)) { + player.questRepository.getQuest(Quests.ELEMENTAL_WORKSHOP_I).finish(player) + } } + return@onUseWith true } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt index 481d92f11..cafc8526f 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt @@ -1,4 +1,227 @@ package content.region.kandarin.seers.quest.elementalworkshop2 -class BeatenBook { +import content.data.Quests +import content.global.handlers.iface.* +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2Varbit +import core.game.interaction.InteractionListener +import core.api.* +import core.game.interaction.IntType +import core.game.interaction.QueueStrength +import core.game.node.entity.player.Player +import org.rs09.consts.Items + +/** + * Starts the Elemental Workshop II Quest + * Found at Dig Site Library + */ + +class BeatenBook : InteractionListener { + companion object { + private const val TITLE = "Book of the Elemental Helm" + private val CONTENTS = arrayOf( + // pages 1-2 + PageSet( + Page( + BookLine("This is the second book in", 55), + BookLine("a series which aims to", 56), + BookLine("preserve the knowledge", 57), + BookLine("gained by artisans of the", 58), + BookLine("early Fifth Age. A", 59), + BookLine("thorough grasp of the", 60), + BookLine("processes and techniques", 61), + BookLine("described in my earlier", 62), + BookLine("work, 'Book of the", 63), + BookLine("Elemental Shield', is", 64), + BookLine("necessary to comprehend", 65), + ), + Page( + BookLine("the concepts involved in", 66), + BookLine("the more technical", 67), + BookLine("chapters of this book.", 68), + BookLine("", 69), + BookLine("", 70), + BookLine("", 71), + BookLine("", 72), + BookLine("", 73), + BookLine("", 74), + BookLine("", 75), + BookLine("", 76), + ) + ), + // pages 3-4 + PageSet( + Page( + BookLine("Some years following the", 55), + BookLine("original discovery of the", 56), + BookLine("elemental ore, further", 57), + BookLine("investigation was", 58), + BookLine("performed by a", 59), + BookLine("metallurgist called", 60), + BookLine("Vitruvius in collaboration", 61), + BookLine("with a freelance mage.", 62), + BookLine("Over the course of two", 63), + BookLine("years they did many", 64), + BookLine("tests on the ore and", 65), + ), + Page( + BookLine("refined metal, aiming to", 66), + BookLine("optimise the process by", 67), + BookLine("which the metal was", 68), + BookLine("extracted from its impure", 69), + BookLine("ore. From the data they", 70), + BookLine("gathered, they discovered", 71), + BookLine("the optimum temperature", 72), + BookLine("and carbon/ore ratio for", 73), + BookLine("the production of", 74), + BookLine("elemental metal of the", 75), + BookLine("highest quality. The", 76), + ) + ), + // pages 5-6 + PageSet( + Page( + BookLine("quality of the resulting", 55), + BookLine("metal was assessed on the", 56), + BookLine("basis of its ultimate tensile", 57), + BookLine("strength, hardness (as", 58), + BookLine("compared to a standard", 59), + BookLine("diamond-tipped probe),", 60), + BookLine("magical absorption", 61), + BookLine("capacity per unit mass,", 62), + BookLine("grain size and elastic", 63), + BookLine("deformation modulus.", 64), + BookLine("Their most significant", 65), + ), + Page( + BookLine("discovery in this period,", 66), + BookLine("however, was the novel", 67), + BookLine("technique of 'priming' the", 68), + BookLine("ore.", 69), + BookLine("", 70), + BookLine("", 71), + BookLine("", 72), + BookLine("", 73), + BookLine("", 74), + BookLine("", 75), + BookLine("", 76), + ) + ), + // pages 7-8 + PageSet( + Page( + BookLine("At the time, the", 55), + BookLine("significance of priming", 56), + BookLine("was not fully appreciated.", 57), + BookLine("However, the records kept", 58), + BookLine("by Vitruvius state that his", 59), + BookLine("apprentice, in an", 60), + BookLine("unsuccessful attempt to", 61), + BookLine("animate a small ingot of", 62), + BookLine("elemental metal as part of", 63), + BookLine("a student prank,", 64), + BookLine("accidentally instilled his", 65), + ), + Page( + BookLine("entire consciousness into", 66), + BookLine("the primed ingot. An", 67), + BookLine("unfortunate side-effect of", 68), + BookLine("this process was to", 69), + BookLine("reduce the apprentice's", 70), + BookLine("body to a dead shell.", 71), + BookLine("Attempts to return his", 72), + BookLine("consciousness to his body", 73), + BookLine("were regrettably", 74), + BookLine("unsuccessful.", 75), + BookLine("", 76), + ) + ), + // pages 9-10 + PageSet( + Page( + BookLine("Vitruvius discovered that", 55), + BookLine("the mental energy was so", 56), + BookLine("firmly bound to the", 57), + BookLine("elemental metal that no", 58), + BookLine("dissipation was detectable.", 59), + BookLine("Similar experiments with", 60), + BookLine("unprimed bars initially", 61), + BookLine("yielded comparable", 62), + BookLine("results, but the mental", 63), + BookLine("energy quickly left the", 64), + BookLine("bars and dissipated within", 65), + ), + Page( + BookLine("such a short period that", 66), + BookLine("no practical applications", 67), + BookLine("seemed feasible. However", 68), + BookLine("the primed elemental", 69), + BookLine("metal showed considerably", 70), + BookLine("more promise.", 71), + BookLine("", 72), + BookLine("", 73), + BookLine("", 74), + BookLine("", 75), + BookLine("", 76), + ) + ), + // pages 11-12 + PageSet( + Page( + BookLine("Researchers have", 55), + BookLine("continued to use the", 56), + BookLine("terminology defined by", 57), + BookLine("Vitruvius for this field of", 58), + BookLine("study. In his own", 59), + BookLine("notebooks, Vitruvius", 60), + BookLine("referred to his", 61), + BookLine("preparation technique as", 62), + BookLine("'priming', and he referred", 63), + BookLine("to his apprentice as 'Idiot'", 64), + BookLine("", 65), + ), + Page( + BookLine("", 66), + BookLine("", 67), + BookLine("", 68), + BookLine("", 69), + BookLine("", 70), + BookLine("", 71), + BookLine("", 72), + BookLine("", 73), + BookLine("", 74), + BookLine("", 75), + BookLine("", 76), + ) + ), + ) + } + + private fun display(player: Player, pageNum: Int, buttonID: Int): Boolean { + BookInterface.pageSetup(player, BookInterface.FANCY_BOOK_3_49, TITLE, CONTENTS) + return true + } + + override fun defineListeners() { + on(Items.BEATEN_BOOK_9717, IntType.ITEM, "read") { player, _ -> + // the first time you open the book, the scroll will drop. + if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 1) { + addItemOrDrop(player, Items.SCROLL_9721) + sendMessage(player, "The book has two parts; an introduction and an instruction section.") + sendMessage(player, "You flip the book open to the introduction and start reading.") + + // todo the dialogue and openbook should run sequentially, but i've messed around with queuescript and can't figure it out. + // this also applies to EW1 listener that gives the slashed book and battered key back in EWListeners.kt + sendItemDialogue(player, Items.SCROLL_9721, "You find a slip of paper that has been used for a bookmark. You put it in your pack to study later.") + BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) + + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 2) + setVarbit(player, EW2Varbit, 2, true) + // storeBookInHouse(player, node) // todo uncomment this when poh costume room is in + } else { + // storeBookInHouse(player, node) // todo uncomment this when poh costume room is in + BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) + } + return@on true + } + } } \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 866cf47aa..732a1a7d2 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -1,11 +1,20 @@ package content.region.kandarin.seers.quest.elementalworkshop2 import content.data.Quests +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CranePos +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CraneState +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2HatchVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2MachineryVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2Varbit import core.api.* import core.game.interaction.IntType import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills +import core.game.node.scenery.SceneryBuilder +import core.game.world.map.Location +import core.game.world.map.RegionManager +import core.game.world.map.build.LandscapeParser.removeScenery import org.rs09.consts.* class EW2Listeners : InteractionListener { @@ -16,18 +25,290 @@ class EW2Listeners : InteractionListener { on(Scenery.BOOKCASE_17382, IntType.SCENERY, "search") { player, _ -> if (isQuestComplete(player, Quests.ELEMENTAL_WORKSHOP_I) && !inInventory(player, Items.BEATEN_BOOK_9717) && + !inBank(player, Items.BEATEN_BOOK_9717) && hasLevelStat(player, Skills.MAGIC, 20) && hasLevelStat(player, Skills.SMITHING, 30) ) { sendItemDialogue(player, Items.BEATEN_BOOK_9717, "You find an old-looking book.") addItemOrDrop(player, Items.BEATEN_BOOK_9717) - //setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 1) - setVarbit(player, EW2Varbit, 1, true) + + if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 0) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 1) + setVarbit(player, EW2Varbit, 1, true) + } } else { sendMessage(player, "You search the books...") sendMessage(player, "You find nothing of interest.") } return@on true } + + // the scroll that drops with the beaten book + on(Items.SCROLL_9721, IntType.ITEM, "read") { player, _ -> + openInterface(player, 222) + setInterfaceText(player, "", 222, 1) + setInterfaceText(player, "Down", 222, 2) + setInterfaceText(player, "2 N", 222, 3) + setInterfaceText(player, "2 E", 222, 4) + setInterfaceText(player, "2 N", 222, 5) + setInterfaceText(player, "2 E", 222, 6) + setInterfaceText(player, "10 N", 222, 7) + setInterfaceText(player, "4 W", 222, 8) + setInterfaceText(player, "Pipe 3.", 222, 9) + + if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 2) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 3) + setVarbit(player, EW2Varbit, 3, true) + } + if(getVarbit(player, EW2MachineryVarbit) == 0) + setVarbit(player, EW2MachineryVarbit, 1, true) + return@on true + } + + // machinery where you find the key. + on(Scenery.MACHINERY_18594, IntType.SCENERY, "search") { player, _ -> + if(!inInventory(player, Items.KEY_9722)) { + sendItemDialogue(player, Items.KEY_9722, "You find a key hidden in the third pipe.") + addItemOrDrop(player, Items.KEY_9722) + } + if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 3) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 4) + setVarbit(player, EW2Varbit, 4) + } + return@on true + } + + // unlocking the hatch + onUseWith(IntType.SCENERY, Items.KEY_9722, Scenery.HATCH_18595) { player, _, _ -> + sendItemDialogue(player, Items.KEY_9722, "The key fits the lock in the hatch perfectly.") + setVarbit(player, EW2HatchVarbit, 1, true) + return@onUseWith true + } + + // stairs back up to EW1 + on(Scenery.STAIRS_18598, IntType.SCENERY, "climb-up") { player, _ -> + teleport(player, location(2720, 9892, 0)) + sendMessage(player, "You climb up the stairs.") + return@on true + } + + // stairs to lower level + on(Scenery.STAIRWELL_18597, IntType.SCENERY, "climb-down") { player, _ -> + teleport(player, location(1948, 5158, 0)) + sendMessage(player, "You climb down the stairs.") + return@on true + } + + // stairs back up to EW2 + on(Scenery.STAIRS_18599, IntType.SCENERY, "climb-up") { player, _ -> + teleport(player, location(1948, 5157, 2)) + sendMessage(player, "You climb up the stairs.") + return@on true + } + + // large cog crate + on(Scenery.CRATE_18616, IntType.SCENERY, "search") { player, _ -> + if (!inInventory(player, Items.LARGE_COG_9724)) { + addItemOrDrop(player, Items.LARGE_COG_9724) + sendMessage(player, "You find a big cog.") + } else { + sendMessage(player, "You search the crate but find nothing.") + } + return@on true + } + + // pipe crate + on(Scenery.CRATE_18619, IntType.SCENERY, "search") { player, _ -> + if (!inInventory(player, Items.PIPE_9723)) { + addItemOrDrop(player, Items.PIPE_9723) + sendMessage(player, "You find a pipe.") + } else { + sendMessage(player, "You search the crate but find nothing.") + } + return@on true + } + + // schematic crate + on(Scenery.SCHEMATIC_CRATE_18711, IntType.SCENERY, "take-from") { player, _ -> + sendDialogueOptions(player, "There are two schematics here...", "Crane schematic", "Lever schematic") + addDialogueAction(player) { _, buttonId -> + when (buttonId) { + 2 -> { + if(!inInventory(player, Items.CRANE_SCHEMATIC_9718)){ + addItemOrDrop(player, Items.CRANE_SCHEMATIC_9718) + sendItemDialogue(player, Items.CRANE_SCHEMATIC_9718, "You take a copy of the schematic drawing.") + } + else sendDialogue(player, "You already have this schematic.") + } + 3 -> { + if(!inInventory(player, Items.LEVER_SCHEMATIC_9719)){ + addItemOrDrop(player, Items.LEVER_SCHEMATIC_9719) + sendItemDialogue(player, Items.LEVER_SCHEMATIC_9719, "You take a copy of the schematic drawing.") + } + else sendDialogue(player, "You already have this schematic.") + } + } + } + return@on true + } + + // crane schematic + on(Items.CRANE_SCHEMATIC_9718, IntType.ITEM, "read") { player, _ -> + sendDialogue(player, "The schematic drawing details the construction and maintenance of a mechanical arm. One of the sections you notice is an explanation of how to make an elemental claw.") + return@on true + } + + // lever schematic + on(Items.LEVER_SCHEMATIC_9719, IntType.ITEM, "read") { player, _ -> + openInterface(player, 466) + return@on true + } + + // various states the crane can be in. they are a pattern, so the logic to flip and lower can be generalized. + val craneStates = intArrayOf( + Scenery.OLD_CRANE_18623, // fixed crane facing north, claw up + Scenery.OLD_CRANE_18624, // fixed crane facing north, claw down + Scenery.OLD_CRANE_18625, // fixed crane facing south, claw up + Scenery.OLD_CRANE_18626, // fixed crane facing south, claw down + Scenery.OLD_CRANE_18627, // fixed crane with ele bar facing north, claw up + Scenery.OLD_CRANE_18628, // fixed crane with ele bar facing north, claw down + Scenery.OLD_CRANE_18629, // fixed crane with ele bar facing south, claw up + Scenery.OLD_CRANE_18630, // fixed crane with ele bar facing south, claw down + Scenery.OLD_CRANE_18631, // fixed crane with mind bar facing north, claw up + Scenery.OLD_CRANE_18632, // fixed crane with mind bar facing north, claw down + Scenery.OLD_CRANE_18633, // fixed crane with mind bar facing south, claw up + Scenery.OLD_CRANE_18634, // fixed crane with mind bar facing south, claw down + Scenery.OLD_CRANE_18635, // broken crane facing north, claw up + Scenery.OLD_CRANE_18636, // broken crane facing north, claw down + Scenery.OLD_CRANE_18637, // broken crane facing south, claw up + Scenery.OLD_CRANE_18638, // broken crane facing south, claw down + ) + + val CraneStates = intArrayOf( + // Varbit 2644 = 0: Broken + Scenery.OLD_CRANE_18638, // broken crane facing south, claw down + Scenery.OLD_CRANE_18637, // broken crane facing south, claw up + Scenery.OLD_CRANE_18636, // broken crane facing north, claw down + Scenery.OLD_CRANE_18635, // broken crane facing north, claw up + + // Varbit 2644 = 1: Fixed/Empty (18623=N-Up, 18624=N-Down, 18625=S-Up, 18626=S-Down) + Scenery.OLD_CRANE_18626, // fixed crane facing south, claw down + Scenery.OLD_CRANE_18625, // fixed crane facing south, claw up + Scenery.OLD_CRANE_18624, // fixed crane facing north, claw down + Scenery.OLD_CRANE_18623, // fixed crane facing north, claw up + + // Varbit 2644 = 2: Elemental Bar + Scenery.OLD_CRANE_18630, // fixed crane with ele bar facing south, claw down + Scenery.OLD_CRANE_18629, // fixed crane with ele bar facing south, claw up + Scenery.OLD_CRANE_18628, // fixed crane with ele bar facing north, claw down + Scenery.OLD_CRANE_18627, // fixed crane with ele bar facing north, claw up + + // Varbit 2644 = 3: Mind Bar + Scenery.OLD_CRANE_18634, // fixed crane with mind bar facing south, claw down + Scenery.OLD_CRANE_18633, // fixed crane with mind bar facing south, claw up + Scenery.OLD_CRANE_18632, // fixed crane with mind bar facing north, claw down + Scenery.OLD_CRANE_18631 // fixed crane with mind bar facing north, claw up + ) + + // function to find my crane because it exists at some corner instead of the center. + fun findCraneInArea(center: Location): core.game.node.scenery.Scenery? { + // Search an area around the center + for (x in -2..2) { + for (y in -2..2) { + val checkLoc = center.transform(x, y, 0) + for (id in craneStates) { + val found = RegionManager.getObject(checkLoc.z, checkLoc.x, checkLoc.y, id) + if (found != null) return found + } + } + } + return null + } + + // position the crane based on varbit states + fun syncCrane(player: Player) { + val mat = getVarbit(player, EW2CraneState) + val pos = getVarbit(player, EW2CranePos) + + val index = (mat * 4) + pos + val newId = CraneStates[index] + + val craneLoc = Location.create(1954, 5145, 2) + val currentCrane = findCraneInArea(craneLoc) + + if (currentCrane != null) { + replaceScenery(currentCrane, newId, -1) + } + } + + // west lever by crane. moves crane up and down. + on(Scenery.AN_OLD_LEVER_18622, IntType.SCENERY, "pull") { player, _ -> + val currentPos = getVarbit(player, EW2CranePos) + val nextPos = currentPos xor 1 + setVarbit(player, EW2CranePos, nextPos, true) + animate(player, Animations.HUMAN_PULL_LEVER_4909) + syncCrane(player) + return@on true + } + + // east lever by crane. rotates crane north and south + on(Scenery.AN_OLD_LEVER_18621, IntType.SCENERY, "pull") { player, _ -> + val currentPos = getVarbit(player,EW2CranePos) + + // crane must be up to rotate + if (currentPos == 0 || currentPos == 2) { + sendMessage(player, "The crane is too low to rotate.") + return@on true + } + + val nextPos = currentPos xor 2 + + setVarbit(player, EW2CranePos, nextPos, true) + animate(player, Animations.HUMAN_PULL_LEVER_4909) + syncCrane(player) + return@on true + } + + // fix crane + onUseWith(IntType.SCENERY, Items.CRANE_CLAW_9720, Scenery.OLD_CRANE_18636) { player, used, with -> + // check if already repaired + if(getVarbit(player, EW2CraneState) != 0) { + sendMessage(player, "The crane is already repaired.") + return@onUseWith true + } + sendItemDialogue(player, used, "You replace the broken claw.") + removeItem(player, used) + + setVarbit(player, EW2CraneState, 1, true) + syncCrane(player) + return@onUseWith true + } + + // unlocked center hatch down to EW2 + on(Scenery.HATCH_18596, IntType.SCENERY, "climb-down") { player, _ -> + sendMessage(player, "You climb down the stairs.") + teleport(player, location(1955, 5155, 2)) + + // set the crane state when you go down based on the varbits + // if you log in and out during a server restart it'll reset the crane, but just reenter the workshop to fix it. + syncCrane(player) + + if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 4) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 5) + setVarbit(player, EW2Varbit, 5) + } + return@on true + } + } + + // fixes to get right interact locations + override fun defineDestinationOverrides() { + setDest(IntType.SCENERY, intArrayOf(Scenery.OLD_CRANE_18636), "use") { _, _ -> + return@setDest Location.create(1954, 5148, 2) + } + + setDest(IntType.SCENERY, intArrayOf(Scenery.PIPING_18650), "use") { _, _ -> + return@setDest Location.create(1953, 5167, 3) + } } } \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt index 5a37ce5b9..670e7da02 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt @@ -5,50 +5,149 @@ import core.api.* import core.game.node.entity.player.Player import core.game.node.entity.player.link.quest.Quest import core.game.node.entity.skill.Skills -import org.rs09.consts.* +import core.plugin.Initializable +import org.rs09.consts.Items /** * ELEMENTAL WORKSHOP II QUEST * * Requirements: 20 Magic, 30 Smithing, completion of Elemental Workshop I - * Primary Sources: https://www.youtube.com/watch?v=YKSWisRgGZk, https://www.youtube.com/watch?v=xoDkqRhus4Y + * Primary Source: https://www.youtube.com/watch?v=YKSWisRgGZk */ +@Initializable class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Varbit, 0, 1, 11) { companion object { - const val EW2Varbit = 2639 + const val ELEMENTALWORKSHOP2VARP = 896 // All of these varbits are packaged into varp 896. + const val EW2Varbit = 2639 // Main quest progress. Incremented with quest stages. + const val EW2MachineryVarbit = 2640 // The machinery you have to search for the key. Set to 1 after you read the scroll. + const val EW2HatchVarbit = 2641 // The hatch down to EW2. Set to 1 after you unlock it with the key. + //const val EW2 = 2642 // north lever + const val EW2JigCart = 2643 + const val EW2CraneState = 2644 // Crane State. 0 = Broken, 1 = Fixed/Empty, 2 = Elemental Bar, 3 = Mind Bar. + const val EW2CranePos = 2645 // Crane Position. 0 = South Down, 1 = South Up, 2 = North Down, 3 = North Up. + //const val EW2 = 2646 // junction box + //const val EW2 = 2647 // junction box + //const val EW2 = 2648 // junction box + //const val EW2 = 2649 // junction box + const val EW2PipeVarbit = 2650 + //const val EW2 = 2651 // water valve west + //const val EW2 = 2652 // cork screw lever and water valve east } /** - * Quest Stages and Varp values: + * Quest Stages and Varbit values: * https://chisel.weirdgloop.org/varbs/display?varbit=2639 * * val stage desc. * 0 0 unstarted - * 1 1 found book - * 2 2 read book - * 3 3 - * 4 4 - * 5 5 - * 6 6 - * 7 7 - * 8 8 - * 9 9 - * 10 10 - * 11 100 quest complete + * 1 1 found the beaten book + * 2 2 read the beaten book + * 3 3 read the beaten book's scroll + * 4 4 found key + * 5 5 went down center hatch + * 6 6 read the crane schematic + * 7 7 read the lever schematic? + * 8 8 went through the mind door + * 9 9 primed a mind bar + * 10 10 primed a mind bar? + * 11 100 smithed a mind helmet, quest complete */ override fun drawJournal(player: Player, stage: Int) { super.drawJournal(player, stage) var line = 12 var stage = getStage(player) - var started = getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) > 0 + // unstarted + if (stage == 0) { + line(player, "I can start this quest by reading a", line++) + line(player, "!!book?? found at the !!Dig Site??.", line++) + line++ + line(player,"To start this quest I will need:", line++) + line(player, "Level 20 Magic", line++, hasLevelStat(player, Skills.MAGIC, 20)) + line(player, "Level 30 Smithing", line++, hasLevelStat(player, Skills.SMITHING, 30)) + line(player,"I must have completed the following quests:", line++) // After - I have completed all of the required quests: + line(player, "Elemental Workshop I", line++, isQuestComplete(player, Quests.ELEMENTAL_WORKSHOP_I)) + } - // journal here + // stage 1, found the beaten book + if (stage >= 1) { + line(player, "I have found a book at the Dig Site.", line++, true) + // in-progress stage text + if(stage == 1) line(player, "I should read the book.", line++) + line++ + } + + // stage 2, read the beaten book + if (stage >= 2) { + line(player, "The book tells of a way to !!make elemental items??", line++, true) + line(player, "even more !!powerful?? than before.", line++, true) + // in-progress stage text + if(stage == 2) line(player, "I should read the scroll.", line++) + line++ + } + + // stage 3, read the scroll from the beaten book + if (stage >= 3) { + line(player, "The note found with the book has a strange code written on it.", line++, stage > 3) + // in-progress stage text + //if(stage == 3) line(player, "", line++) + line++ + } + + // stage 4, finding the key + if (stage >= 4) { + line(player, "By following the code on the !!note?? I was able to !!find a key??.", line++, stage > 4) + line++ + } + + // stage 5, going down the center hatch + if (stage >= 5) { + line(player, "The !!key?? I've found opens the !!large hatch?? in the !!workshop??; there are !!4 machines?? on this level to fix:", line++, stage > 5) + line(player, "The crane - now the claw has been replaced the crane looks like it's in good working order.", line++, stage > 5) + line(player, "The pipes - the pipes have now been fitted and it should work as long as it's powered.", line++, stage > 5) + line(player, "The tank - this should work fine now I have replaced the pipe.", line++, stage > 5) + line(player, "A wind tunnel - I have fitted all the cogs on to the pins. The win tunnel should now work as long as it's powered.", line++, stage > 5) + line(player, "A wind tunnel - I have fitted all the cogs on to the pins. The win tunnel should now work as long as it's powered.", line++, stage > 5) + + line(player, "I have found a track-mounted jig on which I was able to clamp an elemental bar.", line++, stage > 5) + line(player, "I used the press to flatten the hot elemental bar.", line++, stage > 5) + line(player, "The water tank was used to cool the elemental bar down.", line++, stage > 5) + line(player, "The wind tunnel proved a great way to dry off the wet bar.", line++, stage > 5) + line(player, "I have primed a bar of elemental ore.", line++, stage > 5) + line(player, "I placed the primed bar on the device found in the room with the mind symbols on the door.", line++, stage > 5) + line(player, "Operating the device charged the primed bar with some of my own mind power.", line++, stage > 5) + line(player, "I have made an elemental mind bar, now I just have to make one of the helms from it.", line++, stage > 5) + line(player, "Creating the elemental mind helm was easy with the book for instruction.", line++, stage > 5) + line(player, "", line++, stage > 5) + line++ + } + + // stage 6 + if (stage >= 6) { + line(player, "", line++, stage > 6) + line++ + } + + // stage 7 + if (stage >= 7) { + line(player, "", line++, stage > 7) + line++ + } + + // stage 8 + if (stage >= 8) { + line(player, "", line++, stage > 8) + line++ + } } - override fun reset(player: Player?) { + // use ::setqueststage 53 0 to reset quest. + override fun reset(player: Player) { + // this varp wipes all the varbits + setVarp(player, ELEMENTALWORKSHOP2VARP, 0, true) + player.getQuestRepository().syncronizeTab(player) super.reset(player) } From aeb673cc927fa88ca28928e6800d4d92b7aa71e8 Mon Sep 17 00:00:00 2001 From: aidan Date: Wed, 21 Jan 2026 19:35:30 -0600 Subject: [PATCH 03/45] some cart logic --- .../quest/elementalworkshop2/EW2Listeners.kt | 333 +++++++++++++----- .../elementalworkshop2/ElementalWorkshop2.kt | 9 +- 2 files changed, 251 insertions(+), 91 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 732a1a7d2..bb7bfe0d5 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -1,14 +1,18 @@ package content.region.kandarin.seers.quest.elementalworkshop2 import content.data.Quests +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CartLever import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CranePos import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CraneState import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2HatchVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2JigCart import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2MachineryVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2PipeVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2Varbit import core.api.* import core.game.interaction.IntType import core.game.interaction.InteractionListener +import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills import core.game.node.scenery.SceneryBuilder @@ -19,6 +23,115 @@ import org.rs09.consts.* class EW2Listeners : InteractionListener { + companion object { + + // various states the crane can be in. they are a pattern, so the logic to flip and lower can be generalized. + val craneStates = intArrayOf( + // Varbit 2644 = 0: Broken + Scenery.OLD_CRANE_18638, // broken crane facing south, claw down + Scenery.OLD_CRANE_18637, // broken crane facing south, claw up + Scenery.OLD_CRANE_18636, // broken crane facing north, claw down + Scenery.OLD_CRANE_18635, // broken crane facing north, claw up + + // Varbit 2644 = 1: Fixed/Empty (18623=N-Up, 18624=N-Down, 18625=S-Up, 18626=S-Down) + Scenery.OLD_CRANE_18626, // fixed crane facing south, claw down + Scenery.OLD_CRANE_18625, // fixed crane facing south, claw up + Scenery.OLD_CRANE_18624, // fixed crane facing north, claw down + Scenery.OLD_CRANE_18623, // fixed crane facing north, claw up + + // Varbit 2644 = 2: Elemental Bar + Scenery.OLD_CRANE_18630, // fixed crane with ele bar facing south, claw down + Scenery.OLD_CRANE_18629, // fixed crane with ele bar facing south, claw up + Scenery.OLD_CRANE_18628, // fixed crane with ele bar facing north, claw down + Scenery.OLD_CRANE_18627, // fixed crane with ele bar facing north, claw up + + // Varbit 2644 = 3: Mind Bar + Scenery.OLD_CRANE_18634, // fixed crane with mind bar facing south, claw down + Scenery.OLD_CRANE_18633, // fixed crane with mind bar facing south, claw up + Scenery.OLD_CRANE_18632, // fixed crane with mind bar facing north, claw down + Scenery.OLD_CRANE_18631 // fixed crane with mind bar facing north, claw up + ) + + // cart state is stored in varbit 2643 + val jigCartStates = intArrayOf( + NPCs.JIG_CART_4913, // empty + NPCs.JIG_CART_4914, // ele bar + NPCs.JIG_CART_4915, // mind bar + NPCs.JIG_CART_4916, // smashed mind bar + NPCs.JIG_CART_4917, // smashed ele bar + NPCs.JIG_CART_4918, // smashed white bar + ) + + val jigCartLocations = arrayOf( + Location.create(1954, 5147, 2), // south (start) + Location.create(1946, 5155, 2), // west (hammer) + Location.create(1953, 5162, 2), // north (water) + Location.create(1961, 5155, 2) // east (fans) + ) + + // function to find my crane because it exists at some corner instead of the center. + fun findCraneInArea(center: Location): core.game.node.scenery.Scenery? { + // Search an area around the center + for (x in -2..2) { + for (y in -2..2) { + val checkLoc = center.transform(x, y, 0) + for (id in craneStates) { + val found = RegionManager.getObject(checkLoc.z, checkLoc.x, checkLoc.y, id) + if (found != null) return found + } + } + } + return null + } + + // position the crane based on varbit states + fun syncCrane(player: Player) { + val mat = getVarbit(player, EW2CraneState) + val pos = getVarbit(player, EW2CranePos) + + val index = (mat * 4) + pos + val newId = craneStates[index] + + val craneLoc = Location.create(1954, 5145, 2) + val currentCrane = findCraneInArea(craneLoc) + + if (currentCrane != null) { + // todo it would be nice to animate this crane + replaceScenery(currentCrane, newId, -1) + } + } + + // update the jig cart based on varbit status + fun syncCart(player: Player) { + val cartState = getVarbit(player, EW2JigCart) + val cartPosIndex = getVarbit(player, EW2CartLever) + + val targetId = jigCartStates[cartState] + val targetLocation = jigCartLocations[cartPosIndex] + + // find existing cart + val trackCenter = Location.create(1953, 5155, 2) + val existingCart = RegionManager.getLocalNpcs(trackCenter, 15).find { + it.id in jigCartStates + } + + if (existingCart != null) { + // update appearance + if (existingCart.id != targetId) { + existingCart.transform(targetId) + } + // update location + if (existingCart.location != targetLocation) { + existingCart.teleport(targetLocation) + } + } else { + // Spawn if missing + val newCart = NPC(targetId, targetLocation) + newCart.init() + } + } + } + override fun defineListeners() { // Bookcase at digsite where you start quest. @@ -85,6 +198,23 @@ class EW2Listeners : InteractionListener { return@onUseWith true } + // unlocked center hatch down to EW2 + on(Scenery.HATCH_18596, IntType.SCENERY, "climb-down") { player, _ -> + sendMessage(player, "You climb down the stairs.") + teleport(player, location(1955, 5155, 2)) + + // set the crane state and cart state when you go down based on the varbits + // if you log in and out during a server restart it'll reset the crane, but just reenter the workshop to fix it. + syncCrane(player) + syncCart(player) + + if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 4) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 5) + setVarbit(player, EW2Varbit, 5) + } + return@on true + } + // stairs back up to EW1 on(Scenery.STAIRS_18598, IntType.SCENERY, "climb-up") { player, _ -> teleport(player, location(2720, 9892, 0)) @@ -106,6 +236,10 @@ class EW2Listeners : InteractionListener { return@on true } + // todo small cog crate + + // todo medium cog crate + // large cog crate on(Scenery.CRATE_18616, IntType.SCENERY, "search") { player, _ -> if (!inInventory(player, Items.LARGE_COG_9724)) { @@ -164,87 +298,40 @@ class EW2Listeners : InteractionListener { return@on true } - // various states the crane can be in. they are a pattern, so the logic to flip and lower can be generalized. - val craneStates = intArrayOf( - Scenery.OLD_CRANE_18623, // fixed crane facing north, claw up - Scenery.OLD_CRANE_18624, // fixed crane facing north, claw down - Scenery.OLD_CRANE_18625, // fixed crane facing south, claw up - Scenery.OLD_CRANE_18626, // fixed crane facing south, claw down - Scenery.OLD_CRANE_18627, // fixed crane with ele bar facing north, claw up - Scenery.OLD_CRANE_18628, // fixed crane with ele bar facing north, claw down - Scenery.OLD_CRANE_18629, // fixed crane with ele bar facing south, claw up - Scenery.OLD_CRANE_18630, // fixed crane with ele bar facing south, claw down - Scenery.OLD_CRANE_18631, // fixed crane with mind bar facing north, claw up - Scenery.OLD_CRANE_18632, // fixed crane with mind bar facing north, claw down - Scenery.OLD_CRANE_18633, // fixed crane with mind bar facing south, claw up - Scenery.OLD_CRANE_18634, // fixed crane with mind bar facing south, claw down - Scenery.OLD_CRANE_18635, // broken crane facing north, claw up - Scenery.OLD_CRANE_18636, // broken crane facing north, claw down - Scenery.OLD_CRANE_18637, // broken crane facing south, claw up - Scenery.OLD_CRANE_18638, // broken crane facing south, claw down - ) - - val CraneStates = intArrayOf( - // Varbit 2644 = 0: Broken - Scenery.OLD_CRANE_18638, // broken crane facing south, claw down - Scenery.OLD_CRANE_18637, // broken crane facing south, claw up - Scenery.OLD_CRANE_18636, // broken crane facing north, claw down - Scenery.OLD_CRANE_18635, // broken crane facing north, claw up - - // Varbit 2644 = 1: Fixed/Empty (18623=N-Up, 18624=N-Down, 18625=S-Up, 18626=S-Down) - Scenery.OLD_CRANE_18626, // fixed crane facing south, claw down - Scenery.OLD_CRANE_18625, // fixed crane facing south, claw up - Scenery.OLD_CRANE_18624, // fixed crane facing north, claw down - Scenery.OLD_CRANE_18623, // fixed crane facing north, claw up - - // Varbit 2644 = 2: Elemental Bar - Scenery.OLD_CRANE_18630, // fixed crane with ele bar facing south, claw down - Scenery.OLD_CRANE_18629, // fixed crane with ele bar facing south, claw up - Scenery.OLD_CRANE_18628, // fixed crane with ele bar facing north, claw down - Scenery.OLD_CRANE_18627, // fixed crane with ele bar facing north, claw up - - // Varbit 2644 = 3: Mind Bar - Scenery.OLD_CRANE_18634, // fixed crane with mind bar facing south, claw down - Scenery.OLD_CRANE_18633, // fixed crane with mind bar facing south, claw up - Scenery.OLD_CRANE_18632, // fixed crane with mind bar facing north, claw down - Scenery.OLD_CRANE_18631 // fixed crane with mind bar facing north, claw up - ) - - // function to find my crane because it exists at some corner instead of the center. - fun findCraneInArea(center: Location): core.game.node.scenery.Scenery? { - // Search an area around the center - for (x in -2..2) { - for (y in -2..2) { - val checkLoc = center.transform(x, y, 0) - for (id in craneStates) { - val found = RegionManager.getObject(checkLoc.z, checkLoc.x, checkLoc.y, id) - if (found != null) return found - } - } - } - return null - } - - // position the crane based on varbit states - fun syncCrane(player: Player) { - val mat = getVarbit(player, EW2CraneState) - val pos = getVarbit(player, EW2CranePos) - - val index = (mat * 4) + pos - val newId = CraneStates[index] - - val craneLoc = Location.create(1954, 5145, 2) - val currentCrane = findCraneInArea(craneLoc) - - if (currentCrane != null) { - replaceScenery(currentCrane, newId, -1) - } - } - // west lever by crane. moves crane up and down. on(Scenery.AN_OLD_LEVER_18622, IntType.SCENERY, "pull") { player, _ -> val currentPos = getVarbit(player, EW2CranePos) val nextPos = currentPos xor 1 + val craneMat = getVarbit(player, EW2CraneState) + val cartState = getVarbit(player, EW2JigCart) + + // facing north: pick up and drop off bar + if (currentPos == 3 && nextPos == 2) { // Moving from North-Up to North-Down + if (craneMat == 1 && (cartState == 1 || cartState == 2)) { + // pick up + setVarbit(player, EW2CraneState, cartState + 1) + setVarbit(player, EW2JigCart, 0) + sendMessage(player, "The crane claw picks up the bar from the cart.") + syncCart(player) + } else if ((craneMat == 2 || craneMat == 3) && cartState == 0) { + // drop off + setVarbit(player, EW2JigCart, craneMat - 1) + setVarbit(player, EW2CraneState, 1) + sendMessage(player, "You lower the bar onto the jig cart.") + syncCart(player) + } + } + + // facing south: process bar + if (currentPos == 1 && nextPos == 0) { + if (craneMat == 2) { + // if holding elemental bar, transform into mind bar + setVarbit(player, EW2CraneState, 3) + sendMessage(player, "As you lower the bar into the machinery, it glows with a strange mind-energy.") + playAudio(player, Sounds.EW2_CRANE_LOWER_3169) + } + } + setVarbit(player, EW2CranePos, nextPos, true) animate(player, Animations.HUMAN_PULL_LEVER_4909) syncCrane(player) @@ -257,7 +344,7 @@ class EW2Listeners : InteractionListener { // crane must be up to rotate if (currentPos == 0 || currentPos == 2) { - sendMessage(player, "The crane is too low to rotate.") + sendMessage(player, "The arm cannot be rotated while the claw is down.") return@on true } @@ -284,19 +371,79 @@ class EW2Listeners : InteractionListener { return@onUseWith true } - // unlocked center hatch down to EW2 - on(Scenery.HATCH_18596, IntType.SCENERY, "climb-down") { player, _ -> - sendMessage(player, "You climb down the stairs.") - teleport(player, location(1955, 5155, 2)) + // opens the junction box + on(Scenery.JUNCTION_BOX_18641, IntType.SCENERY, "open") { player, _ -> + openInterface(player, Components.JUNCTION_BOX_262) + return@on true + } - // set the crane state when you go down based on the varbits - // if you log in and out during a server restart it'll reset the crane, but just reenter the workshop to fix it. - syncCrane(player) - - if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 4) { - setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 5) - setVarbit(player, EW2Varbit, 5) + // take item from the jig cart + on(jigCartStates, IntType.NPC, "take-from") { player, node -> + when(node.id) { + 4914 -> { + addItemOrDrop(player, Items.ELEMENTAL_METAL_2893) + sendItemDialogue(player, Items.ELEMENTAL_METAL_2893, "You take the elemental metal.") + } + 4915 -> { + //addItemOrDrop(player, Items.ELEMENTAL_MIND_BAR_9728) + //sendItemDialogue(player, Items.ELEMENTAL_MIND_BAR_9728, "You take the elemental mind bar.") + sendMessage(player, "The bar is too hot to touch.") + } + 4918 -> { + addItemOrDrop(player, Items.PRIMED_BAR_9727) + sendItemDialogue(player, Items.PRIMED_BAR_9727, "You take the primed bar.") + } } + animate(player, 537) + setVarbit(player, EW2JigCart, 0, true) + syncCart(player) + return@on true + } + + // fix the pipe + onUseWith(IntType.SCENERY, Items.PIPE_9723, Scenery.PIPING_18650) { player, item, _ -> + animate(player, 537) + sendItemDialogue(player, item, "You replace the section of broken pipe.") + playAudio(player, Sounds.EW2_PLACE_PIPE_3179) + setVarbit(player, EW2PipeVarbit, 1, true) + removeItem(player, item) + return@onUseWith true + } + + // put regular bar on the jig cart + onUseWith(IntType.NPC, intArrayOf(Items.ELEMENTAL_METAL_2893), NPCs.JIG_CART_4913) { player, used, _ -> + val cranePos = getVarbit(player, EW2CranePos) + + // if crane is south down, it's blocking the cart. + if (cranePos == 2) { + sendMessage(player, "You cannot place that while the claw is down.") + return@onUseWith true + } + + setVarbit(player, EW2JigCart, 1, true) + animate(player, 537) + syncCart(player) + removeItem(player, used) + return@onUseWith true + } + + on(Scenery.LEVER_18620, IntType.SCENERY, "pull") { player, _ -> + val currentPos = getVarbit(player, EW2CartLever) + val cranePos = getVarbit(player, EW2CranePos) + + // crane must be up + if (currentPos == 0 && cranePos == 2) { + sendMessage(player, "You cannot do that while the claw is down.") + return@on true + } + + // positions cycles 0 -> 1 -> 2 -> 3 -> 0 etc. + val nextPos = (currentPos + 1) % 4 + + animate(player, Animations.HUMAN_PULL_LEVER_4909) + setVarbit(player, EW2CartLever, nextPos, true) + syncCart(player) + return@on true } } @@ -307,6 +454,14 @@ class EW2Listeners : InteractionListener { return@setDest Location.create(1954, 5148, 2) } + setDest(IntType.NPC, intArrayOf(NPCs.JIG_CART_4913), "use") { _, _ -> + return@setDest Location.create(1954, 5148, 2) + } + + setDest(IntType.NPC, intArrayOf(NPCs.JIG_CART_4914, NPCs.JIG_CART_4915), "take-from") { _, _ -> + return@setDest Location.create(1954, 5148, 2) + } + setDest(IntType.SCENERY, intArrayOf(Scenery.PIPING_18650), "use") { _, _ -> return@setDest Location.create(1953, 5167, 3) } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt index 670e7da02..3fbb32315 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt @@ -1,12 +1,17 @@ package content.region.kandarin.seers.quest.elementalworkshop2 import content.data.Quests +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCart +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCrane import core.api.* +import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.player.link.quest.Quest import core.game.node.entity.skill.Skills +import core.game.world.map.Location import core.plugin.Initializable import org.rs09.consts.Items +import org.rs09.consts.NPCs /** * ELEMENTAL WORKSHOP II QUEST @@ -23,8 +28,8 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Var const val EW2Varbit = 2639 // Main quest progress. Incremented with quest stages. const val EW2MachineryVarbit = 2640 // The machinery you have to search for the key. Set to 1 after you read the scroll. const val EW2HatchVarbit = 2641 // The hatch down to EW2. Set to 1 after you unlock it with the key. - //const val EW2 = 2642 // north lever - const val EW2JigCart = 2643 + const val EW2CartLever = 2642 // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east + const val EW2JigCart = 2643 // Jig Cart state. 0 = Empty, 1 = ele bar, 2 = mind bar, 3 = smashed mind, 4 = smashed ele, 5 = primed const val EW2CraneState = 2644 // Crane State. 0 = Broken, 1 = Fixed/Empty, 2 = Elemental Bar, 3 = Mind Bar. const val EW2CranePos = 2645 // Crane Position. 0 = South Down, 1 = South Up, 2 = North Down, 3 = North Up. //const val EW2 = 2646 // junction box From 8e42bdf7188181066380023981f6fe9a56146f70 Mon Sep 17 00:00:00 2001 From: aidan Date: Thu, 22 Jan 2026 14:58:20 -0600 Subject: [PATCH 04/45] holy shit more varbits! --- .../quest/elementalworkshop2/EW2Listeners.kt | 150 +++++++-- .../seers/quest/elementalworkshop2/EW2Zone.kt | 47 +++ .../elementalworkshop2/ElementalWorkshop2.kt | 32 +- .../JunctionBoxInterface.kt | 288 +++++++++++++++++- 4 files changed, 489 insertions(+), 28 deletions(-) create mode 100644 Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index bb7bfe0d5..79a4dbfc9 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -15,14 +15,26 @@ import core.game.interaction.InteractionListener import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills -import core.game.node.scenery.SceneryBuilder import core.game.world.map.Location import core.game.world.map.RegionManager -import core.game.world.map.build.LandscapeParser.removeScenery import org.rs09.consts.* class EW2Listeners : InteractionListener { + /** + * Overall there are 4 stations down here: + * + * 1- crane - takes ele bar. outputs hot ele bar + * 2- anvil - takes hot ele bar. outputs smashed hot bar + * 3- water - takes smashed hot bar. outputs smashed cooled bar + * 4- fan - takes smashed cooled bar. outputs primed bar + * + * each station only accepts one specific state of the jig cart, and only progresses the jig cart by one state. + * + */ + + // todo finish organizing the listeners under their stages + companion object { // various states the crane can be in. they are a pattern, so the logic to flip and lower can be generalized. @@ -56,8 +68,8 @@ class EW2Listeners : InteractionListener { val jigCartStates = intArrayOf( NPCs.JIG_CART_4913, // empty NPCs.JIG_CART_4914, // ele bar - NPCs.JIG_CART_4915, // mind bar - NPCs.JIG_CART_4916, // smashed mind bar + NPCs.JIG_CART_4915, // hot ele bar + NPCs.JIG_CART_4916, // smashed hot bar NPCs.JIG_CART_4917, // smashed ele bar NPCs.JIG_CART_4918, // smashed white bar ) @@ -203,11 +215,6 @@ class EW2Listeners : InteractionListener { sendMessage(player, "You climb down the stairs.") teleport(player, location(1955, 5155, 2)) - // set the crane state and cart state when you go down based on the varbits - // if you log in and out during a server restart it'll reset the crane, but just reenter the workshop to fix it. - syncCrane(player) - syncCart(player) - if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 4) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 5) setVarbit(player, EW2Varbit, 5) @@ -236,9 +243,27 @@ class EW2Listeners : InteractionListener { return@on true } - // todo small cog crate + // small cog crate + on(Scenery.CRATE_18618, IntType.SCENERY, "search") { player, _ -> + if (!inInventory(player, Items.SMALL_COG_9726)) { + addItemOrDrop(player, Items.SMALL_COG_9726) + sendMessage(player, "You find a small cog.") + } else { + sendMessage(player, "It's empty.") + } + return@on true + } - // todo medium cog crate + // medium cog crate + on(Scenery.CRATES_18617, IntType.SCENERY, "search") { player, _ -> + if (!inInventory(player, Items.MEDIUM_COG_9725)) { + addItemOrDrop(player, Items.MEDIUM_COG_9725) + sendMessage(player, "You find a medium-sized cog.") + } else { + sendMessage(player, "It's empty.") + } + return@on true + } // large cog crate on(Scenery.CRATE_18616, IntType.SCENERY, "search") { player, _ -> @@ -246,7 +271,7 @@ class EW2Listeners : InteractionListener { addItemOrDrop(player, Items.LARGE_COG_9724) sendMessage(player, "You find a big cog.") } else { - sendMessage(player, "You search the crate but find nothing.") + sendMessage(player, "It's empty.") } return@on true } @@ -257,7 +282,7 @@ class EW2Listeners : InteractionListener { addItemOrDrop(player, Items.PIPE_9723) sendMessage(player, "You find a pipe.") } else { - sendMessage(player, "You search the crate but find nothing.") + sendMessage(player, "It's empty.") } return@on true } @@ -298,6 +323,12 @@ class EW2Listeners : InteractionListener { return@on true } + /**------------------------ + * STATION 1 - THE CRANE + ** ------------------------ + */ + // requires the elemental claw to be smithed and attached to the crane + // west lever by crane. moves crane up and down. on(Scenery.AN_OLD_LEVER_18622, IntType.SCENERY, "pull") { player, _ -> val currentPos = getVarbit(player, EW2CranePos) @@ -327,7 +358,6 @@ class EW2Listeners : InteractionListener { if (craneMat == 2) { // if holding elemental bar, transform into mind bar setVarbit(player, EW2CraneState, 3) - sendMessage(player, "As you lower the bar into the machinery, it glows with a strange mind-energy.") playAudio(player, Sounds.EW2_CRANE_LOWER_3169) } } @@ -356,6 +386,90 @@ class EW2Listeners : InteractionListener { return@on true } + /**------------------------ + * STATION 2 - THE ANVIL + ** ------------------------ + */ + // requires the junction box interface to be fixed + + // west lever, checks junction box status + on(Scenery.AN_OLD_LEVER_18640, IntType.SCENERY, "pull") { player, _ -> + + if (JunctionBoxInterface.isSolutionCorrect(player)) { + // Jig Cart state. 0 = Empty, 1 = ele bar, 2 = hot bar, 3 = smashed hot, 4 = smashed cooled, 5 = primed + val cartState = getVarbit(player, EW2JigCart) + val cartPos = getVarbit(player, EW2CartLever) + + // check if cart is here + if (cartPos == 1) { + when (cartState) { + 0 -> { // empty + sendDialogue(player, "You pull the lever and the press operates, but there is nothing on the cart to flatten.") + } + // todo animate the hammer smash + 1 -> { // ele bar + setVarbit(player, EW2JigCart, 4) + // "hardy" is the real word used. I thought it was a typo for 'hammer' but it's not. https://en.wikipedia.org/wiki/Hardy_tool + sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is not hot enough to flatten.") + syncCart(player) + } + 2 -> { // hot ele bar + setVarbit(player, EW2JigCart, 3) + // "hardy" is correct. see above. + sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar has been flattened.") + syncCart(player) + } + else -> { // smashed hot, smashed cooled, or primed + sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is already flattened.") + } + } + } else { + // cart in wrong position + sendDialogue(player, "You pull the lever and the press operates, but the jig cart is not underneath it.") + } + } else { + // The junction box is not configured correctly + sendDialogue(player, "You pull the lever but nothing happens.") + } + + return@on true + } + + /**------------------------ + * STATION 2 - WATER + ** ------------------------ + */ + // requires the pipe to be replaced + + // todo north lever + // north lever that opens and closes the door + on(Scenery.AN_OLD_LEVER_18648, IntType.SCENERY, "pull") { player, _ -> + + return@on true + } + + // todo corkscrew lever + // corkscrew lever that grabs and ungrabs the cart as long as the door is open + on(Scenery.CORKSCREW_LEVER_18649, IntType.SCENERY, "turn") { player, _ -> + + return@on true + } + + // todo west water valve + // west water valve fills the chamber if the door is closed "you open the valve" "you close the valve" make sure it's closed or the chamber will fill up again + // changes cart state into flattened mind bar + on(Scenery.WATER_VALVE_18646, IntType.SCENERY, "turn") { player, _ -> + + return@on true + } + + // todo east water valve + // east water valve drains the tank if it is full "you open the valve" + on(Scenery.WATER_VALVE_18647, IntType.SCENERY, "turn") { player, _ -> + + return@on true + } + // fix crane onUseWith(IntType.SCENERY, Items.CRANE_CLAW_9720, Scenery.OLD_CRANE_18636) { player, used, with -> // check if already repaired @@ -383,19 +497,19 @@ class EW2Listeners : InteractionListener { 4914 -> { addItemOrDrop(player, Items.ELEMENTAL_METAL_2893) sendItemDialogue(player, Items.ELEMENTAL_METAL_2893, "You take the elemental metal.") + animate(player, 537) + setVarbit(player, EW2JigCart, 0, true) } 4915 -> { - //addItemOrDrop(player, Items.ELEMENTAL_MIND_BAR_9728) - //sendItemDialogue(player, Items.ELEMENTAL_MIND_BAR_9728, "You take the elemental mind bar.") sendMessage(player, "The bar is too hot to touch.") } 4918 -> { addItemOrDrop(player, Items.PRIMED_BAR_9727) sendItemDialogue(player, Items.PRIMED_BAR_9727, "You take the primed bar.") + animate(player, 537) + setVarbit(player, EW2JigCart, 0, true) } } - animate(player, 537) - setVarbit(player, EW2JigCart, 0, true) syncCart(player) return@on true } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt new file mode 100644 index 000000000..c063a4d51 --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt @@ -0,0 +1,47 @@ +package content.region.kandarin.seers.quest.elementalworkshop2 + +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCart +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCrane +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.game.world.map.zone.MapZone +import core.game.world.map.zone.ZoneBorders +import core.game.world.map.zone.ZoneBuilder +import core.plugin.Plugin + +/** + * This updates the Elemental Workshop II state whenever the player enters + * Controls things like crane state and jig cart state. + */ + +@Initializable +class EW2Zone : MapZone("Elemental Workshop II", true), Plugin { + + + override fun newInstance(arg: Any?): EW2Zone { + ZoneBuilder.configure(this) + return this + } + + override fun configure() { + super.register(ZoneBorders(1936,5128,1982,5172)) + } + + // updates the workshop state based on the varbits for Elemental Workshop II + override fun enter(e: Entity?): Boolean { + + if(e is Player) { + syncCrane(e) + syncCart(e) + } + + return super.enter(e) + } + + override fun fireEvent(identifier: String?, vararg args: Any?): Any { + return Unit + } + + +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt index 3fbb32315..990cf4132 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt @@ -23,22 +23,37 @@ import org.rs09.consts.NPCs @Initializable class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Varbit, 0, 1, 11) { + // a truckload of varbits companion object { - const val ELEMENTALWORKSHOP2VARP = 896 // All of these varbits are packaged into varp 896. + const val ELEMENTALWORKSHOP2VARP = 896 // first EW2 varp that packages varbits 2639-2652 const val EW2Varbit = 2639 // Main quest progress. Incremented with quest stages. const val EW2MachineryVarbit = 2640 // The machinery you have to search for the key. Set to 1 after you read the scroll. const val EW2HatchVarbit = 2641 // The hatch down to EW2. Set to 1 after you unlock it with the key. - const val EW2CartLever = 2642 // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east - const val EW2JigCart = 2643 // Jig Cart state. 0 = Empty, 1 = ele bar, 2 = mind bar, 3 = smashed mind, 4 = smashed ele, 5 = primed + const val EW2CartLever = 2642 // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east + const val EW2JigCart = 2643 // Jig Cart state. 0 = Empty, 1 = ele bar, 2 = hot bar, 3 = smashed hot, 4 = smashed cooled, 5 = primed const val EW2CraneState = 2644 // Crane State. 0 = Broken, 1 = Fixed/Empty, 2 = Elemental Bar, 3 = Mind Bar. const val EW2CranePos = 2645 // Crane Position. 0 = South Down, 1 = South Up, 2 = North Down, 3 = North Up. - //const val EW2 = 2646 // junction box - //const val EW2 = 2647 // junction box - //const val EW2 = 2648 // junction box - //const val EW2 = 2649 // junction box + const val EW2JB1 = 2646 // junction box status + const val EW2JB2 = 2647 // junction box status + const val EW2JB3 = 2648 // junction box status + //const val EW2JB = 2649 // junction box - this overlaps 2646, 2647, 2648 const val EW2PipeVarbit = 2650 //const val EW2 = 2651 // water valve west //const val EW2 = 2652 // cork screw lever and water valve east + + const val ELEMENTALWORKSHOP2VARP2 = 897 // second EW2 varp that packages varbits 2653-2664 + //const val EW2 = 2653 + //const val EW2 = 2654 + //const val EW2 = 2655 // cog pin + //const val EW2 = 2656 // cog pin + //const val EW2 = 2657 // cog pin + //const val EW2 = 2658 + //const val EW2 = 2659 + //const val EW2 = 2660 + //const val EW2 = 2661 + //const val EW2 = 2662 + //const val EW2 = 2663 + //const val EW2 = 2664 } /** @@ -150,8 +165,9 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Var // use ::setqueststage 53 0 to reset quest. override fun reset(player: Player) { - // this varp wipes all the varbits + // these varps wipes all the varbits setVarp(player, ELEMENTALWORKSHOP2VARP, 0, true) + setVarp(player, ELEMENTALWORKSHOP2VARP2, 0, true) player.getQuestRepository().syncronizeTab(player) super.reset(player) } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt index bb6916ba1..e09bb9d1b 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt @@ -1,4 +1,288 @@ package content.region.kandarin.seers.quest.elementalworkshop2 -class JunctionBoxInterface { -} \ No newline at end of file +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2JB1 +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2JB2 +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2JB3 +import core.api.* +import core.game.interaction.InterfaceListener +import core.game.node.entity.player.Player +import org.rs09.consts.Components + +/** + * Junction box in Elemental Workshop II + */ + +class JunctionBoxInterface : InterfaceListener { + + private val junctionBox = Components.JUNCTION_BOX_262 + + // 1317.cs2 controls the visibility of the pipes based on varbit 4835 + private val pipesLeftVarbit = 4835 // 0 = no pipes, 1 = one pipe, 2 = two pipes, 3 = three pipes + + // 1319.cs2 controls the highlighting of the pipe based on varbit 4834 + private val pipeSelectVarbit = + 4834 // 0 = no select, 1 = top left, 2 = top middle, 3 = top right, 4 = bottom left, 5 = bottom middle, 6 = bottom right + + private val firstSelect = EW2JB1 // this is the main quest varbit used to save and recall pipe selection. values 0-15 + private val secondSelect = EW2JB2 // this is the main quest varbit used to save and recall pipe selection. values 0-15 + private val thirdSelect = EW2JB3 // this is the main quest varbit used to save and recall pipe selection. values 0-15 + + // the interface children for displaying each placed pipe + private val pipes = intArrayOf( + 5, // from button1 to button4 + 6, // from button2 to button5 + 7, // from button3 to button6 + + 8, // from button4 to button5 NEEDED TO SOLVE + 9, // from button4 to button6 + 10,// from button5 to button6 + 11,// from button1 to button5 + 12,// from button1 to button6 NEEDED TO SOLVE + + 13,// from button1 to button2 + 14,// from button1 to button3 + + 15,// from button2 to button4 + 16,// from button2 to button6 + + 17,// from button2 to button3 NEEDED TO SOLVE + 18,// from button3 to button4 + + 19,// from button3 to button5 + ) + + // this solution checker is called in the lever listeners + companion object { + /** + * Checks if the three specific pipes required for the puzzle solution are placed. + */ + fun isSolutionCorrect(player: Player): Boolean { + val s1 = getVarbit(player, EW2JB1) + val s2 = getVarbit(player, EW2JB2) + val s3 = getVarbit(player, EW2JB3) + + val requiredIndices = intArrayOf(3, 7, 12) + val currentValues = intArrayOf(s1, s2, s3) + + return requiredIndices.all { required -> + currentValues.contains(required + 1) + } + } + } + + override fun defineInterfaceListeners() { + onOpen(junctionBox) { player, _ -> + val s1 = getVarbit(player, firstSelect) + val s2 = getVarbit(player, secondSelect) + val s3 = getVarbit(player, thirdSelect) + + // Hide all pipes initially + pipes.forEach { setComponentVisibility(player, junctionBox, it, true) } + + // Count how many pipes are already placed to set pipesLeftVarbit + var placedCount = 0 + if (s1 > 0) { + setComponentVisibility(player, junctionBox, pipes[s1 - 1], false); placedCount++ + } + if (s2 > 0) { + setComponentVisibility(player, junctionBox, pipes[s2 - 1], false); placedCount++ + } + if (s3 > 0) { + setComponentVisibility(player, junctionBox, pipes[s3 - 1], false); placedCount++ + } + + setVarbit(player, pipesLeftVarbit, 3 - placedCount) + + // reset any selected + setVarbit(player, pipeSelectVarbit, 0) + + return@onOpen true + } + + /** + * When a button is pressed, we first check to see if the pipeSelect varbit has been set. + * If it's zero, this is our first selection. Record it. + * If it has a value, select the child to display based on the pipeSelect varbit and current button selection. + * After displaying a child, clear pipeSelect and decrement the pipesLeft varbit. + * If this is the first selection, record the child in the firstSelect varbit. + * If this is the second selection, record the child in the secondSelect varbit. + * If this is the third selection, record the child in the thirdSelect varbit. + */ + on(junctionBox) { player, _, _, buttonID, _, _ -> + + // interface buttons + val b1 = 24; + val b2 = 25; + val b3 = 20 + val b4 = 21; + val b5 = 22; + val b6 = 23 + + // check if the player is clicking a button that already has a pipe attached. if yes, remove the pipe. + if (getVarbit(player, pipeSelectVarbit) == 0) { + if (tryDetachPipe(player, buttonID, b1, b2, b3, b4, b5, b6)) { + return@on true + } + } + + val currentSelection = getVarbit(player, pipeSelectVarbit) + + // connecting a new pipe + if (currentSelection == 0) { + val selection = when (buttonID) { + b1 -> 1; b2 -> 2; b3 -> 3 + b4 -> 4; b5 -> 5; b6 -> 6 + else -> 0 + } + setVarbit(player, pipeSelectVarbit, selection) + } else { + val pipeIndex = getPipeIndex(currentSelection, buttonID, b1, b2, b3, b4, b5, b6) + + if (pipeIndex != -1) { + // check if this specific pipe is already placed + if (isPipeAlreadyPlaced(player, pipeIndex + 1)) { + setVarbit(player, pipeSelectVarbit, 0) + return@on true + } + + // place the pipe + val pipeChild = pipes[pipeIndex] + setComponentVisibility(player, junctionBox, pipeChild, false) + + // pipe status is saved in these varbits and recalled on open. they are linked to main quest progression status + val saved = when { + getVarbit(player, firstSelect) == 0 -> { + setVarbit(player, firstSelect, pipeIndex + 1); true + } + + getVarbit(player, secondSelect) == 0 -> { + setVarbit(player, secondSelect, pipeIndex + 1); true + } + + getVarbit(player, thirdSelect) == 0 -> { + setVarbit(player, thirdSelect, pipeIndex + 1); true + } + + else -> false + } + + if (saved) { + val left = getVarbit(player, pipesLeftVarbit) + setVarbit(player, pipesLeftVarbit, left - 1) + } + } + setVarbit(player, pipeSelectVarbit, 0) + } + + // if there are no pipes left, we have placed them all. hammer time! + if (getVarbit(player, pipesLeftVarbit) == 0) { + lock(player, 3) + runTask(player, 2) { + closeInterface(player) + sendPlayerDialogue(player, "I hope I got that right.") + } + } + return@on true + } + } + + /** + * Checks if any active pipe uses the clicked button as an endpoint. + * If so, it removes that pipe and increments the pipesLeftVarbit. + */ + private fun tryDetachPipe( + player: Player, + buttonID: Int, + b1: Int, + b2: Int, + b3: Int, + b4: Int, + b5: Int, + b6: Int + ): Boolean { + val saveVarbits = intArrayOf(firstSelect, secondSelect, thirdSelect) + + for (v in saveVarbits) { + val pipeIndexPlusOne = getVarbit(player, v) + if (pipeIndexPlusOne == 0) continue + + val pipeIndex = pipeIndexPlusOne - 1 + // Check if the clicked button is one of the two ends for this pipe index + if (isButtonPartOfPipe(pipeIndex, buttonID, b1, b2, b3, b4, b5, b6)) { + // Hide the pipe on interface + setComponentVisibility(player, junctionBox, pipes[pipeIndex], true) + // Reset the varbit + setVarbit(player, v, 0) + // Increment pipes left + val left = getVarbit(player, pipesLeftVarbit) + setVarbit(player, pipesLeftVarbit, left + 1) + return true + } + } + return false + } + + // just read the function title + private fun isPipeAlreadyPlaced(player: Player, pipeID: Int): Boolean { + return getVarbit(player, firstSelect) == pipeID || + getVarbit(player, secondSelect) == pipeID || + getVarbit(player, thirdSelect) == pipeID + } + + /** + * Maps the start selection (1-6) and the end button ID to the index in the 'pipes' array. + */ + private fun getPipeIndex(start: Int, endBtn: Int, b1: Int, b2: Int, b3: Int, b4: Int, b5: Int, b6: Int): Int { + return when (start) { + 1 -> when (endBtn) { // Top Left + b4 -> 0; b5 -> 6; b6 -> 7; b2 -> 8; b3 -> 9; else -> -1 + } + 2 -> when (endBtn) { // Top Mid + b5 -> 1; b4 -> 10; b6 -> 11; b1 -> 8; b3 -> 12; else -> -1 + } + 3 -> when (endBtn) { // Top Right + b6 -> 2; b4 -> 13; b5 -> 14; b1 -> 9; b2 -> 12; else -> -1 + } + 4 -> when (endBtn) { // Bottom Left + b1 -> 0; b5 -> 3; b6 -> 4; b2 -> 10; b3 -> 13; else -> -1 + } + 5 -> when (endBtn) { // Bottom Mid + b2 -> 1; b4 -> 3; b6 -> 5; b1 -> 6; b3 -> 14; else -> -1 + } + 6 -> when (endBtn) { // Bottom Right + b3 -> 2; b4 -> 4; b5 -> 5; b1 -> 7; b2 -> 11; else -> -1 + } + else -> -1 + } + } + + private fun isButtonPartOfPipe( + pipeIndex: Int, + buttonID: Int, + b1: Int, + b2: Int, + b3: Int, + b4: Int, + b5: Int, + b6: Int + ): Boolean { + return when (pipeIndex) { + 0 -> buttonID == b1 || buttonID == b4 + 1 -> buttonID == b2 || buttonID == b5 + 2 -> buttonID == b3 || buttonID == b6 + 3 -> buttonID == b4 || buttonID == b5 + 4 -> buttonID == b4 || buttonID == b6 + 5 -> buttonID == b5 || buttonID == b6 + 6 -> buttonID == b1 || buttonID == b5 + 7 -> buttonID == b1 || buttonID == b6 + 8 -> buttonID == b1 || buttonID == b2 + 9 -> buttonID == b1 || buttonID == b3 + 10 -> buttonID == b2 || buttonID == b4 + 11 -> buttonID == b2 || buttonID == b6 + 12 -> buttonID == b2 || buttonID == b3 + 13 -> buttonID == b3 || buttonID == b4 + 14 -> buttonID == b3 || buttonID == b5 + else -> false + } + } +} From 39e6a72e8a56c1a2cc5ac5db15a081dc02b6e463 Mon Sep 17 00:00:00 2001 From: aidan Date: Fri, 23 Jan 2026 20:15:45 -0600 Subject: [PATCH 05/45] added fan station --- .../quest/elementalworkshop2/BeatenBook.kt | 5 +- .../quest/elementalworkshop2/EW2Listeners.kt | 869 ++++++++++++------ .../elementalworkshop2/ElementalWorkshop2.kt | 65 +- .../JunctionBoxInterface.kt | 19 +- 4 files changed, 615 insertions(+), 343 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt index cafc8526f..f81bb75be 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt @@ -2,11 +2,10 @@ package content.region.kandarin.seers.quest.elementalworkshop2 import content.data.Quests import content.global.handlers.iface.* -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2Varbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2StageVarbit import core.game.interaction.InteractionListener import core.api.* import core.game.interaction.IntType -import core.game.interaction.QueueStrength import core.game.node.entity.player.Player import org.rs09.consts.Items @@ -215,7 +214,7 @@ class BeatenBook : InteractionListener { BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 2) - setVarbit(player, EW2Varbit, 2, true) + setVarbit(player, EW2StageVarbit, 2, true) // storeBookInHouse(player, node) // todo uncomment this when poh costume room is in } else { // storeBookInHouse(player, node) // todo uncomment this when poh costume room is in diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 79a4dbfc9..60e532390 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -1,14 +1,20 @@ package content.region.kandarin.seers.quest.elementalworkshop2 import content.data.Quests -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CartLever -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CranePos -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CraneState +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CartLeverVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CogVarbit1 +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CogVarbit2 +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CogVarbit3 +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CranePosVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CraneStateVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2DoorLeverVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2FanLeverVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2HatchVarbit -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2JigCart +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2JigCartVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2MachineryVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2PipeVarbit -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2Varbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2StageVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2TankDoorVarbit import core.api.* import core.game.interaction.IntType import core.game.interaction.InteractionListener @@ -22,22 +28,40 @@ import org.rs09.consts.* class EW2Listeners : InteractionListener { /** - * Overall there are 4 stations down here: + * Quest structure and testing notes: * - * 1- crane - takes ele bar. outputs hot ele bar - * 2- anvil - takes hot ele bar. outputs smashed hot bar - * 3- water - takes smashed hot bar. outputs smashed cooled bar - * 4- fan - takes smashed cooled bar. outputs primed bar + * I've divided the listeners into the below sections for easier parsing. Overall, the main EW2 workshop floor is divided + * into 4 stations. A jig cart rolls between them. Each station only accepts one specific state of the jig cart, and only + * progresses the jig cart by one state. * - * each station only accepts one specific state of the jig cart, and only progresses the jig cart by one state. + * Each station must be checked that it's in the correct state to allow the cart to travel around. + * + * Each station must only progress the cart state if it's the correct step, and otherwise fail gracefully. + * + * Start + * 0 - Listeners for the dig site books, finding keys, stairs, jig cart, etc. + * + * EW2 main floor + * 1 - crane - takes ele bar. outputs hot ele bar + * 2 - anvil - takes hot ele bar. outputs smashed hot bar + * 3 - water - takes smashed hot bar. outputs smashed cooled bar + * 4 - fan - takes smashed cooled bar. outputs primed bar + * + * EW2 basement + * 5 - mind imbuement station. Takes primed bar and outputs mind bar. * */ - // todo finish organizing the listeners under their stages - companion object { - // various states the crane can be in. they are a pattern, so the logic to flip and lower can be generalized. + /** + * This companion object stores helper functions to save and recall workshop state. + * All the machinery states are stored in varbits and are updated in various listeners, + * then synced in the various sync() functions. The whole workshop is synced using the + * EW2Zone enter trigger, so when the player comes back, the workshop is where they left it. + */ + + // various states the crane can be in. val craneStates = intArrayOf( // Varbit 2644 = 0: Broken Scenery.OLD_CRANE_18638, // broken crane facing south, claw down @@ -81,42 +105,50 @@ class EW2Listeners : InteractionListener { Location.create(1961, 5155, 2) // east (fans) ) - // function to find my crane because it exists at some corner instead of the center. - fun findCraneInArea(center: Location): core.game.node.scenery.Scenery? { - // Search an area around the center - for (x in -2..2) { - for (y in -2..2) { - val checkLoc = center.transform(x, y, 0) - for (id in craneStates) { - val found = RegionManager.getObject(checkLoc.z, checkLoc.x, checkLoc.y, id) - if (found != null) return found - } - } - } - return null - } + val tankDoorStates = intArrayOf( + Scenery.DOOR_18651, // door closed + Scenery.DOOR_18652, + Scenery.DOOR_18653, + Scenery.DOOR_18654, + Scenery.DOOR_18655, + Scenery.DOOR_18656, + Scenery.DOOR_18657, + Scenery.DOOR_18658, + Scenery.DOOR_18659 + ) // position the crane based on varbit states + // todo it would be nice to animate this crane fun syncCrane(player: Player) { - val mat = getVarbit(player, EW2CraneState) - val pos = getVarbit(player, EW2CranePos) + val mat = getVarbit(player, EW2CraneStateVarbit) + val pos = getVarbit(player, EW2CranePosVarbit) val index = (mat * 4) + pos val newId = craneStates[index] + val center = Location.create(1954, 5145, 2) - val craneLoc = Location.create(1954, 5145, 2) - val currentCrane = findCraneInArea(craneLoc) + for (x in -2..2) { + for (y in -2..2) { + val checkLoc = center.transform(x, y, 0) - if (currentCrane != null) { - // todo it would be nice to animate this crane - replaceScenery(currentCrane, newId, -1) + // look for the object at this specific tile + val currentObject = RegionManager.getObject(checkLoc.z, checkLoc.x, checkLoc.y) ?: continue + + // check if the object at this tile is a crane + if (craneStates.contains(currentObject.id)) { + if (currentObject.id != newId) { + replaceScenery(currentObject, newId, -1) + } + return + } + } } } // update the jig cart based on varbit status fun syncCart(player: Player) { - val cartState = getVarbit(player, EW2JigCart) - val cartPosIndex = getVarbit(player, EW2CartLever) + val cartState = getVarbit(player, EW2JigCartVarbit) + val cartPosIndex = getVarbit(player, EW2CartLeverVarbit) val targetId = jigCartStates[cartState] val targetLocation = jigCartLocations[cartPosIndex] @@ -134,18 +166,70 @@ class EW2Listeners : InteractionListener { } // update location if (existingCart.location != targetLocation) { + // todo it would be nice if the cart moved instead of teleporting. try and make the tracks walkable? existingCart.teleport(targetLocation) } } else { - // Spawn if missing + // Spawn if missing (like if this is the quest start) val newCart = NPC(targetId, targetLocation) newCart.init() } } + + // values for the cogs + val cogRange = (Scenery.COG_18667..Scenery.COG_18684).toList().toIntArray() // 18 different cogs. That is enough to give each cog a static/fwd spin/rev spin animation. + + val smallCogs = intArrayOf(Scenery.COG_18673, Scenery.COG_18667, Scenery.COG_18670) // pin 1, pin 2, pin 3 + val medCogs = intArrayOf(Scenery.COG_18674, Scenery.COG_18668, Scenery.COG_18671) // pin 1, pin 2, pin 3 + val largeCogs = intArrayOf(Scenery.COG_18675, Scenery.COG_18669, Scenery.COG_18672) // pin 1, pin 2, pin 3 + + // offset by 9 from the static cogs. this is a guess for now (see below) + val smallMovingCogs = intArrayOf(Scenery.COG_18682, Scenery.COG_18676, Scenery.COG_18679) + val medMovingCogs = intArrayOf(Scenery.COG_18683, Scenery.COG_18677, Scenery.COG_18680) + val largeMovingCogs = intArrayOf(Scenery.COG_18684, Scenery.COG_18678, Scenery.COG_18681) + + // todo it'd be nice to have a function that swapped the cog scenery for an animated version. + // function to swap scenery values of the cogs based on the varbit settings. this makes them appear to spin. + fun syncCogs(player: Player) { + + } + + // syncs the tank door based on state of varbit 2653 + fun syncTankDoor(player: Player) { + val doorState = getVarbit(player, EW2TankDoorVarbit) + + val targetId = tankDoorStates[doorState] + val center = Location.create(1953, 5164, 2) + + // Scan the 5x5 area around the tank door location + for (x in -2..2) { + for (y in -2..2) { + val checkLoc = center.transform(x, y, 0) + val currentObject = RegionManager.getObject(checkLoc.z, checkLoc.x, checkLoc.y) ?: continue + + sendMessage(player, "$currentObject") + sendMessage(player,"$doorState") + + // Check if the object is any of the possible door states + if (tankDoorStates.contains(currentObject.id)) { + if (currentObject.id != targetId) { + replaceScenery(currentObject, targetId, -1) + } + return + } + } + } + } + } override fun defineListeners() { + /**------------------------ + * SECTION 0 - START + ** ------------------------ + */ + // Bookcase at digsite where you start quest. on(Scenery.BOOKCASE_17382, IntType.SCENERY, "search") { player, _ -> if (isQuestComplete(player, Quests.ELEMENTAL_WORKSHOP_I) && @@ -159,7 +243,7 @@ class EW2Listeners : InteractionListener { if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 0) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 1) - setVarbit(player, EW2Varbit, 1, true) + setVarbit(player, EW2StageVarbit, 1, true) } } else { sendMessage(player, "You search the books...") @@ -183,7 +267,7 @@ class EW2Listeners : InteractionListener { if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 2) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 3) - setVarbit(player, EW2Varbit, 3, true) + setVarbit(player, EW2StageVarbit, 3, true) } if(getVarbit(player, EW2MachineryVarbit) == 0) setVarbit(player, EW2MachineryVarbit, 1, true) @@ -198,7 +282,7 @@ class EW2Listeners : InteractionListener { } if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 3) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 4) - setVarbit(player, EW2Varbit, 4) + setVarbit(player, EW2StageVarbit, 4) } return@on true } @@ -217,7 +301,7 @@ class EW2Listeners : InteractionListener { if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 4) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 5) - setVarbit(player, EW2Varbit, 5) + setVarbit(player, EW2StageVarbit, 5) } return@on true } @@ -243,6 +327,324 @@ class EW2Listeners : InteractionListener { return@on true } + // schematic crate + on(Scenery.SCHEMATIC_CRATE_18711, IntType.SCENERY, "take-from") { player, _ -> + sendDialogueOptions(player, "There are two schematics here...", "Crane schematic", "Lever schematic") + addDialogueAction(player) { _, buttonId -> + when (buttonId) { + 2 -> { + if(!inInventory(player, Items.CRANE_SCHEMATIC_9718)){ + addItemOrDrop(player, Items.CRANE_SCHEMATIC_9718) + sendItemDialogue(player, Items.CRANE_SCHEMATIC_9718, "You take a copy of the schematic drawing.") + } + else sendDialogue(player, "You already have this schematic.") + } + 3 -> { + if(!inInventory(player, Items.LEVER_SCHEMATIC_9719)){ + addItemOrDrop(player, Items.LEVER_SCHEMATIC_9719) + sendItemDialogue(player, Items.LEVER_SCHEMATIC_9719, "You take a copy of the schematic drawing.") + } + else sendDialogue(player, "You already have this schematic.") + } + } + } + return@on true + } + + // lever that moves the cart between stations + on(Scenery.LEVER_18620, IntType.SCENERY, "pull") { player, _ -> + val currentPos = getVarbit(player, EW2CartLeverVarbit) // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east + val cranePos = getVarbit(player, EW2CranePosVarbit) // 2 if crane is down on north side + val doorPos = getVarbit(player, EW2DoorLeverVarbit) // 1 if door is open + val fanPos = getVarbit(player, EW2FanLeverVarbit) // 1 if the fan is on + + // crane must be up + if (currentPos == 0 && cranePos == 2) { + sendMessage(player, "You cannot do that while the claw is down.") + return@on true + } + // door must be closed + if (currentPos == 2 && doorPos == 1) { + sendMessage(player, "You cannot do that while the door is open.") + return@on true + } + // todo check the corkscrew because i think that'll shift the cart off the track + // door must be closed + if (currentPos == 3 && fanPos == 1) { + sendMessage(player, "You cannot do that while the fan is on.") + return@on true + } + + // positions cycles 0 -> 1 -> 2 -> 3 -> 0 etc. + val nextPos = (currentPos + 1) % 4 + + animate(player, Animations.HUMAN_PULL_LEVER_4909) + setVarbit(player, EW2CartLeverVarbit, nextPos, true) + syncCart(player) + + return@on true + } + + // take item from the jig cart + on(jigCartStates, IntType.NPC, "take-from") { player, node -> + when(node.id) { + 4914 -> { + addItemOrDrop(player, Items.ELEMENTAL_METAL_2893) + sendItemDialogue(player, Items.ELEMENTAL_METAL_2893, "You take the elemental metal.") + animate(player, 537) + setVarbit(player, EW2JigCartVarbit, 0, true) + } + 4915 -> { + sendMessage(player, "The bar is too hot to touch.") + } + 4918 -> { + addItemOrDrop(player, Items.PRIMED_BAR_9727) + sendItemDialogue(player, Items.PRIMED_BAR_9727, "You take the primed bar.") + animate(player, 537) + setVarbit(player, EW2JigCartVarbit, 0, true) + } + } + syncCart(player) + return@on true + } + + // put bar on the jig cart + onUseWith(IntType.NPC, intArrayOf(Items.ELEMENTAL_METAL_2893), NPCs.JIG_CART_4913) { player, used, _ -> + val cranePos = getVarbit(player, EW2CranePosVarbit) + + // if crane is south down, it's blocking the cart. + if (cranePos == 2) { + sendMessage(player, "You cannot place that while the claw is down.") + return@onUseWith true + } + + setVarbit(player, EW2JigCartVarbit, 1, true) + animate(player, 537) + syncCart(player) + removeItem(player, used) + return@onUseWith true + } + + /**------------------------ + * SECTION 1 - THE CRANE + ** ------------------------ + */ + // requires the elemental claw to be smithed and attached to the crane + + // crane schematic + on(Items.CRANE_SCHEMATIC_9718, IntType.ITEM, "read") { player, _ -> + sendDialogue(player, "The schematic drawing details the construction and maintenance of a mechanical arm. One of the sections you notice is an explanation of how to make an elemental claw.") + return@on true + } + + // lever schematic (what lever is this even for?) + on(Items.LEVER_SCHEMATIC_9719, IntType.ITEM, "read") { player, _ -> + openInterface(player, 466) + return@on true + } + + // fix crane + onUseWith(IntType.SCENERY, Items.CRANE_CLAW_9720, Scenery.OLD_CRANE_18636) { player, used, with -> + // check if already repaired + if(getVarbit(player, EW2CraneStateVarbit) != 0) { + sendMessage(player, "The crane is already repaired.") + return@onUseWith true + } + sendItemDialogue(player, used, "You replace the broken claw.") + removeItem(player, used) + + setVarbit(player, EW2CraneStateVarbit, 1, true) + syncCrane(player) + return@onUseWith true + } + + // west lever by crane. moves crane up and down. + on(Scenery.AN_OLD_LEVER_18622, IntType.SCENERY, "pull") { player, _ -> + val currentPos = getVarbit(player, EW2CranePosVarbit) + val nextPos = currentPos xor 1 + val craneMat = getVarbit(player, EW2CraneStateVarbit) // Crane State. 0 = broken, 1 = fixed/empty, 2 = ele bar, 3 = hot bar. + val cartState = getVarbit(player, EW2JigCartVarbit) // Jig Cart state. 0 = Empty, 1 = ele bar, 2 = hot bar, 3 = smashed hot, 4 = smashed cooled, 5 = primed + + // todo make sure the crane doesn't pick up other bars + // facing north: pick up and drop off bar + if (currentPos == 3 && nextPos == 2) { + if (craneMat == 1 && (cartState == 1 || cartState == 2)) { + // pick up + setVarbit(player, EW2CraneStateVarbit, cartState + 1) + setVarbit(player, EW2JigCartVarbit, 0) + sendMessage(player, "The crane claw picks up the bar from the cart.") + syncCart(player) + } else if ((craneMat == 2 || craneMat == 3) && cartState == 0) { + // drop off + setVarbit(player, EW2JigCartVarbit, craneMat - 1) + setVarbit(player, EW2CraneStateVarbit, 1) + sendMessage(player, "You lower the bar onto the jig cart.") + syncCart(player) + } + } + + // facing south: heat bar + if (currentPos == 1 && nextPos == 0) { + if (craneMat == 2) { + // if holding elemental bar, transform into hot bar + setVarbit(player, EW2CraneStateVarbit, 3) + playAudio(player, Sounds.EW2_CRANE_LOWER_3169) + } + } + setVarbit(player, EW2CranePosVarbit, nextPos, true) + animate(player, Animations.HUMAN_PULL_LEVER_4909) + syncCrane(player) + return@on true + } + + // east lever by crane. rotates crane north and south + on(Scenery.AN_OLD_LEVER_18621, IntType.SCENERY, "pull") { player, _ -> + val currentPos = getVarbit(player,EW2CranePosVarbit) + + // crane must be up to rotate + if (currentPos == 0 || currentPos == 2) { + sendMessage(player, "The arm cannot be rotated while the claw is down.") + return@on true + } + + val nextPos = currentPos xor 2 + + setVarbit(player, EW2CranePosVarbit, nextPos, true) + animate(player, Animations.HUMAN_PULL_LEVER_4909) + syncCrane(player) + return@on true + } + + /**------------------------ + * SECTION 2 - THE ANVIL + ** ------------------------ + */ + // requires the junction box interface to be fixed + + // opens the junction box + on(Scenery.JUNCTION_BOX_18641, IntType.SCENERY, "open") { player, _ -> + openInterface(player, Components.JUNCTION_BOX_262) + return@on true + } + + // west lever, checks junction box status + on(Scenery.AN_OLD_LEVER_18640, IntType.SCENERY, "pull") { player, _ -> + + if (JunctionBoxInterface.isSolutionCorrect(player)) { + val cartState = getVarbit(player, EW2JigCartVarbit) // Jig Cart state. 0 = Empty, 1 = ele bar, 2 = hot bar, 3 = smashed hot, 4 = smashed cooled, 5 = primed + val cartPos = getVarbit(player, EW2CartLeverVarbit) // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east + + // check if cart is here + if (cartPos == 1) { + when (cartState) { + 0 -> { // empty + sendDialogue(player, "You pull the lever and the press operates, but there is nothing on the cart to flatten.") + } + // todo animate the hammer smash + 1 -> { // ele bar + setVarbit(player, EW2JigCartVarbit, 4) + // "hardy" is the real word used. I thought it was a typo for 'hammer' but it's not. https://en.wikipedia.org/wiki/Hardy_tool + sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is not hot enough to flatten.") + syncCart(player) + } + 2 -> { // hot ele bar + setVarbit(player, EW2JigCartVarbit, 3) + // "hardy" is correct. see above. + sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar has been flattened.") + syncCart(player) + } + else -> { // smashed hot, smashed cooled, or primed + sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is already flattened.") + } + } + } else { + // cart in wrong position + sendDialogue(player, "You pull the lever and the press operates, but the jig cart is not underneath it.") + } + } else { + // The junction box is not configured correctly + sendDialogue(player, "You pull the lever but nothing happens.") + } + + return@on true + } + + /**------------------------ + * SECTION 3 - WATER + ** ------------------------ + */ + // requires the pipe to be replaced + + // pipe crate + on(Scenery.CRATE_18619, IntType.SCENERY, "search") { player, _ -> + if (!inInventory(player, Items.PIPE_9723)) { + addItemOrDrop(player, Items.PIPE_9723) + sendMessage(player, "You find a pipe.") + } else { + sendMessage(player, "It's empty.") + } + return@on true + } + + // fix the pipe + onUseWith(IntType.SCENERY, Items.PIPE_9723, Scenery.PIPING_18650) { player, item, _ -> + animate(player, 537) + sendItemDialogue(player, item, "You replace the section of broken pipe.") + playAudio(player, Sounds.EW2_PLACE_PIPE_3179) + setVarbit(player, EW2PipeVarbit, 1, true) + removeItem(player, item) + return@onUseWith true + } + + // todo north lever + // north lever that opens and closes the door + on(Scenery.AN_OLD_LEVER_18648, IntType.SCENERY, "pull") { player, _ -> + + val currentState = getVarbit(player, EW2TankDoorVarbit) + + // Increment from 0-7, or reset to 0 if at 8 + val nextState = if (currentState < 8) currentState + 1 else 0 + + setVarbit(player, EW2TankDoorVarbit, nextState, true) + syncTankDoor(player) + + return@on true + } + + // todo corkscrew lever + // corkscrew lever that grabs and ungrabs the cart as long as the door is open + on(Scenery.CORKSCREW_LEVER_18649, IntType.SCENERY, "turn") { player, _ -> + + return@on true + } + + // todo west water valve + // west water valve fills the chamber if the door is closed "you open the valve" "you close the valve" make sure it's closed or the chamber will fill up again + // changes cart state into flattened mind bar + on(Scenery.WATER_VALVE_18646, IntType.SCENERY, "turn") { player, _ -> + + return@on true + } + + // todo east water valve + // east water valve drains the tank if it is full "you open the valve" + on(Scenery.WATER_VALVE_18647, IntType.SCENERY, "turn") { player, _ -> + + return@on true + } + + /** + * Scenery.WATER_TANK_18660 + * Scenery.WATER_TANK_18661 + */ + + + /**------------------------ + * SECTION 4 - FAN + ** ------------------------ + */ + // requires the cogs to be placed + // small cog crate on(Scenery.CRATE_18618, IntType.SCENERY, "search") { player, _ -> if (!inInventory(player, Items.SMALL_COG_9726)) { @@ -276,290 +678,165 @@ class EW2Listeners : InteractionListener { return@on true } - // pipe crate - on(Scenery.CRATE_18619, IntType.SCENERY, "search") { player, _ -> - if (!inInventory(player, Items.PIPE_9723)) { - addItemOrDrop(player, Items.PIPE_9723) - sendMessage(player, "You find a pipe.") - } else { - sendMessage(player, "It's empty.") + // taking the cogs from the pins + on(cogRange, IntType.SCENERY, "take") { player, node -> + // determine what to take + val cogToGive = when { + smallCogs.contains(node.id) -> Items.SMALL_COG_9726 + medCogs.contains(node.id) -> Items.MEDIUM_COG_9725 + largeCogs.contains(node.id) -> Items.LARGE_COG_9724 + else -> -1 + } + + if (cogToGive != -1) { + // determine which varbit to reset + // Pin 1 uses: 18673, 18674, 18675 + // Pin 2 uses: 18667, 18668, 18669 + // Pin 3 uses: 18670, 18671, 18672 + when (node.id) { + 18673, 18674, 18675 -> setVarbit(player, EW2CogVarbit1, 0, true) + 18667, 18668, 18669 -> setVarbit(player, EW2CogVarbit2, 0, true) + 18670, 18671, 18672 -> setVarbit(player, EW2CogVarbit3, 0, true) + } + + addItemOrDrop(player, cogToGive) + animate(player, 537) + sendMessage(player, "You remove the cog from the shaft.") } return@on true } - // schematic crate - on(Scenery.SCHEMATIC_CRATE_18711, IntType.SCENERY, "take-from") { player, _ -> - sendDialogueOptions(player, "There are two schematics here...", "Crane schematic", "Lever schematic") - addDialogueAction(player) { _, buttonId -> - when (buttonId) { - 2 -> { - if(!inInventory(player, Items.CRANE_SCHEMATIC_9718)){ - addItemOrDrop(player, Items.CRANE_SCHEMATIC_9718) - sendItemDialogue(player, Items.CRANE_SCHEMATIC_9718, "You take a copy of the schematic drawing.") - } - else sendDialogue(player, "You already have this schematic.") - } - 3 -> { - if(!inInventory(player, Items.LEVER_SCHEMATIC_9719)){ - addItemOrDrop(player, Items.LEVER_SCHEMATIC_9719) - sendItemDialogue(player, Items.LEVER_SCHEMATIC_9719, "You take a copy of the schematic drawing.") - } - else sendDialogue(player, "You already have this schematic.") - } + // use cog with pin 1 + // pin 1: 2655: val 0 is scenery 18665 (pin). val 1 is scenery 18673 (small cog). val 2 is scenery 18674 (med cog). val 3 is scenery 18675 (large cog). + onUseWith(IntType.SCENERY, intArrayOf(Items.SMALL_COG_9726, Items.MEDIUM_COG_9725, Items.LARGE_COG_9724), Scenery.PIN_18665) { player, item, _ -> + when(item.id) { + Items.SMALL_COG_9726 -> { + setVarbit(player, EW2CogVarbit1, 1, true) + sendItemDialogue(player, item, "You put the small cog onto the shaft.") + animate(player, 537) + removeItem(player, item) + } + Items.MEDIUM_COG_9725 -> { + setVarbit(player, EW2CogVarbit1, 2, true) + sendItemDialogue(player, item, "You put the medium cog onto the shaft.") + animate(player, 537) + removeItem(player, item) + } + Items.LARGE_COG_9724 -> { + setVarbit(player, EW2CogVarbit1, 3, true) + sendItemDialogue(player, item, "You put the large cog onto the shaft.") + animate(player, 537) + removeItem(player, item) } } - return@on true + return@onUseWith true } - // crane schematic - on(Items.CRANE_SCHEMATIC_9718, IntType.ITEM, "read") { player, _ -> - sendDialogue(player, "The schematic drawing details the construction and maintenance of a mechanical arm. One of the sections you notice is an explanation of how to make an elemental claw.") - return@on true - } - - // lever schematic - on(Items.LEVER_SCHEMATIC_9719, IntType.ITEM, "read") { player, _ -> - openInterface(player, 466) - return@on true - } - - /**------------------------ - * STATION 1 - THE CRANE - ** ------------------------ - */ - // requires the elemental claw to be smithed and attached to the crane - - // west lever by crane. moves crane up and down. - on(Scenery.AN_OLD_LEVER_18622, IntType.SCENERY, "pull") { player, _ -> - val currentPos = getVarbit(player, EW2CranePos) - val nextPos = currentPos xor 1 - val craneMat = getVarbit(player, EW2CraneState) - val cartState = getVarbit(player, EW2JigCart) - - // facing north: pick up and drop off bar - if (currentPos == 3 && nextPos == 2) { // Moving from North-Up to North-Down - if (craneMat == 1 && (cartState == 1 || cartState == 2)) { - // pick up - setVarbit(player, EW2CraneState, cartState + 1) - setVarbit(player, EW2JigCart, 0) - sendMessage(player, "The crane claw picks up the bar from the cart.") - syncCart(player) - } else if ((craneMat == 2 || craneMat == 3) && cartState == 0) { - // drop off - setVarbit(player, EW2JigCart, craneMat - 1) - setVarbit(player, EW2CraneState, 1) - sendMessage(player, "You lower the bar onto the jig cart.") - syncCart(player) + // use cog with pin 2 + // pin 2: 2656: val 0 is scenery 18664 (pin). val 1 is scenery 18667 (small cog). val 2 is scenery 18668 (med cog). val 3 is scenery 18669 (large cog) + onUseWith(IntType.SCENERY, intArrayOf(Items.SMALL_COG_9726, Items.MEDIUM_COG_9725, Items.LARGE_COG_9724), Scenery.PIN_18664) { player, item, _ -> + when(item.id) { + Items.SMALL_COG_9726 -> { + setVarbit(player, EW2CogVarbit2, 1, true) + sendItemDialogue(player, item, "You put the small cog onto the shaft.") + animate(player, 537) + removeItem(player, item) + } + Items.MEDIUM_COG_9725 -> { + setVarbit(player, EW2CogVarbit2, 2, true) + sendItemDialogue(player, item, "You put the medium cog onto the shaft.") + animate(player, 537) + removeItem(player, item) + } + Items.LARGE_COG_9724 -> { + setVarbit(player, EW2CogVarbit2, 3, true) + sendItemDialogue(player, item, "You put the large cog onto the shaft.") + animate(player, 537) + removeItem(player, item) } } + return@onUseWith true + } - // facing south: process bar - if (currentPos == 1 && nextPos == 0) { - if (craneMat == 2) { - // if holding elemental bar, transform into mind bar - setVarbit(player, EW2CraneState, 3) - playAudio(player, Sounds.EW2_CRANE_LOWER_3169) + // use cog with pin 3 + // pin 3: 2657: val 0 is scenery 18666 (pin). val 1 is scenery 18670 (small cog). val 2 is scenery 18671 (med cog). val 3 is scenery 18672 (large cog) + onUseWith(IntType.SCENERY, intArrayOf(Items.SMALL_COG_9726, Items.MEDIUM_COG_9725, Items.LARGE_COG_9724), Scenery.PIN_18666) { player, item, _ -> + when(item.id) { + Items.SMALL_COG_9726 -> { + setVarbit(player, EW2CogVarbit3, 1, true) + sendItemDialogue(player, item, "You put the small cog onto the shaft.") + animate(player, 537) + removeItem(player, item) + } + Items.MEDIUM_COG_9725 -> { + setVarbit(player, EW2CogVarbit3, 2, true) + sendItemDialogue(player, item, "You put the medium cog onto the shaft.") + animate(player, 537) + removeItem(player, item) + } + Items.LARGE_COG_9724 -> { + setVarbit(player, EW2CogVarbit3, 3, true) + sendItemDialogue(player, item, "You put the large cog onto the shaft.") + animate(player, 537) + removeItem(player, item) } } + return@onUseWith true + } - setVarbit(player, EW2CranePos, nextPos, true) + // fan lever. Authentically this check should allow the machine to start and the fan to either not start, start backwards, or start correctly, but I'm simplifying it for my sanity. + on(Scenery.AN_OLD_LEVER_18663, IntType.SCENERY, "pull") { player, _ -> animate(player, Animations.HUMAN_PULL_LEVER_4909) - syncCrane(player) - return@on true - } - // east lever by crane. rotates crane north and south - on(Scenery.AN_OLD_LEVER_18621, IntType.SCENERY, "pull") { player, _ -> - val currentPos = getVarbit(player,EW2CranePos) + val p1 = getVarbit(player, EW2CogVarbit1) + val p2 = getVarbit(player, EW2CogVarbit2) + val p3 = getVarbit(player, EW2CogVarbit3) + val lever = getVarbit(player, EW2FanLeverVarbit) + val cartpos = getVarbit(player, EW2CartLeverVarbit) // needs to be 3 to be at this station + val cartstate = getVarbit(player, EW2JigCartVarbit) // needs to be state 4: smashed cooled - // crane must be up to rotate - if (currentPos == 0 || currentPos == 2) { - sendMessage(player, "The arm cannot be rotated while the claw is down.") - return@on true - } - - val nextPos = currentPos xor 2 - - setVarbit(player, EW2CranePos, nextPos, true) - animate(player, Animations.HUMAN_PULL_LEVER_4909) - syncCrane(player) - return@on true - } - - /**------------------------ - * STATION 2 - THE ANVIL - ** ------------------------ - */ - // requires the junction box interface to be fixed - - // west lever, checks junction box status - on(Scenery.AN_OLD_LEVER_18640, IntType.SCENERY, "pull") { player, _ -> - - if (JunctionBoxInterface.isSolutionCorrect(player)) { - // Jig Cart state. 0 = Empty, 1 = ele bar, 2 = hot bar, 3 = smashed hot, 4 = smashed cooled, 5 = primed - val cartState = getVarbit(player, EW2JigCart) - val cartPos = getVarbit(player, EW2CartLever) - - // check if cart is here - if (cartPos == 1) { - when (cartState) { - 0 -> { // empty - sendDialogue(player, "You pull the lever and the press operates, but there is nothing on the cart to flatten.") - } - // todo animate the hammer smash - 1 -> { // ele bar - setVarbit(player, EW2JigCart, 4) - // "hardy" is the real word used. I thought it was a typo for 'hammer' but it's not. https://en.wikipedia.org/wiki/Hardy_tool - sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is not hot enough to flatten.") - syncCart(player) - } - 2 -> { // hot ele bar - setVarbit(player, EW2JigCart, 3) - // "hardy" is correct. see above. - sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar has been flattened.") - syncCart(player) - } - else -> { // smashed hot, smashed cooled, or primed - sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is already flattened.") - } + // check if the cogs are right. pin 1 (2655) should have the small cog. pin 2 (2656) should have the med cog. pin 3 (2656) should have the large cog. + if (p1 == 1 && p2 == 2 && p3 == 3) { + if(lever == 0) { + // turn on + sendDialogue(player, "The machine rumbles and the fan starts to blow.") // Is authentically two messages of "The machine starts rumbling." and "The fan starts to blow.", but I am simplifying this check. + setVarbit(player, EW2FanLeverVarbit, 1, true) + // check cart and cool if necessary. otherwise nothing should happen. + if (cartpos == 3 && cartstate == 4) { + setVarbit(player, EW2JigCartVarbit, 5, true) + syncCart(player) } + //todo animate the fan somehow + // find scenery ID {FANSTOPPED} or {FANSTARTED} near tile Location.create(1961, 5154, 2). + // swap for scenery {FANSTOPPED} if EW2FanStateVarbit is 0 or {FANSTARTED} if EW2FanStateVarbit is 1 + } else { - // cart in wrong position - sendDialogue(player, "You pull the lever and the press operates, but the jig cart is not underneath it.") + // turn off + sendDialogue(player, "The machine grinds to a standstill.") + setVarbit(player, EW2FanLeverVarbit, 0, true) } + // cogs are not correct } else { - // The junction box is not configured correctly - sendDialogue(player, "You pull the lever but nothing happens.") + sendDialogue(player, "The machine starts up, but the fan does not blow.") } - return@on true } /**------------------------ - * STATION 2 - WATER + * SECTION 5 - MIND IMBUE ** ------------------------ */ - // requires the pipe to be replaced - // todo north lever - // north lever that opens and closes the door - on(Scenery.AN_OLD_LEVER_18648, IntType.SCENERY, "pull") { player, _ -> + /** + * EXTRACTOR_GUN_18693 + * EXTRACTOR_GUN_18694 + * EXTRACTOR_GUN_18695 + * EXTRACTOR_GUN_18696 + * EXTRACTOR_HAT_18690 + * EXTRACTOR_HAT_18691 + * EXTRACTOR_HAT_TOP_18692 + */ - return@on true - } - - // todo corkscrew lever - // corkscrew lever that grabs and ungrabs the cart as long as the door is open - on(Scenery.CORKSCREW_LEVER_18649, IntType.SCENERY, "turn") { player, _ -> - - return@on true - } - - // todo west water valve - // west water valve fills the chamber if the door is closed "you open the valve" "you close the valve" make sure it's closed or the chamber will fill up again - // changes cart state into flattened mind bar - on(Scenery.WATER_VALVE_18646, IntType.SCENERY, "turn") { player, _ -> - - return@on true - } - - // todo east water valve - // east water valve drains the tank if it is full "you open the valve" - on(Scenery.WATER_VALVE_18647, IntType.SCENERY, "turn") { player, _ -> - - return@on true - } - - // fix crane - onUseWith(IntType.SCENERY, Items.CRANE_CLAW_9720, Scenery.OLD_CRANE_18636) { player, used, with -> - // check if already repaired - if(getVarbit(player, EW2CraneState) != 0) { - sendMessage(player, "The crane is already repaired.") - return@onUseWith true - } - sendItemDialogue(player, used, "You replace the broken claw.") - removeItem(player, used) - - setVarbit(player, EW2CraneState, 1, true) - syncCrane(player) - return@onUseWith true - } - - // opens the junction box - on(Scenery.JUNCTION_BOX_18641, IntType.SCENERY, "open") { player, _ -> - openInterface(player, Components.JUNCTION_BOX_262) - return@on true - } - - // take item from the jig cart - on(jigCartStates, IntType.NPC, "take-from") { player, node -> - when(node.id) { - 4914 -> { - addItemOrDrop(player, Items.ELEMENTAL_METAL_2893) - sendItemDialogue(player, Items.ELEMENTAL_METAL_2893, "You take the elemental metal.") - animate(player, 537) - setVarbit(player, EW2JigCart, 0, true) - } - 4915 -> { - sendMessage(player, "The bar is too hot to touch.") - } - 4918 -> { - addItemOrDrop(player, Items.PRIMED_BAR_9727) - sendItemDialogue(player, Items.PRIMED_BAR_9727, "You take the primed bar.") - animate(player, 537) - setVarbit(player, EW2JigCart, 0, true) - } - } - syncCart(player) - return@on true - } - - // fix the pipe - onUseWith(IntType.SCENERY, Items.PIPE_9723, Scenery.PIPING_18650) { player, item, _ -> - animate(player, 537) - sendItemDialogue(player, item, "You replace the section of broken pipe.") - playAudio(player, Sounds.EW2_PLACE_PIPE_3179) - setVarbit(player, EW2PipeVarbit, 1, true) - removeItem(player, item) - return@onUseWith true - } - - // put regular bar on the jig cart - onUseWith(IntType.NPC, intArrayOf(Items.ELEMENTAL_METAL_2893), NPCs.JIG_CART_4913) { player, used, _ -> - val cranePos = getVarbit(player, EW2CranePos) - - // if crane is south down, it's blocking the cart. - if (cranePos == 2) { - sendMessage(player, "You cannot place that while the claw is down.") - return@onUseWith true - } - - setVarbit(player, EW2JigCart, 1, true) - animate(player, 537) - syncCart(player) - removeItem(player, used) - return@onUseWith true - } - - on(Scenery.LEVER_18620, IntType.SCENERY, "pull") { player, _ -> - val currentPos = getVarbit(player, EW2CartLever) - val cranePos = getVarbit(player, EW2CranePos) - - // crane must be up - if (currentPos == 0 && cranePos == 2) { - sendMessage(player, "You cannot do that while the claw is down.") - return@on true - } - - // positions cycles 0 -> 1 -> 2 -> 3 -> 0 etc. - val nextPos = (currentPos + 1) % 4 - - animate(player, Animations.HUMAN_PULL_LEVER_4909) - setVarbit(player, EW2CartLever, nextPos, true) - syncCart(player) - - return@on true - } } // fixes to get right interact locations diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt index 990cf4132..ca17f62ab 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt @@ -1,17 +1,12 @@ package content.region.kandarin.seers.quest.elementalworkshop2 import content.data.Quests -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCart -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCrane import core.api.* -import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.player.link.quest.Quest import core.game.node.entity.skill.Skills -import core.game.world.map.Location import core.plugin.Initializable import org.rs09.consts.Items -import org.rs09.consts.NPCs /** * ELEMENTAL WORKSHOP II QUEST @@ -21,39 +16,39 @@ import org.rs09.consts.NPCs */ @Initializable -class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Varbit, 0, 1, 11) { +class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2StageVarbit, 0, 1, 11) { // a truckload of varbits companion object { - const val ELEMENTALWORKSHOP2VARP = 896 // first EW2 varp that packages varbits 2639-2652 - const val EW2Varbit = 2639 // Main quest progress. Incremented with quest stages. + const val EW2Varp1 = 896 // first EW2 varp that packages varbits 2639-2652 + const val EW2StageVarbit = 2639 // Main quest progress. Incremented with quest stages. const val EW2MachineryVarbit = 2640 // The machinery you have to search for the key. Set to 1 after you read the scroll. const val EW2HatchVarbit = 2641 // The hatch down to EW2. Set to 1 after you unlock it with the key. - const val EW2CartLever = 2642 // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east - const val EW2JigCart = 2643 // Jig Cart state. 0 = Empty, 1 = ele bar, 2 = hot bar, 3 = smashed hot, 4 = smashed cooled, 5 = primed - const val EW2CraneState = 2644 // Crane State. 0 = Broken, 1 = Fixed/Empty, 2 = Elemental Bar, 3 = Mind Bar. - const val EW2CranePos = 2645 // Crane Position. 0 = South Down, 1 = South Up, 2 = North Down, 3 = North Up. - const val EW2JB1 = 2646 // junction box status - const val EW2JB2 = 2647 // junction box status - const val EW2JB3 = 2648 // junction box status - //const val EW2JB = 2649 // junction box - this overlaps 2646, 2647, 2648 - const val EW2PipeVarbit = 2650 - //const val EW2 = 2651 // water valve west - //const val EW2 = 2652 // cork screw lever and water valve east + const val EW2CartLeverVarbit = 2642 // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east + const val EW2JigCartVarbit = 2643 // Jig Cart state. 0 = Empty, 1 = ele bar, 2 = hot bar, 3 = smashed hot, 4 = smashed cooled, 5 = primed + const val EW2CraneStateVarbit = 2644 // Crane State. 0 = broken, 1 = fixed/empty, 2 = ele bar, 3 = hot bar. + const val EW2CranePosVarbit = 2645 // Crane Position. 0 = South Down, 1 = South Up, 2 = North Down, 3 = North Up. + const val EW2JctVarbit1 = 2646 // junction box status. see junctionboxinterface for details. + const val EW2JctVarbit2 = 2647 // junction box status. see junctionboxinterface for details. + const val EW2JctVarbit3 = 2648 // junction box status. see junctionboxinterface for details. + //const val EW2JctBoxVarbit = 2649 // junction box - this overlaps 2646, 2647, 2648. could be used to reset whole jct box at once. + const val EW2PipeVarbit = 2650 // pipe varbit. 0 = broken, 1 = fixed + const val EW2ValveWestVarbit = 2651 // water valve west. 0 = closed, 1 = open + const val EW2ValveEastVarbit = 2652 // water valve east. 0 = closed, 1 = open - const val ELEMENTALWORKSHOP2VARP2 = 897 // second EW2 varp that packages varbits 2653-2664 - //const val EW2 = 2653 - //const val EW2 = 2654 - //const val EW2 = 2655 // cog pin - //const val EW2 = 2656 // cog pin - //const val EW2 = 2657 // cog pin - //const val EW2 = 2658 - //const val EW2 = 2659 - //const val EW2 = 2660 - //const val EW2 = 2661 - //const val EW2 = 2662 - //const val EW2 = 2663 - //const val EW2 = 2664 + const val EW2Varp2 = 897 // second EW2 varp that packages varbits 2653-2664 + const val EW2TankDoorVarbit = 2653 // tank door, controlled by corkscrew lever. 0 = , 1 = , 2 = , 3 = , 4 = , 5 = , 6 = , 7 = , 8 = + const val EW2DoorLeverVarbit = 2654 // door lever. 0 = closed, 1 = open + const val EW2CogVarbit1 = 2655 // cog pin. 0 = no cog, 1 = small, 2 = med, 3 = large + const val EW2CogVarbit2 = 2656 // cog pin. 0 = no cog, 1 = small, 2 = med, 3 = large + const val EW2CogVarbit3 = 2657 // cog pin. 0 = no cog, 1 = small, 2 = med, 3 = large + //const val EW2AllCogsVarbit = 2658 // overlaps the cog varbits. could be used to reset all cogs at once. + const val EW2FanLeverVarbit = 2659 // fan switch. 0 = fan off, 1 = fan on + //const val EW2FanStateVarbit = 2660 // fan blades. 0 = off, 1 = forward, 2 = reverse + //const val EW2FanStationVarbit = 2661 // overlaps cogs, fan switch, and fan blades varbits + //const val EW2 = 2662 // extractor gun. 0 = empty, 1 = primed bar, 2 = mind bar + //const val EW2 = 2663 // + //const val EW2 = 2664 // values from 1 to 7 back at the dig site library where you find the book. } /** @@ -166,8 +161,8 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Var // use ::setqueststage 53 0 to reset quest. override fun reset(player: Player) { // these varps wipes all the varbits - setVarp(player, ELEMENTALWORKSHOP2VARP, 0, true) - setVarp(player, ELEMENTALWORKSHOP2VARP2, 0, true) + setVarp(player, EW2Varp1, 0, true) + setVarp(player, EW2Varp2, 0, true) player.getQuestRepository().syncronizeTab(player) super.reset(player) } @@ -184,7 +179,7 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Var drawReward(player, "Ability to make and equip", ln++) drawReward(player, "elemental mind equipment", ln) - setVarbit(player, EW2Varbit, 11, true) + setVarbit(player, EW2StageVarbit, 11, true) rewardXP(player, Skills.SMITHING, 7500.0) rewardXP(player, Skills.CRAFTING, 7500.0) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt index e09bb9d1b..516e0cb8e 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt @@ -1,8 +1,8 @@ package content.region.kandarin.seers.quest.elementalworkshop2 -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2JB1 -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2JB2 -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2JB3 +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2JctVarbit1 +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2JctVarbit2 +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2JctVarbit3 import core.api.* import core.game.interaction.InterfaceListener import core.game.node.entity.player.Player @@ -23,9 +23,9 @@ class JunctionBoxInterface : InterfaceListener { private val pipeSelectVarbit = 4834 // 0 = no select, 1 = top left, 2 = top middle, 3 = top right, 4 = bottom left, 5 = bottom middle, 6 = bottom right - private val firstSelect = EW2JB1 // this is the main quest varbit used to save and recall pipe selection. values 0-15 - private val secondSelect = EW2JB2 // this is the main quest varbit used to save and recall pipe selection. values 0-15 - private val thirdSelect = EW2JB3 // this is the main quest varbit used to save and recall pipe selection. values 0-15 + private val firstSelect = EW2JctVarbit1 // this is the main quest varbit used to save and recall pipe selection. values 0-15 + private val secondSelect = EW2JctVarbit2 // this is the main quest varbit used to save and recall pipe selection. values 0-15 + private val thirdSelect = EW2JctVarbit3 // this is the main quest varbit used to save and recall pipe selection. values 0-15 // the interface children for displaying each placed pipe private val pipes = intArrayOf( @@ -57,9 +57,9 @@ class JunctionBoxInterface : InterfaceListener { * Checks if the three specific pipes required for the puzzle solution are placed. */ fun isSolutionCorrect(player: Player): Boolean { - val s1 = getVarbit(player, EW2JB1) - val s2 = getVarbit(player, EW2JB2) - val s3 = getVarbit(player, EW2JB3) + val s1 = getVarbit(player, EW2JctVarbit1) + val s2 = getVarbit(player, EW2JctVarbit2) + val s3 = getVarbit(player, EW2JctVarbit3) val requiredIndices = intArrayOf(3, 7, 12) val currentValues = intArrayOf(s1, s2, s3) @@ -111,6 +111,7 @@ class JunctionBoxInterface : InterfaceListener { on(junctionBox) { player, _, _, buttonID, _, _ -> // interface buttons + // i think some of this is also controlled by 1318.cs2 fwiw val b1 = 24; val b2 = 25; val b3 = 20 From b388428d542cbca181d50d20647ce01eec8be84c Mon Sep 17 00:00:00 2001 From: aidan Date: Sun, 25 Jan 2026 08:04:45 -0600 Subject: [PATCH 06/45] push this to go work on hazeel --- .../seers/quest/elementalworkshop2/EW2Listeners.kt | 12 ++++++++++++ .../seers/quest/elementalworkshop2/EW2Zone.kt | 2 ++ 2 files changed, 14 insertions(+) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 60e532390..61e140cb8 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -123,6 +123,12 @@ class EW2Listeners : InteractionListener { val mat = getVarbit(player, EW2CraneStateVarbit) val pos = getVarbit(player, EW2CranePosVarbit) + // crane location [1952, 5143, 2] + //removeScenery(scenery) + //addScenery(scenery) + //getScenery(scenery) + //replaceScenery(scenery) + val index = (mat * 4) + pos val newId = craneStates[index] val center = Location.create(1954, 5145, 2) @@ -133,6 +139,9 @@ class EW2Listeners : InteractionListener { // look for the object at this specific tile val currentObject = RegionManager.getObject(checkLoc.z, checkLoc.x, checkLoc.y) ?: continue + //val currentObject = getScenery(checkLoc.z, checkLoc.x, checkLoc.y) ?: continue + // crane is an object not a scenery to get + // check if the object at this tile is a crane if (craneStates.contains(currentObject.id)) { @@ -604,6 +613,9 @@ class EW2Listeners : InteractionListener { // Increment from 0-7, or reset to 0 if at 8 val nextState = if (currentState < 8) currentState + 1 else 0 + val trackscen = getScenery(1953, 5162, 2)!! + addScenery(18751, Location.create(1951, 5159, 2)) + removeScenery(trackscen) setVarbit(player, EW2TankDoorVarbit, nextState, true) syncTankDoor(player) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt index c063a4d51..33097f715 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt @@ -4,11 +4,13 @@ import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Compa import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCrane import core.game.node.entity.Entity import core.game.node.entity.player.Player +import core.game.world.map.RegionManager import core.plugin.Initializable import core.game.world.map.zone.MapZone import core.game.world.map.zone.ZoneBorders import core.game.world.map.zone.ZoneBuilder import core.plugin.Plugin +import org.rs09.consts.Scenery /** * This updates the Elemental Workshop II state whenever the player enters From 9db6993bebecce6c97c2ddb522e976c79410055e Mon Sep 17 00:00:00 2001 From: aidan Date: Mon, 26 Jan 2026 14:55:01 -0600 Subject: [PATCH 07/45] fuck arios --- .../quest/elementalworkshop2/EW2Listeners.kt | 62 +++++++++---------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 61e140cb8..ab55a9a33 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -123,32 +123,20 @@ class EW2Listeners : InteractionListener { val mat = getVarbit(player, EW2CraneStateVarbit) val pos = getVarbit(player, EW2CranePosVarbit) - // crane location [1952, 5143, 2] - //removeScenery(scenery) - //addScenery(scenery) - //getScenery(scenery) - //replaceScenery(scenery) - val index = (mat * 4) + pos val newId = craneStates[index] - val center = Location.create(1954, 5145, 2) + val craneLoc = Location.create(1952, 5143, 2) + val currentObject = getScenery(craneLoc) - for (x in -2..2) { - for (y in -2..2) { - val checkLoc = center.transform(x, y, 0) + // check an object actually exists here + if (currentObject != null) { - // look for the object at this specific tile - val currentObject = RegionManager.getObject(checkLoc.z, checkLoc.x, checkLoc.y) ?: continue - //val currentObject = getScenery(checkLoc.z, checkLoc.x, checkLoc.y) ?: continue - // crane is an object not a scenery to get + // check that the object is a crane + if (craneStates.contains(currentObject.id)) { - - // check if the object at this tile is a crane - if (craneStates.contains(currentObject.id)) { - if (currentObject.id != newId) { - replaceScenery(currentObject, newId, -1) - } - return + // replace crane if current state doesn't match what is called for + if (currentObject.id != newId) { + replaceScenery(currentObject, newId, -1) } } } @@ -187,22 +175,10 @@ class EW2Listeners : InteractionListener { // values for the cogs val cogRange = (Scenery.COG_18667..Scenery.COG_18684).toList().toIntArray() // 18 different cogs. That is enough to give each cog a static/fwd spin/rev spin animation. - val smallCogs = intArrayOf(Scenery.COG_18673, Scenery.COG_18667, Scenery.COG_18670) // pin 1, pin 2, pin 3 val medCogs = intArrayOf(Scenery.COG_18674, Scenery.COG_18668, Scenery.COG_18671) // pin 1, pin 2, pin 3 val largeCogs = intArrayOf(Scenery.COG_18675, Scenery.COG_18669, Scenery.COG_18672) // pin 1, pin 2, pin 3 - // offset by 9 from the static cogs. this is a guess for now (see below) - val smallMovingCogs = intArrayOf(Scenery.COG_18682, Scenery.COG_18676, Scenery.COG_18679) - val medMovingCogs = intArrayOf(Scenery.COG_18683, Scenery.COG_18677, Scenery.COG_18680) - val largeMovingCogs = intArrayOf(Scenery.COG_18684, Scenery.COG_18678, Scenery.COG_18681) - - // todo it'd be nice to have a function that swapped the cog scenery for an animated version. - // function to swap scenery values of the cogs based on the varbit settings. this makes them appear to spin. - fun syncCogs(player: Player) { - - } - // syncs the tank door based on state of varbit 2653 fun syncTankDoor(player: Player) { val doorState = getVarbit(player, EW2TankDoorVarbit) @@ -474,7 +450,6 @@ class EW2Listeners : InteractionListener { val craneMat = getVarbit(player, EW2CraneStateVarbit) // Crane State. 0 = broken, 1 = fixed/empty, 2 = ele bar, 3 = hot bar. val cartState = getVarbit(player, EW2JigCartVarbit) // Jig Cart state. 0 = Empty, 1 = ele bar, 2 = hot bar, 3 = smashed hot, 4 = smashed cooled, 5 = primed - // todo make sure the crane doesn't pick up other bars // facing north: pick up and drop off bar if (currentPos == 3 && nextPos == 2) { if (craneMat == 1 && (cartState == 1 || cartState == 2)) { @@ -552,6 +527,7 @@ class EW2Listeners : InteractionListener { // todo animate the hammer smash 1 -> { // ele bar setVarbit(player, EW2JigCartVarbit, 4) + //replaceScenery(getScenery(1945, 5154, 3), 18644, 3) // "hardy" is the real word used. I thought it was a typo for 'hammer' but it's not. https://en.wikipedia.org/wiki/Hardy_tool sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is not hot enough to flatten.") syncCart(player) @@ -744,6 +720,23 @@ class EW2Listeners : InteractionListener { return@onUseWith true } + //todo Since Arios is shit (https://gitlab.com/2009scape/2009scape/-/issues/1841) we can't act directly with pin 2. + // I have commented out what would be the correct function below in case the issue ever gets fixed. + // As a hack, the player is only allowed to place the medium cog below the small cog. + // You can't remove the medium cog because it throws an exception (resetting the varbit will 'remove' the cog). kill me. + onUseWith(IntType.SCENERY, intArrayOf(Items.MEDIUM_COG_9725), 18673) { player, item, _ -> + when(item.id) { + Items.MEDIUM_COG_9725 -> { + setVarbit(player, EW2CogVarbit2, 2, true) + sendItemDialogue(player, item, "You put the medium cog onto the shaft.") + animate(player, 537) + removeItem(player, item) + } + } + return@onUseWith true + } + + /* // use cog with pin 2 // pin 2: 2656: val 0 is scenery 18664 (pin). val 1 is scenery 18667 (small cog). val 2 is scenery 18668 (med cog). val 3 is scenery 18669 (large cog) onUseWith(IntType.SCENERY, intArrayOf(Items.SMALL_COG_9726, Items.MEDIUM_COG_9725, Items.LARGE_COG_9724), Scenery.PIN_18664) { player, item, _ -> @@ -769,6 +762,7 @@ class EW2Listeners : InteractionListener { } return@onUseWith true } + */ // use cog with pin 3 // pin 3: 2657: val 0 is scenery 18666 (pin). val 1 is scenery 18670 (small cog). val 2 is scenery 18671 (med cog). val 3 is scenery 18672 (large cog) From 8e261c65d2da9bd5608d5c07d5cd2b2e58ad4ca2 Mon Sep 17 00:00:00 2001 From: aidan Date: Tue, 27 Jan 2026 07:10:38 -0600 Subject: [PATCH 08/45] need to finish the water section and quest log --- .../quest/elementalworkshop/EWListeners.kt | 312 +++++++++++------- .../quest/elementalworkshop2/EW2Listeners.kt | 168 +++++++--- .../seers/quest/elementalworkshop2/EW2Zone.kt | 11 +- .../elementalworkshop2/ElementalWorkshop2.kt | 8 +- .../quest/elementalworkshop2/JigCartNPC.kt | 43 +++ .../system/command/sets/TeleportCommandSet.kt | 4 +- 6 files changed, 358 insertions(+), 188 deletions(-) create mode 100644 Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt index acc48c6f6..91eb22bb0 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt @@ -257,132 +257,206 @@ class EWListeners : InteractionListener { } // Workbenches/Anvil - // Before completing EW1 you can only smith and elemental shield. During EW2 you have the option to make a shield, claw, or helm. - onUseWith(IntType.SCENERY, Items.ELEMENTAL_METAL_2893, Scenery.WORKBENCH_3402) { player, used, workbench -> - // if elemental workshop II is in progress, display a dialogue. otherwise, revert to smithing the elemental shield only. - if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) >= 5) { - sendDialogueOptions(player, "What would you like to make...", "An elemental shield.", "An elemental claw.", "An elemental helm.") - addDialogueAction(player) { _, buttonId -> - when (buttonId) { - 2 -> { // ele shield - // Warn player their smithing level is too low to make the shield - if (player.skills.getLevel(Skills.SMITHING) < 20) { - sendMessage(player, "You need a smithing level of 20 to create an elemental shield.") - return@addDialogueAction - } - // Warn player they don't have the required book in their inventory - if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.SLASHED_BOOK_9715, Items.BATTERED_BOOK_2886))) { - sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") - return@addDialogueAction - } - // Warn player they don't have a hammer in their inventory - if (!inInventory(player, Items.HAMMER_2347)) { - sendDialogue(player, "You don't have a tool available to shape the metal.") - return@addDialogueAction - } - // Successfully smith the elemental shield - lock(player, (animationDuration(smithElementalShield) + 2)) - runTask(player) { - face(player, workbench) - animate(player, smithElementalShield) - playAudio(player, smithElementalShieldSFX) - replaceSlot(player, used.asItem().slot, elementalShield) - rewardXP(player, Skills.SMITHING, 20.0) - sendMessage(player, "Following the instructions in the book you make an elemental shield.") + // Before completing EW1 you can only make an elemental shield from an elemental bar. + // Smithing an elemental shield will complete EW1. + // During EW2 you have the option to make an elemental shield, claw, or helm from an elemental bar. + // Smithing an elemental mind helm from an elemental mind bar will complete EW2. + onUseWith(IntType.SCENERY, Items.ELEMENTAL_METAL_2893, Items.ELEMENTAL_MIND_BAR_9728, Scenery.WORKBENCH_3402) { player, used, workbench -> + + when(used.id) { + Items.ELEMENTAL_METAL_2893 -> { + // if elemental workshop II is in progress, display a dialogue. otherwise, revert to smithing the elemental shield only. + if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) >= 5) { + sendDialogueOptions(player, "What would you like to make...", "An elemental shield.", "An elemental claw.", "An elemental helm.") + addDialogueAction(player) { _, buttonId -> + when (buttonId) { + 2 -> { // ele shield + // Warn player their smithing level is too low to make the shield + if (player.skills.getLevel(Skills.SMITHING) < 20) { + sendMessage(player, "You need a smithing level of 20 to create an elemental shield.") + return@addDialogueAction + } + // Warn player they don't have the required book in their inventory + if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.SLASHED_BOOK_9715, Items.BATTERED_BOOK_2886))) { + sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") + return@addDialogueAction + } + // Warn player they don't have a hammer in their inventory + if (!inInventory(player, Items.HAMMER_2347)) { + sendDialogue(player, "You don't have a tool available to shape the metal.") + return@addDialogueAction + } + // Successfully smith the elemental shield + lock(player, (animationDuration(smithElementalShield) + 2)) + runTask(player) { + face(player, workbench) + animate(player, smithElementalShield) + playAudio(player, smithElementalShieldSFX) + replaceSlot(player, used.asItem().slot, elementalShield) + rewardXP(player, Skills.SMITHING, 20.0) + sendMessage(player, "Following the instructions in the book you make an elemental shield.") + } + } + 3 -> { // ele claw + // Warn player their smithing level is too low to make the shield + if (player.skills.getLevel(Skills.SMITHING) < 20) { + sendMessage(player, "You need a smithing level of 20 to create an elemental claw.") + return@addDialogueAction + } + // Warn player they don't have the required book in their inventory + if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.CRANE_SCHEMATIC_9718))) { + sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") + return@addDialogueAction + } + // Warn player they don't have a hammer in their inventory + if (!inInventory(player, Items.HAMMER_2347)) { + sendDialogue(player, "You don't have a tool available to shape the metal.") + return@addDialogueAction + } + // Successfully smith the elemental shield + lock(player, (animationDuration(smithElementalShield) + 2)) + runTask(player) { + face(player, workbench) + animate(player, smithElementalShield) + playAudio(player, smithElementalShieldSFX) + replaceSlot(player, used.asItem().slot, Item(Items.CRANE_CLAW_9720)) + rewardXP(player, Skills.SMITHING, 20.0) + sendMessage(player, "Following the instructions from the schematic you make an elemental claw.") + } + } + 4 -> { // ele helm + // Warn player their smithing level is too low to make the helm + if (player.skills.getLevel(Skills.SMITHING) < 30) { + sendMessage(player, "You need a smithing level of 30 to create an elemental helm.") + return@addDialogueAction + } + // Warn player they don't have the required book in their inventory + if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.BEATEN_BOOK_9717))) { + sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") + return@addDialogueAction + } + // Warn player they don't have a hammer in their inventory + if (!inInventory(player, Items.HAMMER_2347)) { + sendDialogue(player, "You don't have a tool available to shape the metal.") + return@addDialogueAction + } + // Successfully smith the elemental helm + lock(player, (animationDuration(smithElementalShield) + 2)) + runTask(player) { + face(player, workbench) + animate(player, smithElementalShield) + playAudio(player, smithElementalShieldSFX) + replaceSlot(player, used.asItem().slot, Item(Items.ELEMENTAL_HELMET_9729)) + rewardXP(player, Skills.SMITHING, 20.0) + sendMessage(player, "Following the instructions in the book you make an elemental helm.") + } + } } } - 3 -> { // ele claw - // Warn player their smithing level is too low to make the shield - if (player.skills.getLevel(Skills.SMITHING) < 20) { - sendMessage(player, "You need a smithing level of 20 to create an elemental claw.") - return@addDialogueAction - } - // Warn player they don't have the required book in their inventory - if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.CRANE_SCHEMATIC_9718))) { - sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") - return@addDialogueAction - } - // Warn player they don't have a hammer in their inventory - if (!inInventory(player, Items.HAMMER_2347)) { - sendDialogue(player, "You don't have a tool available to shape the metal.") - return@addDialogueAction - } - // Successfully smith the elemental shield - lock(player, (animationDuration(smithElementalShield) + 2)) - runTask(player) { - face(player, workbench) - animate(player, smithElementalShield) - playAudio(player, smithElementalShieldSFX) - replaceSlot(player, used.asItem().slot, Item(Items.CRANE_CLAW_9720)) - rewardXP(player, Skills.SMITHING, 20.0) - sendMessage(player, "Following the instructions from the schematic you make an elemental claw.") - } + + // EW1 fallback + } else { + // Warn player their smithing level is too low to make the shield + if (player.skills.getLevel(Skills.SMITHING) < 20) { + sendMessage(player, "You need a smithing level of 20 to create an elemental shield.") + return@onUseWith true } - 4 -> { // ele helm - // Warn player their smithing level is too low to make the helm - if (player.skills.getLevel(Skills.SMITHING) < 30) { - sendMessage(player, "You need a smithing level of 30 to create an elemental helm.") - return@addDialogueAction - } - // Warn player they don't have the required book in their inventory - if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.BEATEN_BOOK_9717))) { - sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") - return@addDialogueAction - } - // Warn player they don't have a hammer in their inventory - if (!inInventory(player, Items.HAMMER_2347)) { - sendDialogue(player, "You don't have a tool available to shape the metal.") - return@addDialogueAction - } - // Successfully smith the elemental helm - lock(player, (animationDuration(smithElementalShield) + 2)) - runTask(player) { - face(player, workbench) - animate(player, smithElementalShield) - playAudio(player, smithElementalShieldSFX) - replaceSlot(player, used.asItem().slot, Item(Items.ELEMENTAL_HELMET_9729)) - rewardXP(player, Skills.SMITHING, 20.0) - sendMessage(player, "Following the instructions in the book you make an elemental helm.") - } + // Warn player they don't have the required book in their inventory + if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.SLASHED_BOOK_9715, Items.BATTERED_BOOK_2886))) { + sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") + return@onUseWith true + } + // Warn player they don't have a hammer in their inventory + if (!inInventory(player, Items.HAMMER_2347)) { + sendDialogue(player, "You don't have a tool available to shape the metal.") + return@onUseWith true + } + // Sanity error check (Should never get thrown) + if (!player.inventory.containsAll(*elementalShieldReqItems)) { + log(this::class.java, Log.ERR, "${player.username} tried to forge an elemental shield without all the required items.") + return@onUseWith false + } + // Successfully smith the elemental shield + lock(player, (animationDuration(smithElementalShield) + 2)) + runTask(player) { + face(player, workbench) + animate(player, smithElementalShield) + playAudio(player, smithElementalShieldSFX) + replaceSlot(player, used.asItem().slot, elementalShield) + rewardXP(player, Skills.SMITHING, 20.0) + sendMessage(player, "Following the instructions in the book you make an elemental shield.") + } + // Check to see if the quest is completed, if not, complete the quest + if (!player.questRepository.getQuest(Quests.ELEMENTAL_WORKSHOP_I).isCompleted(player)) { + player.questRepository.getQuest(Quests.ELEMENTAL_WORKSHOP_I).finish(player) } } } + Items.ELEMENTAL_MIND_BAR_9728 -> { + sendDialogueOptions(player, "Would you like to make...", "An elemental mine shield.", "An elemental mind helm.") + addDialogueAction(player) { _, buttonId -> + when (buttonId) { + 2 -> { // ele mind shield + // Warn player their smithing level is too low to make the shield + if (player.skills.getLevel(Skills.SMITHING) < 30) { + sendMessage(player, "You need a smithing level of 30 to create an elemental shield.") + return@addDialogueAction + } + // Warn player they don't have the required book in their inventory + if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.BEATEN_BOOK_9717))) { + sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") + return@addDialogueAction + } + // Warn player they don't have a hammer in their inventory + if (!inInventory(player, Items.HAMMER_2347)) { + sendDialogue(player, "You don't have a tool available to shape the metal.") + return@addDialogueAction + } + // Successfully smith the elemental mind shield + lock(player, (animationDuration(smithElementalShield) + 2)) + runTask(player) { + face(player, workbench) + animate(player, smithElementalShield) + playAudio(player, smithElementalShieldSFX) + replaceSlot(player, used.asItem().slot, Item(Items.MIND_SHIELD_9731)) + rewardXP(player, Skills.SMITHING, 30.0) + sendMessage(player, "Following the instructions in the book you make an elemental mind shield.") + } + } + 3 -> { // ele mind helm + // Warn player their smithing level is too low to make the helm + if (player.skills.getLevel(Skills.SMITHING) < 30) { + sendMessage(player, "You need a smithing level of 30 to create an elemental mind helm.") + return@addDialogueAction + } + // Warn player they don't have the required book in their inventory + if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.BEATEN_BOOK_9717))) { + sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") + return@addDialogueAction + } + // Warn player they don't have a hammer in their inventory + if (!inInventory(player, Items.HAMMER_2347)) { + sendDialogue(player, "You don't have a tool available to shape the metal.") + return@addDialogueAction + } + // Successfully smith the elemental helm + lock(player, (animationDuration(smithElementalShield) + 2)) + runTask(player) { + face(player, workbench) + animate(player, smithElementalShield) + playAudio(player, smithElementalShieldSFX) + replaceSlot(player, used.asItem().slot, Item(Items.MIND_HELMET_9733)) + rewardXP(player, Skills.SMITHING, 30.0) + sendMessage(player, "Following the instructions in the book you make an elemental mind helm.") - // EW1 fallback - } else { - // Warn player their smithing level is too low to make the shield - if (player.skills.getLevel(Skills.SMITHING) < 20) { - sendMessage(player, "You need a smithing level of 20 to create an elemental shield.") - return@onUseWith true - } - // Warn player they don't have the required book in their inventory - if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.SLASHED_BOOK_9715, Items.BATTERED_BOOK_2886))) { - sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") - return@onUseWith true - } - // Warn player they don't have a hammer in their inventory - if (!inInventory(player, Items.HAMMER_2347)) { - sendDialogue(player, "You don't have a tool available to shape the metal.") - return@onUseWith true - } - // Sanity error check (Should never get thrown) - if (!player.inventory.containsAll(*elementalShieldReqItems)) { - log(this::class.java, Log.ERR, "${player.username} tried to forge an elemental shield without all the required items.") - return@onUseWith false - } - // Successfully smith the elemental shield - lock(player, (animationDuration(smithElementalShield) + 2)) - runTask(player) { - face(player, workbench) - animate(player, smithElementalShield) - playAudio(player, smithElementalShieldSFX) - replaceSlot(player, used.asItem().slot, elementalShield) - rewardXP(player, Skills.SMITHING, 20.0) - sendMessage(player, "Following the instructions in the book you make an elemental shield.") - } - // Check to see if the quest is completed, if not, complete the quest - if (!player.questRepository.getQuest(Quests.ELEMENTAL_WORKSHOP_I).isCompleted(player)) { - player.questRepository.getQuest(Quests.ELEMENTAL_WORKSHOP_I).finish(player) + // complete quest if it's not done. + if (!isQuestComplete(player, Quests.ELEMENTAL_WORKSHOP_II)) { + finishQuest(player, Quests.ELEMENTAL_WORKSHOP_II) + } + } + } + } + } } } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index ab55a9a33..3a5832ae8 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -8,6 +8,7 @@ import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2 import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CranePosVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CraneStateVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2DoorLeverVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2ExtractorGun import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2FanLeverVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2HatchVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2JigCartVarbit @@ -18,11 +19,14 @@ import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2 import core.api.* import core.game.interaction.IntType import core.game.interaction.InteractionListener +import core.game.node.entity.impl.WalkingQueue import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills +import core.game.system.task.Pulse import core.game.world.map.Location import core.game.world.map.RegionManager +import core.game.world.map.path.Pathfinder import org.rs09.consts.* class EW2Listeners : InteractionListener { @@ -98,6 +102,7 @@ class EW2Listeners : InteractionListener { NPCs.JIG_CART_4918, // smashed white bar ) + // locations the jig cart can be at val jigCartLocations = arrayOf( Location.create(1954, 5147, 2), // south (start) Location.create(1946, 5155, 2), // west (hammer) @@ -105,6 +110,7 @@ class EW2Listeners : InteractionListener { Location.create(1961, 5155, 2) // east (fans) ) + // tank door states are currently not updated due to Arios. val tankDoorStates = intArrayOf( Scenery.DOOR_18651, // door closed Scenery.DOOR_18652, @@ -145,67 +151,52 @@ class EW2Listeners : InteractionListener { // update the jig cart based on varbit status fun syncCart(player: Player) { val cartState = getVarbit(player, EW2JigCartVarbit) - val cartPosIndex = getVarbit(player, EW2CartLeverVarbit) - + val cartPosIndex = getVarbit(player, EW2CartLeverVarbit) // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east val targetId = jigCartStates[cartState] val targetLocation = jigCartLocations[cartPosIndex] - // find existing cart + // find cart val trackCenter = Location.create(1953, 5155, 2) - val existingCart = RegionManager.getLocalNpcs(trackCenter, 15).find { - it.id in jigCartStates - } - + val existingCart = RegionManager.getLocalNpcs(trackCenter, 15).find { it.id in jigCartStates } if (existingCart != null) { // update appearance if (existingCart.id != targetId) { existingCart.transform(targetId) } - // update location - if (existingCart.location != targetLocation) { - // todo it would be nice if the cart moved instead of teleporting. try and make the tracks walkable? + // failsafe to reset the cart where it's supposed to be + if (!existingCart.walkingQueue.isMoving && existingCart.location != targetLocation) { existingCart.teleport(targetLocation) } + + // if there is no cart, spawn a new one (like if this is the quest start) } else { - // Spawn if missing (like if this is the quest start) val newCart = NPC(targetId, targetLocation) newCart.init() } } // values for the cogs - val cogRange = (Scenery.COG_18667..Scenery.COG_18684).toList().toIntArray() // 18 different cogs. That is enough to give each cog a static/fwd spin/rev spin animation. + val cogRange = (Scenery.COG_18667..Scenery.COG_18684).toList().toIntArray() // 18 different cogs. I'm guessing that is enough to give each cog a static/fwd/rev state (probably). val smallCogs = intArrayOf(Scenery.COG_18673, Scenery.COG_18667, Scenery.COG_18670) // pin 1, pin 2, pin 3 val medCogs = intArrayOf(Scenery.COG_18674, Scenery.COG_18668, Scenery.COG_18671) // pin 1, pin 2, pin 3 val largeCogs = intArrayOf(Scenery.COG_18675, Scenery.COG_18669, Scenery.COG_18672) // pin 1, pin 2, pin 3 - // syncs the tank door based on state of varbit 2653 - fun syncTankDoor(player: Player) { - val doorState = getVarbit(player, EW2TankDoorVarbit) + // visualizes the steam press smasher + // todo it doesn't work right now + fun steamPress() { + val pressId = Scenery.STEAM_PRESS_18643 + val currentObject = getScenery(Location.create(1945, 5154, 3)) - val targetId = tankDoorStates[doorState] - val center = Location.create(1953, 5164, 2) + // check an object actually exists here + if (currentObject != null) { + //println(currentObject.type) - // Scan the 5x5 area around the tank door location - for (x in -2..2) { - for (y in -2..2) { - val checkLoc = center.transform(x, y, 0) - val currentObject = RegionManager.getObject(checkLoc.z, checkLoc.x, checkLoc.y) ?: continue - - sendMessage(player, "$currentObject") - sendMessage(player,"$doorState") - - // Check if the object is any of the possible door states - if (tankDoorStates.contains(currentObject.id)) { - if (currentObject.id != targetId) { - replaceScenery(currentObject, targetId, -1) - } - return - } + // check that the object is the press + if (pressId == currentObject.id) { + replaceScenery(currentObject, Scenery.STEAM_PRESS_18645, 5) // either this or Scenery.STEAM_PRESS_18644 } } } - } override fun defineListeners() { @@ -312,6 +303,12 @@ class EW2Listeners : InteractionListener { return@on true } + // body doors (locked) + on(intArrayOf(Scenery.DOOR_18702, Scenery.DOOR_18703), IntType.SCENERY, "open") { player, _ -> + sendMessage(player, "The door is locked.") + return@on true + } + // schematic crate on(Scenery.SCHEMATIC_CRATE_18711, IntType.SCENERY, "take-from") { player, _ -> sendDialogueOptions(player, "There are two schematics here...", "Crane schematic", "Lever schematic") @@ -343,6 +340,14 @@ class EW2Listeners : InteractionListener { val doorPos = getVarbit(player, EW2DoorLeverVarbit) // 1 if door is open val fanPos = getVarbit(player, EW2FanLeverVarbit) // 1 if the fan is on + // find the cart + val trackCenter = Location.create(1953, 5155, 2) + val existingCart = RegionManager.getLocalNpcs(trackCenter, 15).find { it.id in jigCartStates } + if (existingCart == null) { + sendMessage(player, "CART MISSING. LOG IN AND OUT TO RESET IT.") + return@on true + } + // crane must be up if (currentPos == 0 && cranePos == 2) { sendMessage(player, "You cannot do that while the claw is down.") @@ -359,13 +364,44 @@ class EW2Listeners : InteractionListener { sendMessage(player, "You cannot do that while the fan is on.") return@on true } + // cart must be currently stopped + if (existingCart.walkingQueue.isMoving) { + return@on true + } - // positions cycles 0 -> 1 -> 2 -> 3 -> 0 etc. - val nextPos = (currentPos + 1) % 4 + // set path + val path = when (currentPos) { + // south: south (crane) to west (hammer) + 0 -> arrayOf(Location.create(1954, 5147, 2), Location.create(1946, 5147, 2), Location.create(1946, 5155, 2)) - animate(player, Animations.HUMAN_PULL_LEVER_4909) - setVarbit(player, EW2CartLeverVarbit, nextPos, true) - syncCart(player) + // west: west (hammer) to north (water) + 1 -> arrayOf(Location.create(1946, 5155, 2), Location.create(1946, 5162, 2), Location.create(1953, 5162, 2)) + + // north: north (water) to east (fan) + 2 -> arrayOf(Location.create(1953, 5162, 2), Location.create(1961, 5162, 2), Location.create(1961, 5155, 2)) + + // east: east (fan) to south (crane) + 3 -> arrayOf(Location.create(1961, 5155, 2), Location.create(1961, 5147, 2), Location.create(1954, 5147, 2)) + + // else nothing + else -> arrayOf() + } + + if (path.isNotEmpty()) { + animate(player, Animations.HUMAN_PULL_LEVER_4909) + + // set the next varbit state + val nextPos = (currentPos + 1) % 4 + setVarbit(player, EW2CartLeverVarbit, nextPos, true) + + // todo there are weird bugs with walkingQueue. cart sometimes veers off randomly to one of the other destinations. just teleporting for now. + // move + //existingCart.walkingQueue.reset() + //for (step in path) { + // existingCart.walkingQueue.addPath(step.x, step.y) + //} + teleport(existingCart, path[2]) + } return@on true } @@ -522,28 +558,31 @@ class EW2Listeners : InteractionListener { if (cartPos == 1) { when (cartState) { 0 -> { // empty + steamPress() sendDialogue(player, "You pull the lever and the press operates, but there is nothing on the cart to flatten.") } - // todo animate the hammer smash 1 -> { // ele bar setVarbit(player, EW2JigCartVarbit, 4) - //replaceScenery(getScenery(1945, 5154, 3), 18644, 3) + steamPress() // "hardy" is the real word used. I thought it was a typo for 'hammer' but it's not. https://en.wikipedia.org/wiki/Hardy_tool sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is not hot enough to flatten.") syncCart(player) } 2 -> { // hot ele bar setVarbit(player, EW2JigCartVarbit, 3) + steamPress() // "hardy" is correct. see above. sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar has been flattened.") syncCart(player) } else -> { // smashed hot, smashed cooled, or primed + steamPress() sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is already flattened.") } } } else { // cart in wrong position + steamPress() sendDialogue(player, "You pull the lever and the press operates, but the jig cart is not underneath it.") } } else { @@ -582,20 +621,12 @@ class EW2Listeners : InteractionListener { } // todo north lever + // todo see below, note that the door cannot have its scenery updated because it overlaps with the track and our limited engine can't find it. // north lever that opens and closes the door on(Scenery.AN_OLD_LEVER_18648, IntType.SCENERY, "pull") { player, _ -> val currentState = getVarbit(player, EW2TankDoorVarbit) - // Increment from 0-7, or reset to 0 if at 8 - val nextState = if (currentState < 8) currentState + 1 else 0 - val trackscen = getScenery(1953, 5162, 2)!! - addScenery(18751, Location.create(1951, 5159, 2)) - removeScenery(trackscen) - - setVarbit(player, EW2TankDoorVarbit, nextState, true) - syncTankDoor(player) - return@on true } @@ -666,6 +697,7 @@ class EW2Listeners : InteractionListener { return@on true } + // todo see below, note that the pin 2 cannot be taken from because it overlaps with pin 1 and our limited engine can't find it. // taking the cogs from the pins on(cogRange, IntType.SCENERY, "take") { player, node -> // determine what to take @@ -790,6 +822,7 @@ class EW2Listeners : InteractionListener { return@onUseWith true } + // todo see above, note that the fan scenery cannot be animated :( because it must overlap with some other object and our limited engine can't find it. // fan lever. Authentically this check should allow the machine to start and the fan to either not start, start backwards, or start correctly, but I'm simplifying it for my sanity. on(Scenery.AN_OLD_LEVER_18663, IntType.SCENERY, "pull") { player, _ -> animate(player, Animations.HUMAN_PULL_LEVER_4909) @@ -812,10 +845,6 @@ class EW2Listeners : InteractionListener { setVarbit(player, EW2JigCartVarbit, 5, true) syncCart(player) } - //todo animate the fan somehow - // find scenery ID {FANSTOPPED} or {FANSTARTED} near tile Location.create(1961, 5154, 2). - // swap for scenery {FANSTOPPED} if EW2FanStateVarbit is 0 or {FANSTARTED} if EW2FanStateVarbit is 1 - } else { // turn off sendDialogue(player, "The machine grinds to a standstill.") @@ -833,6 +862,37 @@ class EW2Listeners : InteractionListener { ** ------------------------ */ + // todo add graphics to this section + + // use bar with extractor gun + onUseWith(IntType.SCENERY, intArrayOf(Items.PRIMED_BAR_9727), Scenery.EXTRACTOR_GUN_18693) { player, item, _ -> + setVarbit(player, EW2ExtractorGun, 1, true) + return@onUseWith true + } + + // take primed bar + on(intArrayOf(Scenery.EXTRACTOR_GUN_18694), IntType.SCENERY, "take-from") { player, _ -> + addItemOrDrop(player, Items.PRIMED_BAR_9727) + setVarbit(player, EW2ExtractorGun, 0, true) + return@on true + } + + // take mind bar + on(intArrayOf(Scenery.EXTRACTOR_GUN_18695), IntType.SCENERY, "take-from") { player, _ -> + addItemOrDrop(player, Items.ELEMENTAL_MIND_BAR_9728) + setVarbit(player, EW2ExtractorGun, 0, true) + return@on true + } + + // sit in chair + on(intArrayOf(Scenery.EXTRACTOR_HAT_18690), IntType.SCENERY, "operate") { player, _ -> + // if bar is in place + if(getVarbit(player, EW2ExtractorGun) == 1) { + setVarbit(player, EW2ExtractorGun, 2, true) + } + return@on true + } + /** * EXTRACTOR_GUN_18693 * EXTRACTOR_GUN_18694 diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt index 33097f715..fd4048c45 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt @@ -4,23 +4,20 @@ import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Compa import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCrane import core.game.node.entity.Entity import core.game.node.entity.player.Player -import core.game.world.map.RegionManager import core.plugin.Initializable import core.game.world.map.zone.MapZone import core.game.world.map.zone.ZoneBorders import core.game.world.map.zone.ZoneBuilder import core.plugin.Plugin -import org.rs09.consts.Scenery /** - * This updates the Elemental Workshop II state whenever the player enters - * Controls things like crane state and jig cart state. + * This updates the Elemental Workshop II state whenever the player enters, + * keeping the equipment state synced to the saved varbits. */ @Initializable class EW2Zone : MapZone("Elemental Workshop II", true), Plugin { - override fun newInstance(arg: Any?): EW2Zone { ZoneBuilder.configure(this) return this @@ -32,18 +29,14 @@ class EW2Zone : MapZone("Elemental Workshop II", true), Plugin { // updates the workshop state based on the varbits for Elemental Workshop II override fun enter(e: Entity?): Boolean { - if(e is Player) { syncCrane(e) syncCart(e) } - return super.enter(e) } override fun fireEvent(identifier: String?, vararg args: Any?): Any { return Unit } - - } \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt index ca17f62ab..0fe6b7a67 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt @@ -18,7 +18,7 @@ import org.rs09.consts.Items @Initializable class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2StageVarbit, 0, 1, 11) { - // a truckload of varbits + // a truckload of varbits - https://chisel.weirdgloop.org/varbs/display?varplayer=896 companion object { const val EW2Varp1 = 896 // first EW2 varp that packages varbits 2639-2652 const val EW2StageVarbit = 2639 // Main quest progress. Incremented with quest stages. @@ -37,8 +37,8 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Sta const val EW2ValveEastVarbit = 2652 // water valve east. 0 = closed, 1 = open const val EW2Varp2 = 897 // second EW2 varp that packages varbits 2653-2664 - const val EW2TankDoorVarbit = 2653 // tank door, controlled by corkscrew lever. 0 = , 1 = , 2 = , 3 = , 4 = , 5 = , 6 = , 7 = , 8 = - const val EW2DoorLeverVarbit = 2654 // door lever. 0 = closed, 1 = open + const val EW2TankDoorVarbit = 2653 // tank door holding state, controlled by corkscrew lever. 0 = closed, 1 = , 2 = , 3 = , 4 = , 5 = , 6 = , 7 = , 8 = // nine values. jig cart has six + const val EW2DoorLeverVarbit = 2654 // door lever for tank. 0 = closed, 1 = open const val EW2CogVarbit1 = 2655 // cog pin. 0 = no cog, 1 = small, 2 = med, 3 = large const val EW2CogVarbit2 = 2656 // cog pin. 0 = no cog, 1 = small, 2 = med, 3 = large const val EW2CogVarbit3 = 2657 // cog pin. 0 = no cog, 1 = small, 2 = med, 3 = large @@ -46,7 +46,7 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Sta const val EW2FanLeverVarbit = 2659 // fan switch. 0 = fan off, 1 = fan on //const val EW2FanStateVarbit = 2660 // fan blades. 0 = off, 1 = forward, 2 = reverse //const val EW2FanStationVarbit = 2661 // overlaps cogs, fan switch, and fan blades varbits - //const val EW2 = 2662 // extractor gun. 0 = empty, 1 = primed bar, 2 = mind bar + const val EW2ExtractorGun = 2662 // extractor gun. 0 = empty, 1 = primed bar, 2 = mind bar //const val EW2 = 2663 // //const val EW2 = 2664 // values from 1 to 7 back at the dig site library where you find the book. } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt new file mode 100644 index 000000000..7967c2e65 --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt @@ -0,0 +1,43 @@ +package content.region.kandarin.seers.quest.elementalworkshop2 + +import core.game.node.entity.npc.AbstractNPC +import core.game.node.entity.npc.NPC +import core.game.node.entity.npc.NPCBehavior +import core.game.world.map.Location +import core.game.world.map.RegionManager +import core.game.world.map.build.RegionFlags +import core.game.world.map.path.ClipMaskSupplier +import org.rs09.consts.NPCs + +/** + * Jig cart in Elemental Workshop II + * The only reason I have this here is to set a custom clipping mask to allow the cart to walk over the track scenery. + * Right now the walking part is broken, so the cart just teleports. + */ + +class JigCartNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + + override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { + return JigCartNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.JIG_CART_4913, NPCs.JIG_CART_4914, NPCs.JIG_CART_4915, NPCs.JIG_CART_4916, NPCs.JIG_CART_4917, NPCs.JIG_CART_4918,) + } +} + +class JigCartNPCBehavior : NPCBehavior(NPCs.JIG_CART_4913, NPCs.JIG_CART_4914, NPCs.JIG_CART_4915, NPCs.JIG_CART_4916, NPCs.JIG_CART_4917, NPCs.JIG_CART_4918,) { + + override fun getClippingSupplier (self: NPC) : ClipMaskSupplier { + return CartClipper + } +} + + +// this clip mask lets the jig cart walk through scenery +object CartClipper : ClipMaskSupplier { + override fun getClippingFlag (z: Int, x: Int, y: Int) : Int { + val flag = RegionManager.getClippingFlag(z, x, y) + return flag and (RegionFlags.SOLID_TILE.inv()) and (RegionFlags.TILE_OBJECT.inv()) and (RegionFlags.OBJ_10.inv()) + } +} \ No newline at end of file diff --git a/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt b/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt index 30e4107c9..0f45f3766 100644 --- a/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt @@ -142,8 +142,8 @@ class TeleportCommandSet : CommandSet(Privilege.ADMIN){ /** * Finds a list of objects/sceneries in a region */ - define("findobjs", Privilege.ADMIN, "::findobjs REGION ID SCENERY ID", "Finds all locations of scenery objects of id."){player, args -> - if(args.size < 4) reject(player, "Usage: region_id scenery_id") + define("findobjs", Privilege.ADMIN, "::findobjs REGION ID SCENERY ID START SCENERY ID END", "Finds all locations of scenery objects of id."){player, args -> + if(args.size < 4) reject(player, "Usage: region_id scenery_id_start scenery_id_end") val regionId = args[1].toInt() val sceneryId = args[2].toInt() val sceneryIdEnd = args[3].toInt() From 60ce5a2390b2991ac5b3528083a36c4ff900b56d Mon Sep 17 00:00:00 2001 From: aidan Date: Fri, 30 Jan 2026 12:28:39 -0600 Subject: [PATCH 09/45] DONE --- .../quest/elementalworkshop/EWListeners.kt | 159 +++-- .../seers/quest/elementalworkshop/EWZone.kt | 52 ++ .../quest/elementalworkshop2/EW2Listeners.kt | 541 +++++++++++++++--- .../seers/quest/elementalworkshop2/EW2Zone.kt | 20 + .../elementalworkshop2/ElementalWorkshop2.kt | 167 ++++-- .../JunctionBoxInterface.kt | 3 + 6 files changed, 746 insertions(+), 196 deletions(-) create mode 100644 Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWZone.kt diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt index 91eb22bb0..d6d9660cd 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt @@ -17,6 +17,7 @@ import core.game.interaction.InteractionListener import core.game.interaction.IntType import core.tools.Log import content.data.Quests +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2StageVarbit /** * Listeners for the Elemental Workshop I quest @@ -261,7 +262,7 @@ class EWListeners : InteractionListener { // Smithing an elemental shield will complete EW1. // During EW2 you have the option to make an elemental shield, claw, or helm from an elemental bar. // Smithing an elemental mind helm from an elemental mind bar will complete EW2. - onUseWith(IntType.SCENERY, Items.ELEMENTAL_METAL_2893, Items.ELEMENTAL_MIND_BAR_9728, Scenery.WORKBENCH_3402) { player, used, workbench -> + onUseWith(IntType.SCENERY, intArrayOf(Items.ELEMENTAL_METAL_2893, Items.ELEMENTAL_MIND_BAR_9728), Scenery.WORKBENCH_3402) { player, used, workbench -> when(used.id) { Items.ELEMENTAL_METAL_2893 -> { @@ -354,7 +355,7 @@ class EWListeners : InteractionListener { } } - // EW1 fallback + // EW1 fallback } else { // Warn player their smithing level is too low to make the shield if (player.skills.getLevel(Skills.SMITHING) < 20) { @@ -392,66 +393,101 @@ class EWListeners : InteractionListener { } } } - Items.ELEMENTAL_MIND_BAR_9728 -> { - sendDialogueOptions(player, "Would you like to make...", "An elemental mine shield.", "An elemental mind helm.") - addDialogueAction(player) { _, buttonId -> - when (buttonId) { - 2 -> { // ele mind shield - // Warn player their smithing level is too low to make the shield - if (player.skills.getLevel(Skills.SMITHING) < 30) { - sendMessage(player, "You need a smithing level of 30 to create an elemental shield.") - return@addDialogueAction - } - // Warn player they don't have the required book in their inventory - if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.BEATEN_BOOK_9717))) { - sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") - return@addDialogueAction - } - // Warn player they don't have a hammer in their inventory - if (!inInventory(player, Items.HAMMER_2347)) { - sendDialogue(player, "You don't have a tool available to shape the metal.") - return@addDialogueAction - } - // Successfully smith the elemental mind shield - lock(player, (animationDuration(smithElementalShield) + 2)) - runTask(player) { - face(player, workbench) - animate(player, smithElementalShield) - playAudio(player, smithElementalShieldSFX) - replaceSlot(player, used.asItem().slot, Item(Items.MIND_SHIELD_9731)) - rewardXP(player, Skills.SMITHING, 30.0) - sendMessage(player, "Following the instructions in the book you make an elemental mind shield.") - } - } - 3 -> { // ele mind helm - // Warn player their smithing level is too low to make the helm - if (player.skills.getLevel(Skills.SMITHING) < 30) { - sendMessage(player, "You need a smithing level of 30 to create an elemental mind helm.") - return@addDialogueAction - } - // Warn player they don't have the required book in their inventory - if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.BEATEN_BOOK_9717))) { - sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") - return@addDialogueAction - } - // Warn player they don't have a hammer in their inventory - if (!inInventory(player, Items.HAMMER_2347)) { - sendDialogue(player, "You don't have a tool available to shape the metal.") - return@addDialogueAction - } - // Successfully smith the elemental helm - lock(player, (animationDuration(smithElementalShield) + 2)) - runTask(player) { - face(player, workbench) - animate(player, smithElementalShield) - playAudio(player, smithElementalShieldSFX) - replaceSlot(player, used.asItem().slot, Item(Items.MIND_HELMET_9733)) - rewardXP(player, Skills.SMITHING, 30.0) - sendMessage(player, "Following the instructions in the book you make an elemental mind helm.") - // complete quest if it's not done. - if (!isQuestComplete(player, Quests.ELEMENTAL_WORKSHOP_II)) { - finishQuest(player, Quests.ELEMENTAL_WORKSHOP_II) + Items.ELEMENTAL_MIND_BAR_9728 -> { + // if EW2 is not complete, the player makes a mind helmet. after the quest, you have a choice. + if(!isQuestComplete(player, Quests.ELEMENTAL_WORKSHOP_II)) { + // Warn player their smithing level is too low to make the helm + if (player.skills.getLevel(Skills.SMITHING) < 30) { + sendMessage(player, "You need a smithing level of 30 to create an elemental mind helm.") + return@onUseWith true + } + // Warn player they don't have the required book in their inventory + if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.BEATEN_BOOK_9717))) { + sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") + return@onUseWith true + } + // Warn player they don't have a hammer in their inventory + if (!inInventory(player, Items.HAMMER_2347)) { + sendDialogue(player, "You don't have a tool available to shape the metal.") + return@onUseWith true + } + // Successfully smith the elemental helm and complete quest + lock(player, (animationDuration(smithElementalShield) + 2)) + runTask(player) { + face(player, workbench) + animate(player, smithElementalShield) + playAudio(player, smithElementalShieldSFX) + replaceSlot(player, used.asItem().slot, Item(Items.MIND_HELMET_9733)) + rewardXP(player, Skills.SMITHING, 30.0) + sendMessage(player, "Following the instructions in the book you make an elemental mind helm.") + + if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 10) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 11) + setVarbit(player, EW2StageVarbit, 11, true) + } + // complete quest if it's not done. + if (!isQuestComplete(player, Quests.ELEMENTAL_WORKSHOP_II)) { + finishQuest(player, Quests.ELEMENTAL_WORKSHOP_II) + } + } + // choice to make helmet or shield + } else { + sendDialogueOptions(player, "Would you like to make...", "An elemental mind shield.", "An elemental mind helm.") + addDialogueAction(player) { _, buttonId -> + when (buttonId) { + 2 -> { // ele mind shield + // Warn player their smithing level is too low to make the shield + if (player.skills.getLevel(Skills.SMITHING) < 30) { + sendMessage(player, "You need a smithing level of 30 to create an elemental mind shield.") + return@addDialogueAction + } + // Warn player they don't have the required book in their inventory + if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.BEATEN_BOOK_9717))) { + sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") + return@addDialogueAction + } + // Warn player they don't have a hammer in their inventory + if (!inInventory(player, Items.HAMMER_2347)) { + sendDialogue(player, "You don't have a tool available to shape the metal.") + return@addDialogueAction + } + // Successfully smith the elemental mind shield + lock(player, (animationDuration(smithElementalShield) + 2)) + runTask(player) { + face(player, workbench) + animate(player, smithElementalShield) + playAudio(player, smithElementalShieldSFX) + replaceSlot(player, used.asItem().slot, Item(Items.MIND_SHIELD_9731)) + rewardXP(player, Skills.SMITHING, 30.0) + sendMessage(player, "Following the instructions in the book you make an elemental mind shield.") + } + } + 3 -> { // ele mind helm + // Warn player their smithing level is too low to make the helm + if (player.skills.getLevel(Skills.SMITHING) < 30) { + sendMessage(player, "You need a smithing level of 30 to create an elemental mind helm.") + return@addDialogueAction + } + // Warn player they don't have the required book in their inventory + if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.BEATEN_BOOK_9717))) { + sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") + return@addDialogueAction + } + // Warn player they don't have a hammer in their inventory + if (!inInventory(player, Items.HAMMER_2347)) { + sendDialogue(player, "You don't have a tool available to shape the metal.") + return@addDialogueAction + } + // Successfully smith the elemental helm + lock(player, (animationDuration(smithElementalShield) + 2)) + runTask(player) { + face(player, workbench) + animate(player, smithElementalShield) + playAudio(player, smithElementalShieldSFX) + replaceSlot(player, used.asItem().slot, Item(Items.MIND_HELMET_9733)) + rewardXP(player, Skills.SMITHING, 30.0) + sendMessage(player, "Following the instructions in the book you make an elemental mind helm.") } } } @@ -459,7 +495,6 @@ class EWListeners : InteractionListener { } } } - return@onUseWith true } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWZone.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWZone.kt new file mode 100644 index 000000000..ac0b84f0b --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWZone.kt @@ -0,0 +1,52 @@ +package content.region.kandarin.seers.quest.elementalworkshop + +import content.data.Quests +import content.region.kandarin.seers.quest.elementalworkshop.EWUtils.BELLOWS_STATE +import content.region.kandarin.seers.quest.elementalworkshop.EWUtils.FURNACE_STATE +import content.region.kandarin.seers.quest.elementalworkshop.EWUtils.LEFT_WATER_CONTROL_STATE +import content.region.kandarin.seers.quest.elementalworkshop.EWUtils.RIGHT_WATER_CONTROL_STATE +import content.region.kandarin.seers.quest.elementalworkshop.EWUtils.WATER_WHEEL_STATE +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.game.world.map.zone.MapZone +import core.game.world.map.zone.ZoneBorders +import core.game.world.map.zone.ZoneBuilder +import core.plugin.Plugin + +/** + * This updates the Elemental Workshop I state whenever the player enters, if the quest is complete. + * Workshop should maintain state per this video: https://www.youtube.com/watch?v=YKSWisRgGZk + */ + +@Initializable +class EWZone : MapZone("Elemental Workshop I", true), Plugin { + + override fun newInstance(arg: Any?): EWZone { + ZoneBuilder.configure(this) + return this + } + + override fun configure() { + super.register(ZoneBorders(2688,9862,2744,9920)) + } + + // updates the workshop state based on the varbits for Elemental Workshop II + override fun enter(e: Entity?): Boolean { + if(e is Player) { + if(isQuestComplete(e, Quests.ELEMENTAL_WORKSHOP_I)) { + setVarbit(e, LEFT_WATER_CONTROL_STATE, 1) + setVarbit(e, RIGHT_WATER_CONTROL_STATE, 1) + setVarbit(e, WATER_WHEEL_STATE, 1) + setVarbit(e, FURNACE_STATE, 1) + setVarbit(e, BELLOWS_STATE, 1) + } + } + return super.enter(e) + } + + override fun fireEvent(identifier: String?, vararg args: Any?): Any { + return Unit + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 3a5832ae8..432aba94f 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -1,36 +1,57 @@ package content.region.kandarin.seers.quest.elementalworkshop2 import content.data.Quests +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2AllCogsVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CartLeverVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CogVarbit1 import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CogVarbit2 import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CogVarbit3 import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CranePosVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2CraneStateVarbit -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2DoorLeverVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2TankDoorStatusVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2ExtractorGun import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2FanLeverVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2FanStateVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2HatchVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2JigCartVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2MachineryVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2PipeVarbit import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2StageVarbit -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2TankDoorVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2TankDoorStateVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2ValveEastVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2ValveWestVarbit +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.attrDipped +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.attrFlattened +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.attrJigged +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.attrWatered +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.attrWinded +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.badHat +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.eHelmEquip +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.eShieldEquip +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.goodHat +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.mHelmEquip +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.mShieldEquip +import core.game.world.update.flag.context.Graphics import core.api.* import core.game.interaction.IntType import core.game.interaction.InteractionListener -import core.game.node.entity.impl.WalkingQueue import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills -import core.game.system.task.Pulse +import core.game.node.scenery.SceneryBuilder import core.game.world.map.Location import core.game.world.map.RegionManager -import core.game.world.map.path.Pathfinder import org.rs09.consts.* class EW2Listeners : InteractionListener { + // values from 530_synth_debug_names.txt that are still unused + //val ew2_dial_spin = 2326 + //val ew2_ambient_loop = 3149 + //val ew2_machine_loop = 3150 + + // gfx 809 mind shield equip, 810 mind helmet equip + /** * Quest structure and testing notes: * @@ -73,7 +94,7 @@ class EW2Listeners : InteractionListener { Scenery.OLD_CRANE_18636, // broken crane facing north, claw down Scenery.OLD_CRANE_18635, // broken crane facing north, claw up - // Varbit 2644 = 1: Fixed/Empty (18623=N-Up, 18624=N-Down, 18625=S-Up, 18626=S-Down) + // Varbit 2644 = 1: Fixed/Empty Scenery.OLD_CRANE_18626, // fixed crane facing south, claw down Scenery.OLD_CRANE_18625, // fixed crane facing south, claw up Scenery.OLD_CRANE_18624, // fixed crane facing north, claw down @@ -85,21 +106,21 @@ class EW2Listeners : InteractionListener { Scenery.OLD_CRANE_18628, // fixed crane with ele bar facing north, claw down Scenery.OLD_CRANE_18627, // fixed crane with ele bar facing north, claw up - // Varbit 2644 = 3: Mind Bar - Scenery.OLD_CRANE_18634, // fixed crane with mind bar facing south, claw down - Scenery.OLD_CRANE_18633, // fixed crane with mind bar facing south, claw up - Scenery.OLD_CRANE_18632, // fixed crane with mind bar facing north, claw down - Scenery.OLD_CRANE_18631 // fixed crane with mind bar facing north, claw up + // Varbit 2644 = 3: Hot Bar + Scenery.OLD_CRANE_18634, // fixed crane with hot bar facing south, claw down + Scenery.OLD_CRANE_18633, // fixed crane with hot bar facing south, claw up + Scenery.OLD_CRANE_18632, // fixed crane with hot bar facing north, claw down + Scenery.OLD_CRANE_18631 // fixed crane with hot bar facing north, claw up ) // cart state is stored in varbit 2643 val jigCartStates = intArrayOf( - NPCs.JIG_CART_4913, // empty - NPCs.JIG_CART_4914, // ele bar - NPCs.JIG_CART_4915, // hot ele bar - NPCs.JIG_CART_4916, // smashed hot bar - NPCs.JIG_CART_4917, // smashed ele bar - NPCs.JIG_CART_4918, // smashed white bar + NPCs.JIG_CART_4913, // 0 empty + NPCs.JIG_CART_4914, // 1 ele bar + NPCs.JIG_CART_4915, // 2 hot ele bar + NPCs.JIG_CART_4916, // 3 smashed hot bar + NPCs.JIG_CART_4917, // 4 smashed cooled bar + NPCs.JIG_CART_4918, // 5 primed bar ) // locations the jig cart can be at @@ -110,21 +131,27 @@ class EW2Listeners : InteractionListener { Location.create(1961, 5155, 2) // east (fans) ) - // tank door states are currently not updated due to Arios. + // tank door states stored in varbit 2653 val tankDoorStates = intArrayOf( - Scenery.DOOR_18651, // door closed - Scenery.DOOR_18652, - Scenery.DOOR_18653, - Scenery.DOOR_18654, - Scenery.DOOR_18655, - Scenery.DOOR_18656, - Scenery.DOOR_18657, - Scenery.DOOR_18658, - Scenery.DOOR_18659 + Scenery.DOOR_18651, // 0 door closed + Scenery.DOOR_18652, // 1 door opened, grabber inside + Scenery.DOOR_18653, // 2 door opened, grabber out + Scenery.DOOR_18654, // 3 door closed, hot bar inside + Scenery.DOOR_18655, // 4 door opened, hot bar inside + Scenery.DOOR_18656, // 5 door opened, hot bar out + Scenery.DOOR_18657, // 6 door closed, cooled bar inside + Scenery.DOOR_18658, // 7 door opened, cooled bar inside + Scenery.DOOR_18659 // 8 door opened, cooled bar out + ) + + // steam press scenery. right now I just use the third one to make the hammer appear + val steamPressStates = intArrayOf( + Scenery.STEAM_PRESS_18643, // top of the steam press + Scenery.STEAM_PRESS_18644, // press with ele bar cart + Scenery.STEAM_PRESS_18645 // press with nothing under it ) // position the crane based on varbit states - // todo it would be nice to animate this crane fun syncCrane(player: Player) { val mat = getVarbit(player, EW2CraneStateVarbit) val pos = getVarbit(player, EW2CranePosVarbit) @@ -141,8 +168,18 @@ class EW2Listeners : InteractionListener { if (craneStates.contains(currentObject.id)) { // replace crane if current state doesn't match what is called for + // todo it would be nice to animate this crane if (currentObject.id != newId) { replaceScenery(currentObject, newId, -1) + playAudio(player, Sounds.EW2_CRANE_TURN_3170) + //playAudio(player, Sounds.EW2_CRANE_LOWER_3169) + + // if bar is hot set attr for quest log + if(newId == 18634) { + if(!getAttribute(player, attrDipped, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { + setAttribute(player, attrDipped, true) + } + } } } } @@ -154,6 +191,7 @@ class EW2Listeners : InteractionListener { val cartPosIndex = getVarbit(player, EW2CartLeverVarbit) // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east val targetId = jigCartStates[cartState] val targetLocation = jigCartLocations[cartPosIndex] + val tankState = getVarbit(player, EW2TankDoorStateVarbit) // if tank state is 3, 4, 6, 7 the cart is inside the tank and should be removed. // find cart val trackCenter = Location.create(1953, 5155, 2) @@ -163,15 +201,23 @@ class EW2Listeners : InteractionListener { if (existingCart.id != targetId) { existingCart.transform(targetId) } - // failsafe to reset the cart where it's supposed to be + // failsafe to reset the cart where it's supposed to be. right now cart just teleports so this shouldn't trigger if (!existingCart.walkingQueue.isMoving && existingCart.location != targetLocation) { existingCart.teleport(targetLocation) } + // if cart is inside water tank, clear it + if (tankState == 3 || tankState == 4 || tankState == 6 || tankState == 7) { + existingCart.clear() + } - // if there is no cart, spawn a new one (like if this is the quest start) + // if there is no cart, spawn a new one (like if player is just logging in) } else { val newCart = NPC(targetId, targetLocation) newCart.init() + // if cart is inside water tank, clear it + if (tankState == 3 || tankState == 4 || tankState == 6 || tankState == 7) { + newCart.clear() + } } } @@ -181,20 +227,76 @@ class EW2Listeners : InteractionListener { val medCogs = intArrayOf(Scenery.COG_18674, Scenery.COG_18668, Scenery.COG_18671) // pin 1, pin 2, pin 3 val largeCogs = intArrayOf(Scenery.COG_18675, Scenery.COG_18669, Scenery.COG_18672) // pin 1, pin 2, pin 3 - // visualizes the steam press smasher - // todo it doesn't work right now - fun steamPress() { - val pressId = Scenery.STEAM_PRESS_18643 - val currentObject = getScenery(Location.create(1945, 5154, 3)) + /* + // updates the cog states (unused right now) + fun syncCogs(player: Player) { - // check an object actually exists here - if (currentObject != null) { - //println(currentObject.type) + } + */ - // check that the object is the press - if (pressId == currentObject.id) { - replaceScenery(currentObject, Scenery.STEAM_PRESS_18645, 5) // either this or Scenery.STEAM_PRESS_18644 + /* + // visualizes the steam press. right now all it does is spawn the hammer part. no anims or fancy stuff. + // if you just do a simple scenerybuilder.add it'll overwrite the track, so i've disabled it for now. + fun syncSteamPress(player: Player) { + SceneryBuilder.add(core.game.node.scenery.Scenery(steamPressStates[2], Location.create(1946, 5155, 2), 0)) + } + */ + + // syncs the water tank state. this is dependent on the valve states and the door state. also changes cart state (cooling bar) + fun syncTank(player: Player) { + val waterIn = getVarbit(player, EW2ValveWestVarbit) // 0 if valve closed, 1 if open + val waterOut = getVarbit(player, EW2ValveEastVarbit) // 0 if valve closed, 1 if open + val cart = getVarbit(player, EW2TankDoorStateVarbit) // whether the cart is in the tank or not + + if(waterIn == 0 && waterOut == 0) { // both valves closed, nothing happens + return + } else if(waterIn == 0 && waterOut == 1) { // drain is open, remove all added scenery + SceneryBuilder.remove(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, location(1951, 5164, 2))) // full tank + SceneryBuilder.remove(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18661, location(1951, 5164, 2))) // full tank + playAudio(player, Sounds.EW2_WATER_RACK_3184) // drain? + return + } else if(waterIn == 1 && waterOut == 0) { // inlet is open, fill tank + SceneryBuilder.add(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18661, location(1951, 5164, 2))) // full tank + playAudio(player, Sounds.EW2_WATER_FLOW_3183) // fill? + if(cart == 3) { // if cart state is hot bar, transform to cool bar. + setVarbit(player, EW2TankDoorStateVarbit, 6, true) + setVarbit(player, EW2JigCartVarbit, 4, true) + syncTankDoor(player) + playAudio(player, Sounds.EW2_COOL_BAR_3168) + if(!getAttribute(player, attrWatered, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { + setAttribute(player, attrWatered, true) + } } + return + } else if(waterIn == 1 && waterOut == 1) { // inlet and drain are open, tank is damp + SceneryBuilder.add(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, location(1951, 5164, 2))) // damp tank + playAudio(player, Sounds.EW2_WATER_FLOW_3183) // fill? + return + } + } + + // syncs the tank door state. this is dependent on the corkscrew lever and cart state + fun syncTankDoor(player: Player) { + val state = getVarbit(player, EW2TankDoorStateVarbit) + val doorLoc = Location.create(1953, 5162, 2) + val currentObject = getScenery(doorLoc) + val targetId = tankDoorStates[state] + + // remove old scenery then add the new one + // todo right now this also deletes a piece of track that shares a tile with the door, but I don't really care. + if(currentObject != null && currentObject.id != targetId) { + SceneryBuilder.remove(core.game.node.scenery.Scenery(currentObject.id, doorLoc, 1)) + SceneryBuilder.add(core.game.node.scenery.Scenery(targetId, doorLoc, 1)) + } + } + + // updates fan state. fun fact, the fan state varbit isn't saved even if you say to save it. gotta make sure it's set on login. + fun syncFan(player: Player) { + val fanMachine = getVarbit(player, EW2FanLeverVarbit) + if (fanMachine == 1) { + setVarbit(player, EW2FanStateVarbit, 1) + } else { + setVarbit(player, EW2FanStateVarbit, 0) } } } @@ -247,6 +349,7 @@ class EW2Listeners : InteractionListener { } if(getVarbit(player, EW2MachineryVarbit) == 0) setVarbit(player, EW2MachineryVarbit, 1, true) + // todo the wiki notes that you must actually walk the path for the varbit to flip. Here i just flip it as soon as you read the scroll. return@on true } @@ -255,10 +358,12 @@ class EW2Listeners : InteractionListener { if(!inInventory(player, Items.KEY_9722)) { sendItemDialogue(player, Items.KEY_9722, "You find a key hidden in the third pipe.") addItemOrDrop(player, Items.KEY_9722) + } else { + sendDialogue(player, "You search the machinery but find nothing.") } if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 3) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 4) - setVarbit(player, EW2StageVarbit, 4) + setVarbit(player, EW2StageVarbit, 4, true) } return@on true } @@ -277,7 +382,7 @@ class EW2Listeners : InteractionListener { if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 4) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 5) - setVarbit(player, EW2StageVarbit, 5) + setVarbit(player, EW2StageVarbit, 5, true) } return@on true } @@ -337,8 +442,10 @@ class EW2Listeners : InteractionListener { on(Scenery.LEVER_18620, IntType.SCENERY, "pull") { player, _ -> val currentPos = getVarbit(player, EW2CartLeverVarbit) // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east val cranePos = getVarbit(player, EW2CranePosVarbit) // 2 if crane is down on north side - val doorPos = getVarbit(player, EW2DoorLeverVarbit) // 1 if door is open - val fanPos = getVarbit(player, EW2FanLeverVarbit) // 1 if the fan is on + val doorPos = getVarbit(player, EW2TankDoorStatusVarbit) // 1 if door is open + val tankState = getVarbit(player, EW2TankDoorStateVarbit) + val fanState = getVarbit(player, EW2FanLeverVarbit) // 1 if the fan is on + // find the cart val trackCenter = Location.create(1953, 5155, 2) @@ -358,9 +465,8 @@ class EW2Listeners : InteractionListener { sendMessage(player, "You cannot do that while the door is open.") return@on true } - // todo check the corkscrew because i think that'll shift the cart off the track - // door must be closed - if (currentPos == 3 && fanPos == 1) { + // fan must be off + if (currentPos == 3 && fanState == 1) { sendMessage(player, "You cannot do that while the fan is on.") return@on true } @@ -368,6 +474,11 @@ class EW2Listeners : InteractionListener { if (existingCart.walkingQueue.isMoving) { return@on true } + // cart must not be in the water tank + if (tankState == 3 || tankState == 4 || tankState == 6 || tankState == 7) { + sendMessage(player, "You cannot do that while the cart is inside the tank.") + return@on true + } // set path val path = when (currentPos) { @@ -396,11 +507,14 @@ class EW2Listeners : InteractionListener { // todo there are weird bugs with walkingQueue. cart sometimes veers off randomly to one of the other destinations. just teleporting for now. // move - //existingCart.walkingQueue.reset() - //for (step in path) { - // existingCart.walkingQueue.addPath(step.x, step.y) - //} + /* + existingCart.walkingQueue.reset() + for (step in path) { + existingCart.walkingQueue.addPath(step.x, step.y) + } + */ teleport(existingCart, path[2]) + playAudio(player, Sounds.EW2_CART_LOOP_3167) } return@on true @@ -418,11 +532,21 @@ class EW2Listeners : InteractionListener { 4915 -> { sendMessage(player, "The bar is too hot to touch.") } + 4916 -> { + sendMessage(player, "The bar is too hot to touch.") + } + 4917 -> { + sendMessage(player, "You should finish the process before taking the bar.") + } 4918 -> { addItemOrDrop(player, Items.PRIMED_BAR_9727) sendItemDialogue(player, Items.PRIMED_BAR_9727, "You take the primed bar.") animate(player, 537) setVarbit(player, EW2JigCartVarbit, 0, true) + if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 7) + setVarbit(player, EW2StageVarbit, 7, true) + } } } syncCart(player) @@ -441,8 +565,12 @@ class EW2Listeners : InteractionListener { setVarbit(player, EW2JigCartVarbit, 1, true) animate(player, 537) + playAudio(player, Sounds.EW2_PLACE_BAR_3177) syncCart(player) removeItem(player, used) + if(!getAttribute(player, attrJigged, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { + setAttribute(player, attrJigged, true) + } return@onUseWith true } @@ -465,7 +593,7 @@ class EW2Listeners : InteractionListener { } // fix crane - onUseWith(IntType.SCENERY, Items.CRANE_CLAW_9720, Scenery.OLD_CRANE_18636) { player, used, with -> + onUseWith(IntType.SCENERY, Items.CRANE_CLAW_9720, Scenery.OLD_CRANE_18636) { player, used, _ -> // check if already repaired if(getVarbit(player, EW2CraneStateVarbit) != 0) { sendMessage(player, "The crane is already repaired.") @@ -558,36 +686,44 @@ class EW2Listeners : InteractionListener { if (cartPos == 1) { when (cartState) { 0 -> { // empty - steamPress() sendDialogue(player, "You pull the lever and the press operates, but there is nothing on the cart to flatten.") + playAudio(player, Sounds.EW2_PRESS_BAR_3180) + animate(player, Animations.HUMAN_PULL_LEVER_4909) } 1 -> { // ele bar setVarbit(player, EW2JigCartVarbit, 4) - steamPress() // "hardy" is the real word used. I thought it was a typo for 'hammer' but it's not. https://en.wikipedia.org/wiki/Hardy_tool + // also note during one of the source videos it's authentic that the press state shows a purple ele bar for a moment. weird. sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is not hot enough to flatten.") + playAudio(player, Sounds.EW2_PRESS_BAR_3180) + animate(player, Animations.HUMAN_PULL_LEVER_4909) syncCart(player) } 2 -> { // hot ele bar setVarbit(player, EW2JigCartVarbit, 3) - steamPress() // "hardy" is correct. see above. sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar has been flattened.") + playAudio(player, Sounds.EW2_PRESS_BAR_3180) + animate(player, Animations.HUMAN_PULL_LEVER_4909) syncCart(player) + if(!getAttribute(player, attrFlattened, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { + setAttribute(player, attrFlattened, true) + } } else -> { // smashed hot, smashed cooled, or primed - steamPress() sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is already flattened.") + playAudio(player, Sounds.EW2_PRESS_BAR_3180) + animate(player, Animations.HUMAN_PULL_LEVER_4909) } } } else { // cart in wrong position - steamPress() sendDialogue(player, "You pull the lever and the press operates, but the jig cart is not underneath it.") } } else { // The junction box is not configured correctly sendDialogue(player, "You pull the lever but nothing happens.") + animate(player, Animations.HUMAN_PULL_LEVER_4909) } return@on true @@ -614,50 +750,179 @@ class EW2Listeners : InteractionListener { onUseWith(IntType.SCENERY, Items.PIPE_9723, Scenery.PIPING_18650) { player, item, _ -> animate(player, 537) sendItemDialogue(player, item, "You replace the section of broken pipe.") - playAudio(player, Sounds.EW2_PLACE_PIPE_3179) + playAudio(player, Sounds.EW2_PLACE_PIPE_3433) setVarbit(player, EW2PipeVarbit, 1, true) removeItem(player, item) return@onUseWith true } - // todo north lever - // todo see below, note that the door cannot have its scenery updated because it overlaps with the track and our limited engine can't find it. // north lever that opens and closes the door on(Scenery.AN_OLD_LEVER_18648, IntType.SCENERY, "pull") { player, _ -> + val doorOpen = getVarbit(player, EW2TankDoorStatusVarbit) + val tankState = getVarbit(player, EW2TankDoorStateVarbit) + val tankScen = getScenery(location(1951, 5164, 2)) - val currentState = getVarbit(player, EW2TankDoorVarbit) + animate(player, Animations.HUMAN_PULL_LEVER_4909) + // if tank has water, don't open. + if(tankScen != null) { + sendDialogue(player, "You pull the lever but nothing happens. Maybe the tank needs to be empty first.") + return@on true + } + + // check the rack is not out + if(tankState == 2 || tankState == 5 || tankState == 8) { + sendDialogue(player, "You pull the lever but nothing happens. Maybe the rack needs retracting first.") + return@on true + } + + // update door status open/close + if(doorOpen == 0) { + setVarbit(player, EW2TankDoorStatusVarbit, 1, true) + } else { + setVarbit(player, EW2TankDoorStatusVarbit, 0, true) + } + + // update door state + val nextTankState = when (tankState) { + 1 -> 0 // Opened -> Closed + 0 -> 1 // Closed -> Opened + 4 -> 3 // Opened/Hot -> Closed/Hot + 3 -> 4 // Closed/Hot -> Opened/Hot + 7 -> 6 // Opened/Cooled -> Closed/Cooled + 6 -> 7 // Closed/Cooled -> Opened/Cooled + // six seeeven hehehehe + else -> tankState + } + + setVarbit(player, EW2TankDoorStateVarbit, nextTankState) + syncTankDoor(player) return@on true } - // todo corkscrew lever // corkscrew lever that grabs and ungrabs the cart as long as the door is open on(Scenery.CORKSCREW_LEVER_18649, IntType.SCENERY, "turn") { player, _ -> + val cartPos = getVarbit(player, EW2CartLeverVarbit) + val doorStatus = getVarbit(player, EW2TankDoorStatusVarbit) // 0 = closed, 1 = open + val cartState = getVarbit(player, EW2JigCartVarbit) + val tankState = getVarbit(player, EW2TankDoorStateVarbit) + // cart must be at north station + if (cartPos != 2) { + sendMessage(player, "The jig cart is not in position to be grabbed.") + return@on true + } + + // door must be open (this also requires tank to be empty) + if (doorStatus == 0) { + sendMessage(player, "The tank door must be open to use the corkscrew.") + return@on true + } + + playAudio(player, Sounds.EW2_SCREW_TURN_3182) + animate(player, Animations.HUMAN_TURN_CORKSCREW_LEVER_4905) + + // either pull cart in or push it out. transfer between all the door open states + runTask(player, 1) { + when (tankState) { + 1 -> { // 1 door opened, grabber inside + setVarbit(player, EW2TankDoorStateVarbit, 2) // 2 door opened, grabber out + // no cart update + } + 2 -> { // 2 door opened, grabber out + // only grab the hot bar + if(cartState == 3) setVarbit(player, EW2TankDoorStateVarbit, 4) // 4 door opened, hot bar inside + // remove hot bar cart + } + 4 -> { // 4 door opened, hot bar inside + setVarbit(player, EW2TankDoorStateVarbit, 5) // 5 door opened, hot bar out + } + 5 -> { // 5 door opened, hot bar out + setVarbit(player, EW2TankDoorStateVarbit, 1) // 1 door opened, grabber inside + // add hot bar cart + } + 7 -> { // 7 door opened, cooled bar inside + setVarbit(player, EW2TankDoorStateVarbit, 8) // 8 door opened, cooled bar out + } + 8 -> { // 8 door opened, cooled bar out + setVarbit(player, EW2TankDoorStateVarbit, 1) // 1 door opened, grabber inside + // add cool bar cart + } + } + syncTankDoor(player) + syncCart(player) + } return@on true } - // todo west water valve - // west water valve fills the chamber if the door is closed "you open the valve" "you close the valve" make sure it's closed or the chamber will fill up again - // changes cart state into flattened mind bar + // west water valve operates the water inflow on(Scenery.WATER_VALVE_18646, IntType.SCENERY, "turn") { player, _ -> + val valveState = getVarbit(player, EW2ValveWestVarbit) // water valve west. 0 = closed, 1 = open + val door = getVarbit(player, EW2TankDoorStatusVarbit) // 0 if door closed, 1 if open + + if(door == 1) { + sendMessage(player, "The valve won't turn, maybe the door needs to be closed first.") + return@on true + } + + player.locks.lockInteractions(4) + animate(player, 4861) + + // if closed, open the valve + if(valveState == 0){ + setVarbit(player, EW2ValveWestVarbit, 1, true) + runTask(player, 3) { + syncTank(player) + sendDialogue(player, "you open the valve") + playAudio(player, Sounds.EW2_WATER_VALVE_3185) + } + // if open, close the valve + } else { + setVarbit(player, EW2ValveWestVarbit, 0, true) + runTask(player, 3) { + syncTank(player) + sendDialogue(player, "you close the valve") + playAudio(player, Sounds.EW2_WATER_VALVE_3185) + } + } return@on true } - // todo east water valve - // east water valve drains the tank if it is full "you open the valve" + // east water valve operates the drain on(Scenery.WATER_VALVE_18647, IntType.SCENERY, "turn") { player, _ -> + val valveState = getVarbit(player, EW2ValveEastVarbit) // water valve west. 0 = closed, 1 = open + val door = getVarbit(player, EW2TankDoorStatusVarbit) // 0 if door closed, 1 if open + + if(door == 1) { + sendMessage(player, "The valve won't turn, maybe the door needs to be closed first.") + return@on true + } + + player.locks.lockInteractions(4) + animate(player, 4861) + + // if closed, open the valve + if(valveState == 0){ + setVarbit(player, EW2ValveEastVarbit, 1, true) + runTask(player, 3) { + syncTank(player) + sendDialogue(player, "you open the valve") + playAudio(player, Sounds.EW2_WATER_VALVE_3185) + } + // if open, close the valve + } else { + setVarbit(player, EW2ValveEastVarbit, 0, true) + runTask(player, 3) { + syncTank(player) + sendDialogue(player, "you close the valve") + playAudio(player, Sounds.EW2_WATER_VALVE_3185) + } + } return@on true } - /** - * Scenery.WATER_TANK_18660 - * Scenery.WATER_TANK_18661 - */ - - /**------------------------ * SECTION 4 - FAN ** ------------------------ @@ -734,18 +999,21 @@ class EW2Listeners : InteractionListener { setVarbit(player, EW2CogVarbit1, 1, true) sendItemDialogue(player, item, "You put the small cog onto the shaft.") animate(player, 537) + playAudio(player, Sounds.EW2_PLACE_COG_3178) removeItem(player, item) } Items.MEDIUM_COG_9725 -> { setVarbit(player, EW2CogVarbit1, 2, true) sendItemDialogue(player, item, "You put the medium cog onto the shaft.") animate(player, 537) + playAudio(player, Sounds.EW2_PLACE_COG_3178) removeItem(player, item) } Items.LARGE_COG_9724 -> { setVarbit(player, EW2CogVarbit1, 3, true) sendItemDialogue(player, item, "You put the large cog onto the shaft.") animate(player, 537) + playAudio(player, Sounds.EW2_PLACE_COG_3178) removeItem(player, item) } } @@ -822,37 +1090,42 @@ class EW2Listeners : InteractionListener { return@onUseWith true } - // todo see above, note that the fan scenery cannot be animated :( because it must overlap with some other object and our limited engine can't find it. // fan lever. Authentically this check should allow the machine to start and the fan to either not start, start backwards, or start correctly, but I'm simplifying it for my sanity. on(Scenery.AN_OLD_LEVER_18663, IntType.SCENERY, "pull") { player, _ -> animate(player, Animations.HUMAN_PULL_LEVER_4909) - val p1 = getVarbit(player, EW2CogVarbit1) - val p2 = getVarbit(player, EW2CogVarbit2) - val p3 = getVarbit(player, EW2CogVarbit3) + val allCogs = getVarbit(player, EW2AllCogsVarbit) val lever = getVarbit(player, EW2FanLeverVarbit) val cartpos = getVarbit(player, EW2CartLeverVarbit) // needs to be 3 to be at this station val cartstate = getVarbit(player, EW2JigCartVarbit) // needs to be state 4: smashed cooled // check if the cogs are right. pin 1 (2655) should have the small cog. pin 2 (2656) should have the med cog. pin 3 (2656) should have the large cog. - if (p1 == 1 && p2 == 2 && p3 == 3) { + if (allCogs == 57) { if(lever == 0) { // turn on sendDialogue(player, "The machine rumbles and the fan starts to blow.") // Is authentically two messages of "The machine starts rumbling." and "The fan starts to blow.", but I am simplifying this check. + playAudio(player, Sounds.EW2_MACHINE_RUMBLE_3175) + playAudio(player, Sounds.EW2_FANBLADE_3174) setVarbit(player, EW2FanLeverVarbit, 1, true) + syncFan(player) // check cart and cool if necessary. otherwise nothing should happen. if (cartpos == 3 && cartstate == 4) { setVarbit(player, EW2JigCartVarbit, 5, true) syncCart(player) + if(!getAttribute(player, attrWinded, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { + setAttribute(player, attrWinded, true) + } } } else { // turn off sendDialogue(player, "The machine grinds to a standstill.") - setVarbit(player, EW2FanLeverVarbit, 0, true) + setVarbit(player, EW2FanStateVarbit, 0, true) + syncFan(player) } // cogs are not correct } else { sendDialogue(player, "The machine starts up, but the fan does not blow.") + playAudio(player, Sounds.EW2_MACHINE_RUMBLE_3175) } return@on true } @@ -862,47 +1135,127 @@ class EW2Listeners : InteractionListener { ** ------------------------ */ - // todo add graphics to this section - // use bar with extractor gun onUseWith(IntType.SCENERY, intArrayOf(Items.PRIMED_BAR_9727), Scenery.EXTRACTOR_GUN_18693) { player, item, _ -> - setVarbit(player, EW2ExtractorGun, 1, true) + if(removeItem(player, item)) { + setVarbit(player, EW2ExtractorGun, 1, true) + animate(player, 537) + sendDialogue(player, "You place the primed bar under the device.") + if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 8) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 9) + setVarbit(player, EW2StageVarbit, 9, true) + } + } return@onUseWith true } // take primed bar on(intArrayOf(Scenery.EXTRACTOR_GUN_18694), IntType.SCENERY, "take-from") { player, _ -> addItemOrDrop(player, Items.PRIMED_BAR_9727) + animate(player, 537) setVarbit(player, EW2ExtractorGun, 0, true) + sendDialogue(player, "You take the primed bar.") return@on true } // take mind bar on(intArrayOf(Scenery.EXTRACTOR_GUN_18695), IntType.SCENERY, "take-from") { player, _ -> addItemOrDrop(player, Items.ELEMENTAL_MIND_BAR_9728) + animate(player, 537) setVarbit(player, EW2ExtractorGun, 0, true) + sendDialogue(player, "You take the elemental mind bar.") return@on true } // sit in chair + // todo the extractor gun needs a graphic + // todo per one of the sources the camera angle changes for this. on(intArrayOf(Scenery.EXTRACTOR_HAT_18690), IntType.SCENERY, "operate") { player, _ -> - // if bar is in place + // check magic level + if(getDynLevel(player, Skills.MAGIC) < 20) { + sendMessage(player, "You must have a Magic level of 20 and above to use the device.") + return@on true + } + // if bar is in place, charge it and drain magic if(getVarbit(player, EW2ExtractorGun) == 1) { - setVarbit(player, EW2ExtractorGun, 2, true) + runTask(player) { + forceMove( + player, + Location.create(1961, 5150, 0), + Location.create(1962, 5150, 0), + 0, + 0 + ) + visualize(player, Animations.HUMAN_USE_EXTRACTOR_HAT_CHAIR_4884, goodHat) + playAudio(player, Sounds.EW2_EXTRACTOR_OPERATION_3166) + sendGraphics(809, Location.create(1962, 5148, 0)) + setVarbit(player, EW2ExtractorGun, 2, true) + adjustLevel(player, Skills.MAGIC, -20) + sendMessage(player, "You feel magically weakened.") + if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 9) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 10) + setVarbit(player, EW2StageVarbit, 10, true) + } + } + forceMove( + player, + Location.create(1962, 5150, 0), + Location.create(1961, 5150, 0), + 200, + 2 + ) + // if bar is not in place (or if it's already a primed bar), hit player + } else { + runTask(player) { // 50 client cycles = 1 second + forceMove( + player, + Location.create(1961, 5150, 0), + Location.create(1962, 5150, 0), + 0, + 0 + ) + visualize(player, Animations.HUMAN_IMPROPERLY_USE_EXTRACTOR_HAT_CHAIR_4885, badHat) + playAudio(player, Sounds.EW2_EXTRACTOR_ZAP_3173) + impact(player, 4) + sendMessage(player, "The extractor hat shocks you!.") + } + forceMove( + player, + Location.create(1962, 5150, 0), + Location.create(1961, 5150, 0), + 350, + 2 + ) } return@on true } - /** - * EXTRACTOR_GUN_18693 - * EXTRACTOR_GUN_18694 - * EXTRACTOR_GUN_18695 - * EXTRACTOR_GUN_18696 - * EXTRACTOR_HAT_18690 - * EXTRACTOR_HAT_18691 - * EXTRACTOR_HAT_TOP_18692 + /**------------------------ + * EXTRA + ** ------------------------ */ + // equip graphics for helmets and shields. the shields should not technically show until the graphic finishes, but I can't find an easy way to do that. + onEquip(Items.MIND_HELMET_9733) { player, _ -> + sendGraphics(mHelmEquip, player.location) + return@onEquip true + } + + onEquip(Items.MIND_SHIELD_9731) { player, _ -> + sendGraphics(Graphics(mShieldEquip,95), player.location) + return@onEquip true + } + + onEquip(Items.ELEMENTAL_HELMET_9729) { player, _ -> + sendGraphics(eHelmEquip, player.location) + return@onEquip true + } + + onEquip(Items.ELEMENTAL_SHIELD_2890) { player, _ -> + sendGraphics(Graphics(eShieldEquip,95), player.location) + return@onEquip true + } + } // fixes to get right interact locations @@ -915,7 +1268,7 @@ class EW2Listeners : InteractionListener { return@setDest Location.create(1954, 5148, 2) } - setDest(IntType.NPC, intArrayOf(NPCs.JIG_CART_4914, NPCs.JIG_CART_4915), "take-from") { _, _ -> + setDest(IntType.NPC, intArrayOf(NPCs.JIG_CART_4914, NPCs.JIG_CART_4915, NPCs.JIG_CART_4916, NPCs.JIG_CART_4917, NPCs.JIG_CART_4918), "take-from") { _, _ -> return@setDest Location.create(1954, 5148, 2) } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt index fd4048c45..3980cdce9 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt @@ -1,14 +1,23 @@ package content.region.kandarin.seers.quest.elementalworkshop2 +import content.data.Quests import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCart import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCrane +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncFan +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTank +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTankDoor +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2StageVarbit +import core.api.* import core.game.node.entity.Entity import core.game.node.entity.player.Player +import core.game.node.scenery.SceneryBuilder +import core.game.world.map.Location import core.plugin.Initializable import core.game.world.map.zone.MapZone import core.game.world.map.zone.ZoneBorders import core.game.world.map.zone.ZoneBuilder import core.plugin.Plugin +import org.rs09.consts.Scenery /** * This updates the Elemental Workshop II state whenever the player enters, @@ -30,8 +39,19 @@ class EW2Zone : MapZone("Elemental Workshop II", true), Plugin { // updates the workshop state based on the varbits for Elemental Workshop II override fun enter(e: Entity?): Boolean { if(e is Player) { + + if(getQuestStage(e, Quests.ELEMENTAL_WORKSHOP_II) <= 5) { + setQuestStage(e, Quests.ELEMENTAL_WORKSHOP_II, 6) + setVarbit(e, EW2StageVarbit, 6, true) + } + + SceneryBuilder.add(core.game.node.scenery.Scenery(Scenery.DOOR_18651, Location.create(1953, 5162, 2), 1)) syncCrane(e) syncCart(e) + //syncSteamPress(e) + syncTank(e) + syncTankDoor(e) + syncFan(e) } return super.enter(e) } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt index 0fe6b7a67..c1dac6f59 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt @@ -6,13 +6,15 @@ import core.game.node.entity.player.Player import core.game.node.entity.player.link.quest.Quest import core.game.node.entity.skill.Skills import core.plugin.Initializable +import org.rs09.consts.Graphics import org.rs09.consts.Items /** * ELEMENTAL WORKSHOP II QUEST * * Requirements: 20 Magic, 30 Smithing, completion of Elemental Workshop I - * Primary Source: https://www.youtube.com/watch?v=YKSWisRgGZk + * Primary Sources: https://www.youtube.com/watch?v=YKSWisRgGZk, https://www.youtube.com/watch?v=hv2rCofydXo + * Wiki: https://runescape.wiki/w/Elemental_Workshop_II?oldid=849620 */ @Initializable @@ -37,18 +39,32 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Sta const val EW2ValveEastVarbit = 2652 // water valve east. 0 = closed, 1 = open const val EW2Varp2 = 897 // second EW2 varp that packages varbits 2653-2664 - const val EW2TankDoorVarbit = 2653 // tank door holding state, controlled by corkscrew lever. 0 = closed, 1 = , 2 = , 3 = , 4 = , 5 = , 6 = , 7 = , 8 = // nine values. jig cart has six - const val EW2DoorLeverVarbit = 2654 // door lever for tank. 0 = closed, 1 = open + const val EW2TankDoorStateVarbit = 2653 // tank door holding state, controlled by corkscrew lever. 0 = closed, 1 = door opened grabber inside, 2 = door opened grabber out, 3 = door closed hot bar inside, 4 = door opened hot bar inside, 5 = door opened hot bar out, 6 = door closed cooled bar inside, 7 = door opened cooled bar inside, 8 = door closed cooled bar out + const val EW2TankDoorStatusVarbit = 2654// door lever for tank. 0 = closed, 1 = open const val EW2CogVarbit1 = 2655 // cog pin. 0 = no cog, 1 = small, 2 = med, 3 = large const val EW2CogVarbit2 = 2656 // cog pin. 0 = no cog, 1 = small, 2 = med, 3 = large const val EW2CogVarbit3 = 2657 // cog pin. 0 = no cog, 1 = small, 2 = med, 3 = large - //const val EW2AllCogsVarbit = 2658 // overlaps the cog varbits. could be used to reset all cogs at once. + const val EW2AllCogsVarbit = 2658 // overlaps the cog varbits. const val EW2FanLeverVarbit = 2659 // fan switch. 0 = fan off, 1 = fan on - //const val EW2FanStateVarbit = 2660 // fan blades. 0 = off, 1 = forward, 2 = reverse + const val EW2FanStateVarbit = 2660 // fan blades. 0 = off, 1 = forward, 2 = reverse //const val EW2FanStationVarbit = 2661 // overlaps cogs, fan switch, and fan blades varbits const val EW2ExtractorGun = 2662 // extractor gun. 0 = empty, 1 = primed bar, 2 = mind bar - //const val EW2 = 2663 // - //const val EW2 = 2664 // values from 1 to 7 back at the dig site library where you find the book. + //const val EW2mystery1 = 2663 // no idea what this one does. it's in the place of the hammer lever? + //const val EW2mystery2 = 2664 // no idea what this one does. values from 1 to 7 back at the dig site library where you find the book. + + const val attrJigged = "/save:quest:ew2-jigged" //true after you put a bar on the jig cart + const val attrDipped = "/save:quest:ew2-dipped" //true after dip the bar in the lava + const val attrFlattened = "/save:quest:ew2-flattened" //true after you flatten the hot bar + const val attrWatered = "/save:quest:ew2-watered" //true after you cool the bar in the tank + const val attrWinded = "/save:quest:ew2-winded" //true after you cool the bar in the wind tunnel + + // defining these graphics here because the graphics consts conflicts with the graphics object in the listeners + const val goodHat = Graphics.EXTRACTOR_HAT_CHAIR_807 + const val badHat = Graphics.IMPROPERLY_EXTRACTOR_HAT_CHAIR_808 + const val mShieldEquip = 809 + const val mHelmEquip = 810 + const val eShieldEquip = 244 + const val eHelmEquip = 811 } /** @@ -61,18 +77,21 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Sta * 2 2 read the beaten book * 3 3 read the beaten book's scroll * 4 4 found key - * 5 5 went down center hatch - * 6 6 read the crane schematic - * 7 7 read the lever schematic? + * 5 5 went down center hatch. need to fix equipment + * 6 6 inside the workshop. processing bar + * 7 7 take primed bar * 8 8 went through the mind door - * 9 9 primed a mind bar - * 10 10 primed a mind bar? + * 9 9 placed a primed bar + * 10 10 sat in the extractor * 11 100 smithed a mind helmet, quest complete */ override fun drawJournal(player: Player, stage: Int) { + + // Quest journal source: https://www.youtube.com/watch?v=hv2rCofydXo + super.drawJournal(player, stage) var line = 12 - var stage = getStage(player) + val stage = getStage(player) // unstarted if (stage == 0) { @@ -105,57 +124,116 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Sta // stage 3, read the scroll from the beaten book if (stage >= 3) { - line(player, "The note found with the book has a strange code written on it.", line++, stage > 3) + line(player, "The note found with the book has a !!strange code?? written on it.", line++, stage > 3) // in-progress stage text - //if(stage == 3) line(player, "", line++) + if(stage == 3) line(player, "I wonder what it means.", line++) line++ } // stage 4, finding the key if (stage >= 4) { line(player, "By following the code on the !!note?? I was able to !!find a key??.", line++, stage > 4) + // in-progress stage text + if(stage == 4) line(player, "Now I just have to find what it opens.", line++) line++ } - // stage 5, going down the center hatch + // stage 5, going down the center hatch and getting the machines fixed if (stage >= 5) { - line(player, "The !!key?? I've found opens the !!large hatch?? in the !!workshop??; there are !!4 machines?? on this level to fix:", line++, stage > 5) - line(player, "The crane - now the claw has been replaced the crane looks like it's in good working order.", line++, stage > 5) - line(player, "The pipes - the pipes have now been fitted and it should work as long as it's powered.", line++, stage > 5) - line(player, "The tank - this should work fine now I have replaced the pipe.", line++, stage > 5) - line(player, "A wind tunnel - I have fitted all the cogs on to the pins. The win tunnel should now work as long as it's powered.", line++, stage > 5) - line(player, "A wind tunnel - I have fitted all the cogs on to the pins. The win tunnel should now work as long as it's powered.", line++, stage > 5) + line(player, "Climbing down through the hatch I found a new area in the", line++, (getVarbit(player, EW2CraneStateVarbit) != 0 && JunctionBoxInterface.isSolutionCorrect(player) && getVarbit(player, EW2PipeVarbit) != 0 && getVarbit(player, EW2AllCogsVarbit) == 57)) + line(player, "workshop; there are 4 machines on this level to !!fix??:", line++, (getVarbit(player, EW2CraneStateVarbit) != 0 && JunctionBoxInterface.isSolutionCorrect(player) && getVarbit(player, EW2PipeVarbit) != 0 && getVarbit(player, EW2AllCogsVarbit) == 57)) - line(player, "I have found a track-mounted jig on which I was able to clamp an elemental bar.", line++, stage > 5) - line(player, "I used the press to flatten the hot elemental bar.", line++, stage > 5) - line(player, "The water tank was used to cool the elemental bar down.", line++, stage > 5) - line(player, "The wind tunnel proved a great way to dry off the wet bar.", line++, stage > 5) - line(player, "I have primed a bar of elemental ore.", line++, stage > 5) - line(player, "I placed the primed bar on the device found in the room with the mind symbols on the door.", line++, stage > 5) - line(player, "Operating the device charged the primed bar with some of my own mind power.", line++, stage > 5) - line(player, "I have made an elemental mind bar, now I just have to make one of the helms from it.", line++, stage > 5) - line(player, "Creating the elemental mind helm was easy with the book for instruction.", line++, stage > 5) - line(player, "", line++, stage > 5) + if(getVarbit(player, EW2CraneStateVarbit) == 0)line(player, "The crane - this looks in a state of disrepair.", line++) + // "The crane - this looks in a state of disrepair, but I have \n created a replacement claw for it." + if(getVarbit(player, EW2CraneStateVarbit) != 0)line(player, "The crane - now the claw has been replaced the crane", line++, true) + if(getVarbit(player, EW2CraneStateVarbit) != 0)line(player, "looks like it's in good working order.", line++, true) + line++ + + if(!JunctionBoxInterface.isSolutionCorrect(player))line(player, "The press - I should see if this needs fixing.", line++) + // "The press - I should try to fit all the pipes in the right order." + if(JunctionBoxInterface.isSolutionCorrect(player))line(player, "The press - the pipes have now been fitted and ", line++, true) + if(JunctionBoxInterface.isSolutionCorrect(player))line(player, "it should work as long as it's powered.", line++, true) + line++ + + if(getVarbit(player, EW2PipeVarbit) == 0)line(player, "The tank - I'll need to fix this before I can use it.", line++) + if(getVarbit(player, EW2PipeVarbit) != 0)line(player, "The tank - this should work fine now I have replaced the pipe.", line++, true) + line++ + + if(getVarbit(player, EW2AllCogsVarbit) != 57)line(player, "A wind tunnel - it looks like a few bits are missing from it.", line++) + if(getVarbit(player, EW2AllCogsVarbit) == 57)line(player, "A wind tunnel - I have fitted all the cogs on to the pins.", line++, true) + if(getVarbit(player, EW2AllCogsVarbit) == 57)line(player, "The wind tunnel should now work as long as it's powered.", line++, true) line++ } - // stage 6 - if (stage >= 6) { - line(player, "", line++, stage > 6) + // stage 6, inside the workshop. put bar on cart + if (stage >= 6 && getAttribute(player, attrJigged, false)) { + line(player, "I have found a track-mounted jig", line++, stage > 6) + line(player, "on which I was able to clamp an elemental bar.", line++, stage > 6) line++ } - // stage 7 + // stage 6, inside the workshop. dipped + if (stage >= 6 && getAttribute(player, attrDipped, false)) { + line(player, "I was able to operate the crane and dip the elemental bar", line++, stage > 6) + line(player, "into the lava. The bar is now very hot.", line++, stage > 6) + line++ + } + + // stage 6, inside the workshop. flattened bar + if (stage >= 6 && getAttribute(player, attrFlattened, false)) { + line(player, "I used the press to flatten the hot elemental bar.", line++, stage > 6) + line++ + } + + // stage 6, inside the workshop. water tanked bar + if (stage >= 6 && getAttribute(player, attrWatered, false)) { + line(player, "The water tank was used to cool the elemental bar down.", line++, stage > 6) + line++ + } + + // stage 6, inside the workshop. wind tunneled bar + if (stage >= 6 && getAttribute(player, attrWinded, false)) { + line(player, "The wind tunnel proved a great way to dry off the wet bar.", line++, stage > 6) + line++ + } + + // stage 7, retrieve primed bar if (stage >= 7) { - line(player, "", line++, stage > 7) + line(player, "I have primed a bar of elemental ore, I wonder what to do", line++, stage > 7) + line(player, "with it.", line++, stage > 7) line++ } - // stage 8 + // stage 8, went through the mind door + /* if (stage >= 8) { line(player, "", line++, stage > 8) line++ } + */ + + // stage 9, placed a primed bar + if (stage >= 9) { + line(player, "I placed the primed bar on the device", line++, stage > 9) + line(player, "found in the room with the mind symbols on the door.", line++, stage > 9) + line++ + } + + // stage 10, sat in the extractor + if (stage >= 10) { + line(player, "Operating the device charged the", line++, stage > 10) + line(player, "primed bar with some of my own mind power.", line++, stage > 10) + line++ + line(player, "I have made an elemental mind bar,", line++, stage > 10) + line(player, "now I just have to make one of the helms from it.", line++, stage > 10) + line++ + } + + // stage 11, smithed a mind helmet, quest complete + if (stage >= 11) { + line(player, "Creating the elemental mind helm", line++, stage > 11) + line(player, "was easy with the book for instruction.", line++, stage > 11) + } } // use ::setqueststage 53 0 to reset quest. @@ -163,6 +241,11 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Sta // these varps wipes all the varbits setVarp(player, EW2Varp1, 0, true) setVarp(player, EW2Varp2, 0, true) + removeAttribute(player, attrJigged) + removeAttribute(player, attrDipped) + removeAttribute(player, attrFlattened) + removeAttribute(player, attrWatered) + removeAttribute(player, attrWinded) player.getQuestRepository().syncronizeTab(player) super.reset(player) } @@ -171,7 +254,7 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Sta var ln = 10 super.finish(player) player.packetDispatch.sendString("You have completed the Elemental Workshop II quest!", 277, 4) - player.packetDispatch.sendItemZoomOnInterface(Items.ELEMENTAL_HELMET_9729, 230, 277, 5) + player.packetDispatch.sendItemZoomOnInterface(Items.MIND_HELMET_9733, 230, 277, 5) drawReward(player, "1 Quest Point", ln++) drawReward(player, "7,500 Smithing XP,", ln++) @@ -183,7 +266,11 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Sta rewardXP(player, Skills.SMITHING, 7500.0) rewardXP(player, Skills.CRAFTING, 7500.0) - // remove attributes + removeAttribute(player, attrJigged) + removeAttribute(player, attrDipped) + removeAttribute(player, attrFlattened) + removeAttribute(player, attrWatered) + removeAttribute(player, attrWinded) } override fun newInstance(`object`: Any?): Quest { diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt index 516e0cb8e..3aa5dccd9 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt @@ -7,6 +7,7 @@ import core.api.* import core.game.interaction.InterfaceListener import core.game.node.entity.player.Player import org.rs09.consts.Components +import org.rs09.consts.Sounds /** * Junction box in Elemental Workshop II @@ -149,6 +150,7 @@ class JunctionBoxInterface : InterfaceListener { // place the pipe val pipeChild = pipes[pipeIndex] setComponentVisibility(player, junctionBox, pipeChild, false) + playAudio(player, Sounds.EW2_PLACE_PIPE_3179) // pipe status is saved in these varbits and recalled on open. they are linked to main quest progression status val saved = when { @@ -217,6 +219,7 @@ class JunctionBoxInterface : InterfaceListener { // Increment pipes left val left = getVarbit(player, pipesLeftVarbit) setVarbit(player, pipesLeftVarbit, left + 1) + playAudio(player, Sounds.EW2_REMOVE_PIPE_3181) return true } } From e1fef2c54f5256e6fab6d1f304c3a37ecafc3612 Mon Sep 17 00:00:00 2001 From: aidan Date: Fri, 30 Jan 2026 14:46:45 -0600 Subject: [PATCH 10/45] fix mind shield reqs --- .../kandarin/seers/quest/elementalworkshop/EWListeners.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt index d6d9660cd..111d6a274 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt @@ -443,7 +443,7 @@ class EWListeners : InteractionListener { return@addDialogueAction } // Warn player they don't have the required book in their inventory - if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.BEATEN_BOOK_9717))) { + if (!player.inventory.containsAtLeastOneItem(intArrayOf(Items.SLASHED_BOOK_9715, Items.BATTERED_BOOK_2886))) { sendDialogue(player, "This workbench is too complicated. You need instructions to follow.") return@addDialogueAction } From b4babec54f6a3f151c7d6bf994483586493603bc Mon Sep 17 00:00:00 2001 From: aidan Date: Thu, 19 Feb 2026 14:45:45 -0600 Subject: [PATCH 11/45] fixes from testing. --- .../quest/elementalworkshop2/EW2Listeners.kt | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 432aba94f..2a5916b9b 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -522,6 +522,14 @@ class EW2Listeners : InteractionListener { // take item from the jig cart on(jigCartStates, IntType.NPC, "take-from") { player, node -> + val cartPos = getVarbit(player, EW2CartLeverVarbit) // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east + + // don't allow interact if cart is not at south station + if (cartPos != 0) { + sendMessage(player, "I can't reach that.") + return@on true + } + when(node.id) { 4914 -> { addItemOrDrop(player, Items.ELEMENTAL_METAL_2893) @@ -556,6 +564,13 @@ class EW2Listeners : InteractionListener { // put bar on the jig cart onUseWith(IntType.NPC, intArrayOf(Items.ELEMENTAL_METAL_2893), NPCs.JIG_CART_4913) { player, used, _ -> val cranePos = getVarbit(player, EW2CranePosVarbit) + val cartPos = getVarbit(player, EW2CartLeverVarbit) // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east + + // don't allow interact if cart is not at south station + if (cartPos != 0) { + sendMessage(player, "I can't reach that.") + return@onUseWith true + } // if crane is south down, it's blocking the cart. if (cranePos == 2) { @@ -613,16 +628,17 @@ class EW2Listeners : InteractionListener { val nextPos = currentPos xor 1 val craneMat = getVarbit(player, EW2CraneStateVarbit) // Crane State. 0 = broken, 1 = fixed/empty, 2 = ele bar, 3 = hot bar. val cartState = getVarbit(player, EW2JigCartVarbit) // Jig Cart state. 0 = Empty, 1 = ele bar, 2 = hot bar, 3 = smashed hot, 4 = smashed cooled, 5 = primed + val cartPos = getVarbit(player, EW2CartLeverVarbit) // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east - // facing north: pick up and drop off bar + // facing north: pick up and drop off bar. don't interact with the cart if the cart isn't here. if (currentPos == 3 && nextPos == 2) { - if (craneMat == 1 && (cartState == 1 || cartState == 2)) { + if (craneMat == 1 && (cartState == 1 || cartState == 2) && cartPos == 0) { // pick up setVarbit(player, EW2CraneStateVarbit, cartState + 1) setVarbit(player, EW2JigCartVarbit, 0) sendMessage(player, "The crane claw picks up the bar from the cart.") syncCart(player) - } else if ((craneMat == 2 || craneMat == 3) && cartState == 0) { + } else if ((craneMat == 2 || craneMat == 3) && cartState == 0 && cartPos == 0) { // drop off setVarbit(player, EW2JigCartVarbit, craneMat - 1) setVarbit(player, EW2CraneStateVarbit, 1) @@ -855,7 +871,7 @@ class EW2Listeners : InteractionListener { return@on true } - // west water valve operates the water inflow + // west water valve operates the water inflow. requires pipe to be fixed. on(Scenery.WATER_VALVE_18646, IntType.SCENERY, "turn") { player, _ -> val valveState = getVarbit(player, EW2ValveWestVarbit) // water valve west. 0 = closed, 1 = open @@ -866,6 +882,11 @@ class EW2Listeners : InteractionListener { return@on true } + if(getVarbit(player, EW2PipeVarbit) == 0) { + sendMessage(player, "You turn the valve but nothing happens, maybe the pipe needs to be repaired first.") + return@on true + } + player.locks.lockInteractions(4) animate(player, 4861) @@ -1099,7 +1120,10 @@ class EW2Listeners : InteractionListener { val cartpos = getVarbit(player, EW2CartLeverVarbit) // needs to be 3 to be at this station val cartstate = getVarbit(player, EW2JigCartVarbit) // needs to be state 4: smashed cooled - // check if the cogs are right. pin 1 (2655) should have the small cog. pin 2 (2656) should have the med cog. pin 3 (2656) should have the large cog. + // check if the cogs are right. + // pin 1 (2655) should have the small cog. + // pin 2 (2656) should have the med cog. + // pin 3 (2656) should have the large cog. if (allCogs == 57) { if(lever == 0) { // turn on @@ -1116,14 +1140,12 @@ class EW2Listeners : InteractionListener { setAttribute(player, attrWinded, true) } } - } else { - // turn off + } else { // lever == 1, turn off sendDialogue(player, "The machine grinds to a standstill.") - setVarbit(player, EW2FanStateVarbit, 0, true) + setVarbit(player, EW2FanLeverVarbit, 0, true) syncFan(player) } - // cogs are not correct - } else { + } else { // allCogs != 57, cogs are not correct sendDialogue(player, "The machine starts up, but the fan does not blow.") playAudio(player, Sounds.EW2_MACHINE_RUMBLE_3175) } From 210be91d4cee7168e7890cabf0c22b93cdc8dc42 Mon Sep 17 00:00:00 2001 From: aidan Date: Fri, 27 Feb 2026 11:58:03 -0600 Subject: [PATCH 12/45] fix more testing issues --- .../quest/elementalworkshop2/EW2Listeners.kt | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 2a5916b9b..872e97246 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -701,21 +701,19 @@ class EW2Listeners : InteractionListener { // check if cart is here if (cartPos == 1) { when (cartState) { - 0 -> { // empty + 0 -> { // empty. nothing happens. sendDialogue(player, "You pull the lever and the press operates, but there is nothing on the cart to flatten.") playAudio(player, Sounds.EW2_PRESS_BAR_3180) animate(player, Animations.HUMAN_PULL_LEVER_4909) } - 1 -> { // ele bar - setVarbit(player, EW2JigCartVarbit, 4) + 1 -> { // ele bar. nothing happens. // "hardy" is the real word used. I thought it was a typo for 'hammer' but it's not. https://en.wikipedia.org/wiki/Hardy_tool // also note during one of the source videos it's authentic that the press state shows a purple ele bar for a moment. weird. sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is not hot enough to flatten.") playAudio(player, Sounds.EW2_PRESS_BAR_3180) animate(player, Animations.HUMAN_PULL_LEVER_4909) - syncCart(player) } - 2 -> { // hot ele bar + 2 -> { // hot ele bar. changes state to hot smashed bar. setVarbit(player, EW2JigCartVarbit, 3) // "hardy" is correct. see above. sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar has been flattened.") @@ -726,7 +724,7 @@ class EW2Listeners : InteractionListener { setAttribute(player, attrFlattened, true) } } - else -> { // smashed hot, smashed cooled, or primed + else -> { // smashed hot, smashed cooled, or primed. nothing happens. sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is already flattened.") playAudio(player, Sounds.EW2_PRESS_BAR_3180) animate(player, Animations.HUMAN_PULL_LEVER_4909) @@ -846,9 +844,13 @@ class EW2Listeners : InteractionListener { // no cart update } 2 -> { // 2 door opened, grabber out - // only grab the hot bar - if(cartState == 3) setVarbit(player, EW2TankDoorStateVarbit, 4) // 4 door opened, hot bar inside - // remove hot bar cart + // only grab the hot bar. otherwise, put the grabber back in + if(cartState == 3) { + setVarbit(player, EW2TankDoorStateVarbit, 4) // 4 door opened, hot bar inside + // remove hot bar cart + } else { + setVarbit(player, EW2TankDoorStateVarbit, 1) // 1 door opened, grabber inside + } } 4 -> { // 4 door opened, hot bar inside setVarbit(player, EW2TankDoorStateVarbit, 5) // 5 door opened, hot bar out From 22f6c5f31d1db1a4f85c0ae3e521ff382ec39778 Mon Sep 17 00:00:00 2001 From: aidan Date: Wed, 4 Mar 2026 11:07:02 -0600 Subject: [PATCH 13/45] fix elemental shield equip gfx --- .../quest/elementalworkshop2/EW2Listeners.kt | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 872e97246..4042a1eeb 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -33,11 +33,13 @@ import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2 import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.mShieldEquip import core.game.world.update.flag.context.Graphics import core.api.* +import core.game.container.impl.EquipmentContainer import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills +import core.game.node.item.Item import core.game.node.scenery.SceneryBuilder import core.game.world.map.Location import core.game.world.map.RegionManager @@ -1261,23 +1263,41 @@ class EW2Listeners : InteractionListener { // equip graphics for helmets and shields. the shields should not technically show until the graphic finishes, but I can't find an easy way to do that. onEquip(Items.MIND_HELMET_9733) { player, _ -> - sendGraphics(mHelmEquip, player.location) + visualize(player, -1, mHelmEquip) return@onEquip true } - onEquip(Items.MIND_SHIELD_9731) { player, _ -> - sendGraphics(Graphics(mShieldEquip,95), player.location) - return@onEquip true + onEquip(Items.MIND_SHIELD_9731) { player, node -> + val currentShield = player.equipment[EquipmentContainer.SLOT_SHIELD] + if (currentShield != null) { + player.equipment.remove(currentShield) + } + visualize(player, -1, Graphics(mShieldEquip,95)) + runTask(player, 2) { + val item = node.asItem() + player.equipment.add(item, item.slot, true, true) + player.inventory.add(currentShield) + } + return@onEquip false } onEquip(Items.ELEMENTAL_HELMET_9729) { player, _ -> - sendGraphics(eHelmEquip, player.location) + visualize(player, -1, eHelmEquip) return@onEquip true } - onEquip(Items.ELEMENTAL_SHIELD_2890) { player, _ -> - sendGraphics(Graphics(eShieldEquip,95), player.location) - return@onEquip true + onEquip(Items.ELEMENTAL_SHIELD_2890) { player, node -> + val currentShield = player.equipment[EquipmentContainer.SLOT_SHIELD] + if (currentShield != null) { + player.equipment.remove(currentShield) + } + visualize(player, -1, Graphics(eShieldEquip,95)) + runTask(player, 2) { + val item = node.asItem() + player.equipment.add(item, item.slot, true, true) + player.inventory.add(currentShield) + } + return@onEquip false } } From ca5b1f6ca970e40b0a0dd4aab172857686dc08ac Mon Sep 17 00:00:00 2001 From: aidan Date: Tue, 7 Apr 2026 19:56:40 -0500 Subject: [PATCH 14/45] add overrides for setstage and updatevarps so that the journal updates if you cheat your way to the end. modify pin interact destinations. --- .../quest/elementalworkshop2/EW2Listeners.kt | 5 +++++ .../elementalworkshop2/ElementalWorkshop2.kt | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 4042a1eeb..d4a4ddb44 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -1319,5 +1319,10 @@ class EW2Listeners : InteractionListener { setDest(IntType.SCENERY, intArrayOf(Scenery.PIPING_18650), "use") { _, _ -> return@setDest Location.create(1953, 5167, 3) } + + // the double stacked pins and the cogs on them. + setDest(IntType.SCENERY, intArrayOf(Scenery.PIN_18665, Scenery.PIN_18666, Scenery.COG_18673, Scenery.COG_18674, Scenery.COG_18675, Scenery.COG_18667, Scenery.COG_18668, Scenery.COG_18669), "use") { _, _ -> + return@setDest Location.create(1958, 5157, 2) + } } } \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt index c1dac6f59..f5dc32f0a 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt @@ -234,6 +234,12 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Sta line(player, "Creating the elemental mind helm", line++, stage > 11) line(player, "was easy with the book for instruction.", line++, stage > 11) } + + // quest complete + if (stage >= 100) { + line++ + line(player,"QUEST COMPLETE!", line) + } } // use ::setqueststage 53 0 to reset quest. @@ -250,6 +256,20 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Sta super.reset(player) } + override fun setStage(player: Player, stage: Int) { + super.setStage(player, stage) + this.updateVarps(player) + } + + override fun updateVarps(player: Player) { + // The quest stages are perfectly aligned with the varp + if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) >= 11) { + setVarbit(player, EW2StageVarbit, 11, true) // except for stage 100 which is varp set to 11. + } else { + setVarbit(player, EW2StageVarbit, getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II), true) + } + } + override fun finish(player: Player) { var ln = 10 super.finish(player) From 91f9bcaa1953f16089624e2c3aa2acdc3e6686e5 Mon Sep 17 00:00:00 2001 From: aidan Date: Wed, 8 Apr 2026 07:23:07 -0500 Subject: [PATCH 15/45] remove the dest overrides --- .../seers/quest/elementalworkshop2/EW2Listeners.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index d4a4ddb44..40ba3aa29 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -1320,9 +1320,9 @@ class EW2Listeners : InteractionListener { return@setDest Location.create(1953, 5167, 3) } - // the double stacked pins and the cogs on them. - setDest(IntType.SCENERY, intArrayOf(Scenery.PIN_18665, Scenery.PIN_18666, Scenery.COG_18673, Scenery.COG_18674, Scenery.COG_18675, Scenery.COG_18667, Scenery.COG_18668, Scenery.COG_18669), "use") { _, _ -> - return@setDest Location.create(1958, 5157, 2) - } + // the double stacked pins and the cogs on them. todo probably remove these later + //setDest(IntType.SCENERY, intArrayOf(Scenery.PIN_18665, Scenery.PIN_18666, Scenery.COG_18673, Scenery.COG_18674, Scenery.COG_18675, Scenery.COG_18667, Scenery.COG_18668, Scenery.COG_18669), "use") { _, _ -> + // return@setDest Location.create(1958, 5157, 2) + //} } } \ No newline at end of file From 2d69b4a1fefec045885107ccaeb5a85593a51f8e Mon Sep 17 00:00:00 2001 From: aidan Date: Wed, 8 Apr 2026 08:10:47 -0500 Subject: [PATCH 16/45] remove the regionChunk hack --- .../seers/quest/elementalworkshop2/EW2Listeners.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 40ba3aa29..d11510a30 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -1049,7 +1049,7 @@ class EW2Listeners : InteractionListener { // I have commented out what would be the correct function below in case the issue ever gets fixed. // As a hack, the player is only allowed to place the medium cog below the small cog. // You can't remove the medium cog because it throws an exception (resetting the varbit will 'remove' the cog). kill me. - onUseWith(IntType.SCENERY, intArrayOf(Items.MEDIUM_COG_9725), 18673) { player, item, _ -> + /*onUseWith(IntType.SCENERY, intArrayOf(Items.MEDIUM_COG_9725), 18673) { player, item, _ -> when(item.id) { Items.MEDIUM_COG_9725 -> { setVarbit(player, EW2CogVarbit2, 2, true) @@ -1059,9 +1059,9 @@ class EW2Listeners : InteractionListener { } } return@onUseWith true - } + }*/ + - /* // use cog with pin 2 // pin 2: 2656: val 0 is scenery 18664 (pin). val 1 is scenery 18667 (small cog). val 2 is scenery 18668 (med cog). val 3 is scenery 18669 (large cog) onUseWith(IntType.SCENERY, intArrayOf(Items.SMALL_COG_9726, Items.MEDIUM_COG_9725, Items.LARGE_COG_9724), Scenery.PIN_18664) { player, item, _ -> @@ -1087,7 +1087,7 @@ class EW2Listeners : InteractionListener { } return@onUseWith true } - */ + // use cog with pin 3 // pin 3: 2657: val 0 is scenery 18666 (pin). val 1 is scenery 18670 (small cog). val 2 is scenery 18671 (med cog). val 3 is scenery 18672 (large cog) From 0bc209e63242fee5f579590ef7ef0ed69012e2b4 Mon Sep 17 00:00:00 2001 From: aidan Date: Wed, 8 Apr 2026 11:10:39 -0500 Subject: [PATCH 17/45] Revert "remove the regionChunk hack" This reverts commit 205f0fd748e02b47a79daffd35b6eb366d5b66fb. --- .../seers/quest/elementalworkshop2/EW2Listeners.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index d11510a30..40ba3aa29 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -1049,7 +1049,7 @@ class EW2Listeners : InteractionListener { // I have commented out what would be the correct function below in case the issue ever gets fixed. // As a hack, the player is only allowed to place the medium cog below the small cog. // You can't remove the medium cog because it throws an exception (resetting the varbit will 'remove' the cog). kill me. - /*onUseWith(IntType.SCENERY, intArrayOf(Items.MEDIUM_COG_9725), 18673) { player, item, _ -> + onUseWith(IntType.SCENERY, intArrayOf(Items.MEDIUM_COG_9725), 18673) { player, item, _ -> when(item.id) { Items.MEDIUM_COG_9725 -> { setVarbit(player, EW2CogVarbit2, 2, true) @@ -1059,9 +1059,9 @@ class EW2Listeners : InteractionListener { } } return@onUseWith true - }*/ - + } + /* // use cog with pin 2 // pin 2: 2656: val 0 is scenery 18664 (pin). val 1 is scenery 18667 (small cog). val 2 is scenery 18668 (med cog). val 3 is scenery 18669 (large cog) onUseWith(IntType.SCENERY, intArrayOf(Items.SMALL_COG_9726, Items.MEDIUM_COG_9725, Items.LARGE_COG_9724), Scenery.PIN_18664) { player, item, _ -> @@ -1087,7 +1087,7 @@ class EW2Listeners : InteractionListener { } return@onUseWith true } - + */ // use cog with pin 3 // pin 3: 2657: val 0 is scenery 18666 (pin). val 1 is scenery 18670 (small cog). val 2 is scenery 18671 (med cog). val 3 is scenery 18672 (large cog) From 977f31796f7abcb00685971a560e9b3e015d2e7e Mon Sep 17 00:00:00 2001 From: aidan Date: Wed, 8 Apr 2026 08:10:47 -0500 Subject: [PATCH 18/45] remove the regionChunk hack --- .../seers/quest/elementalworkshop2/EW2Listeners.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 40ba3aa29..d11510a30 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -1049,7 +1049,7 @@ class EW2Listeners : InteractionListener { // I have commented out what would be the correct function below in case the issue ever gets fixed. // As a hack, the player is only allowed to place the medium cog below the small cog. // You can't remove the medium cog because it throws an exception (resetting the varbit will 'remove' the cog). kill me. - onUseWith(IntType.SCENERY, intArrayOf(Items.MEDIUM_COG_9725), 18673) { player, item, _ -> + /*onUseWith(IntType.SCENERY, intArrayOf(Items.MEDIUM_COG_9725), 18673) { player, item, _ -> when(item.id) { Items.MEDIUM_COG_9725 -> { setVarbit(player, EW2CogVarbit2, 2, true) @@ -1059,9 +1059,9 @@ class EW2Listeners : InteractionListener { } } return@onUseWith true - } + }*/ + - /* // use cog with pin 2 // pin 2: 2656: val 0 is scenery 18664 (pin). val 1 is scenery 18667 (small cog). val 2 is scenery 18668 (med cog). val 3 is scenery 18669 (large cog) onUseWith(IntType.SCENERY, intArrayOf(Items.SMALL_COG_9726, Items.MEDIUM_COG_9725, Items.LARGE_COG_9724), Scenery.PIN_18664) { player, item, _ -> @@ -1087,7 +1087,7 @@ class EW2Listeners : InteractionListener { } return@onUseWith true } - */ + // use cog with pin 3 // pin 3: 2657: val 0 is scenery 18666 (pin). val 1 is scenery 18670 (small cog). val 2 is scenery 18671 (med cog). val 3 is scenery 18672 (large cog) From ac074d0aa7c2ecac696f9c1d6bea201b29e58d81 Mon Sep 17 00:00:00 2001 From: aidan Date: Wed, 8 Apr 2026 11:10:39 -0500 Subject: [PATCH 19/45] Revert "remove the regionChunk hack" This reverts commit 205f0fd748e02b47a79daffd35b6eb366d5b66fb. --- .../seers/quest/elementalworkshop2/EW2Listeners.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index d11510a30..40ba3aa29 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -1049,7 +1049,7 @@ class EW2Listeners : InteractionListener { // I have commented out what would be the correct function below in case the issue ever gets fixed. // As a hack, the player is only allowed to place the medium cog below the small cog. // You can't remove the medium cog because it throws an exception (resetting the varbit will 'remove' the cog). kill me. - /*onUseWith(IntType.SCENERY, intArrayOf(Items.MEDIUM_COG_9725), 18673) { player, item, _ -> + onUseWith(IntType.SCENERY, intArrayOf(Items.MEDIUM_COG_9725), 18673) { player, item, _ -> when(item.id) { Items.MEDIUM_COG_9725 -> { setVarbit(player, EW2CogVarbit2, 2, true) @@ -1059,9 +1059,9 @@ class EW2Listeners : InteractionListener { } } return@onUseWith true - }*/ - + } + /* // use cog with pin 2 // pin 2: 2656: val 0 is scenery 18664 (pin). val 1 is scenery 18667 (small cog). val 2 is scenery 18668 (med cog). val 3 is scenery 18669 (large cog) onUseWith(IntType.SCENERY, intArrayOf(Items.SMALL_COG_9726, Items.MEDIUM_COG_9725, Items.LARGE_COG_9724), Scenery.PIN_18664) { player, item, _ -> @@ -1087,7 +1087,7 @@ class EW2Listeners : InteractionListener { } return@onUseWith true } - + */ // use cog with pin 3 // pin 3: 2657: val 0 is scenery 18666 (pin). val 1 is scenery 18670 (small cog). val 2 is scenery 18671 (med cog). val 3 is scenery 18672 (large cog) From 74bc12749c5156842c62672b2886cdd1b466d678 Mon Sep 17 00:00:00 2001 From: Aidan Schieuer Date: Sun, 19 Apr 2026 11:40:30 -0500 Subject: [PATCH 20/45] store book in poh --- .../seers/quest/elementalworkshop/SlashedBookHandler.kt | 2 ++ .../kandarin/seers/quest/elementalworkshop2/BeatenBook.kt | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/SlashedBookHandler.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/SlashedBookHandler.kt index 01f24b0e6..6028bf595 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/SlashedBookHandler.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/SlashedBookHandler.kt @@ -1,6 +1,7 @@ package content.region.kandarin.seers.quest.elementalworkshop import content.global.handlers.iface.BookInterface +import core.api.storeBookInHouse import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.node.entity.player.Player @@ -25,6 +26,7 @@ class SlashedBookHandler : InteractionListener { override fun defineListeners() { on(Items.SLASHED_BOOK_9715, IntType.ITEM, "read") { player, _ -> BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) + storeBookInHouse(player, Items.BATTERED_BOOK_2886) return@on true } } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt index f81bb75be..139a5d710 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt @@ -201,7 +201,7 @@ class BeatenBook : InteractionListener { } override fun defineListeners() { - on(Items.BEATEN_BOOK_9717, IntType.ITEM, "read") { player, _ -> + on(Items.BEATEN_BOOK_9717, IntType.ITEM, "read") { player, node -> // the first time you open the book, the scroll will drop. if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 1) { addItemOrDrop(player, Items.SCROLL_9721) @@ -215,9 +215,9 @@ class BeatenBook : InteractionListener { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 2) setVarbit(player, EW2StageVarbit, 2, true) - // storeBookInHouse(player, node) // todo uncomment this when poh costume room is in + storeBookInHouse(player, node) } else { - // storeBookInHouse(player, node) // todo uncomment this when poh costume room is in + storeBookInHouse(player, node) BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) } return@on true From 4424a6fc3904bbddffbdaaadc9d614fdd4aaf220 Mon Sep 17 00:00:00 2001 From: Aidan Schieuer Date: Thu, 23 Apr 2026 10:27:53 -0500 Subject: [PATCH 21/45] revert a change to make way for PARLOR CHAIRS --- .../main/core/game/system/command/sets/TeleportCommandSet.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt b/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt index 0f45f3766..30e4107c9 100644 --- a/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt @@ -142,8 +142,8 @@ class TeleportCommandSet : CommandSet(Privilege.ADMIN){ /** * Finds a list of objects/sceneries in a region */ - define("findobjs", Privilege.ADMIN, "::findobjs REGION ID SCENERY ID START SCENERY ID END", "Finds all locations of scenery objects of id."){player, args -> - if(args.size < 4) reject(player, "Usage: region_id scenery_id_start scenery_id_end") + define("findobjs", Privilege.ADMIN, "::findobjs REGION ID SCENERY ID", "Finds all locations of scenery objects of id."){player, args -> + if(args.size < 4) reject(player, "Usage: region_id scenery_id") val regionId = args[1].toInt() val sceneryId = args[2].toInt() val sceneryIdEnd = args[3].toInt() From 94257a93c280621d821abe72385cd9013d074024 Mon Sep 17 00:00:00 2001 From: Aidan Schieuer Date: Sat, 25 Apr 2026 02:31:03 -0500 Subject: [PATCH 22/45] listener logic is now proper with no arios hacks. help me player name, you're my only hope. --- .../quest/elementalworkshop2/EW2Listeners.kt | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 40ba3aa29..88f894474 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -987,7 +987,6 @@ class EW2Listeners : InteractionListener { return@on true } - // todo see below, note that the pin 2 cannot be taken from because it overlaps with pin 1 and our limited engine can't find it. // taking the cogs from the pins on(cogRange, IntType.SCENERY, "take") { player, node -> // determine what to take @@ -1045,24 +1044,7 @@ class EW2Listeners : InteractionListener { return@onUseWith true } - //todo Since Arios is shit (https://gitlab.com/2009scape/2009scape/-/issues/1841) we can't act directly with pin 2. - // I have commented out what would be the correct function below in case the issue ever gets fixed. - // As a hack, the player is only allowed to place the medium cog below the small cog. - // You can't remove the medium cog because it throws an exception (resetting the varbit will 'remove' the cog). kill me. - onUseWith(IntType.SCENERY, intArrayOf(Items.MEDIUM_COG_9725), 18673) { player, item, _ -> - when(item.id) { - Items.MEDIUM_COG_9725 -> { - setVarbit(player, EW2CogVarbit2, 2, true) - sendItemDialogue(player, item, "You put the medium cog onto the shaft.") - animate(player, 537) - removeItem(player, item) - } - } - return@onUseWith true - } - - /* - // use cog with pin 2 + // use cog with pin 2 // This listener brought to you by Player Name and his famous work with Arios RegionChunk fixing. Without that work it wouldn't have been possible to interact with this pin. // pin 2: 2656: val 0 is scenery 18664 (pin). val 1 is scenery 18667 (small cog). val 2 is scenery 18668 (med cog). val 3 is scenery 18669 (large cog) onUseWith(IntType.SCENERY, intArrayOf(Items.SMALL_COG_9726, Items.MEDIUM_COG_9725, Items.LARGE_COG_9724), Scenery.PIN_18664) { player, item, _ -> when(item.id) { @@ -1087,7 +1069,6 @@ class EW2Listeners : InteractionListener { } return@onUseWith true } - */ // use cog with pin 3 // pin 3: 2657: val 0 is scenery 18666 (pin). val 1 is scenery 18670 (small cog). val 2 is scenery 18671 (med cog). val 3 is scenery 18672 (large cog) @@ -1320,9 +1301,13 @@ class EW2Listeners : InteractionListener { return@setDest Location.create(1953, 5167, 3) } - // the double stacked pins and the cogs on them. todo probably remove these later - //setDest(IntType.SCENERY, intArrayOf(Scenery.PIN_18665, Scenery.PIN_18666, Scenery.COG_18673, Scenery.COG_18674, Scenery.COG_18675, Scenery.COG_18667, Scenery.COG_18668, Scenery.COG_18669), "use") { _, _ -> - // return@setDest Location.create(1958, 5157, 2) - //} + // the double stacked pins and the cogs on them. + setDest(IntType.SCENERY, intArrayOf( + Scenery.PIN_18664, Scenery.PIN_18665, + Scenery.COG_18673, Scenery.COG_18674, Scenery.COG_18675, + Scenery.COG_18667, Scenery.COG_18668, Scenery.COG_18669 + ), "use","take") { _, _ -> + return@setDest Location.create(1958, 5157, 2) + } } } \ No newline at end of file From 8903ed619aec2a385277379cd369a2b39ce510d8 Mon Sep 17 00:00:00 2001 From: aidan Date: Fri, 1 May 2026 08:53:53 -0500 Subject: [PATCH 23/45] make scroll reobtainable, fix scroll drop dialogue, add a period, fix EW1 slashed book retrieval --- .../quest/elementalworkshop/EWListeners.kt | 49 ++++++++++++++----- .../quest/elementalworkshop2/BeatenBook.kt | 39 ++++++++++----- 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt index 111d6a274..716b4f667 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt @@ -17,7 +17,9 @@ import core.game.interaction.InteractionListener import core.game.interaction.IntType import core.tools.Log import content.data.Quests +import content.global.handlers.iface.BookInterface import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2StageVarbit +import core.game.dialogue.DialogueLabeller /** * Listeners for the Elemental Workshop I quest @@ -109,22 +111,45 @@ class EWListeners : InteractionListener { } // --- AFTER QUEST START --- - if (player.inventory.containsItem(slashedBook)) { - sendItemDialogue(player, Item(Items.SLASHED_BOOK_9715), - "There is a book here titled 'The Elemental Shield'. " + - "It can stay here, as you have a copy in your backpack." - ) - if (player.inventory.addIfDoesntHave(batteredKey)) { - sendItemDialogue(player, Item(Items.BATTERED_KEY_2887),"You also find a key.") - } + + // if player doesn't have book and doesn't have key + if (!inInventory(player, slashedBook.id) && !inInventory(player, batteredKey.id)) { + addItemOrDrop(player, batteredKey.id) + addItemOrDrop(player, slashedBook.id) + openDialogue(player, object : DialogueLabeller() { + override fun addConversation() { + item(slashedBook, "You find a book titled 'The Elemental Shield'.") + item(batteredKey,"You also find a key.") + } + }) return@on true } - sendItemDialogue(player, Item(Items.SLASHED_BOOK_9715), "You find a book titled 'The Elemental Shield'.") - addItemOrDrop(player, slashedBook.id) - if (player.inventory.addIfDoesntHave(batteredKey)) { - sendItemDialogue(player, Item(Items.BATTERED_KEY_2887),"You also find a key.") + // if player doesn't have book and does have key + if (!inInventory(player, slashedBook.id) && inInventory(player, batteredKey.id)) { + addItemOrDrop(player, slashedBook.id) + sendItemDialogue(player, slashedBook, "You find a book titled 'The Elemental Shield'.") + return@on true } + + // if player does have book but doesn't have key + if (inInventory(player, slashedBook.id) && !inInventory(player, batteredKey.id)) { + addItemOrDrop(player, batteredKey.id) + openDialogue(player, object : DialogueLabeller() { + override fun addConversation() { + item(slashedBook, "There is a book here titled 'The Elemental Shield'. " + "It can stay here, as you have a copy in your backpack.") + item(batteredKey,"You also find a key.") + } + }) + return@on true + } + + // if player does have book and does have key + if (inInventory(player, slashedBook.id) && inInventory(player, batteredKey.id)) { + sendItemDialogue(player, slashedBook, "There is a book here titled 'The Elemental Shield'. " + "It can stay here, as you have a copy in your backpack.") + return@on true + } + return@on true } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt index 139a5d710..335fd222a 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt @@ -5,8 +5,10 @@ import content.global.handlers.iface.* import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2StageVarbit import core.game.interaction.InteractionListener import core.api.* +import core.game.dialogue.DialogueLabeller import core.game.interaction.IntType import core.game.node.entity.player.Player +import core.game.node.item.Item import org.rs09.consts.Items /** @@ -175,7 +177,7 @@ class BeatenBook : InteractionListener { BookLine("referred to his", 61), BookLine("preparation technique as", 62), BookLine("'priming', and he referred", 63), - BookLine("to his apprentice as 'Idiot'", 64), + BookLine("to his apprentice as 'Idiot'.", 64), BookLine("", 65), ), Page( @@ -202,22 +204,33 @@ class BeatenBook : InteractionListener { override fun defineListeners() { on(Items.BEATEN_BOOK_9717, IntType.ITEM, "read") { player, node -> - // the first time you open the book, the scroll will drop. + + // increment quest stage if needed if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 1) { - addItemOrDrop(player, Items.SCROLL_9721) - sendMessage(player, "The book has two parts; an introduction and an instruction section.") - sendMessage(player, "You flip the book open to the introduction and start reading.") - - // todo the dialogue and openbook should run sequentially, but i've messed around with queuescript and can't figure it out. - // this also applies to EW1 listener that gives the slashed book and battered key back in EWListeners.kt - sendItemDialogue(player, Items.SCROLL_9721, "You find a slip of paper that has been used for a bookmark. You put it in your pack to study later.") - BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) - setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 2) setVarbit(player, EW2StageVarbit, 2, true) - storeBookInHouse(player, node) + } + + storeBookInHouse(player, node) + + // if the player doesn't have the scroll, drop it + if (hasAnItem(player, Items.SCROLL_9721).container == null) { + sendMessage(player, "The book has two parts; an introduction and an instruction section.") + sendMessage(player, "You flip the book open to the introduction and start reading.") + addItemOrDrop(player, Items.SCROLL_9721) // scroll needs to be added here because otherwise in the afterClose block it'll give you 3 lmao + openDialogue(player, object : DialogueLabeller() { + override fun addConversation() { + item(Item(Items.SCROLL_9721), "You find a slip of paper that has been used", "for a bookmark. You put it in your pack to", "study later.") + afterClose { player -> + BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) + } + } + }) + + // if player has scroll, open book } else { - storeBookInHouse(player, node) + sendMessage(player, "The book has two parts; an introduction and an instruction section.") + sendMessage(player, "You flip the book open to the introduction and start reading.") BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) } return@on true From 430d7e0467378bd7691264fa2d3670f23579ce43 Mon Sep 17 00:00:00 2001 From: aidan Date: Fri, 22 May 2026 07:03:00 -0500 Subject: [PATCH 24/45] "You open the valve." "You close the valve." --- .../kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 88f894474..ead95da5d 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -899,7 +899,7 @@ class EW2Listeners : InteractionListener { setVarbit(player, EW2ValveWestVarbit, 1, true) runTask(player, 3) { syncTank(player) - sendDialogue(player, "you open the valve") + sendDialogue(player, "You open the valve.") playAudio(player, Sounds.EW2_WATER_VALVE_3185) } // if open, close the valve @@ -907,7 +907,7 @@ class EW2Listeners : InteractionListener { setVarbit(player, EW2ValveWestVarbit, 0, true) runTask(player, 3) { syncTank(player) - sendDialogue(player, "you close the valve") + sendDialogue(player, "You close the valve.") playAudio(player, Sounds.EW2_WATER_VALVE_3185) } } From 9300b98766cac00625ebdbdb3dce51ea2113b769 Mon Sep 17 00:00:00 2001 From: aidan Date: Wed, 27 May 2026 20:28:34 -0500 Subject: [PATCH 25/45] getLocalNPCs and ChunkFlags --- .../kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt | 4 ++-- .../kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index ead95da5d..7b519ed2a 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -197,7 +197,7 @@ class EW2Listeners : InteractionListener { // find cart val trackCenter = Location.create(1953, 5155, 2) - val existingCart = RegionManager.getLocalNpcs(trackCenter, 15).find { it.id in jigCartStates } + val existingCart = RegionManager.getLocalNPCs(trackCenter, 15).find { it.id in jigCartStates } if (existingCart != null) { // update appearance if (existingCart.id != targetId) { @@ -451,7 +451,7 @@ class EW2Listeners : InteractionListener { // find the cart val trackCenter = Location.create(1953, 5155, 2) - val existingCart = RegionManager.getLocalNpcs(trackCenter, 15).find { it.id in jigCartStates } + val existingCart = RegionManager.getLocalNPCs(trackCenter, 15).find { it.id in jigCartStates } if (existingCart == null) { sendMessage(player, "CART MISSING. LOG IN AND OUT TO RESET IT.") return@on true diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt index 7967c2e65..d83a97a3b 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt @@ -5,7 +5,7 @@ import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPCBehavior import core.game.world.map.Location import core.game.world.map.RegionManager -import core.game.world.map.build.RegionFlags +import core.game.world.map.build.ChunkFlags import core.game.world.map.path.ClipMaskSupplier import org.rs09.consts.NPCs @@ -38,6 +38,6 @@ class JigCartNPCBehavior : NPCBehavior(NPCs.JIG_CART_4913, NPCs.JIG_CART_4914, N object CartClipper : ClipMaskSupplier { override fun getClippingFlag (z: Int, x: Int, y: Int) : Int { val flag = RegionManager.getClippingFlag(z, x, y) - return flag and (RegionFlags.SOLID_TILE.inv()) and (RegionFlags.TILE_OBJECT.inv()) and (RegionFlags.OBJ_10.inv()) + return flag and (ChunkFlags.SOLID_TILE.inv()) and (ChunkFlags.TILE_OBJECT.inv()) and (ChunkFlags.OBJ_10.inv()) } } \ No newline at end of file From a21577191c1ba66f93b16a351df5534a07d0e699 Mon Sep 17 00:00:00 2001 From: aidan Date: Wed, 27 May 2026 20:41:26 -0500 Subject: [PATCH 26/45] add an open listener to hatch --- .../seers/quest/elementalworkshop2/EW2Listeners.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 7b519ed2a..2660458a7 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -377,6 +377,16 @@ class EW2Listeners : InteractionListener { return@onUseWith true } + // unlocking the hatch + on(Scenery.HATCH_18595, IntType.SCENERY, "open") { player, _ -> + if (inInventory(player, Items.KEY_9722)) { + sendItemDialogue(player, Items.KEY_9722, "The key fits the lock in the hatch perfectly.") + setVarbit(player, EW2HatchVarbit, 1, true) + } else sendMessage(player, "The hatch is locked.") + + return@on true + } + // unlocked center hatch down to EW2 on(Scenery.HATCH_18596, IntType.SCENERY, "climb-down") { player, _ -> sendMessage(player, "You climb down the stairs.") From dd001312f1a7d8fac18c83686ff7560cededcd45 Mon Sep 17 00:00:00 2001 From: aidan Date: Thu, 28 May 2026 11:40:39 -0500 Subject: [PATCH 27/45] you must close the book before you can open it again --- .../quest/elementalworkshop2/BeatenBook.kt | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt index 335fd222a..11f167232 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt @@ -205,34 +205,38 @@ class BeatenBook : InteractionListener { override fun defineListeners() { on(Items.BEATEN_BOOK_9717, IntType.ITEM, "read") { player, node -> - // increment quest stage if needed - if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 1) { - setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 2) - setVarbit(player, EW2StageVarbit, 2, true) - } + // check the player is not already reading the book + if (!player.interfaceManager.isOpened) { + // increment quest stage if needed + if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 1) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 2) + setVarbit(player, EW2StageVarbit, 2, true) + } - storeBookInHouse(player, node) + storeBookInHouse(player, node) - // if the player doesn't have the scroll, drop it - if (hasAnItem(player, Items.SCROLL_9721).container == null) { - sendMessage(player, "The book has two parts; an introduction and an instruction section.") - sendMessage(player, "You flip the book open to the introduction and start reading.") - addItemOrDrop(player, Items.SCROLL_9721) // scroll needs to be added here because otherwise in the afterClose block it'll give you 3 lmao - openDialogue(player, object : DialogueLabeller() { - override fun addConversation() { - item(Item(Items.SCROLL_9721), "You find a slip of paper that has been used", "for a bookmark. You put it in your pack to", "study later.") - afterClose { player -> - BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) + // if the player doesn't have the scroll, drop it + if (hasAnItem(player, Items.SCROLL_9721).container == null) { + sendMessage(player, "The book has two parts; an introduction and an instruction section.") + sendMessage(player, "You flip the book open to the introduction and start reading.") + addItemOrDrop(player, Items.SCROLL_9721) // scroll needs to be added here because otherwise in the afterClose block it'll give you 3 lmao + openDialogue(player, object : DialogueLabeller() { + override fun addConversation() { + item(Item(Items.SCROLL_9721), "You find a slip of paper that has been used", "for a bookmark. You put it in your pack to", "study later.") + afterClose { player -> + BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) + } } - } - }) + }) + + // if player has scroll, open book + } else { + sendMessage(player, "The book has two parts; an introduction and an instruction section.") + sendMessage(player, "You flip the book open to the introduction and start reading.") + BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) + } + } else sendMessage(player, "Please finish what you're doing first.") // this is the same message/logic used for clue scrolls - // if player has scroll, open book - } else { - sendMessage(player, "The book has two parts; an introduction and an instruction section.") - sendMessage(player, "You flip the book open to the introduction and start reading.") - BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) - } return@on true } } From 84faca26081931345539ebb78979c39b50c87e15 Mon Sep 17 00:00:00 2001 From: aidan Date: Thu, 28 May 2026 16:03:41 -0500 Subject: [PATCH 28/45] double hatch define --- .../kandarin/seers/quest/elementalworkshop/EWListeners.kt | 6 ------ .../kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt index 716b4f667..40eacb72d 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt @@ -241,12 +241,6 @@ class EWListeners : InteractionListener { /* * * * * * * * * * * * * * * * * Workshop Area listeners * * * * * * * * * * * * * * * */ - // Center hatch, inaccessible until unlocked during EW2 - on(Scenery.HATCH_18595, IntType.SCENERY, "open") { player, _ -> - sendDialogue(player, "You're unable to open the locked hatch.") - return@on true - } - // Stone bowl box, player can get more at any time on(Scenery.BOXES_3397, IntType.SCENERY, "search") { player, _ -> // If the player doesn't have a stone bowl in their inventory diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 2660458a7..6dd04a408 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -382,7 +382,7 @@ class EW2Listeners : InteractionListener { if (inInventory(player, Items.KEY_9722)) { sendItemDialogue(player, Items.KEY_9722, "The key fits the lock in the hatch perfectly.") setVarbit(player, EW2HatchVarbit, 1, true) - } else sendMessage(player, "The hatch is locked.") + } else sendMessage(player, "You're unable to open the locked hatch.") return@on true } From 2c720efd441aec0ea8a29d21544fb8d02aae5b2e Mon Sep 17 00:00:00 2001 From: aidan Date: Mon, 8 Jun 2026 14:10:39 -0500 Subject: [PATCH 29/45] rebase and rejson --- Server/data/configs/item_configs.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/data/configs/item_configs.json b/Server/data/configs/item_configs.json index 8a7014647..2dddd7791 100644 --- a/Server/data/configs/item_configs.json +++ b/Server/data/configs/item_configs.json @@ -27086,7 +27086,7 @@ { "id": "2888", "name": "A stone bowl", - "examine": "This is an empty stone bowl. / This is a stone bowl full of lava.", + "examine": "This is an empty stone bowl.", "archery_ticket_price": "0", "durability": null, "equipment_slot": "3" @@ -27094,7 +27094,7 @@ { "id": "2889", "name": "A stone bowl", - "examine": "This is an empty stone bowl. / This is a stone bowl full of lava.", + "examine": "This is a stone bowl full of lava.", "archery_ticket_price": "0", "durability": null, "equipment_slot": "3" From e009d55ab213be3e96f7344f5c49df9715cfdadd Mon Sep 17 00:00:00 2001 From: aidan Date: Tue, 7 Jul 2026 09:46:19 -0500 Subject: [PATCH 30/45] this zone somewhat works --- .../seers/quest/elementalworkshop2/EW2Zone.kt | 84 ++++++++++++------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt index 3980cdce9..fd3dae6cf 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt @@ -4,59 +4,79 @@ import content.data.Quests import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCart import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCrane import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncFan +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncSteamPress import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTank import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTankDoor +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.toInstance import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2StageVarbit import core.api.* +import core.game.interaction.QueueStrength import core.game.node.entity.Entity import core.game.node.entity.player.Player -import core.game.node.scenery.SceneryBuilder import core.game.world.map.Location -import core.plugin.Initializable -import core.game.world.map.zone.MapZone +import core.game.world.map.build.DynamicRegion import core.game.world.map.zone.ZoneBorders -import core.game.world.map.zone.ZoneBuilder -import core.plugin.Plugin import org.rs09.consts.Scenery /** * This updates the Elemental Workshop II state whenever the player enters, - * keeping the equipment state synced to the saved varbits. + * keeping the equipment state synced to the saved varbits. The workshop + * area is a dynamic region, per OSRS wiki. */ -@Initializable -class EW2Zone : MapZone("Elemental Workshop II", true), Plugin { +class EW2Zone : MapArea { - override fun newInstance(arg: Any?): EW2Zone { - ZoneBuilder.configure(this) - return this + override fun defineAreaBorders(): Array { + return arrayOf(ZoneBorders(1920,5120,1983,5183)) } - override fun configure() { - super.register(ZoneBorders(1936,5128,1982,5172)) - } + override fun areaEnter(entity: Entity) { + if(entity is Player) { - // updates the workshop state based on the varbits for Elemental Workshop II - override fun enter(e: Entity?): Boolean { - if(e is Player) { + val baseRegion = 7760 - if(getQuestStage(e, Quests.ELEMENTAL_WORKSHOP_II) <= 5) { - setQuestStage(e, Quests.ELEMENTAL_WORKSHOP_II, 6) - setVarbit(e, EW2StageVarbit, 6, true) + // create the dynamic region + val dynamicRegion = DynamicRegion.create(baseRegion) + + // save the exit location back to the real world surface + setAttribute(entity, "ew2_exit_location", Location.create(2713, 3484, 0)) + + // find the player's offset from the base coordinates + val offsetX = entity.location.x - ((baseRegion shr 8) shl 6) + val offsetY = entity.location.y - ((baseRegion and 0xFF) shl 6) + + // apply offset to the dynamic region base + val destX = dynamicRegion.borders.southWestX + offsetX + val destY = dynamicRegion.borders.southWestY + offsetY + + // teleport the player into the instance + teleport(entity, Location.create(destX, destY, entity.location.z)) + + // save the dynamic region coords to the player to reference later + setAttribute(entity, ElementalWorkshop2.coordsX, dynamicRegion.borders.southWestX) + setAttribute(entity, ElementalWorkshop2.coordsY, dynamicRegion.borders.southWestY) + + // advance quest + if(getQuestStage(entity, Quests.ELEMENTAL_WORKSHOP_II) <= 5) { + setQuestStage(entity, Quests.ELEMENTAL_WORKSHOP_II, 6) + setVarbit(entity, EW2StageVarbit, 6, true) } - SceneryBuilder.add(core.game.node.scenery.Scenery(Scenery.DOOR_18651, Location.create(1953, 5162, 2), 1)) - syncCrane(e) - syncCart(e) - //syncSteamPress(e) - syncTank(e) - syncTankDoor(e) - syncFan(e) - } - return super.enter(e) - } + // sync the workshop state + queueScript(entity, 2, QueueStrength.SOFT) { _ -> - override fun fireEvent(identifier: String?, vararg args: Any?): Any { - return Unit + // add this door because I can't interact without parlour chairs todo test removing this on test + // toInstance(entity, Location.create(1953, 5162, 2)) + addScenery(core.game.node.scenery.Scenery(Scenery.DOOR_18651, location(33,42,2), 1)) + + syncCrane(entity) + syncCart(entity) + syncSteamPress(entity) + syncTank(entity) + syncTankDoor(entity) + syncFan(entity) + return@queueScript stopExecuting(entity) + } + } } } \ No newline at end of file From 4c17d1f37c683a26f2df70bdce3f369a0c3d89e0 Mon Sep 17 00:00:00 2001 From: aidan Date: Tue, 7 Jul 2026 09:46:34 -0500 Subject: [PATCH 31/45] equip graphics --- .../equipment/ElementalEquipmentGraphics.kt | 73 +++++++++++++++++++ .../player/link/appearance/Appearance.java | 23 +++++- 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 Server/src/main/content/global/handlers/item/equipment/ElementalEquipmentGraphics.kt diff --git a/Server/src/main/content/global/handlers/item/equipment/ElementalEquipmentGraphics.kt b/Server/src/main/content/global/handlers/item/equipment/ElementalEquipmentGraphics.kt new file mode 100644 index 000000000..225a7fd1a --- /dev/null +++ b/Server/src/main/content/global/handlers/item/equipment/ElementalEquipmentGraphics.kt @@ -0,0 +1,73 @@ +package content.global.handlers.item.equipment + +import content.data.Quests +import core.api.* +import core.game.interaction.InteractionListener +import core.game.interaction.QueueStrength +import core.game.node.entity.player.Player +import core.game.world.update.flag.context.Graphics +import org.rs09.consts.Items + +/** + * The graphics that run when you equip elemental equipment. + */ + +class ElementalEquipmentGraphics : InteractionListener { + + // the graphics that run on equip + private val mShieldEquip = 809 + private val mHelmEquip = 810 + private val eShieldEquip = 244 + private val eHelmEquip = 811 + + override fun defineListeners() { + + onEquip(Items.ELEMENTAL_SHIELD_2890) { player, _ -> + equipShield(player, Quests.ELEMENTAL_WORKSHOP_I, eShieldEquip) + } + + onEquip(Items.MIND_SHIELD_9731) { player, _ -> + equipShield(player, Quests.ELEMENTAL_WORKSHOP_II, mShieldEquip) + } + + onEquip(Items.ELEMENTAL_HELMET_9729) { player, _ -> + equipHelm(player, Quests.ELEMENTAL_WORKSHOP_II, eHelmEquip) + } + + onEquip(Items.MIND_HELMET_9733) { player, _ -> + equipHelm(player, Quests.ELEMENTAL_WORKSHOP_II, mHelmEquip) + } + } + + private fun equipShield(player: Player, quest: Quests, gfxId: Int): Boolean { + if (!isQuestComplete(player, quest)) { + sendMessage(player, "You need to have completed ${quest.questName} to equip this.") + return false + } + + // hide the shield while the gfx plays + player.appearance.isHideShield = true + player.updateAppearance() + visualize(player, -1, Graphics(gfxId, 95)) + + // unhide the shield + queueScript(player, 2, QueueStrength.SOFT) { + player.appearance.isHideShield = false + player.updateAppearance() + stopExecuting(player) + } + + return true + } + + private fun equipHelm(player: Player, quest: Quests, gfxId: Int): Boolean { + if (!isQuestComplete(player, quest)) { + sendMessage(player, "You need to have completed ${quest.questName} to equip this.") + return false + } + + // play the graphics + visualize(player, -1, gfxId) + return true + } +} \ No newline at end of file diff --git a/Server/src/main/core/game/node/entity/player/link/appearance/Appearance.java b/Server/src/main/core/game/node/entity/player/link/appearance/Appearance.java index e4c5ecfb4..439664122 100644 --- a/Server/src/main/core/game/node/entity/player/link/appearance/Appearance.java +++ b/Server/src/main/core/game/node/entity/player/link/appearance/Appearance.java @@ -64,6 +64,11 @@ public final class Appearance { */ private boolean ridingMinecart; + /** + * If the player's shield should be hidden (like when equipping elemental shields). + */ + private boolean hideShield; + /** * Constructs a new {@code Appearance} {@code Object}. * @param player the player of this appearance. @@ -279,7 +284,7 @@ public final class Appearance { } else { clearBodyPart(3); } - if (shield != null) { + if (shield != null && !hideShield) { drawItem(5, shield); } else { clearBodyPart(5); @@ -682,6 +687,22 @@ public final class Appearance { this.ridingMinecart = ridingMinecart; } + /** + * Gets the hideShield + * @return the hideShield + */ + public boolean isHideShield() { + return hideShield; + } + + /** + * Sets the hideShield + * @param hideShield the hideShield to set. + */ + public void setHideShield(boolean hideShield) { + this.hideShield = hideShield; + } + /** * Represents a class of cached appearance related information. * @author 'Vexia From 7d24bf1f8552ad05511cf636f15759e41a6620c5 Mon Sep 17 00:00:00 2001 From: aidan Date: Tue, 7 Jul 2026 09:46:44 -0500 Subject: [PATCH 32/45] json fixes --- Server/data/configs/item_configs.json | 2 +- Server/data/configs/object_configs.json | 38 ++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Server/data/configs/item_configs.json b/Server/data/configs/item_configs.json index 2dddd7791..c8589cdca 100644 --- a/Server/data/configs/item_configs.json +++ b/Server/data/configs/item_configs.json @@ -77821,7 +77821,7 @@ { "id": "9723", "name": "Pipe", - "examine": "A pipe that belongs in a cannon stand.", + "examine": "A spare section of pipe.", "archery_ticket_price": "0", "destroy": "true", "durability": null, diff --git a/Server/data/configs/object_configs.json b/Server/data/configs/object_configs.json index e4f2c26a3..5ab9f7575 100644 --- a/Server/data/configs/object_configs.json +++ b/Server/data/configs/object_configs.json @@ -7343,14 +7343,50 @@ "examine": "This lets in more light.", "ids": "1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863" }, + { + "examine": "An empty claw.", + "ids": "18623,18624,18625,18626" + }, + { + "examine": "A heavy claw.", + "ids": "18627,18628,18629,18630,18631,18632,18633,18634" + }, + { + "examine": "A broken claw.", + "ids": "18635,18636,18637,18638" + }, { "examine": "These obviously mean keep out!", "ids": "1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875" }, + { + "examine": "The inner workings for the press are in here.", + "ids": "18641" + }, + { + "examine": "A valve to start and stop the flow of water.", + "ids": "18646,18647" + }, + { + "examine": "I wonder what it does.", + "ids": "18663" + }, { "examine": "Fly Gnome Air.", "ids": "187" }, + { + "examine": "Seems to connect this level to the one above!", + "ids": "18705" + }, + { + "examine": "Is that a shopping trolley?", + "ids": "18746" + }, + { + "examine": "A magical wind tunnel.", + "ids": "18758,18759" + }, { "examine": "I shudder to think what has taken place here.", "ids": "1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,32289" @@ -10645,7 +10681,7 @@ }, { "examine": "This lever can be operated.", - "ids": "3417,3418,3419,3420,3421,3422,3423,3424,3425,3426,3427,3428" + "ids": "3417,3418,3419,3420,3421,3423,3424,3425,3426,3427,3428" }, { "examine": "Water powered machinery.", From d879416a85f553255a9b407a2ada2c59432f4c85 Mon Sep 17 00:00:00 2001 From: aidan Date: Tue, 7 Jul 2026 20:57:54 -0500 Subject: [PATCH 33/45] fuck these dynamic regions though --- .../quest/elementalworkshop/EWListeners.kt | 61 ++-- .../elementalworkshop2/EW2ActivityPlugin.kt | 78 +++++ .../quest/elementalworkshop2/EW2Listeners.kt | 306 ++++++++---------- .../seers/quest/elementalworkshop2/EW2Zone.kt | 63 +--- .../elementalworkshop2/ElementalWorkshop2.kt | 15 +- .../quest/elementalworkshop2/JigCartNPC.kt | 26 +- 6 files changed, 271 insertions(+), 278 deletions(-) create mode 100644 Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2ActivityPlugin.kt diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt index 40eacb72d..8cc3616e5 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt @@ -20,6 +20,7 @@ import content.data.Quests import content.global.handlers.iface.BookInterface import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2StageVarbit import core.game.dialogue.DialogueLabeller +import core.game.interaction.QueueStrength /** * Listeners for the Elemental Workshop I quest @@ -308,13 +309,14 @@ class EWListeners : InteractionListener { } // Successfully smith the elemental shield lock(player, (animationDuration(smithElementalShield) + 2)) - runTask(player) { + queueScript(player, 0, QueueStrength.SOFT) { _ -> face(player, workbench) animate(player, smithElementalShield) playAudio(player, smithElementalShieldSFX) replaceSlot(player, used.asItem().slot, elementalShield) rewardXP(player, Skills.SMITHING, 20.0) sendMessage(player, "Following the instructions in the book you make an elemental shield.") + return@queueScript stopExecuting(player) } } 3 -> { // ele claw @@ -335,13 +337,14 @@ class EWListeners : InteractionListener { } // Successfully smith the elemental shield lock(player, (animationDuration(smithElementalShield) + 2)) - runTask(player) { + queueScript(player, 0, QueueStrength.SOFT) { _ -> face(player, workbench) animate(player, smithElementalShield) playAudio(player, smithElementalShieldSFX) replaceSlot(player, used.asItem().slot, Item(Items.CRANE_CLAW_9720)) rewardXP(player, Skills.SMITHING, 20.0) sendMessage(player, "Following the instructions from the schematic you make an elemental claw.") + return@queueScript stopExecuting(player) } } 4 -> { // ele helm @@ -362,13 +365,14 @@ class EWListeners : InteractionListener { } // Successfully smith the elemental helm lock(player, (animationDuration(smithElementalShield) + 2)) - runTask(player) { + queueScript(player, 0, QueueStrength.SOFT) { _ -> face(player, workbench) animate(player, smithElementalShield) playAudio(player, smithElementalShieldSFX) replaceSlot(player, used.asItem().slot, Item(Items.ELEMENTAL_HELMET_9729)) rewardXP(player, Skills.SMITHING, 20.0) sendMessage(player, "Following the instructions in the book you make an elemental helm.") + return@queueScript stopExecuting(player) } } } @@ -398,13 +402,14 @@ class EWListeners : InteractionListener { } // Successfully smith the elemental shield lock(player, (animationDuration(smithElementalShield) + 2)) - runTask(player) { + queueScript(player, 0, QueueStrength.SOFT) { _ -> face(player, workbench) animate(player, smithElementalShield) playAudio(player, smithElementalShieldSFX) replaceSlot(player, used.asItem().slot, elementalShield) rewardXP(player, Skills.SMITHING, 20.0) sendMessage(player, "Following the instructions in the book you make an elemental shield.") + return@queueScript stopExecuting(player) } // Check to see if the quest is completed, if not, complete the quest if (!player.questRepository.getQuest(Quests.ELEMENTAL_WORKSHOP_I).isCompleted(player)) { @@ -433,24 +438,36 @@ class EWListeners : InteractionListener { } // Successfully smith the elemental helm and complete quest lock(player, (animationDuration(smithElementalShield) + 2)) - runTask(player) { - face(player, workbench) - animate(player, smithElementalShield) - playAudio(player, smithElementalShieldSFX) - replaceSlot(player, used.asItem().slot, Item(Items.MIND_HELMET_9733)) - rewardXP(player, Skills.SMITHING, 30.0) - sendMessage(player, "Following the instructions in the book you make an elemental mind helm.") + queueScript(player, 0, QueueStrength.SOFT) { count -> + when(count) { + // make the helm + 0 -> { + face(player, workbench) + animate(player, smithElementalShield) + playAudio(player, smithElementalShieldSFX) + replaceSlot(player, used.asItem().slot, Item(Items.MIND_HELMET_9733)) + rewardXP(player, Skills.SMITHING, 30.0) + sendMessage(player, "Following the instructions in the book you make an elemental mind helm.") + return@queueScript delayScript(player, smithElementalShield.duration) + } - if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 10) { - setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 11) - setVarbit(player, EW2StageVarbit, 11, true) - } - // complete quest if it's not done. - if (!isQuestComplete(player, Quests.ELEMENTAL_WORKSHOP_II)) { - finishQuest(player, Quests.ELEMENTAL_WORKSHOP_II) + // complete quest + 1 -> { + if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 10) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 11) + setVarbit(player, EW2StageVarbit, 11, true) + } + // complete quest if it's not done. + if (!isQuestComplete(player, Quests.ELEMENTAL_WORKSHOP_II)) { + finishQuest(player, Quests.ELEMENTAL_WORKSHOP_II) + } + return@queueScript delayScript(player, 1) + } } + return@queueScript stopExecuting(player) } - // choice to make helmet or shield + + // after quest, choice to make helmet or shield } else { sendDialogueOptions(player, "Would you like to make...", "An elemental mind shield.", "An elemental mind helm.") addDialogueAction(player) { _, buttonId -> @@ -473,13 +490,14 @@ class EWListeners : InteractionListener { } // Successfully smith the elemental mind shield lock(player, (animationDuration(smithElementalShield) + 2)) - runTask(player) { + queueScript(player, 0, QueueStrength.SOFT) { _ -> face(player, workbench) animate(player, smithElementalShield) playAudio(player, smithElementalShieldSFX) replaceSlot(player, used.asItem().slot, Item(Items.MIND_SHIELD_9731)) rewardXP(player, Skills.SMITHING, 30.0) sendMessage(player, "Following the instructions in the book you make an elemental mind shield.") + return@queueScript stopExecuting(player) } } 3 -> { // ele mind helm @@ -500,13 +518,14 @@ class EWListeners : InteractionListener { } // Successfully smith the elemental helm lock(player, (animationDuration(smithElementalShield) + 2)) - runTask(player) { + queueScript(player, 0, QueueStrength.SOFT) { _ -> face(player, workbench) animate(player, smithElementalShield) playAudio(player, smithElementalShieldSFX) replaceSlot(player, used.asItem().slot, Item(Items.MIND_HELMET_9733)) rewardXP(player, Skills.SMITHING, 30.0) sendMessage(player, "Following the instructions in the book you make an elemental mind helm.") + return@queueScript stopExecuting(player) } } } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2ActivityPlugin.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2ActivityPlugin.kt new file mode 100644 index 000000000..c20595485 --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2ActivityPlugin.kt @@ -0,0 +1,78 @@ +package content.region.kandarin.seers.quest.elementalworkshop2 + +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCart +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCrane +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncFan +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncSteamPress +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTank +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTankDoor +import core.api.* +import core.game.activity.ActivityPlugin +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.world.map.Location +import core.game.world.map.build.DynamicRegion +import core.plugin.Initializable + +/** + * Handles an instanced Elemental Workshop II session. + */ + +@Initializable +class EW2ActivityPlugin : ActivityPlugin { + + // blank constructor because ... + constructor() : super("EW2 Workshop", true, false, false) + + // real constructor + constructor(player: Player) : super("EW2 Workshop", true, false, false) { + this.player = player + } + + // more empty shit + override fun configure() { + } + + // starts the dynamic region + override fun start(player: Player, login: Boolean, vararg args: Any): Boolean { + player.properties.teleportLocation = base.transform(35, 35, 2) + return super.start(player, login, *args) + } + + // the exit location back in EW1 workshop + override fun getSpawnLocation(): Location = Location.create(2719, 9892, 0) + + // creates the region + override fun newInstance(p: Player): ActivityPlugin { + val plugin = EW2ActivityPlugin(p) + plugin.region = DynamicRegion.create(7760) + plugin.setRegionBase() + plugin.register(plugin.region.borders) + return plugin + } + + // sync the workshop state + override fun enter(e: Entity?): Boolean { + if (e is Player) { + + // need a slight delay or all the stuff doesn't load right + queueScript(player, 1) { + syncCrane(player) + syncCart(player) + syncSteamPress(player) + syncTank(player) + syncTankDoor(player) + syncFan(player) + return@queueScript stopExecuting(player) + } + } + + return super.enter(e) + } + + // this should shut down the dynamic region, I hope + override fun leave(e: Entity, logout: Boolean): Boolean { + unregisterRegion(region.id) + return super.leave(e, logout) + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 6dd04a408..be2c29859 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -25,24 +25,15 @@ import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2 import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.attrJigged import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.attrWatered import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.attrWinded -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.badHat -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.eHelmEquip -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.eShieldEquip -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.goodHat -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.mHelmEquip -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.mShieldEquip -import core.game.world.update.flag.context.Graphics import core.api.* -import core.game.container.impl.EquipmentContainer +import core.game.activity.ActivityPlugin import core.game.interaction.IntType import core.game.interaction.InteractionListener +import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills -import core.game.node.item.Item -import core.game.node.scenery.SceneryBuilder import core.game.world.map.Location -import core.game.world.map.RegionManager import org.rs09.consts.* class EW2Listeners : InteractionListener { @@ -52,8 +43,6 @@ class EW2Listeners : InteractionListener { //val ew2_ambient_loop = 3149 //val ew2_machine_loop = 3150 - // gfx 809 mind shield equip, 810 mind helmet equip - /** * Quest structure and testing notes: * @@ -81,6 +70,18 @@ class EW2Listeners : InteractionListener { companion object { + // graphics + const val goodHatGfx = Graphics.EXTRACTOR_HAT_CHAIR_807 + const val badHatGfx = Graphics.IMPROPERLY_EXTRACTOR_HAT_CHAIR_808 + + // animations + const val goodHatAnim = Animations.HUMAN_USE_EXTRACTOR_HAT_CHAIR_4884 + const val badHatAnim = Animations.HUMAN_IMPROPERLY_USE_EXTRACTOR_HAT_CHAIR_4885 + const val CRANE_LOWER = 4887 + const val CRANE_RAISE = 4888 + const val CRANE_PIVOT_TO_LAVA = 4889 + const val CRANE_PIVOT_TO_CART = 4890 + /** * This companion object stores helper functions to save and recall workshop state. * All the machinery states are stored in varbits and are updated in various listeners, @@ -157,32 +158,37 @@ class EW2Listeners : InteractionListener { fun syncCrane(player: Player) { val mat = getVarbit(player, EW2CraneStateVarbit) val pos = getVarbit(player, EW2CranePosVarbit) - val index = (mat * 4) + pos val newId = craneStates[index] - val craneLoc = Location.create(1952, 5143, 2) - val currentObject = getScenery(craneLoc) + val craneLoc = toInstance(player, Location.create(1952, 5143, 2)) + val currentObject = getScenery(craneLoc) ?: return - // check an object actually exists here - if (currentObject != null) { + if (!craneStates.contains(currentObject.id)) return + if (currentObject.id == newId) return - // check that the object is a crane - if (craneStates.contains(currentObject.id)) { + queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + animateScenery(player, currentObject, 4890) // replace with confirmed anim ID + return@queueScript delayScript(player, 4) // adjust to match anim duration + } - // replace crane if current state doesn't match what is called for - // todo it would be nice to animate this crane - if (currentObject.id != newId) { + 1 -> { replaceScenery(currentObject, newId, -1) playAudio(player, Sounds.EW2_CRANE_TURN_3170) - //playAudio(player, Sounds.EW2_CRANE_LOWER_3169) - - // if bar is hot set attr for quest log - if(newId == 18634) { - if(!getAttribute(player, attrDipped, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { + if (newId == Scenery.OLD_CRANE_18634) { + if (!getAttribute(player, attrDipped, false) && getQuestStage( + player, + Quests.ELEMENTAL_WORKSHOP_II + ) <= 6 + ) { setAttribute(player, attrDipped, true) } } + return@queueScript stopExecuting(player) } + + else -> return@queueScript stopExecuting(player) } } } @@ -192,12 +198,12 @@ class EW2Listeners : InteractionListener { val cartState = getVarbit(player, EW2JigCartVarbit) val cartPosIndex = getVarbit(player, EW2CartLeverVarbit) // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east val targetId = jigCartStates[cartState] - val targetLocation = jigCartLocations[cartPosIndex] + val targetLocation = toInstance(player, jigCartLocations[cartPosIndex]) val tankState = getVarbit(player, EW2TankDoorStateVarbit) // if tank state is 3, 4, 6, 7 the cart is inside the tank and should be removed. // find cart - val trackCenter = Location.create(1953, 5155, 2) - val existingCart = RegionManager.getLocalNPCs(trackCenter, 15).find { it.id in jigCartStates } + val trackCenter = toInstance(player, Location.create(1953, 5155, 2)) + val existingCart = findLocalNPCs(trackCenter, 15).find { it.id in jigCartStates } if (existingCart != null) { // update appearance if (existingCart.id != targetId) { @@ -214,8 +220,10 @@ class EW2Listeners : InteractionListener { // if there is no cart, spawn a new one (like if player is just logging in) } else { - val newCart = NPC(targetId, targetLocation) + // create the cart + val newCart = NPC.create(targetId, targetLocation) newCart.init() + // if cart is inside water tank, clear it if (tankState == 3 || tankState == 4 || tankState == 6 || tankState == 7) { newCart.clear() @@ -230,19 +238,17 @@ class EW2Listeners : InteractionListener { val largeCogs = intArrayOf(Scenery.COG_18675, Scenery.COG_18669, Scenery.COG_18672) // pin 1, pin 2, pin 3 /* - // updates the cog states (unused right now) + // updates the cog states (unused right now, could maybe set the anims here) fun syncCogs(player: Player) { } */ - /* - // visualizes the steam press. right now all it does is spawn the hammer part. no anims or fancy stuff. - // if you just do a simple scenerybuilder.add it'll overwrite the track, so i've disabled it for now. + // visualizes the steam press. right now all it does is spawn the hammer part. fun syncSteamPress(player: Player) { - SceneryBuilder.add(core.game.node.scenery.Scenery(steamPressStates[2], Location.create(1946, 5155, 2), 0)) + val pressLoc = toInstance(player, Location.create(1946, 5155, 2)) + addScenery(core.game.node.scenery.Scenery(steamPressStates[2], pressLoc, 0)) } - */ // syncs the water tank state. this is dependent on the valve states and the door state. also changes cart state (cooling bar) fun syncTank(player: Player) { @@ -253,12 +259,12 @@ class EW2Listeners : InteractionListener { if(waterIn == 0 && waterOut == 0) { // both valves closed, nothing happens return } else if(waterIn == 0 && waterOut == 1) { // drain is open, remove all added scenery - SceneryBuilder.remove(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, location(1951, 5164, 2))) // full tank - SceneryBuilder.remove(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18661, location(1951, 5164, 2))) // full tank + removeScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, toInstance(player, location(1951, 5164, 2)))) // full tank + removeScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18661, toInstance(player, location(1951, 5164, 2)))) // full tank playAudio(player, Sounds.EW2_WATER_RACK_3184) // drain? return } else if(waterIn == 1 && waterOut == 0) { // inlet is open, fill tank - SceneryBuilder.add(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18661, location(1951, 5164, 2))) // full tank + addScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18661, toInstance(player, location(1951, 5164, 2)))) // full tank playAudio(player, Sounds.EW2_WATER_FLOW_3183) // fill? if(cart == 3) { // if cart state is hot bar, transform to cool bar. setVarbit(player, EW2TankDoorStateVarbit, 6, true) @@ -271,7 +277,7 @@ class EW2Listeners : InteractionListener { } return } else if(waterIn == 1 && waterOut == 1) { // inlet and drain are open, tank is damp - SceneryBuilder.add(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, location(1951, 5164, 2))) // damp tank + addScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, toInstance(player, location(1951, 5164, 2)))) // damp tank playAudio(player, Sounds.EW2_WATER_FLOW_3183) // fill? return } @@ -280,15 +286,15 @@ class EW2Listeners : InteractionListener { // syncs the tank door state. this is dependent on the corkscrew lever and cart state fun syncTankDoor(player: Player) { val state = getVarbit(player, EW2TankDoorStateVarbit) - val doorLoc = Location.create(1953, 5162, 2) + val doorLoc = toInstance(player, Location.create(1953, 5162, 2)) val currentObject = getScenery(doorLoc) val targetId = tankDoorStates[state] // remove old scenery then add the new one - // todo right now this also deletes a piece of track that shares a tile with the door, but I don't really care. if(currentObject != null && currentObject.id != targetId) { - SceneryBuilder.remove(core.game.node.scenery.Scenery(currentObject.id, doorLoc, 1)) - SceneryBuilder.add(core.game.node.scenery.Scenery(targetId, doorLoc, 1)) + // I know what you're thinking, replaceScenery doesn't work in this situation. Why? idfk + removeScenery(core.game.node.scenery.Scenery(currentObject.id, doorLoc, 1)) + addScenery(core.game.node.scenery.Scenery(targetId, doorLoc, 1)) } } @@ -301,16 +307,26 @@ class EW2Listeners : InteractionListener { setVarbit(player, EW2FanStateVarbit, 0) } } + + // helper to convert static location coordinates to the dynamic instance location coordinates + fun toInstance(player: Player, staticLoc: Location): Location { + val activity = player.getAttribute("activity") + if (activity == null || activity.base == null) return staticLoc + + val offsetX = staticLoc.x - 1920 + val offsetY = staticLoc.y - 5120 + + return activity.base.transform(offsetX, offsetY, staticLoc.z) + } } override fun defineListeners() { - /**------------------------ - * SECTION 0 - START - ** ------------------------ - */ + /** ------------------------ * + * SECTION 0 - START + * ------------------------- */ - // Bookcase at digsite where you start quest. + // bookcase at digsite where you start quest on(Scenery.BOOKCASE_17382, IntType.SCENERY, "search") { player, _ -> if (isQuestComplete(player, Quests.ELEMENTAL_WORKSHOP_I) && !inInventory(player, Items.BEATEN_BOOK_9717) && @@ -351,7 +367,6 @@ class EW2Listeners : InteractionListener { } if(getVarbit(player, EW2MachineryVarbit) == 0) setVarbit(player, EW2MachineryVarbit, 1, true) - // todo the wiki notes that you must actually walk the path for the varbit to flip. Here i just flip it as soon as you read the scroll. return@on true } @@ -408,14 +423,14 @@ class EW2Listeners : InteractionListener { // stairs to lower level on(Scenery.STAIRWELL_18597, IntType.SCENERY, "climb-down") { player, _ -> - teleport(player, location(1948, 5158, 0)) + teleport(player, toInstance(player,location(1948, 5158, 0))) sendMessage(player, "You climb down the stairs.") return@on true } // stairs back up to EW2 on(Scenery.STAIRS_18599, IntType.SCENERY, "climb-up") { player, _ -> - teleport(player, location(1948, 5157, 2)) + teleport(player, toInstance(player,location(1948, 5157, 2))) sendMessage(player, "You climb up the stairs.") return@on true } @@ -459,9 +474,9 @@ class EW2Listeners : InteractionListener { val fanState = getVarbit(player, EW2FanLeverVarbit) // 1 if the fan is on - // find the cart - val trackCenter = Location.create(1953, 5155, 2) - val existingCart = RegionManager.getLocalNPCs(trackCenter, 15).find { it.id in jigCartStates } + // find the player's cart + val existingCart = findLocalNPCs(player, jigCartStates, 32).firstOrNull() + if (existingCart == null) { sendMessage(player, "CART MISSING. LOG IN AND OUT TO RESET IT.") return@on true @@ -508,7 +523,7 @@ class EW2Listeners : InteractionListener { // else nothing else -> arrayOf() - } + }.map { toInstance(player, it) }.toTypedArray() if (path.isNotEmpty()) { animate(player, Animations.HUMAN_PULL_LEVER_4909) @@ -543,26 +558,27 @@ class EW2Listeners : InteractionListener { } when(node.id) { - 4914 -> { + NPCs.JIG_CART_4914 -> { addItemOrDrop(player, Items.ELEMENTAL_METAL_2893) sendItemDialogue(player, Items.ELEMENTAL_METAL_2893, "You take the elemental metal.") animate(player, 537) setVarbit(player, EW2JigCartVarbit, 0, true) } - 4915 -> { + NPCs.JIG_CART_4915 -> { sendMessage(player, "The bar is too hot to touch.") } - 4916 -> { + NPCs.JIG_CART_4916 -> { sendMessage(player, "The bar is too hot to touch.") } - 4917 -> { + NPCs.JIG_CART_4917 -> { sendMessage(player, "You should finish the process before taking the bar.") } - 4918 -> { + NPCs.JIG_CART_4918 -> { addItemOrDrop(player, Items.PRIMED_BAR_9727) sendItemDialogue(player, Items.PRIMED_BAR_9727, "You take the primed bar.") - animate(player, 537) setVarbit(player, EW2JigCartVarbit, 0, true) + + // advance quest stage if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 7) setVarbit(player, EW2StageVarbit, 7, true) @@ -591,7 +607,6 @@ class EW2Listeners : InteractionListener { } setVarbit(player, EW2JigCartVarbit, 1, true) - animate(player, 537) playAudio(player, Sounds.EW2_PLACE_BAR_3177) syncCart(player) removeItem(player, used) @@ -601,10 +616,10 @@ class EW2Listeners : InteractionListener { return@onUseWith true } - /**------------------------ - * SECTION 1 - THE CRANE - ** ------------------------ - */ + /** ------------------------ * + * SECTION 1 - THE CRANE + * ------------------------- */ + // requires the elemental claw to be smithed and attached to the crane // crane schematic @@ -648,13 +663,11 @@ class EW2Listeners : InteractionListener { // pick up setVarbit(player, EW2CraneStateVarbit, cartState + 1) setVarbit(player, EW2JigCartVarbit, 0) - sendMessage(player, "The crane claw picks up the bar from the cart.") syncCart(player) } else if ((craneMat == 2 || craneMat == 3) && cartState == 0 && cartPos == 0) { // drop off setVarbit(player, EW2JigCartVarbit, craneMat - 1) setVarbit(player, EW2CraneStateVarbit, 1) - sendMessage(player, "You lower the bar onto the jig cart.") syncCart(player) } } @@ -691,10 +704,10 @@ class EW2Listeners : InteractionListener { return@on true } - /**------------------------ - * SECTION 2 - THE ANVIL - ** ------------------------ - */ + /** ------------------------ * + * SECTION 2 - THE ANVIL + * ------------------------- */ + // requires the junction box interface to be fixed // opens the junction box @@ -714,7 +727,7 @@ class EW2Listeners : InteractionListener { if (cartPos == 1) { when (cartState) { 0 -> { // empty. nothing happens. - sendDialogue(player, "You pull the lever and the press operates, but there is nothing on the cart to flatten.") + sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, stopping inches from the jig, then returns to its original position.") playAudio(player, Sounds.EW2_PRESS_BAR_3180) animate(player, Animations.HUMAN_PULL_LEVER_4909) } @@ -748,17 +761,17 @@ class EW2Listeners : InteractionListener { } } else { // The junction box is not configured correctly - sendDialogue(player, "You pull the lever but nothing happens.") + sendDialogue(player, "You pull the lever but nothing happens. Maybe the pipes are connected the wrong way round.") animate(player, Animations.HUMAN_PULL_LEVER_4909) } return@on true } - /**------------------------ - * SECTION 3 - WATER - ** ------------------------ - */ + /** ------------------------ * + * SECTION 3 - WATER + * ------------------------- */ + // requires the pipe to be replaced // pipe crate @@ -943,7 +956,7 @@ class EW2Listeners : InteractionListener { setVarbit(player, EW2ValveEastVarbit, 1, true) runTask(player, 3) { syncTank(player) - sendDialogue(player, "you open the valve") + sendDialogue(player, "You open the valve.") playAudio(player, Sounds.EW2_WATER_VALVE_3185) } // if open, close the valve @@ -951,19 +964,25 @@ class EW2Listeners : InteractionListener { setVarbit(player, EW2ValveEastVarbit, 0, true) runTask(player, 3) { syncTank(player) - sendDialogue(player, "you close the valve") + sendDialogue(player, "You close the valve.") playAudio(player, Sounds.EW2_WATER_VALVE_3185) } } return@on true } - /**------------------------ - * SECTION 4 - FAN - ** ------------------------ - */ + /** ------------------------ * + * SECTION 4 - FAN + * ------------------------- */ + // requires the cogs to be placed + // this crate authentically has its own special search message! + on(intArrayOf(Scenery.CRATE_18507, Scenery.CRATES_18508), IntType.SCENERY, "search") { player, _ -> + sendMessage(player, "You don't find anything interesting.") + return@on true + } + // small cog crate on(Scenery.CRATE_18618, IntType.SCENERY, "search") { player, _ -> if (!inInventory(player, Items.SMALL_COG_9726)) { @@ -1013,9 +1032,9 @@ class EW2Listeners : InteractionListener { // Pin 2 uses: 18667, 18668, 18669 // Pin 3 uses: 18670, 18671, 18672 when (node.id) { - 18673, 18674, 18675 -> setVarbit(player, EW2CogVarbit1, 0, true) - 18667, 18668, 18669 -> setVarbit(player, EW2CogVarbit2, 0, true) - 18670, 18671, 18672 -> setVarbit(player, EW2CogVarbit3, 0, true) + Scenery.COG_18673, Scenery.COG_18674, Scenery.COG_18675 -> setVarbit(player, EW2CogVarbit1, 0, true) + Scenery.COG_18667, Scenery.COG_18668, Scenery.COG_18669 -> setVarbit(player, EW2CogVarbit2, 0, true) + Scenery.COG_18670, Scenery.COG_18671, Scenery.COG_18672 -> setVarbit(player, EW2CogVarbit3, 0, true) } addItemOrDrop(player, cogToGive) @@ -1038,14 +1057,14 @@ class EW2Listeners : InteractionListener { } Items.MEDIUM_COG_9725 -> { setVarbit(player, EW2CogVarbit1, 2, true) - sendItemDialogue(player, item, "You put the medium cog onto the shaft.") + sendItemDialogue(player, item, "You put the medium-sized cog onto the shaft.") animate(player, 537) playAudio(player, Sounds.EW2_PLACE_COG_3178) removeItem(player, item) } Items.LARGE_COG_9724 -> { setVarbit(player, EW2CogVarbit1, 3, true) - sendItemDialogue(player, item, "You put the large cog onto the shaft.") + sendItemDialogue(player, item, "You put the big cog onto the shaft.") animate(player, 537) playAudio(player, Sounds.EW2_PLACE_COG_3178) removeItem(player, item) @@ -1106,7 +1125,7 @@ class EW2Listeners : InteractionListener { return@onUseWith true } - // fan lever. Authentically this check should allow the machine to start and the fan to either not start, start backwards, or start correctly, but I'm simplifying it for my sanity. + // fan lever. todo Authentically this check should allow the machine to start and the fan to either not start, start backwards, or start correctly, but I'm simplifying it for my sanity. on(Scenery.AN_OLD_LEVER_18663, IntType.SCENERY, "pull") { player, _ -> animate(player, Animations.HUMAN_PULL_LEVER_4909) @@ -1147,17 +1166,15 @@ class EW2Listeners : InteractionListener { return@on true } - /**------------------------ - * SECTION 5 - MIND IMBUE - ** ------------------------ - */ + /** ------------------------ * + * SECTION 5 - MIND IMBUE + * ------------------------- */ // use bar with extractor gun onUseWith(IntType.SCENERY, intArrayOf(Items.PRIMED_BAR_9727), Scenery.EXTRACTOR_GUN_18693) { player, item, _ -> if(removeItem(player, item)) { setVarbit(player, EW2ExtractorGun, 1, true) - animate(player, 537) - sendDialogue(player, "You place the primed bar under the device.") + sendItemDialogue(player, Items.PRIMED_BAR_9727, "You place the primed bar under the device.") if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 8) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 9) setVarbit(player, EW2StageVarbit, 9, true) @@ -1169,18 +1186,16 @@ class EW2Listeners : InteractionListener { // take primed bar on(intArrayOf(Scenery.EXTRACTOR_GUN_18694), IntType.SCENERY, "take-from") { player, _ -> addItemOrDrop(player, Items.PRIMED_BAR_9727) - animate(player, 537) setVarbit(player, EW2ExtractorGun, 0, true) - sendDialogue(player, "You take the primed bar.") + sendItemDialogue(player, Items.PRIMED_BAR_9727, "You take the primed bar.") return@on true } // take mind bar on(intArrayOf(Scenery.EXTRACTOR_GUN_18695), IntType.SCENERY, "take-from") { player, _ -> addItemOrDrop(player, Items.ELEMENTAL_MIND_BAR_9728) - animate(player, 537) setVarbit(player, EW2ExtractorGun, 0, true) - sendDialogue(player, "You take the elemental mind bar.") + sendItemDialogue(player, Items.ELEMENTAL_MIND_BAR_9728, "You take the elemental mind bar.") return@on true } @@ -1198,12 +1213,12 @@ class EW2Listeners : InteractionListener { runTask(player) { forceMove( player, - Location.create(1961, 5150, 0), - Location.create(1962, 5150, 0), + toInstance(player, Location.create(1961, 5150, 0)), + toInstance(player, Location.create(1962, 5150, 0)), 0, 0 ) - visualize(player, Animations.HUMAN_USE_EXTRACTOR_HAT_CHAIR_4884, goodHat) + visualize(player, goodHatAnim, goodHatGfx) playAudio(player, Sounds.EW2_EXTRACTOR_OPERATION_3166) sendGraphics(809, Location.create(1962, 5148, 0)) setVarbit(player, EW2ExtractorGun, 2, true) @@ -1216,8 +1231,8 @@ class EW2Listeners : InteractionListener { } forceMove( player, - Location.create(1962, 5150, 0), - Location.create(1961, 5150, 0), + toInstance(player,Location.create(1962, 5150, 0)), + toInstance(player,Location.create(1961, 5150, 0)), 200, 2 ) @@ -1226,89 +1241,44 @@ class EW2Listeners : InteractionListener { runTask(player) { // 50 client cycles = 1 second forceMove( player, - Location.create(1961, 5150, 0), - Location.create(1962, 5150, 0), + toInstance(player,Location.create(1961, 5150, 0)), + toInstance(player,Location.create(1962, 5150, 0)), 0, 0 ) - visualize(player, Animations.HUMAN_IMPROPERLY_USE_EXTRACTOR_HAT_CHAIR_4885, badHat) + visualize(player, badHatAnim, badHatGfx) playAudio(player, Sounds.EW2_EXTRACTOR_ZAP_3173) impact(player, 4) sendMessage(player, "The extractor hat shocks you!.") } forceMove( player, - Location.create(1962, 5150, 0), - Location.create(1961, 5150, 0), + toInstance(player,Location.create(1962, 5150, 0)), + toInstance(player,Location.create(1961, 5150, 0)), 350, 2 ) } return@on true } - - /**------------------------ - * EXTRA - ** ------------------------ - */ - - // equip graphics for helmets and shields. the shields should not technically show until the graphic finishes, but I can't find an easy way to do that. - onEquip(Items.MIND_HELMET_9733) { player, _ -> - visualize(player, -1, mHelmEquip) - return@onEquip true - } - - onEquip(Items.MIND_SHIELD_9731) { player, node -> - val currentShield = player.equipment[EquipmentContainer.SLOT_SHIELD] - if (currentShield != null) { - player.equipment.remove(currentShield) - } - visualize(player, -1, Graphics(mShieldEquip,95)) - runTask(player, 2) { - val item = node.asItem() - player.equipment.add(item, item.slot, true, true) - player.inventory.add(currentShield) - } - return@onEquip false - } - - onEquip(Items.ELEMENTAL_HELMET_9729) { player, _ -> - visualize(player, -1, eHelmEquip) - return@onEquip true - } - - onEquip(Items.ELEMENTAL_SHIELD_2890) { player, node -> - val currentShield = player.equipment[EquipmentContainer.SLOT_SHIELD] - if (currentShield != null) { - player.equipment.remove(currentShield) - } - visualize(player, -1, Graphics(eShieldEquip,95)) - runTask(player, 2) { - val item = node.asItem() - player.equipment.add(item, item.slot, true, true) - player.inventory.add(currentShield) - } - return@onEquip false - } - } // fixes to get right interact locations override fun defineDestinationOverrides() { - setDest(IntType.SCENERY, intArrayOf(Scenery.OLD_CRANE_18636), "use") { _, _ -> - return@setDest Location.create(1954, 5148, 2) + setDest(IntType.SCENERY, intArrayOf(Scenery.OLD_CRANE_18636), "use") { entity, _ -> + return@setDest toInstance(entity as Player,Location.create(1954, 5148, 2)) } - setDest(IntType.NPC, intArrayOf(NPCs.JIG_CART_4913), "use") { _, _ -> - return@setDest Location.create(1954, 5148, 2) + setDest(IntType.NPC, intArrayOf(NPCs.JIG_CART_4913), "use") { entity, _ -> + return@setDest toInstance(entity as Player,Location.create(1954, 5148, 2)) } - setDest(IntType.NPC, intArrayOf(NPCs.JIG_CART_4914, NPCs.JIG_CART_4915, NPCs.JIG_CART_4916, NPCs.JIG_CART_4917, NPCs.JIG_CART_4918), "take-from") { _, _ -> - return@setDest Location.create(1954, 5148, 2) + setDest(IntType.NPC, intArrayOf(NPCs.JIG_CART_4914, NPCs.JIG_CART_4915, NPCs.JIG_CART_4916, NPCs.JIG_CART_4917, NPCs.JIG_CART_4918), "take-from") { entity, _ -> + return@setDest toInstance(entity as Player,Location.create(1954, 5148, 2)) } - setDest(IntType.SCENERY, intArrayOf(Scenery.PIPING_18650), "use") { _, _ -> - return@setDest Location.create(1953, 5167, 3) + setDest(IntType.SCENERY, intArrayOf(Scenery.PIPING_18650), "use") { entity, _ -> + return@setDest toInstance(entity as Player,Location.create(1953, 5167, 3)) } // the double stacked pins and the cogs on them. @@ -1316,8 +1286,8 @@ class EW2Listeners : InteractionListener { Scenery.PIN_18664, Scenery.PIN_18665, Scenery.COG_18673, Scenery.COG_18674, Scenery.COG_18675, Scenery.COG_18667, Scenery.COG_18668, Scenery.COG_18669 - ), "use","take") { _, _ -> - return@setDest Location.create(1958, 5157, 2) + ), "use","take") { entity, _ -> + return@setDest toInstance(entity as Player,Location.create(1958, 5157, 2)) } } } \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt index fd3dae6cf..349bd3b43 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt @@ -1,82 +1,33 @@ package content.region.kandarin.seers.quest.elementalworkshop2 import content.data.Quests -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCart -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCrane -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncFan -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncSteamPress -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTank -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTankDoor -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.toInstance import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2StageVarbit import core.api.* -import core.game.interaction.QueueStrength +import core.game.activity.ActivityManager import core.game.node.entity.Entity import core.game.node.entity.player.Player -import core.game.world.map.Location -import core.game.world.map.build.DynamicRegion import core.game.world.map.zone.ZoneBorders -import org.rs09.consts.Scenery /** - * This updates the Elemental Workshop II state whenever the player enters, - * keeping the equipment state synced to the saved varbits. The workshop - * area is a dynamic region, per OSRS wiki. + * Handles an instanced Elemental Workshop II session. */ class EW2Zone : MapArea { - override fun defineAreaBorders(): Array { return arrayOf(ZoneBorders(1920,5120,1983,5183)) } override fun areaEnter(entity: Entity) { - if(entity is Player) { + if (entity is Player) { - val baseRegion = 7760 + // start the instanced workshop region + ActivityManager.start(entity, "EW2 Workshop", false) - // create the dynamic region - val dynamicRegion = DynamicRegion.create(baseRegion) - - // save the exit location back to the real world surface - setAttribute(entity, "ew2_exit_location", Location.create(2713, 3484, 0)) - - // find the player's offset from the base coordinates - val offsetX = entity.location.x - ((baseRegion shr 8) shl 6) - val offsetY = entity.location.y - ((baseRegion and 0xFF) shl 6) - - // apply offset to the dynamic region base - val destX = dynamicRegion.borders.southWestX + offsetX - val destY = dynamicRegion.borders.southWestY + offsetY - - // teleport the player into the instance - teleport(entity, Location.create(destX, destY, entity.location.z)) - - // save the dynamic region coords to the player to reference later - setAttribute(entity, ElementalWorkshop2.coordsX, dynamicRegion.borders.southWestX) - setAttribute(entity, ElementalWorkshop2.coordsY, dynamicRegion.borders.southWestY) - - // advance quest - if(getQuestStage(entity, Quests.ELEMENTAL_WORKSHOP_II) <= 5) { + // advance quest stage + if (getQuestStage(entity, Quests.ELEMENTAL_WORKSHOP_II) <= 5) { setQuestStage(entity, Quests.ELEMENTAL_WORKSHOP_II, 6) setVarbit(entity, EW2StageVarbit, 6, true) } - - // sync the workshop state - queueScript(entity, 2, QueueStrength.SOFT) { _ -> - - // add this door because I can't interact without parlour chairs todo test removing this on test - // toInstance(entity, Location.create(1953, 5162, 2)) - addScenery(core.game.node.scenery.Scenery(Scenery.DOOR_18651, location(33,42,2), 1)) - - syncCrane(entity) - syncCart(entity) - syncSteamPress(entity) - syncTank(entity) - syncTankDoor(entity) - syncFan(entity) - return@queueScript stopExecuting(entity) - } } } } \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt index f5dc32f0a..35c0fc0cb 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt @@ -6,7 +6,6 @@ import core.game.node.entity.player.Player import core.game.node.entity.player.link.quest.Quest import core.game.node.entity.skill.Skills import core.plugin.Initializable -import org.rs09.consts.Graphics import org.rs09.consts.Items /** @@ -57,14 +56,6 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Sta const val attrFlattened = "/save:quest:ew2-flattened" //true after you flatten the hot bar const val attrWatered = "/save:quest:ew2-watered" //true after you cool the bar in the tank const val attrWinded = "/save:quest:ew2-winded" //true after you cool the bar in the wind tunnel - - // defining these graphics here because the graphics consts conflicts with the graphics object in the listeners - const val goodHat = Graphics.EXTRACTOR_HAT_CHAIR_807 - const val badHat = Graphics.IMPROPERLY_EXTRACTOR_HAT_CHAIR_808 - const val mShieldEquip = 809 - const val mHelmEquip = 810 - const val eShieldEquip = 244 - const val eHelmEquip = 811 } /** @@ -273,12 +264,12 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Sta override fun finish(player: Player) { var ln = 10 super.finish(player) - player.packetDispatch.sendString("You have completed the Elemental Workshop II quest!", 277, 4) + player.packetDispatch.sendString("You have completed the Elemental Workshop II Quest!", 277, 4) player.packetDispatch.sendItemZoomOnInterface(Items.MIND_HELMET_9733, 230, 277, 5) drawReward(player, "1 Quest Point", ln++) - drawReward(player, "7,500 Smithing XP,", ln++) - drawReward(player, "7,500 Crafting XP,", ln++) + drawReward(player, "7500 Smithing XP,", ln++) + drawReward(player, "7500 Crafting XP,", ln++) drawReward(player, "Ability to make and equip", ln++) drawReward(player, "elemental mind equipment", ln) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt index d83a97a3b..22c3bd1cc 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt @@ -1,43 +1,27 @@ package content.region.kandarin.seers.quest.elementalworkshop2 -import core.game.node.entity.npc.AbstractNPC +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.jigCartStates import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPCBehavior -import core.game.world.map.Location -import core.game.world.map.RegionManager -import core.game.world.map.build.ChunkFlags import core.game.world.map.path.ClipMaskSupplier import org.rs09.consts.NPCs /** * Jig cart in Elemental Workshop II - * The only reason I have this here is to set a custom clipping mask to allow the cart to walk over the track scenery. - * Right now the walking part is broken, so the cart just teleports. */ -class JigCartNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { - - override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { - return JigCartNPC(id, location) - } - - override fun getIds(): IntArray { - return intArrayOf(NPCs.JIG_CART_4913, NPCs.JIG_CART_4914, NPCs.JIG_CART_4915, NPCs.JIG_CART_4916, NPCs.JIG_CART_4917, NPCs.JIG_CART_4918,) - } -} - -class JigCartNPCBehavior : NPCBehavior(NPCs.JIG_CART_4913, NPCs.JIG_CART_4914, NPCs.JIG_CART_4915, NPCs.JIG_CART_4916, NPCs.JIG_CART_4917, NPCs.JIG_CART_4918,) { +// the only reason I have this here is to set a custom clipping mask to allow the cart to walk over the track scenery. +// right now the walking part is broken, so the cart just teleports. +class JigCartNPCBehavior : NPCBehavior(*jigCartStates) { override fun getClippingSupplier (self: NPC) : ClipMaskSupplier { return CartClipper } } - // this clip mask lets the jig cart walk through scenery object CartClipper : ClipMaskSupplier { override fun getClippingFlag (z: Int, x: Int, y: Int) : Int { - val flag = RegionManager.getClippingFlag(z, x, y) - return flag and (ChunkFlags.SOLID_TILE.inv()) and (ChunkFlags.TILE_OBJECT.inv()) and (ChunkFlags.OBJ_10.inv()) + return 0 } } \ No newline at end of file From ce85d62e396e4cb86a4786d5297b0987273e9515 Mon Sep 17 00:00:00 2001 From: aidan Date: Thu, 9 Jul 2026 10:56:30 -0500 Subject: [PATCH 34/45] convert plugin dynamic region to not a plugin --- .../elementalworkshop2/EW2ActivityPlugin.kt | 78 ------------------- .../quest/elementalworkshop2/EW2Session.kt | 70 +++++++++++++++++ .../seers/quest/elementalworkshop2/EW2Zone.kt | 33 -------- 3 files changed, 70 insertions(+), 111 deletions(-) delete mode 100644 Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2ActivityPlugin.kt create mode 100644 Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt delete mode 100644 Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2ActivityPlugin.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2ActivityPlugin.kt deleted file mode 100644 index c20595485..000000000 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2ActivityPlugin.kt +++ /dev/null @@ -1,78 +0,0 @@ -package content.region.kandarin.seers.quest.elementalworkshop2 - -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCart -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCrane -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncFan -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncSteamPress -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTank -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTankDoor -import core.api.* -import core.game.activity.ActivityPlugin -import core.game.node.entity.Entity -import core.game.node.entity.player.Player -import core.game.world.map.Location -import core.game.world.map.build.DynamicRegion -import core.plugin.Initializable - -/** - * Handles an instanced Elemental Workshop II session. - */ - -@Initializable -class EW2ActivityPlugin : ActivityPlugin { - - // blank constructor because ... - constructor() : super("EW2 Workshop", true, false, false) - - // real constructor - constructor(player: Player) : super("EW2 Workshop", true, false, false) { - this.player = player - } - - // more empty shit - override fun configure() { - } - - // starts the dynamic region - override fun start(player: Player, login: Boolean, vararg args: Any): Boolean { - player.properties.teleportLocation = base.transform(35, 35, 2) - return super.start(player, login, *args) - } - - // the exit location back in EW1 workshop - override fun getSpawnLocation(): Location = Location.create(2719, 9892, 0) - - // creates the region - override fun newInstance(p: Player): ActivityPlugin { - val plugin = EW2ActivityPlugin(p) - plugin.region = DynamicRegion.create(7760) - plugin.setRegionBase() - plugin.register(plugin.region.borders) - return plugin - } - - // sync the workshop state - override fun enter(e: Entity?): Boolean { - if (e is Player) { - - // need a slight delay or all the stuff doesn't load right - queueScript(player, 1) { - syncCrane(player) - syncCart(player) - syncSteamPress(player) - syncTank(player) - syncTankDoor(player) - syncFan(player) - return@queueScript stopExecuting(player) - } - } - - return super.enter(e) - } - - // this should shut down the dynamic region, I hope - override fun leave(e: Entity, logout: Boolean): Boolean { - unregisterRegion(region.id) - return super.leave(e, logout) - } -} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt new file mode 100644 index 000000000..c5fbc81ca --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt @@ -0,0 +1,70 @@ +package content.region.kandarin.seers.quest.elementalworkshop2 + +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCart +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCrane +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncFan +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncSteamPress +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTank +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTankDoor +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.world.map.Location +import core.game.world.map.build.DynamicRegion +import core.game.world.map.zone.ZoneBorders + +/** + * Handles an instanced Elemental Workshop II session. + */ + +class EW2Session(val player: Player? = null) : MapArea { + + lateinit var region: DynamicRegion + lateinit var base: Location + + // borders will be registered dynamically in start() + override fun defineAreaBorders(): Array { + return emptyArray() + } + + // start() is triggered by the listener for Scenery.HATCH_18596 + fun start() { + val p = player ?: return + + region = DynamicRegion.create(7760) + base = region.baseLocation + + zone.register(getRegionBorders(region.id)) + setAttribute(p, "ew2-session", this) + teleport(p, base.transform(35, 35, 2)) + + // logout listener returns player to the EW1 workshop + registerLogoutListener(p, "ew2-logout") { + val session = getAttribute(p, "ew2-session", null) ?: return@registerLogoutListener + p.location = Location.create(2719, 9892, 0) + p.locks.unlockTeleport() + session.cleanup(p) + } + + // sync the workshop state + syncCrane(p) + syncCart(p) + syncSteamPress(p) + syncTank(p) + syncTankDoor(p) + syncFan(p) + } + + // clean up the instanced area + override fun areaLeave(entity: Entity, logout: Boolean) { + if (entity is Player && entity == player && !logout) { + cleanup(entity) + } + } + + fun cleanup(p: Player) { + zone.unregister(getRegionBorders(region.id)) + clearLogoutListener(p, "ew2-logout") + removeAttribute(p, "ew2-session") + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt deleted file mode 100644 index 349bd3b43..000000000 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Zone.kt +++ /dev/null @@ -1,33 +0,0 @@ -package content.region.kandarin.seers.quest.elementalworkshop2 - -import content.data.Quests -import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2StageVarbit -import core.api.* -import core.game.activity.ActivityManager -import core.game.node.entity.Entity -import core.game.node.entity.player.Player -import core.game.world.map.zone.ZoneBorders - -/** - * Handles an instanced Elemental Workshop II session. - */ - -class EW2Zone : MapArea { - override fun defineAreaBorders(): Array { - return arrayOf(ZoneBorders(1920,5120,1983,5183)) - } - - override fun areaEnter(entity: Entity) { - if (entity is Player) { - - // start the instanced workshop region - ActivityManager.start(entity, "EW2 Workshop", false) - - // advance quest stage - if (getQuestStage(entity, Quests.ELEMENTAL_WORKSHOP_II) <= 5) { - setQuestStage(entity, Quests.ELEMENTAL_WORKSHOP_II, 6) - setVarbit(entity, EW2StageVarbit, 6, true) - } - } - } -} \ No newline at end of file From e2baea400d0654f72a2d6b410b7df98475e133c3 Mon Sep 17 00:00:00 2001 From: aidan Date: Thu, 9 Jul 2026 14:14:40 -0500 Subject: [PATCH 35/45] these animations are breaking my mind --- .../quest/elementalworkshop2/EW2Listeners.kt | 398 ++++++++++++++---- .../quest/elementalworkshop2/EW2Session.kt | 2 + .../JunctionBoxInterface.kt | 22 +- .../system/command/sets/SpawnCommandSet.kt | 52 +++ 4 files changed, 375 insertions(+), 99 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index be2c29859..7a832974c 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -26,7 +26,6 @@ import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2 import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.attrWatered import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.attrWinded import core.api.* -import core.game.activity.ActivityPlugin import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.interaction.QueueStrength @@ -34,14 +33,14 @@ import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills import core.game.world.map.Location +import core.game.world.update.flag.context.Animation import org.rs09.consts.* class EW2Listeners : InteractionListener { - // values from 530_synth_debug_names.txt that are still unused - //val ew2_dial_spin = 2326 - //val ew2_ambient_loop = 3149 - //val ew2_machine_loop = 3150 + // Sounds I still haven't used but should be part of quest somewhere + // Sounds.EW2_MACHINE_LOOP_3150 + // Sounds.EW2_AMBIENT_LOOP_3149 /** * Quest structure and testing notes: @@ -71,22 +70,46 @@ class EW2Listeners : InteractionListener { companion object { // graphics - const val goodHatGfx = Graphics.EXTRACTOR_HAT_CHAIR_807 - const val badHatGfx = Graphics.IMPROPERLY_EXTRACTOR_HAT_CHAIR_808 + const val EXTRACTOR_GOOD_GFX = Graphics.EXTRACTOR_HAT_CHAIR_807 + const val EXTRACTOR_BAD_GFX = Graphics.IMPROPERLY_EXTRACTOR_HAT_CHAIR_808 // animations - const val goodHatAnim = Animations.HUMAN_USE_EXTRACTOR_HAT_CHAIR_4884 - const val badHatAnim = Animations.HUMAN_IMPROPERLY_USE_EXTRACTOR_HAT_CHAIR_4885 - const val CRANE_LOWER = 4887 - const val CRANE_RAISE = 4888 - const val CRANE_PIVOT_TO_LAVA = 4889 - const val CRANE_PIVOT_TO_CART = 4890 + const val DOOR_OPEN = 4871 // 4875 is another open + const val DOOR_CLOSE = 4872 // 4876 is another open + const val DOOR_EXTEND = 4873 // 4877 is another extend + const val DOOR_RETRACT = 4874 // 4878 is another retract + // 4879 + // 4880 + // 4881 + // 4882 + // 4883 + const val EXTRACTOR_GOOD = Animations.HUMAN_USE_EXTRACTOR_HAT_CHAIR_4884 + const val EXTRACTOR_BAD = Animations.HUMAN_IMPROPERLY_USE_EXTRACTOR_HAT_CHAIR_4885 + // 4886 + const val CRANE_LOWER_CART = 4887 + const val CRANE_RAISE_CART = 4888 + const val CRANE_LOWER_LAVA = 4889 + const val CRANE_RAISE_LAVA = 4890 + // 4891 (another pivot anim, unused) + const val CRANE_PIVOT = 4892 + // 4893 + // 4894 + // 4895 + const val COG_REV = 4896 + // 4897 + const val FAN_SPIN = 4898 + // 4899 + // 4900 + // 4901 + const val TANK_FILL = 4902 + const val TANK_DRAIN = 4903 + const val COG_FWD = 4906 /** * This companion object stores helper functions to save and recall workshop state. * All the machinery states are stored in varbits and are updated in various listeners, * then synced in the various sync() functions. The whole workshop is synced using the - * EW2Zone enter trigger, so when the player comes back, the workshop is where they left it. + * EW2Session start trigger, so when the player comes back, the workshop is where they left it. */ // various states the crane can be in. @@ -157,31 +180,79 @@ class EW2Listeners : InteractionListener { // position the crane based on varbit states fun syncCrane(player: Player) { val mat = getVarbit(player, EW2CraneStateVarbit) - val pos = getVarbit(player, EW2CranePosVarbit) - val index = (mat * 4) + pos - val newId = craneStates[index] + val pos = getVarbit(player, EW2CranePosVarbit) // 0: South Down, 1: South Up, 2: North Down, 3: North Up + val targetIndex = (mat * 4) + pos + val newId = craneStates[targetIndex] + val craneLoc = toInstance(player, Location.create(1952, 5143, 2)) val currentObject = getScenery(craneLoc) ?: return if (!craneStates.contains(currentObject.id)) return if (currentObject.id == newId) return + // get the crane's current position + val currentIndex = craneStates.indexOf(currentObject.id) + val currentPos = if (currentIndex != -1) currentIndex % 4 else pos + + // select the anim and sound + var animId = -1 + var soundId = -1 + if (currentPos != pos) { + when { + // lowering into lava + currentPos == 1 && pos == 0 -> { + animId = CRANE_LOWER_LAVA + soundId = Sounds.EW2_CRANE_LOWER_3169 + } + // raising from lava + currentPos == 0 && pos == 1 -> { + animId = CRANE_RAISE_LAVA + soundId = Sounds.EW2_CRANE_LOWER_3169 // I can't find raise sound + } + // lowering to cart + currentPos == 3 && pos == 2 -> { + animId = CRANE_LOWER_CART + soundId = Sounds.EW2_CRANE_LOWER_3169 + } + // raising from cart + currentPos == 2 && pos == 3 -> { + animId = CRANE_RAISE_CART + soundId = Sounds.EW2_CRANE_LOWER_3169 + } + // pivot to cart + currentPos == 1 && pos == 3 -> { + animId = CRANE_PIVOT + soundId = Sounds.EW2_CRANE_TURN_3170 + } + // pivot to lava + currentPos == 3 && pos == 1 -> { + animId = CRANE_PIVOT + soundId = Sounds.EW2_CRANE_TURN_3170 + } + } + } + queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> when (stage) { + // animate the scenery 0 -> { - animateScenery(player, currentObject, 4890) // replace with confirmed anim ID - return@queueScript delayScript(player, 4) // adjust to match anim duration + if (animId != -1) { + val anim = Animation(animId) + animateScenery(player, currentObject, animId) + playAudio(player, soundId) + return@queueScript delayScript(player, anim.duration) + } else return@queueScript delayScript(player, 1) } + // update the scenery 1 -> { replaceScenery(currentObject, newId, -1) - playAudio(player, Sounds.EW2_CRANE_TURN_3170) + syncCart(player) + + // advance quest stage if (newId == Scenery.OLD_CRANE_18634) { - if (!getAttribute(player, attrDipped, false) && getQuestStage( - player, - Quests.ELEMENTAL_WORKSHOP_II - ) <= 6 - ) { + if (!getAttribute(player, attrDipped, false) && + getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { setAttribute(player, attrDipped, true) } } @@ -191,6 +262,7 @@ class EW2Listeners : InteractionListener { else -> return@queueScript stopExecuting(player) } } + syncCart(player) } // update the jig cart based on varbit status @@ -211,7 +283,7 @@ class EW2Listeners : InteractionListener { } // failsafe to reset the cart where it's supposed to be. right now cart just teleports so this shouldn't trigger if (!existingCart.walkingQueue.isMoving && existingCart.location != targetLocation) { - existingCart.teleport(targetLocation) + teleport(existingCart, targetLocation) } // if cart is inside water tank, clear it if (tankState == 3 || tankState == 4 || tankState == 6 || tankState == 7) { @@ -237,12 +309,24 @@ class EW2Listeners : InteractionListener { val medCogs = intArrayOf(Scenery.COG_18674, Scenery.COG_18668, Scenery.COG_18671) // pin 1, pin 2, pin 3 val largeCogs = intArrayOf(Scenery.COG_18675, Scenery.COG_18669, Scenery.COG_18672) // pin 1, pin 2, pin 3 - /* - // updates the cog states (unused right now, could maybe set the anims here) + // spins the cogs fun syncCogs(player: Player) { + val lever = getVarbit(player, EW2FanLeverVarbit) + val cogs1 = getScenery(toInstance(player,Location.create(1959, 5157, 2))) + val cogs2 = getScenery(toInstance(player,Location.create(1959, 5156, 2))) + if (cogs1 != null && cogs2 != null) { + if (lever == 1) { + // spin cogs + animateScenery(cogs1, COG_FWD) + animateScenery(cogs2, COG_FWD) + } else { + // stop cogs + animateScenery(cogs1, -1) + animateScenery(cogs2, -1) + } + } } - */ // visualizes the steam press. right now all it does is spawn the hammer part. fun syncSteamPress(player: Player) { @@ -255,31 +339,102 @@ class EW2Listeners : InteractionListener { val waterIn = getVarbit(player, EW2ValveWestVarbit) // 0 if valve closed, 1 if open val waterOut = getVarbit(player, EW2ValveEastVarbit) // 0 if valve closed, 1 if open val cart = getVarbit(player, EW2TankDoorStateVarbit) // whether the cart is in the tank or not + val tankLoc = toInstance(player, location(1951, 5164, 2)) + val currentObject = getScenery(tankLoc) - if(waterIn == 0 && waterOut == 0) { // both valves closed, nothing happens + if (waterIn == 0 && waterOut == 0) { + // both valves closed, nothing happens return - } else if(waterIn == 0 && waterOut == 1) { // drain is open, remove all added scenery - removeScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, toInstance(player, location(1951, 5164, 2)))) // full tank - removeScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18661, toInstance(player, location(1951, 5164, 2)))) // full tank - playAudio(player, Sounds.EW2_WATER_RACK_3184) // drain? - return - } else if(waterIn == 1 && waterOut == 0) { // inlet is open, fill tank - addScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18661, toInstance(player, location(1951, 5164, 2)))) // full tank - playAudio(player, Sounds.EW2_WATER_FLOW_3183) // fill? - if(cart == 3) { // if cart state is hot bar, transform to cool bar. - setVarbit(player, EW2TankDoorStateVarbit, 6, true) - setVarbit(player, EW2JigCartVarbit, 4, true) - syncTankDoor(player) - playAudio(player, Sounds.EW2_COOL_BAR_3168) - if(!getAttribute(player, attrWatered, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { - setAttribute(player, attrWatered, true) + + } else if (waterIn == 0 && waterOut == 1) { + // drain is open, empty the tank + if (currentObject != null && currentObject.id == Scenery.WATER_TANK_18661) { + // tank is full: play drain anim, then remove + queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + animateScenery(player, currentObject, TANK_DRAIN) + playAudio(player, Sounds.EW2_WATER_RACK_3184) + return@queueScript delayScript(player, Animation(TANK_DRAIN).duration) + } + 1 -> { + removeScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18661, tankLoc)) + return@queueScript stopExecuting(player) + } + else -> return@queueScript stopExecuting(player) + } + } + } else if (currentObject != null && currentObject.id == Scenery.WATER_TANK_18660) { + // tank is damp: just remove the scenery + removeScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, tankLoc)) + playAudio(player, Sounds.EW2_WATER_RACK_3184) + } + + } else if (waterIn == 1 && waterOut == 0) { + // inlet is open, fill tank + if (currentObject?.id == Scenery.WATER_TANK_18661) return // already full + + queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + // add damp tank so we have something to animate + var animTarget = currentObject + if (animTarget == null || animTarget.id != Scenery.WATER_TANK_18660) { + animTarget = core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, tankLoc) + addScenery(animTarget) + } + + animateScenery(player, animTarget, TANK_FILL) + playAudio(player, Sounds.EW2_WATER_FLOW_3183) + return@queueScript delayScript(player, Animation(TANK_FILL).duration) + } + 1 -> { + // swap damp for full tank + val existing = getScenery(tankLoc) + if (existing != null) removeScenery(core.game.node.scenery.Scenery(existing.id, tankLoc)) + addScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18661, tankLoc)) + + // cooling the hot bar + if (cart == 3) { + setVarbit(player, EW2TankDoorStateVarbit, 6, true) + setVarbit(player, EW2JigCartVarbit, 4, true) + syncTankDoor(player) + playAudio(player, Sounds.EW2_COOL_BAR_3168) + + if (!getAttribute(player, attrWatered, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { + setAttribute(player, attrWatered, true) + } + } + return@queueScript stopExecuting(player) + } + else -> return@queueScript stopExecuting(player) } } - return - } else if(waterIn == 1 && waterOut == 1) { // inlet and drain are open, tank is damp - addScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, toInstance(player, location(1951, 5164, 2)))) // damp tank - playAudio(player, Sounds.EW2_WATER_FLOW_3183) // fill? - return + + } else if (waterIn == 1 && waterOut == 1) { + // inlet and drain are open, tank is damp + if (currentObject?.id == Scenery.WATER_TANK_18661) { + // tank is full: play drain anim, then swap to damp + queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + animateScenery(player, currentObject, TANK_DRAIN) + playAudio(player, Sounds.EW2_WATER_FLOW_3183) + return@queueScript delayScript(player, Animation(TANK_DRAIN).duration) + } + 1 -> { + removeScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18661, tankLoc)) + addScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, tankLoc)) + return@queueScript stopExecuting(player) + } + else -> return@queueScript stopExecuting(player) + } + } + } else if (currentObject?.id != Scenery.WATER_TANK_18660) { + // tank is empty: add the damp scenery + addScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, tankLoc)) + playAudio(player, Sounds.EW2_WATER_FLOW_3183) + } } } @@ -287,14 +442,64 @@ class EW2Listeners : InteractionListener { fun syncTankDoor(player: Player) { val state = getVarbit(player, EW2TankDoorStateVarbit) val doorLoc = toInstance(player, Location.create(1953, 5162, 2)) - val currentObject = getScenery(doorLoc) + val currentObject = getScenery(doorLoc) ?: return val targetId = tankDoorStates[state] - // remove old scenery then add the new one - if(currentObject != null && currentObject.id != targetId) { - // I know what you're thinking, replaceScenery doesn't work in this situation. Why? idfk - removeScenery(core.game.node.scenery.Scenery(currentObject.id, doorLoc, 1)) - addScenery(core.game.node.scenery.Scenery(targetId, doorLoc, 1)) + if (currentObject.id == targetId) return + + var animId = -1 + var soundId = -1 + + when { + // door opening/closing + (currentObject.id == Scenery.DOOR_18651 && targetId == Scenery.DOOR_18652) || + (currentObject.id == Scenery.DOOR_18654 && targetId == Scenery.DOOR_18655) || + (currentObject.id == Scenery.DOOR_18657 && targetId == Scenery.DOOR_18658) -> { + animId = DOOR_OPEN + soundId = Sounds.EW2_DIAL_SPIN_2326 + } + (currentObject.id == Scenery.DOOR_18652 && targetId == Scenery.DOOR_18651) || + (currentObject.id == Scenery.DOOR_18655 && targetId == Scenery.DOOR_18654) || + (currentObject.id == Scenery.DOOR_18658 && targetId == Scenery.DOOR_18657) -> { + animId = DOOR_CLOSE + soundId = Sounds.EW2_DIAL_SPIN_2326 + } + // rack extending/retracting + (currentObject.id == Scenery.DOOR_18652 && targetId == Scenery.DOOR_18653) || + (currentObject.id == Scenery.DOOR_18655 && targetId == Scenery.DOOR_18656) || + (currentObject.id == Scenery.DOOR_18658 && targetId == Scenery.DOOR_18659) -> { + animId = DOOR_EXTEND + soundId = Sounds.EW2_SCREW_TURN_3182 + } + (currentObject.id == Scenery.DOOR_18653 && targetId == Scenery.DOOR_18652) || + (currentObject.id == Scenery.DOOR_18656 && targetId == Scenery.DOOR_18655) || + (currentObject.id == Scenery.DOOR_18659 && targetId == Scenery.DOOR_18658) -> { + animId = DOOR_RETRACT + soundId = Sounds.EW2_SCREW_TURN_3182 + } + } + + queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + if (animId != -1) { + val anim = Animation(animId) + animateScenery(player, currentObject, animId) + if (soundId != -1) playAudio(player, soundId) + return@queueScript delayScript(player, anim.duration) + } + return@queueScript delayScript(player, 1) + } + 1 -> { + // replaceScenery doesn't work. why? idfk + removeScenery(core.game.node.scenery.Scenery(currentObject.id, doorLoc, 1)) + addScenery(core.game.node.scenery.Scenery(targetId, doorLoc, 1)) + syncCart(player) + + return@queueScript stopExecuting(player) + } + else -> return@queueScript stopExecuting(player) + } } } @@ -302,21 +507,19 @@ class EW2Listeners : InteractionListener { fun syncFan(player: Player) { val fanMachine = getVarbit(player, EW2FanLeverVarbit) if (fanMachine == 1) { - setVarbit(player, EW2FanStateVarbit, 1) + // spins the fan + setVarbit(player, EW2FanStateVarbit, 1, true) } else { - setVarbit(player, EW2FanStateVarbit, 0) + setVarbit(player, EW2FanStateVarbit, 0, true) } } // helper to convert static location coordinates to the dynamic instance location coordinates fun toInstance(player: Player, staticLoc: Location): Location { - val activity = player.getAttribute("activity") - if (activity == null || activity.base == null) return staticLoc - + val session = player.getAttribute("ew2-session", null) ?: return staticLoc val offsetX = staticLoc.x - 1920 val offsetY = staticLoc.y - 5120 - - return activity.base.transform(offsetX, offsetY, staticLoc.z) + return session.base.transform(offsetX, offsetY, staticLoc.z) } } @@ -405,7 +608,12 @@ class EW2Listeners : InteractionListener { // unlocked center hatch down to EW2 on(Scenery.HATCH_18596, IntType.SCENERY, "climb-down") { player, _ -> sendMessage(player, "You climb down the stairs.") - teleport(player, location(1955, 5155, 2)) + + // this is the non-instanced area + //teleport(player, location(1955, 5155, 2)) + + // start the instanced workshop session + EW2Session(player).start() if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 4) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 5) @@ -610,6 +818,13 @@ class EW2Listeners : InteractionListener { playAudio(player, Sounds.EW2_PLACE_BAR_3177) syncCart(player) removeItem(player, used) + + // advance quest stage + if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 5) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 6) + setVarbit(player, EW2StageVarbit, 6, true) + } + if(!getAttribute(player, attrJigged, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { setAttribute(player, attrJigged, true) } @@ -661,14 +876,12 @@ class EW2Listeners : InteractionListener { if (currentPos == 3 && nextPos == 2) { if (craneMat == 1 && (cartState == 1 || cartState == 2) && cartPos == 0) { // pick up - setVarbit(player, EW2CraneStateVarbit, cartState + 1) - setVarbit(player, EW2JigCartVarbit, 0) - syncCart(player) + setVarbit(player, EW2CraneStateVarbit, cartState + 1, true) + setVarbit(player, EW2JigCartVarbit, 0, true) } else if ((craneMat == 2 || craneMat == 3) && cartState == 0 && cartPos == 0) { // drop off - setVarbit(player, EW2JigCartVarbit, craneMat - 1) - setVarbit(player, EW2CraneStateVarbit, 1) - syncCart(player) + setVarbit(player, EW2JigCartVarbit, craneMat - 1, true) + setVarbit(player, EW2CraneStateVarbit, 1, true) } } @@ -676,13 +889,14 @@ class EW2Listeners : InteractionListener { if (currentPos == 1 && nextPos == 0) { if (craneMat == 2) { // if holding elemental bar, transform into hot bar - setVarbit(player, EW2CraneStateVarbit, 3) + setVarbit(player, EW2CraneStateVarbit, 3, true) playAudio(player, Sounds.EW2_CRANE_LOWER_3169) } } setVarbit(player, EW2CranePosVarbit, nextPos, true) + lockInteractions(player, 5) animate(player, Animations.HUMAN_PULL_LEVER_4909) - syncCrane(player) + syncCrane(player) // also syncs the cart inside the crane function return@on true } @@ -699,6 +913,7 @@ class EW2Listeners : InteractionListener { val nextPos = currentPos xor 2 setVarbit(player, EW2CranePosVarbit, nextPos, true) + lockInteractions(player, 5) animate(player, Animations.HUMAN_PULL_LEVER_4909) syncCrane(player) return@on true @@ -739,7 +954,7 @@ class EW2Listeners : InteractionListener { animate(player, Animations.HUMAN_PULL_LEVER_4909) } 2 -> { // hot ele bar. changes state to hot smashed bar. - setVarbit(player, EW2JigCartVarbit, 3) + setVarbit(player, EW2JigCartVarbit, 3, true) // "hardy" is correct. see above. sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar has been flattened.") playAudio(player, Sounds.EW2_PRESS_BAR_3180) @@ -834,7 +1049,7 @@ class EW2Listeners : InteractionListener { else -> tankState } - setVarbit(player, EW2TankDoorStateVarbit, nextTankState) + setVarbit(player, EW2TankDoorStateVarbit, nextTankState, true) syncTankDoor(player) return@on true } @@ -865,35 +1080,34 @@ class EW2Listeners : InteractionListener { runTask(player, 1) { when (tankState) { 1 -> { // 1 door opened, grabber inside - setVarbit(player, EW2TankDoorStateVarbit, 2) // 2 door opened, grabber out + setVarbit(player, EW2TankDoorStateVarbit, 2, true) // 2 door opened, grabber out // no cart update } 2 -> { // 2 door opened, grabber out // only grab the hot bar. otherwise, put the grabber back in if(cartState == 3) { - setVarbit(player, EW2TankDoorStateVarbit, 4) // 4 door opened, hot bar inside + setVarbit(player, EW2TankDoorStateVarbit, 4, true) // 4 door opened, hot bar inside // remove hot bar cart } else { - setVarbit(player, EW2TankDoorStateVarbit, 1) // 1 door opened, grabber inside + setVarbit(player, EW2TankDoorStateVarbit, 1, true) // 1 door opened, grabber inside } } 4 -> { // 4 door opened, hot bar inside - setVarbit(player, EW2TankDoorStateVarbit, 5) // 5 door opened, hot bar out + setVarbit(player, EW2TankDoorStateVarbit, 5, true) // 5 door opened, hot bar out } 5 -> { // 5 door opened, hot bar out - setVarbit(player, EW2TankDoorStateVarbit, 1) // 1 door opened, grabber inside + setVarbit(player, EW2TankDoorStateVarbit, 1, true) // 1 door opened, grabber inside // add hot bar cart } 7 -> { // 7 door opened, cooled bar inside - setVarbit(player, EW2TankDoorStateVarbit, 8) // 8 door opened, cooled bar out + setVarbit(player, EW2TankDoorStateVarbit, 8, true) // 8 door opened, cooled bar out } 8 -> { // 8 door opened, cooled bar out - setVarbit(player, EW2TankDoorStateVarbit, 1) // 1 door opened, grabber inside + setVarbit(player, EW2TankDoorStateVarbit, 1, true) // 1 door opened, grabber inside // add cool bar cart } } - syncTankDoor(player) - syncCart(player) + syncTankDoor(player) // also syncs the cart inside there } return@on true } @@ -914,24 +1128,26 @@ class EW2Listeners : InteractionListener { return@on true } - player.locks.lockInteractions(4) + lockInteractions(player, 4) animate(player, 4861) // if closed, open the valve if(valveState == 0){ setVarbit(player, EW2ValveWestVarbit, 1, true) - runTask(player, 3) { + queueScript(player, 3, QueueStrength.SOFT) { syncTank(player) sendDialogue(player, "You open the valve.") playAudio(player, Sounds.EW2_WATER_VALVE_3185) + return@queueScript true } // if open, close the valve } else { setVarbit(player, EW2ValveWestVarbit, 0, true) - runTask(player, 3) { + queueScript(player, 3, QueueStrength.SOFT) { syncTank(player) sendDialogue(player, "You close the valve.") playAudio(player, Sounds.EW2_WATER_VALVE_3185) + return@queueScript true } } return@on true @@ -1038,6 +1254,7 @@ class EW2Listeners : InteractionListener { } addItemOrDrop(player, cogToGive) + syncCogs(player) animate(player, 537) sendMessage(player, "You remove the cog from the shaft.") } @@ -1070,6 +1287,7 @@ class EW2Listeners : InteractionListener { removeItem(player, item) } } + syncCogs(player) return@onUseWith true } @@ -1096,6 +1314,7 @@ class EW2Listeners : InteractionListener { removeItem(player, item) } } + syncCogs(player) return@onUseWith true } @@ -1122,6 +1341,7 @@ class EW2Listeners : InteractionListener { removeItem(player, item) } } + syncCogs(player) return@onUseWith true } @@ -1146,6 +1366,7 @@ class EW2Listeners : InteractionListener { playAudio(player, Sounds.EW2_FANBLADE_3174) setVarbit(player, EW2FanLeverVarbit, 1, true) syncFan(player) + syncCogs(player) // check cart and cool if necessary. otherwise nothing should happen. if (cartpos == 3 && cartstate == 4) { setVarbit(player, EW2JigCartVarbit, 5, true) @@ -1158,6 +1379,7 @@ class EW2Listeners : InteractionListener { sendDialogue(player, "The machine grinds to a standstill.") setVarbit(player, EW2FanLeverVarbit, 0, true) syncFan(player) + syncCogs(player) } } else { // allCogs != 57, cogs are not correct sendDialogue(player, "The machine starts up, but the fan does not blow.") @@ -1218,7 +1440,7 @@ class EW2Listeners : InteractionListener { 0, 0 ) - visualize(player, goodHatAnim, goodHatGfx) + visualize(player, EXTRACTOR_GOOD, EXTRACTOR_GOOD_GFX) playAudio(player, Sounds.EW2_EXTRACTOR_OPERATION_3166) sendGraphics(809, Location.create(1962, 5148, 0)) setVarbit(player, EW2ExtractorGun, 2, true) @@ -1246,7 +1468,7 @@ class EW2Listeners : InteractionListener { 0, 0 ) - visualize(player, badHatAnim, badHatGfx) + visualize(player, EXTRACTOR_BAD, EXTRACTOR_BAD_GFX) playAudio(player, Sounds.EW2_EXTRACTOR_ZAP_3173) impact(player, 4) sendMessage(player, "The extractor hat shocks you!.") diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt index c5fbc81ca..59995acee 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt @@ -1,6 +1,7 @@ package content.region.kandarin.seers.quest.elementalworkshop2 import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCart +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCogs import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncCrane import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncFan import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncSteamPress @@ -53,6 +54,7 @@ class EW2Session(val player: Player? = null) : MapArea { syncTank(p) syncTankDoor(p) syncFan(p) + syncCogs(p) } // clean up the instanced area diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt index 3aa5dccd9..ade19851b 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt @@ -92,10 +92,10 @@ class JunctionBoxInterface : InterfaceListener { setComponentVisibility(player, junctionBox, pipes[s3 - 1], false); placedCount++ } - setVarbit(player, pipesLeftVarbit, 3 - placedCount) + setVarbit(player, pipesLeftVarbit, 3 - placedCount, true) // reset any selected - setVarbit(player, pipeSelectVarbit, 0) + setVarbit(player, pipeSelectVarbit, 0, true) return@onOpen true } @@ -136,14 +136,14 @@ class JunctionBoxInterface : InterfaceListener { b4 -> 4; b5 -> 5; b6 -> 6 else -> 0 } - setVarbit(player, pipeSelectVarbit, selection) + setVarbit(player, pipeSelectVarbit, selection, true) } else { val pipeIndex = getPipeIndex(currentSelection, buttonID, b1, b2, b3, b4, b5, b6) if (pipeIndex != -1) { // check if this specific pipe is already placed if (isPipeAlreadyPlaced(player, pipeIndex + 1)) { - setVarbit(player, pipeSelectVarbit, 0) + setVarbit(player, pipeSelectVarbit, 0, true) return@on true } @@ -155,15 +155,15 @@ class JunctionBoxInterface : InterfaceListener { // pipe status is saved in these varbits and recalled on open. they are linked to main quest progression status val saved = when { getVarbit(player, firstSelect) == 0 -> { - setVarbit(player, firstSelect, pipeIndex + 1); true + setVarbit(player, firstSelect, pipeIndex + 1, true); true } getVarbit(player, secondSelect) == 0 -> { - setVarbit(player, secondSelect, pipeIndex + 1); true + setVarbit(player, secondSelect, pipeIndex + 1, true); true } getVarbit(player, thirdSelect) == 0 -> { - setVarbit(player, thirdSelect, pipeIndex + 1); true + setVarbit(player, thirdSelect, pipeIndex + 1, true); true } else -> false @@ -171,10 +171,10 @@ class JunctionBoxInterface : InterfaceListener { if (saved) { val left = getVarbit(player, pipesLeftVarbit) - setVarbit(player, pipesLeftVarbit, left - 1) + setVarbit(player, pipesLeftVarbit, left - 1, true) } } - setVarbit(player, pipeSelectVarbit, 0) + setVarbit(player, pipeSelectVarbit, 0, true) } // if there are no pipes left, we have placed them all. hammer time! @@ -215,10 +215,10 @@ class JunctionBoxInterface : InterfaceListener { // Hide the pipe on interface setComponentVisibility(player, junctionBox, pipes[pipeIndex], true) // Reset the varbit - setVarbit(player, v, 0) + setVarbit(player, v, 0, true) // Increment pipes left val left = getVarbit(player, pipesLeftVarbit) - setVarbit(player, pipesLeftVarbit, left + 1) + setVarbit(player, pipesLeftVarbit, left + 1, true) playAudio(player, Sounds.EW2_REMOVE_PIPE_3181) return true } diff --git a/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt b/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt index 468bdaf96..987cc7b84 100644 --- a/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt @@ -1,8 +1,14 @@ package core.game.system.command.sets +import core.api.addScenery +import core.api.animateScenery +import core.api.delayScript import core.api.log +import core.api.queueScript import core.api.sendMessage +import core.api.stopExecuting import core.cache.Cache +import core.game.interaction.QueueStrength import core.game.node.scenery.Scenery import core.game.node.scenery.SceneryBuilder import core.game.node.entity.npc.NPC @@ -121,6 +127,52 @@ class SpawnCommandSet : CommandSet(Privilege.ADMIN){ log(this::class.java, Log.FINE, "object = $`object`") } + /** + * Spawn object with given ID at the player's location and animate it + */ + define("objanim", usage = "::objanim [objectId] [animFrom] [(opt)animTo] [(opt)delay]", description = "Spawns an object and animates it with the provided anim ID. If a second ID is provided, it iterates through the anims.") { player, args -> + if (args.size < 3) { + sendMessage(player, "Usage: ::objanim [objectId] [animFrom] [(opt)animTo] [(opt)delay]") + return@define + } + + val objectId = args[1].toIntOrNull() + val animFrom = args[2].toIntOrNull() + + val animTo = args.getOrNull(3)?.toIntOrNull() + val delay = args.getOrNull(4)?.toIntOrNull() ?: 3 + + if (objectId == null || animFrom == null) { + sendMessage(player, "Invalid arguments. Please use numbers.") + return@define + } + + // spawn the object + val obj = core.game.node.scenery.Scenery(objectId, player.location) + addScenery(obj) + + // animate a single anim + if (animTo == null) { + animateScenery(player, obj, animFrom) + sendMessage(player, "Spawned object $objectId and playing animation $animFrom.") + return@define + } + + // loop through the anims if it's a range + sendMessage(player, "Spawned object $objectId, playing anims $animFrom to $animTo.") + queueScript(player, 1, QueueStrength.STRONG) { stage: Int -> + val currentAnimId = animFrom + stage + animateScenery(player, obj, currentAnimId) + sendMessage(player, "Playing object animation $currentAnimId") + if (currentAnimId >= animTo) { + return@queueScript stopExecuting(player) + } + return@queueScript delayScript(player, delay) + } + + return@define + } + define("objectgrid", usage = "::objectgrid start-id end-id type rotation", description = "Spawns a 10-wide grid cycling through the given object id range.") { player, args -> if(args!!.size != 5) { reject(player, "Usage: objectgrid beginId endId type rotation") From 373677f492f7cda017a568433ed8644910361602 Mon Sep 17 00:00:00 2001 From: aidan Date: Fri, 10 Jul 2026 21:20:44 -0500 Subject: [PATCH 36/45] simplified some, converted to contentAPI, general QA review, added camera angles/cutscene --- .../quest/elementalworkshop2/EW2Cutscene.kt | 89 +++ .../quest/elementalworkshop2/EW2Listeners.kt | 610 ++++++++---------- .../quest/elementalworkshop2/EW2Session.kt | 14 +- .../elementalworkshop2/ElementalWorkshop2.kt | 6 +- .../JunctionBoxInterface.kt | 6 + 5 files changed, 372 insertions(+), 353 deletions(-) create mode 100644 Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Cutscene.kt diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Cutscene.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Cutscene.kt new file mode 100644 index 000000000..fd2b5d917 --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Cutscene.kt @@ -0,0 +1,89 @@ +package content.region.kandarin.seers.quest.elementalworkshop2 + +import content.data.Quests +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.EXTRACTOR_BAD +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.EXTRACTOR_BAD_GFX +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.EXTRACTOR_GOOD +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.EXTRACTOR_GOOD_GFX +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.EXTR_GUN +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.toInstance +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Session.Companion.EW2_REGION +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2ExtractorGun +import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2StageVarbit +import core.api.* +import core.game.activity.Cutscene +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.world.map.Location +import core.game.world.update.flag.context.Animation +import org.rs09.consts.Sounds + +/** + * The extractor hat cutscene in EW2 + */ + +class EW2Cutscene(player: Player, good: Boolean) : Cutscene(player) { + override fun setup() { + setExit(toInstance(player, Location.create(1961, 5150, 0))) + loadRegion(EW2_REGION) + } + + val successfulExtract = good + + override fun runStage(stage: Int) { + when(stage) { + 0 -> { + teleport(player, 41, 30) + moveCamera(37, 25, 350) + rotateCamera(42, 29) + timedUpdate(0) + } + + 1 -> { + face(player, player.location.transform(1, 0, 0)) + timedUpdate(0) + } + + 2 -> { + teleport(player, 42, 30) + if (successfulExtract) { + visualize(player, EXTRACTOR_GOOD, EXTRACTOR_GOOD_GFX) + playAudio(player, Sounds.EW2_EXTRACTOR_OPERATION_3166) + val gun = getScenery(base.transform(42, 28, 0)) + if (gun != null) animateScenery(gun, EXTR_GUN) + timedUpdate(Animation(EXTRACTOR_GOOD).duration) + } else { + visualize(player, EXTRACTOR_BAD, EXTRACTOR_BAD_GFX) + playAudio(player, Sounds.EW2_EXTRACTOR_ZAP_3173) + timedUpdate(Animation(EXTRACTOR_BAD).duration) + } + } + + 3 -> { + teleport(player, 41, 30) + if (successfulExtract) { + // make a mind bar + setVarbit(player, EW2ExtractorGun, 2, true) + + // drain magic + adjustLevel(player, Skills.MAGIC, -20) + sendMessage(player, "You feel magically weakened.") + + // advance quest + if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 9) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 10) + setVarbit(player, EW2StageVarbit, 10, true) + } + } else { + impact(player, 4) + sendMessage(player, "The extractor hat shocks you!") + } + timedUpdate(1) + } + + 4 -> { + endWithoutFade { } + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 7a832974c..66b3d6505 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -26,14 +26,21 @@ import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2 import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.attrWatered import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.attrWinded import core.api.* +import core.api.utils.PlayerCamera +import core.game.dialogue.DialogueLabeller import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import core.game.world.map.Direction import core.game.world.map.Location import core.game.world.update.flag.context.Animation +import core.net.packet.PacketRepository +import core.net.packet.context.CameraContext +import core.net.packet.out.CameraViewPacket import org.rs09.consts.* class EW2Listeners : InteractionListener { @@ -68,39 +75,29 @@ class EW2Listeners : InteractionListener { */ companion object { + + // interface + const val LEVER_SCHEM = 466 // graphics const val EXTRACTOR_GOOD_GFX = Graphics.EXTRACTOR_HAT_CHAIR_807 const val EXTRACTOR_BAD_GFX = Graphics.IMPROPERLY_EXTRACTOR_HAT_CHAIR_808 // animations + const val VALVE_TURN = 4861 const val DOOR_OPEN = 4871 // 4875 is another open const val DOOR_CLOSE = 4872 // 4876 is another open const val DOOR_EXTEND = 4873 // 4877 is another extend const val DOOR_RETRACT = 4874 // 4878 is another retract - // 4879 - // 4880 - // 4881 - // 4882 - // 4883 const val EXTRACTOR_GOOD = Animations.HUMAN_USE_EXTRACTOR_HAT_CHAIR_4884 const val EXTRACTOR_BAD = Animations.HUMAN_IMPROPERLY_USE_EXTRACTOR_HAT_CHAIR_4885 - // 4886 const val CRANE_LOWER_CART = 4887 const val CRANE_RAISE_CART = 4888 const val CRANE_LOWER_LAVA = 4889 const val CRANE_RAISE_LAVA = 4890 - // 4891 (another pivot anim, unused) const val CRANE_PIVOT = 4892 - // 4893 - // 4894 - // 4895 - const val COG_REV = 4896 - // 4897 - const val FAN_SPIN = 4898 - // 4899 - // 4900 - // 4901 + //const val COG_REV = 4896 // not used + const val EXTR_GUN = 4897 const val TANK_FILL = 4902 const val TANK_DRAIN = 4903 const val COG_FWD = 4906 @@ -277,20 +274,23 @@ class EW2Listeners : InteractionListener { val trackCenter = toInstance(player, Location.create(1953, 5155, 2)) val existingCart = findLocalNPCs(trackCenter, 15).find { it.id in jigCartStates } if (existingCart != null) { + // update appearance if (existingCart.id != targetId) { - existingCart.transform(targetId) + existingCart.transform(targetId) // needs to be permanent so can't use the contentAPI transform } + // failsafe to reset the cart where it's supposed to be. right now cart just teleports so this shouldn't trigger if (!existingCart.walkingQueue.isMoving && existingCart.location != targetLocation) { teleport(existingCart, targetLocation) } + // if cart is inside water tank, clear it if (tankState == 3 || tankState == 4 || tankState == 6 || tankState == 7) { existingCart.clear() } - // if there is no cart, spawn a new one (like if player is just logging in) + // if there is no cart, spawn a new one (like if player is just entering) } else { // create the cart val newCart = NPC.create(targetId, targetLocation) @@ -342,14 +342,13 @@ class EW2Listeners : InteractionListener { val tankLoc = toInstance(player, location(1951, 5164, 2)) val currentObject = getScenery(tankLoc) - if (waterIn == 0 && waterOut == 0) { - // both valves closed, nothing happens - return + // both valves closed, nothing happens + if (waterIn == 0 && waterOut == 0) return - } else if (waterIn == 0 && waterOut == 1) { - // drain is open, empty the tank + // drain is open, empty the tank + if (waterIn == 0 && waterOut == 1) { + // tank is full: play drain anim, then remove if (currentObject != null && currentObject.id == Scenery.WATER_TANK_18661) { - // tank is full: play drain anim, then remove queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> when (stage) { 0 -> { @@ -364,14 +363,17 @@ class EW2Listeners : InteractionListener { else -> return@queueScript stopExecuting(player) } } - } else if (currentObject != null && currentObject.id == Scenery.WATER_TANK_18660) { - // tank is damp: just remove the scenery + } + + // tank is damp: just remove the scenery + if (currentObject != null && currentObject.id == Scenery.WATER_TANK_18660) { removeScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, tankLoc)) playAudio(player, Sounds.EW2_WATER_RACK_3184) } + } - } else if (waterIn == 1 && waterOut == 0) { - // inlet is open, fill tank + // inlet is open, fill tank + if (waterIn == 1 && waterOut == 0) { if (currentObject?.id == Scenery.WATER_TANK_18661) return // already full queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> @@ -388,6 +390,7 @@ class EW2Listeners : InteractionListener { playAudio(player, Sounds.EW2_WATER_FLOW_3183) return@queueScript delayScript(player, Animation(TANK_FILL).duration) } + 1 -> { // swap damp for full tank val existing = getScenery(tankLoc) @@ -407,14 +410,16 @@ class EW2Listeners : InteractionListener { } return@queueScript stopExecuting(player) } + else -> return@queueScript stopExecuting(player) } } + } - } else if (waterIn == 1 && waterOut == 1) { - // inlet and drain are open, tank is damp + // inlet and drain are open, tank is damp + if (waterIn == 1 && waterOut == 1) { + // tank is full: play drain anim, then swap to damp if (currentObject?.id == Scenery.WATER_TANK_18661) { - // tank is full: play drain anim, then swap to damp queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> when (stage) { 0 -> { @@ -422,16 +427,20 @@ class EW2Listeners : InteractionListener { playAudio(player, Sounds.EW2_WATER_FLOW_3183) return@queueScript delayScript(player, Animation(TANK_DRAIN).duration) } + 1 -> { removeScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18661, tankLoc)) addScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, tankLoc)) return@queueScript stopExecuting(player) } + else -> return@queueScript stopExecuting(player) } } - } else if (currentObject?.id != Scenery.WATER_TANK_18660) { - // tank is empty: add the damp scenery + } + + // tank is empty: add the damp scenery + if (currentObject?.id != Scenery.WATER_TANK_18660) { addScenery(core.game.node.scenery.Scenery(Scenery.WATER_TANK_18660, tankLoc)) playAudio(player, Sounds.EW2_WATER_FLOW_3183) } @@ -464,53 +473,115 @@ class EW2Listeners : InteractionListener { animId = DOOR_CLOSE soundId = Sounds.EW2_DIAL_SPIN_2326 } - // rack extending/retracting - (currentObject.id == Scenery.DOOR_18652 && targetId == Scenery.DOOR_18653) || - (currentObject.id == Scenery.DOOR_18655 && targetId == Scenery.DOOR_18656) || - (currentObject.id == Scenery.DOOR_18658 && targetId == Scenery.DOOR_18659) -> { + + // rack extending + (currentObject.id == Scenery.DOOR_18652 && targetId == Scenery.DOOR_18653) || // extend empty + (currentObject.id == Scenery.DOOR_18655 && targetId == Scenery.DOOR_18656) || // extend holding hot + (currentObject.id == Scenery.DOOR_18658 && targetId == Scenery.DOOR_18659) -> { // extend holding cooled animId = DOOR_EXTEND soundId = Sounds.EW2_SCREW_TURN_3182 } - (currentObject.id == Scenery.DOOR_18653 && targetId == Scenery.DOOR_18652) || - (currentObject.id == Scenery.DOOR_18656 && targetId == Scenery.DOOR_18655) || - (currentObject.id == Scenery.DOOR_18659 && targetId == Scenery.DOOR_18658) -> { + + // rack retracting + (currentObject.id == Scenery.DOOR_18653 && targetId == Scenery.DOOR_18652) || // retract empty + (currentObject.id == Scenery.DOOR_18653 && targetId == Scenery.DOOR_18655) || // retract grabbing hot cart + (currentObject.id == Scenery.DOOR_18656 && targetId == Scenery.DOOR_18652) || // retract leaving hot cart + (currentObject.id == Scenery.DOOR_18659 && targetId == Scenery.DOOR_18652) -> { // retract leaving cooled cart animId = DOOR_RETRACT soundId = Sounds.EW2_SCREW_TURN_3182 } } + // if the cart is being dropped off + val isDroppingCart = (currentObject.id == Scenery.DOOR_18656 || currentObject.id == Scenery.DOOR_18659) && targetId == Scenery.DOOR_18652 + queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> when (stage) { 0 -> { + // spawn cart visually immediately so the empty rack retracts away from it + if (isDroppingCart) syncCart(player) + if (animId != -1) { - val anim = Animation(animId) animateScenery(player, currentObject, animId) if (soundId != -1) playAudio(player, soundId) - return@queueScript delayScript(player, anim.duration) + return@queueScript delayScript(player, Animation(animId).duration) } return@queueScript delayScript(player, 1) } + 1 -> { - // replaceScenery doesn't work. why? idfk + // replaceScenery doesn't work here. Why? idfk. removeScenery(core.game.node.scenery.Scenery(currentObject.id, doorLoc, 1)) addScenery(core.game.node.scenery.Scenery(targetId, doorLoc, 1)) - syncCart(player) - + // sync the cart after the door closes + if (!isDroppingCart) syncCart(player) return@queueScript stopExecuting(player) } + else -> return@queueScript stopExecuting(player) } } } - // updates fan state. fun fact, the fan state varbit isn't saved even if you say to save it. gotta make sure it's set on login. + // updates fan state. fun fact, the fan state varbit isn't saved even if you say to save it. gotta make sure it's set on entry. fun syncFan(player: Player) { val fanMachine = getVarbit(player, EW2FanLeverVarbit) - if (fanMachine == 1) { - // spins the fan - setVarbit(player, EW2FanStateVarbit, 1, true) + setVarbit(player, EW2FanStateVarbit, fanMachine, true) + } + + // helper function for the cog crates + private fun searchCogCrate(player: Player, cogItem: Int, cogName: String) { + if (!inInventory(player, cogItem)) { + addItemOrDrop(player, cogItem) + sendMessage(player, "You find a $cogName cog.") } else { - setVarbit(player, EW2FanStateVarbit, 0, true) + sendMessage(player, "It's empty.") + } + } + + // helper function for cog placement + private fun placeCog(player: Player, item: Item, varbit: Int) { + val (cogVal, cogName) = when (item.id) { + Items.SMALL_COG_9726 -> 1 to "small" + Items.MEDIUM_COG_9725 -> 2 to "medium-sized" + Items.LARGE_COG_9724 -> 3 to "big" + else -> return + } + if (removeItem(player, item)) { + setVarbit(player, varbit, cogVal, true) + sendItemDialogue(player, item, "You put the $cogName cog onto the shaft.") + animate(player, 537) + playAudio(player, Sounds.EW2_PLACE_COG_3178) + syncCogs(player) + } + } + + // helper function for the water valves + private fun handleValve(player: Player, valveVarbit: Int) { + val valveState = getVarbit(player, valveVarbit) + val door = getVarbit(player, EW2TankDoorStatusVarbit) // 0 if door closed, 1 if open + + if (door == 1) { + sendMessage(player, "The valve won't turn, maybe the door needs to be closed first.") + return + } + + // only the west (fill) valve requires the pipe to be fixed first + if (valveVarbit == EW2ValveWestVarbit && getVarbit(player, EW2PipeVarbit) == 0) { + sendMessage(player, "You turn the valve but nothing happens, maybe the pipe needs to be repaired first.") + return + } + + lockInteractions(player, 4) + animate(player, VALVE_TURN) + + val nextState = valveState xor 1 + setVarbit(player, valveVarbit, nextState, true) + queueScript(player, 3, QueueStrength.SOFT) { + syncTank(player) + sendDialogue(player, if (nextState == 1) "You open the valve." else "You close the valve.") + playAudio(player, Sounds.EW2_WATER_VALVE_3185) + return@queueScript true } } @@ -540,7 +611,8 @@ class EW2Listeners : InteractionListener { sendItemDialogue(player, Items.BEATEN_BOOK_9717, "You find an old-looking book.") addItemOrDrop(player, Items.BEATEN_BOOK_9717) - if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 0) { + // advance quest stage + if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 0) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 1) setVarbit(player, EW2StageVarbit, 1, true) } @@ -564,24 +636,29 @@ class EW2Listeners : InteractionListener { setInterfaceText(player, "4 W", 222, 8) setInterfaceText(player, "Pipe 3.", 222, 9) - if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 2) { + // advance quest stage + if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 2) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 3) setVarbit(player, EW2StageVarbit, 3, true) } - if(getVarbit(player, EW2MachineryVarbit) == 0) + + // set the varbit that unlocks the machinery key (below) + if (getVarbit(player, EW2MachineryVarbit) == 0) setVarbit(player, EW2MachineryVarbit, 1, true) return@on true } // machinery where you find the key. on(Scenery.MACHINERY_18594, IntType.SCENERY, "search") { player, _ -> - if(!inInventory(player, Items.KEY_9722)) { + if (!inInventory(player, Items.KEY_9722)) { sendItemDialogue(player, Items.KEY_9722, "You find a key hidden in the third pipe.") addItemOrDrop(player, Items.KEY_9722) } else { sendDialogue(player, "You search the machinery but find nothing.") } - if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 3) { + + // advance quest stage + if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 3) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 4) setVarbit(player, EW2StageVarbit, 4, true) } @@ -615,7 +692,8 @@ class EW2Listeners : InteractionListener { // start the instanced workshop session EW2Session(player).start() - if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 4) { + // advance quest stage + if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 4) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 5) setVarbit(player, EW2StageVarbit, 5, true) } @@ -629,14 +707,14 @@ class EW2Listeners : InteractionListener { return@on true } - // stairs to lower level + // stairs to EW2 lower level on(Scenery.STAIRWELL_18597, IntType.SCENERY, "climb-down") { player, _ -> teleport(player, toInstance(player,location(1948, 5158, 0))) sendMessage(player, "You climb down the stairs.") return@on true } - // stairs back up to EW2 + // stairs from EW2 lower level back up to EW2 main level on(Scenery.STAIRS_18599, IntType.SCENERY, "climb-up") { player, _ -> teleport(player, toInstance(player,location(1948, 5157, 2))) sendMessage(player, "You climb up the stairs.") @@ -655,18 +733,16 @@ class EW2Listeners : InteractionListener { addDialogueAction(player) { _, buttonId -> when (buttonId) { 2 -> { - if(!inInventory(player, Items.CRANE_SCHEMATIC_9718)){ + if (!inInventory(player, Items.CRANE_SCHEMATIC_9718)) { addItemOrDrop(player, Items.CRANE_SCHEMATIC_9718) sendItemDialogue(player, Items.CRANE_SCHEMATIC_9718, "You take a copy of the schematic drawing.") - } - else sendDialogue(player, "You already have this schematic.") + } else sendDialogue(player, "You already have this schematic.") } 3 -> { - if(!inInventory(player, Items.LEVER_SCHEMATIC_9719)){ + if (!inInventory(player, Items.LEVER_SCHEMATIC_9719)) { addItemOrDrop(player, Items.LEVER_SCHEMATIC_9719) sendItemDialogue(player, Items.LEVER_SCHEMATIC_9719, "You take a copy of the schematic drawing.") - } - else sendDialogue(player, "You already have this schematic.") + } else sendDialogue(player, "You already have this schematic.") } } } @@ -685,8 +761,9 @@ class EW2Listeners : InteractionListener { // find the player's cart val existingCart = findLocalNPCs(player, jigCartStates, 32).firstOrNull() + // no cart (should not happen) if (existingCart == null) { - sendMessage(player, "CART MISSING. LOG IN AND OUT TO RESET IT.") + sendMessage(player, "CART MISSING. RE-ENTER THE WORKSHOP TO RESET IT, OR REPORT THIS.") return@on true } @@ -695,20 +772,24 @@ class EW2Listeners : InteractionListener { sendMessage(player, "You cannot do that while the claw is down.") return@on true } + // door must be closed if (currentPos == 2 && doorPos == 1) { sendMessage(player, "You cannot do that while the door is open.") return@on true } + // fan must be off if (currentPos == 3 && fanState == 1) { sendMessage(player, "You cannot do that while the fan is on.") return@on true } + // cart must be currently stopped if (existingCart.walkingQueue.isMoving) { return@on true } + // cart must not be in the water tank if (tankState == 3 || tankState == 4 || tankState == 6 || tankState == 7) { sendMessage(player, "You cannot do that while the cart is inside the tank.") @@ -759,7 +840,7 @@ class EW2Listeners : InteractionListener { on(jigCartStates, IntType.NPC, "take-from") { player, node -> val cartPos = getVarbit(player, EW2CartLeverVarbit) // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east - // don't allow interact if cart is not at south station + // don't allow interaction if cart is not at south station if (cartPos != 0) { sendMessage(player, "I can't reach that.") return@on true @@ -772,22 +853,16 @@ class EW2Listeners : InteractionListener { animate(player, 537) setVarbit(player, EW2JigCartVarbit, 0, true) } - NPCs.JIG_CART_4915 -> { - sendMessage(player, "The bar is too hot to touch.") - } - NPCs.JIG_CART_4916 -> { - sendMessage(player, "The bar is too hot to touch.") - } - NPCs.JIG_CART_4917 -> { - sendMessage(player, "You should finish the process before taking the bar.") - } + NPCs.JIG_CART_4915 -> sendMessage(player, "The bar is too hot to touch.") + NPCs.JIG_CART_4916 -> sendMessage(player, "The bar is too hot to touch.") + NPCs.JIG_CART_4917 -> sendMessage(player, "You should finish the process before taking the bar.") NPCs.JIG_CART_4918 -> { addItemOrDrop(player, Items.PRIMED_BAR_9727) sendItemDialogue(player, Items.PRIMED_BAR_9727, "You take the primed bar.") setVarbit(player, EW2JigCartVarbit, 0, true) // advance quest stage - if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { + if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 7) setVarbit(player, EW2StageVarbit, 7, true) } @@ -802,7 +877,7 @@ class EW2Listeners : InteractionListener { val cranePos = getVarbit(player, EW2CranePosVarbit) val cartPos = getVarbit(player, EW2CartLeverVarbit) // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east - // don't allow interact if cart is not at south station + // don't allow interaction if cart is not at south station if (cartPos != 0) { sendMessage(player, "I can't reach that.") return@onUseWith true @@ -825,7 +900,8 @@ class EW2Listeners : InteractionListener { setVarbit(player, EW2StageVarbit, 6, true) } - if(!getAttribute(player, attrJigged, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { + // attribute for quest log + if (!getAttribute(player, attrJigged, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { setAttribute(player, attrJigged, true) } return@onUseWith true @@ -845,22 +921,24 @@ class EW2Listeners : InteractionListener { // lever schematic (what lever is this even for?) on(Items.LEVER_SCHEMATIC_9719, IntType.ITEM, "read") { player, _ -> - openInterface(player, 466) + openInterface(player, LEVER_SCHEM) return@on true } // fix crane onUseWith(IntType.SCENERY, Items.CRANE_CLAW_9720, Scenery.OLD_CRANE_18636) { player, used, _ -> // check if already repaired - if(getVarbit(player, EW2CraneStateVarbit) != 0) { + if (getVarbit(player, EW2CraneStateVarbit) != 0) { sendMessage(player, "The crane is already repaired.") return@onUseWith true } - sendItemDialogue(player, used, "You replace the broken claw.") - removeItem(player, used) - - setVarbit(player, EW2CraneStateVarbit, 1, true) - syncCrane(player) + + // repair crane + if (removeItem(player, used)) { + sendItemDialogue(player, used, "You replace the broken claw.") + setVarbit(player, EW2CraneStateVarbit, 1, true) + syncCrane(player) + } return@onUseWith true } @@ -896,7 +974,7 @@ class EW2Listeners : InteractionListener { setVarbit(player, EW2CranePosVarbit, nextPos, true) lockInteractions(player, 5) animate(player, Animations.HUMAN_PULL_LEVER_4909) - syncCrane(player) // also syncs the cart inside the crane function + syncCrane(player) // this also syncs the cart inside the crane function return@on true } @@ -942,28 +1020,31 @@ class EW2Listeners : InteractionListener { if (cartPos == 1) { when (cartState) { 0 -> { // empty. nothing happens. + // "hardy" is the real word used. I thought it was a typo for 'hammer' but it's not. https://en.wikipedia.org/wiki/Hardy_tool + // also note during one of the source videos it's authentic that the press state shows a purple ele bar for a moment. weird. sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, stopping inches from the jig, then returns to its original position.") playAudio(player, Sounds.EW2_PRESS_BAR_3180) animate(player, Animations.HUMAN_PULL_LEVER_4909) } + 1 -> { // ele bar. nothing happens. - // "hardy" is the real word used. I thought it was a typo for 'hammer' but it's not. https://en.wikipedia.org/wiki/Hardy_tool - // also note during one of the source videos it's authentic that the press state shows a purple ele bar for a moment. weird. sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is not hot enough to flatten.") playAudio(player, Sounds.EW2_PRESS_BAR_3180) animate(player, Animations.HUMAN_PULL_LEVER_4909) } + 2 -> { // hot ele bar. changes state to hot smashed bar. setVarbit(player, EW2JigCartVarbit, 3, true) - // "hardy" is correct. see above. sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar has been flattened.") playAudio(player, Sounds.EW2_PRESS_BAR_3180) animate(player, Animations.HUMAN_PULL_LEVER_4909) syncCart(player) - if(!getAttribute(player, attrFlattened, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { + // attribute for quest log + if (!getAttribute(player, attrFlattened, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { setAttribute(player, attrFlattened, true) } } + else -> { // smashed hot, smashed cooled, or primed. nothing happens. sendDialogue(player, "You pull the lever and the press responds. The hardy comes down, hitting the elemental bar. The bar is already flattened.") playAudio(player, Sounds.EW2_PRESS_BAR_3180) @@ -979,7 +1060,6 @@ class EW2Listeners : InteractionListener { sendDialogue(player, "You pull the lever but nothing happens. Maybe the pipes are connected the wrong way round.") animate(player, Animations.HUMAN_PULL_LEVER_4909) } - return@on true } @@ -994,19 +1074,18 @@ class EW2Listeners : InteractionListener { if (!inInventory(player, Items.PIPE_9723)) { addItemOrDrop(player, Items.PIPE_9723) sendMessage(player, "You find a pipe.") - } else { - sendMessage(player, "It's empty.") - } + } else sendMessage(player, "It's empty.") return@on true } // fix the pipe onUseWith(IntType.SCENERY, Items.PIPE_9723, Scenery.PIPING_18650) { player, item, _ -> - animate(player, 537) - sendItemDialogue(player, item, "You replace the section of broken pipe.") - playAudio(player, Sounds.EW2_PLACE_PIPE_3433) - setVarbit(player, EW2PipeVarbit, 1, true) - removeItem(player, item) + if (removeItem(player, item)) { + animate(player, 537) + sendItemDialogue(player, item, "You replace the section of broken pipe.") + playAudio(player, Sounds.EW2_PLACE_PIPE_3433) + setVarbit(player, EW2PipeVarbit, 1, true) + } return@onUseWith true } @@ -1019,19 +1098,19 @@ class EW2Listeners : InteractionListener { animate(player, Animations.HUMAN_PULL_LEVER_4909) // if tank has water, don't open. - if(tankScen != null) { + if (tankScen != null) { sendDialogue(player, "You pull the lever but nothing happens. Maybe the tank needs to be empty first.") return@on true } // check the rack is not out - if(tankState == 2 || tankState == 5 || tankState == 8) { + if (tankState == 2 || tankState == 5 || tankState == 8) { sendDialogue(player, "You pull the lever but nothing happens. Maybe the rack needs retracting first.") return@on true } // update door status open/close - if(doorOpen == 0) { + if (doorOpen == 0) { setVarbit(player, EW2TankDoorStatusVarbit, 1, true) } else { setVarbit(player, EW2TankDoorStatusVarbit, 0, true) @@ -1045,7 +1124,6 @@ class EW2Listeners : InteractionListener { 3 -> 4 // Closed/Hot -> Opened/Hot 7 -> 6 // Opened/Cooled -> Closed/Cooled 6 -> 7 // Closed/Cooled -> Opened/Cooled - // six seeeven hehehehe else -> tankState } @@ -1077,113 +1155,49 @@ class EW2Listeners : InteractionListener { animate(player, Animations.HUMAN_TURN_CORKSCREW_LEVER_4905) // either pull cart in or push it out. transfer between all the door open states - runTask(player, 1) { + queueScript(player, 1, QueueStrength.SOFT) { when (tankState) { - 1 -> { // 1 door opened, grabber inside - setVarbit(player, EW2TankDoorStateVarbit, 2, true) // 2 door opened, grabber out - // no cart update - } + + // 1 door opened, grabber inside. no cart update + 1 -> setVarbit(player, EW2TankDoorStateVarbit, 2, true) // 2 door opened, grabber out + 2 -> { // 2 door opened, grabber out // only grab the hot bar. otherwise, put the grabber back in - if(cartState == 3) { + if (cartState == 3) { setVarbit(player, EW2TankDoorStateVarbit, 4, true) // 4 door opened, hot bar inside // remove hot bar cart } else { setVarbit(player, EW2TankDoorStateVarbit, 1, true) // 1 door opened, grabber inside } } - 4 -> { // 4 door opened, hot bar inside - setVarbit(player, EW2TankDoorStateVarbit, 5, true) // 5 door opened, hot bar out - } - 5 -> { // 5 door opened, hot bar out - setVarbit(player, EW2TankDoorStateVarbit, 1, true) // 1 door opened, grabber inside - // add hot bar cart - } - 7 -> { // 7 door opened, cooled bar inside - setVarbit(player, EW2TankDoorStateVarbit, 8, true) // 8 door opened, cooled bar out - } - 8 -> { // 8 door opened, cooled bar out - setVarbit(player, EW2TankDoorStateVarbit, 1, true) // 1 door opened, grabber inside - // add cool bar cart - } + + // 4 door opened, hot bar inside + 4 -> setVarbit(player, EW2TankDoorStateVarbit, 5, true) // 5 door opened, hot bar out + + // 5 door opened, hot bar out. add hot bar cart + 5 -> setVarbit(player, EW2TankDoorStateVarbit, 1, true) // 1 door opened, grabber inside + + // 7 door opened, cooled bar inside + 7 -> setVarbit(player, EW2TankDoorStateVarbit, 8, true) // 8 door opened, cooled bar out + + // 8 door opened, cooled bar out. add cool bar cart + 8 -> setVarbit(player, EW2TankDoorStateVarbit, 1, true) // 1 door opened, grabber inside } syncTankDoor(player) // also syncs the cart inside there + return@queueScript true } return@on true } // west water valve operates the water inflow. requires pipe to be fixed. on(Scenery.WATER_VALVE_18646, IntType.SCENERY, "turn") { player, _ -> - - val valveState = getVarbit(player, EW2ValveWestVarbit) // water valve west. 0 = closed, 1 = open - val door = getVarbit(player, EW2TankDoorStatusVarbit) // 0 if door closed, 1 if open - - if(door == 1) { - sendMessage(player, "The valve won't turn, maybe the door needs to be closed first.") - return@on true - } - - if(getVarbit(player, EW2PipeVarbit) == 0) { - sendMessage(player, "You turn the valve but nothing happens, maybe the pipe needs to be repaired first.") - return@on true - } - - lockInteractions(player, 4) - animate(player, 4861) - - // if closed, open the valve - if(valveState == 0){ - setVarbit(player, EW2ValveWestVarbit, 1, true) - queueScript(player, 3, QueueStrength.SOFT) { - syncTank(player) - sendDialogue(player, "You open the valve.") - playAudio(player, Sounds.EW2_WATER_VALVE_3185) - return@queueScript true - } - // if open, close the valve - } else { - setVarbit(player, EW2ValveWestVarbit, 0, true) - queueScript(player, 3, QueueStrength.SOFT) { - syncTank(player) - sendDialogue(player, "You close the valve.") - playAudio(player, Sounds.EW2_WATER_VALVE_3185) - return@queueScript true - } - } + handleValve(player, EW2ValveWestVarbit) return@on true } // east water valve operates the drain on(Scenery.WATER_VALVE_18647, IntType.SCENERY, "turn") { player, _ -> - - val valveState = getVarbit(player, EW2ValveEastVarbit) // water valve west. 0 = closed, 1 = open - val door = getVarbit(player, EW2TankDoorStatusVarbit) // 0 if door closed, 1 if open - - if(door == 1) { - sendMessage(player, "The valve won't turn, maybe the door needs to be closed first.") - return@on true - } - - player.locks.lockInteractions(4) - animate(player, 4861) - - // if closed, open the valve - if(valveState == 0){ - setVarbit(player, EW2ValveEastVarbit, 1, true) - runTask(player, 3) { - syncTank(player) - sendDialogue(player, "You open the valve.") - playAudio(player, Sounds.EW2_WATER_VALVE_3185) - } - // if open, close the valve - } else { - setVarbit(player, EW2ValveEastVarbit, 0, true) - runTask(player, 3) { - syncTank(player) - sendDialogue(player, "You close the valve.") - playAudio(player, Sounds.EW2_WATER_VALVE_3185) - } - } + handleValve(player, EW2ValveEastVarbit) return@on true } @@ -1201,34 +1215,19 @@ class EW2Listeners : InteractionListener { // small cog crate on(Scenery.CRATE_18618, IntType.SCENERY, "search") { player, _ -> - if (!inInventory(player, Items.SMALL_COG_9726)) { - addItemOrDrop(player, Items.SMALL_COG_9726) - sendMessage(player, "You find a small cog.") - } else { - sendMessage(player, "It's empty.") - } + searchCogCrate(player, Items.SMALL_COG_9726, "small") return@on true } // medium cog crate on(Scenery.CRATES_18617, IntType.SCENERY, "search") { player, _ -> - if (!inInventory(player, Items.MEDIUM_COG_9725)) { - addItemOrDrop(player, Items.MEDIUM_COG_9725) - sendMessage(player, "You find a medium-sized cog.") - } else { - sendMessage(player, "It's empty.") - } + searchCogCrate(player, Items.MEDIUM_COG_9725, "medium-sized") return@on true } // large cog crate on(Scenery.CRATE_18616, IntType.SCENERY, "search") { player, _ -> - if (!inInventory(player, Items.LARGE_COG_9724)) { - addItemOrDrop(player, Items.LARGE_COG_9724) - sendMessage(player, "You find a big cog.") - } else { - sendMessage(player, "It's empty.") - } + searchCogCrate(player, Items.LARGE_COG_9724, "big") return@on true } @@ -1264,88 +1263,25 @@ class EW2Listeners : InteractionListener { // use cog with pin 1 // pin 1: 2655: val 0 is scenery 18665 (pin). val 1 is scenery 18673 (small cog). val 2 is scenery 18674 (med cog). val 3 is scenery 18675 (large cog). onUseWith(IntType.SCENERY, intArrayOf(Items.SMALL_COG_9726, Items.MEDIUM_COG_9725, Items.LARGE_COG_9724), Scenery.PIN_18665) { player, item, _ -> - when(item.id) { - Items.SMALL_COG_9726 -> { - setVarbit(player, EW2CogVarbit1, 1, true) - sendItemDialogue(player, item, "You put the small cog onto the shaft.") - animate(player, 537) - playAudio(player, Sounds.EW2_PLACE_COG_3178) - removeItem(player, item) - } - Items.MEDIUM_COG_9725 -> { - setVarbit(player, EW2CogVarbit1, 2, true) - sendItemDialogue(player, item, "You put the medium-sized cog onto the shaft.") - animate(player, 537) - playAudio(player, Sounds.EW2_PLACE_COG_3178) - removeItem(player, item) - } - Items.LARGE_COG_9724 -> { - setVarbit(player, EW2CogVarbit1, 3, true) - sendItemDialogue(player, item, "You put the big cog onto the shaft.") - animate(player, 537) - playAudio(player, Sounds.EW2_PLACE_COG_3178) - removeItem(player, item) - } - } - syncCogs(player) + placeCog(player, item as Item, EW2CogVarbit1) return@onUseWith true } // use cog with pin 2 // This listener brought to you by Player Name and his famous work with Arios RegionChunk fixing. Without that work it wouldn't have been possible to interact with this pin. // pin 2: 2656: val 0 is scenery 18664 (pin). val 1 is scenery 18667 (small cog). val 2 is scenery 18668 (med cog). val 3 is scenery 18669 (large cog) onUseWith(IntType.SCENERY, intArrayOf(Items.SMALL_COG_9726, Items.MEDIUM_COG_9725, Items.LARGE_COG_9724), Scenery.PIN_18664) { player, item, _ -> - when(item.id) { - Items.SMALL_COG_9726 -> { - setVarbit(player, EW2CogVarbit2, 1, true) - sendItemDialogue(player, item, "You put the small cog onto the shaft.") - animate(player, 537) - removeItem(player, item) - } - Items.MEDIUM_COG_9725 -> { - setVarbit(player, EW2CogVarbit2, 2, true) - sendItemDialogue(player, item, "You put the medium cog onto the shaft.") - animate(player, 537) - removeItem(player, item) - } - Items.LARGE_COG_9724 -> { - setVarbit(player, EW2CogVarbit2, 3, true) - sendItemDialogue(player, item, "You put the large cog onto the shaft.") - animate(player, 537) - removeItem(player, item) - } - } - syncCogs(player) + placeCog(player, item as Item, EW2CogVarbit2) return@onUseWith true } // use cog with pin 3 // pin 3: 2657: val 0 is scenery 18666 (pin). val 1 is scenery 18670 (small cog). val 2 is scenery 18671 (med cog). val 3 is scenery 18672 (large cog) onUseWith(IntType.SCENERY, intArrayOf(Items.SMALL_COG_9726, Items.MEDIUM_COG_9725, Items.LARGE_COG_9724), Scenery.PIN_18666) { player, item, _ -> - when(item.id) { - Items.SMALL_COG_9726 -> { - setVarbit(player, EW2CogVarbit3, 1, true) - sendItemDialogue(player, item, "You put the small cog onto the shaft.") - animate(player, 537) - removeItem(player, item) - } - Items.MEDIUM_COG_9725 -> { - setVarbit(player, EW2CogVarbit3, 2, true) - sendItemDialogue(player, item, "You put the medium cog onto the shaft.") - animate(player, 537) - removeItem(player, item) - } - Items.LARGE_COG_9724 -> { - setVarbit(player, EW2CogVarbit3, 3, true) - sendItemDialogue(player, item, "You put the large cog onto the shaft.") - animate(player, 537) - removeItem(player, item) - } - } - syncCogs(player) + placeCog(player, item as Item, EW2CogVarbit3) return@onUseWith true } - // fan lever. todo Authentically this check should allow the machine to start and the fan to either not start, start backwards, or start correctly, but I'm simplifying it for my sanity. + // fan lever. on(Scenery.AN_OLD_LEVER_18663, IntType.SCENERY, "pull") { player, _ -> animate(player, Animations.HUMAN_PULL_LEVER_4909) @@ -1359,19 +1295,29 @@ class EW2Listeners : InteractionListener { // pin 2 (2656) should have the med cog. // pin 3 (2656) should have the large cog. if (allCogs == 57) { - if(lever == 0) { + if (lever == 0) { + // rotate camera to view fan + val cam = PlayerCamera(player) + cam.setPosition(38, 28, 350) + // turn on - sendDialogue(player, "The machine rumbles and the fan starts to blow.") // Is authentically two messages of "The machine starts rumbling." and "The fan starts to blow.", but I am simplifying this check. playAudio(player, Sounds.EW2_MACHINE_RUMBLE_3175) playAudio(player, Sounds.EW2_FANBLADE_3174) setVarbit(player, EW2FanLeverVarbit, 1, true) syncFan(player) syncCogs(player) + openDialogue(player, object : DialogueLabeller() { + override fun addConversation() { + line("The machine starts rumbling.") + line("The fan starts to blow.") + } + }) + // check cart and cool if necessary. otherwise nothing should happen. if (cartpos == 3 && cartstate == 4) { setVarbit(player, EW2JigCartVarbit, 5, true) syncCart(player) - if(!getAttribute(player, attrWinded, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { + if (!getAttribute(player, attrWinded, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { setAttribute(player, attrWinded, true) } } @@ -1381,7 +1327,7 @@ class EW2Listeners : InteractionListener { syncFan(player) syncCogs(player) } - } else { // allCogs != 57, cogs are not correct + } else { // allCogs != 57, cogs are not correct todo Authentically there is an extra check where the fan can start backwards, but I'm simplifying it for my sanity. this would require reverse cog anims, too sendDialogue(player, "The machine starts up, but the fan does not blow.") playAudio(player, Sounds.EW2_MACHINE_RUMBLE_3175) } @@ -1393,11 +1339,11 @@ class EW2Listeners : InteractionListener { * ------------------------- */ // use bar with extractor gun - onUseWith(IntType.SCENERY, intArrayOf(Items.PRIMED_BAR_9727), Scenery.EXTRACTOR_GUN_18693) { player, item, _ -> - if(removeItem(player, item)) { + onUseWith(IntType.SCENERY, Items.PRIMED_BAR_9727, Scenery.EXTRACTOR_GUN_18693) { player, used, _ -> + if (removeItem(player, used as Item)) { setVarbit(player, EW2ExtractorGun, 1, true) sendItemDialogue(player, Items.PRIMED_BAR_9727, "You place the primed bar under the device.") - if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 8) { + if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 8) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 9) setVarbit(player, EW2StageVarbit, 9, true) } @@ -1405,81 +1351,37 @@ class EW2Listeners : InteractionListener { return@onUseWith true } - // take primed bar - on(intArrayOf(Scenery.EXTRACTOR_GUN_18694), IntType.SCENERY, "take-from") { player, _ -> + // take primed bar from the extractor gun + on(Scenery.EXTRACTOR_GUN_18694, IntType.SCENERY, "take-from") { player, _ -> addItemOrDrop(player, Items.PRIMED_BAR_9727) setVarbit(player, EW2ExtractorGun, 0, true) sendItemDialogue(player, Items.PRIMED_BAR_9727, "You take the primed bar.") return@on true } - // take mind bar - on(intArrayOf(Scenery.EXTRACTOR_GUN_18695), IntType.SCENERY, "take-from") { player, _ -> + // take mind bar from the extractor gun + on(Scenery.EXTRACTOR_GUN_18695, IntType.SCENERY, "take-from") { player, _ -> addItemOrDrop(player, Items.ELEMENTAL_MIND_BAR_9728) setVarbit(player, EW2ExtractorGun, 0, true) sendItemDialogue(player, Items.ELEMENTAL_MIND_BAR_9728, "You take the elemental mind bar.") return@on true } - // sit in chair - // todo the extractor gun needs a graphic - // todo per one of the sources the camera angle changes for this. - on(intArrayOf(Scenery.EXTRACTOR_HAT_18690), IntType.SCENERY, "operate") { player, _ -> - // check magic level - if(getDynLevel(player, Skills.MAGIC) < 20) { + // sit in the extractor chair + on(Scenery.EXTRACTOR_HAT_18690, IntType.SCENERY, "operate") { player, _ -> + // check magic level. if you use the extractor too much, it can drain you too far. + if (getDynLevel(player, Skills.MAGIC) < 20) { sendMessage(player, "You must have a Magic level of 20 and above to use the device.") return@on true } - // if bar is in place, charge it and drain magic - if(getVarbit(player, EW2ExtractorGun) == 1) { - runTask(player) { - forceMove( - player, - toInstance(player, Location.create(1961, 5150, 0)), - toInstance(player, Location.create(1962, 5150, 0)), - 0, - 0 - ) - visualize(player, EXTRACTOR_GOOD, EXTRACTOR_GOOD_GFX) - playAudio(player, Sounds.EW2_EXTRACTOR_OPERATION_3166) - sendGraphics(809, Location.create(1962, 5148, 0)) - setVarbit(player, EW2ExtractorGun, 2, true) - adjustLevel(player, Skills.MAGIC, -20) - sendMessage(player, "You feel magically weakened.") - if(getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 9) { - setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 10) - setVarbit(player, EW2StageVarbit, 10, true) - } - } - forceMove( - player, - toInstance(player,Location.create(1962, 5150, 0)), - toInstance(player,Location.create(1961, 5150, 0)), - 200, - 2 - ) - // if bar is not in place (or if it's already a primed bar), hit player + + // start the cutscene + if (getVarbit(player, EW2ExtractorGun) == 1) { + // if bar is in place, charge it and drain magic + EW2Cutscene(player, true).start() } else { - runTask(player) { // 50 client cycles = 1 second - forceMove( - player, - toInstance(player,Location.create(1961, 5150, 0)), - toInstance(player,Location.create(1962, 5150, 0)), - 0, - 0 - ) - visualize(player, EXTRACTOR_BAD, EXTRACTOR_BAD_GFX) - playAudio(player, Sounds.EW2_EXTRACTOR_ZAP_3173) - impact(player, 4) - sendMessage(player, "The extractor hat shocks you!.") - } - forceMove( - player, - toInstance(player,Location.create(1962, 5150, 0)), - toInstance(player,Location.create(1961, 5150, 0)), - 350, - 2 - ) + // if bar is not in place (or if it's already a primed bar), hit player + EW2Cutscene(player, false).start() } return@on true } @@ -1488,28 +1390,36 @@ class EW2Listeners : InteractionListener { // fixes to get right interact locations override fun defineDestinationOverrides() { setDest(IntType.SCENERY, intArrayOf(Scenery.OLD_CRANE_18636), "use") { entity, _ -> - return@setDest toInstance(entity as Player,Location.create(1954, 5148, 2)) + return@setDest toInstance(entity as Player, Location.create(1954, 5148, 2)) } setDest(IntType.NPC, intArrayOf(NPCs.JIG_CART_4913), "use") { entity, _ -> - return@setDest toInstance(entity as Player,Location.create(1954, 5148, 2)) + return@setDest toInstance(entity as Player, Location.create(1954, 5148, 2)) } - setDest(IntType.NPC, intArrayOf(NPCs.JIG_CART_4914, NPCs.JIG_CART_4915, NPCs.JIG_CART_4916, NPCs.JIG_CART_4917, NPCs.JIG_CART_4918), "take-from") { entity, _ -> - return@setDest toInstance(entity as Player,Location.create(1954, 5148, 2)) + setDest( + IntType.NPC, intArrayOf( + NPCs.JIG_CART_4914, NPCs.JIG_CART_4915, + NPCs.JIG_CART_4916, NPCs.JIG_CART_4917, + NPCs.JIG_CART_4918 + ), "take-from" + ) { entity, _ -> + return@setDest toInstance(entity as Player, Location.create(1954, 5148, 2)) } setDest(IntType.SCENERY, intArrayOf(Scenery.PIPING_18650), "use") { entity, _ -> - return@setDest toInstance(entity as Player,Location.create(1953, 5167, 3)) + return@setDest toInstance(entity as Player, Location.create(1953, 5167, 3)) } // the double stacked pins and the cogs on them. - setDest(IntType.SCENERY, intArrayOf( - Scenery.PIN_18664, Scenery.PIN_18665, - Scenery.COG_18673, Scenery.COG_18674, Scenery.COG_18675, - Scenery.COG_18667, Scenery.COG_18668, Scenery.COG_18669 - ), "use","take") { entity, _ -> - return@setDest toInstance(entity as Player,Location.create(1958, 5157, 2)) + setDest( + IntType.SCENERY, intArrayOf( + Scenery.PIN_18664, Scenery.PIN_18665, + Scenery.COG_18673, Scenery.COG_18674, Scenery.COG_18675, + Scenery.COG_18667, Scenery.COG_18668, Scenery.COG_18669 + ), "use", "take" + ) { entity, _ -> + return@setDest toInstance(entity as Player, Location.create(1958, 5157, 2)) } } } \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt index 59995acee..cc3ed2d86 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt @@ -8,6 +8,7 @@ import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Compa import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTank import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTankDoor import core.api.* +import core.game.interaction.QueueStrength import core.game.node.entity.Entity import core.game.node.entity.player.Player import core.game.world.map.Location @@ -20,6 +21,10 @@ import core.game.world.map.zone.ZoneBorders class EW2Session(val player: Player? = null) : MapArea { + companion object { + const val EW2_REGION = 7760 + } + lateinit var region: DynamicRegion lateinit var base: Location @@ -32,12 +37,11 @@ class EW2Session(val player: Player? = null) : MapArea { fun start() { val p = player ?: return - region = DynamicRegion.create(7760) + region = DynamicRegion.create(EW2_REGION) base = region.baseLocation zone.register(getRegionBorders(region.id)) setAttribute(p, "ew2-session", this) - teleport(p, base.transform(35, 35, 2)) // logout listener returns player to the EW1 workshop registerLogoutListener(p, "ew2-logout") { @@ -55,6 +59,12 @@ class EW2Session(val player: Player? = null) : MapArea { syncTankDoor(p) syncFan(p) syncCogs(p) + + // teleport in after the workshop is ready + queueScript(player, 1, QueueStrength.SOFT) { + teleport(p, base.transform(35, 35, 2)) + return@queueScript true + } } // clean up the instanced area diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt index 35c0fc0cb..919b105f8 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt @@ -195,7 +195,7 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Sta line++ } - // stage 8, went through the mind door + // stage 8, went through the mind door. I skip using this due to lack of source /* if (stage >= 8) { line(player, "", line++, stage > 8) @@ -238,11 +238,15 @@ class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2Sta // these varps wipes all the varbits setVarp(player, EW2Varp1, 0, true) setVarp(player, EW2Varp2, 0, true) + + // remove attributes removeAttribute(player, attrJigged) removeAttribute(player, attrDipped) removeAttribute(player, attrFlattened) removeAttribute(player, attrWatered) removeAttribute(player, attrWinded) + + // sync the quest tab player.getQuestRepository().syncronizeTab(player) super.reset(player) } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt index ade19851b..ac15f71a7 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt @@ -241,21 +241,27 @@ class JunctionBoxInterface : InterfaceListener { 1 -> when (endBtn) { // Top Left b4 -> 0; b5 -> 6; b6 -> 7; b2 -> 8; b3 -> 9; else -> -1 } + 2 -> when (endBtn) { // Top Mid b5 -> 1; b4 -> 10; b6 -> 11; b1 -> 8; b3 -> 12; else -> -1 } + 3 -> when (endBtn) { // Top Right b6 -> 2; b4 -> 13; b5 -> 14; b1 -> 9; b2 -> 12; else -> -1 } + 4 -> when (endBtn) { // Bottom Left b1 -> 0; b5 -> 3; b6 -> 4; b2 -> 10; b3 -> 13; else -> -1 } + 5 -> when (endBtn) { // Bottom Mid b2 -> 1; b4 -> 3; b6 -> 5; b1 -> 6; b3 -> 14; else -> -1 } + 6 -> when (endBtn) { // Bottom Right b3 -> 2; b4 -> 4; b5 -> 5; b1 -> 7; b2 -> 11; else -> -1 } + else -> -1 } } From ed225eeacb0b0ea424cfab220e703e360726dd1f Mon Sep 17 00:00:00 2001 From: aidan Date: Sat, 11 Jul 2026 11:42:07 -0500 Subject: [PATCH 37/45] fix merge conflict --- Server/data/configs/object_configs.json | 2 +- .../quest/elementalworkshop2/EW2Listeners.kt | 2 + .../command/sets/AnimationCommandSet.kt | 45 +++++++++++++++- .../system/command/sets/SpawnCommandSet.kt | 52 ------------------- 4 files changed, 47 insertions(+), 54 deletions(-) diff --git a/Server/data/configs/object_configs.json b/Server/data/configs/object_configs.json index 5ab9f7575..6f7ff2f90 100644 --- a/Server/data/configs/object_configs.json +++ b/Server/data/configs/object_configs.json @@ -7377,7 +7377,7 @@ }, { "examine": "Seems to connect this level to the one above!", - "ids": "18705" + "ids": "18705,18598" }, { "examine": "Is that a shopping trolley?", diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 66b3d6505..d2a6ef385 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -615,6 +615,8 @@ class EW2Listeners : InteractionListener { if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 0) { setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 1) setVarbit(player, EW2StageVarbit, 1, true) + // the crane starts in this position per source + setVarbit(player, EW2CranePosVarbit, 3, true) } } else { sendMessage(player, "You search the books...") diff --git a/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt b/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt index d06258333..45e00387d 100644 --- a/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt @@ -1,8 +1,11 @@ package core.game.system.command.sets +import core.api.addScenery import core.api.animate +import core.api.animateScenery import core.api.delayScript import core.api.queueScript +import core.api.sendMessage import core.api.stopExecuting import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC @@ -103,7 +106,6 @@ class AnimationCommandSet : CommandSet(Privilege.ADMIN) { } } - /** * Reset the player's render animation to default */ @@ -113,5 +115,46 @@ class AnimationCommandSet : CommandSet(Privilege.ADMIN) { player.appearance.setAnimations() player.appearance.sync() } + + /** + * Spawn object with given ID at the player's location and animate it + */ + define("objanim", usage = "::objanim [objectId] [animFrom] [(opt)animTo] [(opt)delay]", description = "Spawns an object and animates it with the provided anim ID. If a second ID is provided, it iterates through the anims.") { player, args -> + if (args.size < 3) { + sendMessage(player, "Usage: ::objanim [objectId] [animFrom] [(opt)animTo] [(opt)delay]") + return@define + } + + val objectId = args[1].toIntOrNull() + val animFrom = args[2].toIntOrNull() + var animTo = args.getOrNull(3)?.toIntOrNull() + val delay = args.getOrNull(4)?.toIntOrNull() ?: 3 + + if (objectId == null || animFrom == null) { + sendMessage(player, "Invalid arguments. Please use numbers.") + return@define + } + + // spawn the object + val obj = core.game.node.scenery.Scenery(objectId, player.location) + addScenery(obj) + + // set the range if it's just a single anim + if (animTo == null) animTo = animFrom + + // loop through the anims if it's a range + sendMessage(player, "Spawned object $objectId, playing anims $animFrom to $animTo.") + queueScript(player, 1, QueueStrength.STRONG) { stage: Int -> + val currentAnimId = animFrom + stage + animateScenery(player, obj, currentAnimId) + sendMessage(player, "Playing object animation $currentAnimId") + if (currentAnimId >= animTo) { + return@queueScript stopExecuting(player) + } + return@queueScript delayScript(player, delay) + } + + return@define + } } } diff --git a/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt b/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt index 987cc7b84..468bdaf96 100644 --- a/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt @@ -1,14 +1,8 @@ package core.game.system.command.sets -import core.api.addScenery -import core.api.animateScenery -import core.api.delayScript import core.api.log -import core.api.queueScript import core.api.sendMessage -import core.api.stopExecuting import core.cache.Cache -import core.game.interaction.QueueStrength import core.game.node.scenery.Scenery import core.game.node.scenery.SceneryBuilder import core.game.node.entity.npc.NPC @@ -127,52 +121,6 @@ class SpawnCommandSet : CommandSet(Privilege.ADMIN){ log(this::class.java, Log.FINE, "object = $`object`") } - /** - * Spawn object with given ID at the player's location and animate it - */ - define("objanim", usage = "::objanim [objectId] [animFrom] [(opt)animTo] [(opt)delay]", description = "Spawns an object and animates it with the provided anim ID. If a second ID is provided, it iterates through the anims.") { player, args -> - if (args.size < 3) { - sendMessage(player, "Usage: ::objanim [objectId] [animFrom] [(opt)animTo] [(opt)delay]") - return@define - } - - val objectId = args[1].toIntOrNull() - val animFrom = args[2].toIntOrNull() - - val animTo = args.getOrNull(3)?.toIntOrNull() - val delay = args.getOrNull(4)?.toIntOrNull() ?: 3 - - if (objectId == null || animFrom == null) { - sendMessage(player, "Invalid arguments. Please use numbers.") - return@define - } - - // spawn the object - val obj = core.game.node.scenery.Scenery(objectId, player.location) - addScenery(obj) - - // animate a single anim - if (animTo == null) { - animateScenery(player, obj, animFrom) - sendMessage(player, "Spawned object $objectId and playing animation $animFrom.") - return@define - } - - // loop through the anims if it's a range - sendMessage(player, "Spawned object $objectId, playing anims $animFrom to $animTo.") - queueScript(player, 1, QueueStrength.STRONG) { stage: Int -> - val currentAnimId = animFrom + stage - animateScenery(player, obj, currentAnimId) - sendMessage(player, "Playing object animation $currentAnimId") - if (currentAnimId >= animTo) { - return@queueScript stopExecuting(player) - } - return@queueScript delayScript(player, delay) - } - - return@define - } - define("objectgrid", usage = "::objectgrid start-id end-id type rotation", description = "Spawns a 10-wide grid cycling through the given object id range.") { player, args -> if(args!!.size != 5) { reject(player, "Usage: objectgrid beginId endId type rotation") From d935f4ea724aa4000b40e297c8ac6d3ef0cd88fe Mon Sep 17 00:00:00 2001 From: aidan Date: Sun, 12 Jul 2026 08:40:25 -0500 Subject: [PATCH 38/45] extractor gun stacked scenery wtf is up --- .../seers/quest/elementalworkshop2/EW2Listeners.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index d2a6ef385..39df15522 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -79,6 +79,9 @@ class EW2Listeners : InteractionListener { // interface const val LEVER_SCHEM = 466 + // scenery + const val EXTRACTOR_WRAPPER = 18725 + // graphics const val EXTRACTOR_GOOD_GFX = Graphics.EXTRACTOR_HAT_CHAIR_807 const val EXTRACTOR_BAD_GFX = Graphics.IMPROPERLY_EXTRACTOR_HAT_CHAIR_808 @@ -1341,7 +1344,7 @@ class EW2Listeners : InteractionListener { * ------------------------- */ // use bar with extractor gun - onUseWith(IntType.SCENERY, Items.PRIMED_BAR_9727, Scenery.EXTRACTOR_GUN_18693) { player, used, _ -> + onUseWith(IntType.SCENERY, Items.PRIMED_BAR_9727, EXTRACTOR_WRAPPER, Scenery.EXTRACTOR_GUN_18693) { player, used, _ -> if (removeItem(player, used as Item)) { setVarbit(player, EW2ExtractorGun, 1, true) sendItemDialogue(player, Items.PRIMED_BAR_9727, "You place the primed bar under the device.") @@ -1354,7 +1357,7 @@ class EW2Listeners : InteractionListener { } // take primed bar from the extractor gun - on(Scenery.EXTRACTOR_GUN_18694, IntType.SCENERY, "take-from") { player, _ -> + on(intArrayOf(EXTRACTOR_WRAPPER, Scenery.EXTRACTOR_GUN_18694), IntType.SCENERY, "take-from") { player, _ -> addItemOrDrop(player, Items.PRIMED_BAR_9727) setVarbit(player, EW2ExtractorGun, 0, true) sendItemDialogue(player, Items.PRIMED_BAR_9727, "You take the primed bar.") @@ -1362,7 +1365,7 @@ class EW2Listeners : InteractionListener { } // take mind bar from the extractor gun - on(Scenery.EXTRACTOR_GUN_18695, IntType.SCENERY, "take-from") { player, _ -> + on(intArrayOf(EXTRACTOR_WRAPPER, Scenery.EXTRACTOR_GUN_18695), IntType.SCENERY, "take-from") { player, _ -> addItemOrDrop(player, Items.ELEMENTAL_MIND_BAR_9728) setVarbit(player, EW2ExtractorGun, 0, true) sendItemDialogue(player, Items.ELEMENTAL_MIND_BAR_9728, "You take the elemental mind bar.") From 5ecb0e4fb4085fbf30b17c5964321a4f2e892d78 Mon Sep 17 00:00:00 2001 From: aidan Date: Sun, 12 Jul 2026 12:09:05 -0500 Subject: [PATCH 39/45] the extractor chair area is no longer instanced --- .../quest/elementalworkshop2/EW2Listeners.kt | 23 +++++++++++-------- .../quest/elementalworkshop2/EW2Session.kt | 9 +++++--- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 39df15522..6957766dd 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -691,11 +691,8 @@ class EW2Listeners : InteractionListener { on(Scenery.HATCH_18596, IntType.SCENERY, "climb-down") { player, _ -> sendMessage(player, "You climb down the stairs.") - // this is the non-instanced area - //teleport(player, location(1955, 5155, 2)) - // start the instanced workshop session - EW2Session(player).start() + EW2Session(player).start(Location.create(1955, 5155, 2)) // advance quest stage if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 4) { @@ -707,6 +704,7 @@ class EW2Listeners : InteractionListener { // stairs back up to EW1 on(Scenery.STAIRS_18598, IntType.SCENERY, "climb-up") { player, _ -> + // non-instanced teleport(player, location(2720, 9892, 0)) sendMessage(player, "You climb up the stairs.") return@on true @@ -714,15 +712,19 @@ class EW2Listeners : InteractionListener { // stairs to EW2 lower level on(Scenery.STAIRWELL_18597, IntType.SCENERY, "climb-down") { player, _ -> - teleport(player, toInstance(player,location(1948, 5158, 0))) + // non-instanced + teleport(player, location(1948, 5158, 0)) sendMessage(player, "You climb down the stairs.") return@on true } // stairs from EW2 lower level back up to EW2 main level on(Scenery.STAIRS_18599, IntType.SCENERY, "climb-up") { player, _ -> - teleport(player, toInstance(player,location(1948, 5157, 2))) sendMessage(player, "You climb up the stairs.") + + // start the instanced workshop session + EW2Session(player).start(Location.create(1948, 5157, 2)) + return@on true } @@ -1332,7 +1334,8 @@ class EW2Listeners : InteractionListener { syncFan(player) syncCogs(player) } - } else { // allCogs != 57, cogs are not correct todo Authentically there is an extra check where the fan can start backwards, but I'm simplifying it for my sanity. this would require reverse cog anims, too + } else { // allCogs != 57, cogs are not correct + // todo Authentically there is an extra check where the fan can start backwards, but I'm simplifying it for my sanity. this would require reverse cog anims, too sendDialogue(player, "The machine starts up, but the fan does not blow.") playAudio(player, Sounds.EW2_MACHINE_RUMBLE_3175) } @@ -1344,7 +1347,7 @@ class EW2Listeners : InteractionListener { * ------------------------- */ // use bar with extractor gun - onUseWith(IntType.SCENERY, Items.PRIMED_BAR_9727, EXTRACTOR_WRAPPER, Scenery.EXTRACTOR_GUN_18693) { player, used, _ -> + onUseWith(IntType.SCENERY, Items.PRIMED_BAR_9727, Scenery.EXTRACTOR_GUN_18693) { player, used, _ -> if (removeItem(player, used as Item)) { setVarbit(player, EW2ExtractorGun, 1, true) sendItemDialogue(player, Items.PRIMED_BAR_9727, "You place the primed bar under the device.") @@ -1357,7 +1360,7 @@ class EW2Listeners : InteractionListener { } // take primed bar from the extractor gun - on(intArrayOf(EXTRACTOR_WRAPPER, Scenery.EXTRACTOR_GUN_18694), IntType.SCENERY, "take-from") { player, _ -> + on(intArrayOf(Scenery.EXTRACTOR_GUN_18694), IntType.SCENERY, "take-from") { player, _ -> addItemOrDrop(player, Items.PRIMED_BAR_9727) setVarbit(player, EW2ExtractorGun, 0, true) sendItemDialogue(player, Items.PRIMED_BAR_9727, "You take the primed bar.") @@ -1365,7 +1368,7 @@ class EW2Listeners : InteractionListener { } // take mind bar from the extractor gun - on(intArrayOf(EXTRACTOR_WRAPPER, Scenery.EXTRACTOR_GUN_18695), IntType.SCENERY, "take-from") { player, _ -> + on(intArrayOf(Scenery.EXTRACTOR_GUN_18695), IntType.SCENERY, "take-from") { player, _ -> addItemOrDrop(player, Items.ELEMENTAL_MIND_BAR_9728) setVarbit(player, EW2ExtractorGun, 0, true) sendItemDialogue(player, Items.ELEMENTAL_MIND_BAR_9728, "You take the elemental mind bar.") diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt index cc3ed2d86..74edf5068 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt @@ -7,6 +7,7 @@ import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Compa import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncSteamPress import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTank import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.syncTankDoor +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.toInstance import core.api.* import core.game.interaction.QueueStrength import core.game.node.entity.Entity @@ -33,8 +34,8 @@ class EW2Session(val player: Player? = null) : MapArea { return emptyArray() } - // start() is triggered by the listener for Scenery.HATCH_18596 - fun start() { + // start() is triggered by the listener for Scenery.HATCH_18596 and Scenery.STAIRS_18599 + fun start(loc: Location) { val p = player ?: return region = DynamicRegion.create(EW2_REGION) @@ -60,9 +61,11 @@ class EW2Session(val player: Player? = null) : MapArea { syncFan(p) syncCogs(p) + val startLoc = toInstance(player, loc) + // teleport in after the workshop is ready queueScript(player, 1, QueueStrength.SOFT) { - teleport(p, base.transform(35, 35, 2)) + teleport(p, startLoc) return@queueScript true } } From c4445920f1ea198aa0974c3b59d40ddab9f9550d Mon Sep 17 00:00:00 2001 From: aidan Date: Sun, 12 Jul 2026 17:11:55 -0500 Subject: [PATCH 40/45] fix cutscene endpoint --- .../kandarin/seers/quest/elementalworkshop2/EW2Cutscene.kt | 4 ++-- .../kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt | 4 ---- .../kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Cutscene.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Cutscene.kt index fd2b5d917..683dc8b6d 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Cutscene.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Cutscene.kt @@ -6,7 +6,6 @@ import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Compa import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.EXTRACTOR_GOOD import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.EXTRACTOR_GOOD_GFX import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.EXTR_GUN -import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.toInstance import content.region.kandarin.seers.quest.elementalworkshop2.EW2Session.Companion.EW2_REGION import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2ExtractorGun import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2StageVarbit @@ -24,7 +23,8 @@ import org.rs09.consts.Sounds class EW2Cutscene(player: Player, good: Boolean) : Cutscene(player) { override fun setup() { - setExit(toInstance(player, Location.create(1961, 5150, 0))) + // returns to the non-instanced region, since lower portion of the workshop isn't instanced + setExit(Location.create(1961, 5150, 0)) loadRegion(EW2_REGION) } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 6957766dd..8f64df01f 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -35,12 +35,8 @@ import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills import core.game.node.item.Item -import core.game.world.map.Direction import core.game.world.map.Location import core.game.world.update.flag.context.Animation -import core.net.packet.PacketRepository -import core.net.packet.context.CameraContext -import core.net.packet.out.CameraViewPacket import org.rs09.consts.* class EW2Listeners : InteractionListener { diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt index 22c3bd1cc..6d6c45c0d 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt @@ -4,7 +4,6 @@ import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Compa import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPCBehavior import core.game.world.map.path.ClipMaskSupplier -import org.rs09.consts.NPCs /** * Jig cart in Elemental Workshop II From 04fffd56684c7e806629935263e93b4089c5c92c Mon Sep 17 00:00:00 2001 From: aidan Date: Sun, 12 Jul 2026 19:17:52 -0500 Subject: [PATCH 41/45] fix dest override for the other damn pin --- .../quest/elementalworkshop2/EW2Listeners.kt | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 8f64df01f..3ae7324c0 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -75,9 +75,6 @@ class EW2Listeners : InteractionListener { // interface const val LEVER_SCHEM = 466 - // scenery - const val EXTRACTOR_WRAPPER = 18725 - // graphics const val EXTRACTOR_GOOD_GFX = Graphics.EXTRACTOR_HAT_CHAIR_807 const val EXTRACTOR_BAD_GFX = Graphics.IMPROPERLY_EXTRACTOR_HAT_CHAIR_808 @@ -940,7 +937,14 @@ class EW2Listeners : InteractionListener { if (removeItem(player, used)) { sendItemDialogue(player, used, "You replace the broken claw.") setVarbit(player, EW2CraneStateVarbit, 1, true) - syncCrane(player) + playAudio(player, Sounds.EW2_PLACE_PIPE_3179) // this sound was confirmed via youtube source + + // update the crane state + val craneLoc = toInstance(player, Location.create(1952, 5143, 2)) + val currentObject = getScenery(craneLoc) + if (currentObject != null) { + replaceScenery(currentObject, Scenery.OLD_CRANE_18624, -1) + } } return@onUseWith true } @@ -1415,7 +1419,7 @@ class EW2Listeners : InteractionListener { return@setDest toInstance(entity as Player, Location.create(1953, 5167, 3)) } - // the double stacked pins and the cogs on them. + // the double stacked pins and the cogs on them setDest( IntType.SCENERY, intArrayOf( Scenery.PIN_18664, Scenery.PIN_18665, @@ -1425,5 +1429,15 @@ class EW2Listeners : InteractionListener { ) { entity, _ -> return@setDest toInstance(entity as Player, Location.create(1958, 5157, 2)) } + + // the single pin and its cogs + setDest( + IntType.SCENERY, intArrayOf( + Scenery.PIN_18666, + Scenery.COG_18670, Scenery.COG_18671, Scenery.COG_18672, + ), "use", "take" + ) { entity, _ -> + return@setDest toInstance(entity as Player, Location.create(1958, 5156, 2)) + } } } \ No newline at end of file From ab35b7142005c1542ac720be8f7a6f306541ad7b Mon Sep 17 00:00:00 2001 From: aidan Date: Sun, 12 Jul 2026 20:24:35 -0500 Subject: [PATCH 42/45] extractor gun setDest and cutscene fix --- .../quest/elementalworkshop2/EW2Cutscene.kt | 9 ++++++--- .../quest/elementalworkshop2/EW2Listeners.kt | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Cutscene.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Cutscene.kt index 683dc8b6d..6f421ec1d 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Cutscene.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Cutscene.kt @@ -6,6 +6,7 @@ import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Compa import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.EXTRACTOR_GOOD import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.EXTRACTOR_GOOD_GFX import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.EXTR_GUN +import content.region.kandarin.seers.quest.elementalworkshop2.EW2Listeners.Companion.toInstance import content.region.kandarin.seers.quest.elementalworkshop2.EW2Session.Companion.EW2_REGION import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2ExtractorGun import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2.Companion.EW2StageVarbit @@ -23,8 +24,7 @@ import org.rs09.consts.Sounds class EW2Cutscene(player: Player, good: Boolean) : Cutscene(player) { override fun setup() { - // returns to the non-instanced region, since lower portion of the workshop isn't instanced - setExit(Location.create(1961, 5150, 0)) + setExit(toInstance(player, Location.create(1961, 5150, 0))) loadRegion(EW2_REGION) } @@ -82,7 +82,10 @@ class EW2Cutscene(player: Player, good: Boolean) : Cutscene(player) { } 4 -> { - endWithoutFade { } + endWithoutFade { + EW2Session(player).start(Location.create(1961, 5150, 0)) + setExit(toInstance(player, Location.create(1961, 5150, 0))) + } } } } diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index 3ae7324c0..da724c0a1 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -705,8 +705,7 @@ class EW2Listeners : InteractionListener { // stairs to EW2 lower level on(Scenery.STAIRWELL_18597, IntType.SCENERY, "climb-down") { player, _ -> - // non-instanced - teleport(player, location(1948, 5158, 0)) + teleport(player, toInstance(player, location(1948, 5158, 0))) sendMessage(player, "You climb down the stairs.") return@on true } @@ -714,10 +713,7 @@ class EW2Listeners : InteractionListener { // stairs from EW2 lower level back up to EW2 main level on(Scenery.STAIRS_18599, IntType.SCENERY, "climb-up") { player, _ -> sendMessage(player, "You climb up the stairs.") - - // start the instanced workshop session - EW2Session(player).start(Location.create(1948, 5157, 2)) - + teleport(player, toInstance(player, location(1948, 5157, 2))) return@on true } @@ -1439,5 +1435,15 @@ class EW2Listeners : InteractionListener { ) { entity, _ -> return@setDest toInstance(entity as Player, Location.create(1958, 5156, 2)) } + + // the extractor gun + setDest( + IntType.SCENERY, intArrayOf( + Scenery.EXTRACTOR_GUN_18693, Scenery.EXTRACTOR_GUN_18694, + Scenery.EXTRACTOR_GUN_18695, Scenery.EXTRACTOR_GUN_18696 + ), "use", "take-from" + ) { entity, _ -> + return@setDest toInstance(entity as Player, Location.create(1961, 5148, 0)) + } } } \ No newline at end of file From 3cc9e8e8c86355412830025a3a5816dd5a2a7b1e Mon Sep 17 00:00:00 2001 From: aidan Date: Sun, 26 Jul 2026 10:42:50 -0500 Subject: [PATCH 43/45] handle the instanced stairs --- .../quest/elementalworkshop2/EW2Listeners.kt | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index da724c0a1..ba5831e86 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -28,6 +28,7 @@ import content.region.kandarin.seers.quest.elementalworkshop2.ElementalWorkshop2 import core.api.* import core.api.utils.PlayerCamera import core.game.dialogue.DialogueLabeller +import core.game.global.action.ClimbActionHandler import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.interaction.QueueStrength @@ -598,11 +599,10 @@ class EW2Listeners : InteractionListener { // bookcase at digsite where you start quest on(Scenery.BOOKCASE_17382, IntType.SCENERY, "search") { player, _ -> - if (isQuestComplete(player, Quests.ELEMENTAL_WORKSHOP_I) && - !inInventory(player, Items.BEATEN_BOOK_9717) && - !inBank(player, Items.BEATEN_BOOK_9717) && - hasLevelStat(player, Skills.MAGIC, 20) && - hasLevelStat(player, Skills.SMITHING, 30) + if (isQuestComplete(player, Quests.ELEMENTAL_WORKSHOP_I) + && !hasAnItem(player, Items.BEATEN_BOOK_9717).exists() + && hasLevelStat(player, Skills.MAGIC, 20) + && hasLevelStat(player, Skills.SMITHING, 30) ) { sendItemDialogue(player, Items.BEATEN_BOOK_9717, "You find an old-looking book.") addItemOrDrop(player, Items.BEATEN_BOOK_9717) @@ -723,6 +723,39 @@ class EW2Listeners : InteractionListener { return@on true } + // stairs in the main level of the workshop + on(intArrayOf(Scenery.DOOR_18702, Scenery.STAIRS_18610, Scenery.STAIRS_18611), IntType.SCENERY, "climb-up", "climb-down") { player, node -> + // locations + val playerLoc = toInstance(player, player.location) + val northTop = toInstance(player, Location.create(1959, 5159, 3)) + val northBottom = toInstance(player, Location.create(1957, 5159, 2)) + val southTop = toInstance(player, Location.create(1948, 5149, 3)) + val southBottom = toInstance(player, Location.create(1950, 5149, 2)) + + // handlers + val climb = Animation(Animations.HUMAN_CLIMB_STAIRS_828) + val up = "You climb up the stairs." + val down = "You climb down the stairs." + when(node.id) { + // up + Scenery.STAIRS_18610 -> { + when(playerLoc) { + northBottom -> ClimbActionHandler.climb(player, climb, northTop, up) + southBottom -> ClimbActionHandler.climb(player, climb, southTop, up) + } + } + + // down + Scenery.STAIRS_18611 -> { + when(playerLoc) { + northTop -> ClimbActionHandler.climb(player, climb, northBottom, down) + southTop -> ClimbActionHandler.climb(player, climb, southBottom, down) + } + } + } + return@on true + } + // schematic crate on(Scenery.SCHEMATIC_CRATE_18711, IntType.SCENERY, "take-from") { player, _ -> sendDialogueOptions(player, "There are two schematics here...", "Crane schematic", "Lever schematic") From c578434b81aa96c327ed5cff18bf6bed3fd865c5 Mon Sep 17 00:00:00 2001 From: aidan Date: Sun, 26 Jul 2026 12:12:18 -0500 Subject: [PATCH 44/45] actually fix the ladders --- .../quest/elementalworkshop2/EW2Listeners.kt | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt index ba5831e86..5abd816d9 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -724,35 +724,26 @@ class EW2Listeners : InteractionListener { } // stairs in the main level of the workshop - on(intArrayOf(Scenery.DOOR_18702, Scenery.STAIRS_18610, Scenery.STAIRS_18611), IntType.SCENERY, "climb-up", "climb-down") { player, node -> + on(intArrayOf(Scenery.DOOR_18702, Scenery.STAIRS_18610, Scenery.STAIRS_18611), IntType.SCENERY, "climb-up", "climb-down") { player, _ -> // locations - val playerLoc = toInstance(player, player.location) val northTop = toInstance(player, Location.create(1959, 5159, 3)) val northBottom = toInstance(player, Location.create(1957, 5159, 2)) val southTop = toInstance(player, Location.create(1948, 5149, 3)) val southBottom = toInstance(player, Location.create(1950, 5149, 2)) - // handlers + // handler options val climb = Animation(Animations.HUMAN_CLIMB_STAIRS_828) val up = "You climb up the stairs." val down = "You climb down the stairs." - when(node.id) { - // up - Scenery.STAIRS_18610 -> { - when(playerLoc) { - northBottom -> ClimbActionHandler.climb(player, climb, northTop, up) - southBottom -> ClimbActionHandler.climb(player, climb, southTop, up) - } - } - // down - Scenery.STAIRS_18611 -> { - when(playerLoc) { - northTop -> ClimbActionHandler.climb(player, climb, northBottom, down) - southTop -> ClimbActionHandler.climb(player, climb, southBottom, down) - } - } + // get destination + when(player.location) { + northBottom -> ClimbActionHandler.climb(player, climb, northTop, up) + southBottom -> ClimbActionHandler.climb(player, climb, southTop, up) + northTop -> ClimbActionHandler.climb(player, climb, northBottom, down) + southTop -> ClimbActionHandler.climb(player, climb, southBottom, down) } + return@on true } From 0290d8684ce4e0b8a921e8a7c188f7e8383c0bc4 Mon Sep 17 00:00:00 2001 From: aidan Date: Sat, 1 Aug 2026 13:04:28 -0500 Subject: [PATCH 45/45] trade deal to send objanim to bishop --- .../command/sets/AnimationCommandSet.kt | 45 +------------------ 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt b/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt index 45e00387d..d06258333 100644 --- a/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt @@ -1,11 +1,8 @@ package core.game.system.command.sets -import core.api.addScenery import core.api.animate -import core.api.animateScenery import core.api.delayScript import core.api.queueScript -import core.api.sendMessage import core.api.stopExecuting import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC @@ -106,6 +103,7 @@ class AnimationCommandSet : CommandSet(Privilege.ADMIN) { } } + /** * Reset the player's render animation to default */ @@ -115,46 +113,5 @@ class AnimationCommandSet : CommandSet(Privilege.ADMIN) { player.appearance.setAnimations() player.appearance.sync() } - - /** - * Spawn object with given ID at the player's location and animate it - */ - define("objanim", usage = "::objanim [objectId] [animFrom] [(opt)animTo] [(opt)delay]", description = "Spawns an object and animates it with the provided anim ID. If a second ID is provided, it iterates through the anims.") { player, args -> - if (args.size < 3) { - sendMessage(player, "Usage: ::objanim [objectId] [animFrom] [(opt)animTo] [(opt)delay]") - return@define - } - - val objectId = args[1].toIntOrNull() - val animFrom = args[2].toIntOrNull() - var animTo = args.getOrNull(3)?.toIntOrNull() - val delay = args.getOrNull(4)?.toIntOrNull() ?: 3 - - if (objectId == null || animFrom == null) { - sendMessage(player, "Invalid arguments. Please use numbers.") - return@define - } - - // spawn the object - val obj = core.game.node.scenery.Scenery(objectId, player.location) - addScenery(obj) - - // set the range if it's just a single anim - if (animTo == null) animTo = animFrom - - // loop through the anims if it's a range - sendMessage(player, "Spawned object $objectId, playing anims $animFrom to $animTo.") - queueScript(player, 1, QueueStrength.STRONG) { stage: Int -> - val currentAnimId = animFrom + stage - animateScenery(player, obj, currentAnimId) - sendMessage(player, "Playing object animation $currentAnimId") - if (currentAnimId >= animTo) { - return@queueScript stopExecuting(player) - } - return@queueScript delayScript(player, delay) - } - - return@define - } } }