Climbing rope fixes

Same-tile interaction still fails
This commit is contained in:
dam 2026-04-01 21:56:18 +03:00
parent e258be1898
commit 7c8aa93ce0
No known key found for this signature in database
GPG key ID: 4AF4E722399663FB
2 changed files with 96 additions and 35 deletions

View file

@ -16,6 +16,7 @@ import core.game.node.scenery.Scenery
import core.game.node.scenery.SceneryBuilder
import core.game.world.map.Location
import core.game.world.map.RegionManager
import core.game.world.update.flag.chunk.ObjectUpdateFlag
import core.game.world.map.zone.ZoneBorders
import core.tools.ticksPerMinute
import org.rs09.consts.Items
@ -61,6 +62,11 @@ enum class BoulderState {
COLLAPSED, WEAKENED, CLEARED
}
data class ActiveClimbingRope(
val rope: Scenery,
val originalWall: Scenery
)
/**
* Manages the state of a Castle Wars game including scores, flags, and game lifecycle.
* This is a singleton that tracks the current game state.
@ -91,6 +97,8 @@ object CastleWarsGameState {
var zamorakDroppedFlagScenery: Scenery? = null
private set
private val activeClimbingRopes: MutableMap<Location, ActiveClimbingRope> = mutableMapOf()
// Tick when each flag was dropped (for auto-return timer)
private var saradominFlagDroppedTick: Int = 0
private var zamorakFlagDroppedTick: Int = 0
@ -263,22 +271,47 @@ object CastleWarsGameState {
resetBattlefield()
}
fun registerClimbingRope(rope: Scenery, originalWall: Scenery) {
val key = Location.create(rope.location)
val chunk = RegionManager.getRegionChunk(rope.location)
rope.isActive = true
rope.isRenderable = true
activeClimbingRopes[key] = ActiveClimbingRope(rope, Scenery(originalWall))
chunk.objects[rope.location.chunkOffsetX][rope.location.chunkOffsetY] = rope
}
fun getClimbingRope(location: Location): Scenery? {
return activeClimbingRopes[Location.create(location)]?.rope?.takeIf { it.isActive }
}
fun removeClimbingRope(location: Location, sendUpdate: Boolean = true): Boolean {
val activeRope = activeClimbingRopes.remove(Location.create(location)) ?: return false
val chunk = RegionManager.getRegionChunk(activeRope.rope.location)
activeRope.rope.isActive = false
chunk.objects[activeRope.rope.location.chunkOffsetX][activeRope.rope.location.chunkOffsetY] =
activeRope.originalWall
if (sendUpdate) {
chunk.flag(ObjectUpdateFlag(activeRope.rope, true))
}
return true
}
fun clearClimbingRopes(sendUpdate: Boolean = true) {
val ropeLocations = activeClimbingRopes.keys.map { Location.create(it) }
for (location in ropeLocations) {
removeClimbingRope(location, sendUpdate)
}
}
/**
* Reset the battlefield by removing climbing ropes, restoring modified battlements,
* and closing any open doors. Called at the end of each match.
*/
private fun resetBattlefield() {
for (x in 2368..2431) {
for (y in 3072..3135) {
for (z in 0..3) {
val loc = Location.create(x, y, z)
val obj = RegionManager.getObject(loc)
if (obj != null && obj.id == CastleWars.cwCastleClimbingRope) {
SceneryBuilder.remove(obj)
}
}
}
}
clearClimbingRopes()
// Reset modified battlements back to original state
// 36313 -> 4446, 36314 -> 4447
for (x in 2368..2431) {

View file

@ -38,6 +38,11 @@ data class BarrierData(
val insideTile: Location
)
data class ClimbingRopeTraversal(
val topOfWallTile: Location,
val baseOfWallTile: Location
)
private val barrierCoordinates: Map<Int, List<BarrierData>> = mapOf(
CastleWars.zamorakEnergyBarrier to listOf(
BarrierData(
@ -111,19 +116,49 @@ internal fun getBarrierPassTiles(playerLoc: Location, barrierData: BarrierData):
}
}
internal fun getClimbingRopeTraversal(rope: Scenery): ClimbingRopeTraversal {
val ropeLoc = rope.location
val wallDirection = rope.direction.opposite
val topOfWallTile = Location(wallDirection.stepY + ropeLoc.x, -wallDirection.stepX + ropeLoc.y, ropeLoc.z)
val baseOfWallTile = Location(-wallDirection.stepY + ropeLoc.x, wallDirection.stepX + ropeLoc.y, ropeLoc.z)
return ClimbingRopeTraversal(topOfWallTile, baseOfWallTile)
}
internal fun isOnClimbingRopeBaseSide(playerLoc: Location, rope: Scenery): Boolean {
val traversal = getClimbingRopeTraversal(rope)
if (playerLoc == traversal.baseOfWallTile) {
return true
}
val ropeLoc = rope.location
val baseVectorX = traversal.baseOfWallTile.x - ropeLoc.x
val baseVectorY = traversal.baseOfWallTile.y - ropeLoc.y
val playerVectorX = playerLoc.x - ropeLoc.x
val playerVectorY = playerLoc.y - ropeLoc.y
return playerVectorX * baseVectorX + playerVectorY * baseVectorY > 0
}
internal fun getClimbingRopeInteractionDestination(playerLoc: Location, rope: Scenery): Location {
val traversal = getClimbingRopeTraversal(rope)
return if (isOnClimbingRopeBaseSide(playerLoc, rope)) {
traversal.baseOfWallTile
} else {
traversal.topOfWallTile
}
}
internal fun canClimbUpClimbingRope(playerLoc: Location, rope: Scenery): Boolean {
return playerLoc == getClimbingRopeTraversal(rope).baseOfWallTile
}
class CastleWarsListeners : InteractionListener {
private val pickaxeIds = intArrayOf(1265, 1267, 1269, 1271, 1273, 1275)
override fun defineDestinationOverrides() {
setDest(IntType.SCENERY, intArrayOf(CastleWars.cwCastleClimbingRope), "climb") { entity, node ->
val rope = node.asScenery()
val playerLoc = entity.location
val ropeLoc = rope.location
if (playerLoc.getDistance(ropeLoc) <= 2.0) {
return@setDest playerLoc
}
return@setDest ropeLoc
return@setDest getClimbingRopeInteractionDestination(entity.location, node.asScenery())
}
setDest(
@ -242,17 +277,20 @@ class CastleWarsListeners : InteractionListener {
CastleWars.cwClimbingRope,
*CastleWars.cwCastleBattlementsMap.keys.toIntArray()
) { player, rope, wall ->
if (CastleWarsGameState.getClimbingRope(wall.location) != null) {
sendDialogue(player, "There is already a rope tied to the battlements.")
return@onUseWith true
}
removeItem(player, rope)
val wallScenery = wall.asScenery()
val ropeWall = wallScenery.transform(CastleWars.cwCastleBattlementsMap.getValue(wall.id))
SceneryBuilder.replace(wallScenery, ropeWall, CastleWars.ropeAliveTicks, false)
val hangingRope = Scenery(CastleWars.cwCastleClimbingRope, wall.location, 4, wall.direction.toInteger())
CastleWarsGameState.registerClimbingRope(hangingRope, wallScenery)
RegionManager.getRegionChunk(hangingRope.location).flag(ObjectUpdateFlag(hangingRope, false))
hangingRope.isActive = true
GameWorld.Pulser.submit(object : Pulse(CastleWars.ropeAliveTicks) {
override fun pulse(): Boolean {
RegionManager.getRegionChunk(hangingRope.location).flag(ObjectUpdateFlag(hangingRope, true))
hangingRope.isActive = false
CastleWarsGameState.removeClimbingRope(hangingRope.location)
return true
}
})
@ -260,21 +298,11 @@ class CastleWarsListeners : InteractionListener {
}
on(CastleWars.cwCastleClimbingRope, SCENERY, "climb") { player, rope ->
val ropeLoc = rope.asScenery().location
val dir = rope.asScenery().direction.opposite
val ropeScenery = rope.asScenery()
val traversal = getClimbingRopeTraversal(ropeScenery)
val topOfWallLoc = Location(dir.stepY + ropeLoc.x, -dir.stepX + ropeLoc.y, ropeLoc.z)
val baseOfWallLoc = Location(-dir.stepY + ropeLoc.x, dir.stepX + ropeLoc.y, ropeLoc.z)
val distToTop = player.location.getDistance(topOfWallLoc)
val distToBase = player.location.getDistance(baseOfWallLoc)
val onRopeTile =
player.location.x == ropeLoc.x && player.location.y == ropeLoc.y && player.location.z == ropeLoc.z
if (onRopeTile || distToBase <= distToTop) {
teleport(player, topOfWallLoc, TeleportManager.TeleportType.INSTANT)
if (canClimbUpClimbingRope(player.location, ropeScenery)) {
teleport(player, traversal.topOfWallTile, TeleportManager.TeleportType.INSTANT)
} else {
sendMessage(player, "You can't climb down the rope.")
}