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.") + } + +} + 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..4c955c18 --- /dev/null +++ b/content/src/main/kotlin/org/alter/skills/agility/GnomeStrongholdCoursePlugin.kt @@ -0,0 +1,282 @@ +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 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 } + + 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 + onObjectOption("objects.gnome_log_balance1", "walk-across") { + handleObstacle( + player = player, + destination = Tile(2474, 3429, 0), + anim = "sequences.human_walk_logbalance_loop", + 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.", + stage = 1 + ) + } + + //Net 1 + onObjectOption("objects.obstical_net2", "climb-over") { + if (player.tile.x !in 2471..2476) return@onObjectOption + handleObstacle( + player = player, + destination = Tile(player.tile.x, 3424, 1), + anim = "sequences.human_reachforladder", + simpleMove = true, + xp = 10.0, + messageStart = "You climb up the netting...", + stage = 2 + ) + } + + //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 + ) + } + + //Climbing Tree (2 objects) + 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 + ) + } + 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 + ) + } + + //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), + anim = "sequences.human_reachforladder", + simpleMove = true, + xp = 10.0, + messageStart = "You climb up the netting...", + stage = 6 + ) + } + + //Pipe (2 objects) — EndStage + onObjectOption("objects.obstical_pipe3_1", "squeeze-through") { + handleObstacle( + player = player, + destination = Tile(2484, 3437, 0), + anim = "sequences.human_pipesqueeze", + duration1 = 30, + duration2 = 210, + 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 = 30, + duration2 = 210, + angle = Direction.NORTH.angle, + xp = 10.0, + 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 Gnome Stronghold 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.") + } + +} 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)) + } + + } +} 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..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