Merge branch 'ew2' into 'master'

Added Elemental Workshop II Quest

Closes #1189

See merge request 2009scape/2009scape!2253
This commit is contained in:
oftheshire 2026-08-01 18:04:37 +00:00
commit 336d38a628
14 changed files with 2993 additions and 58 deletions

View file

@ -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,

View file

@ -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.",

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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<Any?> {
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
}
}

View file

@ -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
}
}

View file

@ -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
}
}
}

View file

@ -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)))
}
}
}
}
}

View file

@ -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<ZoneBorders> {
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<EW2Session?>(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")
}
}

View file

@ -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,"<col=FF0000>QUEST COMPLETE!</col>", 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
}
}

View file

@ -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
}
}

View file

@ -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
}
}
}

View file

@ -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