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 b6409eb6c..a68f498e6 100644 --- a/Server/src/main/core/game/world/map/path/RsmodPathfinder.kt +++ b/Server/src/main/core/game/world/map/path/RsmodPathfinder.kt @@ -5,24 +5,16 @@ import core.api.utils.Vector import core.game.world.map.Location import core.game.world.map.Point import core.game.world.map.RegionManager -import org.rsmod.game.pathfinder.LinePathFinder import org.rsmod.game.pathfinder.PathFinder -import org.rsmod.game.pathfinder.RayCast import org.rsmod.game.pathfinder.collision.CollisionFlagMap -import org.rsmod.game.pathfinder.reach.ReachStrategy import kotlin.math.floor private const val SEARCH_MAP_SIZE = 128 private const val RING_BUFFER_SIZE = 4096 -class RsmodPathfinder( - private val maxWaypoints: Int = 25 -) : Pathfinder() { +class RsmodPathfinder : Pathfinder() { - private val defaultFinder = ThreadLocal.withInitial { - PathFinder(RegionManager.RSMOD_CLIPPING_FLAGS, SEARCH_MAP_SIZE, RING_BUFFER_SIZE) - } - private val suppliedFinder = ThreadLocal.withInitial { RouteFinderState() } + private val routeFinder = ThreadLocal.withInitial { RouteFinderState() } override fun find( start: Location?, @@ -38,6 +30,9 @@ class RsmodPathfinder( ): Path { val source = requireNotNull(start) val destination = requireNotNull(dest) + val supplier = clipMaskSupplier ?: ClipMaskSupplier { z, x, y -> + RegionManager.getClippingFlag(z, x, y) + } val path = Path() var end = destination val vector = Vector.betweenLocs(source, destination) @@ -52,16 +47,15 @@ class RsmodPathfinder( } } - val shape = routeShape(type, sizeX, sizeY) - val finder = if (clipMaskSupplier == null) { - RegionManager.loadClippingWindow(source, SEARCH_MAP_SIZE) - defaultFinder.get() - } else { - val state = suppliedFinder.get() - state.loadCollisionWindow(source, clipMaskSupplier) - state.finder + val state = routeFinder.get() + state.loadCollisionWindow(source, supplier) + + val shape = when { + type != 0 -> type - 1 + sizeX != 0 && sizeY != 0 -> 10 + else -> -1 } - val route = finder.findPath( + val route = state.finder.findPath( level = source.z, srcX = source.x, srcZ = source.y, @@ -73,8 +67,7 @@ class RsmodPathfinder( objRot = rotation, objShape = shape, moveNear = near, - blockAccessFlags = walkingFlag, - maxWaypoints = maxWaypoints + blockAccessFlags = walkingFlag ) if (route.failed) { @@ -89,82 +82,11 @@ class RsmodPathfinder( return path } - companion object { - @JvmStatic - fun canReach( - srcX: Int, - srcY: Int, - moverSize: Int, - destX: Int, - destY: Int, - destWidth: Int, - destHeight: Int, - rotation: Int, - type: Int, - walkingFlag: Int, - z: Int, - clipMaskSupplier: ClipMaskSupplier? - ): Boolean { - val flags = 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 = 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 - ) - } + private class RouteFinderState { + val flags = CollisionFlagMap() + val finder = PathFinder(flags, SEARCH_MAP_SIZE, RING_BUFFER_SIZE) - @JvmStatic - fun lineOfSight( - start: Location, - dest: Location, - moverSize: Int, - destWidth: Int, - destHeight: Int - ): RayCast { - RegionManager.loadClippingWindow(start, SEARCH_MAP_SIZE) - return LinePathFinder(RegionManager.RSMOD_PROJECTILE_FLAGS).lineOfSight( - level = start.z, - srcX = start.x, - srcZ = start.y, - destX = dest.x, - destZ = dest.y, - srcSize = moverSize, - destWidth = destWidth, - destHeight = destHeight - ) - } - - private fun routeShape(type: Int, sizeX: Int, sizeY: Int): Int = when { - type >= 0 -> type - sizeX != 0 && sizeY != 0 -> 10 - else -> -1 - } - - private fun loadCollisionWindow( - flags: CollisionFlagMap, - start: Location, - supplier: ClipMaskSupplier - ) { + fun loadCollisionWindow(start: Location, supplier: ClipMaskSupplier) { val baseX = start.x - (SEARCH_MAP_SIZE / 2) val baseY = start.y - (SEARCH_MAP_SIZE / 2) for (x in baseX until baseX + SEARCH_MAP_SIZE) { @@ -174,13 +96,4 @@ class RsmodPathfinder( } } } - - private class RouteFinderState { - val flags = CollisionFlagMap() - val finder = PathFinder(flags, SEARCH_MAP_SIZE, RING_BUFFER_SIZE) - - fun loadCollisionWindow(start: Location, supplier: ClipMaskSupplier) { - loadCollisionWindow(flags, start, supplier) - } - } } diff --git a/Server/src/test/kotlin/core/PathfinderTests.kt b/Server/src/test/kotlin/core/PathfinderTests.kt index fb6f31ac6..f3c2732c7 100644 --- a/Server/src/test/kotlin/core/PathfinderTests.kt +++ b/Server/src/test/kotlin/core/PathfinderTests.kt @@ -1,7 +1,6 @@ package core import TestUtils -import content.global.handlers.scenery.BankBoothListener import content.global.skill.gather.GatheringSkillOptionListeners import content.global.skill.gather.woodcutting.WoodcuttingListener import core.api.log @@ -25,18 +24,9 @@ import core.plugin.ClassScanner import core.plugin.Plugin import core.tools.Log import org.rs09.consts.NPCs -import org.rs09.consts.Scenery as SceneryIds class PathfinderTests { - companion object { - init { - TestUtils.preTestSetup() - GatheringSkillOptionListeners().defineListeners() - WoodcuttingListener().defineListeners() - BankBoothListener().defineListeners() - } - val NPC_TEST_LOC = ServerConstants.HOME_LOCATION!!.transform(2, 10, 0) - } + companion object {init {TestUtils.preTestSetup(); GatheringSkillOptionListeners().defineListeners(); WoodcuttingListener().defineListeners() }; val NPC_TEST_LOC = ServerConstants.HOME_LOCATION!!.transform(2, 10, 0)} @Test fun getOccupiedTilesShouldReturnCorrectSetOfTilesThatAnObjectOccupiesAtAllRotations() { //clay fireplace - 13609 - sizex: 1, sizey: 2 @@ -75,150 +65,6 @@ class PathfinderTests { ) } - @Test fun dumbPathfinderShouldUseRsmodRouting() { - val start = Location.create(3200, 3200, 0) - val dest = Location.create(3202, 3200, 0) - val blockedMiddle = ClipMaskSupplier { _, x, y -> - if (x == 3201 && y == 3200) 0x100 else 0 - } - - val path = Pathfinder.DUMB.find(start, 1, dest, 0, 0, 0, -1, 0, false, blockedMiddle) - - Assertions.assertTrue(path.isSuccessful) - Assertions.assertTrue(path.points.isNotEmpty()) - } - - @Test fun projectilePathfinderShouldUseRsmodLineOfSightFlags() { - val start = Location.create(3200, 3200, 0) - val dest = Location.create(3202, 3200, 0) - RegionManager.loadClippingWindow(start, 128) - try { - RegionManager.setRsmodFlag(0, 3201, 3200, true, 0x20000) - - val blocked = Pathfinder.PROJECTILE.find(start, 1, dest, 0, 0, 0, -1, 0, false, null) - - Assertions.assertFalse(blocked.isSuccessful) - } finally { - RegionManager.setRsmodFlag(0, 3201, 3200, true, 0) - } - } - - @Test fun metadataSceneryInteractionShouldTriggerWhenAlreadyAtRsmodApproachTile() { - TestUtils.getMockPlayer("bankBoothApproach").use { p -> - val (booth, approach) = findReachableBankBoothFixture() - p.location = approach - val alreadyAtPath = Pathfinder.find(p, booth) - Assertions.assertTrue(alreadyAtPath.isSuccessful) - Assertions.assertFalse(alreadyAtPath.isMoveNear) - - Assertions.assertTrue(InteractionListeners.run(booth.id, IntType.SCENERY, "bank", p, booth)) - TestUtils.advanceTicks(10, false) - - Assertions.assertTrue(p.bank.isOpen) - } - } - - @Test fun metadataSceneryCollectShouldTriggerWhenAlreadyAtRsmodApproachTile() { - TestUtils.getMockPlayer("bankBoothCollectApproach").use { p -> - val (booth, approach) = findReachableBankBoothFixture() - p.location = approach - var collected = false - InteractionListeners.addMetadata( - booth.id, - IntType.SCENERY, - arrayOf("collect"), - InteractionListener.InteractionMetadata({ _, _, _ -> - collected = true - true - }, 1, false) - ) - - try { - Assertions.assertTrue(InteractionListeners.run(booth.id, IntType.SCENERY, "collect", p, booth)) - TestUtils.advanceTicks(10, false) - - Assertions.assertTrue(collected) - } finally { - BankBoothListener().defineListeners() - } - } - } - - @Test fun directObjectMovementPulseShouldTriggerWhenAlreadyAtRsmodApproachTile() { - TestUtils.getMockPlayer("objectPulseApproach").use { p -> - val tree = RegionManager.getObject(0, 2720, 3475, 1307) - ?: throw AssertionError("Expected test tree object.") - val approach = findReachableApproachTile(tree) - var pulsed = false - p.location = approach - - GameWorld.Pulser.submit(object : MovementPulse(p, tree) { - override fun pulse(): Boolean { - pulsed = true - return true - } - }) - TestUtils.advanceTicks(3, false) - - Assertions.assertTrue(pulsed) - } - } - - @Test fun entityMovementPulseShouldTriggerWhenDestinationOverrideIsAlreadyReached() { - TestUtils.getMockPlayer("bankerOverrideApproach").use { p -> - val npc = NPC.create(0, NPC_TEST_LOC) - npc.isNeverWalks = true - npc.init() - p.location = ServerConstants.HOME_LOCATION - var pulsed = false - - GameWorld.Pulser.submit(object : MovementPulse(p, npc, DestinationFlag.ENTITY, { _, _ -> p.location }) { - override fun pulse(): Boolean { - pulsed = true - return true - } - }) - TestUtils.advanceTicks(3, false) - - Assertions.assertTrue(pulsed) - } - } - - private fun findReachableBankBoothFixture(): Pair { - val base = ServerConstants.HOME_LOCATION!!.transform(8, 8, 0) - for (rotation in 0..3) { - val booth = Scenery(SceneryIds.BANK_BOOTH_2213, base, 10, rotation) - runCatching { findReachableApproachTile(booth) } - .getOrNull() - ?.let { return booth to it } - } - throw AssertionError("Could not find a reachable synthetic bank booth fixture.") - } - - private fun findReachableApproachTile(scenery: Scenery): Location { - for (radius in 1..8) { - for (x in scenery.location.x - radius..scenery.location.x + radius) { - for (y in scenery.location.y - radius..scenery.location.y + radius) { - val start = Location.create(x, y, scenery.location.z) - if (!RegionManager.isTeleportPermitted(start)) { - continue - } - val path = Pathfinder.find(start, scenery) - if (!path.isSuccessful || path.isMoveNear) { - continue - } - val point = path.points.lastOrNull() - val approach = Location.create(point?.x ?: start.x, point?.y ?: start.y, start.z) - val check = Pathfinder.find(approach, scenery) - if (check.isSuccessful && !check.isMoveNear) { - return approach - } - } - } - } - throw AssertionError("Could not find a reachable approach tile for $scenery.") - } - @Test fun movementPulseShouldStopEarlyIfNextToATileOccupiedByTargetObject() { val start = Location.create(2731, 3481) val dest = RegionManager.getObject(0, 2720, 3475, 1307)