diff --git a/Server/data/configs/drop_tables.json b/Server/data/configs/drop_tables.json index 24427fd7a..78c198de4 100644 --- a/Server/data/configs/drop_tables.json +++ b/Server/data/configs/drop_tables.json @@ -63624,5 +63624,71 @@ "maxAmount": "1" } ] + }, + { + "default": [ + { + "minAmount": "1", + "weight": "1.0", + "id": "2892", + "maxAmount": "1" + }, + { + "minAmount": "1", + "weight": "1.0", + "id": "1480", + "maxAmount": "1" + } + ], + "charm": [], + "ids": "4910", + "description": "Earth Elemental", + "main": [ + { + "minAmount": "1", + "weight": "1.0", + "id": "0", + "maxAmount": "1" + }, + { + "minAmount": "1", + "weight": "1.0", + "id": "0", + "maxAmount": "1" + } + ] + }, + { + "default": [ + { + "minAmount": "1", + "weight": "1.0", + "id": "2892", + "maxAmount": "1" + }, + { + "minAmount": "1", + "weight": "1.0", + "id": "1480", + "maxAmount": "1" + } + ], + "charm": [], + "ids": "4911", + "description": "Elemental Rock", + "main": [ + { + "minAmount": "1", + "weight": "1.0", + "id": "0", + "maxAmount": "1" + }, + { + "minAmount": "1", + "weight": "1.0", + "id": "0", + "maxAmount": "1" + } + ] } ] \ No newline at end of file diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 17b9674f0..e4290d0bd 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -10445,22 +10445,23 @@ "melee_animation": "4868", "range_animation": "0", "combat_audio": "1531,1533,1532", - "attack_speed": "7", - "respawn_delay": "60", - "defence_animation": "0", + "attack_speed": "6", + "magic_level": "10", + "respawn_delay": "50", + "defence_animation": "4869", "weakness": "7", "magic_animation": "0", "death_animation": "4870", "name": "Earth elemental", - "defence_level": "32", + "defence_level": "35", "safespot": null, "movement_radius": "4", - "lifepoints": "45", + "lifepoints": "35", "strength_level": "32", "id": "1020", - "aggressive": "true", - "range_level": "1", - "attack_level": "32" + "aggressive": "false", + "range_level": "30", + "attack_level": "20" }, { "examine": "An air elemental.", @@ -42052,20 +42053,23 @@ "attack_level": "1" }, { + "examine": "An earth elemental.", "melee_animation": "4868", - "attack_speed": "7", - "respawn_delay": "60", + "combat_audio": "1531,1533,1532", + "attack_speed": "6", + "magic_level": "10", + "respawn_delay": "5", "defence_animation": "4869", "death_animation": "4870", "name": "Earth elemental", - "defence_level": "1", + "defence_level": "35", "safespot": null, "lifepoints": "35", - "strength_level": "1", + "strength_level": "35", "id": "4910", - "aggressive": "true", - "range_level": "1", - "attack_level": "1" + "aggressive": "false", + "range_level": "30", + "attack_level": "20" }, { "examine": "An elemental rock.", diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index 7619d805a..925d69d7d 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -7015,6 +7015,10 @@ "npc_id": "4909", "loc_data": "{2831,3352,0,0,7}" }, + { + "npc_id": "4911", + "loc_data": "{2703,9894,0,0,0}-{2705,9897,0,0,0}-{2706,9904,0,0,0}-{2707,9906,0,0,0}-{2704,9906,0,0,0}-{2706,9910,0,0,0}-{2703,9911,0,0,0}-{2699,9915,0,0,0}-{2697,9915,0,0,0}-{2694,9915,0,0,0}-{2692,9914,0,0,0}-{2696,9910,0,0,0}-{2694,9908,0,0,0}-{2693,9906,0,0,0}-{2696,9906,0,0,0}-{2698,9901,0,0,0}-{2698,9900,0,0,0}-{2696,9900,0,0,0}-{2690,9903,0,0,0}-{2692,9902,0,0,0}-{2693,9900,0,0,0}-{2692,9900,0,0,0}-{2691,9899,0,0,0}-{2692,9895,0,0,0}-{2693,9891,0,0,0}-{2695,9889,0,0,0}-{2692,9885,0,0,0}-{2699,9882,0,0,0}-{2695,9881,0,0,0}-{2693,9881,0,0,0}-{2690,9880,0,0,0}-{2700,9879,0,0,0}-{2696,9880,0,0,0}-{2694,9878,0,0,0}-{2695,9877,0,0,0}-{2699,9876,0,0,0}-{2692,9876,0,0,0}-{2692,9874,0,0,0}-{2699,9872,0,0,0}-{2698,9871,0,0,0}-{2701,9870,0,0,0}-{2695,9870,0,0,0}-{2695,9869,0,0,0}-{2702,9868,0,0,0}-{2701,9867,0,0,0}-{2697,9866,0,0,0}-{2699,9865,0,0,0}" + }, { "npc_id": "4920", "loc_data": "{3289,5496,0,0,5}-{3272,5490,0,0,0}-{3188,5510,0,1,6}-{3194,5510,0,1,2}" diff --git a/Server/src/main/java/core/game/content/global/action/SpecialLadders.java b/Server/src/main/java/core/game/content/global/action/SpecialLadders.java index ec9e56941..7dceeb340 100644 --- a/Server/src/main/java/core/game/content/global/action/SpecialLadders.java +++ b/Server/src/main/java/core/game/content/global/action/SpecialLadders.java @@ -51,9 +51,7 @@ public enum SpecialLadders implements LadderAchievementCheck { player.getAchievementDiaryManager().finishTask(player,DiaryType.SEERS_VILLAGE,1,3); } }, - SEERS_VILLAGE_SPINNING_HOUSE_ROOFTOP_DOWN(new Location(2715,3472,3), new Location(2714,3472,1)), - ELEMENTAL_WORKSHOP_STAIRS_DOWN(Location.create(2710,3497, 0), Location.create(2713,9887, 0)), - ELEMENTAL_WORKSHOP_STAIRS_UP(Location.create(2714,9887, 0), Location.create(2709,3498, 0)); + SEERS_VILLAGE_SPINNING_HOUSE_ROOFTOP_DOWN(new Location(2715,3472,3), new Location(2714,3472,1)); private static HashMap destinationMap = new HashMap<>(); private static HashMap ladderMap = new HashMap<>(); diff --git a/Server/src/main/java/core/game/interaction/item/BookreadOption.java b/Server/src/main/java/core/game/interaction/item/BookreadOption.java index b0fc69cfb..dcc395449 100644 --- a/Server/src/main/java/core/game/interaction/item/BookreadOption.java +++ b/Server/src/main/java/core/game/interaction/item/BookreadOption.java @@ -7,6 +7,7 @@ import core.game.node.entity.player.Player; import core.game.node.item.Item; import core.plugin.Initializable; import core.plugin.Plugin; +import org.rs09.consts.Items; /** * Represents the plugin used to handle the "read" option of a book. @@ -22,6 +23,8 @@ public class BookreadOption extends OptionHandler { ItemDefinition.forId(292).getHandlers().put("option:read", this); ItemDefinition.forId(757).getHandlers().put("option:read", this); ItemDefinition.forId(1856).getHandlers().put("option:read", this); + ItemDefinition.forId(Items.BATTERED_BOOK_2886).getHandlers().put("option:read", this); + ItemDefinition.forId(Items.SLASHED_BOOK_9715).getHandlers().put("option:read", this); ItemDefinition.forId(9003).getHandlers().put("option:read", this); ItemDefinition.forId(9004).getHandlers().put("option:read", this); ItemDefinition.forId(11710).getHandlers().put("option:read", this); @@ -52,6 +55,10 @@ public class BookreadOption extends OptionHandler { return 49610758; case 9003: return 49610759; + case Items.BATTERED_BOOK_2886: + return 49610760; + case Items.SLASHED_BOOK_9715: + return 49610761; /*case 9004: return 423943;*/ case 11710: diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/BatteredBookHandler.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/BatteredBookHandler.kt new file mode 100644 index 000000000..b25b5d20e --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/BatteredBookHandler.kt @@ -0,0 +1,60 @@ +package rs09.game.content.quest.members.elementalworkshop + +import api.setQuestStage +import core.game.content.dialogue.DialoguePlugin +import core.game.content.dialogue.book.Book +import core.game.content.dialogue.book.Page +import core.game.node.entity.player.Player +import core.plugin.Initializable +import org.rs09.consts.Items + +@Initializable +class BatteredBookHandler : Book { + + constructor(player: Player?) : super(player, "Book of the elemental shield", Items.BATTERED_BOOK_2886, *EWUtils.PAGES) {} + + constructor() { + /** + * empty. + */ + } + + override fun finish() { + val stage = player.questRepository.getStage("Elemental Workshop I") + if (stage == 0) { + setQuestStage(player, "Elemental Workshop I", 1) + } + } + + override fun display(set: Array) { + player.lock() + player.interfaceManager.open(getInterface()) + + for (i in 55..76) { + player.packetDispatch.sendString("", getInterface().id, i) + } + player.packetDispatch.sendString("", getInterface().id, 77) + player.packetDispatch.sendString("", getInterface().id, 78) + player.packetDispatch.sendString(getName(), getInterface().id, 6) + for (page in set) { + for (line in page.lines) { + player.packetDispatch.sendString(line.message, getInterface().id, line.child) + } + } + player.packetDispatch.sendInterfaceConfig(getInterface().id, 51, index < 1) + val lastPage = index == sets.size - 1 + player.packetDispatch.sendInterfaceConfig(getInterface().id, 53, lastPage) + if (lastPage) { + finish() + } + player.unlock() + } + + override fun newInstance(player: Player): DialoguePlugin { + return BatteredBookHandler(player) + } + + override fun getIds(): IntArray { + return intArrayOf(EWUtils.BATTERED_BOOK_ID) + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/EWListeners.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/EWListeners.kt new file mode 100644 index 000000000..5c74bd760 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/EWListeners.kt @@ -0,0 +1,536 @@ +package rs09.game.content.quest.members.elementalworkshop + +import api.* +import core.game.content.dialogue.FacialExpression +import core.game.content.global.action.DoorActionHandler +import core.game.node.entity.player.link.audio.Audio +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import core.game.system.task.Pulse +import core.game.world.map.Location +import core.game.world.update.flag.context.Animation +import org.rs09.consts.* +import rs09.game.content.quest.members.elementalworkshop.EWUtils.BELLOWS_STATE +import rs09.game.content.quest.members.elementalworkshop.EWUtils.FURNACE_STATE +import rs09.game.content.quest.members.elementalworkshop.EWUtils.LEFT_WATER_CONTROL_STATE +import rs09.game.content.quest.members.elementalworkshop.EWUtils.RIGHT_WATER_CONTROL_STATE +import rs09.game.content.quest.members.elementalworkshop.EWUtils.WATER_WHEEL_STATE +import rs09.game.content.quest.members.elementalworkshop.EWUtils.currentStage +import rs09.game.interaction.InteractionListener +import rs09.game.system.SystemLogger + +class EWListeners : InteractionListener { + + /* Items */ + private val batteredBook = Item(Items.BATTERED_BOOK_2886) + private val batteredKey = Item(Items.BATTERED_KEY_2887) + private val slashedBook = Item(Items.SLASHED_BOOK_9715) + private val emptyStoneBowl = Item(Items.A_STONE_BOWL_2888) + private val fullStoneBowl = Item(Items.A_STONE_BOWL_2889) + private val elementalOre = Item(Items.ELEMENTAL_ORE_2892) + private val elementalMetal = Item(Items.ELEMENTAL_METAL_2893) + private val elementalShield = Item(Items.ELEMENTAL_SHIELD_2890) + private val bellowFixReqItems = intArrayOf( + Items.NEEDLE_1733, + Items.THREAD_1734, + Items.LEATHER_1741 + ) + private val elementalShieldReqItems = intArrayOf( + Items.SLASHED_BOOK_9715, + Items.HAMMER_2347, + Items.ELEMENTAL_METAL_2893 + ) + + /* Scenery */ + private val bookcase = Scenery.BOOKCASE_26113 + private val lavaTrough = intArrayOf( + Scenery.LAVA_TROUGH_18519, Scenery.LAVA_TROUGH_18520, + Scenery.LAVA_TROUGH_18521, Scenery.LAVA_TROUGH_18522, + Scenery.LAVA_TROUGH_18523 + ) + // Handling for on/off state (one obj id for both left and right) + private val waterControls = intArrayOf( + Scenery.WATER_CONTROLS_18509, Scenery.WATER_CONTROLS_18510 + ) + + /* Animations */ + private val turnWaterControlAnimation = Animation(Animations.HUMAN_TURN_LARGE_VALVE_4861) + private val fillStoneBowlAnimation = Animation(Animations.HUMAN_FILL_STONE_BOWL_4862) + private val fixBellowsAnimation = Animation(Animations.HUMAN_SEW_LARGE_SCENERY_4862) + private val smeltElementalBar = Animation(Animations.HUMAN_FURNACE_SMELTING_3243) + + /* Sound effects */ + private val fillStoneBowlSFX = Audio(Sounds.FILL_STONE_BOWL_1537, 1) + private val fillFurnaceWithLavaSFX = Audio(Sounds.FILL_FURNACE_WITH_LAVA_1538) + private val pullLeverResetGatesSFX = Audio(Sounds.PULL_LEVER_RESET_GATE_1540) + private val turnWaterControlsSFX = Audio(Sounds.TURN_METAL_WATER_VALVE) + private val pullLeverEnabledSFX = Audio(Sounds.PULL_LEVER_ENABLED_1547) + private val pullLeverDisabledSFX = Audio(Sounds.PULL_LEVER_DISABLED_1548) + private val bellowLeverSFX = Audio(Sounds.PULL_LEVER_GENERAL_2400) + + /* In-game locations */ + private val leftWaterControlsLocation = Location.create(2713, 9907, 0) + private val rightWaterControlsLocation = Location.create(2726, 9907, 0) + + private val DISABLED = 0 + private val ENABLED = 1 + + override fun defineListeners() { + /* * * * * * * * * * * * * * * * * * * + * Seers Village House listeners * + * * * * * * * * * * * * * * * * * */ + // Bookcase (quest start) + on(Scenery.BOOKCASE_26113, SCENERY, "search") { player, _ -> + val stage = player.questRepository.getStage("Elemental Workshop I") + + if (stage < 3) { + // Player already has battered book in inventory + if (player.inventory.containsItem(batteredBook)) { + sendItemDialogue( + player, Item(Items.BATTERED_BOOK_2886), "There is a book here titled 'The Elemental Shield'. " + + "It can stay here, as you have a copy in your backpack." + ) + return@on true + } + // Player needs to receive a battered book + sendItemDialogue(player, Item(Items.BATTERED_BOOK_2886), "You find a book titled 'The Elemental Shield'.") + addItem(player, batteredBook.id) + return@on true + } + + // --- 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." + ) + player.inventory.addIfDoesntHave(batteredKey) + return@on true + } + + sendItemDialogue(player, Item(Items.SLASHED_BOOK_9715), "You find a book titled 'The Elemental Shield'.") + addItem(player, slashedBook.id) + player.inventory.addIfDoesntHave(batteredKey) + return@on true + } + + // Knife on Battered book -> Slashed book + Battered key + onUseWith(ITEM, Items.KNIFE_946, Items.BATTERED_BOOK_2886) { player, _, with -> + val stage = currentStage(player) + if (stage >= 1) { + lock(player, 2) + player.pulseManager.run(object : Pulse() { + var count = 0 + override fun pulse(): Boolean { + when (count) { + 0 -> sendMessage(player, "You make a small cut in the spine of the book.") + 1 -> { + sendMessage(player, "Inside you find a small, old, battered key.") + replaceSlot(player, with.asItem().slot, slashedBook) + addItemOrDrop(player, Items.BATTERED_KEY_2887) + setQuestStage(player, "Elemental Workshop I", 3) + return true + } + } + count++ + return false + } + }) + return@onUseWith true + } else { + sendMessage(player, "Nothing interesting happens.") + return@onUseWith true + } + } + + + /* * * * * * * * * * * * * * * * * * * + * Seers Village Smithy listeners * + * * * * * * * * * * * * * * * * * */ + // Odd looking wall handler + on(intArrayOf(Scenery.ODD_LOOKING_WALL_26114, Scenery.ODD_LOOKING_WALL_26115), SCENERY, "open") { player, wall -> + // Player is allowed to exit without key + if (player.location == Location.create(2710, 3496, 0) || player.location == Location.create(2709, 3496, 0)) { + DoorActionHandler.handleAutowalkDoor(player, wall.asScenery()) + return@on true + } + // Player does not have battered key in inventory + if (!inInventory(player, Items.BATTERED_KEY_2887)) { + sendMessage(player, "You see a small hole in the wall but no way to open it.") + return@on true + } + // Increment quest stage + if (questStage(player, "Elemental Workshop I") < 5) { + setQuestStage(player, "Elemental Workshop I", 5) + } + // Allow player through the wall + sendMessage(player, "You use the battered key to open the doors.") + DoorActionHandler.handleAutowalkDoor(player, wall.asScenery()) + return@on true + } + + on(Scenery.STAIRCASE_3415, SCENERY, "climb-down") { player, _ -> + teleport(player, Location.create(2716, 9888, 0)) + // If it is the players first time in the workshop + if (currentStage(player) < 6) { + sendPlayerDialogue(player, + "Now to explore this area thoroughly, to find what " + + "forgotten secrets it contains.", FacialExpression.NEUTRAL) + setQuestStage(player, "Elemental Workshop I", 7) + } + return@on true + } + + on(Scenery.STAIRCASE_3416, SCENERY, "climb-up") { player, _ -> + teleport(player, Location.create(2709, 3498, 0)) + return@on true + } + + + /* * * * * * * * * * * * * * * * + * Workshop Area listeners * + * * * * * * * * * * * * * * */ + // Center hatch, inaccessible + // NOTE: REMOVE ME/ADD CORRECT CHECKS FOR ELEMENTAL WORKSHOP II + on(Scenery.HATCH_18595, 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, SCENERY, "search") { player, _ -> + // If the player doesn't have a stone bowl in their inventory + if (player.inventory.addIfDoesntHave(emptyStoneBowl)) { + sendMessage(player, "You find a stone bowl.") + } else { + sendMessage(player, "It's empty.") + } + return@on true + } + + // Needle crate, player can only receive once + on(Scenery.CRATE_3400, SCENERY, "search") { player, _ -> + if (!getAttribute(player, "/save:ew1:got_needle", false)) { + setAttribute(player, "/save:ew1:got_needle", true) + addItem(player, Items.NEEDLE_1733) + sendMessage(player, "You find a needle.") + } else { + sendMessage(player, "You search the crate but find nothing.") + } + return@on true + } + + // Leather crate, player can only receive once + on(Scenery.CRATE_3394, SCENERY, "search") { player, _ -> + if (!getAttribute(player, "/save:ew1:got_leather", false)) { + setAttribute(player, "/save:ew1:got_leather", true) + addItem(player, Items.LEATHER_1741) + sendMessage(player, "You find some leather.") + } else { + sendMessage(player, "You search the crate but find nothing.") + } + return@on true + } + + // Workbenches/Anvil + onUseWith(SCENERY, Items.ELEMENTAL_METAL_2893, Scenery.WORKBENCH_3402) { player, used, _ -> + // 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))) { + sendMessage(player, "You are unsure what to do with the bar. Perhaps there is a book to help guide you.") + return@onUseWith true + } + // Warn player they don't have a hammer in their inventory + if (!inInventory(player, Items.HAMMER_2347)) { + sendMessage(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)) { + SystemLogger.logErr("${player.username} tried to forge an elemental shield without all the required items.") + return@onUseWith false + } + // Successfully smith the elemental shield + replaceSlot(player, used.asItem().slot, elementalShield) + sendMessage(player, "Following the instructions in the book you make an elemental shield.") + player.questRepository.getQuest("Elemental Workshop I").finish(player) + return@onUseWith true + } + + + /* * * * * * * * * * * * * * + * Fire room listeners * + * * * * * * * * * * * * */ + // Lava trough stone bowl (empty) + onUseWith(SCENERY, Items.A_STONE_BOWL_2888, *lavaTrough) { player, used, trough -> + lock(player, (animationDuration(fillStoneBowlAnimation) + 1)) + runTask(player) { + face(player, trough) + playAudio(player, fillStoneBowlSFX) + animate(player, fillStoneBowlAnimation) + replaceSlot(player, used.asItem().slot, fullStoneBowl) + sendMessage(player, "You fill the bowl with hot lava.") + } + return@onUseWith true + } + + // Lava trough stone bowl (filled with lava) + onUseWith(SCENERY, fullStoneBowl.id, *lavaTrough) { player, _, _ -> + sendMessage(player, "The bowl is already full of lava.") + return@onUseWith true + } + + // Pour lava into unlit furnace + onUseWith(SCENERY, fullStoneBowl.id, Scenery.FURNACE_18525) { player, used, _ -> + player.faceLocation(Location.create(2726, 9875, 0)) + lock(player, 3) + submitIndividualPulse(player, object : Pulse() { + var count = 0 + override fun pulse(): Boolean { + when (count) { + 0 -> { + replaceSlot(player, used.asItem().slot, emptyStoneBowl) + playAudio(player, fillFurnaceWithLavaSFX) + sendMessage(player, "You empty the lava into the furnace.") + } + 2 -> { + setVarbit(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, FURNACE_STATE, ENABLED, true) + sendMessage(player, "The furnace bursts to life.") + return true + } + } + count++ + return false + } + }) + return@onUseWith true + } + + // Pour lava into lit furnace + onUseWith(SCENERY, fullStoneBowl.id, Scenery.FURNACE_18526) { player, used, _ -> + player.faceLocation(Location.create(2726, 9875, 0)) + runTask(player) { + playAudio(player, fillFurnaceWithLavaSFX) + replaceSlot(player, used.asItem().slot, emptyStoneBowl) + sendMessage(player, "The lava makes little difference to the furnace's searing heat.") + } + return@onUseWith true + } + + // Use elemental ore on lit furnace + onUseWith(SCENERY, Items.ELEMENTAL_ORE_2892, Scenery.FURNACE_18526) { player, _, _ -> + player.faceLocation(Location.create(2726, 9875, 0)) + // Warn player their smithing level is too low to smelt the ore + if (player.skills.getLevel(Skills.SMITHING) < 20) { + sendMessage(player, "You need a smithing level of 20 to smelt elemental ore.") + return@onUseWith true + } + // Warn player the bellows must be on so the furnace is properly heated + if (!EWUtils.bellowsEnabled(player)) { + sendMessage(player, "The furnace needs to be hotter to be of any use.") + return@onUseWith true + } + // Warn player they don't have enough coal to smelt the bar + if (amountInInventory(player, Items.COAL_453) < 4) { + sendMessage(player, "You need four heaps of coal to smelt elemental ore.") + return@onUseWith true + } + + val duration = animationDuration(smeltElementalBar) + lock(player, (duration + 1)) + // Run the "smelting" pulse + submitIndividualPulse(player, object : Pulse() { + var count = 0 + override fun pulse(): Boolean { + when (count) { + 0 -> { + removeItem(player, Item(Items.COAL_453, 4)) + removeItem(player, elementalOre) + animate(player, Animations.HUMAN_FURNACE_SMELTING_3243) + sendMessage(player, "You place the elemental ore and four heaps of coal into the furnace.") + } + duration -> { + addItem(player, Items.ELEMENTAL_METAL_2893) + sendMessage(player, "You retrieve a bar of elemental metal.") + rewardXP(player, Skills.SMITHING, 7.0) + return true + } + } + count++ + return false + } + }) + return@onUseWith true + } + + + /* * * * * * * * * * * * * * + * Water room listeners * + * * * * * * * * * * * * */ + // Water controls + on(waterControls, SCENERY, "turn") { player, _ -> + // Notify player they can't change water control values while water wheel is running + if (EWUtils.waterWheelEnabled(player)) { + sendMessage(player, "Now that the water wheel is running, the valve seems locked off.") + return@on true + } + + // Varbit offset + val offset: Int + // Whether it is enabled or disabled (used for xor toggle) + val enabled: Int + when (player.location) { + // Check left control + leftWaterControlsLocation -> { + offset = LEFT_WATER_CONTROL_STATE + enabled = EWUtils.leftWaterControlBit(player) xor 0x1 + } + // Check right control + rightWaterControlsLocation -> { + offset = RIGHT_WATER_CONTROL_STATE + if (EWUtils.leftWaterControlEnabled(player)) { + enabled = 0 + } else { + enabled = EWUtils.rightWaterControlBit(player) xor 0x1 + } + } + // Sanity control check + else -> { + offset = -1 + enabled = 0 + SystemLogger.logErr("Unhandled location when determining enabled water controls! ${player.location}") + return@on false + } + } + + // Run the turn valve pulse + lock(player, (animationDuration(turnWaterControlAnimation) + 1)) + submitIndividualPulse(player, object : Pulse() { + var count = 0 + override fun pulse(): Boolean { + when (count) { + 0 -> { + playAudio(player, turnWaterControlsSFX) + animate(player, turnWaterControlAnimation) + sendMessage(player, "You turn the handle.") + } + 3 -> { + setVarbit(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, offset, enabled, true) + return true + } + } + count++ + return false + } + }) + return@on true + } + + // Water wheel lever handler + on(Scenery.LEVER_3406, SCENERY, "pull") { player, lever -> + // Default will happen no matter what + lock(player, 2) + sendMessage(player, "You pull the lever") + replaceScenery(lever.asScenery(), Scenery.LEVER_3417, 2) + + // Check to see if the water wheel is running + if (EWUtils.waterWheelEnabled(player)) { + playAudio(player, pullLeverDisabledSFX) + sendMessage(player, "You hear the sound of a water wheel coming to a standstill.") + setVarbit(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, WATER_WHEEL_STATE, DISABLED, true) + setVarbit(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, BELLOWS_STATE, DISABLED, true) + return@on true + } + + // Check to reset both valves to the "off" position if both aren't green + if (!EWUtils.leftWaterControlEnabled(player) || !EWUtils.rightWaterControlEnabled(player)) { + playAudio(player, pullLeverResetGatesSFX) + sendMessage(player, "You hear the sound of the flow gates resetting.") + setVarbit(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, LEFT_WATER_CONTROL_STATE, DISABLED, true) + setVarbit(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, RIGHT_WATER_CONTROL_STATE, DISABLED, true) + return@on true + } + + // If both of the above are false, the water wheel can start running + playAudio(player, pullLeverEnabledSFX) + sendMessage(player, "You hear the sound of a water wheel starting up.") + setVarbit(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, WATER_WHEEL_STATE, ENABLED, true) + return@on true + } + + + /* * * * * * * * * * * * * * + * Air room listeners * + * * * * * * * * * * * * */ + on(Scenery.BELLOWS_18516, SCENERY, "fix") { player, bellows -> + player.faceLocation(Location.create(2736, 9883, 0)) + // Warn player bellows are already fixed + if (getAttribute(player, "/save:ew1:bellows_fixed", false)) { + sendMessage(player, "The bellows already look fixed to you.") + return@on true + } + // Warn player they don't have a high enough crafting level + if (player.skills.getLevel(Skills.CRAFTING) < 20) { + sendMessage(player, "You need a crafting level of 20 to fix the bellows.") + return@on true + } + // Warn player they don't have the required items + if (!player.inventory.containsAll(*bellowFixReqItems)) { + sendMessage(player, "You don't have the required item(s) to fix the bellows.") + return@on true + } + // If the player has all the requirements to fix the bellows + lock(player, (animationDuration(fixBellowsAnimation) + 1)) + runTask(player) { + removeItem(player, Items.LEATHER_1741) + removeItem(player, Item(Items.THREAD_1734, 1)) + animate(player, fixBellowsAnimation) + sendMessage(player, "You stitch the leather over the hole in the bellows.") + setAttribute(player, "/save:ew1:bellows_fixed", true) + } + return@on true + } + + on(Scenery.LEVER_3409, SCENERY, "pull") { player, lever -> + // Warn player they have to fix the bellows before pulling the lever + if (!getAttribute(player, "/save:ew1:bellows_fixed", false)) { + sendPlayerDialogue(player, "I shouldn't risk damaging the bellows any further.") + return@on true + } + // Pull the lever checks + lock(player, 2) + sendMessage(player, "You pull the lever") + replaceScenery(lever.asScenery(), Scenery.LEVER_3417, 2) + playAudio(player, bellowLeverSFX) + + // Warn player the water wheel is not running + if (!EWUtils.waterWheelEnabled(player)) { + sendMessage(player, "Nothing happens; the lever resets itself.") + return@on true + } + // Bellows lever "OFF" state + if (EWUtils.bellowsEnabled(player)) { + sendMessage(player, "The bellows stop pumping air.") + setVarbit(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, BELLOWS_STATE, DISABLED, true) + return@on true + } + + // Bellows lever "ON" state + sendMessage(player, "The bellows pump air down the pipe.") + setVarbit(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, BELLOWS_STATE, ENABLED, true) + return@on true + } + } + override fun defineDestinationOverrides() { + setDest(SCENERY, intArrayOf(Scenery.FURNACE_18525, Scenery.FURNACE_18526), "use") { _, _ -> + return@setDest Location.create(2724,9875) + } + setDest(SCENERY, intArrayOf( Scenery.BELLOWS_18516), "fix") { _, _ -> + return@setDest Location.create(2733, 9884, 0) + } + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/EWUtils.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/EWUtils.kt new file mode 100644 index 000000000..6799d74a1 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/EWUtils.kt @@ -0,0 +1,109 @@ +package rs09.game.content.quest.members.elementalworkshop + +import api.getVarbitValue +import core.game.content.dialogue.book.BookLine +import core.game.content.dialogue.book.Page +import core.game.content.dialogue.book.PageSet +import core.game.node.entity.player.Player +import org.rs09.consts.Vars + +object EWUtils { + + // Helpers for the battered/slashed book + /** + * Represents the book id + */ + const val BATTERED_BOOK_ID = 49610760 + const val SLASHED_BOOK_ID = 49610761 + + /** + * Represents the array of pages for this book. + */ + val PAGES = arrayOf( + PageSet( + Page( + BookLine("Within the pages of this", 55), + BookLine("book you will find the", 56), + BookLine("secret to working the", 57), + BookLine("very elements themselves.", 58), + BookLine("Early in the fifth age, a", 59), + BookLine("new ore was discovered.", 60), + BookLine("This ore has a unique", 61), + BookLine("property of absorbing,", 62), + BookLine("transforming or focusing", 63), + BookLine("elemental energy. A", 64), + BookLine("workshop was erected", 65), + ), + Page( + BookLine("close by to work this new", 66), + BookLine("material. The workshop", 67), + BookLine("was set up for artisans", 68), + BookLine("and inventors to be able", 69), + BookLine("to come and create", 70), + BookLine("devices made from the", 71), + BookLine("unique ore, found only in", 72), + BookLine("the village of the Seers.", 73) + ) + ), + PageSet( + Page( + BookLine("After some time of", 55), + BookLine("successful industry the", 56), + BookLine("true power of this ore", 57), + BookLine("became apparent, as", 58), + BookLine("greater and more", 59), + BookLine("powerful weapons were", 60), + BookLine("created. Realising the", 61), + BookLine("threat this posed, the magi", 62), + BookLine("of the time closed down", 63), + BookLine("the workshop and bound", 64), + BookLine("it under lock and key,", 65) + ), + Page( + BookLine("also trying to destroy all",66), + BookLine("knowledge of ",67), + BookLine("manufacturing processes.",68), + BookLine("Yet this book remains and",69), + BookLine("you may still find a way",70), + BookLine("to enter the workshop",71), + BookLine("within this leather bound",72), + BookLine("volume.",73), + ) + ) + ) + + /* OFFSETS */ + const val LEFT_WATER_CONTROL_STATE = 3 + const val RIGHT_WATER_CONTROL_STATE = 4 + const val WATER_WHEEL_STATE = 5 + const val FURNACE_STATE = 7 + const val BELLOWS_STATE = 9 + + fun leftWaterControlBit(player: Player): Int { + return getVarbitValue(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, LEFT_WATER_CONTROL_STATE) + } + + fun rightWaterControlBit(player: Player): Int { + return getVarbitValue(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, RIGHT_WATER_CONTROL_STATE) + } + + fun leftWaterControlEnabled(player: Player): Boolean { + return getVarbitValue(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, LEFT_WATER_CONTROL_STATE) == 1 + } + + fun rightWaterControlEnabled(player: Player): Boolean { + return getVarbitValue(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, RIGHT_WATER_CONTROL_STATE) == 1 + } + + fun waterWheelEnabled(player: Player): Boolean { + return getVarbitValue(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, WATER_WHEEL_STATE) == 1 + } + + fun bellowsEnabled(player: Player): Boolean { + return getVarbitValue(player, Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, BELLOWS_STATE) == 1 + } + + fun currentStage(player: Player): Int { + return player.questRepository.getStage("Elemental Workshop I") + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/ElementalRockNPC.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/ElementalRockNPC.kt new file mode 100644 index 000000000..a98fa10c1 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/ElementalRockNPC.kt @@ -0,0 +1,88 @@ +package rs09.game.content.quest.members.elementalworkshop + +import api.getTool +import api.sendDialogue +import core.game.node.entity.Entity +import core.game.node.entity.npc.AbstractNPC +import core.game.node.entity.skill.Skills +import core.game.node.entity.skill.gather.SkillingTool +import core.game.system.task.Pulse +import core.game.world.map.Location +import core.game.world.update.flag.context.Animation +import core.plugin.Initializable +import org.rs09.consts.NPCs +import rs09.game.interaction.InteractionListener + +/** + * Npc(s): + * Earth elemental - ID: 4910 (Attack-able form) + * Elemental rock - ID: 4911 (Sleeping "mine-able" form) + * Option(s): + * "Mine" + * + * @author Woah, with love + */ +@Initializable +class ElementalRockNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id,location), InteractionListener { + + private val ELEMENTAL_ROCK_TRANSFORMATION_4865 = Animation(4865) + + // Sanity transform back to elemental rock + override fun init() { + transform(NPCs.ELEMENTAL_ROCK_4911) + super.init() + } + + override fun construct(id: Int, location: Location?, vararg objects: Any?): AbstractNPC { + return ElementalRockNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.EARTH_ELEMENTAL_4910, NPCs.ELEMENTAL_ROCK_4911) + } + + override fun defineListeners() { + on(ids, NPC, "Mine") { player, node -> + val tool: SkillingTool? = getTool(player, true) + // Warn player they don't have a high enough mining level + if (player.skills.getStaticLevel(Skills.MINING) < 20) { + sendDialogue(player, "You need a mining level of at least 20 to mine elemental ore.") + return@on true + } + // Check to see if the player can proc the NPC + if (tool == null) { + sendDialogue(player, "You do not have a pickaxe which you have the Mining level to use.") + return@on true + } + // Transform the current NPC to 4910 (monster form) + pulseManager.run(object : Pulse() { + var count = 0 + override fun pulse(): Boolean { + when (count) { + // Play the transformation Animation + 0 -> node.asNpc().animate(ELEMENTAL_ROCK_TRANSFORMATION_4865) + // Yell at the player and transform into the combat NPC 4910 + 1 -> { + node.asNpc().sendChat("Grr... Ge'roff us!") + node.asNpc().transform(NPCs.EARTH_ELEMENTAL_4910) + } + // Start attacking the player + 3 -> { + node.asNpc().attack(player) + return true + } + } + count++ + return false + } + }) + return@on true + } + } + + // Reset back to rock NPC + override fun finalizeDeath(killer: Entity?) { + transform(NPCs.ELEMENTAL_ROCK_4911) + super.finalizeDeath(killer) + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/ElementalWorkshopQuest.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/ElementalWorkshopQuest.kt new file mode 100644 index 000000000..5b602d360 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/ElementalWorkshopQuest.kt @@ -0,0 +1,135 @@ +package rs09.game.content.quest.members.elementalworkshop + +import api.Commands +import api.addItem +import api.setAttribute +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.quest.Quest +import core.game.node.entity.skill.Skills +import core.game.world.map.Location +import core.plugin.Initializable +import org.rs09.consts.Items +import org.rs09.consts.Vars +import rs09.game.system.command.Privilege + +/** + * Elemental Workshop I + * + * Original Development Team: + * Developer: Dylan C + * Graphics: Tom W + * Quality Assurance: Andrew C + * Audio: Ian T + * + * RS2 Rework Team: + * Developer: Dylan C + * Graphics: Tom W + * Quality Assurance: Andrew C + * QuestHelp: Rob M + * + * 2009scape adaptation: + * @author Woah, with love + */ +@Initializable +class ElementalWorkshopQuest : Quest("Elemental Workshop I", 52, 51, 1), Commands { + + override fun newInstance(`object`: Any?): Quest { + return this + } + + override fun drawJournal(player: Player?, stage: Int) { + super.drawJournal(player, stage) + player ?: return + var line = 11 + if (stage == 0) { + line(player, "I can start this quest by reading a", line++) + line(player, "!!book?? found in !!Seers village??.", line++) + line++ + line(player, "Minimum requirements:", line++) + line(player, if (player.skills.getStaticLevel(Skills.MINING) >= 20) "---Level 20 Mining/--" else "!!Level 20 Mining??", line++) + line(player, if (player.skills.getStaticLevel(Skills.SMITHING) >= 20) "---Level 20 Smithing/--" else "!!Level 20 Smithing??", line++) + line(player, if (player.skills.getStaticLevel(Skills.CRAFTING) >= 20) "---Level 20 Crafting/--" else "!!Level 20 Crafting??", line++) + } else { + // Player read through book on bookshelf + if (stage < 100) { + if (stage >= 1) { + line(player, "---I have found a battered book in a house in Seers Village./--", line++) + line(player, "---It tells of magic ore and a workshop created to fashion it./--", line++) + line++ + if (stage <= 2) { + line(player, "Where is the workshop and how do I get in?", line++) + } + } + + if (stage >= 3) { + line(player, "---Cutting open the spine of the book with a knife,/--", line++) + line(player, "---I found a key hidden under the leather binding./--", line++) + line++ + if (stage <= 4) { + line(player, "Where is the workshop and how do I get in?", line++) + } + } + + if (stage >= 5) { + line(player, "---I have found a secret door in the Seers Village smithy/--", line++) + line++ + line(player, "---Where is the workshop and how do I get in?/--", line++) + line++ + } + + // Player climbed down staircase + if (stage == 7) { + line(player, "There is obviously lots to do here.", line++) + } + } else { + line(player, "---I have found a battered book in a house in Seers Village./--", line++) + line(player, "---It tells of magic ore and a workshop created to fashion it./--", line++) + line++ + line(player, "---After fixing up the old workshop machinery, collecting ore", line++) + line(player, "---and smelting it I was able to create an Elemental Shield./--", line++) + line++ + line(player, "!!QUEST COMPLETE!??", line++) + } + } + } + + override fun finish(player: Player?) { + super.finish(player) + player ?: return + player.packetDispatch.sendString("1 Quest Point,", 277, 8 + 2) + player.packetDispatch.sendString("5,000 Crafting XP", 277, 9 + 2) + player.packetDispatch.sendString("5,000 Smithing XP", 277, 10 + 2) + player.packetDispatch.sendString("The ability to make", 277, 11 + 2) + player.packetDispatch.sendString("elemental shields.", 277, 12 + 2) + player.packetDispatch.sendItemZoomOnInterface(Items.ELEMENTAL_SHIELD_2890, 235, 277, 3 + 2) + player.skills.addExperience(Skills.CRAFTING, 5000.0) + player.skills.addExperience(Skills.SMITHING, 5000.0) + player.questRepository.syncronizeTab(player) + } + + override fun getConfig(player: Player?, stage: Int): IntArray { + if (stage >= 100) return intArrayOf(Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, 1048576) + if (stage > 0) return intArrayOf(Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, 3) + else return intArrayOf(Vars.VARP_QUEST_ELEMENTAL_WORKSHOP, 0) + } + + override fun defineCommands() { + define("resetew", Privilege.ADMIN) { player, _ -> + setAttribute(player, "/save:ew1:got_needle", false) + setAttribute(player, "/save:ew1:got_leather", false) + setAttribute(player, "/save:ew1:bellows_fixed", false) + player.questRepository.setStageNonmonotonic(player.questRepository.forIndex(52), 0) + player.varpManager.get(Vars.VARP_QUEST_ELEMENTAL_WORKSHOP).clearBitRange(0, 31) + player.varpManager.get(Vars.VARP_QUEST_ELEMENTAL_WORKSHOP).send(player) + player.teleport(Location.create(2715, 3481, 0)) + player.inventory.clear() + addItem(player, Items.KNIFE_946) + addItem(player, Items.BRONZE_PICKAXE_1265) + addItem(player, Items.NEEDLE_1733) + addItem(player, Items.THREAD_1734) + addItem(player, Items.LEATHER_1741) + addItem(player, Items.HAMMER_2347) + addItem(player, Items.COAL_453, 4) + } + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/SlashedBookHandler.kt b/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/SlashedBookHandler.kt new file mode 100644 index 000000000..b0fa09bc9 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/content/quest/members/elementalworkshop/SlashedBookHandler.kt @@ -0,0 +1,53 @@ +package rs09.game.content.quest.members.elementalworkshop + +import core.game.content.dialogue.DialoguePlugin +import core.game.content.dialogue.book.Book +import core.game.content.dialogue.book.Page +import core.game.node.entity.player.Player +import core.plugin.Initializable +import org.rs09.consts.Items + +@Initializable +class SlashedBookHandler : Book { + constructor(player: Player?) : super(player, "Book of the elemental shield", Items.SLASHED_BOOK_9715, *EWUtils.PAGES) {} + + constructor() { + /** + * empty. + */ + } + + override fun finish() {} + + override fun display(set: Array) { + player.lock() + player.interfaceManager.open(getInterface()) + + for (i in 55..76) { + player.packetDispatch.sendString("", getInterface().id, i) + } + player.packetDispatch.sendString("", getInterface().id, 77) + player.packetDispatch.sendString("", getInterface().id, 78) + player.packetDispatch.sendString(getName(), getInterface().id, 6) + for (page in set) { + for (line in page.lines) { + player.packetDispatch.sendString(line.message, getInterface().id, line.child) + } + } + player.packetDispatch.sendInterfaceConfig(getInterface().id, 51, index < 1) + val lastPage = index == sets.size - 1 + player.packetDispatch.sendInterfaceConfig(getInterface().id, 53, lastPage) + if (lastPage) { + finish() + } + player.unlock() + } + + override fun newInstance(player: Player): DialoguePlugin { + return BatteredBookHandler(player) + } + + override fun getIds(): IntArray { + return intArrayOf(EWUtils.SLASHED_BOOK_ID) + } +} \ No newline at end of file