From 2a82483d2ed310fc65a2a83b0c03a97ab40ce944 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Fri, 11 Oct 2024 23:52:39 -0700 Subject: [PATCH 01/30] Werewolf agility course init --- .../werewolfagility/WerewolfAgilityCourse.kt | 151 ++++++++++++++++++ .../WerewolfAgilityGuardDialogue.kt | 50 ++++++ 2 files changed, 201 insertions(+) create mode 100644 Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt create mode 100644 Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt diff --git a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt new file mode 100644 index 000000000..8aa5fd13e --- /dev/null +++ b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt @@ -0,0 +1,151 @@ +package content.region.morytania.werewolfagility + +import core.api.* +import core.game.dialogue.FacialExpression +import core.game.global.action.ClimbActionHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.interaction.QueueStrength +import core.game.node.entity.impl.ForceMovement +import core.game.node.entity.skill.Skills +import core.game.node.item.GroundItem +import core.game.node.item.Item +import core.game.world.map.Direction +import core.game.world.map.Location +import core.game.world.update.flag.context.Animation +import org.rs09.consts.Items +import org.rs09.consts.NPCs +import org.rs09.consts.Scenery + +class WerewolfAgilityCourse : InteractionListener { + + companion object { + + private const val LAST_VISITED_STONE_TILE_KEY = "lastVisitedStoneTile" + + val steppingStones = listOf( + Location(3538, 9873, 0), + Location(3538, 9875, 0), // 1 + Location(3538, 9877, 0), // 2 + Location(3540, 9877, 0), // 3 + Location(3540, 9879, 0), // 4 + Location(3540, 9881, 0), // 5 + Location(3540, 9882, 0), + ) + + val endTile: Location = Location(3528, 9869, 0) + val midwayTile1: Location = Location(3528, 9890, 0) + val midwayTile2: Location = Location(3528, 9882, 0) + val failureLocation1: Location = Location(3526, 9887, 0) + val failureLocation2: Location = Location(3526, 9879, 0) + + + } + + override fun defineListeners() { + + on(Scenery.TRAPDOOR_5131, IntType.SCENERY, "open") { player, node -> + replaceScenery(node as core.game.node.scenery.Scenery, Scenery.TRAPDOOR_5132, 20) + return@on true + } + + // Ladder Down + on(Scenery.TRAPDOOR_5132, IntType.SCENERY, "climb-down") { player, node -> + if (!anyInEquipment(player, Items.RING_OF_CHAROS_4202, Items.RING_OF_CHAROSA_6465)) { + sendNPCDialogue(player, NPCs.WEREWOLF_1665, "You can't go down there, human. If it wasn't my duty to guard this trapdoor, I would be relieving you of the burden of your life right now.", FacialExpression.ANGRY) + } else { + sendNPCDialogue(player, NPCs.WEREWOLF_1665, "Good luck down there, my friend. Remember, to the west is the main agility course, while to the east is a skullball course. ", FacialExpression.FRIENDLY) + teleport(player, Location(3549, 9865, 0)) + } + return@on true + } + + // Ladder Up + on(Scenery.LADDER_5130, IntType.SCENERY, "climb-up") { player, node -> + teleport(player, Location(3543, 3463, 0)) + return@on true + } + + + // Stepping stones + on(Scenery.STEPPING_STONE_35996, IntType.SCENERY, "jump-to") { player, node -> + if (!hasLevelStat(player, Skills.AGILITY, 60)) { + sendDialogue(player, "You need an Agility level of at least 60 to do this.") + return@on false + } + var count = 0 + queueScript(player, 3, QueueStrength.SOFT) { + if (player.location != steppingStones[6] || count <= 5) { + lock(player, 3) + count++ + player.locks.lockTeleport(3) + ForceMovement.run(player, steppingStones[count], steppingStones[count+1], Animation(741), Animation(741), if (count == 2) Direction.EAST else Direction.NORTH, 20).endAnimation = Animation.RESET + return@queueScript delayScript(player, 3) + } + return@queueScript stopExecuting(player) + } + return@on true + } + + // Hurdles + on(intArrayOf(Scenery.HURDLE_5133, Scenery.HURDLE_5134, Scenery.HURDLE_5135), IntType.SCENERY, "jump") { player, node -> + if (!hasLevelStat(player, Skills.AGILITY, 60)) { + sendDialogue(player, "You need an Agility level of at least 60 to do this.") + return@on false + } + if (player.location.y < node.location.y) { + rewardXP(player, Skills.AGILITY, 20.0) + ForceMovement.run(player, player.location, player.location.transform(0, 2, 0), Animation(1603), Animation(1603), Direction.NORTH, 20).endAnimation = Animation.RESET + } + return@on true + } + + // Pipes + on(intArrayOf(Scenery.PIPE_5152), IntType.SCENERY, "squeeze-through") { player, node -> + if (!hasLevelStat(player, Skills.AGILITY, 60)) { + sendDialogue(player, "You need an Agility level of at least 60 to do this.") + return@on false + } + if (player.location.y < node.location.y) { + rewardXP(player, Skills.AGILITY, 15.0) + ForceMovement.run(player, player.location, player.location.transform(0, 5, 0), Animation(10580), Animation(844), Direction.NORTH, 10).endAnimation = Animation(10579) + } + return@on true + } + + // Skull slopes + on(intArrayOf(Scenery.SKULL_SLOPE_5136), IntType.SCENERY, "climb-up") { player, node -> + if (!hasLevelStat(player, Skills.AGILITY, 60)) { + sendDialogue(player, "You need an Agility level of at least 60 to do this.") + return@on false + } + if (player.location.x > node.location.x) { + rewardXP(player, Skills.AGILITY, 15.0) + ForceMovement.run(player, player.location, player.location.transform(-2, 0, 0), Animation(2049), Animation(2049), Direction.WEST, 10).endAnimation = Animation.RESET + } + return@on true + } + + // Zip line + on(intArrayOf(Scenery.ZIP_LINE_5139, Scenery.ZIP_LINE_5140, Scenery.ZIP_LINE_5141), IntType.SCENERY, "teeth-grip") { player, node -> + if (!hasLevelStat(player, Skills.AGILITY, 60)) { + sendDialogue(player, "You need an Agility level of at least 60 to do this.") + return@on false + } + forceMove(player, player.location, Location(3528, 9910, 0), 0,10) + face(player, Location(3528, 9900, 0)) + queueScript(player, 2, QueueStrength.SOFT) { + sendChat(player, "WAAAAAARRRGGGHHH!!!!!!") + ForceMovement.run(player, Location(3528, 9910, 0), endTile, Animation(1601), Animation(1602), Direction.SOUTH, 40).endAnimation = Animation.RESET + return@queueScript stopExecuting(player) + } + return@on true + } + } + + override fun defineDestinationOverrides() { + setDest(IntType.SCENERY, intArrayOf(Scenery.STEPPING_STONE_35996),"jump-to"){ player, node -> + return@setDest Location.create(3538, 9873, 0) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt new file mode 100644 index 000000000..73e59c90f --- /dev/null +++ b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt @@ -0,0 +1,50 @@ +package content.region.morytania.werewolfagility + +import core.api.* +import core.game.dialogue.DialogueBuilder +import core.game.dialogue.DialogueBuilderFile +import core.game.dialogue.DialoguePlugin +import core.game.dialogue.FacialExpression +import core.game.node.entity.player.Player +import core.plugin.Initializable +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +@Initializable +class WerewolfAgilityGuardDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, WerewolfAgilityGuardDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return WerewolfAgilityGuardDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.WEREWOLF_1665) + } +} + +class WerewolfAgilityGuardDialogueFile : DialogueBuilderFile() { + override fun create(b: DialogueBuilder) { + b.onPredicate { _ -> true } + .playerl(FacialExpression.FRIENDLY, "What's beneath the trapdoor?") + .branch { player -> if (anyInEquipment(player, Items.RING_OF_CHAROS_4202, Items.RING_OF_CHAROSA_6465)) { 1 } else { 0 } } + .let { branch -> + branch.onValue(0) + .npcl(FacialExpression.OLD_ANGRY1, "That's none of your business, human, and I'll never tell.") + .playerl(FacialExpression.FRIENDLY, "Oh, come on - I'm only curious.") + .npcl(FacialExpression.OLD_ANGRY1, "If it wasn't my duty to stand here and guard our agility course from the likes of you, I would be relieving you of your life right now.") + .playerl(FacialExpression.FRIENDLY, "So it's an agility course, then?") + .npcl(FacialExpression.OLD_ANGRY1, "No ... yes ... oh blast - you didn't hear me say anything, right?") + .playerl(FacialExpression.FRIENDLY, "No problem. Can I come in?") + .npcl(FacialExpression.OLD_ANGRY1, "No, human - it's werewolves only.") + .end() + + branch.onValue(1) + .npcl(FacialExpression.OLD_HAPPY, "It's an agility course designed for lycanthropes like ourselves, my friend.") + .playerl(FacialExpression.FRIENDLY, "Can I come in and use it?") + .npcl(FacialExpression.OLD_HAPPY, "Certainly. The cavern contains two courses: on the west side is a level 60 Agility Course, and on the east side is a level 25 Skullball Course.") + .end() + } + } +} \ No newline at end of file From 80bf2b166315246ad66e5bda46c959b410a3ba5a Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Fri, 11 Oct 2024 23:53:19 -0700 Subject: [PATCH 02/30] Werewolf agility course init2 --- Server/data/configs/npc_spawns.json | 20 +++++++++++++++++++ .../morytania/handlers/MorytaniaArea.kt | 2 -- .../AgilityBossDialogue.kt | 16 ++++++++------- 3 files changed, 29 insertions(+), 9 deletions(-) rename Server/src/main/content/region/morytania/{canifis/dialogue => werewolfagility}/AgilityBossDialogue.kt (65%) diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index e152ca446..2a20194c2 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -4087,6 +4087,26 @@ "npc_id": "1658", "loc_data": "{2595,3087,1,1,1}-" }, + { + "npc_id": "1660", + "loc_data": "{3549,9867,0,1,0}-" + }, + { + "npc_id": "1661", + "loc_data": "{3540,9873,0,0,1}-" + }, + { + "npc_id": "1662", + "loc_data": "{3554,9886,0,1,0}-{3560,9908,0,1,0}-{3565,9865,0,1,0}-{3572,9908,0,1,0}-{3573,9891,0,1,0}-{3577,9875,0,1,0}-" + }, + { + "npc_id": "1663", + "loc_data": "{3527,9909,0,1,0}-{3533,9912,0,1,0}-{3540,9892,0,1,0}-{3540,9902,0,1,0}-" + }, + { + "npc_id": "1664", + "loc_data": "{3528,9865,0,1,0}-" + }, { "npc_id": "1665", "loc_data": "{3544,3462,0,0,1}-" diff --git a/Server/src/main/content/region/morytania/handlers/MorytaniaArea.kt b/Server/src/main/content/region/morytania/handlers/MorytaniaArea.kt index 5edfa55f7..d3e2439bc 100644 --- a/Server/src/main/content/region/morytania/handlers/MorytaniaArea.kt +++ b/Server/src/main/content/region/morytania/handlers/MorytaniaArea.kt @@ -17,14 +17,12 @@ class MorytaniaArea : MapArea { override fun defineAreaBorders(): Array { return arrayOf( ZoneBorders(3426, 3191, 3715, 3588), //Morytania overworld - ZoneBorders(3520, 9856, 3583, 9919) //Werewolf agility course ) } override fun areaEnter(entity: Entity) { if (entity is Player && entity !is AIPlayer && ( !isQuestComplete(entity, Quests.PRIEST_IN_PERIL) || //not allowed to be anywhere in Morytania - defineAreaBorders()[1].insideBorder(entity) //Werewolf agility course is not implemented )) { kickThemOut(entity) } diff --git a/Server/src/main/content/region/morytania/canifis/dialogue/AgilityBossDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt similarity index 65% rename from Server/src/main/content/region/morytania/canifis/dialogue/AgilityBossDialogue.kt rename to Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt index 5b476b620..b81412e05 100644 --- a/Server/src/main/content/region/morytania/canifis/dialogue/AgilityBossDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt @@ -1,10 +1,12 @@ -package content.region.morytania.canifis.dialogue +package content.region.morytania.werewolfagility +import core.api.anyInEquipment import core.game.dialogue.DialoguePlugin import core.game.dialogue.FacialExpression import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.plugin.Initializable +import org.rs09.consts.Items /** * @author qmqz @@ -16,29 +18,29 @@ class AgilityBossDialogue(player: Player? = null) : DialoguePlugin(player){ override fun open(vararg args: Any?): Boolean { npc = args[0] as NPC - if(player.equipment.contains(4202,1)) { + if(anyInEquipment(player, Items.RING_OF_CHAROS_4202, Items.RING_OF_CHAROSA_6465)) { player(FacialExpression.ASKING,"How do I use the agility course?").also { stage = 0 } } else { - npc(FacialExpression.CHILD_SUSPICIOUS,"Grrr - you don't belong in here, human!").also { stage = 99 } + npc(FacialExpression.OLD_NORMAL,"Grrr - you don't belong in here, human!").also { stage = 99 } } return true } override fun handle(interfaceId: Int, buttonId: Int): Boolean { when(stage){ - 0 -> npc(FacialExpression.CHILD_NORMAL,"I'll throw you a stick, which you need to", + 0 -> npc(FacialExpression.OLD_NORMAL,"I'll throw you a stick, which you need to", "fetch as quickly as possible, ", "from the area beyond the pipes.").also { stage++ } - 1 -> npc(FacialExpression.CHILD_NORMAL,"Be wary of the deathslide - you must hang by your teeth,", + 1 -> npc(FacialExpression.OLD_NORMAL,"Be wary of the deathslide - you must hang by your teeth,", "and if your strength is not up to the job you will", "fall into a pit of spikes. Also, I would advise not", "carrying too much extra weight.").also { stage++ } - 2 -> npc(FacialExpression.CHILD_NORMAL,"Bring the stick back to the werewolf waiting at", + 2 -> npc(FacialExpression.OLD_NORMAL,"Bring the stick back to the werewolf waiting at", "the end of the death slide to get your agility bonus.").also { stage++ } - 3 ->npc(FacialExpression.CHILD_NORMAL,"I will throw your stick as soon as you jump onto the", + 3 ->npc(FacialExpression.OLD_NORMAL,"I will throw your stick as soon as you jump onto the", "first stone.").also { stage = 99 } 99 -> end() From 420de8162ea40b89870e57c8ffb2904e13afb9ef Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Mon, 14 Oct 2024 19:02:01 -0700 Subject: [PATCH 03/30] Some progress. --- .../werewolfagility/WerewolfAgilityCourse.kt | 63 ++++++++++++++++--- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt index 8aa5fd13e..dc57b23db 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt @@ -13,6 +13,7 @@ import core.game.node.item.Item import core.game.world.map.Direction import core.game.world.map.Location import core.game.world.update.flag.context.Animation +import core.tools.RandomFunction import org.rs09.consts.Items import org.rs09.consts.NPCs import org.rs09.consts.Scenery @@ -33,7 +34,8 @@ class WerewolfAgilityCourse : InteractionListener { Location(3540, 9882, 0), ) - val endTile: Location = Location(3528, 9869, 0) + val startTile: Location = Location(3528, 9910, 0) + val endTile: Location = Location(3528, 9873, 0) val midwayTile1: Location = Location(3528, 9890, 0) val midwayTile2: Location = Location(3528, 9882, 0) val failureLocation1: Location = Location(3526, 9887, 0) @@ -74,8 +76,9 @@ class WerewolfAgilityCourse : InteractionListener { return@on false } var count = 0 + rewardXP(player, Skills.AGILITY, 50.0) queueScript(player, 3, QueueStrength.SOFT) { - if (player.location != steppingStones[6] || count <= 5) { + if (!player.location.equals(steppingStones[6]) || count <= 5) { lock(player, 3) count++ player.locks.lockTeleport(3) @@ -94,7 +97,7 @@ class WerewolfAgilityCourse : InteractionListener { return@on false } if (player.location.y < node.location.y) { - rewardXP(player, Skills.AGILITY, 20.0) + rewardXP(player, Skills.AGILITY, 60.0) ForceMovement.run(player, player.location, player.location.transform(0, 2, 0), Animation(1603), Animation(1603), Direction.NORTH, 20).endAnimation = Animation.RESET } return@on true @@ -120,7 +123,7 @@ class WerewolfAgilityCourse : InteractionListener { return@on false } if (player.location.x > node.location.x) { - rewardXP(player, Skills.AGILITY, 15.0) + rewardXP(player, Skills.AGILITY, 25.0) ForceMovement.run(player, player.location, player.location.transform(-2, 0, 0), Animation(2049), Animation(2049), Direction.WEST, 10).endAnimation = Animation.RESET } return@on true @@ -132,12 +135,56 @@ class WerewolfAgilityCourse : InteractionListener { sendDialogue(player, "You need an Agility level of at least 60 to do this.") return@on false } + rewardXP(player, Skills.AGILITY, 200.0) + + // This is up to my decision on whether to torture you. + // "Below 0 it makes no further difference." (Referring to weight) + // "With level 80 in Agility and Strength and a weight of 2 kg or lower, this obstacle will never be failed." + + var totalSuccess = 1.0 + if (getDynLevel(player, Skills.AGILITY) >= 80 && getDynLevel(player, Skills.STRENGTH) >= 80) { + val weightSuccess = (102.0 - Math.max(player.settings.weight, 0.0)) / 100 // 102% chance, minus 1% per weight gain. Wearing lover or equal to 2kg will be 100%. + totalSuccess *= weightSuccess + } else { + val agilitySuccess = RandomFunction.getSkillSuccessChance(0.0, 320.0, 60) // 0 at lvl1, 256 at lvl80 + val strengthSuccess = RandomFunction.getSkillSuccessChance(0.0, 320.0, 60) // 0 at lvl1, 256 at lvl80 + val weightSuccess = (80.0 - Math.max(player.settings.weight, 0.0)) / 100 // 80% chance, minus 1% per weight gain. + totalSuccess *= agilitySuccess + totalSuccess *= strengthSuccess + totalSuccess *= weightSuccess + } + + var finalTile = endTile + // roll a number, between 0-totalSuccess means you succeed, otherwise you fail. + if(RandomFunction.random(0.0, 100.0) < totalSuccess) { + + } else { + // based on total success, find where to land. + 100 - totalSuccess + } + + forceMove(player, player.location, Location(3528, 9910, 0), 0,10) face(player, Location(3528, 9900, 0)) - queueScript(player, 2, QueueStrength.SOFT) { - sendChat(player, "WAAAAAARRRGGGHHH!!!!!!") - ForceMovement.run(player, Location(3528, 9910, 0), endTile, Animation(1601), Animation(1602), Direction.SOUTH, 40).endAnimation = Animation.RESET - return@queueScript stopExecuting(player) + queueScript(player, 2, QueueStrength.SOFT) { stage -> + when (stage) { + 0 -> { + animate(player, 1601) + sendMessage(player, "You bravely cling on to the death slide by your teeth ...") + return@queueScript delayScript(player, 2) + } + 1 -> { + sendChat(player, "WAAAAAARRRGGGHHH!!!!!!") + ForceMovement.run(player, startTile, finalTile, Animation(1602), Animation(1602), Direction.SOUTH, 40).endAnimation = Animation.RESET + return@queueScript delayScript(player, 2) + } + 2 -> { + return@queueScript stopExecuting(player) + } + else -> return@queueScript stopExecuting(player) + } + + } return@on true } From ea0f810af17a8b3e9ae0bbec1611df0ebe004864 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Fri, 8 Nov 2024 22:48:15 -0800 Subject: [PATCH 04/30] Found chat heads for werewolves. --- .../werewolfagility/AgilityBossDialogue.kt | 11 ++++--- .../werewolfagility/SkullballBossDialogue.kt | 33 +++++++++++++++++++ .../werewolfagility/WerewolfAgilityCourse.kt | 4 +-- .../WerewolfAgilityGuardDialogue.kt | 23 +++++++++---- .../core/game/dialogue/FacialExpression.java | 7 ++++ 5 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt diff --git a/Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt index b81412e05..d0f6ae331 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt @@ -10,6 +10,7 @@ import org.rs09.consts.Items /** * @author qmqz + * https://www.youtube.com/watch?v=9u_qJW1eKR0 */ @Initializable @@ -21,26 +22,26 @@ class AgilityBossDialogue(player: Player? = null) : DialoguePlugin(player){ if(anyInEquipment(player, Items.RING_OF_CHAROS_4202, Items.RING_OF_CHAROSA_6465)) { player(FacialExpression.ASKING,"How do I use the agility course?").also { stage = 0 } } else { - npc(FacialExpression.OLD_NORMAL,"Grrr - you don't belong in here, human!").also { stage = 99 } + npc(FacialExpression.WEREWOLF_NEUTRAL,"Grrr - you don't belong in here, human!").also { stage = 99 } } return true } override fun handle(interfaceId: Int, buttonId: Int): Boolean { when(stage){ - 0 -> npc(FacialExpression.OLD_NORMAL,"I'll throw you a stick, which you need to", + 0 -> npc(FacialExpression.WEREWOLF_NEUTRAL,"I'll throw you a stick, which you need to", "fetch as quickly as possible, ", "from the area beyond the pipes.").also { stage++ } - 1 -> npc(FacialExpression.OLD_NORMAL,"Be wary of the deathslide - you must hang by your teeth,", + 1 -> npc(FacialExpression.WEREWOLF_NEUTRAL,"Be wary of the deathslide - you must hang by your teeth,", "and if your strength is not up to the job you will", "fall into a pit of spikes. Also, I would advise not", "carrying too much extra weight.").also { stage++ } - 2 -> npc(FacialExpression.OLD_NORMAL,"Bring the stick back to the werewolf waiting at", + 2 -> npc(FacialExpression.WEREWOLF_NEUTRAL,"Bring the stick back to the werewolf waiting at", "the end of the death slide to get your agility bonus.").also { stage++ } - 3 ->npc(FacialExpression.OLD_NORMAL,"I will throw your stick as soon as you jump onto the", + 3 ->npc(FacialExpression.WEREWOLF_NEUTRAL,"I will throw your stick as soon as you jump onto the", "first stone.").also { stage = 99 } 99 -> end() diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt new file mode 100644 index 000000000..6975669e5 --- /dev/null +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt @@ -0,0 +1,33 @@ +package content.region.morytania.werewolfagility + +import core.api.isQuestComplete +import core.api.toIntArray +import core.game.dialogue.DialoguePlugin +import core.game.dialogue.FacialExpression +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import core.tools.START_DIALOGUE +import org.rs09.consts.NPCs + +@Initializable +class SkullballBossDialogue(player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int) : Boolean { + when (stage) { + START_DIALOGUE -> playerl(FacialExpression.THINKING, "What are the instructions for using the skullball course?").also { stage++ } + 1 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "The skullball comes out of one of these four spawnholes. Just kick the ball through the middle of each goal, through the skeleton's feet.").also { stage++ } + 2 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "There are 10 goals, which you must complete in order, and one final goal.").also { stage++ } + 3 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "An arrow will point to your ball, just in case lots of people are using the course at the same time as yourself.").also { stage++ } + 4 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "The better your time, the more agillity XP you will be awarded. The timer starts when you score your first goal.").also { stage = END_DIALOGUE } + } + return true + } + + override fun newInstance(player: Player?): DialoguePlugin { + return SkullballBossDialogue(player) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.SKULLBALL_BOSS_1660) + } +} diff --git a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt index dc57b23db..4ad410e92 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt @@ -54,9 +54,9 @@ class WerewolfAgilityCourse : InteractionListener { // Ladder Down on(Scenery.TRAPDOOR_5132, IntType.SCENERY, "climb-down") { player, node -> if (!anyInEquipment(player, Items.RING_OF_CHAROS_4202, Items.RING_OF_CHAROSA_6465)) { - sendNPCDialogue(player, NPCs.WEREWOLF_1665, "You can't go down there, human. If it wasn't my duty to guard this trapdoor, I would be relieving you of the burden of your life right now.", FacialExpression.ANGRY) + sendNPCDialogue(player, NPCs.WEREWOLF_1665, "You can't go down there, human. If it wasn't my duty to guard this trapdoor, I would be relieving you of the burden of your life right now.", FacialExpression.WEREWOLF_NEUTRAL) } else { - sendNPCDialogue(player, NPCs.WEREWOLF_1665, "Good luck down there, my friend. Remember, to the west is the main agility course, while to the east is a skullball course. ", FacialExpression.FRIENDLY) + sendNPCDialogue(player, NPCs.WEREWOLF_1665, "Good luck down there, my friend. Remember, to the west is the main agility course, while to the east is a skullball course. ", FacialExpression.WEREWOLF_NEUTRAL) teleport(player, Location(3549, 9865, 0)) } return@on true diff --git a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt index 73e59c90f..695103149 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt @@ -31,19 +31,28 @@ class WerewolfAgilityGuardDialogueFile : DialogueBuilderFile() { .branch { player -> if (anyInEquipment(player, Items.RING_OF_CHAROS_4202, Items.RING_OF_CHAROSA_6465)) { 1 } else { 0 } } .let { branch -> branch.onValue(0) - .npcl(FacialExpression.OLD_ANGRY1, "That's none of your business, human, and I'll never tell.") +// .let { builder -> +// val returnJoin = b.placeholder() +// returnJoin.builder() +// .manualStage { _, player, _, _ -> +// sendNPCDialogue(player, NPCs.WEREWOLF_1665, "Face. " + expCount, expCount) +// expCount++ +// } +// .goto(returnJoin) +// } + .npcl(FacialExpression.WEREWOLF_NEUTRAL, "That's none of your business, human, and I'll never tell.") .playerl(FacialExpression.FRIENDLY, "Oh, come on - I'm only curious.") - .npcl(FacialExpression.OLD_ANGRY1, "If it wasn't my duty to stand here and guard our agility course from the likes of you, I would be relieving you of your life right now.") - .playerl(FacialExpression.FRIENDLY, "So it's an agility course, then?") - .npcl(FacialExpression.OLD_ANGRY1, "No ... yes ... oh blast - you didn't hear me say anything, right?") + .npcl(FacialExpression.WEREWOLF_NEUTRAL, "If it wasn't my duty to stand here and guard our agility course from the likes of you, I would be relieving you of your life right now.") + .playerl(FacialExpression.THINKING, "So it's an agility course, then?") + .npcl(FacialExpression.WEREWOLF_SUSPICIOUS, "No ... yes ... oh blast - you didn't hear me say anything, right?") .playerl(FacialExpression.FRIENDLY, "No problem. Can I come in?") - .npcl(FacialExpression.OLD_ANGRY1, "No, human - it's werewolves only.") + .npcl(FacialExpression.WEREWOLF_NEUTRAL, "No, human - it's werewolves only.") .end() branch.onValue(1) - .npcl(FacialExpression.OLD_HAPPY, "It's an agility course designed for lycanthropes like ourselves, my friend.") + .npcl(FacialExpression.WEREWOLF_NEUTRAL, "It's an agility course designed for lycanthropes like ourselves, my friend.") .playerl(FacialExpression.FRIENDLY, "Can I come in and use it?") - .npcl(FacialExpression.OLD_HAPPY, "Certainly. The cavern contains two courses: on the west side is a level 60 Agility Course, and on the east side is a level 25 Skullball Course.") + .npcl(FacialExpression.WEREWOLF_HAPPY, "Certainly. The cavern contains two courses: on the west side is a level 60 Agility Course, and on the east side is a level 25 Skullball Course.") .end() } } diff --git a/Server/src/main/core/game/dialogue/FacialExpression.java b/Server/src/main/core/game/dialogue/FacialExpression.java index 6d415cf0f..5153ad7c0 100644 --- a/Server/src/main/core/game/dialogue/FacialExpression.java +++ b/Server/src/main/core/game/dialogue/FacialExpression.java @@ -89,6 +89,13 @@ public enum FacialExpression { STRUGGLE(9865), //TODO: More? //9855-9857 are like disgusted? does it just repeat after this? + //Chatheads for werewolves + WEREWOLF_SAD(6550), + WEREWOLF_NEUTRAL(6551), + WEREWOLF_SUSPICIOUS(6552), + WEREWOLF_THINKING(6553), + WEREWOLF_HAPPY(6555), + //Child Chathead? CHILD_ANGRY(7168), CHILD_SIDE_EYE(7169), From 81802621970a2a5a92f1a55f037d3c5e77d5784d Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Thu, 14 Nov 2024 00:22:52 -0800 Subject: [PATCH 05/30] More skullball dialogue --- .../werewolfagility/AgilityTrainerDialogue.kt | 59 +++++++++++++++++++ .../werewolfagility/SkullballBossDialogue.kt | 18 ++++-- .../SkullballTrainerDialogue.kt | 31 ++++++++++ .../werewolfagility/WerewolfAgilityCourse.kt | 5 +- 4 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 Server/src/main/content/region/morytania/werewolfagility/AgilityTrainerDialogue.kt create mode 100644 Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt diff --git a/Server/src/main/content/region/morytania/werewolfagility/AgilityTrainerDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityTrainerDialogue.kt new file mode 100644 index 000000000..ddc62baaa --- /dev/null +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityTrainerDialogue.kt @@ -0,0 +1,59 @@ +package content.region.morytania.werewolfagility + +import core.api.anyInEquipment +import core.game.dialogue.DialoguePlugin +import core.game.dialogue.FacialExpression +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.plugin.Initializable +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +/** + * https://www.youtube.com/watch?v=mIKPpc30XBQ - What stick + */ + +@Initializable +class AgilityTrainerDialogue(player: Player? = null) : DialoguePlugin(player){ + + override fun open(vararg args: Any?): Boolean { + npc = args[0] as NPC + + if(anyInEquipment(player, Items.RING_OF_CHAROS_4202, Items.RING_OF_CHAROSA_6465)) { + player(FacialExpression.ASKING,"How do I use the agility course?").also { stage = 0 } + } else { + npc(FacialExpression.WEREWOLF_NEUTRAL,"Grrr - you don't belong in here, human!").also { stage = 99 } + } + return true + } + + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + when(stage){ + 0 -> npc(FacialExpression.WEREWOLF_NEUTRAL,"I'll throw you a stick, which you need to", + "fetch as quickly as possible, ", + "from the area beyond the pipes.").also { stage++ } + + 1 -> npc(FacialExpression.WEREWOLF_NEUTRAL,"Be wary of the deathslide - you must hang by your teeth,", + "and if your strength is not up to the job you will", + "fall into a pit of spikes. Also, I would advise not", + "carrying too much extra weight.").also { stage++ } + + 2 -> npc(FacialExpression.WEREWOLF_NEUTRAL,"Bring the stick back to the werewolf waiting at", + "the end of the death slide to get your agility bonus.").also { stage++ } + + 3 ->npc(FacialExpression.WEREWOLF_NEUTRAL,"I will throw your stick as soon as you jump onto the", + "first stone.").also { stage = 99 } + + 99 -> end() + } + return true + } + + override fun newInstance(player: Player?): DialoguePlugin { + return AgilityTrainerDialogue(player) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.AGILITY_TRAINER_1664) + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt index 6975669e5..1cce3dabb 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt @@ -14,11 +14,19 @@ import org.rs09.consts.NPCs class SkullballBossDialogue(player: Player? = null) : DialoguePlugin(player) { override fun handle(interfaceId: Int, buttonId: Int) : Boolean { when (stage) { - START_DIALOGUE -> playerl(FacialExpression.THINKING, "What are the instructions for using the skullball course?").also { stage++ } - 1 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "The skullball comes out of one of these four spawnholes. Just kick the ball through the middle of each goal, through the skeleton's feet.").also { stage++ } - 2 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "There are 10 goals, which you must complete in order, and one final goal.").also { stage++ } - 3 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "An arrow will point to your ball, just in case lots of people are using the course at the same time as yourself.").also { stage++ } - 4 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "The better your time, the more agillity XP you will be awarded. The timer starts when you score your first goal.").also { stage = END_DIALOGUE } + START_DIALOGUE -> options("I would like to do the skullball course.", "What are the instructions for using the skullball course?").also { stage++ } + 2 -> when (buttonId) { + 1 -> playerl(FacialExpression.NEUTRAL, "I would like to do the skullball course.").also { stage = 5 } + 2 -> playerl(FacialExpression.THINKING, "What are the instructions for using the skullball course?").also { stage = 11 } + } + 5 -> playerl(FacialExpression.NEUTRAL, "I would like to do the skullball course.").also { stage++ } + 6 -> end().also { + // Skullball comes out. + } + 11 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "The skullball comes out of one of these four spawnholes. Just kick the ball through the middle of each goal, through the skeleton's feet.").also { stage++ } + 12 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "There are 10 goals, which you must complete in order, and one final goal.").also { stage++ } + 13 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "An arrow will point to your ball, just in case lots of people are using the course at the same time as yourself.").also { stage++ } + 14 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "The better your time, the more agillity XP you will be awarded. The timer starts when you score your first goal.").also { stage = END_DIALOGUE } } return true } diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt new file mode 100644 index 000000000..e6f4ade9c --- /dev/null +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt @@ -0,0 +1,31 @@ +package content.region.morytania.werewolfagility + +import core.api.isQuestComplete +import core.api.toIntArray +import core.game.dialogue.DialoguePlugin +import core.game.dialogue.FacialExpression +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import core.tools.START_DIALOGUE +import org.rs09.consts.NPCs + +@Initializable +class SkullballTrainerDialogue(player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int) : Boolean { + when (stage) { + START_DIALOGUE -> playerl(FacialExpression.THINKING, "What is this place?").also { stage++ } + 1 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "This is the Skullball Course").also { stage++ } + 2 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "Go talk to the boss at the beginning of the course if you'd like a go.").also { stage = END_DIALOGUE } + } + return true + } + + override fun newInstance(player: Player?): DialoguePlugin { + return SkullballTrainerDialogue(player) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.SKULLBALL_TRAINER_1662) + } +} diff --git a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt index 4ad410e92..817b60186 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt @@ -34,12 +34,15 @@ class WerewolfAgilityCourse : InteractionListener { Location(3540, 9882, 0), ) + // https://www.youtube.com/watch?v=JN_1c7r9PVo - Popular courses GOOD! + //https://www.youtube.com/watch?v=fmzzLy5fXK4 + //https://www.youtube.com/watch?v=RnhjHPuae3Q - best with all 3 fall locations. val startTile: Location = Location(3528, 9910, 0) - val endTile: Location = Location(3528, 9873, 0) val midwayTile1: Location = Location(3528, 9890, 0) val midwayTile2: Location = Location(3528, 9882, 0) val failureLocation1: Location = Location(3526, 9887, 0) val failureLocation2: Location = Location(3526, 9879, 0) + val endTile: Location = Location(3528, 9873, 0) } From f59142029a57cf41b55c3237648ac667f6dfc1bc Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sun, 9 Feb 2025 00:10:39 -0800 Subject: [PATCH 06/30] Oops --- .../src/main/content/region/morytania/handlers/MorytaniaArea.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/region/morytania/handlers/MorytaniaArea.kt b/Server/src/main/content/region/morytania/handlers/MorytaniaArea.kt index d3e2439bc..9241c19e2 100644 --- a/Server/src/main/content/region/morytania/handlers/MorytaniaArea.kt +++ b/Server/src/main/content/region/morytania/handlers/MorytaniaArea.kt @@ -22,7 +22,7 @@ class MorytaniaArea : MapArea { override fun areaEnter(entity: Entity) { if (entity is Player && entity !is AIPlayer && ( - !isQuestComplete(entity, Quests.PRIEST_IN_PERIL) || //not allowed to be anywhere in Morytania + !isQuestComplete(entity, Quests.PRIEST_IN_PERIL) //not allowed to be anywhere in Morytania )) { kickThemOut(entity) } From a783d0d7b86a4b15a2a450c1e8a17766dc81b488 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sun, 9 Feb 2025 01:57:55 -0800 Subject: [PATCH 07/30] Skullball works? --- .../werewolfagility/SkullballBossDialogue.kt | 6 +- .../werewolfagility/SkullballCourse.kt | 58 +++++++++++++++++++ .../morytania/werewolfagility/SkullballNPC.kt | 18 ++++++ 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt create mode 100644 Server/src/main/content/region/morytania/werewolfagility/SkullballNPC.kt diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt index 1cce3dabb..f2bf92056 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt @@ -1,7 +1,6 @@ package content.region.morytania.werewolfagility -import core.api.isQuestComplete -import core.api.toIntArray +import core.api.* import core.game.dialogue.DialoguePlugin import core.game.dialogue.FacialExpression import core.game.node.entity.player.Player @@ -15,12 +14,13 @@ class SkullballBossDialogue(player: Player? = null) : DialoguePlugin(player) { override fun handle(interfaceId: Int, buttonId: Int) : Boolean { when (stage) { START_DIALOGUE -> options("I would like to do the skullball course.", "What are the instructions for using the skullball course?").also { stage++ } - 2 -> when (buttonId) { + 1 -> when (buttonId) { 1 -> playerl(FacialExpression.NEUTRAL, "I would like to do the skullball course.").also { stage = 5 } 2 -> playerl(FacialExpression.THINKING, "What are the instructions for using the skullball course?").also { stage = 11 } } 5 -> playerl(FacialExpression.NEUTRAL, "I would like to do the skullball course.").also { stage++ } 6 -> end().also { + SkullballCourse.startBall(player) // Skullball comes out. } 11 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "The skullball comes out of one of these four spawnholes. Just kick the ball through the middle of each goal, through the skeleton's feet.").also { stage++ } diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt new file mode 100644 index 000000000..d65fb8fc6 --- /dev/null +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -0,0 +1,58 @@ +package content.region.morytania.werewolfagility + +import core.api.* +import core.game.interaction.InteractionListener +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.world.map.Direction +import core.game.world.map.Location +import org.rs09.consts.NPCs +import org.rs09.consts.Scenery + +class SkullballCourse : InteractionListener { + + companion object { + val startingBall = arrayOf( + Location.create(3552, 9860), + Location.create(3554, 9861), + Location.create(3555, 9861), + Location.create(3557, 9860), + ) + + fun startBall(player: Player) { + if (getAttribute(player, "tempAttribute", null) == null) { + val npc = NPC(NPCs.SKULLBALL_1659) + npc.isRespawn = false + npc.isWalks = true + npc.location = startingBall.random() + npc.direction = Direction.NORTH + npc.init() + registerHintIcon(player, npc) + //forceWalk(npc, npc.location.transform(0, 4, 0), "DUMB") + val newLoc = npc.location.transform(Location(0, 4, 0)) + npc.walkingQueue.reset() + npc.lock(5) + npc.walkingQueue.addPath(newLoc.x, newLoc.y) + /* + lock(player, 5) + sendMessage(player, "You carefully walk across the rickety bridge...") + player.walkingQueue.reset() + player.walkingQueue.addPath(2476, 4972) + */ + } + } + } + + override fun defineListeners() { + + on(NPCs.SKULLBALL_1659, NPC, "tap") { player, node -> + val npc = node as NPC + val distance = Location.getDelta(player.location, node.location) + val newLoc = node.location.transform(Location(distance.x * 2, distance.y * 2)) + animate(player, 1606) + npc.walkingQueue.reset() + npc.walkingQueue.addPath(newLoc.x, newLoc.y) + return@on true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballNPC.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballNPC.kt new file mode 100644 index 000000000..5769e7133 --- /dev/null +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballNPC.kt @@ -0,0 +1,18 @@ +package content.region.morytania.werewolfagility + +import core.game.node.entity.npc.AbstractNPC +import core.game.node.entity.player.Player +import core.game.world.map.Location +import org.rs09.consts.NPCs + +class SkullballNPC(id: Int = 0, val player: Player?, location: Location? = null) : AbstractNPC(id, location) { + var clearTime = 0 + // Technically not constructed this way since this is invoked on load. + override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { + return SkullballNPC(id, null, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.SKULLBALL_1659) + } +} \ No newline at end of file From afc5b47dffbff9531a237d65d104237a4acc5185 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sun, 9 Feb 2025 14:51:51 -0800 Subject: [PATCH 08/30] Skullball can now be kicked --- .../werewolfagility/SkullballBossDialogue.kt | 4 +- .../werewolfagility/SkullballCourse.kt | 87 +++++++++++++++---- 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt index f2bf92056..203ed68cf 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt @@ -18,10 +18,8 @@ class SkullballBossDialogue(player: Player? = null) : DialoguePlugin(player) { 1 -> playerl(FacialExpression.NEUTRAL, "I would like to do the skullball course.").also { stage = 5 } 2 -> playerl(FacialExpression.THINKING, "What are the instructions for using the skullball course?").also { stage = 11 } } - 5 -> playerl(FacialExpression.NEUTRAL, "I would like to do the skullball course.").also { stage++ } - 6 -> end().also { + 5 -> end().also { SkullballCourse.startBall(player) - // Skullball comes out. } 11 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "The skullball comes out of one of these four spawnholes. Just kick the ball through the middle of each goal, through the skeleton's feet.").also { stage++ } 12 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "There are 10 goals, which you must complete in order, and one final goal.").also { stage++ } diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index d65fb8fc6..e691abeb8 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -2,45 +2,61 @@ package content.region.morytania.werewolfagility import core.api.* import core.game.interaction.InteractionListener +import core.game.node.entity.Entity import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.world.map.Direction import core.game.world.map.Location +import core.game.world.map.path.Pathfinder +import core.game.world.map.zone.ZoneBorders +import core.game.world.map.zone.ZoneRestriction import org.rs09.consts.NPCs -import org.rs09.consts.Scenery -class SkullballCourse : InteractionListener { +class SkullballCourse : InteractionListener, MapArea { companion object { + val attributeSkullballInstance = "skullball-instance" + val attributeSkullballCurrentGoal = "skullball-currentgoal" + val startingBall = arrayOf( - Location.create(3552, 9860), - Location.create(3554, 9861), - Location.create(3555, 9861), - Location.create(3557, 9860), + Location.create(3552, 9859), + Location.create(3554, 9860), + Location.create(3555, 9860), + Location.create(3557, 9859), ) fun startBall(player: Player) { if (getAttribute(player, "tempAttribute", null) == null) { val npc = NPC(NPCs.SKULLBALL_1659) npc.isRespawn = false - npc.isWalks = true + npc.isWalks = false npc.location = startingBall.random() npc.direction = Direction.NORTH + npc.walkRadius = 100 npc.init() registerHintIcon(player, npc) - //forceWalk(npc, npc.location.transform(0, 4, 0), "DUMB") - val newLoc = npc.location.transform(Location(0, 4, 0)) - npc.walkingQueue.reset() npc.lock(5) + // Force walk doesn't work here because this npc isn't flagged to be forced walked. + npc.walkingQueue.reset() + val newLoc = npc.location.transform(Location(0, 4, 0)) npc.walkingQueue.addPath(newLoc.x, newLoc.y) - /* - lock(player, 5) - sendMessage(player, "You carefully walk across the rickety bridge...") - player.walkingQueue.reset() - player.walkingQueue.addPath(2476, 4972) - */ } } + + /** Generates a movement queue for a kicked/pushed thing. Bounces against walls. **/ + fun generatePath(npc: NPC, kickDelta: Location, distanceToMove: Int) { + npc.walkingQueue.reset() + + val startingLoc = npc.location + var distanceLeft = distanceToMove + while(distanceLeft > 0) { + //val walkingFlag: Int = startingLoc.getDefinition().getWalkingFlag() + + distanceLeft--; + } + + //npc.walkingQueue.addPath(newLoc.x, newLoc.y) + } } override fun defineListeners() { @@ -48,11 +64,48 @@ class SkullballCourse : InteractionListener { on(NPCs.SKULLBALL_1659, NPC, "tap") { player, node -> val npc = node as NPC val distance = Location.getDelta(player.location, node.location) - val newLoc = node.location.transform(Location(distance.x * 2, distance.y * 2)) + val newLoc = node.location.transform(Location(distance.x * 1, distance.y * 1)) animate(player, 1606) npc.walkingQueue.reset() npc.walkingQueue.addPath(newLoc.x, newLoc.y) return@on true } + + on(NPCs.SKULLBALL_1659, NPC, "kick") { player, node -> + val npc = node as NPC + val distance = Location.getDelta(player.location, node.location) + val newLoc = node.location.transform(Location(distance.x * 4, distance.y * 4)) + animate(player, 1606) + npc.walkingQueue.reset() + npc.walkingQueue.addPath(newLoc.x, newLoc.y) + return@on true + } + + on(NPCs.SKULLBALL_1659, NPC, "shoot") { player, node -> + val npc = node as NPC + val distance = Location.getDelta(player.location, node.location) + val newLoc = node.location.transform(Location(distance.x * 9, distance.y * 9)) + animate(player, 1606) + println(Pathfinder.find(npc, newLoc, false, Pathfinder.DUMB).isSuccessful) + npc.walkingQueue.reset() + npc.walkingQueue.addPath(newLoc.x, newLoc.y) + return@on true + } + } + + override fun defineAreaBorders(): Array { + return arrayOf(ZoneBorders(3555,9870,3555,9870)) + } + + override fun getRestrictions(): Array { + return arrayOf() + } + + override fun areaEnter(entity: Entity) { + } + + override fun areaLeave(entity: Entity, logout: Boolean) { + val skullballGoal = getScenery(Location(3555,9870)) + animateScenery(skullballGoal!!, 1598) // anim 1599 is when skullball enters from behind. } } \ No newline at end of file From f336b224028df017aa0cb99dbfcbd99878bb0be8 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Tue, 11 Feb 2025 16:36:31 -0800 Subject: [PATCH 09/30] Ball bounces off walls now. --- .../werewolfagility/SkullballCourse.kt | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index e691abeb8..f07308a6c 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -7,6 +7,7 @@ import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.world.map.Direction import core.game.world.map.Location +import core.game.world.map.RegionManager import core.game.world.map.path.Pathfinder import core.game.world.map.zone.ZoneBorders import core.game.world.map.zone.ZoneRestriction @@ -47,54 +48,57 @@ class SkullballCourse : InteractionListener, MapArea { fun generatePath(npc: NPC, kickDelta: Location, distanceToMove: Int) { npc.walkingQueue.reset() - val startingLoc = npc.location - var distanceLeft = distanceToMove - while(distanceLeft > 0) { - //val walkingFlag: Int = startingLoc.getDefinition().getWalkingFlag() - - distanceLeft--; + var currentKickDelta = kickDelta + var destinationLocation = npc.location + for (i in 1..distanceToMove) { + destinationLocation = destinationLocation.transform(currentKickDelta) + if (!RegionManager.isTeleportPermitted(destinationLocation)) { + currentKickDelta = Location(-currentKickDelta.x, -currentKickDelta.y, currentKickDelta.z) + destinationLocation = destinationLocation.transform(currentKickDelta) + npc.walkingQueue.addPath(destinationLocation.x, destinationLocation.y) + destinationLocation = destinationLocation.transform(currentKickDelta) + } + println("Loc is landscape" + destinationLocation.x + " " + destinationLocation.y + "KICKDELTA" + currentKickDelta.x + " " + currentKickDelta.y) } - - //npc.walkingQueue.addPath(newLoc.x, newLoc.y) + npc.walkingQueue.addPath(destinationLocation.x, destinationLocation.y) + println("Loc is final" + destinationLocation.x + " " + destinationLocation.y) } } override fun defineListeners() { on(NPCs.SKULLBALL_1659, NPC, "tap") { player, node -> - val npc = node as NPC - val distance = Location.getDelta(player.location, node.location) - val newLoc = node.location.transform(Location(distance.x * 1, distance.y * 1)) animate(player, 1606) - npc.walkingQueue.reset() - npc.walkingQueue.addPath(newLoc.x, newLoc.y) + generatePath(node as NPC, Location.getDelta(player.location, node.location), 1) return@on true } on(NPCs.SKULLBALL_1659, NPC, "kick") { player, node -> - val npc = node as NPC - val distance = Location.getDelta(player.location, node.location) - val newLoc = node.location.transform(Location(distance.x * 4, distance.y * 4)) animate(player, 1606) - npc.walkingQueue.reset() - npc.walkingQueue.addPath(newLoc.x, newLoc.y) + generatePath(node as NPC, Location.getDelta(player.location, node.location), 4) return@on true } on(NPCs.SKULLBALL_1659, NPC, "shoot") { player, node -> - val npc = node as NPC - val distance = Location.getDelta(player.location, node.location) - val newLoc = node.location.transform(Location(distance.x * 9, distance.y * 9)) animate(player, 1606) - println(Pathfinder.find(npc, newLoc, false, Pathfinder.DUMB).isSuccessful) - npc.walkingQueue.reset() - npc.walkingQueue.addPath(newLoc.x, newLoc.y) + generatePath(node as NPC, Location.getDelta(player.location, node.location), 9) return@on true } } override fun defineAreaBorders(): Array { - return arrayOf(ZoneBorders(3555,9870,3555,9870)) + // The goals here are in order + return arrayOf( + ZoneBorders(3555,9870,3555,9870), // rot 0 + ZoneBorders(3556,9883,3556,9883), + ZoneBorders(3558,9891,3558,9891), + ZoneBorders(3557,9900,3557,9900), + ZoneBorders(3558,9906,3558,9906), + ZoneBorders(3563,9911,3563,9911), // rot 1 + ZoneBorders(3575,9905,3575,9905), // rot 2 + ZoneBorders(3574,9888,3574,9888), + ZoneBorders(3568,9864,3568,9864), // rot 3 + ) } override fun getRestrictions(): Array { @@ -105,7 +109,9 @@ class SkullballCourse : InteractionListener, MapArea { } override fun areaLeave(entity: Entity, logout: Boolean) { - val skullballGoal = getScenery(Location(3555,9870)) - animateScenery(skullballGoal!!, 1598) // anim 1599 is when skullball enters from behind. + val skullballGoal = getScenery(entity.location) + if (skullballGoal != null) { + animateScenery(skullballGoal, 1598) // anim 1599 is when skullball enters from behind. + } } } \ No newline at end of file From 8c56ff960babafb39c8885bd6c9ee6c61b7339d4 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Wed, 12 Feb 2025 16:22:31 -0800 Subject: [PATCH 10/30] Ball bounces off walls now. --- .../werewolfagility/SkullballCourse.kt | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index f07308a6c..a5c4e4214 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -25,10 +25,22 @@ class SkullballCourse : InteractionListener, MapArea { Location.create(3555, 9860), Location.create(3557, 9859), ) + val skullballGoals = arrayOf( + ZoneBorders(3555,9870,3555,9870), // rot 0 + ZoneBorders(3556,9883,3556,9883), + ZoneBorders(3558,9891,3558,9891), + ZoneBorders(3557,9900,3557,9900), + ZoneBorders(3558,9906,3558,9906), + ZoneBorders(3563,9911,3563,9911), // rot 1 + ZoneBorders(3575,9905,3575,9905), // rot 2 + ZoneBorders(3574,9888,3574,9888), + ZoneBorders(3568,9864,3568,9864), // rot 3 + ) fun startBall(player: Player) { if (getAttribute(player, "tempAttribute", null) == null) { val npc = NPC(NPCs.SKULLBALL_1659) + setAttribute(npc, "target", player) npc.isRespawn = false npc.isWalks = false npc.location = startingBall.random() @@ -53,15 +65,19 @@ class SkullballCourse : InteractionListener, MapArea { for (i in 1..distanceToMove) { destinationLocation = destinationLocation.transform(currentKickDelta) if (!RegionManager.isTeleportPermitted(destinationLocation)) { + val scenery = getScenery(destinationLocation) + if (scenery?.id == 5146) { + continue + // DON'T ANIMATE HERE. THIS IS FOR CALCULATION, NOT THE ACTUAL RUN. + // animateScenery(scenery, 1598) // skullball goal + } currentKickDelta = Location(-currentKickDelta.x, -currentKickDelta.y, currentKickDelta.z) destinationLocation = destinationLocation.transform(currentKickDelta) npc.walkingQueue.addPath(destinationLocation.x, destinationLocation.y) destinationLocation = destinationLocation.transform(currentKickDelta) } - println("Loc is landscape" + destinationLocation.x + " " + destinationLocation.y + "KICKDELTA" + currentKickDelta.x + " " + currentKickDelta.y) } npc.walkingQueue.addPath(destinationLocation.x, destinationLocation.y) - println("Loc is final" + destinationLocation.x + " " + destinationLocation.y) } } @@ -109,9 +125,14 @@ class SkullballCourse : InteractionListener, MapArea { } override fun areaLeave(entity: Entity, logout: Boolean) { - val skullballGoal = getScenery(entity.location) - if (skullballGoal != null) { - animateScenery(skullballGoal, 1598) // anim 1599 is when skullball enters from behind. + val leavingLocation = getScenery(entity.location) + val surroundingScenery = + getScenery(entity.location.transform(1,0,0)) ?: + getScenery(entity.location.transform(0,1,0)) ?: + getScenery(entity.location.transform(-1,0,0)) ?: + getScenery(entity.location.transform(0,-1,0)) + if (surroundingScenery != null && surroundingScenery.id == 5146) { + animateScenery(surroundingScenery, 1598) // anim 1599 is when skullball enters from behind. } } } \ No newline at end of file From 4737660fcf12417ccb327207c6ef6b8c870cefc4 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sat, 5 Jul 2025 22:17:14 -0700 Subject: [PATCH 11/30] Update dialogue to be generally complete. --- .../werewolfagility/AgilityBossDialogue.kt | 2 + .../werewolfagility/AgilityTrainerDialogue.kt | 59 ++++---------- .../werewolfagility/SkullballBehavior.kt | 27 +++++++ .../werewolfagility/SkullballBossDialogue.kt | 76 +++++++++++++------ .../werewolfagility/SkullballCourse.kt | 42 +++++----- .../morytania/werewolfagility/SkullballNPC.kt | 18 ----- .../SkullballTrainerDialogue.kt | 57 +++++++++----- .../WerewolfAgilityGuardDialogue.kt | 2 +- 8 files changed, 155 insertions(+), 128 deletions(-) create mode 100644 Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt delete mode 100644 Server/src/main/content/region/morytania/werewolfagility/SkullballNPC.kt diff --git a/Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt index d0f6ae331..f7a5689b3 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt @@ -27,6 +27,8 @@ class AgilityBossDialogue(player: Player? = null) : DialoguePlugin(player){ return true } + // Have you brought the stick yet? // shorts/TO-vdlyOa3E + override fun handle(interfaceId: Int, buttonId: Int): Boolean { when(stage){ 0 -> npc(FacialExpression.WEREWOLF_NEUTRAL,"I'll throw you a stick, which you need to", diff --git a/Server/src/main/content/region/morytania/werewolfagility/AgilityTrainerDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityTrainerDialogue.kt index ddc62baaa..84a3698a2 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/AgilityTrainerDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityTrainerDialogue.kt @@ -1,59 +1,30 @@ package content.region.morytania.werewolfagility -import core.api.anyInEquipment -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression +import core.game.dialogue.* +import core.game.interaction.IntType +import core.game.interaction.InteractionListener import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player -import core.plugin.Initializable import org.rs09.consts.Items import org.rs09.consts.NPCs /** * https://www.youtube.com/watch?v=mIKPpc30XBQ - What stick */ +class AgilityTrainerDialogue : InteractionListener { -@Initializable -class AgilityTrainerDialogue(player: Player? = null) : DialoguePlugin(player){ + override fun defineListeners() { + on(NPCs.AGILITY_TRAINER_1664, IntType.NPC, "give-stick") { player, node -> + DialogueLabeller.open(player, object : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.AGILITY_TRAINER_1664) - override fun open(vararg args: Any?): Boolean { - npc = args[0] as NPC - - if(anyInEquipment(player, Items.RING_OF_CHAROS_4202, Items.RING_OF_CHAROSA_6465)) { - player(FacialExpression.ASKING,"How do I use the agility course?").also { stage = 0 } - } else { - npc(FacialExpression.WEREWOLF_NEUTRAL,"Grrr - you don't belong in here, human!").also { stage = 99 } + npc(ChatAnim.WEREWOLF_NEUTRAL, "Have you brought the stick yet?") + player("What stick?") + npc(ChatAnim.WEREWOLF_NEUTRAL, "Come on, get round that course - I need something to chew!") + } + }, node as NPC) + return@on true } - return true - } - - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when(stage){ - 0 -> npc(FacialExpression.WEREWOLF_NEUTRAL,"I'll throw you a stick, which you need to", - "fetch as quickly as possible, ", - "from the area beyond the pipes.").also { stage++ } - - 1 -> npc(FacialExpression.WEREWOLF_NEUTRAL,"Be wary of the deathslide - you must hang by your teeth,", - "and if your strength is not up to the job you will", - "fall into a pit of spikes. Also, I would advise not", - "carrying too much extra weight.").also { stage++ } - - 2 -> npc(FacialExpression.WEREWOLF_NEUTRAL,"Bring the stick back to the werewolf waiting at", - "the end of the death slide to get your agility bonus.").also { stage++ } - - 3 ->npc(FacialExpression.WEREWOLF_NEUTRAL,"I will throw your stick as soon as you jump onto the", - "first stone.").also { stage = 99 } - - 99 -> end() - } - return true - } - - override fun newInstance(player: Player?): DialoguePlugin { - return AgilityTrainerDialogue(player) - } - - override fun getIds(): IntArray { - return intArrayOf(NPCs.AGILITY_TRAINER_1664) } } \ No newline at end of file diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt new file mode 100644 index 000000000..6eeccd632 --- /dev/null +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt @@ -0,0 +1,27 @@ +package content.region.morytania.werewolfagility + +import core.api.poofClear +import core.game.node.entity.npc.AbstractNPC +import core.game.node.entity.npc.NPC +import core.game.node.entity.npc.NPCBehavior +import core.game.node.entity.player.Player +import core.game.world.map.Location +import org.rs09.consts.NPCs + +/** + * This is a Skullball, treated as an NPC. + * + * Most of the logic is in SkullballCourse + */ +class SkullballBehavior : NPCBehavior(NPCs.SKULLBALL_1659) { + var clearTime = 0 + + override fun tick(self: NPC): Boolean { + // You have 800 ticks to kick the ball into the goal. + if (clearTime++ > 800) { + clearTime = 0 + poofClear(self) + } + return true + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt index 203ed68cf..c15f6e203 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt @@ -1,39 +1,65 @@ package content.region.morytania.werewolfagility import core.api.* -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression +import core.game.dialogue.* +import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.plugin.Initializable -import core.tools.END_DIALOGUE -import core.tools.START_DIALOGUE +import org.rs09.consts.Items import org.rs09.consts.NPCs @Initializable -class SkullballBossDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun handle(interfaceId: Int, buttonId: Int) : Boolean { - when (stage) { - START_DIALOGUE -> options("I would like to do the skullball course.", "What are the instructions for using the skullball course?").also { stage++ } - 1 -> when (buttonId) { - 1 -> playerl(FacialExpression.NEUTRAL, "I would like to do the skullball course.").also { stage = 5 } - 2 -> playerl(FacialExpression.THINKING, "What are the instructions for using the skullball course?").also { stage = 11 } - } - 5 -> end().also { - SkullballCourse.startBall(player) - } - 11 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "The skullball comes out of one of these four spawnholes. Just kick the ball through the middle of each goal, through the skeleton's feet.").also { stage++ } - 12 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "There are 10 goals, which you must complete in order, and one final goal.").also { stage++ } - 13 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "An arrow will point to your ball, just in case lots of people are using the course at the same time as yourself.").also { stage++ } - 14 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "The better your time, the more agillity XP you will be awarded. The timer starts when you score your first goal.").also { stage = END_DIALOGUE } - } - return true - } - - override fun newInstance(player: Player?): DialoguePlugin { +class SkullballBossDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun newInstance(player: Player): DialoguePlugin { return SkullballBossDialogue(player) } - + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, SkullballBossDialogueFile(), npc) + return false + } override fun getIds(): IntArray { return intArrayOf(NPCs.SKULLBALL_BOSS_1660) } } +class SkullballBossDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.SKULLBALL_BOSS_1660) + + exec { player, npc -> + if(!anyInEquipment(player, Items.RING_OF_CHAROS_4202, Items.RING_OF_CHAROSA_6465)) { + goto("ishuman") + } else if (getAttribute(player, SkullballCourse.attributeSkullballInstance, null) != null) { + goto("skullballinprogress") + } else { + goto("noskullball") + } + } + + label("ishuman") + player(ChatAnim.WEREWOLF_SUSPICIOUS, "Grrr - you don't belong in here, human!") + + label("skullballinprogress") + options( + DialogueOption("explainskullball", "What are the instructions for using the skullball course?", expression=ChatAnim.THINKING), + DialogueOption("startskullball", "I seem to have lost my ball - can I have another one?", expression=ChatAnim.THINKING), + DialogueOption("startskullball", "I give up, I can't do it - take my ball away.", expression=ChatAnim.NEUTRAL), + ) + + label("noskullball") + options( + DialogueOption("startskullball", "I would like to do the skullball course.", expression=ChatAnim.NEUTRAL), + DialogueOption("explainskullball", "What are the instructions for using the skullball course?", expression=ChatAnim.THINKING), + ) + + label("startskullball") + exec { player, npc -> + SkullballCourse.startBall(player) + } + + label("explainskullball") + npc(ChatAnim.WEREWOLF_NEUTRAL, "The skullball comes out of one of these four spawnholes. Just kick the ball through the middle of each goal, through the skeleton's feet.") + npc(ChatAnim.WEREWOLF_NEUTRAL, "There are 10 goals, which you must complete in order, and one final goal.") + npc(ChatAnim.WEREWOLF_NEUTRAL, "An arrow will point to your ball, just in case lots of people are using the course at the same time as yourself.") + npc(ChatAnim.WEREWOLF_NEUTRAL, "The better your time, the more agility XP you will be awarded. The timer starts when you score your first goal.") + } +} diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index a5c4e4214..236dded30 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -25,20 +25,22 @@ class SkullballCourse : InteractionListener, MapArea { Location.create(3555, 9860), Location.create(3557, 9859), ) + /** Array of ZoneBorders with Skullball Goals */ val skullballGoals = arrayOf( - ZoneBorders(3555,9870,3555,9870), // rot 0 - ZoneBorders(3556,9883,3556,9883), - ZoneBorders(3558,9891,3558,9891), - ZoneBorders(3557,9900,3557,9900), - ZoneBorders(3558,9906,3558,9906), - ZoneBorders(3563,9911,3563,9911), // rot 1 - ZoneBorders(3575,9905,3575,9905), // rot 2 - ZoneBorders(3574,9888,3574,9888), - ZoneBorders(3568,9864,3568,9864), // rot 3 + ZoneBorders(3555,9870,3555,9870), // rot 0 + ZoneBorders(3556,9883,3556,9883), + ZoneBorders(3558,9891,3558,9891), + ZoneBorders(3557,9900,3557,9900), + ZoneBorders(3558,9906,3558,9906), + ZoneBorders(3563,9911,3563,9911), // rot 1 + ZoneBorders(3575,9905,3575,9905), // rot 2 + ZoneBorders(3574,9888,3574,9888), + ZoneBorders(3575,9878,3575,9878), + ZoneBorders(3568,9864,3568,9864), // rot 3 ) fun startBall(player: Player) { - if (getAttribute(player, "tempAttribute", null) == null) { + if (getAttribute(player, attributeSkullballInstance, null) == null) { val npc = NPC(NPCs.SKULLBALL_1659) setAttribute(npc, "target", player) npc.isRespawn = false @@ -53,6 +55,7 @@ class SkullballCourse : InteractionListener, MapArea { npc.walkingQueue.reset() val newLoc = npc.location.transform(Location(0, 4, 0)) npc.walkingQueue.addPath(newLoc.x, newLoc.y) + setAttribute(player, attributeSkullballInstance, npc) } } @@ -62,15 +65,19 @@ class SkullballCourse : InteractionListener, MapArea { var currentKickDelta = kickDelta var destinationLocation = npc.location + // For each square to move, for (i in 1..distanceToMove) { destinationLocation = destinationLocation.transform(currentKickDelta) - if (!RegionManager.isTeleportPermitted(destinationLocation)) { + // Check if destination square is blocked. + if (!RegionManager.isTeleportPermitted(destinationLocation) || + !destinationLocation.equals(3563, 9866, 0)) { val scenery = getScenery(destinationLocation) if (scenery?.id == 5146) { continue // DON'T ANIMATE HERE. THIS IS FOR CALCULATION, NOT THE ACTUAL RUN. // animateScenery(scenery, 1598) // skullball goal } + // If square is blocked, reverse ball direction. currentKickDelta = Location(-currentKickDelta.x, -currentKickDelta.y, currentKickDelta.z) destinationLocation = destinationLocation.transform(currentKickDelta) npc.walkingQueue.addPath(destinationLocation.x, destinationLocation.y) @@ -103,18 +110,7 @@ class SkullballCourse : InteractionListener, MapArea { } override fun defineAreaBorders(): Array { - // The goals here are in order - return arrayOf( - ZoneBorders(3555,9870,3555,9870), // rot 0 - ZoneBorders(3556,9883,3556,9883), - ZoneBorders(3558,9891,3558,9891), - ZoneBorders(3557,9900,3557,9900), - ZoneBorders(3558,9906,3558,9906), - ZoneBorders(3563,9911,3563,9911), // rot 1 - ZoneBorders(3575,9905,3575,9905), // rot 2 - ZoneBorders(3574,9888,3574,9888), - ZoneBorders(3568,9864,3568,9864), // rot 3 - ) + return skullballGoals } override fun getRestrictions(): Array { diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballNPC.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballNPC.kt deleted file mode 100644 index 5769e7133..000000000 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballNPC.kt +++ /dev/null @@ -1,18 +0,0 @@ -package content.region.morytania.werewolfagility - -import core.game.node.entity.npc.AbstractNPC -import core.game.node.entity.player.Player -import core.game.world.map.Location -import org.rs09.consts.NPCs - -class SkullballNPC(id: Int = 0, val player: Player?, location: Location? = null) : AbstractNPC(id, location) { - var clearTime = 0 - // Technically not constructed this way since this is invoked on load. - override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { - return SkullballNPC(id, null, location) - } - - override fun getIds(): IntArray { - return intArrayOf(NPCs.SKULLBALL_1659) - } -} \ No newline at end of file diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt index e6f4ade9c..cfd68ae24 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt @@ -1,31 +1,54 @@ package content.region.morytania.werewolfagility -import core.api.isQuestComplete -import core.api.toIntArray +import core.api.* +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression +import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.plugin.Initializable -import core.tools.END_DIALOGUE -import core.tools.START_DIALOGUE +import org.rs09.consts.Items import org.rs09.consts.NPCs @Initializable -class SkullballTrainerDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun handle(interfaceId: Int, buttonId: Int) : Boolean { - when (stage) { - START_DIALOGUE -> playerl(FacialExpression.THINKING, "What is this place?").also { stage++ } - 1 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "This is the Skullball Course").also { stage++ } - 2 -> npcl(FacialExpression.WEREWOLF_NEUTRAL, "Go talk to the boss at the beginning of the course if you'd like a go.").also { stage = END_DIALOGUE } - } - return true - } - - override fun newInstance(player: Player?): DialoguePlugin { +class SkullballTrainerDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun newInstance(player: Player): DialoguePlugin { return SkullballTrainerDialogue(player) } - + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, SkullballTrainerDialogueFile(), npc) + return false + } override fun getIds(): IntArray { return intArrayOf(NPCs.SKULLBALL_TRAINER_1662) } } +class SkullballTrainerDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.SKULLBALL_TRAINER_1662) + + exec { player, npc -> + if(!anyInEquipment(player, Items.RING_OF_CHAROS_4202, Items.RING_OF_CHAROSA_6465)) { + goto("ishuman") + } else if (getAttribute(player, SkullballCourse.attributeSkullballInstance, null) != null) { + goto("skullballinprogress") + } else { + goto("noskullball") + } + } + + label("ishuman") + player(ChatAnim.WEREWOLF_SUSPICIOUS, "Grrr - you don't belong in here, human!") + + label("noskullball") + player(ChatAnim.THINKING, "What is this place?") + npc(ChatAnim.WEREWOLF_NEUTRAL, "This is the Skullball Course") + npc(ChatAnim.WEREWOLF_NEUTRAL, "Go talk to the boss at the beginning of the course if you'd like a go.") + + // Eovq4QBY39c + label("skullballinprogress") + player(ChatAnim.THINKING, "How many goals have I got left?") + npc(ChatAnim.WEREWOLF_NEUTRAL, "You have X goals left to complete.") + + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt index 695103149..72410d3d6 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt @@ -52,7 +52,7 @@ class WerewolfAgilityGuardDialogueFile : DialogueBuilderFile() { branch.onValue(1) .npcl(FacialExpression.WEREWOLF_NEUTRAL, "It's an agility course designed for lycanthropes like ourselves, my friend.") .playerl(FacialExpression.FRIENDLY, "Can I come in and use it?") - .npcl(FacialExpression.WEREWOLF_HAPPY, "Certainly. The cavern contains two courses: on the west side is a level 60 Agility Course, and on the east side is a level 25 Skullball Course.") + .npc(FacialExpression.WEREWOLF_HAPPY, "Certainly. The cavern contains two courses - on the", "west side is a level 60 Agility Course, and on the east", "side is a level 25 Skullball Course.") .end() } } From 20fd93b2091dee887031df63b881d600945ced7a Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sat, 5 Jul 2025 23:50:00 -0700 Subject: [PATCH 12/30] Completable skullball. --- .../werewolfagility/SkullballBehavior.kt | 22 ++++++++++++++++--- .../werewolfagility/SkullballCourse.kt | 6 +++-- .../werewolfagility/WerewolfAgilityCourse.kt | 1 + 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt index 6eeccd632..df8aa7d9e 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt @@ -1,7 +1,6 @@ package content.region.morytania.werewolfagility -import core.api.poofClear -import core.game.node.entity.npc.AbstractNPC +import core.api.* import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPCBehavior import core.game.node.entity.player.Player @@ -20,7 +19,24 @@ class SkullballBehavior : NPCBehavior(NPCs.SKULLBALL_1659) { // You have 800 ticks to kick the ball into the goal. if (clearTime++ > 800) { clearTime = 0 - poofClear(self) + val player = getAttribute(self, "target", null) + if (player != null) { + removeAttribute(player, SkullballCourse.attributeSkullballInstance) + } + removeAttribute(self, "target") + self.clear() + } + if (self.location.equals(3563, 9866, 0)) { + // TODO: Check if other goals are passed. + val player = getAttribute(self, "target", null) + if (player != null) { + setInterfaceText(player, "5:30", SkullballCourse.skullballGoalIface, 5) + setInterfaceText(player, "lmao", SkullballCourse.skullballGoalIface, 6) + openInterface(player, SkullballCourse.skullballGoalIface) + removeAttribute(player, SkullballCourse.attributeSkullballInstance) + removeAttribute(self, "target") + self.clear() + } } return true } diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index 236dded30..905ccc4c7 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -19,6 +19,8 @@ class SkullballCourse : InteractionListener, MapArea { val attributeSkullballInstance = "skullball-instance" val attributeSkullballCurrentGoal = "skullball-currentgoal" + val skullballGoalIface = 379 + val startingBall = arrayOf( Location.create(3552, 9859), Location.create(3554, 9860), @@ -43,6 +45,7 @@ class SkullballCourse : InteractionListener, MapArea { if (getAttribute(player, attributeSkullballInstance, null) == null) { val npc = NPC(NPCs.SKULLBALL_1659) setAttribute(npc, "target", player) + setAttribute(player, attributeSkullballInstance, npc) npc.isRespawn = false npc.isWalks = false npc.location = startingBall.random() @@ -55,7 +58,6 @@ class SkullballCourse : InteractionListener, MapArea { npc.walkingQueue.reset() val newLoc = npc.location.transform(Location(0, 4, 0)) npc.walkingQueue.addPath(newLoc.x, newLoc.y) - setAttribute(player, attributeSkullballInstance, npc) } } @@ -69,7 +71,7 @@ class SkullballCourse : InteractionListener, MapArea { for (i in 1..distanceToMove) { destinationLocation = destinationLocation.transform(currentKickDelta) // Check if destination square is blocked. - if (!RegionManager.isTeleportPermitted(destinationLocation) || + if (!RegionManager.isTeleportPermitted(destinationLocation) && !destinationLocation.equals(3563, 9866, 0)) { val scenery = getScenery(destinationLocation) if (scenery?.id == 5146) { diff --git a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt index 817b60186..e1b71b10c 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt @@ -125,6 +125,7 @@ class WerewolfAgilityCourse : InteractionListener { sendDialogue(player, "You need an Agility level of at least 60 to do this.") return@on false } + // sendChat(player, "You've already climbed the skull wall.") // If you are climbing down. if (player.location.x > node.location.x) { rewardXP(player, Skills.AGILITY, 25.0) ForceMovement.run(player, player.location, player.location.transform(-2, 0, 0), Animation(2049), Animation(2049), Direction.WEST, 10).endAnimation = Animation.RESET From 8d6b91b72fe695bdd56c7031bfca8eead3ca642a Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sun, 6 Jul 2025 22:52:17 -0700 Subject: [PATCH 13/30] Did skullball calculations. --- .../werewolfagility/SkullballBehavior.kt | 33 +++++++++++++++++-- .../werewolfagility/SkullballCourse.kt | 11 ++++++- .../werewolfagility/WerewolfAgilityCourse.kt | 6 ++-- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt index df8aa7d9e..4d4aa6f58 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt @@ -4,6 +4,7 @@ import core.api.* import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPCBehavior import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills import core.game.world.map.Location import org.rs09.consts.NPCs @@ -13,6 +14,27 @@ import org.rs09.consts.NPCs * Most of the logic is in SkullballCourse */ class SkullballBehavior : NPCBehavior(NPCs.SKULLBALL_1659) { + + companion object { + fun getTime(player: Player) : String { + val startTime = getAttribute(player, SkullballCourse.attributeSkullballStartTime, System.currentTimeMillis()) + val endTime = System.currentTimeMillis() + val timeDiffInSecs = (endTime - startTime) / 1000 + val finalMins = timeDiffInSecs / 60 + val finalSecs = timeDiffInSecs % 60 + return String.format("%01d:%02d", finalMins, finalSecs) + } + + fun getExp(player: Player) : Int { + val startTime = getAttribute(player, SkullballCourse.attributeSkullballStartTime, System.currentTimeMillis()) + val endTime = System.currentTimeMillis() + var timeDiffInSecs = ((endTime - startTime) / 1000).toInt() + timeDiffInSecs -= 240 // Take away 4 mins + + return (750 - timeDiffInSecs / 3).coerceAtLeast(0) // Kotlin what the fuck is this function + } + } + var clearTime = 0 override fun tick(self: NPC): Boolean { @@ -30,10 +52,17 @@ class SkullballBehavior : NPCBehavior(NPCs.SKULLBALL_1659) { // TODO: Check if other goals are passed. val player = getAttribute(self, "target", null) if (player != null) { - setInterfaceText(player, "5:30", SkullballCourse.skullballGoalIface, 5) - setInterfaceText(player, "lmao", SkullballCourse.skullballGoalIface, 6) + sendMessage(player, "Well done - you've finished the skullball course!!!") + // TODO: Player jumps for joy THEN this interface pops up + animate(player, 1605) + setInterfaceText(player, getTime(player), SkullballCourse.skullballGoalIface, 5) + val finalExp = getExp(player) + setInterfaceText(player, finalExp.toString(), SkullballCourse.skullballGoalIface, 6) + rewardXP(player, Skills.AGILITY, finalExp.toDouble()) + openInterface(player, SkullballCourse.skullballGoalIface) removeAttribute(player, SkullballCourse.attributeSkullballInstance) + removeAttribute(player, SkullballCourse.attributeSkullballStartTime) removeAttribute(self, "target") self.clear() } diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index 905ccc4c7..f795dd04a 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -17,7 +17,8 @@ class SkullballCourse : InteractionListener, MapArea { companion object { val attributeSkullballInstance = "skullball-instance" - val attributeSkullballCurrentGoal = "skullball-currentgoal" + val attributeSkullballCurrentGoal = "skullball-currentgoal" // 0 to 10 + val attributeSkullballStartTime = "skullball-starttime" val skullballGoalIface = 379 @@ -129,7 +130,15 @@ class SkullballCourse : InteractionListener, MapArea { getScenery(entity.location.transform(0,1,0)) ?: getScenery(entity.location.transform(-1,0,0)) ?: getScenery(entity.location.transform(0,-1,0)) + // If it is the goalie. if (surroundingScenery != null && surroundingScenery.id == 5146) { + val player = getAttribute(entity, "target", null) + // On the first goal, start the time. + if (player != null && entity.location.equals(3555,9870, 0)) { + if (getAttribute(player, attributeSkullballStartTime, null) == null) { + setAttribute(player, attributeSkullballStartTime, System.currentTimeMillis()) + } + } animateScenery(surroundingScenery, 1598) // anim 1599 is when skullball enters from behind. } } diff --git a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt index e1b71b10c..91b1c35a0 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt @@ -38,9 +38,9 @@ class WerewolfAgilityCourse : InteractionListener { //https://www.youtube.com/watch?v=fmzzLy5fXK4 //https://www.youtube.com/watch?v=RnhjHPuae3Q - best with all 3 fall locations. val startTile: Location = Location(3528, 9910, 0) - val midwayTile1: Location = Location(3528, 9890, 0) + val midwayTile1: Location = Location(3528, 9891, 0) val midwayTile2: Location = Location(3528, 9882, 0) - val failureLocation1: Location = Location(3526, 9887, 0) + val failureLocation1: Location = Location(3526, 9888, 0) val failureLocation2: Location = Location(3526, 9879, 0) val endTile: Location = Location(3528, 9873, 0) @@ -125,7 +125,7 @@ class WerewolfAgilityCourse : InteractionListener { sendDialogue(player, "You need an Agility level of at least 60 to do this.") return@on false } - // sendChat(player, "You've already climbed the skull wall.") // If you are climbing down. + // sendMessage(player, "You've already climbed the skull wall.") // If you are climbing down. if (player.location.x > node.location.x) { rewardXP(player, Skills.AGILITY, 25.0) ForceMovement.run(player, player.location, player.location.transform(-2, 0, 0), Animation(2049), Animation(2049), Direction.WEST, 10).endAnimation = Animation.RESET From 9436cb72c6526721afc431c81cd51dd407d56077 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sun, 6 Jul 2025 23:07:20 -0700 Subject: [PATCH 14/30] What the fuck. --- .../region/morytania/werewolfagility/SkullballBehavior.kt | 4 +--- .../region/morytania/werewolfagility/SkullballCourse.kt | 2 +- .../region/morytania/werewolfagility/WerewolfAgilityCourse.kt | 1 + 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt index 4d4aa6f58..510e095a2 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt @@ -28,9 +28,7 @@ class SkullballBehavior : NPCBehavior(NPCs.SKULLBALL_1659) { fun getExp(player: Player) : Int { val startTime = getAttribute(player, SkullballCourse.attributeSkullballStartTime, System.currentTimeMillis()) val endTime = System.currentTimeMillis() - var timeDiffInSecs = ((endTime - startTime) / 1000).toInt() - timeDiffInSecs -= 240 // Take away 4 mins - + val timeDiffInSecs = (((endTime - startTime) / 1000) - 240).toInt().coerceAtLeast(0) return (750 - timeDiffInSecs / 3).coerceAtLeast(0) // Kotlin what the fuck is this function } } diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index f795dd04a..c3d46a8b6 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -134,7 +134,7 @@ class SkullballCourse : InteractionListener, MapArea { if (surroundingScenery != null && surroundingScenery.id == 5146) { val player = getAttribute(entity, "target", null) // On the first goal, start the time. - if (player != null && entity.location.equals(3555,9870, 0)) { + if (player != null && entity.location.equals(3555,9871, 0)) { if (getAttribute(player, attributeSkullballStartTime, null) == null) { setAttribute(player, attributeSkullballStartTime, System.currentTimeMillis()) } diff --git a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt index 91b1c35a0..9d00ea9f5 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt @@ -44,6 +44,7 @@ class WerewolfAgilityCourse : InteractionListener { val failureLocation2: Location = Location(3526, 9879, 0) val endTile: Location = Location(3528, 9873, 0) + // anim 767 - Landing on stomach } From d6194e057eb6433036feed88b443bd79ad2a39fd Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Fri, 11 Jul 2025 01:04:56 -0700 Subject: [PATCH 15/30] I think I fixed all the important parts. --- .../werewolfagility/AgilityBossDialogue.kt | 2 - .../werewolfagility/AgilityTrainerDialogue.kt | 10 +++- .../werewolfagility/SkullballBehavior.kt | 55 +++++++++++++------ .../werewolfagility/SkullballBossDialogue.kt | 18 +++++- .../werewolfagility/SkullballCourse.kt | 24 +++++++- .../SkullballTrainerDialogue.kt | 14 ++++- .../werewolfagility/WerewolfAgilityCourse.kt | 7 +++ 7 files changed, 105 insertions(+), 25 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt index f7a5689b3..d0f6ae331 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityBossDialogue.kt @@ -27,8 +27,6 @@ class AgilityBossDialogue(player: Player? = null) : DialoguePlugin(player){ return true } - // Have you brought the stick yet? // shorts/TO-vdlyOa3E - override fun handle(interfaceId: Int, buttonId: Int): Boolean { when(stage){ 0 -> npc(FacialExpression.WEREWOLF_NEUTRAL,"I'll throw you a stick, which you need to", diff --git a/Server/src/main/content/region/morytania/werewolfagility/AgilityTrainerDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityTrainerDialogue.kt index 84a3698a2..ee059338a 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/AgilityTrainerDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityTrainerDialogue.kt @@ -1,10 +1,11 @@ package content.region.morytania.werewolfagility +import core.api.* import core.game.dialogue.* import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.node.entity.npc.NPC -import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills import org.rs09.consts.Items import org.rs09.consts.NPCs @@ -15,10 +16,15 @@ class AgilityTrainerDialogue : InteractionListener { override fun defineListeners() { on(NPCs.AGILITY_TRAINER_1664, IntType.NPC, "give-stick") { player, node -> + if (inInventory(player, Items.STICK_4179)) { + removeItem(player, Items.STICK_4179) + rewardXP(player, Skills.AGILITY, 190.0) + return@on true + } DialogueLabeller.open(player, object : DialogueLabeller() { override fun addConversation() { assignToIds(NPCs.AGILITY_TRAINER_1664) - + // shorts/TO-vdlyOa3E npc(ChatAnim.WEREWOLF_NEUTRAL, "Have you brought the stick yet?") player("What stick?") npc(ChatAnim.WEREWOLF_NEUTRAL, "Come on, get round that course - I need something to chew!") diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt index 510e095a2..6aecfb4c5 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt @@ -1,11 +1,12 @@ package content.region.morytania.werewolfagility import core.api.* +import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPCBehavior 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.NPCs /** @@ -46,23 +47,43 @@ class SkullballBehavior : NPCBehavior(NPCs.SKULLBALL_1659) { removeAttribute(self, "target") self.clear() } - if (self.location.equals(3563, 9866, 0)) { - // TODO: Check if other goals are passed. - val player = getAttribute(self, "target", null) - if (player != null) { - sendMessage(player, "Well done - you've finished the skullball course!!!") - // TODO: Player jumps for joy THEN this interface pops up - animate(player, 1605) - setInterfaceText(player, getTime(player), SkullballCourse.skullballGoalIface, 5) - val finalExp = getExp(player) - setInterfaceText(player, finalExp.toString(), SkullballCourse.skullballGoalIface, 6) - rewardXP(player, Skills.AGILITY, finalExp.toDouble()) + if (!self.location.equals(3563, 9866, 0)) { + return true + } + val player = getAttribute(self, "target", null) + if (player == null) { + return true + } + if (getAttribute(player, SkullballCourse.attributeSkullballCurrentGoal, 0) != 10) { + sendMessage(player, "You did not score all the goals.") + return true + } + sendMessage(player, "Well done - you've finished the skullball course!!!") + lock(player, Animation(1605).duration) + queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + animate(player, 1605) + return@queueScript delayScript(player, Animation(1605).duration) + } - openInterface(player, SkullballCourse.skullballGoalIface) - removeAttribute(player, SkullballCourse.attributeSkullballInstance) - removeAttribute(player, SkullballCourse.attributeSkullballStartTime) - removeAttribute(self, "target") - self.clear() + 1 -> { + setInterfaceText(player, getTime(player), SkullballCourse.skullballGoalIface, 5) + val finalExp = getExp(player) + setInterfaceText(player, finalExp.toString(), SkullballCourse.skullballGoalIface, 6) + rewardXP(player, Skills.AGILITY, finalExp.toDouble()) + + openInterface(player, SkullballCourse.skullballGoalIface) + + removeAttribute(player, SkullballCourse.attributeSkullballInstance) + removeAttribute(player, SkullballCourse.attributeSkullballCurrentGoal) + removeAttribute(player, SkullballCourse.attributeSkullballStartTime) + removeAttribute(self, "target") + self.clear() + return@queueScript stopExecuting(player) + } + + else -> return@queueScript stopExecuting(player) } } return true diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt index c15f6e203..50df12d0a 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballBossDialogue.kt @@ -41,8 +41,8 @@ class SkullballBossDialogueFile : DialogueLabeller() { label("skullballinprogress") options( DialogueOption("explainskullball", "What are the instructions for using the skullball course?", expression=ChatAnim.THINKING), - DialogueOption("startskullball", "I seem to have lost my ball - can I have another one?", expression=ChatAnim.THINKING), - DialogueOption("startskullball", "I give up, I can't do it - take my ball away.", expression=ChatAnim.NEUTRAL), + DialogueOption("lostskullball", "I seem to have lost my ball - can I have another one?", expression=ChatAnim.THINKING), + DialogueOption("clearskullball", "I give up, I can't do it - take my ball away.", expression=ChatAnim.NEUTRAL), ) label("noskullball") @@ -56,6 +56,20 @@ class SkullballBossDialogueFile : DialogueLabeller() { SkullballCourse.startBall(player) } + + label("lostskullball") + npc(ChatAnim.WEREWOLF_NEUTRAL, "No problem, here's another one. You'll have to start from the beginning again, but the timer will be restarted too.") + exec { player, npc -> + SkullballCourse.clearBall(player) + SkullballCourse.startBall(player) + } + + label("clearskullball") + npc(ChatAnim.WEREWOLF_NEUTRAL, "Oh dear, such a defeatist.") + exec { player, npc -> + SkullballCourse.clearBall(player) + } + label("explainskullball") npc(ChatAnim.WEREWOLF_NEUTRAL, "The skullball comes out of one of these four spawnholes. Just kick the ball through the middle of each goal, through the skeleton's feet.") npc(ChatAnim.WEREWOLF_NEUTRAL, "There are 10 goals, which you must complete in order, and one final goal.") diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index c3d46a8b6..e82ddb8b2 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -40,8 +40,14 @@ class SkullballCourse : InteractionListener, MapArea { ZoneBorders(3574,9888,3574,9888), ZoneBorders(3575,9878,3575,9878), ZoneBorders(3568,9864,3568,9864), // rot 3 + ZoneBorders(0,0,0,0), // Should be the end goal ) + /** Extract Location from ZoneBorders **/ + fun extractLoc(z :ZoneBorders): Location { + return Location(z.northEastX, z.northEastY, 0) + } + fun startBall(player: Player) { if (getAttribute(player, attributeSkullballInstance, null) == null) { val npc = NPC(NPCs.SKULLBALL_1659) @@ -62,6 +68,17 @@ class SkullballCourse : InteractionListener, MapArea { } } + fun clearBall(player: Player) { + val npcBall = getAttribute(player, attributeSkullballInstance, null) + if (npcBall != null) { + removeAttribute(player, attributeSkullballInstance) + removeAttribute(player, attributeSkullballCurrentGoal) + removeAttribute(player, attributeSkullballStartTime) + removeAttribute(npcBall, "target") + npcBall.clear() + } + } + /** Generates a movement queue for a kicked/pushed thing. Bounces against walls. **/ fun generatePath(npc: NPC, kickDelta: Location, distanceToMove: Int) { npc.walkingQueue.reset() @@ -133,12 +150,17 @@ class SkullballCourse : InteractionListener, MapArea { // If it is the goalie. if (surroundingScenery != null && surroundingScenery.id == 5146) { val player = getAttribute(entity, "target", null) + if (player == null) { return } // On the first goal, start the time. - if (player != null && entity.location.equals(3555,9871, 0)) { + if (surroundingScenery.location.equals(extractLoc(skullballGoals[0]))) { if (getAttribute(player, attributeSkullballStartTime, null) == null) { setAttribute(player, attributeSkullballStartTime, System.currentTimeMillis()) } } + val currGoal = getAttribute(player, attributeSkullballCurrentGoal, 0) + if (surroundingScenery.location.equals(extractLoc(skullballGoals[currGoal]))) { + setAttribute(player, attributeSkullballCurrentGoal, currGoal + 1) + } animateScenery(surroundingScenery, 1598) // anim 1599 is when skullball enters from behind. } } diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt index cfd68ae24..359f104b1 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt @@ -48,7 +48,19 @@ class SkullballTrainerDialogueFile : DialogueLabeller() { // Eovq4QBY39c label("skullballinprogress") player(ChatAnim.THINKING, "How many goals have I got left?") - npc(ChatAnim.WEREWOLF_NEUTRAL, "You have X goals left to complete.") + exec { player, npc -> + if (getAttribute(player, SkullballCourse.attributeSkullballInstance, null) != null) { + goto("xgoals") + } else { + goto("finalgoal") + } + } + label("xgoals") + npc(ChatAnim.WEREWOLF_NEUTRAL, "You have ${10 - getAttribute(player!!, + SkullballCourse.attributeSkullballCurrentGoal, 0)} goals left to complete.") + + label("finalgoal") + npc(ChatAnim.WEREWOLF_NEUTRAL, "You have the final goal left to complete.") } } \ No newline at end of file diff --git a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt index 9d00ea9f5..a54bc2ca4 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt @@ -75,6 +75,13 @@ class WerewolfAgilityCourse : InteractionListener { // Stepping stones on(Scenery.STEPPING_STONE_35996, IntType.SCENERY, "jump-to") { player, node -> + val agilityBoss = findLocalNPC(player, NPCs.AGILITY_BOSS_1661) + if (agilityBoss != null) { + sendChat(agilityBoss, "FETCH!!!!!") + face(agilityBoss, Location(3540, 9877, 0)) + animate(agilityBoss, 6547) + produceGroundItem(player, Items.STICK_4179, 1, Location(3543, 9912)) + } if (!hasLevelStat(player, Skills.AGILITY, 60)) { sendDialogue(player, "You need an Agility level of at least 60 to do this.") return@on false From f67ac2d37021e2b73724e0d5d365ced1aeb63077 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Fri, 11 Jul 2025 09:08:52 -0700 Subject: [PATCH 16/30] Show goal thing. --- .../werewolfagility/SkullballCourse.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index e82ddb8b2..394406303 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -12,6 +12,7 @@ import core.game.world.map.path.Pathfinder import core.game.world.map.zone.ZoneBorders import core.game.world.map.zone.ZoneRestriction import org.rs09.consts.NPCs +import org.rs09.consts.Scenery class SkullballCourse : InteractionListener, MapArea { @@ -59,6 +60,7 @@ class SkullballCourse : InteractionListener, MapArea { npc.direction = Direction.NORTH npc.walkRadius = 100 npc.init() + clearHintIcon(player) registerHintIcon(player, npc) npc.lock(5) // Force walk doesn't work here because this npc isn't flagged to be forced walked. @@ -111,22 +113,36 @@ class SkullballCourse : InteractionListener, MapArea { override fun defineListeners() { on(NPCs.SKULLBALL_1659, NPC, "tap") { player, node -> + clearHintIcon(player) + registerHintIcon(player, node) animate(player, 1606) generatePath(node as NPC, Location.getDelta(player.location, node.location), 1) return@on true } on(NPCs.SKULLBALL_1659, NPC, "kick") { player, node -> + clearHintIcon(player) + registerHintIcon(player, node) animate(player, 1606) generatePath(node as NPC, Location.getDelta(player.location, node.location), 4) return@on true } on(NPCs.SKULLBALL_1659, NPC, "shoot") { player, node -> + clearHintIcon(player) + registerHintIcon(player, node) animate(player, 1606) generatePath(node as NPC, Location.getDelta(player.location, node.location), 9) return@on true } + + on(NPCs.SKULLBALL_1659, NPC, "show-goal") { player, node -> + animate(player, 1606) + val currGoal = getAttribute(player, attributeSkullballCurrentGoal, 0) + clearHintIcon(player) + registerHintIcon(player, getScenery(extractLoc(skullballGoals[currGoal]))!!.location, 10) + return@on true + } } override fun defineAreaBorders(): Array { @@ -160,6 +176,9 @@ class SkullballCourse : InteractionListener, MapArea { val currGoal = getAttribute(player, attributeSkullballCurrentGoal, 0) if (surroundingScenery.location.equals(extractLoc(skullballGoals[currGoal]))) { setAttribute(player, attributeSkullballCurrentGoal, currGoal + 1) + val nextGoal = getAttribute(player, attributeSkullballCurrentGoal, 0) + clearHintIcon(player) + registerHintIcon(player, getScenery(extractLoc(skullballGoals[nextGoal]))!!.location, 10) } animateScenery(surroundingScenery, 1598) // anim 1599 is when skullball enters from behind. } From d5f21bdf16cdecf27df5b7d2d18ed299de276a05 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Fri, 11 Jul 2025 23:08:23 -0700 Subject: [PATCH 17/30] Not your ball. --- .../morytania/werewolfagility/SkullballCourse.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index 394406303..a9ad786a5 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -113,6 +113,10 @@ class SkullballCourse : InteractionListener, MapArea { override fun defineListeners() { on(NPCs.SKULLBALL_1659, NPC, "tap") { player, node -> + if (getAttribute(node as NPC, "target", null)?.username != player.username) { + sendMessage(player, "That is not your ball.") + return@on true + } clearHintIcon(player) registerHintIcon(player, node) animate(player, 1606) @@ -121,6 +125,10 @@ class SkullballCourse : InteractionListener, MapArea { } on(NPCs.SKULLBALL_1659, NPC, "kick") { player, node -> + if (getAttribute(node as NPC, "target", null)?.username != player.username) { + sendMessage(player, "That is not your ball.") + return@on true + } clearHintIcon(player) registerHintIcon(player, node) animate(player, 1606) @@ -129,6 +137,10 @@ class SkullballCourse : InteractionListener, MapArea { } on(NPCs.SKULLBALL_1659, NPC, "shoot") { player, node -> + if (getAttribute(node as NPC, "target", null)?.username != player.username) { + sendMessage(player, "That is not your ball.") + return@on true + } clearHintIcon(player) registerHintIcon(player, node) animate(player, 1606) @@ -137,6 +149,10 @@ class SkullballCourse : InteractionListener, MapArea { } on(NPCs.SKULLBALL_1659, NPC, "show-goal") { player, node -> + if (getAttribute(node as NPC, "target", null)?.username != player.username) { + sendMessage(player, "That is not your ball.") + return@on true + } animate(player, 1606) val currGoal = getAttribute(player, attributeSkullballCurrentGoal, 0) clearHintIcon(player) From 3519531e9040c95f256b82f7b2c2ca2aed25b77d Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Fri, 11 Jul 2025 23:51:28 -0700 Subject: [PATCH 18/30] Organization and more authentic messages for the death slide. --- Server/data/configs/npc_spawns.json | 2 +- ...ewolfAgilityCourse.kt => AgilityCourse.kt} | 30 ++++++++++++------- .../werewolfagility/SkullballBehavior.kt | 3 ++ .../werewolfagility/SkullballCourse.kt | 19 ++++++++++++ ...rdDialogue.kt => WerewolfGuardDialogue.kt} | 8 ++--- 5 files changed, 46 insertions(+), 16 deletions(-) rename Server/src/main/content/region/morytania/werewolfagility/{WerewolfAgilityCourse.kt => AgilityCourse.kt} (87%) rename Server/src/main/content/region/morytania/werewolfagility/{WerewolfAgilityGuardDialogue.kt => WerewolfGuardDialogue.kt} (91%) diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index 2a20194c2..560a59d1e 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -4089,7 +4089,7 @@ }, { "npc_id": "1660", - "loc_data": "{3549,9867,0,1,0}-" + "loc_data": "{3549,9867,0,0,6}-" }, { "npc_id": "1661", diff --git a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt similarity index 87% rename from Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt rename to Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt index a54bc2ca4..a1710cd84 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt @@ -2,14 +2,11 @@ package content.region.morytania.werewolfagility import core.api.* import core.game.dialogue.FacialExpression -import core.game.global.action.ClimbActionHandler import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.interaction.QueueStrength import core.game.node.entity.impl.ForceMovement import core.game.node.entity.skill.Skills -import core.game.node.item.GroundItem -import core.game.node.item.Item import core.game.world.map.Direction import core.game.world.map.Location import core.game.world.update.flag.context.Animation @@ -18,7 +15,7 @@ import org.rs09.consts.Items import org.rs09.consts.NPCs import org.rs09.consts.Scenery -class WerewolfAgilityCourse : InteractionListener { +class AgilityCourse : InteractionListener { companion object { @@ -35,13 +32,21 @@ class WerewolfAgilityCourse : InteractionListener { ) // https://www.youtube.com/watch?v=JN_1c7r9PVo - Popular courses GOOD! - //https://www.youtube.com/watch?v=fmzzLy5fXK4 - //https://www.youtube.com/watch?v=RnhjHPuae3Q - best with all 3 fall locations. + // https://www.youtube.com/watch?v=fmzzLy5fXK4 - a fall location on the other side + // https://www.youtube.com/watch?v=RnhjHPuae3Q - best with 2 fall locations nearest and furthest. + // https://www.youtube.com/watch?v=_Re3MRhHbZk - middle location fall. + // 180 160 140 exp for the 3 failure locations + // 200 exp for success val startTile: Location = Location(3528, 9910, 0) - val midwayTile1: Location = Location(3528, 9891, 0) - val midwayTile2: Location = Location(3528, 9882, 0) - val failureLocation1: Location = Location(3526, 9888, 0) - val failureLocation2: Location = Location(3526, 9879, 0) + val midwayTile1: Location = Location(3528, 9890, 0) + val midwayTile2: Location = Location(3528, 9885, 0) + val midwayTile3: Location = Location(3528, 9880, 0) + val failureTileLeft1: Location = Location(3526, 9888, 0) + val failureTileLeft2: Location = Location(3526, 9883, 0) + val failureTileLeft3: Location = Location(3526, 9878, 0) + val failureTileRight1: Location = Location(3530, 9888, 0) + val failureTileRight2: Location = Location(3530, 9883, 0) + val failureTileRight3: Location = Location(3530, 9878, 0) val endTile: Location = Location(3528, 9873, 0) // anim 767 - Landing on stomach @@ -188,9 +193,12 @@ class WerewolfAgilityCourse : InteractionListener { 1 -> { sendChat(player, "WAAAAAARRRGGGHHH!!!!!!") ForceMovement.run(player, startTile, finalTile, Animation(1602), Animation(1602), Direction.SOUTH, 40).endAnimation = Animation.RESET - return@queueScript delayScript(player, 2) + return@queueScript delayScript(player, 4) } 2 -> { + rewardXP(player, Skills.AGILITY, 200.0) + sendMessage(player, ".. and land safely on your feet.") + sendMessage(player, ".. only to fall from a great height!") return@queueScript stopExecuting(player) } else -> return@queueScript stopExecuting(player) diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt index 6aecfb4c5..98798ee5c 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt @@ -35,6 +35,9 @@ class SkullballBehavior : NPCBehavior(NPCs.SKULLBALL_1659) { } var clearTime = 0 + override fun onRemoval(self: NPC) { + clearTime = 0 + } override fun tick(self: NPC): Boolean { // You have 800 ticks to kick the ball into the goal. diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index a9ad786a5..24a8e48a7 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -199,4 +199,23 @@ class SkullballCourse : InteractionListener, MapArea { animateScenery(surroundingScenery, 1598) // anim 1599 is when skullball enters from behind. } } +} + +class SkullballQuit : MapArea { + override fun defineAreaBorders(): Array { + return arrayOf(getRegionBorders(14234)) + } + + override fun getRestrictions(): Array { + return arrayOf() + } + + override fun areaEnter(entity: Entity) { + } + + override fun areaLeave(entity: Entity, logout: Boolean) { + if (entity is Player) { + SkullballCourse.clearBall(entity) + } + } } \ No newline at end of file diff --git a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/WerewolfGuardDialogue.kt similarity index 91% rename from Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt rename to Server/src/main/content/region/morytania/werewolfagility/WerewolfGuardDialogue.kt index 72410d3d6..718946602 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/WerewolfAgilityGuardDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/WerewolfGuardDialogue.kt @@ -11,20 +11,20 @@ import org.rs09.consts.Items import org.rs09.consts.NPCs @Initializable -class WerewolfAgilityGuardDialogue (player: Player? = null) : DialoguePlugin(player) { +class WerewolfGuardDialogue (player: Player? = null) : DialoguePlugin(player) { override fun handle(interfaceId: Int, buttonId: Int): Boolean { - openDialogue(player, WerewolfAgilityGuardDialogueFile(), npc) + openDialogue(player, WerewolfGuardDialogueFile(), npc) return true } override fun newInstance(player: Player): DialoguePlugin { - return WerewolfAgilityGuardDialogue(player) + return WerewolfGuardDialogue(player) } override fun getIds(): IntArray { return intArrayOf(NPCs.WEREWOLF_1665) } } -class WerewolfAgilityGuardDialogueFile : DialogueBuilderFile() { +class WerewolfGuardDialogueFile : DialogueBuilderFile() { override fun create(b: DialogueBuilder) { b.onPredicate { _ -> true } .playerl(FacialExpression.FRIENDLY, "What's beneath the trapdoor?") From f8eae0e870e041d80094e840c4a62a70611703d2 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sat, 12 Jul 2025 17:21:41 -0700 Subject: [PATCH 19/30] Consolidate a file in. --- .../werewolfagility/AgilityCourse.kt | 7 +- .../werewolfagility/SkullballBehavior.kt | 94 ------ .../werewolfagility/SkullballCourse.kt | 276 +++++++++++------- .../SkullballTrainerDialogue.kt | 2 +- 4 files changed, 176 insertions(+), 203 deletions(-) delete mode 100644 Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt diff --git a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt index a1710cd84..b83ff8896 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt @@ -115,6 +115,8 @@ class AgilityCourse : InteractionListener { if (player.location.y < node.location.y) { rewardXP(player, Skills.AGILITY, 60.0) ForceMovement.run(player, player.location, player.location.transform(0, 2, 0), Animation(1603), Animation(1603), Direction.NORTH, 20).endAnimation = Animation.RESET + } else { + sendMessage(player, "You've already jumped over the hurdle.") } return@on true } @@ -128,6 +130,8 @@ class AgilityCourse : InteractionListener { if (player.location.y < node.location.y) { rewardXP(player, Skills.AGILITY, 15.0) ForceMovement.run(player, player.location, player.location.transform(0, 5, 0), Animation(10580), Animation(844), Direction.NORTH, 10).endAnimation = Animation(10579) + } else { + sendMessage(player, "You've already squeezed through the pipe.") } return@on true } @@ -138,10 +142,11 @@ class AgilityCourse : InteractionListener { sendDialogue(player, "You need an Agility level of at least 60 to do this.") return@on false } - // sendMessage(player, "You've already climbed the skull wall.") // If you are climbing down. if (player.location.x > node.location.x) { rewardXP(player, Skills.AGILITY, 25.0) ForceMovement.run(player, player.location, player.location.transform(-2, 0, 0), Animation(2049), Animation(2049), Direction.WEST, 10).endAnimation = Animation.RESET + } else { + sendMessage(player, "You've already climbed the skull wall.") } return@on true } diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt deleted file mode 100644 index 98798ee5c..000000000 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballBehavior.kt +++ /dev/null @@ -1,94 +0,0 @@ -package content.region.morytania.werewolfagility - -import core.api.* -import core.game.interaction.QueueStrength -import core.game.node.entity.npc.NPC -import core.game.node.entity.npc.NPCBehavior -import core.game.node.entity.player.Player -import core.game.node.entity.skill.Skills -import core.game.world.update.flag.context.Animation -import org.rs09.consts.NPCs - -/** - * This is a Skullball, treated as an NPC. - * - * Most of the logic is in SkullballCourse - */ -class SkullballBehavior : NPCBehavior(NPCs.SKULLBALL_1659) { - - companion object { - fun getTime(player: Player) : String { - val startTime = getAttribute(player, SkullballCourse.attributeSkullballStartTime, System.currentTimeMillis()) - val endTime = System.currentTimeMillis() - val timeDiffInSecs = (endTime - startTime) / 1000 - val finalMins = timeDiffInSecs / 60 - val finalSecs = timeDiffInSecs % 60 - return String.format("%01d:%02d", finalMins, finalSecs) - } - - fun getExp(player: Player) : Int { - val startTime = getAttribute(player, SkullballCourse.attributeSkullballStartTime, System.currentTimeMillis()) - val endTime = System.currentTimeMillis() - val timeDiffInSecs = (((endTime - startTime) / 1000) - 240).toInt().coerceAtLeast(0) - return (750 - timeDiffInSecs / 3).coerceAtLeast(0) // Kotlin what the fuck is this function - } - } - - var clearTime = 0 - override fun onRemoval(self: NPC) { - clearTime = 0 - } - - override fun tick(self: NPC): Boolean { - // You have 800 ticks to kick the ball into the goal. - if (clearTime++ > 800) { - clearTime = 0 - val player = getAttribute(self, "target", null) - if (player != null) { - removeAttribute(player, SkullballCourse.attributeSkullballInstance) - } - removeAttribute(self, "target") - self.clear() - } - if (!self.location.equals(3563, 9866, 0)) { - return true - } - val player = getAttribute(self, "target", null) - if (player == null) { - return true - } - if (getAttribute(player, SkullballCourse.attributeSkullballCurrentGoal, 0) != 10) { - sendMessage(player, "You did not score all the goals.") - return true - } - sendMessage(player, "Well done - you've finished the skullball course!!!") - lock(player, Animation(1605).duration) - queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - animate(player, 1605) - return@queueScript delayScript(player, Animation(1605).duration) - } - - 1 -> { - setInterfaceText(player, getTime(player), SkullballCourse.skullballGoalIface, 5) - val finalExp = getExp(player) - setInterfaceText(player, finalExp.toString(), SkullballCourse.skullballGoalIface, 6) - rewardXP(player, Skills.AGILITY, finalExp.toDouble()) - - openInterface(player, SkullballCourse.skullballGoalIface) - - removeAttribute(player, SkullballCourse.attributeSkullballInstance) - removeAttribute(player, SkullballCourse.attributeSkullballCurrentGoal) - removeAttribute(player, SkullballCourse.attributeSkullballStartTime) - removeAttribute(self, "target") - self.clear() - return@queueScript stopExecuting(player) - } - - else -> return@queueScript stopExecuting(player) - } - } - return true - } -} \ No newline at end of file diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index 24a8e48a7..47f4b3960 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -2,19 +2,20 @@ package content.region.morytania.werewolfagility import core.api.* import core.game.interaction.InteractionListener +import core.game.interaction.QueueStrength import core.game.node.entity.Entity import core.game.node.entity.npc.NPC +import core.game.node.entity.npc.NPCBehavior import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills import core.game.world.map.Direction import core.game.world.map.Location import core.game.world.map.RegionManager -import core.game.world.map.path.Pathfinder import core.game.world.map.zone.ZoneBorders -import core.game.world.map.zone.ZoneRestriction +import core.game.world.update.flag.context.Animation import org.rs09.consts.NPCs -import org.rs09.consts.Scenery -class SkullballCourse : InteractionListener, MapArea { +class SkullballCourse : MapArea { companion object { val attributeSkullballInstance = "skullball-instance" @@ -41,7 +42,7 @@ class SkullballCourse : InteractionListener, MapArea { ZoneBorders(3574,9888,3574,9888), ZoneBorders(3575,9878,3575,9878), ZoneBorders(3568,9864,3568,9864), // rot 3 - ZoneBorders(0,0,0,0), // Should be the end goal + ZoneBorders(3563,9865,3563, 9865), // End goal tunnel ) /** Extract Location from ZoneBorders **/ @@ -49,6 +50,7 @@ class SkullballCourse : InteractionListener, MapArea { return Location(z.northEastX, z.northEastY, 0) } + /** Creates a skullball for the player to kick around. */ fun startBall(player: Player) { if (getAttribute(player, attributeSkullballInstance, null) == null) { val npc = NPC(NPCs.SKULLBALL_1659) @@ -70,117 +72,35 @@ class SkullballCourse : InteractionListener, MapArea { } } + /** Clears the skullball from the world for that player. */ fun clearBall(player: Player) { val npcBall = getAttribute(player, attributeSkullballInstance, null) if (npcBall != null) { - removeAttribute(player, attributeSkullballInstance) - removeAttribute(player, attributeSkullballCurrentGoal) - removeAttribute(player, attributeSkullballStartTime) + clearHintIcon(player) removeAttribute(npcBall, "target") npcBall.clear() + removeAttribute(player, attributeSkullballCurrentGoal) + removeAttribute(player, attributeSkullballStartTime) + removeAttribute(player, attributeSkullballInstance) } } - - /** Generates a movement queue for a kicked/pushed thing. Bounces against walls. **/ - fun generatePath(npc: NPC, kickDelta: Location, distanceToMove: Int) { - npc.walkingQueue.reset() - - var currentKickDelta = kickDelta - var destinationLocation = npc.location - // For each square to move, - for (i in 1..distanceToMove) { - destinationLocation = destinationLocation.transform(currentKickDelta) - // Check if destination square is blocked. - if (!RegionManager.isTeleportPermitted(destinationLocation) && - !destinationLocation.equals(3563, 9866, 0)) { - val scenery = getScenery(destinationLocation) - if (scenery?.id == 5146) { - continue - // DON'T ANIMATE HERE. THIS IS FOR CALCULATION, NOT THE ACTUAL RUN. - // animateScenery(scenery, 1598) // skullball goal - } - // If square is blocked, reverse ball direction. - currentKickDelta = Location(-currentKickDelta.x, -currentKickDelta.y, currentKickDelta.z) - destinationLocation = destinationLocation.transform(currentKickDelta) - npc.walkingQueue.addPath(destinationLocation.x, destinationLocation.y) - destinationLocation = destinationLocation.transform(currentKickDelta) - } - } - npc.walkingQueue.addPath(destinationLocation.x, destinationLocation.y) - } - } - - override fun defineListeners() { - - on(NPCs.SKULLBALL_1659, NPC, "tap") { player, node -> - if (getAttribute(node as NPC, "target", null)?.username != player.username) { - sendMessage(player, "That is not your ball.") - return@on true - } - clearHintIcon(player) - registerHintIcon(player, node) - animate(player, 1606) - generatePath(node as NPC, Location.getDelta(player.location, node.location), 1) - return@on true - } - - on(NPCs.SKULLBALL_1659, NPC, "kick") { player, node -> - if (getAttribute(node as NPC, "target", null)?.username != player.username) { - sendMessage(player, "That is not your ball.") - return@on true - } - clearHintIcon(player) - registerHintIcon(player, node) - animate(player, 1606) - generatePath(node as NPC, Location.getDelta(player.location, node.location), 4) - return@on true - } - - on(NPCs.SKULLBALL_1659, NPC, "shoot") { player, node -> - if (getAttribute(node as NPC, "target", null)?.username != player.username) { - sendMessage(player, "That is not your ball.") - return@on true - } - clearHintIcon(player) - registerHintIcon(player, node) - animate(player, 1606) - generatePath(node as NPC, Location.getDelta(player.location, node.location), 9) - return@on true - } - - on(NPCs.SKULLBALL_1659, NPC, "show-goal") { player, node -> - if (getAttribute(node as NPC, "target", null)?.username != player.username) { - sendMessage(player, "That is not your ball.") - return@on true - } - animate(player, 1606) - val currGoal = getAttribute(player, attributeSkullballCurrentGoal, 0) - clearHintIcon(player) - registerHintIcon(player, getScenery(extractLoc(skullballGoals[currGoal]))!!.location, 10) - return@on true - } } override fun defineAreaBorders(): Array { - return skullballGoals - } - - override fun getRestrictions(): Array { - return arrayOf() - } - - override fun areaEnter(entity: Entity) { + return skullballGoals.copyOf(skullballGoals.lastIndex).filterNotNull().toTypedArray() // remove last goal } override fun areaLeave(entity: Entity, logout: Boolean) { - val leavingLocation = getScenery(entity.location) + // When leaving the area with the goals, the scenery of the goal is one tile adjacent to it. val surroundingScenery = getScenery(entity.location.transform(1,0,0)) ?: getScenery(entity.location.transform(0,1,0)) ?: getScenery(entity.location.transform(-1,0,0)) ?: getScenery(entity.location.transform(0,-1,0)) - // If it is the goalie. + // If that tile is the actual goal, if (surroundingScenery != null && surroundingScenery.id == 5146) { + animateScenery(surroundingScenery, 1598) // anim 1599 is when skullball enters from behind. + val player = getAttribute(entity, "target", null) if (player == null) { return } // On the first goal, start the time. @@ -194,27 +114,169 @@ class SkullballCourse : InteractionListener, MapArea { setAttribute(player, attributeSkullballCurrentGoal, currGoal + 1) val nextGoal = getAttribute(player, attributeSkullballCurrentGoal, 0) clearHintIcon(player) - registerHintIcon(player, getScenery(extractLoc(skullballGoals[nextGoal]))!!.location, 10) + registerHintIcon(player, getScenery(extractLoc(skullballGoals[nextGoal]))!!) } - animateScenery(surroundingScenery, 1598) // anim 1599 is when skullball enters from behind. } } } -class SkullballQuit : MapArea { +class SkullballBehavior : NPCBehavior(NPCs.SKULLBALL_1659), InteractionListener, MapArea { + + companion object { + + /** Generates a movement queue for a kicked/pushed thing. Bounces against walls. **/ + private fun generatePath(npc: NPC, kickDirection: Location, distanceToMove: Int) { + npc.walkingQueue.reset() + var moveDirection = kickDirection + var nextTile = npc.location + // For each tile to move, + for (i in 1..distanceToMove) { + nextTile = nextTile.transform(moveDirection) + if (!(RegionManager.isTeleportPermitted(nextTile) || // If not a walkable square + nextTile.equals(3563, 9865, 0) || // If not the final goal + nextTile.equals(3563, 9866, 0) || // If not the final goal + getScenery(nextTile)?.id != 5146 // If not the skeleton goal + ) + ) { + // Tile is blocked, reverse ball direction and set a walkingQueue Path. + moveDirection = Location(-moveDirection.x, -moveDirection.y, moveDirection.z) + nextTile = nextTile.transform(moveDirection) + npc.walkingQueue.addPath(nextTile.x, nextTile.y) + nextTile = nextTile.transform(moveDirection) + } + } + npc.walkingQueue.addPath(nextTile.x, nextTile.y) + } + + /** Moves the ball a certain distance (1,4,9 as authentic). */ + fun moveBall(player: Player, ballNpc: NPC, distance: Int) { + if (getAttribute(ballNpc, "target", null)?.username == player.username) { + clearHintIcon(player) + registerHintIcon(player, ballNpc) + animate(player, 1606) + generatePath(ballNpc, Location.getDelta(player.location, ballNpc.location), distance) + } else { + sendMessage(player, "That is not your skullball.") + } + } + /** Show current goal to score. */ + fun showGoal(player: Player, ballNpc: NPC) { + if (getAttribute(ballNpc, "target", null)?.username == player.username) { + val currGoal = getAttribute(player, SkullballCourse.attributeSkullballCurrentGoal, 0) + clearHintIcon(player) + registerHintIcon(player, getScenery(SkullballCourse.extractLoc(SkullballCourse.skullballGoals[currGoal]))!!) + } else { + sendMessage(player, "That is not your skullball.") + } + } + + /** Calculate the amount of time from the first goal to the final goal. */ + fun calcTime(player: Player) : String { + val startTime = getAttribute(player, SkullballCourse.attributeSkullballStartTime, System.currentTimeMillis()) + val endTime = System.currentTimeMillis() + val timeDiffInSecs = (endTime - startTime) / 1000 + val finalMins = timeDiffInSecs / 60 + val finalSecs = timeDiffInSecs % 60 + return String.format("%01d:%02d", finalMins, finalSecs) + } + + /** Calculate the amount exp earned when kicked into the last goal. */ + fun calcExp(player: Player) : Int { + val startTime = getAttribute(player, SkullballCourse.attributeSkullballStartTime, System.currentTimeMillis()) + val endTime = System.currentTimeMillis() + val timeDiffInSecs = (((endTime - startTime) / 1000) - 240).toInt().coerceAtLeast(0) + return (750 - timeDiffInSecs / 3).coerceAtLeast(0) // Kotlin what the fuck is this function + } + } + + var clearTime = 0 + + override fun onRemoval(self: NPC) { + clearTime = 0 + } + + override fun tick(self: NPC): Boolean { + // You have 800 ticks = 8 mins to kick the ball into the goal. + if (clearTime++ > 800) { + clearTime = 0 + val player = getAttribute(self, "target", null) + if (player != null) { + removeAttribute(player, SkullballCourse.attributeSkullballInstance) + } + removeAttribute(self, "target") + self.clear() + } + if (!self.location.equals(3563, 9866, 0)) { + return true + } + val player = getAttribute(self, "target", null) + if (player == null) { + return true + } + if (getAttribute(player, SkullballCourse.attributeSkullballCurrentGoal, 0) != 10) { + sendMessage(player, "You did not score all the goals.") + return true + } + sendMessage(player, "Well done - you've finished the skullball course!!!") + lock(player, Animation(1605).duration) + queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + animate(player, 1605) + return@queueScript delayScript(player, Animation(1605).duration) + } + + 1 -> { + setInterfaceText(player, calcTime(player), SkullballCourse.skullballGoalIface, 5) + val finalExp = calcExp(player) + setInterfaceText(player, finalExp.toString(), SkullballCourse.skullballGoalIface, 6) + rewardXP(player, Skills.AGILITY, finalExp.toDouble()) + + openInterface(player, SkullballCourse.skullballGoalIface) + + removeAttribute(player, SkullballCourse.attributeSkullballInstance) + removeAttribute(player, SkullballCourse.attributeSkullballCurrentGoal) + removeAttribute(player, SkullballCourse.attributeSkullballStartTime) + removeAttribute(self, "target") + self.clear() + return@queueScript stopExecuting(player) + } + + else -> return@queueScript stopExecuting(player) + } + } + return true + } + + override fun defineListeners() { + on(NPCs.SKULLBALL_1659, NPC, "tap") { player, node -> + moveBall(player, node as NPC, 1) + return@on true + } + + on(NPCs.SKULLBALL_1659, NPC, "kick") { player, node -> + moveBall(player, node as NPC, 4) + return@on true + } + + on(NPCs.SKULLBALL_1659, NPC, "shoot") { player, node -> + moveBall(player, node as NPC, 9) + return@on true + } + + on(NPCs.SKULLBALL_1659, NPC, "show-goal") { player, node -> + showGoal(player, node as NPC) + return@on true + } + } + override fun defineAreaBorders(): Array { return arrayOf(getRegionBorders(14234)) } - override fun getRestrictions(): Array { - return arrayOf() - } - - override fun areaEnter(entity: Entity) { - } - override fun areaLeave(entity: Entity, logout: Boolean) { if (entity is Player) { + println("Leaving Skullball: ${entity.username}") SkullballCourse.clearBall(entity) } } diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt index 359f104b1..33de25c63 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballTrainerDialogue.kt @@ -49,7 +49,7 @@ class SkullballTrainerDialogueFile : DialogueLabeller() { label("skullballinprogress") player(ChatAnim.THINKING, "How many goals have I got left?") exec { player, npc -> - if (getAttribute(player, SkullballCourse.attributeSkullballInstance, null) != null) { + if (getAttribute(player, SkullballCourse.attributeSkullballCurrentGoal, 0) != 10) { goto("xgoals") } else { goto("finalgoal") From 7f4f5102af7b3d8c4c5ec52757e76484021b9551 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sat, 12 Jul 2025 17:50:11 -0700 Subject: [PATCH 20/30] Critical fix for skullball. --- .../region/morytania/werewolfagility/SkullballCourse.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index 47f4b3960..000850815 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -132,10 +132,10 @@ class SkullballBehavior : NPCBehavior(NPCs.SKULLBALL_1659), InteractionListener, // For each tile to move, for (i in 1..distanceToMove) { nextTile = nextTile.transform(moveDirection) - if (!(RegionManager.isTeleportPermitted(nextTile) || // If not a walkable square - nextTile.equals(3563, 9865, 0) || // If not the final goal - nextTile.equals(3563, 9866, 0) || // If not the final goal - getScenery(nextTile)?.id != 5146 // If not the skeleton goal + if (!(RegionManager.isTeleportPermitted(nextTile) || // walkable square + nextTile.equals(3563, 9865, 0) || // final goal + nextTile.equals(3563, 9866, 0) || // final goal + getScenery(nextTile)?.id == 5146 // skeleton goal ) ) { // Tile is blocked, reverse ball direction and set a walkingQueue Path. @@ -143,6 +143,7 @@ class SkullballBehavior : NPCBehavior(NPCs.SKULLBALL_1659), InteractionListener, nextTile = nextTile.transform(moveDirection) npc.walkingQueue.addPath(nextTile.x, nextTile.y) nextTile = nextTile.transform(moveDirection) + if (nextTile.equals(3563, 9866, 0)) { break } } } npc.walkingQueue.addPath(nextTile.x, nextTile.y) From 48d3379b0568af20bfd4779ff9ebc30825d7b770 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sat, 12 Jul 2025 18:51:11 -0700 Subject: [PATCH 21/30] Jumping tiles. You have to do it manually now. --- .../werewolfagility/AgilityCourse.kt | 33 ++++++++----------- .../werewolfagility/SkullballCourse.kt | 1 - 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt index b83ff8896..bb5e35361 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt @@ -78,30 +78,24 @@ class AgilityCourse : InteractionListener { } - // Stepping stones + // Stepping stones (destination overrides below) on(Scenery.STEPPING_STONE_35996, IntType.SCENERY, "jump-to") { player, node -> - val agilityBoss = findLocalNPC(player, NPCs.AGILITY_BOSS_1661) - if (agilityBoss != null) { - sendChat(agilityBoss, "FETCH!!!!!") - face(agilityBoss, Location(3540, 9877, 0)) - animate(agilityBoss, 6547) - produceGroundItem(player, Items.STICK_4179, 1, Location(3543, 9912)) - } if (!hasLevelStat(player, Skills.AGILITY, 60)) { sendDialogue(player, "You need an Agility level of at least 60 to do this.") return@on false } - var count = 0 - rewardXP(player, Skills.AGILITY, 50.0) - queueScript(player, 3, QueueStrength.SOFT) { - if (!player.location.equals(steppingStones[6]) || count <= 5) { - lock(player, 3) - count++ - player.locks.lockTeleport(3) - ForceMovement.run(player, steppingStones[count], steppingStones[count+1], Animation(741), Animation(741), if (count == 2) Direction.EAST else Direction.NORTH, 20).endAnimation = Animation.RESET - return@queueScript delayScript(player, 3) + val arrIndex = steppingStones.indexOf(node.location) + ForceMovement.run(player, steppingStones[arrIndex-1], steppingStones[arrIndex], Animation(741), Animation(741), if (arrIndex == 3) Direction.EAST else Direction.NORTH, 20).endAnimation = Animation.RESET + + if (arrIndex == 1) { + val agilityBoss = findLocalNPC(player, NPCs.AGILITY_BOSS_1661) + if (agilityBoss != null) { + sendChat(agilityBoss, "FETCH!!!!!") + face(agilityBoss, Location(3540, 9877, 0)) + animate(agilityBoss, 6547) + produceGroundItem(player, Items.STICK_4179, 1, Location(3543, 9912)) + spawnProjectile(Location(3540, 9873), Location(3540, 9883), 1158, 0, 0, 0, 60, 0) } - return@queueScript stopExecuting(player) } return@on true } @@ -217,7 +211,8 @@ class AgilityCourse : InteractionListener { override fun defineDestinationOverrides() { setDest(IntType.SCENERY, intArrayOf(Scenery.STEPPING_STONE_35996),"jump-to"){ player, node -> - return@setDest Location.create(3538, 9873, 0) + val arrIndex = steppingStones.indexOf(node.location) + return@setDest steppingStones[arrIndex - 1] } } } \ No newline at end of file diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index 000850815..ad4718036 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -277,7 +277,6 @@ class SkullballBehavior : NPCBehavior(NPCs.SKULLBALL_1659), InteractionListener, override fun areaLeave(entity: Entity, logout: Boolean) { if (entity is Player) { - println("Leaving Skullball: ${entity.username}") SkullballCourse.clearBall(entity) } } From 84db1d9f88fbd1517e57544e185d50c3d76e08c3 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sat, 12 Jul 2025 23:03:32 -0700 Subject: [PATCH 22/30] Agility with werewolf saying stuff. --- .../werewolfagility/AgilityCourse.kt | 44 ++++++++++++++++++- .../main/core/game/world/map/Location.java | 11 +++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt index bb5e35361..cbff7478d 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt @@ -51,6 +51,39 @@ class AgilityCourse : InteractionListener { // anim 767 - Landing on stomach + fun nearestWerewolfSay(loc: Location, chatText: String) { + var werewolfNpc = findLocalNPCs(loc, NPCs.AGILITY_TRAINER_1663).sortedWith { a, b -> + a.location.getDistanceSquared(loc) - b.location.getDistanceSquared(loc) + }.getOrNull(0) + if (werewolfNpc != null) { + println("werewolf ${werewolfNpc.location}") + sendChat(werewolfNpc, chatText) + } + } + + fun randomWerewolfSay(): String { + return listOf( + "Remember - a slow wolf is a hungry wolf!!", + "Get on with it - you need your whiskers perking!!!!", + "Claws first - think later.", + "Imagine the smell of blood in your nostrils!!!", + "I never really wanted to be an agility trainer...", + "It'll be worth it when you hunt!!", + "Let's see those powerful backlegs at work!!", + "Let the bloodlust take you!!", + "You're the slowest wolf I've ever had the misfortune to witness!!", + "When you're done there's a human with your name on it!!", + ).random() + } + + fun randomZiplineWerewolfSay(): String { + return listOf( + "Give my regards to the ground...", + "Don't let the spikes or the blood put you off...", + "Now for a true test of teeth...", + ).random() + } + } override fun defineListeners() { @@ -94,9 +127,12 @@ class AgilityCourse : InteractionListener { face(agilityBoss, Location(3540, 9877, 0)) animate(agilityBoss, 6547) produceGroundItem(player, Items.STICK_4179, 1, Location(3543, 9912)) - spawnProjectile(Location(3540, 9873), Location(3540, 9883), 1158, 0, 0, 0, 60, 0) + spawnProjectile(Location(3540, 9873), Location(3540, 9883), 1158, 0, 0, 1, 60, 0) } } + if (arrIndex == 5) { + rewardXP(player, Skills.AGILITY, 50.0) + } return@on true } @@ -107,6 +143,7 @@ class AgilityCourse : InteractionListener { return@on false } if (player.location.y < node.location.y) { + nearestWerewolfSay(Location(3540, 9902), randomWerewolfSay()) rewardXP(player, Skills.AGILITY, 60.0) ForceMovement.run(player, player.location, player.location.transform(0, 2, 0), Animation(1603), Animation(1603), Direction.NORTH, 20).endAnimation = Animation.RESET } else { @@ -122,6 +159,7 @@ class AgilityCourse : InteractionListener { return@on false } if (player.location.y < node.location.y) { + nearestWerewolfSay(Location(3540, 9902), randomWerewolfSay()) rewardXP(player, Skills.AGILITY, 15.0) ForceMovement.run(player, player.location, player.location.transform(0, 5, 0), Animation(10580), Animation(844), Direction.NORTH, 10).endAnimation = Animation(10579) } else { @@ -137,6 +175,7 @@ class AgilityCourse : InteractionListener { return@on false } if (player.location.x > node.location.x) { + nearestWerewolfSay(Location(3536, 9912), randomWerewolfSay()) rewardXP(player, Skills.AGILITY, 25.0) ForceMovement.run(player, player.location, player.location.transform(-2, 0, 0), Animation(2049), Animation(2049), Direction.WEST, 10).endAnimation = Animation.RESET } else { @@ -185,13 +224,14 @@ class AgilityCourse : InteractionListener { queueScript(player, 2, QueueStrength.SOFT) { stage -> when (stage) { 0 -> { + nearestWerewolfSay(Location(3527, 9909), randomZiplineWerewolfSay()) animate(player, 1601) sendMessage(player, "You bravely cling on to the death slide by your teeth ...") return@queueScript delayScript(player, 2) } 1 -> { sendChat(player, "WAAAAAARRRGGGHHH!!!!!!") - ForceMovement.run(player, startTile, finalTile, Animation(1602), Animation(1602), Direction.SOUTH, 40).endAnimation = Animation.RESET + ForceMovement.run(player, startTile, finalTile, Animation(1602), Animation(1602), Direction.SOUTH, 60).endAnimation = Animation.RESET return@queueScript delayScript(player, 4) } 2 -> { diff --git a/Server/src/main/core/game/world/map/Location.java b/Server/src/main/core/game/world/map/Location.java index ea7d1794c..ed0927280 100644 --- a/Server/src/main/core/game/world/map/Location.java +++ b/Server/src/main/core/game/world/map/Location.java @@ -261,6 +261,17 @@ public final class Location extends Node { return Math.sqrt(xdiff * xdiff + ydiff * ydiff); } + /** + * Returns the distance between you and the other squared. This removes the square root for comparison functions. + * @param other The other location. + * @return The amount of distance between you and other, squared. + */ + public int getDistanceSquared(Location other) { + int xdiff = this.getX() - other.getX(); + int ydiff = this.getY() - other.getY(); + return xdiff * xdiff + ydiff * ydiff; + } + /** * Returns the distance between the first and the second specified distance. * @param first The first location. From 2315ba9f9d4bfd055153edb3e8d00e8effa4a39a Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Fri, 18 Jul 2025 00:40:53 -0700 Subject: [PATCH 23/30] Fixed comments --- .../werewolfagility/AgilityCourse.kt | 141 ++++++++++++------ 1 file changed, 92 insertions(+), 49 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt index cbff7478d..e7ae8323d 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt @@ -5,6 +5,7 @@ import core.game.dialogue.FacialExpression import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.interaction.QueueStrength +import core.game.node.entity.combat.ImpactHandler import core.game.node.entity.impl.ForceMovement import core.game.node.entity.skill.Skills import core.game.world.map.Direction @@ -119,8 +120,9 @@ class AgilityCourse : InteractionListener { } val arrIndex = steppingStones.indexOf(node.location) ForceMovement.run(player, steppingStones[arrIndex-1], steppingStones[arrIndex], Animation(741), Animation(741), if (arrIndex == 3) Direction.EAST else Direction.NORTH, 20).endAnimation = Animation.RESET + rewardXP(player, Skills.AGILITY, 10.0) - if (arrIndex == 1) { + if (arrIndex == 1 && !inInventory(player, Items.STICK_4179)) { val agilityBoss = findLocalNPC(player, NPCs.AGILITY_BOSS_1661) if (agilityBoss != null) { sendChat(agilityBoss, "FETCH!!!!!") @@ -130,9 +132,6 @@ class AgilityCourse : InteractionListener { spawnProjectile(Location(3540, 9873), Location(3540, 9883), 1158, 0, 0, 1, 60, 0) } } - if (arrIndex == 5) { - rewardXP(player, Skills.AGILITY, 50.0) - } return@on true } @@ -144,7 +143,7 @@ class AgilityCourse : InteractionListener { } if (player.location.y < node.location.y) { nearestWerewolfSay(Location(3540, 9902), randomWerewolfSay()) - rewardXP(player, Skills.AGILITY, 60.0) + rewardXP(player, Skills.AGILITY, 20.0) ForceMovement.run(player, player.location, player.location.transform(0, 2, 0), Animation(1603), Animation(1603), Direction.NORTH, 20).endAnimation = Animation.RESET } else { sendMessage(player, "You've already jumped over the hurdle.") @@ -190,61 +189,105 @@ class AgilityCourse : InteractionListener { sendDialogue(player, "You need an Agility level of at least 60 to do this.") return@on false } - rewardXP(player, Skills.AGILITY, 200.0) - // This is up to my decision on whether to torture you. - // "Below 0 it makes no further difference." (Referring to weight) + var successChancePercent = 100.0 // "With level 80 in Agility and Strength and a weight of 2 kg or lower, this obstacle will never be failed." - - var totalSuccess = 1.0 - if (getDynLevel(player, Skills.AGILITY) >= 80 && getDynLevel(player, Skills.STRENGTH) >= 80) { - val weightSuccess = (102.0 - Math.max(player.settings.weight, 0.0)) / 100 // 102% chance, minus 1% per weight gain. Wearing lover or equal to 2kg will be 100%. - totalSuccess *= weightSuccess - } else { - val agilitySuccess = RandomFunction.getSkillSuccessChance(0.0, 320.0, 60) // 0 at lvl1, 256 at lvl80 - val strengthSuccess = RandomFunction.getSkillSuccessChance(0.0, 320.0, 60) // 0 at lvl1, 256 at lvl80 - val weightSuccess = (80.0 - Math.max(player.settings.weight, 0.0)) / 100 // 80% chance, minus 1% per weight gain. - totalSuccess *= agilitySuccess - totalSuccess *= strengthSuccess - totalSuccess *= weightSuccess + // Otherwise, this is up to my decision on whether to torture you + if (!(getDynLevel(player, Skills.AGILITY) >= 80 && getDynLevel(player, Skills.STRENGTH) >= 80 && player.settings.weight <= 2.0)) { + // All the successes are between 0.0 to 1.0 range + val agilitySuccess = RandomFunction.getSkillSuccessChance(0.0, 320.0, 60) / 100// 0 at lvl1, 256 at lvl80 extrapolate to 320 at lvl 99 + val strengthSuccess = RandomFunction.getSkillSuccessChance(0.0, 320.0, 60) / 100 // 0 at lvl1, 256 at lvl80 extrapolate to 320 at lvl 99 + val weightSuccess = Math.max(80.0 - player.settings.weight, 0.0) / 100 // 80% chance, minus 1% per weight gain. + successChancePercent *= agilitySuccess + successChancePercent *= strengthSuccess + successChancePercent *= weightSuccess } - var finalTile = endTile - // roll a number, between 0-totalSuccess means you succeed, otherwise you fail. - if(RandomFunction.random(0.0, 100.0) < totalSuccess) { - - } else { - // based on total success, find where to land. - 100 - totalSuccess - } + sendMessage(player, "Total Success " + successChancePercent.toString()) + // Align player up on the zipline forceMove(player, player.location, Location(3528, 9910, 0), 0,10) - face(player, Location(3528, 9900, 0)) - queueScript(player, 2, QueueStrength.SOFT) { stage -> - when (stage) { - 0 -> { - nearestWerewolfSay(Location(3527, 9909), randomZiplineWerewolfSay()) - animate(player, 1601) - sendMessage(player, "You bravely cling on to the death slide by your teeth ...") - return@queueScript delayScript(player, 2) + face(player, Location(3528, 9915, 0)) + + // roll a number, between 0-totalSuccess means you succeed, otherwise between totalSuccess-100 you fail. + if(RandomFunction.random(0.0, 100.0) < successChancePercent) { // Success + queueScript(player, 2, QueueStrength.SOFT) { stage -> + when (stage) { + 0 -> { + nearestWerewolfSay(Location(3527, 9909), randomZiplineWerewolfSay()) + animate(player, 1601) + sendMessage(player, "You bravely cling on to the death slide by your teeth ...") + return@queueScript delayScript(player, 2) + } + 1 -> { + sendChat(player, "WAAAAAARRRGGGHHH!!!!!!") + ForceMovement.run(player, startTile, endTile, Animation(1602), Animation(1602), Direction.SOUTH, 60).endAnimation = Animation.RESET + return@queueScript delayScript(player, 8) + } + 2 -> { + rewardXP(player, Skills.AGILITY, 200.0) + sendMessage(player, ".. and land safely on your feet.") + teleport(player, endTile) + return@queueScript stopExecuting(player) + } + else -> return@queueScript stopExecuting(player) } - 1 -> { - sendChat(player, "WAAAAAARRRGGGHHH!!!!!!") - ForceMovement.run(player, startTile, finalTile, Animation(1602), Animation(1602), Direction.SOUTH, 60).endAnimation = Animation.RESET - return@queueScript delayScript(player, 4) - } - 2 -> { - rewardXP(player, Skills.AGILITY, 200.0) - sendMessage(player, ".. and land safely on your feet.") - sendMessage(player, ".. only to fall from a great height!") - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) + } + } else { + // Based on total success, find where to land. If you had a lower chance, you get the early drop and lower XP. + var finalTile = endTile + var animTicks = 6 + var fallTile = endTile + var rewardXP = 200.0 + if (successChancePercent <= 33.0) { + finalTile = midwayTile1 + fallTile = arrayOf(failureTileLeft1, failureTileRight1).random() + animTicks = 3 + rewardXP = 140.0 + } + if (successChancePercent > 33.0 && successChancePercent <= 66.0 ) { + finalTile = midwayTile2 + fallTile = arrayOf(failureTileLeft2, failureTileRight2).random() + animTicks = 5 + rewardXP = 160.0 + } + if (successChancePercent > 66.0) { + finalTile = midwayTile3 + fallTile = arrayOf(failureTileLeft3, failureTileRight3).random() + animTicks = 7 + rewardXP = 180.0 } - + queueScript(player, 2, QueueStrength.SOFT) { stage -> + when (stage) { + 0 -> { + nearestWerewolfSay(Location(3527, 9909), randomZiplineWerewolfSay()) + animate(player, 1601) + sendMessage(player, "You bravely cling on to the death slide by your teeth ...") + return@queueScript delayScript(player, 2) + } + 1 -> { + sendChat(player, "WAAAAAARRRGGGHHH!!!!!!") + ForceMovement.run(player, startTile, finalTile, Animation(1602), Animation(1602), Direction.SOUTH, 60).endAnimation = Animation(767) + return@queueScript delayScript(player, animTicks) + } + 2 -> { + rewardXP(player, Skills.AGILITY, rewardXP) + sendMessage(player, ".. only to fall from a great height!") + ForceMovement.run(player, finalTile, fallTile, Animation(767), Animation(767), Direction.SOUTH, 60).endAnimation = Animation(767) + return@queueScript delayScript(player, 2) + } + 3 -> { + teleport(player, fallTile) + player.impactHandler.manualHit(player, (1..30).random(), ImpactHandler.HitsplatType.NORMAL) + return@queueScript stopExecuting(player) + } + else -> return@queueScript stopExecuting(player) + } + } } + return@on true } } From b09f97f9c28b119d36e7e424c73f775b0dfac328 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sun, 27 Jul 2025 00:21:16 -0700 Subject: [PATCH 24/30] Some polish --- .../werewolfagility/AgilityCourse.kt | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt index e7ae8323d..b2af1cf02 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt @@ -5,18 +5,21 @@ import core.game.dialogue.FacialExpression import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.interaction.QueueStrength +import core.game.node.entity.Entity import core.game.node.entity.combat.ImpactHandler import core.game.node.entity.impl.ForceMovement +import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills import core.game.world.map.Direction import core.game.world.map.Location +import core.game.world.map.zone.ZoneBorders import core.game.world.update.flag.context.Animation import core.tools.RandomFunction import org.rs09.consts.Items import org.rs09.consts.NPCs import org.rs09.consts.Scenery -class AgilityCourse : InteractionListener { +class AgilityCourse : InteractionListener, MapArea { companion object { @@ -57,7 +60,7 @@ class AgilityCourse : InteractionListener { a.location.getDistanceSquared(loc) - b.location.getDistanceSquared(loc) }.getOrNull(0) if (werewolfNpc != null) { - println("werewolf ${werewolfNpc.location}") + // println("werewolf ${werewolfNpc.location}") sendChat(werewolfNpc, chatText) } } @@ -203,7 +206,7 @@ class AgilityCourse : InteractionListener { successChancePercent *= weightSuccess } - sendMessage(player, "Total Success " + successChancePercent.toString()) + // sendMessage(player, "Total Success " + successChancePercent.toString()) // Align player up on the zipline @@ -212,10 +215,11 @@ class AgilityCourse : InteractionListener { // roll a number, between 0-totalSuccess means you succeed, otherwise between totalSuccess-100 you fail. if(RandomFunction.random(0.0, 100.0) < successChancePercent) { // Success + nearestWerewolfSay(Location(3527, 9909), randomZiplineWerewolfSay()) queueScript(player, 2, QueueStrength.SOFT) { stage -> when (stage) { 0 -> { - nearestWerewolfSay(Location(3527, 9909), randomZiplineWerewolfSay()) + face(player, Location(3528, 9915, 0)) animate(player, 1601) sendMessage(player, "You bravely cling on to the death slide by your teeth ...") return@queueScript delayScript(player, 2) @@ -243,7 +247,7 @@ class AgilityCourse : InteractionListener { if (successChancePercent <= 33.0) { finalTile = midwayTile1 fallTile = arrayOf(failureTileLeft1, failureTileRight1).random() - animTicks = 3 + animTicks = 4 rewardXP = 140.0 } if (successChancePercent > 33.0 && successChancePercent <= 66.0 ) { @@ -255,14 +259,15 @@ class AgilityCourse : InteractionListener { if (successChancePercent > 66.0) { finalTile = midwayTile3 fallTile = arrayOf(failureTileLeft3, failureTileRight3).random() - animTicks = 7 + animTicks = 6 rewardXP = 180.0 } + nearestWerewolfSay(Location(3527, 9909), randomZiplineWerewolfSay()) queueScript(player, 2, QueueStrength.SOFT) { stage -> when (stage) { 0 -> { - nearestWerewolfSay(Location(3527, 9909), randomZiplineWerewolfSay()) + face(player, Location(3528, 9915, 0)) animate(player, 1601) sendMessage(player, "You bravely cling on to the death slide by your teeth ...") return@queueScript delayScript(player, 2) @@ -273,14 +278,16 @@ class AgilityCourse : InteractionListener { return@queueScript delayScript(player, animTicks) } 2 -> { + player.impactHandler.manualHit(player, (1..30).random(), ImpactHandler.HitsplatType.NORMAL) rewardXP(player, Skills.AGILITY, rewardXP) sendMessage(player, ".. only to fall from a great height!") - ForceMovement.run(player, finalTile, fallTile, Animation(767), Animation(767), Direction.SOUTH, 60).endAnimation = Animation(767) + teleport(player, fallTile) + // Can't get this to chain animations. + //ForceMovement.run(player, finalTile, fallTile, Animation(767), Animation(767), Direction.SOUTH, 60).endAnimation = Animation(767) return@queueScript delayScript(player, 2) } 3 -> { teleport(player, fallTile) - player.impactHandler.manualHit(player, (1..30).random(), ImpactHandler.HitsplatType.NORMAL) return@queueScript stopExecuting(player) } else -> return@queueScript stopExecuting(player) @@ -298,4 +305,19 @@ class AgilityCourse : InteractionListener { return@setDest steppingStones[arrIndex - 1] } } + + override fun defineAreaBorders(): Array { + // Area of the zipline. + return arrayOf(ZoneBorders(3527, 9876, 3529, 9907)) + } + + override fun areaLeave(entity: Entity, logout: Boolean) { + // In case you log out during the zipline of death slide, you won't be left on it. + // You lose that XP though... + if (entity is Player) { + if (logout) { + teleport(entity, endTile) + } + } + } } \ No newline at end of file From c28348e96d477af42072313da5a2577f789944d1 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sun, 27 Jul 2025 00:38:47 -0700 Subject: [PATCH 25/30] Some polish --- .../content/region/morytania/werewolfagility/AgilityCourse.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt index b2af1cf02..d61c0e047 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt @@ -145,7 +145,7 @@ class AgilityCourse : InteractionListener, MapArea { return@on false } if (player.location.y < node.location.y) { - nearestWerewolfSay(Location(3540, 9902), randomWerewolfSay()) + nearestWerewolfSay(Location(3540, 9890), randomWerewolfSay()) rewardXP(player, Skills.AGILITY, 20.0) ForceMovement.run(player, player.location, player.location.transform(0, 2, 0), Animation(1603), Animation(1603), Direction.NORTH, 20).endAnimation = Animation.RESET } else { From 5d5cac6156a65cb287b640d4b5f5738a3ae713c8 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sun, 27 Jul 2025 09:20:48 -0700 Subject: [PATCH 26/30] Some polish --- .../region/morytania/werewolfagility/AgilityCourse.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt index d61c0e047..9e12b7c72 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt @@ -145,7 +145,6 @@ class AgilityCourse : InteractionListener, MapArea { return@on false } if (player.location.y < node.location.y) { - nearestWerewolfSay(Location(3540, 9890), randomWerewolfSay()) rewardXP(player, Skills.AGILITY, 20.0) ForceMovement.run(player, player.location, player.location.transform(0, 2, 0), Animation(1603), Animation(1603), Direction.NORTH, 20).endAnimation = Animation.RESET } else { @@ -244,19 +243,19 @@ class AgilityCourse : InteractionListener, MapArea { var animTicks = 6 var fallTile = endTile var rewardXP = 200.0 - if (successChancePercent <= 33.0) { + if (successChancePercent <= 20.0) { finalTile = midwayTile1 fallTile = arrayOf(failureTileLeft1, failureTileRight1).random() animTicks = 4 rewardXP = 140.0 } - if (successChancePercent > 33.0 && successChancePercent <= 66.0 ) { + if (successChancePercent > 20.0 && successChancePercent <= 40.0 ) { finalTile = midwayTile2 fallTile = arrayOf(failureTileLeft2, failureTileRight2).random() animTicks = 5 rewardXP = 160.0 } - if (successChancePercent > 66.0) { + if (successChancePercent > 40.0) { finalTile = midwayTile3 fallTile = arrayOf(failureTileLeft3, failureTileRight3).random() animTicks = 6 @@ -278,7 +277,6 @@ class AgilityCourse : InteractionListener, MapArea { return@queueScript delayScript(player, animTicks) } 2 -> { - player.impactHandler.manualHit(player, (1..30).random(), ImpactHandler.HitsplatType.NORMAL) rewardXP(player, Skills.AGILITY, rewardXP) sendMessage(player, ".. only to fall from a great height!") teleport(player, fallTile) @@ -288,6 +286,7 @@ class AgilityCourse : InteractionListener, MapArea { } 3 -> { teleport(player, fallTile) + player.impactHandler.manualHit(player, (1..30).random(), ImpactHandler.HitsplatType.NORMAL) return@queueScript stopExecuting(player) } else -> return@queueScript stopExecuting(player) From 7fa8958ed57ebf8eade1afb3b3bbade2b028a040 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sat, 25 Oct 2025 23:52:12 -0700 Subject: [PATCH 27/30] Fix skullball goal unable to kick --- .../morytania/werewolfagility/SkullballCourse.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index ad4718036..4af1fef2b 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -1,5 +1,6 @@ package content.region.morytania.werewolfagility +import content.region.morytania.werewolfagility.SkullballCourse.Companion.skullballGoals import core.api.* import core.game.interaction.InteractionListener import core.game.interaction.QueueStrength @@ -42,7 +43,7 @@ class SkullballCourse : MapArea { ZoneBorders(3574,9888,3574,9888), ZoneBorders(3575,9878,3575,9878), ZoneBorders(3568,9864,3568,9864), // rot 3 - ZoneBorders(3563,9865,3563, 9865), // End goal tunnel + ZoneBorders(3563,9865,3563,9865), // End goal tunnel ) /** Extract Location from ZoneBorders **/ @@ -114,7 +115,9 @@ class SkullballCourse : MapArea { setAttribute(player, attributeSkullballCurrentGoal, currGoal + 1) val nextGoal = getAttribute(player, attributeSkullballCurrentGoal, 0) clearHintIcon(player) - registerHintIcon(player, getScenery(extractLoc(skullballGoals[nextGoal]))!!) + if (nextGoal < skullballGoals.size) { + registerHintIcon(player, getScenery(extractLoc(skullballGoals[nextGoal]))!!) + } } } } @@ -165,7 +168,9 @@ class SkullballBehavior : NPCBehavior(NPCs.SKULLBALL_1659), InteractionListener, if (getAttribute(ballNpc, "target", null)?.username == player.username) { val currGoal = getAttribute(player, SkullballCourse.attributeSkullballCurrentGoal, 0) clearHintIcon(player) - registerHintIcon(player, getScenery(SkullballCourse.extractLoc(SkullballCourse.skullballGoals[currGoal]))!!) + if (nextGoal < skullballGoals.size) { + registerHintIcon(player, getScenery(SkullballCourse.extractLoc(SkullballCourse.skullballGoals[currGoal]))!!) + } } else { sendMessage(player, "That is not your skullball.") } From 9ed59abee2e081356a0d4db37ef58ea28d69e544 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sat, 25 Oct 2025 23:54:21 -0700 Subject: [PATCH 28/30] Fix skullball goal unable to kick --- .../content/region/morytania/werewolfagility/SkullballCourse.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index 4af1fef2b..bf1a80422 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -168,7 +168,7 @@ class SkullballBehavior : NPCBehavior(NPCs.SKULLBALL_1659), InteractionListener, if (getAttribute(ballNpc, "target", null)?.username == player.username) { val currGoal = getAttribute(player, SkullballCourse.attributeSkullballCurrentGoal, 0) clearHintIcon(player) - if (nextGoal < skullballGoals.size) { + if (currGoal < skullballGoals.size) { registerHintIcon(player, getScenery(SkullballCourse.extractLoc(SkullballCourse.skullballGoals[currGoal]))!!) } } else { From c13e86e11e2d2908b72c7a24dd079622f059e7c1 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sun, 26 Oct 2025 00:05:47 -0700 Subject: [PATCH 29/30] Lock player on slide. --- .../content/region/morytania/werewolfagility/AgilityCourse.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt index 9e12b7c72..7ce2392ac 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/AgilityCourse.kt @@ -211,7 +211,7 @@ class AgilityCourse : InteractionListener, MapArea { // Align player up on the zipline forceMove(player, player.location, Location(3528, 9910, 0), 0,10) face(player, Location(3528, 9915, 0)) - + lock(player, 6) // roll a number, between 0-totalSuccess means you succeed, otherwise between totalSuccess-100 you fail. if(RandomFunction.random(0.0, 100.0) < successChancePercent) { // Success nearestWerewolfSay(Location(3527, 9909), randomZiplineWerewolfSay()) From afdfbfa80d61e99a86c5de0fa323496abfb37a70 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sun, 26 Oct 2025 23:46:32 -0700 Subject: [PATCH 30/30] Some fuckball shoutouts --- .../werewolfagility/SkullballCourse.kt | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt index bf1a80422..fd94c9033 100644 --- a/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt +++ b/Server/src/main/content/region/morytania/werewolfagility/SkullballCourse.kt @@ -85,6 +85,41 @@ class SkullballCourse : MapArea { removeAttribute(player, attributeSkullballInstance) } } + + fun nearestWerewolfSay(loc: Location, chatText: String) { + var werewolfNpc = findLocalNPCs(loc, 40).filter { npc -> + npc.id == NPCs.SKULLBALL_TRAINER_1662 + }.sortedWith { a, b -> + a.location.getDistanceSquared(loc) - b.location.getDistanceSquared(loc) + }.getOrNull(0) + if (werewolfNpc != null) { + // println("werewolf ${werewolfNpc.location}") + sendChat(werewolfNpc, chatText) + } + } + + fun randomWerewolfSay(): String { + return listOf( + "You have truly gifted paws!", + "I've never seen anything like it!", + "Claws first - think later.", + "You need a few more skullball lessons.", + "Keep it up!", + "Don't give up the day job!", + "Look at @g[her,him] go!", + "Pathetic!", + "What - a - goal !!!", + "That was just plain lucky.", + ).random() + } + + fun randomZiplineWerewolfSay(): String { + return listOf( + "Give my regards to the ground...", + "Don't let the spikes or the blood put you off...", + "Now for a true test of teeth...", + ).random() + } } override fun defineAreaBorders(): Array { @@ -118,6 +153,11 @@ class SkullballCourse : MapArea { if (nextGoal < skullballGoals.size) { registerHintIcon(player, getScenery(extractLoc(skullballGoals[nextGoal]))!!) } + if (nextGoal < skullballGoals.size) { + nearestWerewolfSay(entity.location, randomWerewolfSay()) + } else { + nearestWerewolfSay(entity.location, "@g[He,She] shoots - @g[He,She] scores!!!!!") + } } } }