mirror of
https://gitlab.com/2009scape/2009scape.git
synced 2026-08-28 05:45:10 -06:00
RSMod routefinder
This commit is contained in:
parent
0c196aa9e3
commit
e85f3a6812
2 changed files with 19 additions and 260 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Scenery, Location> {
|
||||
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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue