From 8274f43f458a46631fd64880a2cdfd4f39283278 Mon Sep 17 00:00:00 2001 From: dam <27978131-real_damighty@users.noreply.gitlab.com> Date: Sat, 13 Jun 2026 04:43:29 +0300 Subject: [PATCH] Major performance fix --- .../entity/combat/CombatMovementIntents.kt | 14 ++-- .../game/world/map/path/RsmodPathfinder.kt | 83 ++++++++++++++----- 2 files changed, 71 insertions(+), 26 deletions(-) diff --git a/Server/src/main/core/game/node/entity/combat/CombatMovementIntents.kt b/Server/src/main/core/game/node/entity/combat/CombatMovementIntents.kt index f50c1a507..72aff9da0 100644 --- a/Server/src/main/core/game/node/entity/combat/CombatMovementIntents.kt +++ b/Server/src/main/core/game/node/entity/combat/CombatMovementIntents.kt @@ -282,7 +282,10 @@ object CombatMovementIntents { reserveOccupiedTiles(reservedTiles, attacker, attacker.location) return } - if (canAttackFrom(attacker, target, attacker.location, targetLocation, trace)) { + if (projectedTargetLocation != targetLocation && canAttackFrom( + attacker, target, attacker.location, targetLocation, trace + ) + ) { stopWalk(attacker) face(attacker, target) projectedLocations[attacker] = attacker.location @@ -309,8 +312,6 @@ object CombatMovementIntents { } } - val candidates = movementDestinationsFor(attacker, target, targetLocation, pathfinder, trace) - val standingOnCandidate = candidates.any { it.location == attacker.location } val projectedAttackerLocation = CombatMovementPlanner.predictedMovementLocation(attacker) ?: attacker.location if (projectedAttackerLocation != attacker.location && canAttackFrom( attacker, target, projectedAttackerLocation, targetLocation, trace @@ -322,6 +323,9 @@ object CombatMovementIntents { return } + val candidates = movementDestinationsFor(attacker, target, targetLocation, pathfinder, trace) + val standingOnCandidate = candidates.any { it.location == attacker.location } + val queueStationaryContinuation = attacker.properties.combatPulse.style == CombatStyle.MELEE && !CombatMovementPlanner.hasMovementStepThisTick( target @@ -516,10 +520,10 @@ object CombatMovementIntents { for (x in minX..maxX) { for (y in minY..maxY) { val tile = Location.create(x, y, targetLocation.z) - if (!RegionManager.isTeleportPermitted(tile)) { + if (distanceSquaredToClosestOccupiedTile(target, targetLocation, tile) > range * range) { continue } - if (distanceSquaredToClosestOccupiedTile(target, targetLocation, tile) <= range * range) { + if (RegionManager.isTeleportPermitted(tile)) { tiles.add(tile) } } diff --git a/Server/src/main/core/game/world/map/path/RsmodPathfinder.kt b/Server/src/main/core/game/world/map/path/RsmodPathfinder.kt index 60506890a..5df98f1a9 100644 --- a/Server/src/main/core/game/world/map/path/RsmodPathfinder.kt +++ b/Server/src/main/core/game/world/map/path/RsmodPathfinder.kt @@ -119,30 +119,59 @@ class RsmodPathfinder( z: Int, clipMaskSupplier: ClipMaskSupplier? ): Boolean { - val flags = if (clipMaskSupplier == null) { + if (clipMaskSupplier == null) { RegionManager.loadClippingWindow(Location.create(srcX, srcY, z), SEARCH_MAP_SIZE) - RegionManager.RSMOD_CLIPPING_FLAGS - } else { - CollisionFlagMap().also { - loadCollisionWindow( - flags = it, start = Location.create(srcX, srcY, z), supplier = clipMaskSupplier - ) + return ReachStrategy.reached( + flags = RegionManager.RSMOD_CLIPPING_FLAGS, + level = z, + srcX = srcX, + srcZ = srcY, + destX = destX, + destZ = destY, + destWidth = if (destWidth == 0) 1 else destWidth, + destHeight = if (destHeight == 0) 1 else destHeight, + srcSize = moverSize, + objRot = rotation, + objShape = routeShape(type, destWidth, destHeight), + blockAccessFlags = walkingFlag + ) + } + // Reach checks only read tiles within the source/destination rectangles and + // their cross-axis combinations, so loading just their padded bounding box + // into a reused map produces the same reads as a freshly allocated full map. + val flags = suppliedReachFlags.get() + val destSize = maxOf(if (destWidth == 0) 1 else destWidth, if (destHeight == 0) 1 else destHeight) + val minX = maxOf(0, minOf(srcX, destX) - 1) + val minY = maxOf(0, minOf(srcY, destY) - 1) + val maxX = maxOf(srcX + moverSize, destX + destSize) + 1 + val maxY = maxOf(srcY + moverSize, destY + destSize) + 1 + try { + for (x in minX..maxX) { + for (y in minY..maxY) { + flags[x, y, z] = clipMaskSupplier.getClippingFlag(z, x, y) + } + } + return ReachStrategy.reached( + flags = flags, + level = z, + srcX = srcX, + srcZ = srcY, + destX = destX, + destZ = destY, + destWidth = if (destWidth == 0) 1 else destWidth, + destHeight = if (destHeight == 0) 1 else destHeight, + srcSize = moverSize, + objRot = rotation, + objShape = routeShape(type, destWidth, destHeight), + blockAccessFlags = walkingFlag + ) + } finally { + for (zoneX in (minX shr 3)..(maxX shr 3)) { + for (zoneY in (minY shr 3)..(maxY shr 3)) { + flags.deallocateIfPresent(zoneX shl 3, zoneY shl 3, z) + } } } - return ReachStrategy.reached( - flags = flags, - level = z, - srcX = srcX, - srcZ = srcY, - destX = destX, - destZ = destY, - destWidth = if (destWidth == 0) 1 else destWidth, - destHeight = if (destHeight == 0) 1 else destHeight, - srcSize = moverSize, - objRot = rotation, - objShape = routeShape(type, destWidth, destHeight), - blockAccessFlags = walkingFlag - ) } @JvmStatic @@ -222,6 +251,16 @@ class RsmodPathfinder( val source = sourceLocation.transform(sourceX, sourceY, 0) for (targetX in 0 until targetSize) { for (targetY in 0 until targetSize) { + if (maxRaySteps == 1) { + // A ray between distinct tiles emits a coordinate per axis + // step, so tiles further than one orthogonal step apart can + // never satisfy a single-step ray. + val manhattan = + kotlin.math.abs(source.x - (targetLocation.x + targetX)) + kotlin.math.abs(source.y - (targetLocation.y + targetY)) + if (manhattan > 1) { + continue + } + } val destination = targetLocation.transform(targetX, targetY, 0) val rayCast = lineOfSightLoaded(source, destination, 1, 1, 1) if (rayCast.success && rayCast.coordinates.size <= maxRaySteps) { @@ -259,6 +298,8 @@ class RsmodPathfinder( else -> -1 } + private val suppliedReachFlags = ThreadLocal.withInitial { CollisionFlagMap() } + private val projectileLineValidator = ThreadLocal.withInitial { LineValidator(RegionManager.RSMOD_PROJECTILE_FLAGS) }