From a3ed4bede8e17f9c5d3c9a8525e844d9e9007505 Mon Sep 17 00:00:00 2001 From: "J. Govers" <31247938+Eikenb00m@users.noreply.github.com> Date: Sun, 23 Nov 2025 21:43:32 +0100 Subject: [PATCH 1/9] Create GnomeStrongholdCoursePlugin.kt --- .../agility/GnomeStrongholdCoursePlugin.kt | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt diff --git a/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt b/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt new file mode 100644 index 00000000..fd790ef5 --- /dev/null +++ b/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt @@ -0,0 +1,83 @@ +package org.alter.skills.agility + +import org.alter.api.Skills +import org.alter.api.ext.filterableMessage +import org.alter.game.model.Direction +import org.alter.game.model.ForcedMovement +import org.alter.game.model.Tile +import org.alter.game.model.entity.Player +import org.alter.game.pluginnew.PluginEvent +import org.alter.game.pluginnew.event.impl.onObjectOption +import org.alter.rscm.RSCM + +class GnomeStrongholdCoursePlugin : PluginEvent() { + + override fun init() { + + // LOG + onObjectOption("objects.gnome_log_balance1", "walk-across") { + handleObstacle( + player = player, + destination = Tile(2474, 3429, 0), + anim = "sequences.human_walk_logbalance_loop", + angle = Direction.SOUTH.angle, + duration1 = 5, + duration2 = 250, + xp = 7.5, + messageStart = "You walk carefully across the slippery log...", + messageEnd = "... and make it safely to the other side." + ) + } + + // NET + onObjectOption("objects.obstical_net2", "climb-over") { + if (player.tile.x !in 2471..2476) { + player.filterableMessage("You can't climb the net from this side.") + return@onObjectOption + } + + handleObstacle( + player = player, + destination = Tile(player.tile.x, 3424, 1), + anim = "sequences.human_climbing", + angle = Direction.SOUTH.angle, + duration1 = 33, + duration2 = 60, + xp = 10.0, + messageStart = "You climb up the net..." + ) + } + } + + private fun handleObstacle( + player: Player, + destination: Tile, + anim: String, + angle: Int, + duration1: Int, + duration2: Int, + xp: Double, + messageStart: String? = null, + messageEnd: String? = null + ) { + val movement = ForcedMovement.of( + src = player.tile, + dst = destination, + clientDuration1 = duration1, + clientDuration2 = duration2, + directionAngle = angle + ) + + player.queue { + messageStart?.let { player.filterableMessage(it) } + wait(1) + player.animate(anim) + player.forceMove(this, movement) + player.animate(RSCM.NONE) + wait(1) + + player.addXp(Skills.AGILITY, xp) + messageEnd?.let { player.filterableMessage(it) } + } + } +} From 4e30b58083ddfc2dd66dd4654836900f4097a9ac Mon Sep 17 00:00:00 2001 From: "J. Govers" <31247938+Eikenb00m@users.noreply.github.com> Date: Sun, 23 Nov 2025 23:12:21 +0100 Subject: [PATCH 2/9] Added more obstacles and Course State --- .../agility/GnomeStrongholdCoursePlugin.kt | 210 +++++++++++++++--- .../org/alter/game/model/attr/Attributes.kt | 5 + 2 files changed, 186 insertions(+), 29 deletions(-) diff --git a/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt b/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt index fd790ef5..719d8819 100644 --- a/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt +++ b/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt @@ -5,46 +5,153 @@ import org.alter.api.ext.filterableMessage import org.alter.game.model.Direction import org.alter.game.model.ForcedMovement import org.alter.game.model.Tile +import org.alter.game.model.attr.GNOME_AGILITY_STAGE import org.alter.game.model.entity.Player +import org.alter.game.model.move.moveTo import org.alter.game.pluginnew.PluginEvent import org.alter.game.pluginnew.event.impl.onObjectOption import org.alter.rscm.RSCM class GnomeStrongholdCoursePlugin : PluginEvent() { + private val MAX_STAGES = 7 + private val BONUS_XP = 39.0 + + private fun Player.getStage(): Int = attr[GNOME_AGILITY_STAGE] ?: 0 + private fun Player.setStage(v: Int) { attr[GNOME_AGILITY_STAGE] = v } + + override fun init() { - // LOG + //Balance Log onObjectOption("objects.gnome_log_balance1", "walk-across") { handleObstacle( player = player, destination = Tile(2474, 3429, 0), anim = "sequences.human_walk_logbalance_loop", - angle = Direction.SOUTH.angle, duration1 = 5, duration2 = 250, + angle = Direction.SOUTH.angle, xp = 7.5, messageStart = "You walk carefully across the slippery log...", - messageEnd = "... and make it safely to the other side." + messageEnd = "... and make it safely to the other side.", + stage = 1 ) } - // NET + + //First net onObjectOption("objects.obstical_net2", "climb-over") { + if (player.tile.x !in 2471..2476) { - player.filterableMessage("You can't climb the net from this side.") return@onObjectOption } handleObstacle( player = player, destination = Tile(player.tile.x, 3424, 1), - anim = "sequences.human_climbing", - angle = Direction.SOUTH.angle, - duration1 = 33, - duration2 = 60, + anim = "sequences.human_reachforladder", + simpleMove = true, xp = 10.0, - messageStart = "You climb up the net..." + messageStart = "You climb up the netting...", + stage = 2 + ) + } + //First Tree Branch + onObjectOption("objects.climbing_branch", "climb") { + handleObstacle( + player = player, + destination = Tile(2473, 3420, 2), + anim = "sequences.human_reachforladder", + simpleMove = true, + xp = 6.5, + messageStart = "You climb the tree...", + messageEnd = "...To the platform above.", + stage = 3 + ) + } + //Balance Rope + onObjectOption("objects.balancing_rope", "walk-on") { + handleObstacle( + player = player, + destination = Tile(2483, 3420, 2), + anim = "sequences.human_walk_logbalance_loop", + duration1 = 5, + duration2 = 250, + angle = Direction.EAST.angle, + xp = 10.0, + messageStart = "You carefully cross the tightrope.", + stage = 4 + ) + } + //Second Tree Branch 1 + onObjectOption("objects.climbing_tree", "climb-down") { + handleObstacle( + player = player, + destination = Tile(2487, 3420, 0), + anim = "sequences.human_reachforladder", + simpleMove = true, + xp = 6.5, + messageStart = "You climb the tree...", + messageEnd = "You land on the ground.", + stage = 5 + ) + } + //Second Tree Branch 2 + onObjectOption("objects.climbing_tree2", "climb-down") { + handleObstacle( + player = player, + destination = Tile(2487, 3420, 0), + anim = "sequences.human_reachforladder", + simpleMove = true, + xp = 6.5, + messageStart = "You climb the tree...", + messageEnd = "You land on the ground.", + stage = 5 + ) + } + //Second net + onObjectOption("objects.obstical_net3", "climb-over") { + + if (player.tile.x !in 2483..2488) { + return@onObjectOption + } + + handleObstacle( + player = player, + destination = Tile(player.tile.x, 3428, 0), + anim = "sequences.human_reachforladder", + simpleMove = true, + xp = 10.0, + messageStart = "You climb up the netting...", + stage = 6 + ) + } + //Pipes + onObjectOption("objects.obstical_pipe3_1", "squeeze-through") { + handleObstacle( + player = player, + destination = Tile(2484, 3437, 0), + anim = "sequences.human_pipesqueeze", + duration1 = 5, + duration2 = 250, + angle = Direction.NORTH.angle, + xp = 10.0, + stage = 7, + endStage = true + ) + } + onObjectOption("objects.obstical_pipe3_2", "squeeze-through") { + handleObstacle( + player = player, + destination = Tile(2487, 3437, 0), + anim = "sequences.human_pipesqueeze", + duration1 = 5, + duration2 = 250, + angle = Direction.NORTH.angle, + xp = 10.0, + stage = 7, + endStage = true ) } } @@ -52,32 +159,77 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { private fun handleObstacle( player: Player, destination: Tile, - anim: String, - angle: Int, - duration1: Int, - duration2: Int, - xp: Double, + simpleMove: Boolean = false, + angle: Int? = null, + duration1: Int? = null, + duration2: Int? = null, + anim: String? = null, + xp: Double = 0.0, messageStart: String? = null, - messageEnd: String? = null + messageEnd: String? = null, + stage: Int = -1, + endStage: Boolean = false ) { - val movement = ForcedMovement.of( - src = player.tile, - dst = destination, - clientDuration1 = duration1, - clientDuration2 = duration2, - directionAngle = angle - ) + + val doForcedMove = + angle != null && duration1 != null && duration2 != null + + if (stage >= 0) { + val current = player.getStage() + if (current + 1 != stage) { + player.setStage(0) + return + } + } + player.queue { + messageStart?.let { player.filterableMessage(it) } + anim?.let { player.animate(it) } + if (doForcedMove) { + val movement = ForcedMovement.of( + src = player.tile, + dst = destination, + clientDuration1 = duration1, + clientDuration2 = duration2, + directionAngle = angle + ) + player.forceMove(this, movement) + wait(1) + player.animate(RSCM.NONE) + } + else if (simpleMove) { + player.moveTo(destination) + wait(1) + player.animate(RSCM.NONE) + } wait(1) - player.animate(anim) - player.forceMove(this, movement) - player.animate(RSCM.NONE) - wait(1) - - player.addXp(Skills.AGILITY, xp) + if (xp > 0.0) player.addXp(Skills.AGILITY, xp) messageEnd?.let { player.filterableMessage(it) } + handleStage(player, stage, endStage) + } + } + + private fun handleStage( + player: Player, + stage: Int, + endStage: Boolean + ) { + val current = player.getStage() + + if (!endStage && stage >= 0) { + player.setStage(stage) + return + } + + if (endStage) { + player.setStage(MAX_STAGES) + if (player.getStage() >= MAX_STAGES) { + player.addXp(Skills.AGILITY, BONUS_XP) + player.setStage(0) + player.filterableMessage("You have completed the Gnome Agility Course!") + } } } } diff --git a/game-server/src/main/kotlin/org/alter/game/model/attr/Attributes.kt b/game-server/src/main/kotlin/org/alter/game/model/attr/Attributes.kt index b34ca376..8d2c6fa6 100644 --- a/game-server/src/main/kotlin/org/alter/game/model/attr/Attributes.kt +++ b/game-server/src/main/kotlin/org/alter/game/model/attr/Attributes.kt @@ -260,3 +260,8 @@ val CHANGE_LOGGING = AttributeKey() * Instead of running tp */ val CLIENT_KEY_COMBINATION = AttributeKey() + +/** + * Agility course stages + */ +val GNOME_AGILITY_STAGE = AttributeKey() \ No newline at end of file From 3fc947fb407c8bb2b397138341115bd6bdc6ac38 Mon Sep 17 00:00:00 2001 From: "J. Govers" <31247938+Eikenb00m@users.noreply.github.com> Date: Sun, 23 Nov 2025 23:16:39 +0100 Subject: [PATCH 3/9] fix bonus xp --- .../org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt b/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt index 719d8819..216e88a3 100644 --- a/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt +++ b/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt @@ -15,7 +15,7 @@ import org.alter.rscm.RSCM class GnomeStrongholdCoursePlugin : PluginEvent() { private val MAX_STAGES = 7 - private val BONUS_XP = 39.0 + private val BONUS_XP = 50.0 private fun Player.getStage(): Int = attr[GNOME_AGILITY_STAGE] ?: 0 private fun Player.setStage(v: Int) { attr[GNOME_AGILITY_STAGE] = v } From 19f94123445539732263432d78fc4cb2ee152bc6 Mon Sep 17 00:00:00 2001 From: "J. Govers" <31247938+Eikenb00m@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:25:43 +0100 Subject: [PATCH 4/9] Named Attribute --- .../src/main/kotlin/org/alter/game/model/attr/Attributes.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game-server/src/main/kotlin/org/alter/game/model/attr/Attributes.kt b/game-server/src/main/kotlin/org/alter/game/model/attr/Attributes.kt index 8d2c6fa6..c755cd4c 100644 --- a/game-server/src/main/kotlin/org/alter/game/model/attr/Attributes.kt +++ b/game-server/src/main/kotlin/org/alter/game/model/attr/Attributes.kt @@ -264,4 +264,4 @@ val CLIENT_KEY_COMBINATION = AttributeKey() /** * Agility course stages */ -val GNOME_AGILITY_STAGE = AttributeKey() \ No newline at end of file +val GNOME_AGILITY_STAGE = AttributeKey("GnomeAgilityStage") \ No newline at end of file From 04f96d089bd159185c29d7d4eb88fdb3adf2c101 Mon Sep 17 00:00:00 2001 From: "J. Govers" <31247938+Eikenb00m@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:25:56 +0100 Subject: [PATCH 5/9] Update GnomeStrongholdCoursePlugin.kt --- .../agility/GnomeStrongholdCoursePlugin.kt | 78 ++++++++++--------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt b/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt index 216e88a3..50454158 100644 --- a/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt +++ b/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt @@ -20,7 +20,6 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { private fun Player.getStage(): Int = attr[GNOME_AGILITY_STAGE] ?: 0 private fun Player.setStage(v: Int) { attr[GNOME_AGILITY_STAGE] = v } - override fun init() { //Balance Log @@ -39,14 +38,9 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { ) } - - //First net + //Net 1 onObjectOption("objects.obstical_net2", "climb-over") { - - if (player.tile.x !in 2471..2476) { - return@onObjectOption - } - + if (player.tile.x !in 2471..2476) return@onObjectOption handleObstacle( player = player, destination = Tile(player.tile.x, 3424, 1), @@ -57,9 +51,10 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { stage = 2 ) } - //First Tree Branch + + //Tree Branch onObjectOption("objects.climbing_branch", "climb") { - handleObstacle( + handleObstacle( player = player, destination = Tile(2473, 3420, 2), anim = "sequences.human_reachforladder", @@ -70,6 +65,7 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { stage = 3 ) } + //Balance Rope onObjectOption("objects.balancing_rope", "walk-on") { handleObstacle( @@ -84,7 +80,8 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { stage = 4 ) } - //Second Tree Branch 1 + + //Climbing Tree (2 objects) onObjectOption("objects.climbing_tree", "climb-down") { handleObstacle( player = player, @@ -97,7 +94,6 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { stage = 5 ) } - //Second Tree Branch 2 onObjectOption("objects.climbing_tree2", "climb-down") { handleObstacle( player = player, @@ -110,13 +106,10 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { stage = 5 ) } - //Second net - onObjectOption("objects.obstical_net3", "climb-over") { - - if (player.tile.x !in 2483..2488) { - return@onObjectOption - } + //Net 2 + onObjectOption("objects.obstical_net3", "climb-over") { + if (player.tile.x !in 2483..2488) return@onObjectOption handleObstacle( player = player, destination = Tile(player.tile.x, 3428, 0), @@ -127,14 +120,15 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { stage = 6 ) } - //Pipes + + //Pipe (2 objects) — EndStage onObjectOption("objects.obstical_pipe3_1", "squeeze-through") { handleObstacle( player = player, destination = Tile(2484, 3437, 0), anim = "sequences.human_pipesqueeze", - duration1 = 5, - duration2 = 250, + duration1 = 30, + duration2 = 210, angle = Direction.NORTH.angle, xp = 10.0, stage = 7, @@ -146,8 +140,8 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { player = player, destination = Tile(2487, 3437, 0), anim = "sequences.human_pipesqueeze", - duration1 = 5, - duration2 = 250, + duration1 = 30, + duration2 = 210, angle = Direction.NORTH.angle, xp = 10.0, stage = 7, @@ -171,22 +165,26 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { endStage: Boolean = false ) { - val doForcedMove = - angle != null && duration1 != null && duration2 != null + val doForcedMove = angle != null && duration1 != null && duration2 != null - if (stage >= 0) { - val current = player.getStage() - if (current + 1 != stage) { - player.setStage(0) - return - } + val current = player.getStage() + + val isNext = current + 1 == stage // normale progress + val isRepeat = current == stage // opnieuw klikken/spammen + + if (isNext) { + player.setStage(stage) + } else if (!isRepeat) { + player.setStage(0) } + player.queue { messageStart?.let { player.filterableMessage(it) } anim?.let { player.animate(it) } + if (doForcedMove) { val movement = ForcedMovement.of( src = player.tile, @@ -200,13 +198,16 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { player.animate(RSCM.NONE) } else if (simpleMove) { + wait(2) player.moveTo(destination) wait(1) player.animate(RSCM.NONE) } + wait(1) if (xp > 0.0) player.addXp(Skills.AGILITY, xp) messageEnd?.let { player.filterableMessage(it) } + handleStage(player, stage, endStage) } } @@ -216,20 +217,25 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { stage: Int, endStage: Boolean ) { - val current = player.getStage() + val cur = player.getStage() if (!endStage && stage >= 0) { - player.setStage(stage) + if (stage == cur + 1) { + player.setStage(stage) + } return } if (endStage) { - player.setStage(MAX_STAGES) - if (player.getStage() >= MAX_STAGES) { + val completed = cur == MAX_STAGES + + if (completed) { player.addXp(Skills.AGILITY, BONUS_XP) - player.setStage(0) player.filterableMessage("You have completed the Gnome Agility Course!") } + + player.setStage(0) // altijd resetten + return } } } From f09ae2215746535ceb7d929eb1ec634e4e6bbd10 Mon Sep 17 00:00:00 2001 From: "J. Govers" <31247938+Eikenb00m@users.noreply.github.com> Date: Mon, 24 Nov 2025 22:42:19 +0100 Subject: [PATCH 6/9] Add Mark of Grace spawn to Gnome Agility Course Introduces a chance to spawn a Mark of Grace at random tiles when completing obstacles in the Gnome Stronghold Agility Course. The chance increases slightly with the player's Agility level. Also moves the GNOME_AGILITY_STAGE attribute key to the course plugin for better encapsulation. --- .../agility/GnomeStrongholdCoursePlugin.kt | 39 +++++++++++++++++-- .../org/alter/game/model/attr/Attributes.kt | 7 +--- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt b/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt index 50454158..61b12433 100644 --- a/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt +++ b/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt @@ -5,7 +5,8 @@ import org.alter.api.ext.filterableMessage import org.alter.game.model.Direction import org.alter.game.model.ForcedMovement import org.alter.game.model.Tile -import org.alter.game.model.attr.GNOME_AGILITY_STAGE +import org.alter.game.model.attr.AttributeKey +import org.alter.game.model.entity.GroundItem import org.alter.game.model.entity.Player import org.alter.game.model.move.moveTo import org.alter.game.pluginnew.PluginEvent @@ -16,7 +17,14 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { private val MAX_STAGES = 7 private val BONUS_XP = 50.0 + private val DROP_CHANCE = 0.15 // 15% kans per obstacle //100% test + private val MARK_SPAWN_TILES = listOf( + Tile(2471, 3422, 1), + Tile(2474, 3418, 2), + Tile(2488, 3421, 2) + ) + val GNOME_AGILITY_STAGE = AttributeKey("GnomeAgilityStage") private fun Player.getStage(): Int = attr[GNOME_AGILITY_STAGE] ?: 0 private fun Player.setStage(v: Int) { attr[GNOME_AGILITY_STAGE] = v } @@ -169,8 +177,8 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { val current = player.getStage() - val isNext = current + 1 == stage // normale progress - val isRepeat = current == stage // opnieuw klikken/spammen + val isNext = current + 1 == stage + val isRepeat = current == stage if (isNext) { player.setStage(stage) @@ -208,6 +216,7 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { if (xp > 0.0) player.addXp(Skills.AGILITY, xp) messageEnd?.let { player.filterableMessage(it) } + maybeSpawnMark(player) handleStage(player, stage, endStage) } } @@ -234,8 +243,30 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { player.filterableMessage("You have completed the Gnome Agility Course!") } - player.setStage(0) // altijd resetten + player.setStage(0) return } } + private fun maybeSpawnMark(player: Player) { + + val agilityLevel = player.getSkills().getBaseLevel(Skills.AGILITY) + val extraChance = agilityLevel / 200.0 // +0.5% per 10 levels + + val rng = Math.random() + val totalChance = DROP_CHANCE + extraChance + + if (rng > totalChance) return + + val gracetile = MARK_SPAWN_TILES.random() + player.world.spawn( + GroundItem( + item = 11849, + amount =1, + tile = gracetile, + owner =player, + )) + + player.filterableMessage("A Mark of Grace appears.") + } + } diff --git a/game-server/src/main/kotlin/org/alter/game/model/attr/Attributes.kt b/game-server/src/main/kotlin/org/alter/game/model/attr/Attributes.kt index c755cd4c..f8351579 100644 --- a/game-server/src/main/kotlin/org/alter/game/model/attr/Attributes.kt +++ b/game-server/src/main/kotlin/org/alter/game/model/attr/Attributes.kt @@ -1,10 +1,12 @@ package org.alter.game.model.attr +import org.alter.game.model.Tile import org.alter.game.model.container.ItemTransaction import org.alter.game.model.entity.* import org.alter.game.model.item.Item import org.alter.game.model.shop.Shop import java.lang.ref.WeakReference +import java.util.UUID /** * A decoupled file that holds AttributeKeys that require read-access from our @@ -260,8 +262,3 @@ val CHANGE_LOGGING = AttributeKey() * Instead of running tp */ val CLIENT_KEY_COMBINATION = AttributeKey() - -/** - * Agility course stages - */ -val GNOME_AGILITY_STAGE = AttributeKey("GnomeAgilityStage") \ No newline at end of file From 15262f20018ec545859621b114108266be140462 Mon Sep 17 00:00:00 2001 From: "J. Govers" <31247938+Eikenb00m@users.noreply.github.com> Date: Mon, 24 Nov 2025 22:53:20 +0100 Subject: [PATCH 7/9] Track and display Gnome Agility lap count Introduces a new attribute to track the number of laps a player completes on the Gnome Stronghold Agility Course. Upon course completion, the player's lap count is incremented and displayed in a message. --- .../skills/agility/GnomeStrongholdCoursePlugin.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt b/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt index 61b12433..4c955c18 100644 --- a/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt +++ b/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt @@ -28,6 +28,11 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { private fun Player.getStage(): Int = attr[GNOME_AGILITY_STAGE] ?: 0 private fun Player.setStage(v: Int) { attr[GNOME_AGILITY_STAGE] = v } + val GNOME_LAPS = AttributeKey("GnomeAgilityLaps") + private fun Player.getLaps(): Int = attr[GNOME_LAPS] ?: 0 + private fun Player.setLaps(v: Int) { attr[GNOME_LAPS] = v } + + override fun init() { //Balance Log @@ -240,11 +245,16 @@ class GnomeStrongholdCoursePlugin : PluginEvent() { if (completed) { player.addXp(Skills.AGILITY, BONUS_XP) - player.filterableMessage("You have completed the Gnome Agility Course!") + + val laps = player.getLaps() + 1 + player.setLaps(laps) + + player.filterableMessage("Your Gnome Stronghold Agility lap count is: $laps.") } player.setStage(0) return + } } private fun maybeSpawnMark(player: Player) { From fa6b8e1cdbe1f8d7a99bca28cf95dd2ac13b92d7 Mon Sep 17 00:00:00 2001 From: Mark7625 <72366279+Mark7625@users.noreply.github.com> Date: Wed, 26 Nov 2025 12:01:04 +0000 Subject: [PATCH 8/9] MarkTestCommand --- .../content/commands/commands/MarksPlugin.kt | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 game-plugins/src/main/kotlin/org/alter/plugins/content/commands/commands/MarksPlugin.kt diff --git a/game-plugins/src/main/kotlin/org/alter/plugins/content/commands/commands/MarksPlugin.kt b/game-plugins/src/main/kotlin/org/alter/plugins/content/commands/commands/MarksPlugin.kt new file mode 100644 index 00000000..f184be12 --- /dev/null +++ b/game-plugins/src/main/kotlin/org/alter/plugins/content/commands/commands/MarksPlugin.kt @@ -0,0 +1,50 @@ +package org.alter.plugins.content.commands.commands + +import com.sun.management.HotSpotDiagnosticMXBean +import org.alter.api.ext.player +import org.alter.game.Server +import org.alter.game.model.Tile +import org.alter.game.model.World +import org.alter.game.model.entity.GroundItem +import org.alter.game.model.move.moveTo +import org.alter.game.plugin.KotlinPlugin +import org.alter.game.plugin.PluginRepository +import org.alter.rscm.RSCM.getRSCM +import java.lang.management.ManagementFactory +import java.nio.file.Paths + +/** + * @author CloudS3c 12/30/2024 + */ +class MarksPlugin( + r: PluginRepository, + world: World, + server: Server +) : KotlinPlugin(r, world, server) { + + + private val MARK_SPAWN_TILES = listOf( + Tile(2471, 3422, 1), + Tile(2474, 3418, 2), + Tile(2488, 3421, 2) + ) + + init { + onCommand("m") { + player.world.spawn( + GroundItem( + item = 11849, + amount =1, + tile = Tile(2474,3418,2), + owner =player, + )) + } + onCommand("m2") { + player.moveTo(Tile(2474,3418,2)) + } + onCommand("m3") { + player.moveTo(Tile(2474,3418,0)) + } + + } +} From 4efdb5f302961358d17f0b78eb8792fd223d3309 Mon Sep 17 00:00:00 2001 From: Devexity <91433688+Devexity-fivem@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:19:51 -0600 Subject: [PATCH 9/9] create Draynor rooftop adds Draynor rooftop agility course --- .../agility/DraynorRooftopCoursePlugin.kt | 255 ++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 content/src/main/kotlin/org/alter/skills/agility/DraynorRooftopCoursePlugin.kt diff --git a/content/src/main/kotlin/org/alter/skills/agility/DraynorRooftopCoursePlugin.kt b/content/src/main/kotlin/org/alter/skills/agility/DraynorRooftopCoursePlugin.kt new file mode 100644 index 00000000..8efb4c38 --- /dev/null +++ b/content/src/main/kotlin/org/alter/skills/agility/DraynorRooftopCoursePlugin.kt @@ -0,0 +1,255 @@ +package org.alter.skills.agility + +import org.alter.api.Skills +import org.alter.api.ext.filterableMessage +import org.alter.game.model.Direction +import org.alter.game.model.ForcedMovement +import org.alter.game.model.Tile +import org.alter.game.model.attr.AttributeKey +import org.alter.game.model.entity.GroundItem +import org.alter.game.model.entity.Player +import org.alter.game.model.move.moveTo +import org.alter.game.pluginnew.PluginEvent +import org.alter.game.pluginnew.event.impl.onObjectOption +import org.alter.rscm.RSCM + +class DraynorRooftopCoursePlugin : PluginEvent() { + + private val MAX_STAGES = 7 + private val BONUS_XP = 120.0 + private val DROP_CHANCE = 0.15 // 15% chance per obstacle + + private val MARK_SPAWN_TILES = listOf( + Tile(3088, 3275, 3), + ) + val DRAYNOR_AGILITY_STAGE = AttributeKey("DraynorAgilityStage") + private fun Player.getStage(): Int = attr[DRAYNOR_AGILITY_STAGE] ?: 0 + private fun Player.setStage(v: Int) { attr[DRAYNOR_AGILITY_STAGE] = v } + + val DRAYNOR_LAPS = AttributeKey("DraynorAgilityLaps") + private fun Player.getLaps(): Int = attr[DRAYNOR_LAPS] ?: 0 + private fun Player.setLaps(v: Int) { attr[DRAYNOR_LAPS] = v } + + + override fun init() { + + //Rough Wall (Climb up) + onObjectOption("objects.rooftops_draynor_wallclimb", "climb") { + handleObstacle( + player = player, + destination = Tile(3103, 3279, 3), + anim = "sequences.human_reachforladder", + simpleMove = true, + xp = 8.0, + messageStart = "You climb up the rough wall...", + messageEnd = "...and reach the top.", + stage = 1 + ) + } + + //Tightrope 1 + onObjectOption("objects.rooftops_draynor_tightrope_1", "cross") { + handleObstacle( + player = player, + destination = Tile(3090, 3277, 3), + anim = "sequences.human_walk_logbalance_loop", + duration1 = 5, + duration2 = 250, + angle = Direction.EAST.angle, + xp = 8.0, + messageStart = "You carefully cross the tightrope.", + stage = 2 + ) + } + + //Tightrope 2 + onObjectOption("objects.rooftops_draynor_tightrope_2", "cross") { + handleObstacle( + player = player, + destination = Tile(3092, 3267, 3), + anim = "sequences.human_walk_logbalance_loop", + duration1 = 5, + duration2 = 250, + angle = Direction.EAST.angle, + xp = 8.0, + messageStart = "You carefully cross the tightrope.", + stage = 3 + ) + } + + //Narrow Wall (Balance) + onObjectOption("objects.rooftops_draynor_wallcrossing", "balance") { + handleObstacle( + player = player, + destination = Tile(3088, 3261, 3), + anim = "sequences.human_walk_logbalance_loop", + duration1 = 5, + duration2 = 250, + angle = Direction.EAST.angle, + xp = 8.0, + messageStart = "You balance across the narrow wall.", + stage = 4 + ) + } + + //Wall (Jump) + onObjectOption("objects.rooftops_draynor_wallscramble", "jump-up") { + handleObstacle( + player = player, + destination = Tile(3089, 3254, 3), + anim = "sequences.agility_shortcut_wall_jump", + simpleMove = true, + xp = 8.0, + messageStart = "You jump across the wall.", + stage = 5 + ) + } + + //Gap (Jump down) + onObjectOption("objects.rooftops_draynor_leapdown", "jump") { + handleObstacle( + player = player, + destination = Tile(3096, 3257, 3), + anim = "sequences.agility_shortcut_wall_jumpdown", + simpleMove = true, + xp = 8.0, + messageStart = "You jump down from the gap.", + messageEnd = "You land safely on the ground.", + stage = 6 + ) + } + + //Crate (Jump) — EndStage + onObjectOption("objects.rooftops_draynor_crate", "climb-down") { + handleObstacle( + player = player, + destination = Tile(3103, 3261, 0), + anim = "sequences.human_jump_hurdle", + simpleMove = true, + xp = 8.0, + messageStart = "You jump onto the crate.", + messageEnd = "You complete the course!", + stage = 7, + endStage = true + ) + } + } + + private fun handleObstacle( + player: Player, + destination: Tile, + simpleMove: Boolean = false, + angle: Int? = null, + duration1: Int? = null, + duration2: Int? = null, + anim: String? = null, + xp: Double = 0.0, + messageStart: String? = null, + messageEnd: String? = null, + stage: Int = -1, + endStage: Boolean = false + ) { + + val doForcedMove = angle != null && duration1 != null && duration2 != null + + val current = player.getStage() + + val isNext = current + 1 == stage + val isRepeat = current == stage + + if (isNext) { + player.setStage(stage) + } else if (!isRepeat) { + player.setStage(0) + } + + + + player.queue { + + messageStart?.let { player.filterableMessage(it) } + anim?.let { player.animate(it) } + + if (doForcedMove) { + val movement = ForcedMovement.of( + src = player.tile, + dst = destination, + clientDuration1 = duration1, + clientDuration2 = duration2, + directionAngle = angle + ) + player.forceMove(this, movement) + wait(1) + player.animate(RSCM.NONE) + } + else if (simpleMove) { + wait(2) + player.moveTo(destination) + wait(1) + player.animate(RSCM.NONE) + } + + wait(1) + if (xp > 0.0) player.addXp(Skills.AGILITY, xp) + messageEnd?.let { player.filterableMessage(it) } + + maybeSpawnMark(player) + handleStage(player, stage, endStage) + } + } + + private fun handleStage( + player: Player, + stage: Int, + endStage: Boolean + ) { + val cur = player.getStage() + + if (!endStage && stage >= 0) { + if (stage == cur + 1) { + player.setStage(stage) + } + return + } + + if (endStage) { + val completed = cur == MAX_STAGES + + if (completed) { + player.addXp(Skills.AGILITY, BONUS_XP) + + val laps = player.getLaps() + 1 + player.setLaps(laps) + + player.filterableMessage("Your Draynor Rooftop Agility lap count is: $laps.") + } + + player.setStage(0) + return + + } + } + private fun maybeSpawnMark(player: Player) { + + val agilityLevel = player.getSkills().getBaseLevel(Skills.AGILITY) + val extraChance = agilityLevel / 200.0 // +0.5% per 10 levels + + val rng = Math.random() + val totalChance = DROP_CHANCE + extraChance + + if (rng > totalChance) return + + val gracetile = MARK_SPAWN_TILES.random() + player.world.spawn( + GroundItem( + item = 11849, + amount =1, + tile = gracetile, + owner =player, + )) + + player.filterableMessage("A Mark of Grace appears.") + } + +} +