diff --git a/Server/data/configs/item_configs.json b/Server/data/configs/item_configs.json index b928857e7..23a67341e 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" @@ -77832,7 +77832,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..6f7ff2f90 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,18598" + }, + { + "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.", 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/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt index f43ac6bed..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 @@ -17,6 +17,10 @@ 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 +import core.game.interaction.QueueStrength /** * Listeners for the Elemental Workshop I quest @@ -108,22 +112,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 } @@ -215,13 +242,6 @@ class EWListeners : InteractionListener { /* * * * * * * * * * * * * * * * * Workshop Area listeners * * * * * * * * * * * * * * * */ - // Center hatch, inaccessible - // NOTE: REMOVE ME/ADD CORRECT CHECKS FOR ELEMENTAL WORKSHOP II - 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 @@ -258,40 +278,260 @@ class EWListeners : InteractionListener { } // Workbenches/Anvil - 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) + // 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, intArrayOf(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)) + 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 + // 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)) + 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 + // 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)) + 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) + } + } + } + } + + // 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)) + 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)) { + player.questRepository.getQuest(Quests.ELEMENTAL_WORKSHOP_I).finish(player) + } + } + } + + 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)) + 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) + } + + // 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) + } + + // 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 -> + 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.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 mind shield + lock(player, (animationDuration(smithElementalShield) + 2)) + 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 + // 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)) + 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) + } + } + } + } + } + } } 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/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 new file mode 100644 index 000000000..11f167232 --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/BeatenBook.kt @@ -0,0 +1,243 @@ +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.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 + +/** + * 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, node -> + + // 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) + + // 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 + + return@on true + } + } +} \ No newline at end of file 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..6f421ec1d --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Cutscene.kt @@ -0,0 +1,92 @@ +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 { + EW2Session(player).start(Location.create(1961, 5150, 0)) + setExit(toInstance(player, Location.create(1961, 5150, 0))) + } + } + } + } +} \ 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..5abd816d9 --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Listeners.kt @@ -0,0 +1,1473 @@ +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.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.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 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 +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.Location +import core.game.world.update.flag.context.Animation +import org.rs09.consts.* + +class EW2Listeners : InteractionListener { + + // 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: + * + * 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 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. + * + */ + + 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 + const val EXTRACTOR_GOOD = Animations.HUMAN_USE_EXTRACTOR_HAT_CHAIR_4884 + const val EXTRACTOR_BAD = Animations.HUMAN_IMPROPERLY_USE_EXTRACTOR_HAT_CHAIR_4885 + const val CRANE_LOWER_CART = 4887 + const val CRANE_RAISE_CART = 4888 + const val CRANE_LOWER_LAVA = 4889 + const val CRANE_RAISE_LAVA = 4890 + const val CRANE_PIVOT = 4892 + //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 + + /** + * 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 + * EW2Session start 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 + 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 + 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: 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, // 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 + 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) + ) + + // tank door states stored in varbit 2653 + val tankDoorStates = intArrayOf( + 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 + fun syncCrane(player: Player) { + val mat = getVarbit(player, EW2CraneStateVarbit) + 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 -> { + 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) + syncCart(player) + + // advance quest stage + 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) + } + } + syncCart(player) + } + + // update the jig cart based on varbit status + fun syncCart(player: Player) { + 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 = 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 = 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) // 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 entering) + } else { + // 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() + } + } + } + + // values for the cogs + 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 + + // 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) { + 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) { + 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) + + // both valves closed, nothing happens + if (waterIn == 0 && waterOut == 0) return + + // 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) { + 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) + } + } + } + + // 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) + } + } + + // 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 -> + 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) + } + } + } + + // 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) { + 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) + } + } + } + + // 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) + } + } + } + + // 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 = toInstance(player, Location.create(1953, 5162, 2)) + val currentObject = getScenery(doorLoc) ?: return + val targetId = tankDoorStates[state] + + 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 + (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 + } + + // 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) { + animateScenery(player, currentObject, animId) + if (soundId != -1) playAudio(player, soundId) + return@queueScript delayScript(player, Animation(animId).duration) + } + return@queueScript delayScript(player, 1) + } + + 1 -> { + // 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)) + // 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 entry. + fun syncFan(player: Player) { + val fanMachine = getVarbit(player, EW2FanLeverVarbit) + 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 { + 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 + } + } + + // helper to convert static location coordinates to the dynamic instance location coordinates + fun toInstance(player: Player, staticLoc: Location): Location { + val session = player.getAttribute("ew2-session", null) ?: return staticLoc + val offsetX = staticLoc.x - 1920 + val offsetY = staticLoc.y - 5120 + return session.base.transform(offsetX, offsetY, staticLoc.z) + } + } + + 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) + && !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) + + // advance quest stage + 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...") + 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) + + // advance quest stage + if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 2) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 3) + setVarbit(player, EW2StageVarbit, 3, true) + } + + // 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)) { + 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.") + } + + // advance quest stage + if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 3) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 4) + setVarbit(player, EW2StageVarbit, 4, true) + } + 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 + } + + // 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, "You're unable to open the locked hatch.") + + 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.") + + // start the instanced workshop session + EW2Session(player).start(Location.create(1955, 5155, 2)) + + // advance quest stage + if (getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) == 4) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 5) + setVarbit(player, EW2StageVarbit, 5, true) + } + return@on true + } + + // 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 + } + + // 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 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.") + teleport(player, toInstance(player, location(1948, 5157, 2))) + 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 + } + + // 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, _ -> + // locations + 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)) + + // handler options + val climb = Animation(Animations.HUMAN_CLIMB_STAIRS_828) + val up = "You climb up the stairs." + val down = "You climb down the stairs." + + // 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 + } + + // 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, EW2TankDoorStatusVarbit) // 1 if door is open + val tankState = getVarbit(player, EW2TankDoorStateVarbit) + val fanState = getVarbit(player, EW2FanLeverVarbit) // 1 if the fan is on + + + // find the player's cart + val existingCart = findLocalNPCs(player, jigCartStates, 32).firstOrNull() + + // no cart (should not happen) + if (existingCart == null) { + sendMessage(player, "CART MISSING. RE-ENTER THE WORKSHOP TO RESET IT, OR REPORT THIS.") + return@on true + } + + // 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 + } + + // 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.") + return@on true + } + + // 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)) + + // 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() + }.map { toInstance(player, it) }.toTypedArray() + + 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]) + playAudio(player, Sounds.EW2_CART_LOOP_3167) + } + + return@on true + } + + // 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 interaction if cart is not at south station + if (cartPos != 0) { + sendMessage(player, "I can't reach that.") + return@on true + } + + when(node.id) { + 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) + } + 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) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 7) + setVarbit(player, EW2StageVarbit, 7, 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) + val cartPos = getVarbit(player, EW2CartLeverVarbit) // cart moving lever. 0 = start (south), 1 = west, 2 = north, 3 = east + + // don't allow interaction 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) { + sendMessage(player, "You cannot place that while the claw is down.") + return@onUseWith true + } + + setVarbit(player, EW2JigCartVarbit, 1, true) + 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) + } + + // attribute for quest log + if (!getAttribute(player, attrJigged, false) && getQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II) <= 6) { + setAttribute(player, attrJigged, true) + } + 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, 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) { + sendMessage(player, "The crane is already repaired.") + return@onUseWith true + } + + // repair crane + if (removeItem(player, used)) { + sendItemDialogue(player, used, "You replace the broken claw.") + setVarbit(player, EW2CraneStateVarbit, 1, true) + 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 + } + + // 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 + 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. don't interact with the cart if the cart isn't here. + if (currentPos == 3 && nextPos == 2) { + if (craneMat == 1 && (cartState == 1 || cartState == 2) && cartPos == 0) { + // pick up + 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, true) + setVarbit(player, EW2CraneStateVarbit, 1, true) + } + } + + // facing south: heat bar + if (currentPos == 1 && nextPos == 0) { + if (craneMat == 2) { + // if holding elemental bar, transform into hot bar + 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) // this also syncs the cart inside the crane function + 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) + lockInteractions(player, 5) + 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. 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. + 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) + 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) + // 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) + animate(player, Animations.HUMAN_PULL_LEVER_4909) + } + } + } 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. Maybe the pipes are connected the wrong way round.") + animate(player, Animations.HUMAN_PULL_LEVER_4909) + } + 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, _ -> + 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 + } + + // 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)) + + 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 + else -> tankState + } + + setVarbit(player, EW2TankDoorStateVarbit, nextTankState, true) + syncTankDoor(player) + return@on true + } + + // 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 + queueScript(player, 1, QueueStrength.SOFT) { + when (tankState) { + + // 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) { + 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 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, _ -> + handleValve(player, EW2ValveWestVarbit) + return@on true + } + + // east water valve operates the drain + on(Scenery.WATER_VALVE_18647, IntType.SCENERY, "turn") { player, _ -> + handleValve(player, EW2ValveEastVarbit) + return@on true + } + + /** ------------------------ * + * 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, _ -> + searchCogCrate(player, Items.SMALL_COG_9726, "small") + return@on true + } + + // medium cog crate + on(Scenery.CRATES_18617, IntType.SCENERY, "search") { player, _ -> + searchCogCrate(player, Items.MEDIUM_COG_9725, "medium-sized") + return@on true + } + + // large cog crate + on(Scenery.CRATE_18616, IntType.SCENERY, "search") { player, _ -> + searchCogCrate(player, Items.LARGE_COG_9724, "big") + return@on true + } + + // 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) { + 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) + syncCogs(player) + animate(player, 537) + sendMessage(player, "You remove the cog from the shaft.") + } + return@on true + } + + // 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, _ -> + 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, _ -> + 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, _ -> + placeCog(player, item as Item, EW2CogVarbit3) + return@onUseWith true + } + + // fan lever. + on(Scenery.AN_OLD_LEVER_18663, IntType.SCENERY, "pull") { player, _ -> + animate(player, Animations.HUMAN_PULL_LEVER_4909) + + 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 (allCogs == 57) { + if (lever == 0) { + // rotate camera to view fan + val cam = PlayerCamera(player) + cam.setPosition(38, 28, 350) + + // turn on + 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) { + setAttribute(player, attrWinded, true) + } + } + } else { // lever == 1, turn off + sendDialogue(player, "The machine grinds to a standstill.") + setVarbit(player, EW2FanLeverVarbit, 0, true) + 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 + sendDialogue(player, "The machine starts up, but the fan does not blow.") + playAudio(player, Sounds.EW2_MACHINE_RUMBLE_3175) + } + return@on true + } + + /** ------------------------ * + * SECTION 5 - MIND IMBUE + * ------------------------- */ + + // use bar with extractor gun + 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) { + setQuestStage(player, Quests.ELEMENTAL_WORKSHOP_II, 9) + setVarbit(player, EW2StageVarbit, 9, true) + } + } + return@onUseWith true + } + + // take primed bar from the extractor gun + 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.") + return@on true + } + + // take mind bar from the extractor gun + 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.") + return@on true + } + + // 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 + } + + // start the cutscene + if (getVarbit(player, EW2ExtractorGun) == 1) { + // if bar is in place, charge it and drain magic + EW2Cutscene(player, true).start() + } else { + // if bar is not in place (or if it's already a primed bar), hit player + EW2Cutscene(player, false).start() + } + return@on true + } + } + + // 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)) + } + + 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" + ) { 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)) + } + + // 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)) + } + + // 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)) + } + + // 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 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..74edf5068 --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/EW2Session.kt @@ -0,0 +1,85 @@ +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 +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 +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 { + + companion object { + const val EW2_REGION = 7760 + } + + 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 and Scenery.STAIRS_18599 + fun start(loc: Location) { + val p = player ?: return + + region = DynamicRegion.create(EW2_REGION) + base = region.baseLocation + + zone.register(getRegionBorders(region.id)) + setAttribute(p, "ew2-session", this) + + // 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) + syncCogs(p) + + val startLoc = toInstance(player, loc) + + // teleport in after the workshop is ready + queueScript(player, 1, QueueStrength.SOFT) { + teleport(p, startLoc) + return@queueScript true + } + } + + // 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/ElementalWorkshop2.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt new file mode 100644 index 000000000..919b105f8 --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/ElementalWorkshop2.kt @@ -0,0 +1,294 @@ +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 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=hv2rCofydXo + * Wiki: https://runescape.wiki/w/Elemental_Workshop_II?oldid=849620 + */ + +@Initializable +class ElementalWorkshop2 : Quest(Quests.ELEMENTAL_WORKSHOP_II, 53, 52, 1, EW2StageVarbit, 0, 1, 11) { + + // 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. + 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 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 EW2Varp2 = 897 // second EW2 varp that packages varbits 2653-2664 + 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. + 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 EW2ExtractorGun = 2662 // extractor gun. 0 = empty, 1 = primed bar, 2 = mind bar + //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 + } + + /** + * Quest Stages and Varbit values: + * https://chisel.weirdgloop.org/varbs/display?varbit=2639 + * + * val stage desc. + * 0 0 unstarted + * 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. 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 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 + val stage = getStage(player) + + // 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)) + } + + // 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, "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 and getting the machines fixed + if (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)) + + 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, 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 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, "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, went through the mind door. I skip using this due to lack of source + /* + 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) + } + + // quest complete + if (stage >= 100) { + line++ + line(player,"QUEST COMPLETE!", line) + } + } + + // use ::setqueststage 53 0 to reset quest. + override fun reset(player: Player) { + // 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) + } + + 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) + 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, "7500 Smithing XP,", ln++) + drawReward(player, "7500 Crafting XP,", ln++) + drawReward(player, "Ability to make and equip", ln++) + drawReward(player, "elemental mind equipment", ln) + + setVarbit(player, EW2StageVarbit, 11, true) + rewardXP(player, Skills.SMITHING, 7500.0) + rewardXP(player, Skills.CRAFTING, 7500.0) + + removeAttribute(player, attrJigged) + removeAttribute(player, attrDipped) + removeAttribute(player, attrFlattened) + removeAttribute(player, attrWatered) + removeAttribute(player, attrWinded) + } + + 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/JigCartNPC.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt new file mode 100644 index 000000000..6d6c45c0d --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JigCartNPC.kt @@ -0,0 +1,26 @@ +package content.region.kandarin.seers.quest.elementalworkshop2 + +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.path.ClipMaskSupplier + +/** + * 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 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 { + return 0 + } +} \ 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..ac15f71a7 --- /dev/null +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop2/JunctionBoxInterface.kt @@ -0,0 +1,298 @@ +package content.region.kandarin.seers.quest.elementalworkshop2 + +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 +import org.rs09.consts.Components +import org.rs09.consts.Sounds + +/** + * 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 = 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( + 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, EW2JctVarbit1) + val s2 = getVarbit(player, EW2JctVarbit2) + val s3 = getVarbit(player, EW2JctVarbit3) + + 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, true) + + // reset any selected + setVarbit(player, pipeSelectVarbit, 0, true) + + 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 + // i think some of this is also controlled by 1318.cs2 fwiw + 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, 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, true) + return@on true + } + + // 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 { + getVarbit(player, firstSelect) == 0 -> { + setVarbit(player, firstSelect, pipeIndex + 1, true); true + } + + getVarbit(player, secondSelect) == 0 -> { + setVarbit(player, secondSelect, pipeIndex + 1, true); true + } + + getVarbit(player, thirdSelect) == 0 -> { + setVarbit(player, thirdSelect, pipeIndex + 1, true); true + } + + else -> false + } + + if (saved) { + val left = getVarbit(player, pipesLeftVarbit) + setVarbit(player, pipesLeftVarbit, left - 1, true) + } + } + setVarbit(player, pipeSelectVarbit, 0, true) + } + + // 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, true) + // Increment pipes left + val left = getVarbit(player, pipesLeftVarbit) + setVarbit(player, pipesLeftVarbit, left + 1, true) + playAudio(player, Sounds.EW2_REMOVE_PIPE_3181) + 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 + } + } +} 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