Fixed combat movement pathing past SmartPathfinder limit

Adventure bots could enter combat with NPCs near the edge of the SmartPathfinder truncation window, causing combat movement to try attack tiles at distance 50+ and spam pathfinding exceptions.

Skip SMART combat movement destinations that SmartPathfinder cannot attempt, and make the pathfinder cutoff follow max_pathfind_dist instead of a hard-coded 50. Added a regression test for the reported coordinate boundary.
This commit is contained in:
dam 2026-05-01 14:47:29 +03:00
parent 1fe520836b
commit c52de9464d
No known key found for this signature in database
GPG key ID: 4AF4E722399663FB
3 changed files with 34 additions and 13 deletions

View file

@ -14,6 +14,7 @@ import core.game.world.map.Point
import core.game.world.map.RegionManager
import core.game.world.map.path.Path
import core.game.world.map.path.Pathfinder
import core.game.world.map.path.SmartPathfinder
import core.game.world.repository.Repository
import java.util.LinkedHashMap
@ -405,6 +406,11 @@ object CombatMovementIntents {
if (attacker.location == destination.location) {
return null
}
if (destination.pathfinder === Pathfinder.SMART &&
!SmartPathfinder.canAttempt(attacker.location, destination.location)
) {
return null
}
val path = Pathfinder.find(attacker, destination.node, destination.allowPartialPath, destination.pathfinder)
if (!path.reaches(destination.location) && (!destination.allowPartialPath || path.points.isEmpty())) {

View file

@ -71,6 +71,13 @@ internal constructor() : Pathfinder() {
*/
private var foundPath = false
companion object {
fun canAttempt(start: Location, dest: Location): Boolean {
val distance = kotlin.math.floor(Vector.betweenLocs(start, dest).magnitude())
return distance < ServerConstants.MAX_PATHFIND_DISTANCE * 2.0
}
}
/**
* Resets the pathfinder.
*/
@ -101,16 +108,16 @@ internal constructor() : Pathfinder() {
override fun find(start: Location?, moverSize: Int, dest: Location?, sizeX: Int, sizeY: Int, rotation: Int, type: Int, walkingFlag: Int, near: Boolean, clipMaskSupplier: ClipMaskSupplier?): Path {
reset()
assert(start != null && dest != null)
var vec = Vector.betweenLocs(start!!, dest!!)
val startLoc = requireNotNull(start)
var end = requireNotNull(dest)
var vec = Vector.betweenLocs(startLoc, end)
var mag = kotlin.math.floor(vec.magnitude())
var end = dest!!
if (mag > ServerConstants.MAX_PATHFIND_DISTANCE) {
try {
if (mag < 50.0) { //truncate the path if it's realistically long
if (canAttempt(startLoc, end)) { //truncate the path if it's realistically long
vec = vec.normalized() * (ServerConstants.MAX_PATHFIND_DISTANCE - 1)
end = start!!.transform(vec)
} else throw Exception("Pathfinding distance exceeds server max! -> " + mag.toString() + " {" + start + "->" + end + "}")
end = startLoc.transform(vec)
} else throw Exception("Pathfinding distance exceeds server max! -> " + mag.toString() + " {" + startLoc + "->" + end + "}")
} catch (e: Exception) {
val sw = StringWriter()
val pw = PrintWriter(sw)
@ -129,12 +136,12 @@ internal constructor() : Pathfinder() {
cost[x][y] = 99999999
}
}
val z = start!!.z
val location = Location.create(start.regionX - 6 shl 3, start.regionY - 6 shl 3, z)
curX = start.sceneX
curY = start.sceneY
dstX = end!!.getSceneX(start)
dstY = end.getSceneY(start)
val z = startLoc.z
val location = Location.create(startLoc.regionX - 6 shl 3, startLoc.regionY - 6 shl 3, z)
curX = startLoc.sceneX
curY = startLoc.sceneY
dstX = end.getSceneX(startLoc)
dstY = end.getSceneY(startLoc)
var attempts: Int
var readPosition: Int
check(curX, curY, 99, 0)

View file

@ -17,6 +17,7 @@ import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.world.GameWorld
import core.game.world.map.Region
import core.game.world.map.path.SmartPathfinder
import core.net.packet.PacketProcessor
import core.plugin.ClassScanner
import core.plugin.Plugin
@ -26,6 +27,13 @@ import org.rs09.consts.NPCs
class PathfinderTests {
companion object {init {TestUtils.preTestSetup(); GatheringSkillOptionListeners().defineListeners(); WoodcuttingListener().defineListeners() }; val NPC_TEST_LOC = ServerConstants.HOME_LOCATION!!.transform(2, 10, 0)}
@Test fun smartPathfinderShouldRejectDestinationsAtTheTruncationLimit() {
val start = Location.create(3165, 3218, 0)
Assertions.assertTrue(SmartPathfinder.canAttempt(start, Location.create(3203, 3186, 0)))
Assertions.assertFalse(SmartPathfinder.canAttempt(start, Location.create(3203, 3185, 0)))
}
@Test fun getOccupiedTilesShouldReturnCorrectSetOfTilesThatAnObjectOccupiesAtAllRotations() {
//clay fireplace - 13609 - sizex: 1, sizey: 2
val scenery = Scenery(13609, Location.create(50, 50, 0))
@ -257,4 +265,4 @@ class PathfinderTests {
Assertions.assertEquals(true, npc.location.getDistance(ServerConstants.HOME_LOCATION!!) <= 5)
}
}
}
}