From 0c196aa9e3cfd76b6f9d8238c3e9e18a9d3a264c Mon Sep 17 00:00:00 2001 From: dam <27978131-real_damighty@users.noreply.gitlab.com> Date: Tue, 5 May 2026 21:25:56 +0300 Subject: [PATCH] RSMod pathfinder port --- Server/pom.xml | 6 + .../core/game/interaction/MovementPulse.java | 6 +- .../core/game/interaction/ScriptProcessor.kt | 11 +- .../core/game/node/entity/npc/NPCBehavior.kt | 9 +- .../game/system/config/ServerConfigParser.kt | 1 - .../src/main/core/game/world/GameSettings.kt | 3 - .../main/core/game/world/map/RegionManager.kt | 63 +- .../game/world/map/build/RegionFlags.java | 9 +- .../game/world/map/path/DumbPathfinder.java | 427 ------------- .../core/game/world/map/path/Pathfinder.java | 534 +--------------- .../world/map/path/ProjectilePathfinder.java | 200 ------ .../game/world/map/path/RsmodPathfinder.kt | 186 ++++++ .../map/path/RsmodProjectilePathfinder.kt | 39 ++ .../game/world/map/path/SmartPathfinder.kt | 598 ------------------ .../worldevents/holiday/easter/EasterEvent.kt | 6 +- .../src/test/kotlin/core/PathfinderTests.kt | 180 +++++- 16 files changed, 502 insertions(+), 1776 deletions(-) delete mode 100644 Server/src/main/core/game/world/map/path/DumbPathfinder.java delete mode 100644 Server/src/main/core/game/world/map/path/ProjectilePathfinder.java create mode 100644 Server/src/main/core/game/world/map/path/RsmodPathfinder.kt create mode 100644 Server/src/main/core/game/world/map/path/RsmodProjectilePathfinder.kt delete mode 100644 Server/src/main/core/game/world/map/path/SmartPathfinder.kt diff --git a/Server/pom.xml b/Server/pom.xml index 5c93c44d2..71fad5580 100644 --- a/Server/pom.xml +++ b/Server/pom.xml @@ -63,6 +63,12 @@ [1.4.0,) compile + + org.rsmod + rsmod-pathfinder + 4.2.1 + compile + mysql mysql-connector-java diff --git a/Server/src/main/core/game/interaction/MovementPulse.java b/Server/src/main/core/game/interaction/MovementPulse.java index b378a8a1f..220e2d5dc 100644 --- a/Server/src/main/core/game/interaction/MovementPulse.java +++ b/Server/src/main/core/game/interaction/MovementPulse.java @@ -256,7 +256,7 @@ public abstract class MovementPulse extends Pulse { dl.getX(), dl.getY(), target.size(), target.size(), 0, // walkFlag - "can interact from any unblocked direction" ml.getZ(), - RegionManager::getClippingFlag + null ); } } @@ -313,7 +313,7 @@ public abstract class MovementPulse extends Pulse { dl.getX(), dl.getY(), target.size(), target.size(), 0, // walkFlag - "can interact from any unblocked direction" ml.getZ(), - RegionManager::getClippingFlag + null ); } @@ -435,6 +435,8 @@ public abstract class MovementPulse extends Pulse { if (i == size - 1 && interactLocation == null) interactLocation = Location.create(point.getX(), point.getY(), mover.getLocation().getZ()); } + } else if (interactLocation == null && path.isSuccessful() && !path.isMoveNear()) { + interactLocation = mover.getLocation(); } previousLoc = loc; } diff --git a/Server/src/main/core/game/interaction/ScriptProcessor.kt b/Server/src/main/core/game/interaction/ScriptProcessor.kt index 2c638f35f..8c09165a1 100644 --- a/Server/src/main/core/game/interaction/ScriptProcessor.kt +++ b/Server/src/main/core/game/interaction/ScriptProcessor.kt @@ -253,15 +253,14 @@ class ScriptProcessor(val entity: Entity) { is Scenery -> { val basicPath = Pathfinder.find(entity, interactTarget) val path = basicPath.points.lastOrNull() - if (basicPath.isMoveNear) { - target.location - return - } - if (path == null) { + if (!basicPath.isSuccessful) { clearScripts(entity) return } - Location.create(path.x, path.y, entity.location.z) + if (basicPath.isMoveNear) { + return + } + path?.let { Location.create(it.x, it.y, entity.location.z) } ?: entity.location } is GroundItem -> DestinationFlag.ITEM.getDestination(entity, interactTarget) else -> target.location diff --git a/Server/src/main/core/game/node/entity/npc/NPCBehavior.kt b/Server/src/main/core/game/node/entity/npc/NPCBehavior.kt index e4fa6676b..cc54b0ab0 100644 --- a/Server/src/main/core/game/node/entity/npc/NPCBehavior.kt +++ b/Server/src/main/core/game/node/entity/npc/NPCBehavior.kt @@ -3,7 +3,6 @@ package core.game.node.entity.npc import core.game.node.item.Item import core.api.ContentInterface import core.game.node.entity.Entity -import core.game.world.map.RegionManager import core.game.node.entity.player.Player import core.game.node.entity.combat.BattleState import core.game.node.entity.combat.CombatStyle @@ -24,12 +23,6 @@ open class NPCBehavior(vararg val ids: Int = intArrayOf()) : ContentInterface { } } - object StandardClipMaskSupplier : ClipMaskSupplier { - override fun getClippingFlag (z: Int, x: Int, y: Int) : Int { - return RegionManager.getClippingFlag(z,x,y) - } - } - /** * Called every tick, before the base NPC tick() method. * @param self the NPC instance this behavior belongs to @@ -144,7 +137,7 @@ open class NPCBehavior(vararg val ids: Int = intArrayOf()) : ContentInterface { * Called by pathfinding code to determine the clipping mask supplier this NPC should use. You can use this to ignore water, etc. */ open fun getClippingSupplier(self: NPC) : ClipMaskSupplier? { - return StandardClipMaskSupplier + return null } /** diff --git a/Server/src/main/core/game/system/config/ServerConfigParser.kt b/Server/src/main/core/game/system/config/ServerConfigParser.kt index 58e3e6ebf..27efac098 100644 --- a/Server/src/main/core/game/system/config/ServerConfigParser.kt +++ b/Server/src/main/core/game/system/config/ServerConfigParser.kt @@ -88,7 +88,6 @@ object ServerConfigParser { wild_pvp_enabled = data.getBoolean("world.wild_pvp_enabled"), jad_practice_enabled = data.getBoolean("world.jad_practice_enabled"), ge_announcement_limit = data.getLong("world.ge_announcement_limit", 500L).toInt(), - smartpathfinder_bfs = data.getBoolean("world.smartpathfinder_bfs", false), enable_castle_wars = data.getBoolean("world.enable_castle_wars", false), message_model = data.getString("world.motw_identifier").toInt(), message_string = data.getString("world.motw_text").replace("@name", ServerConstants.SERVER_NAME) diff --git a/Server/src/main/core/game/world/GameSettings.kt b/Server/src/main/core/game/world/GameSettings.kt index e113dbbe3..806977653 100644 --- a/Server/src/main/core/game/world/GameSettings.kt +++ b/Server/src/main/core/game/world/GameSettings.kt @@ -81,7 +81,6 @@ class GameSettings var wild_pvp_enabled: Boolean, var jad_practice_enabled: Boolean, var ge_announcement_limit: Int, - var smartpathfinder_bfs: Boolean, var enable_castle_wars: Boolean, /**"Lobby" interface @@ -135,7 +134,6 @@ class GameSettings val wild_pvp_enabled = if(data.containsKey("wild_pvp_enabled")) data["wild_pvp_enabled"] as Boolean else true val jad_practice_enabled = if(data.containsKey("jad_practice_enabled")) data["jad_practice_enabled"] as Boolean else true val ge_announcement_limit = data["ge_announcement_limit"].toString().toInt() - val smartpathfinder_bfs = if(data.containsKey("smartpathfinder_bfs")) data["smartpathfinder_bfs"] as Boolean else false val enable_castle_wars = if(data.containsKey("enable_castle_wars")) data["enable_castle_wars"] as Boolean else false val allow_token_purchase = data["allow_token_purchase"] as Boolean val message_of_the_week_identifier = data["message_of_the_week_identifier"].toString().toInt() @@ -165,7 +163,6 @@ class GameSettings wild_pvp_enabled, jad_practice_enabled, ge_announcement_limit, - smartpathfinder_bfs, enable_castle_wars, message_of_the_week_identifier, message_of_the_week_text diff --git a/Server/src/main/core/game/world/map/RegionManager.kt b/Server/src/main/core/game/world/map/RegionManager.kt index 9fb7b848b..d70f5c0d6 100644 --- a/Server/src/main/core/game/world/map/RegionManager.kt +++ b/Server/src/main/core/game/world/map/RegionManager.kt @@ -10,6 +10,7 @@ import core.game.world.map.zone.ZoneBorders import core.tools.Log import core.tools.RandomFunction import core.tools.SystemLogger +import org.rsmod.game.pathfinder.collision.CollisionFlagMap import java.util.* import java.util.concurrent.TimeUnit import java.util.concurrent.locks.ReentrantLock @@ -24,8 +25,11 @@ object RegionManager { * The region cache mapping. */ private val REGION_CACHE: MutableMap = HashMap() + private val RSMOD_PROJECTILE_REGIONS = HashSet() @JvmStatic val CLIPPING_FLAGS = HashMap>() @JvmStatic val PROJECTILE_FLAGS = HashMap>() + @JvmStatic val RSMOD_CLIPPING_FLAGS = CollisionFlagMap() + @JvmStatic val RSMOD_PROJECTILE_FLAGS = CollisionFlagMap() public val LOCK = ReentrantLock() @@ -125,16 +129,69 @@ object RegionManager { @JvmStatic fun getFlags(regionId: Int, projectile: Boolean) : Array { - return if (projectile) - PROJECTILE_FLAGS.getOrPut (regionId) {Array(16384){0}} - else + return if (projectile) { + initialiseRsmodProjectileRegion(regionId) + PROJECTILE_FLAGS.getOrPut(regionId) { Array(16384) { 0 } } + } else { CLIPPING_FLAGS.getOrPut (regionId) {Array(16384){-1}} + } } @JvmStatic fun resetFlags(regionId: Int) { PROJECTILE_FLAGS.put (regionId, Array(16384){0}) CLIPPING_FLAGS.put (regionId, Array(16384){-1}) + resetRsmodFlags(regionId) + } + + @JvmStatic + fun setRsmodFlag(z: Int, x: Int, y: Int, projectile: Boolean, flag: Int) { + val flags = if (projectile) RSMOD_PROJECTILE_FLAGS else RSMOD_CLIPPING_FLAGS + flags[x, y, z] = flag + } + + @JvmStatic + fun loadClippingWindow(center: Location, size: Int) { + val minX = center.x - (size / 2) + val minY = center.y - (size / 2) + val maxX = minX + size - 1 + val maxY = minY + size - 1 + for (regionX in (minX shr 6)..(maxX shr 6)) { + for (regionY in (minY shr 6)..(maxY shr 6)) { + val regionId = (regionX shl 8) or regionY + Region.load(forId(regionId)) + initialiseRsmodProjectileRegion(regionId) + } + } + } + + private fun resetRsmodFlags(regionId: Int) { + val baseX = (regionId shr 8) shl 6 + val baseY = (regionId and 0xFF) shl 6 + for (z in 0 until 4) { + for (x in baseX until baseX + 64 step 8) { + for (y in baseY until baseY + 64 step 8) { + RSMOD_CLIPPING_FLAGS.deallocateIfPresent(x, y, z) + val projectileFlags = RSMOD_PROJECTILE_FLAGS.allocateIfAbsent(x, y, z) + projectileFlags.fill(0) + } + } + } + } + + private fun initialiseRsmodProjectileRegion(regionId: Int) { + if (!RSMOD_PROJECTILE_REGIONS.add(regionId)) { + return + } + val baseX = (regionId shr 8) shl 6 + val baseY = (regionId and 0xFF) shl 6 + for (z in 0 until 4) { + for (x in baseX until baseX + 64 step 8) { + for (y in baseY until baseY + 64 step 8) { + RSMOD_PROJECTILE_FLAGS.allocateIfAbsent(x, y, z).fill(0) + } + } + } } /** diff --git a/Server/src/main/core/game/world/map/build/RegionFlags.java b/Server/src/main/core/game/world/map/build/RegionFlags.java index 177da33c9..671935213 100644 --- a/Server/src/main/core/game/world/map/build/RegionFlags.java +++ b/Server/src/main/core/game/world/map/build/RegionFlags.java @@ -179,7 +179,9 @@ public final class RegionFlags { public void addFlag(int x, int y, int clipdata) { int current = getFlag(x, y); Pair indices = getFlagIndex(x, y); - RegionManager.getFlags(indices.getFirst(), projectile)[indices.getSecond()] = max(0, current) | clipdata; + int updated = max(0, current) | clipdata; + RegionManager.getFlags(indices.getFirst(), projectile)[indices.getSecond()] = updated; + RegionManager.setRsmodFlag(plane, baseX + x, baseY + y, projectile, updated); } public void removeFlag(int x, int y, int clipdata) { @@ -189,16 +191,19 @@ public final class RegionFlags { current = max(0, current) & ~clipdata; RegionManager.getFlags(indices.getFirst(), projectile)[indices.getSecond()] = current; + RegionManager.setRsmodFlag(plane, baseX + x, baseY + y, projectile, current); } public void clearFlag(int x, int y) { Pair indices = getFlagIndex(x, y); RegionManager.getFlags(indices.getFirst(), projectile)[indices.getSecond()] = 0; + RegionManager.setRsmodFlag(plane, baseX + x, baseY + y, projectile, 0); } public void invalidateFlag(int x, int y) { Pair indices = getFlagIndex(x, y); RegionManager.getFlags(indices.getFirst(), projectile)[indices.getSecond()] = -1; + RegionManager.setRsmodFlag(plane, baseX + x, baseY + y, projectile, -1); } /** @@ -534,4 +539,4 @@ public final class RegionFlags { public void setLandscape(boolean[][] landscape) { this.landscape = landscape; } -} \ No newline at end of file +} diff --git a/Server/src/main/core/game/world/map/path/DumbPathfinder.java b/Server/src/main/core/game/world/map/path/DumbPathfinder.java deleted file mode 100644 index 8bcfb32e0..000000000 --- a/Server/src/main/core/game/world/map/path/DumbPathfinder.java +++ /dev/null @@ -1,427 +0,0 @@ -package core.game.world.map.path; - -import core.game.world.map.Direction; -import core.game.world.map.Location; -import core.game.world.map.Point; - -import java.util.ArrayList; -import java.util.List; - -/** - * A pathfinder implementation used for an easy path, where the pathfinder won't - * find a way around clipped objects..
This is used for NPC combat - * following, NPC random movement, etc. - * @author Emperor - */ -public final class DumbPathfinder extends Pathfinder { - /** - * If a path can be found. - */ - private boolean found; - - /** - * The plane. - */ - private int z; - - /** - * The x-coordinate. - */ - private int x; - - /** - * The y-coordinate. - */ - private int y; - - @Override - public Path find(Location start, int size, Location end, int sizeX, int sizeY, int rotation, int type, int walkingFlag, boolean near, ClipMaskSupplier clipMaskSupplier) { - Path path = new Path(); - z = start.getZ(); - x = start.getX(); - y = start.getY(); - List points = new ArrayList<>(20); - path.setSuccesful(true); - while (x != end.getX() || y != end.getY()) { - Direction[] directions = getDirection(x, y, end); - if (type != 0) { - if ((type < 5 || type == 10) && canDoorInteract(x, y, size, end.getX(), end.getY(), type - 1, rotation, z, clipMaskSupplier)) { - break; - } - if (type < 10 && canDecorationInteract(x, y, size, end.getX(), end.getY(), type - 1, rotation, z, clipMaskSupplier)) { - break; - } - } - if (sizeX != 0 && sizeY != 0) { - if (canInteract(x, y, size, end.getX(), end.getY(), sizeX, sizeY, walkingFlag, z, clipMaskSupplier)) { - break; - } - if (directions.length > 1) { // Ensures we approach the location - // correctly (non-diagonal). - Direction dir = directions[0]; - if (x + dir.getStepX() == end.getX() && y + dir.getStepY() == end.getY()) { - directions[0] = directions[directions.length - 1]; - directions[directions.length - 1] = dir; - } - } - } - found = true; - if (size < 2) { - checkSingleTraversal(points, clipMaskSupplier, directions); - } else if (size == 2) { - checkDoubleTraversal(points, clipMaskSupplier, directions); - } else { - checkVariableTraversal(points, directions, size, clipMaskSupplier); - } - if (!found) { - path.setMoveNear(x != start.getX() || y != start.getY()); - path.setSuccesful(false); - break; - } - } - if (!points.isEmpty()) { - Direction last = null; - for (int i = 0; i < points.size() - 1; i++) { - Point p = points.get(i); - path.getPoints().add(p); - } - path.getPoints().add(points.get(points.size() - 1)); - } - return path; - } - - /** - * Checks traversal for a size 1 entity. - * @param points The points list. - * @param directions The directions. - */ - private void checkSingleTraversal(List points, ClipMaskSupplier clipMaskSupplier, Direction... directions) { - for (Direction dir : directions) { - found = true; - switch (dir) { - case NORTH: - if ((clipMaskSupplier.getClippingFlag(z, x, y + 1) & PREVENT_NORTH) != 0) { - found = false; - break; - } - points.add(new Point(x, y + 1, dir)); - y++; - break; - case NORTH_EAST: - if ((clipMaskSupplier.getClippingFlag(z, x + 1, y) & PREVENT_EAST) != 0 || (clipMaskSupplier.getClippingFlag(z, x, y + 1) & PREVENT_NORTH) != 0 || (clipMaskSupplier.getClippingFlag(z, x + 1, y + 1) & PREVENT_NORTHEAST) != 0) { - found = false; - break; - } - points.add(new Point(x + 1, y + 1, dir)); - x++; - y++; - break; - case EAST: - if ((clipMaskSupplier.getClippingFlag(z, x + 1, y) & PREVENT_EAST) != 0) { - found = false; - break; - } - points.add(new Point(x + 1, y, dir)); - x++; - break; - case SOUTH_EAST: - if ((clipMaskSupplier.getClippingFlag(z, x + 1, y) & PREVENT_EAST) != 0 || (clipMaskSupplier.getClippingFlag(z, x, y - 1) & PREVENT_SOUTH) != 0 || (clipMaskSupplier.getClippingFlag(z, x + 1, y - 1) & PREVENT_SOUTHEAST) != 0) { - found = false; - break; - } - points.add(new Point(x + 1, y - 1, dir)); - x++; - y--; - break; - case SOUTH: - if ((clipMaskSupplier.getClippingFlag(z, x, y - 1) & PREVENT_SOUTH) != 0) { - found = false; - break; - } - points.add(new Point(x, y - 1, dir)); - y--; - break; - case SOUTH_WEST: - if ((clipMaskSupplier.getClippingFlag(z, x - 1, y) & PREVENT_WEST) != 0 || (clipMaskSupplier.getClippingFlag(z, x, y - 1) & PREVENT_SOUTH) != 0 || (clipMaskSupplier.getClippingFlag(z, x - 1, y - 1) & PREVENT_SOUTHWEST) != 0) { - found = false; - break; - } - points.add(new Point(x - 1, y - 1, dir)); - x--; - y--; - break; - case WEST: - if ((clipMaskSupplier.getClippingFlag(z, x - 1, y) & PREVENT_WEST) != 0) { - found = false; - break; - } - points.add(new Point(x - 1, y, dir)); - x--; - break; - case NORTH_WEST: - if ((clipMaskSupplier.getClippingFlag(z, x - 1, y) & PREVENT_WEST) != 0 || (clipMaskSupplier.getClippingFlag(z, x, y + 1) & PREVENT_NORTH) != 0 || (clipMaskSupplier.getClippingFlag(z, x - 1, y + 1) & PREVENT_NORTHWEST) != 0) { - found = false; - break; - } - points.add(new Point(x - 1, y + 1, dir)); - x--; - y++; - break; - } - if (found) { - break; - } - } - } - - /** - * Checks traversal for a size 1 entity. - * @param points The points list. - * @param directions The directions. - */ - private void checkDoubleTraversal(List points, ClipMaskSupplier clipMaskSupplier, Direction... directions) { - for (Direction dir : directions) { - found = true; - switch (dir) { - case NORTH: - if ((clipMaskSupplier.getClippingFlag(z, x, y + 2) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(z, x + 1, y + 2) & 0x12c01e0) != 0) { - found = false; - break; - } - points.add(new Point(x, y + 1, dir)); - y++; - break; - case NORTH_EAST: - if ((clipMaskSupplier.getClippingFlag(z, x + 1, y + 2) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(z, x + 2, y + 2) & 0x12c01e0) != 0 || (clipMaskSupplier.getClippingFlag(z, x + 2, y + 1) & 0x12c0183) != 0) { - found = false; - break; - } - points.add(new Point(x + 1, y + 1, dir)); - x++; - y++; - break; - case EAST: - if ((clipMaskSupplier.getClippingFlag(z, x + 2, y) & 0x12c0183) != 0 || (clipMaskSupplier.getClippingFlag(z, x + 2, y + 1) & 0x12c01e0) != 0) { - found = false; - break; - } - points.add(new Point(x + 1, y, dir)); - x++; - break; - case SOUTH_EAST: - if ((clipMaskSupplier.getClippingFlag(z, x + 1, y - 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z, x + 2, y) & 0x12c01e0) != 0 || (clipMaskSupplier.getClippingFlag(z, x + 2, y - 1) & 0x12c0183) != 0) { - found = false; - break; - } - points.add(new Point(x + 1, y - 1, dir)); - x++; - y--; - break; - case SOUTH: - if ((clipMaskSupplier.getClippingFlag(z, x, y - 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z, x + 1, y - 1) & 0x12c0183) != 0) { - found = false; - break; - } - points.add(new Point(x, y - 1, dir)); - y--; - break; - case SOUTH_WEST: - if ((clipMaskSupplier.getClippingFlag(z, x - 1, y - 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z, x - 1, y) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(z, x, y - 1) & 0x12c0183) != 0) { - found = false; - break; - } - points.add(new Point(x - 1, y - 1, dir)); - x--; - y--; - break; - case WEST: - if ((clipMaskSupplier.getClippingFlag(z, x - 1, y) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z, x - 1, y + 1) & 0x12c0138) != 0) { - found = false; - break; - } - points.add(new Point(x - 1, y, dir)); - x--; - break; - case NORTH_WEST: - if ((clipMaskSupplier.getClippingFlag(z, x - 1, y + 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z, x - 1, y + 2) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(z, x, y + 2) & 0x12c01e0) != 0) { - found = false; - break; - } - points.add(new Point(x - 1, y + 1, dir)); - x--; - y++; - break; - } - if (found) { - break; - } - } - } - - /** - * Checks traversal for variable size entities. - * @param points The points list. - * @param directions The directions to check. - * @param size The mover size. - */ - private void checkVariableTraversal(List points, Direction[] directions, int size, ClipMaskSupplier clipMaskSupplier) { - for (Direction dir : directions) { - found = true; - roar: switch (dir) { - case NORTH: - if ((clipMaskSupplier.getClippingFlag(z, x, y + size) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(z, x + (size - 1), y + size) & 0x12c01e0) != 0) { - found = false; - break; - } - for (int i = 1; i < size - 1; i++) { - if ((clipMaskSupplier.getClippingFlag(z, x + i, y + size) & 0x12c01f8) != 0) { - found = false; - break roar; - } - } - points.add(new Point(x, y + 1, dir)); - y++; - break; - case NORTH_EAST: - if ((clipMaskSupplier.getClippingFlag(z, x + 1, y + size) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(z, x + size, y + size) & 0x12c01e0) != 0 || (clipMaskSupplier.getClippingFlag(z, x + size, y + 1) & 0x12c0183) != 0) { - found = false; - break; - } - for (int i = 1; i < size - 1; i++) { - if ((clipMaskSupplier.getClippingFlag(z, x + (i + 1), y + size) & 0x12c01f8) != 0 || (clipMaskSupplier.getClippingFlag(z, x + size, y + (i + 1)) & 0x12c01e3) != 0) { - found = false; - break roar; - } - } - points.add(new Point(x + 1, y + 1, dir)); - x++; - y++; - break; - case EAST: - if ((clipMaskSupplier.getClippingFlag(z, x + size, y) & 0x12c0183) != 0 || (clipMaskSupplier.getClippingFlag(z, x + size, y + (size - 1)) & 0x12c01e0) != 0) { - found = false; - break; - } - for (int i = 1; i < size - 1; i++) { - if ((clipMaskSupplier.getClippingFlag(z, x + size, y + i) & 0x12c01e3) != 0) { - found = false; - break roar; - } - } - points.add(new Point(x + 1, y, dir)); - x++; - break; - case SOUTH_EAST: - if ((clipMaskSupplier.getClippingFlag(z, x + 1, y - 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z, x + size, y + (size - 2)) & 0x12c01e0) != 0 || (clipMaskSupplier.getClippingFlag(z, x + size, y - 1) & 0x12c0183) != 0) { - found = false; - break; - } - for (int i = 1; i < size - 1; i++) { - if ((clipMaskSupplier.getClippingFlag(z, x + size, y + (i - 1)) & 0x12c01e3) != 0 || (clipMaskSupplier.getClippingFlag(z, x + (i + 1), y - 1) & 0x12c018f) != 0) { - found = false; - break roar; - } - } - points.add(new Point(x + 1, y - 1, dir)); - x++; - y--; - break; - case SOUTH: - if ((clipMaskSupplier.getClippingFlag(z, x, y - 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z, x + (size - 1), y - 1) & 0x12c0183) != 0) { - found = false; - break; - } - for (int i = 1; i < size - 1; i++) { - if ((clipMaskSupplier.getClippingFlag(z, x + i, y - 1) & 0x12c018f) != 0) { - found = false; - break roar; - } - } - points.add(new Point(x, y - 1, dir)); - y--; - break; - case SOUTH_WEST: - if ((clipMaskSupplier.getClippingFlag(z, x - 1, y + (size - 2)) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(z, x - 1, y - 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z, x + (size - 2), y - 1) & 0x12c0183) != 0) { - found = false; - break; - } - for (int i = 1; i < size - 1; i++) { - if ((clipMaskSupplier.getClippingFlag(z, x - 1, y + (i - 1)) & 0x12c013e) != 0 || (clipMaskSupplier.getClippingFlag(z, x + (i - 1), y - 1) & 0x12c018f) != 0) { - found = false; - break roar; - } - } - points.add(new Point(x - 1, y - 1, dir)); - x--; - y--; - break; - case WEST: - if ((clipMaskSupplier.getClippingFlag(z, x - 1, y) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z, x - 1, y + (size - 1)) & 0x12c0138) != 0) { - found = false; - break; - } - for (int i = 1; i < size - 1; i++) { - if ((clipMaskSupplier.getClippingFlag(z, x - 1, y + i) & 0x12c013e) != 0) { - found = false; - break roar; - } - } - points.add(new Point(x - 1, y, dir)); - x--; - break; - case NORTH_WEST: - if ((clipMaskSupplier.getClippingFlag(z, x - 1, y + 1) & 0x12c010e) != 0 || (clipMaskSupplier.getClippingFlag(z, x - 1, y + size) & 0x12c0138) != 0 || (clipMaskSupplier.getClippingFlag(z, x, y + size) & 0x12c01e0) != 0) { - found = false; - break; - } - for (int i = 1; i < size - 1; i++) { - if ((clipMaskSupplier.getClippingFlag(z, x - 1, y + (i + 1)) & 0x12c013e) != 0 || (clipMaskSupplier.getClippingFlag(z, x + (i - 1), y + size) & 0x12c01f8) != 0) { - found = false; - break roar; - } - } - points.add(new Point(x - 1, y + 1, dir)); - x--; - y++; - break; - } - if (found) { - break; - } - } - } - - /** - * Gets the direction. - * @param end The end direction. - * @return The direction. - */ - private static Direction[] getDirection(int startX, int startY, Location end) { - int endX = end.getX(); - int endY = end.getY(); - if (startX == endX) { - if (startY > endY) { - return new Direction[] { Direction.SOUTH }; - } else if (startY < endY) { - return new Direction[] { Direction.NORTH }; - } - } else if (startY == endY) { - if (startX > endX) { - return new Direction[] { Direction.WEST }; - } - return new Direction[] { Direction.EAST }; - } else { - if (startX < endX && startY < endY) { - return new Direction[] { Direction.NORTH_EAST, Direction.EAST, Direction.NORTH }; - } else if (startX < endX && startY > endY) { - return new Direction[] { Direction.SOUTH_EAST, Direction.EAST, Direction.SOUTH }; - } else if (startX > endX && startY < endY) { - return new Direction[] { Direction.NORTH_WEST, Direction.WEST, Direction.NORTH }; - } else if (startX > endX && startY > endY) { - return new Direction[] { Direction.SOUTH_WEST, Direction.WEST, Direction.SOUTH }; - } - } - return new Direction[0]; - } - -} diff --git a/Server/src/main/core/game/world/map/path/Pathfinder.java b/Server/src/main/core/game/world/map/path/Pathfinder.java index 9fce3a782..50dca754f 100644 --- a/Server/src/main/core/game/world/map/path/Pathfinder.java +++ b/Server/src/main/core/game/world/map/path/Pathfinder.java @@ -5,7 +5,6 @@ import core.game.node.entity.Entity; import core.game.node.entity.npc.NPC; import core.game.node.item.GroundItem; import core.game.node.scenery.Scenery; -import core.game.world.map.Direction; import core.game.world.map.Location; import core.game.world.map.RegionManager; @@ -23,71 +22,17 @@ public abstract class Pathfinder { /** * The smart path finder. */ - public static final SmartPathfinder SMART = new SmartPathfinder(); + public static final Pathfinder SMART = new RsmodPathfinder(); /** * The dumb path finder. */ - public static final DumbPathfinder DUMB = new DumbPathfinder(); + public static final Pathfinder DUMB = new RsmodPathfinder(); /** * The projectile path finder. */ - public static final ProjectilePathfinder PROJECTILE = new ProjectilePathfinder(); - - /** - * The south direction flag. - */ - public static final int SOUTH_FLAG = 0x1; - - /** - * The west direction flag. - */ - public static final int WEST_FLAG = 0x2; - - /** - * The north direction flag. - */ - public static final int NORTH_FLAG = 0x4; - - /** - * The east direction flag. - */ - public static final int EAST_FLAG = 0x8; - - /** - * The south-west direction flag. - */ - public static final int SOUTH_WEST_FLAG = SOUTH_FLAG | WEST_FLAG; - - /** - * The north-west direction flag. - */ - public static final int NORTH_WEST_FLAG = NORTH_FLAG | WEST_FLAG; - - /** - * The south-east direction flag. - */ - public static final int SOUTH_EAST_FLAG = SOUTH_FLAG | EAST_FLAG; - - /** - * The north-east direction flag. - */ - public static final int NORTH_EAST_FLAG = NORTH_FLAG | EAST_FLAG; - - public static int flagForDirection(Direction d) { - switch(d) { - case NORTH_WEST: return NORTH_WEST_FLAG; - case NORTH: return NORTH_FLAG; - case NORTH_EAST: return NORTH_EAST_FLAG; - case WEST: return WEST_FLAG; - case EAST: return EAST_FLAG; - case SOUTH_WEST: return SOUTH_WEST_FLAG; - case SOUTH: return SOUTH_FLAG; - case SOUTH_EAST: return SOUTH_EAST_FLAG; - default: return 0; - } - } + public static final Pathfinder PROJECTILE = new RsmodProjectilePathfinder(); /** * Finds a path from the location to the end location. @@ -125,12 +70,11 @@ public abstract class Pathfinder { * @return The path. */ public static Path find(Entity mover, Node destination, boolean near, Pathfinder finder) { - ClipMaskSupplier cms = null; - if (mover instanceof NPC) { - cms = ((NPC) mover).behavior.getClippingSupplier(((NPC) mover)); - } - if (cms == null) - cms = RegionManager::getClippingFlag; + ClipMaskSupplier cms = null; + if (mover instanceof NPC) { + NPC npc = (NPC) mover; + cms = npc.behavior != null ? npc.behavior.getClippingSupplier(npc) : null; + } return find(mover.getLocation(), mover.size(), destination, near, finder, cms); } @@ -151,8 +95,8 @@ public abstract class Pathfinder { return find(start, destination, true, SMART); } - public static Path find(Location start, Node destination, int moverSize) { - return find(start, moverSize, destination, true, SMART, RegionManager::getClippingFlag); + public static Path find(Location start, Node destination, int moverSize) { + return find(start, moverSize, destination, true, SMART, null); } /** @@ -164,7 +108,7 @@ public abstract class Pathfinder { * @return The path. */ public static Path find(Location start, Node destination, boolean near, Pathfinder finder) { - return find(start, 1, destination, near, finder, RegionManager::getClippingFlag); + return find(start, 1, destination, near, finder, null); } /** @@ -181,19 +125,9 @@ public abstract class Pathfinder { int type = object.getType(); int rotation = object.getRotation(); if (type == 10 || type == 11 || type == 22) { - int sizeX = object.getDefinition().sizeX; - int sizeY = object.getDefinition().sizeY; - if (rotation % 2 != 0) { - sizeX = object.getDefinition().sizeY; - sizeY = object.getDefinition().sizeX; - } - int walkingFlag = object.getDefinition().getWalkingFlag(); - if (rotation != 0) { - walkingFlag = (walkingFlag << rotation & 0xf) + (walkingFlag >> 4 - rotation); - } - return finder.find(start, moverSize, destination.getLocation(), sizeX, sizeY, 0, 0, walkingFlag, near, clipMaskSupplier); + return finder.find(start, moverSize, destination.getLocation(), object.getDefinition().sizeX, object.getDefinition().sizeY, rotation, type, object.getDefinition().getWalkingFlag(), near, clipMaskSupplier); } - return finder.find(start, moverSize, destination.getLocation(), 0, 0, rotation, 1 + type, 0, near, clipMaskSupplier); + return finder.find(start, moverSize, destination.getLocation(), 0, 0, rotation, type, 0, near, clipMaskSupplier); } int size = 0; if (destination instanceof Entity) { @@ -201,7 +135,7 @@ public abstract class Pathfinder { } else if (destination instanceof GroundItem && !RegionManager.isTeleportPermitted(destination.getLocation())) { size = 1; } - return finder.find(start, moverSize, destination.getLocation(), size, size, 0, 0, 0, near, clipMaskSupplier); + return finder.find(start, moverSize, destination.getLocation(), size, size, 0, -1, 0, near, clipMaskSupplier); } /** @@ -216,116 +150,7 @@ public abstract class Pathfinder { * @return {@code True} if so. */ public static boolean canDecorationInteract(int curX, int curY, int size, int destX, int destY, int rotation, int type, int z, ClipMaskSupplier clipMaskSupplier) { - if (size != 1) { - if (destX >= curX && destX <= (curX + size) - 1 && destY <= (destY + size) - 1) { - return true; - } - } else if (destX == curX && curY == destY) { - return true; - } - if (size == 1) { - int flag = clipMaskSupplier.getClippingFlag(z, curX, curY); - if (type == 6 || type == 7) { - if (type == 7) { - rotation = rotation + 2 & 0x3; - } - if (rotation == 0) { - if (curX == 1 + destX && curY == destY && (0x80 & flag) == 0) { - return true; - } - if (destX == curX && curY == destY - 1 && (flag & 0x2) == 0) { - return true; - } - } else if (rotation == 1) { - if (curX == destX - 1 && curY == destY && (0x8 & flag) == 0) { - return true; - } - if (curX == destX && curY == destY - 1 && (flag & 0x2) == 0) { - return true; - } - } else if (rotation == 2) { - if (destX - 1 == curX && destY == curY && (flag & 0x8) == 0) { - return true; - } - if (destX == curX && destY + 1 == curY && (0x20 & flag) == 0) { - return true; - } - } else if (rotation == 3) { - if (destX + 1 == curX && curY == destY && (0x80 & flag) == 0) { - return true; - } - if (destX == curX && curY == destY + 1 && (0x20 & flag) == 0) { - return true; - } - } - } - if (type == 8) { - if (destX == curX && curY == destY + 1 && (flag & 0x20) == 0) { - return true; - } - if (destX == curX && -1 + destY == curY && (0x2 & flag) == 0) { - return true; - } - if (curX == destX - 1 && curY == destY && (0x8 & flag) == 0) { - return true; - } - if (curX == destX + 1 && curY == destY && (flag & 0x80) == 0) { - return true; - } - } - } else { - int cornerX = curX + size - 1; - int cornerY = curY + size - 1; - if (type == 6 || type == 7) { - if (type == 7) { - rotation = 0x3 & 2 + rotation; - } - if (rotation == 0) { - if (destX + 1 == curX && destY >= curY && destY <= cornerY && (clipMaskSupplier.getClippingFlag(z, curX, destY) & 0x80) == 0) { - return true; - } - if (destX >= curX && destX <= cornerX && destY - size == curY && (0x2 & clipMaskSupplier.getClippingFlag(z, destX, cornerY)) == 0) { - return true; - } - } else if (rotation == 1) { - if (-size + destX == curX && destY >= curY && cornerY >= destY && (clipMaskSupplier.getClippingFlag(z, cornerX, destY) & 0x8) == 0) { - return true; - } - if (curX <= destX && cornerX >= destX && -size + destY == curY && (clipMaskSupplier.getClippingFlag(z, destX, cornerY) & 0x2) == 0) { - return true; - } - } else if (rotation == 2) { - if (curX == destX - size && curY <= destY && destY <= cornerY && (0x8 & clipMaskSupplier.getClippingFlag(z, cornerX, destY)) == 0) { - return true; - } - if (curX <= destX && cornerX >= destX && destY + 1 == curY && (0x20 & clipMaskSupplier.getClippingFlag(z, destX, curY)) == 0) { - return true; - } - } else if (rotation == 3) { - if (1 + destX == curX && curY <= destY && destY <= cornerY && (0x80 & clipMaskSupplier.getClippingFlag(z, curX, destY)) == 0) { - return true; - } - if (destX >= curX && destX <= cornerX && 1 + destY == curY && (clipMaskSupplier.getClippingFlag(z, destX, curY) & 0x20) == 0) { - return true; - } - } - } - if (type == 8) { - if (curX <= destX && destX <= cornerX && 1 + destY == curY && (clipMaskSupplier.getClippingFlag(z, destX, curY) & 0x20) == 0) { - return true; - } - if (curX <= destX && destX <= cornerX && curY == -size + destY && (0x2 & clipMaskSupplier.getClippingFlag(z, destX, cornerY)) == 0) { - return true; - } - if (curX == -size + destX && destY >= curY && destY <= cornerY && (0x8 & clipMaskSupplier.getClippingFlag(z, cornerX, destY)) == 0) { - return true; - } - if (1 + destX == curX && curY <= destY && cornerY >= destY && (clipMaskSupplier.getClippingFlag(z, curX, destY) & 0x80) == 0) { - return true; - } - } - } - return false; + return RsmodPathfinder.canReach(curX, curY, size, destX, destY, 1, 1, rotation, type, 0, z, clipMaskSupplier); } /** @@ -340,242 +165,7 @@ public abstract class Pathfinder { * @return {@code True} if so. */ public static boolean canDoorInteract(int curX, int curY, int size, int destX, int destY, int type, int rotation, int z, ClipMaskSupplier clipMaskSupplier) { - if (size != 1) { - if (destX >= curX && destX <= size + curX - 1 && destY <= destY + size - 1) { - return true; - } - } else if (curX == destX && destY == curY) { - return true; - } - - if (size == 1) { - if (type == 0) { - if (rotation == 0) { - if (curX == destX - 1 && destY == curY) { - return true; - } - if (destX == curX && 1 + destY == curY && (0x12c0120 & clipMaskSupplier.getClippingFlag(z, curX, curY)) == 0) { - return true; - } - if (curX == destX && destY - 1 == curY && (clipMaskSupplier.getClippingFlag(z, curX, curY) & 0x12c0102) == 0) { - return true; - } - } else if (rotation == 1) { - if (curX == destX && destY + 1 == curY) { - return true; - } - if (curX == destX - 1 && curY == destY && (0x12c0108 & clipMaskSupplier.getClippingFlag(z, curX, curY)) == 0) { - return true; - } - if (curX == 1 + destX && destY == curY && (0x12c0180 & clipMaskSupplier.getClippingFlag(z, curX, curY)) == 0) { - return true; - } - } else if (rotation == 2) { - if (1 + destX == curX && destY == curY) { - return true; - } - if (destX == curX && 1 + destY == curY && (0x12c0120 & clipMaskSupplier.getClippingFlag(z, curX, curY)) == 0) { - return true; - } - if (curX == destX && curY == destY - 1 && (clipMaskSupplier.getClippingFlag(z, curX, curY) & 0x12c0102) == 0) { - return true; - } - } else if (rotation == 3) { - if (curX == destX && -1 + destY == curY) { - return true; - } - if (curX == -1 + destX && destY == curY && (0x12c0108 & clipMaskSupplier.getClippingFlag(z, curX, curY)) == 0) { - return true; - } - if (curX == 1 + destX && destY == curY && (clipMaskSupplier.getClippingFlag(z, curX, curY) & 0x12c0180) == 0) { - return true; - } - } - } else if (type == 2) { - if (rotation == 0) { - if (destX - 1 == curX && curY == destY) { - return true; - } - if (destX == curX && curY == 1 + destY) { - return true; - } - if (curX == destX + 1 && curY == destY && (0x12c0180 & clipMaskSupplier.getClippingFlag(z, curX, curY)) == 0) { - return true; - } - if (curX == destX && destY - 1 == curY && (clipMaskSupplier.getClippingFlag(z, curX, curY) & 0x12c0102) == 0) { - return true; - } - } else if (rotation == 1) { - if (curX == destX - 1 && curY == destY && (0x12c0108 & clipMaskSupplier.getClippingFlag(z, curX, curY)) == 0) { - return true; - } - if (curX == destX && curY == 1 + destY) { - return true; - } - if (1 + destX == curX && curY == destY) { - return true; - } - if (curX == destX && destY - 1 == curY && (clipMaskSupplier.getClippingFlag(z, curX, curY) & 0x12c0102) == 0) { - return true; - } - } else if (rotation == 2) { - if (destX - 1 == curX && destY == curY && (0x12c0108 & clipMaskSupplier.getClippingFlag(z, curX, curY)) == 0) { - return true; - } - if (destX == curX && 1 + destY == curY && (0x12c0120 & clipMaskSupplier.getClippingFlag(z, curX, curY)) == 0) { - return true; - } - if (1 + destX == curX && curY == destY) { - return true; - } - if (curX == destX && curY == destY - 1) { - return true; - } - } else if (rotation == 3) { - if (destX - 1 == curX && curY == destY) { - return true; - } - if (destX == curX && curY == destY + 1 && (0x12c0120 & clipMaskSupplier.getClippingFlag(z, curX, curY)) == 0) { - return true; - } - if (curX == 1 + destX && curY == destY && (clipMaskSupplier.getClippingFlag(z, curX, curY) & 0x12c0180) == 0) { - return true; - } - if (destX == curX && destY - 1 == curY) { - return true; - } - } - } else if (type == 9) { - if (curX == destX && curY == destY + 1 && (clipMaskSupplier.getClippingFlag(z, curX, curY) & 0x20) == 0) { - return true; - } - if (curX == destX && curY == destY - 1 && (clipMaskSupplier.getClippingFlag(z, curX, curY) & 0x2) == 0) { - return true; - } - if (curX == destX - 1 && curY == destY && (0x8 & clipMaskSupplier.getClippingFlag(z, curX, curY)) == 0) { - return true; - } - if (destX + 1 == curX && curY == destY && (0x80 & clipMaskSupplier.getClippingFlag(z, curX, curY)) == 0) { - return true; - } - } - } else { - int cornerX = curX - (1 - size); - int cornerY = -1 + curY + size; - if (type == 0) { - if (rotation == 0) { - if (destX - size == curX && destY >= curY && destY <= cornerY) { - return true; - } - if (destX >= curX && cornerX >= destX && curY == 1 + destY && (clipMaskSupplier.getClippingFlag(z, destX, curY) & 0x12c0120) == 0) { - return true; - } - if (destX >= curX && cornerX >= destX && destY - size == curY && (clipMaskSupplier.getClippingFlag(z, destX, cornerY) & 0x12c0102) == 0) { - return true; - } - } else if (rotation == 1) { - if (destX >= curX && cornerX >= destX && destY + 1 == curY) { - return true; - } - if (curX == -size + destX && destY >= curY && cornerY >= destY && (0x12c0108 & clipMaskSupplier.getClippingFlag(z, cornerX, destY)) == 0) { - return true; - } - if (curX == 1 + destX && destY >= curY && cornerY >= destY && (clipMaskSupplier.getClippingFlag(z, curX, destY) & 0x12c0180) == 0) { - return true; - } - } else if (rotation == 2) { - if (curX == 1 + destX && curY <= destY && destY <= cornerY) { - return true; - } - if (curX <= destX && cornerX >= destX && destY + 1 == curY && (0x12c0120 & clipMaskSupplier.getClippingFlag(z, destX, curY)) == 0) { - return true; - } - if (destX >= curX && destX <= cornerX && destY - size == curY && (0x12c0102 & clipMaskSupplier.getClippingFlag(z, destX, cornerY)) == 0) { - return true; - } - } else if (rotation == 3) { - if (curX <= destX && destX <= cornerX && curY == -size + destY) { - return true; - } - if (-size + destX == curX && curY <= destY && destY <= cornerY && (clipMaskSupplier.getClippingFlag(z, cornerX, destY) & 0x12c0108) == 0) { - return true; - } - if (1 + destX == curX && curY <= destY && cornerY >= destY && (clipMaskSupplier.getClippingFlag(z, curX, destY) & 0x12c0180) == 0) { - return true; - } - } - } - if (type == 2) { - if (rotation == 0) { - if (destX - size == curX && curY <= destY && destY <= cornerY) { - return true; - } - if (curX <= destX && destX <= cornerX && curY == 1 + destY) { - return true; - } - if (curX == 1 + destX && curY <= destY && destY <= cornerY && (0x12c0180 & clipMaskSupplier.getClippingFlag(z, curX, destY)) == 0) { - return true; - } - if (curX <= destX && cornerX >= destX && -size + destY == curY && (clipMaskSupplier.getClippingFlag(z, destX, cornerY) & 0x12c0102) == 0) { - return true; - } - } else if (rotation == 1) { - if (-size + destX == curX && destY >= curY && destY <= cornerY && (clipMaskSupplier.getClippingFlag(z, cornerX, destY) & 0x12c0108) == 0) { - return true; - } - if (destX >= curX && cornerX >= destX && curY == 1 + destY) { - return true; - } - if (destX + 1 == curX && curY <= destY && destY <= cornerY) { - return true; - } - if (destX >= curX && cornerX >= destX && destY + -size == curY && (0x12c0102 & clipMaskSupplier.getClippingFlag(z, destX, cornerY)) == 0) { - return true; - } - } else if (rotation == 2) { - if (curX == destX - size && curY <= destY && cornerY >= destY && (clipMaskSupplier.getClippingFlag(z, cornerX, destY) & 0x12c0108) == 0) { - return true; - } - if (destX >= curX && destX <= cornerX && 1 + destY == curY && (0x12c0120 & clipMaskSupplier.getClippingFlag(z, destX, curY)) == 0) { - return true; - } - if (1 + destX == curX && destY >= curY && cornerY >= destY) { - return true; - } - if (curX <= destX && destX <= cornerX && curY == -size + destY) { - return true; - } - } else if (rotation == 3) { - if (destX + -size == curX && destY >= curY && destY <= cornerY) { - return true; - } - if (curX <= destX && cornerX >= destX && curY == 1 + destY && (clipMaskSupplier.getClippingFlag(z, destX, curY) & 0x12c0120) == 0) { - return true; - } - if (1 + destX == curX && destY >= curY && cornerY >= destY && (0x12c0180 & clipMaskSupplier.getClippingFlag(z, curX, destY)) == 0) { - return true; - } - if (destX >= curX && destX <= cornerX && curY == -size + destY) { - return true; - } - } - } - if (type == 9) { - if (destX >= curX && destX <= cornerX && curY == 1 + destY && (clipMaskSupplier.getClippingFlag(z, destX, curY) & 0x12c0120) == 0) { - return true; - } - if (destX >= curX && cornerX >= destX && curY == -size + destY && (0x12c0102 & clipMaskSupplier.getClippingFlag(z, destX, cornerY)) == 0) { - return true; - } - if (-size + destX == curX && destY >= curY && cornerY >= destY && (0x12c0108 & clipMaskSupplier.getClippingFlag(z, cornerX, destY)) == 0) { - return true; - } - if (curX == destX + 1 && destY >= curY && cornerY >= destY && (clipMaskSupplier.getClippingFlag(z, curX, destY) & 0x12c0180) == 0) { - return true; - } - } - } - return false; + return RsmodPathfinder.canReach(curX, curY, size, destX, destY, 1, 1, rotation, type, 0, z, clipMaskSupplier); } /** @@ -613,28 +203,7 @@ public abstract class Pathfinder { * @return {@code True} if so. */ public static boolean canInteract(int x, int y, int moverSize, int destX, int destY, int sizeX, int sizeY, int walkFlag, int z, ClipMaskSupplier clipMaskSupplier) { - if (moverSize > 1) { - return isStandingIn(x, y, moverSize, moverSize, destX, destY, sizeX, sizeY) || canInteractSized(x, y, moverSize, moverSize, destX, destY, sizeX, sizeY, walkFlag, z); - } - int flag = clipMaskSupplier.getClippingFlag(z, x, y); - int cornerX = destX + sizeX - 1; - int cornerY = destY + sizeY - 1; - if (destX <= x && cornerX >= x && y >= destY && y <= cornerY) { - return true; - } - if (x == destX - 1 && destY <= y && y <= cornerY && (0x8 & flag) == 0 && (0x8 & walkFlag) == 0) { - return true; - } - if (x == cornerX + 1 && destY <= y && cornerY >= y && (flag & 0x80) == 0 && (0x2 & walkFlag) == 0) { - return true; - } - if (y == destY - 1 && destX <= x && cornerX >= x && (0x2 & flag) == 0 && (0x4 & walkFlag) == 0) { - return true; - } - if (y == cornerY + 1 && destX <= x && cornerX >= x && (flag & 0x20) == 0 && (0x1 & walkFlag) == 0) { - return true; - } - return false; + return RsmodPathfinder.canReach(x, y, moverSize, destX, destY, sizeX, sizeY, 0, -1, walkFlag, z, clipMaskSupplier); } /** @@ -646,73 +215,6 @@ public abstract class Pathfinder { * @return {@code True} if so. */ public static boolean canInteractSized(int curX, int curY, int moverSizeX, int moverSizeY, int destX, int destY, int sizeX, int sizeY, int walkingFlag, int z) { - int fromCornerY = curY + moverSizeY; - int fromCornerX = curX + moverSizeX; - int toCornerX = sizeX + destX; - int toCornerY = sizeY + destY; - if (destX <= curX && curX < toCornerX) { - if (destY == fromCornerY && (walkingFlag & 0x4) == 0) { - int x = curX; - for (int endX = toCornerX < fromCornerX ? toCornerX : fromCornerX; endX > x; x++) { - if ((RegionManager.getClippingFlag(z, x, -1 + fromCornerY) & 0x2) == 0) { - return true; - } - } - } else if (toCornerY == curY && (walkingFlag & 0x1) == 0) { - int x = curX; - for (int endX = fromCornerX <= toCornerX ? fromCornerX : toCornerX; x < endX; x++) { - if ((RegionManager.getClippingFlag(z, x, curY) & 0x20) == 0) { - return true; - } - } - } - } else if (destX < fromCornerX && toCornerX >= fromCornerX) { - if (fromCornerY == destY && (0x4 & walkingFlag) == 0) { - for (int x = destX; fromCornerX > x; x++) { - if ((RegionManager.getClippingFlag(z, x, -1 + (fromCornerY)) & 0x2) == 0) { - return true; - } - } - } else if (toCornerY == curY && (0x1 & walkingFlag) == 0) { - for (int x = destX; fromCornerX > x; x++) { - if ((RegionManager.getClippingFlag(z, x, curY) & 0x20) == 0) { - return true; - } - } - } - } else if (curY < destY || curY >= toCornerY) { - if (fromCornerY > destY && toCornerY >= fromCornerY) { - if (fromCornerX == destX && (walkingFlag & 0x8) == 0) { - for (int y = destY; y < fromCornerY; y++) { - if ((RegionManager.getClippingFlag(z, -1 + fromCornerX, y) & 0x8) == 0) { - return true; - } - } - } else if (curX == toCornerX && (0x2 & walkingFlag) == 0) { - for (int y = destY; fromCornerY > y; y++) { - if ((RegionManager.getClippingFlag(z, curX, y) & 0x80) == 0) { - return true; - } - } - } - } - } else if (destX != fromCornerX || (0x8 & walkingFlag) != 0) { - if (curX == toCornerX && (walkingFlag & 0x2) == 0) { - int y = curY; - for (int endY = fromCornerY <= toCornerY ? fromCornerY : toCornerY; y < endY; y++) { - if ((0x80 & RegionManager.getClippingFlag(z, curX, y)) == 0) { - return true; - } - } - } - } else { - int y = curY; - for (int endY = fromCornerY > toCornerY ? toCornerY : fromCornerY; endY > y; y++) { - if ((RegionManager.getClippingFlag(z, fromCornerX - 1, y) & 0x8) == 0) { - return true; - } - } - } - return false; + return RsmodPathfinder.canReach(curX, curY, moverSizeX, destX, destY, sizeX, sizeY, 0, -1, walkingFlag, z, null); } } diff --git a/Server/src/main/core/game/world/map/path/ProjectilePathfinder.java b/Server/src/main/core/game/world/map/path/ProjectilePathfinder.java deleted file mode 100644 index d20b96c22..000000000 --- a/Server/src/main/core/game/world/map/path/ProjectilePathfinder.java +++ /dev/null @@ -1,200 +0,0 @@ -package core.game.world.map.path; - -import core.game.world.map.Direction; -import core.game.world.map.Location; -import core.game.world.map.Point; -import core.game.world.map.RegionManager; - -import java.util.ArrayList; -import java.util.List; - -/** - * A pathfinder implementation used for checking projectile paths. - * @author Emperor - */ -public final class ProjectilePathfinder extends Pathfinder { - - /** - * If a path can be found. - */ - private boolean found; - - /** - * The plane. - */ - private int z; - - /** - * The x-coordinate. - */ - private int x; - - /** - * The y-coordinate. - */ - private int y; - - @Override - public Path find(Location start, int size, Location end, int sizeX, int sizeY, int rotation, int type, int walkingFlag, boolean near, ClipMaskSupplier clipMaskSupplier) { - Path path = new Path(); - z = start.getZ(); - x = start.getX(); - y = start.getY(); - List points = new ArrayList<>(20); - path.setSuccesful(true); - while (x != end.getX() || y != end.getY()) { - Direction[] directions = getDirection(x, y, end); - found = true; - checkSingleTraversal(points, directions); - if (!found) { - path.setMoveNear(x != start.getX() || y != start.getY()); - path.setSuccesful(false); - break; - } - } - if (!points.isEmpty()) { - for (int i = 0; i < points.size() - 1; i++) { - Point p = points.get(i); - if (p.getDirection() != null) { - path.getPoints().add(p); - } - } - path.getPoints().add(points.get(points.size() - 1)); - } - return path; - } - - /** - * Checks traversal for a size 1 entity. - * - * @param points The points list. - * @param directions The directions. - */ - private void checkSingleTraversal(List points, Direction... directions) { - dir: - for (Direction dir : directions) { - found = true; - switch (dir) { - case NORTH: - if (flagged(z, x, y + 1, 0x12c0120)) { - found = false; - break dir; - } - points.add(new Point(x, y + 1, dir)); - y++; - break; - case NORTH_EAST: - if (flagged(z, x + 1, y, 0x12c0180) - || flagged(z, x, y + 1, 0x12c0120) - || flagged(z, x + 1, y + 1, 0x12c01e0)) { - found = false; - break dir; - } - points.add(new Point(x + 1, y + 1, dir)); - x++; - y++; - break; - case EAST: - if (flagged(z, x + 1, y, 0x12c0180)) { - found = false; - break dir; - } - points.add(new Point(x + 1, y, dir)); - x++; - break; - case SOUTH_EAST: - if (flagged(z, x + 1, y, 0x12c0180) - || flagged(z, x, y - 1, 0x12c0102) - || flagged(z, x + 1, y - 1, 0x12c0183)) { - found = false; - break dir; - } - points.add(new Point(x + 1, y - 1, dir)); - x++; - y--; - break; - case SOUTH: - if (flagged(z, x, y - 1, 0x12c0102)) { - found = false; - break dir; - } - points.add(new Point(x, y - 1, dir)); - y--; - break; - case SOUTH_WEST: - if (flagged(z, x - 1, y, 0x12c0108) - || flagged(z, x, y - 1, 0x12c0102) - || flagged(z, x - 1, y - 1, 0x12c010e)) { - found = false; - break dir; - } - points.add(new Point(x - 1, y - 1, dir)); - x--; - y--; - break; - case WEST: - if (flagged(z, x - 1, y, 0x12c0108)) { - found = false; - break dir; - } - points.add(new Point(x - 1, y, dir)); - x--; - break; - case NORTH_WEST: - if (flagged(z, x - 1, y, 0x12c0108) - || flagged(z, x, y + 1, 0x12c0120) - || flagged(z, x - 1, y + 1, 0x12c0138)) { - found = false; - break dir; - } - points.add(new Point(x - 1, y + 1, dir)); - x--; - y++; - break; - } - if (found) { - break; - } - } - } - - private static boolean flagged(int z, int x, int y, int pFlagMask) { - int pFlag = RegionManager.getProjectileFlag(z, x, y); - return (pFlag & pFlagMask) != 0 || (pFlag & 0x20000) != 0 || (RegionManager.getClippingFlag(z, x, y) & 0x20000) != 0; - } - - /** - * Gets the direction. - * @param startX The startX. - * @param startY The startY. - * @param end The end direction. - * @return The direction. - */ - private static Direction[] getDirection(int startX, int startY, Location end) { - int endX = end.getX(); - int endY = end.getY(); - if (startX == endX) { - if (startY > endY) { - return new Direction[] { Direction.SOUTH }; - } else if (startY < endY) { - return new Direction[] { Direction.NORTH }; - } - } else if (startY == endY) { - if (startX > endX) { - return new Direction[] { Direction.WEST }; - } - return new Direction[] { Direction.EAST }; - } else { - if (startX < endX && startY < endY) { - return new Direction[] { Direction.NORTH_EAST, Direction.EAST, Direction.NORTH }; - } else if (startX < endX && startY > endY) { - return new Direction[] { Direction.SOUTH_EAST, Direction.EAST, Direction.SOUTH }; - } else if (startX > endX && startY < endY) { - return new Direction[] { Direction.NORTH_WEST, Direction.WEST, Direction.NORTH }; - } else if (startX > endX && startY > endY) { - return new Direction[] { Direction.SOUTH_WEST, Direction.WEST, Direction.SOUTH }; - } - } - return new Direction[0]; - } -} \ No newline at end of file diff --git a/Server/src/main/core/game/world/map/path/RsmodPathfinder.kt b/Server/src/main/core/game/world/map/path/RsmodPathfinder.kt new file mode 100644 index 000000000..b6409eb6c --- /dev/null +++ b/Server/src/main/core/game/world/map/path/RsmodPathfinder.kt @@ -0,0 +1,186 @@ +package core.game.world.map.path + +import core.ServerConstants +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() { + + private val defaultFinder = ThreadLocal.withInitial { + PathFinder(RegionManager.RSMOD_CLIPPING_FLAGS, SEARCH_MAP_SIZE, RING_BUFFER_SIZE) + } + private val suppliedFinder = ThreadLocal.withInitial { RouteFinderState() } + + override fun find( + start: Location?, + moverSize: Int, + dest: Location?, + sizeX: Int, + sizeY: Int, + rotation: Int, + type: Int, + walkingFlag: Int, + near: Boolean, + clipMaskSupplier: ClipMaskSupplier? + ): Path { + val source = requireNotNull(start) + val destination = requireNotNull(dest) + val path = Path() + var end = destination + val vector = Vector.betweenLocs(source, destination) + val magnitude = floor(vector.magnitude()) + + if (magnitude > ServerConstants.MAX_PATHFIND_DISTANCE) { + if (magnitude < 50.0) { + end = source.transform(vector.normalized() * (ServerConstants.MAX_PATHFIND_DISTANCE - 1)) + } else { + path.isMoveNear = true + return path + } + } + + 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 route = finder.findPath( + level = source.z, + srcX = source.x, + srcZ = source.y, + destX = end.x, + destZ = end.y, + srcSize = moverSize, + destWidth = if (sizeX == 0) 1 else sizeX, + destHeight = if (sizeY == 0) 1 else sizeY, + objRot = rotation, + objShape = shape, + moveNear = near, + blockAccessFlags = walkingFlag, + maxWaypoints = maxWaypoints + ) + + if (route.failed) { + return path + } + + for (waypoint in route.waypoints) { + path.points.add(Point(waypoint.x, waypoint.z)) + } + path.setSuccesful(true) + path.isMoveNear = route.alternative || end != destination + 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 + ) + } + + @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 + ) { + 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) { + for (y in baseY until baseY + SEARCH_MAP_SIZE) { + flags[x, y, start.z] = supplier.getClippingFlag(start.z, x, y) + } + } + } + } + + 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/main/core/game/world/map/path/RsmodProjectilePathfinder.kt b/Server/src/main/core/game/world/map/path/RsmodProjectilePathfinder.kt new file mode 100644 index 000000000..1169f875d --- /dev/null +++ b/Server/src/main/core/game/world/map/path/RsmodProjectilePathfinder.kt @@ -0,0 +1,39 @@ +package core.game.world.map.path + +import core.game.world.map.Location +import core.game.world.map.Point + +class RsmodProjectilePathfinder : Pathfinder() { + override fun find( + start: Location?, + size: Int, + end: Location?, + sizeX: Int, + sizeY: Int, + rotation: Int, + type: Int, + walkingFlag: Int, + near: Boolean, + clipMaskSupplier: ClipMaskSupplier? + ): Path { + val source = requireNotNull(start) + val destination = requireNotNull(end) + val rayCast = RsmodPathfinder.lineOfSight( + start = source, + dest = destination, + moverSize = size, + destWidth = sizeX, + destHeight = sizeY + ) + val path = Path() + if (!rayCast.success) { + path.isMoveNear = rayCast.alternative + return path + } + for (coordinate in rayCast.coordinates) { + path.points.add(Point(coordinate.x, coordinate.z)) + } + path.setSuccesful(true) + return path + } +} diff --git a/Server/src/main/core/game/world/map/path/SmartPathfinder.kt b/Server/src/main/core/game/world/map/path/SmartPathfinder.kt deleted file mode 100644 index 1087d318b..000000000 --- a/Server/src/main/core/game/world/map/path/SmartPathfinder.kt +++ /dev/null @@ -1,598 +0,0 @@ -package core.game.world.map.path - -import core.game.world.GameWorld -import core.game.world.map.Direction -import core.game.world.map.Location -import core.game.world.map.Point -import core.tools.* -import core.api.* -import core.api.utils.Vector -import core.ServerConstants - -import java.util.Comparator -import java.util.PriorityQueue - -import java.io.* -import javax.imageio.ImageIO -import java.awt.image.BufferedImage - -class SmartPathfinder -/** - * Constructs a new `SmartPathfinder` `Object`. - */ -internal constructor() : Pathfinder() { - /** - * The x-queue. - */ - private var queueX: IntArray = intArrayOf(0) - - /** - * The y-queue. - */ - private var queueY: IntArray = intArrayOf(0) - - /** - * The "via" array. - */ - private var via: Array = Array(104) { IntArray(104) } - - /** - * The cost array. - */ - private var cost: Array = Array(104) { IntArray(104) } - - /** - * The current writing position. - */ - private var writePathPosition = 0 - - /** - * The current x-coordinate. - */ - private var curX = 0 - - /** - * The current y-coordinate. - */ - private var curY = 0 - - /** - * The destination x-coordinate. - */ - private var dstX = 0 - - /** - * The destination y-coordinate. - */ - private var dstY = 0 - - /** - * If a path was found. - */ - 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. - */ - fun reset() { - queueX = IntArray(4096) - queueY = IntArray(4096) - via = Array(104) { IntArray(104) } - cost = Array(104) { IntArray(104) } - writePathPosition = 0 - } - - /** - * Checks a tile. - * @param x The x-coordinate. - * @param y The y-coordinate. - * @param dir The direction. - * @param currentCost The current cost. - */ - fun check(x: Int, y: Int, dir: Int, currentCost: Int, diagonalPenalty: Int = 0) { - if(cost[x][y] > currentCost + diagonalPenalty) { - queueX[writePathPosition] = x - queueY[writePathPosition] = y - via[x][y] = dir - cost[x][y] = currentCost + diagonalPenalty - writePathPosition = writePathPosition + 1 and 0xfff - } - } - - 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() - val startLoc = requireNotNull(start) - var end = requireNotNull(dest) - var vec = Vector.betweenLocs(startLoc, end) - var mag = kotlin.math.floor(vec.magnitude()) - if (mag > ServerConstants.MAX_PATHFIND_DISTANCE) { - try { - if (canAttempt(startLoc, end)) { //truncate the path if it's realistically long - vec = vec.normalized() * (ServerConstants.MAX_PATHFIND_DISTANCE - 1) - 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) - e.printStackTrace(pw) - log(this::class.java, Log.FINE, sw.toString()) - val p = Path() - p.isMoveNear = true - return p - } - } - val path = Path() - foundPath = false - for (x in 0..103) { - for (y in 0..103) { - via[x][y] = 0 - cost[x][y] = 99999999 - } - } - 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) - try { - if (moverSize < 2) { - if(GameWorld.settings?.smartpathfinder_bfs ?: false) { - checkSingleTraversal(end, sizeX, sizeY, type, rotation, walkingFlag, location, clipMaskSupplier!!) - } else { - checkSingleTraversalAstar(end, sizeX, sizeY, type, rotation, walkingFlag, location, clipMaskSupplier!!) - } - } else if (moverSize == 2) { - checkDoubleTraversal(end, sizeX, sizeY, type, rotation, walkingFlag, location, clipMaskSupplier!!) - } else { - checkVariableTraversal(end, moverSize, sizeX, sizeY, type, rotation, walkingFlag, location, clipMaskSupplier!!) - } - } catch (e: Exception) {} - var debugImg = if(false) { BufferedImage(4*104+2, 104, BufferedImage.TYPE_INT_RGB) } else { null } - if(debugImg != null) { - for(y in 0 until 104) { - for(x in 0 until 104) { - debugImg.setRGB(x, 103-y, via[x][y] * (((1 shl 24)-1)/12)) - val c = Math.min(4*Math.min(cost[x][y], 64), 255) - debugImg.setRGB(105+x, 103-y, (c shl 16) or (c shl 8) or c) - debugImg.setRGB(2*105+x, 103-y, clipMaskSupplier!!.getClippingFlag(location.z, location.x + x, location.y + y)) - } - } - } - - if (!foundPath) { - if (near) { - var fullCost = 1000 - var thisCost = 100 - val depth = 10 - for (x in dstX - depth..dstX + depth) { - for (y in dstY - depth..dstY + depth) { - if (x >= 0 && y >= 0 && x < 104 && y < 104 && cost[x][y] < 100) { - var diffX = 0 - if (x < dstX) { - diffX = dstX - x - } else if (x > dstX + sizeX - 1) { - diffX = x - (dstX + sizeX - 1) - } - var diffY = 0 - if (y < dstY) { - diffY = dstY - y - } else if (y > dstY + sizeY - 1) { - diffY = y - (dstY + sizeY - 1) - } - val totalCost = diffX * diffX + diffY * diffY - if (totalCost < fullCost || totalCost == fullCost && cost[x][y] < thisCost) { - fullCost = totalCost - thisCost = cost[x][y] - curX = x - curY = y - } - } - } - } - if (fullCost == 1000) { - return path - } - path.isMoveNear = true - } - } - readPosition = 0 - queueX[readPosition] = curX - queueY[readPosition++] = curY - var previousDirection: Int - attempts = 0 - var directionFlag = via[curX][curY].also { previousDirection = it } - while (curX != start.sceneX || curY != start.sceneY) { - if (++attempts > queueX.size) { - return path - } - previousDirection = directionFlag - queueX[readPosition] = curX - queueY[readPosition++] = curY - if (directionFlag and WEST_FLAG != 0) { - curX++ - } else if (directionFlag and EAST_FLAG != 0) { - curX-- - } - if (directionFlag and SOUTH_FLAG != 0) { - curY++ - } else if (directionFlag and NORTH_FLAG != 0) { - curY-- - } - if(debugImg != null) { - debugImg.setRGB(3*105+curX, 103-curY, 0x0000ff) - } - directionFlag = via[curX][curY] - } - if(debugImg != null) { - debugImg.setRGB(3*105+start.sceneX, 103-start.sceneY, 0xff0000) - debugImg.setRGB(3*105+dstX, 103-dstY, 0x00ff00) - if(GameWorld.settings?.smartpathfinder_bfs ?: false) { - ImageIO.write(debugImg, "png", File(String.format("bfs_%04d_%04d_%04d_%04d.png", start.x, start.y, end.x, end.y))) - } else { - ImageIO.write(debugImg, "png", File(String.format("astar_%04d_%04d_%04d_%04d.png", start.x, start.y, end.x, end.y))) - } - } - val size = readPosition-- - var absX = location.x + queueX[readPosition] - var absY = location.y + queueY[readPosition] - path.points.add(Point(absX, absY)) - for (i in 1 until size) { - readPosition-- - absX = location.x + queueX[readPosition] - absY = location.y + queueY[readPosition] - path.points.add(Point(absX, absY)) - } - path.setSuccesful(true) - if (end != dest) - path.isMoveNear = true - return path - } - - class UIntAsPointComparator(val end: Location) : Comparator { - override fun compare(p: UInt, q: UInt): Int { - val pc: UInt = (p and 0x00ff0000u) shr 16 - val px: UInt = (p and 0x0000ff00u) shr 8 - val py: UInt = (p and 0x000000ffu) - val qc: UInt = (q and 0x00ff0000u) shr 16 - val qx: UInt = (q and 0x0000ff00u) shr 8 - val qy: UInt = (q and 0x000000ffu) - //val dp = pc.toInt() + Math.abs(end.sceneX - (px.toInt())) + Math.abs(end.sceneY - (py.toInt())) - //val dq = qc.toInt() + Math.abs(end.sceneX - (qx.toInt())) + Math.abs(end.sceneY - (qy.toInt())) - val dp = pc.toDouble() + Math.max(Math.abs(end.sceneX - px.toInt()), Math.abs(end.sceneY - py.toInt())).toDouble() - val dq = qc.toDouble() + Math.max(Math.abs(end.sceneX - qx.toInt()), Math.abs(end.sceneY - qy.toInt())).toDouble() - if(dp < dq) { - return -1 - } else if(dq < dp) { - return 1 - } else { - return 0 - } - } - override fun equals(other: Any?): Boolean { - if(other is UIntAsPointComparator) { - return end == other.end - } else { - return false - } - } - override fun hashCode(): Int { - return end.hashCode() - } - } - - private fun checkSingleTraversalAstar(end: Location, sizeX: Int, sizeY: Int, type: Int, rotation: Int, walkingFlag: Int, location: Location, clipMaskSupplier: ClipMaskSupplier) { - val z = location.z - var queue = PriorityQueue(4096, UIntAsPointComparator(end)) - queue.add(((curX.toUInt()) shl 8) or (curY.toUInt())) - while(!foundPath && !queue.isEmpty()) { - val point = queue.poll() - val curCost = ((point and 0xff0000u) shr 16).toInt() - curX = ((point and 0x0000ff00u) shr 8).toInt() - curY = (point and 0x000000ffu).toInt() - val absX = location.x + curX - val absY = location.y + curY - if (curX == dstX && curY == dstY) { - foundPath = true - break - } - if (type != 0) { - if ((type < 5 || type == 10) && canDoorInteract(absX, absY, 1, end.x, end.y, type - 1, rotation, z, clipMaskSupplier)) { - foundPath = true - break - } - if (type < 10 && canDecorationInteract(absX, absY, 1, end.x, end.y, type - 1, rotation, z, clipMaskSupplier)) { - foundPath = true - break - } - } - if (sizeX != 0 && sizeY != 0 && canInteract(absX, absY, 1, end.x, end.y, sizeX, sizeY, walkingFlag, z, clipMaskSupplier)) { - foundPath = true - break - } - val newCost = curCost + 1 - //val orthogonalsFirst = arrayOf(Direction.EAST, Direction.NORTH, Direction.WEST, Direction.SOUTH, Direction.NORTH_EAST, Direction.NORTH_WEST, Direction.SOUTH_WEST, Direction.SOUTH_EAST) - val orthogonalsFirst = arrayOf(Direction.SOUTH, Direction.WEST, Direction.NORTH, Direction.EAST, Direction.SOUTH_WEST, Direction.NORTH_WEST, Direction.SOUTH_EAST, Direction.NORTH_EAST) - //val orthogonalsFirst = arrayOf(Direction.SOUTH, Direction.WEST, Direction.NORTH, Direction.EAST) - //for(dir in Direction.values()) { - for(dir in orthogonalsFirst) { - val newSceneX: Int = curX + dir.stepX - val newSceneY: Int = curY + dir.stepY - if(0 <= newSceneX && newSceneX < 104 && 0 <= newSceneY && newSceneY < 104 && via[newSceneX][newSceneY] == 0) { - if(dir.canMoveFrom(z, absX, absY, clipMaskSupplier)) { - val diagonalPenalty = Math.abs(dir.stepX) + Math.abs(dir.stepY) - 1 - val flag = flagForDirection(dir) - check(newSceneX, newSceneY, flag, newCost, diagonalPenalty) - if(via[newSceneX][newSceneY] == flag) { - queue.add(((newCost + diagonalPenalty).toUInt() shl 16) or (newSceneX.toUInt() shl 8) or newSceneY.toUInt()) - } - } - } - } - } - } - - /** - * Checks possible traversal for a size 1 entity. - * @param end The destination location. - * @param sizeX The x-size of the destination. - * @param sizeY The y-size of the destination. - * @param type The object type. - * @param rotation The object rotation. - * @param walkingFlag The walking flag. - * @param location The viewport location. - */ - private fun checkSingleTraversal(end: Location, sizeX: Int, sizeY: Int, type: Int, rotation: Int, walkingFlag: Int, location: Location, clipMaskSupplier: ClipMaskSupplier) { - var readPosition = 0 - val z = location.z - while (writePathPosition != readPosition) { - curX = queueX[readPosition] - curY = queueY[readPosition] - readPosition = readPosition + 1 and 0xfff - if (curX == dstX && curY == dstY) { - foundPath = true - break - } - try { - val absX = location.x + curX - val absY = location.y + curY - if (type != 0) { - if ((type < 5 || type == 10) && canDoorInteract(absX, absY, 1, end.x, end.y, type - 1, rotation, z, clipMaskSupplier)) { - foundPath = true - break - } - if (type < 10 && canDecorationInteract(absX, absY, 1, end.x, end.y, type - 1, rotation, z, clipMaskSupplier)) { - foundPath = true - break - } - } - if (sizeX != 0 && sizeY != 0 && canInteract(absX, absY, 1, end.x, end.y, sizeX, sizeY, walkingFlag, z, clipMaskSupplier)) { - foundPath = true - break - } - val thisCost = cost[curX][curY] + 1 - if (curY > 0 && via[curX][curY - 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX, absY - 1) and 0x12c0102 == 0) { - check(curX, curY - 1, SOUTH_FLAG, thisCost) - } - if (curX > 0 && via[curX - 1][curY] == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY) and 0x12c0108 == 0) { - check(curX - 1, curY, WEST_FLAG, thisCost) - } - if (curY < 103 && via[curX][curY + 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX, absY + 1) and 0x12c0120 == 0) { - check(curX, curY + 1, NORTH_FLAG, thisCost) - } - if (curX < 103 && via[curX + 1][curY] == 0 && clipMaskSupplier.getClippingFlag(z, absX + 1, absY) and 0x12c0180 == 0) { - check(curX + 1, curY, EAST_FLAG, thisCost) - } - if (curX > 0 && curY > 0 && via[curX - 1][curY - 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY - 1) and 0x12c010e == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY) and 0x12c0108 == 0 && clipMaskSupplier.getClippingFlag(z, absX, absY - 1) and 0x12c0102 == 0) { - check(curX - 1, curY - 1, SOUTH_WEST_FLAG, thisCost) - } - if (curX > 0 && curY < 103 && via[curX - 1][curY + 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY + 1) and 0x12c0138 == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY) and 0x12c0108 == 0 && clipMaskSupplier.getClippingFlag(z, absX, absY + 1) and 0x12c0120 == 0) { - check(curX - 1, curY + 1, NORTH_WEST_FLAG, thisCost) - } - if (curX < 103 && curY > 0 && via[curX + 1][curY - 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX + 1, absY - 1) and 0x12c0183 == 0 && clipMaskSupplier.getClippingFlag(z, absX + 1, absY) and 0x12c0180 == 0 && clipMaskSupplier.getClippingFlag(z, absX, absY - 1) and 0x12c0102 == 0) { - check(curX + 1, curY - 1, SOUTH_EAST_FLAG, thisCost) - } - if (curX < 103 && curY < 103 && via[curX + 1][curY + 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX + 1, absY + 1) and 0x12c01e0 == 0 && clipMaskSupplier.getClippingFlag(z, absX + 1, absY) and 0x12c0180 == 0 && clipMaskSupplier.getClippingFlag(z, absX, absY + 1) and 0x12c0120 == 0) { - check(curX + 1, curY + 1, NORTH_EAST_FLAG, thisCost) - } - } catch (e: Exception) { - // e.printStackTrace()println("curX " + curX + " curY" + curY + " via " + via[curX + 1] + via[curY + 1]) - } - } - } - - /** - * Checks possible traversal for a size 2 entity. - * @param end The destination location. - * @param sizeX The x-size of the destination. - * @param sizeY The y-size of the destination. - * @param type The object type. - * @param rotation The object rotation. - * @param walkingFlag The walking flag. - * @param location The viewport location. - */ - private fun checkDoubleTraversal(end: Location, sizeX: Int, sizeY: Int, type: Int, rotation: Int, walkingFlag: Int, location: Location, clipMaskSupplier: ClipMaskSupplier) { - var readPosition = 0 - val z = location.z - while (writePathPosition != readPosition) { - curX = queueX[readPosition] - curY = queueY[readPosition] - readPosition = readPosition + 1 and 0xfff - if (curX == dstX && curY == dstY) { - foundPath = true - break - } - val absX = location.x + curX - val absY = location.y + curY - if (type != 0) { - if ((type < 5 || type == 10) && canDoorInteract(absX, absY, 2, end.x, end.y, type - 1, rotation, z, clipMaskSupplier)) { - foundPath = true - break - } - if (type < 10 && canDecorationInteract(absX, absY, 2, end.x, end.y, type - 1, rotation, z, clipMaskSupplier)) { - foundPath = true - break - } - } - if (sizeX != 0 && sizeY != 0 && canInteract(absX, absY, 2, end.x, end.y, sizeX, sizeY, walkingFlag, z, clipMaskSupplier)) { - foundPath = true - break - } - val thisCost = cost[curX][curY] + 1 - if (curY > 0 && via[curX][curY - 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX, absY - 1) and 0x12c010e == 0 && clipMaskSupplier.getClippingFlag(z, absX + 1, absY - 1) and 0x12c0183 == 0) { - check(curX, curY - 1, SOUTH_FLAG, thisCost) - } - if (curX > 0 && via[curX - 1][curY] == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY) and 0x12c010e == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY + 1) and 0x12c0138 == 0) { - check(curX - 1, curY, WEST_FLAG, thisCost) - } - if (curY < 102 && via[curX][curY + 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX, absY + 2) and 0x12c0138 == 0 && clipMaskSupplier.getClippingFlag(z, absX + 1, absY + 2) and 0x12c01e0 == 0) { - check(curX, curY + 1, NORTH_FLAG, thisCost) - } - if (curX < 102 && via[curX + 1][curY] == 0 && clipMaskSupplier.getClippingFlag(z, absX + 2, absY) and 0x12c0183 == 0 && clipMaskSupplier.getClippingFlag(z, absX + 2, absY + 1) and 0x12c01e0 == 0) { - check(curX + 1, curY, EAST_FLAG, thisCost) - } - if (curX > 0 && curY > 0 && via[curX - 1][curY - 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY - 1) and 0x12c010e == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY) and 0x12c0138 == 0 && clipMaskSupplier.getClippingFlag(z, absX, absY - 1) and 0x12c0183 == 0) { - check(curX - 1, curY - 1, SOUTH_WEST_FLAG, thisCost) - } - if (curX > 0 && curY < 102 && via[curX - 1][curY + 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY + 1) and 0x12c010e == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY + 2) and 0x12c0138 == 0 && clipMaskSupplier.getClippingFlag(z, absX, absY + 2) and 0x12c01e0 == 0) { - check(curX - 1, curY + 1, NORTH_WEST_FLAG, thisCost) - } - if (curX < 102 && curY > 0 && via[curX + 1][curY - 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX + 1, absY - 1) and 0x12c010e == 0 && clipMaskSupplier.getClippingFlag(z, absX + 2, absY) and 0x12c01e0 == 0 && clipMaskSupplier.getClippingFlag(z, absX + 2, absY - 1) and 0x12c0183 == 0) { - check(curX + 1, curY - 1, SOUTH_EAST_FLAG, thisCost) - } - if (curX < 102 && curY < 102 && via[curX + 1][curY + 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX + 1, absY + 2) and 0x12c0138 == 0 && clipMaskSupplier.getClippingFlag(z, absX + 2, absY + 2) and 0x12c01e0 == 0 && clipMaskSupplier.getClippingFlag(z, absX + 2, absY + 1) and 0x12c0183 == 0) { - check(curX + 1, curY + 1, NORTH_EAST_FLAG, thisCost) - } - } - } - - /** - * Checks possible traversal for any sized entity. - * @param end The destination location. - * @param size The mover size. - * @param sizeX The x-size of the destination. - * @param sizeY The y-size of the destination. - * @param type The object type. - * @param rotation The object rotation. - * @param walkingFlag The walking flag. - * @param location The viewport location. - */ - private fun checkVariableTraversal(end: Location, size: Int, sizeX: Int, sizeY: Int, type: Int, rotation: Int, walkingFlag: Int, location: Location, clipMaskSupplier: ClipMaskSupplier) { - var readPosition = 0 - val z = location.z - main@ while (writePathPosition != readPosition) { - curX = queueX[readPosition] - curY = queueY[readPosition] - readPosition = readPosition + 1 and 0xfff - if (curX == dstX && curY == dstY) { - foundPath = true - break - } - val absX = location.x + curX - val absY = location.y + curY - if (type != 0) { - if ((type < 5 || type == 10) && canDoorInteract(absX, absY, size, end.x, end.y, type - 1, rotation, z, clipMaskSupplier)) { - foundPath = true - break - } - if (type < 10 && canDecorationInteract(absX, absY, size, end.x, end.y, type - 1, rotation, z, clipMaskSupplier)) { - foundPath = true - break - } - } - if (sizeX != 0 && sizeY != 0 && canInteract(absX, absY, size, end.x, end.y, sizeX, sizeY, walkingFlag, z, clipMaskSupplier)) { - foundPath = true - break - } - val thisCost = cost[curX][curY] + 1 - south@ do { - if (curY > 0 && via[curX][curY - 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX, absY - 1) and 0x12c010e == 0 && clipMaskSupplier.getClippingFlag(z, absX + (size - 1), absY - 1) and 0x12c0183 == 0) { - for (i in 1 until size - 1) { - if (clipMaskSupplier.getClippingFlag(z, absX + i, absY - 1) and 0x12c018f != 0) { - break@south - } - } - check(curX, curY - 1, SOUTH_FLAG, thisCost) - } - } while (false) - west@ do { - if (curX > 0 && via[curX - 1][curY] == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY) and 0x12c010e == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY + (size - 1)) and 0x12c0138 == 0) { - for (i in 1 until size - 1) { - if (clipMaskSupplier.getClippingFlag(z, absX - 1, absY + i) and 0x12c013e != 0) { - break@west - } - } - check(curX - 1, curY, WEST_FLAG, thisCost) - } - } while (false) - north@ do { - if (curY < 102 && via[curX][curY + 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX, absY + size) and 0x12c0138 == 0 && clipMaskSupplier.getClippingFlag(z, absX + (size - 1), absY + size) and 0x12c01e0 == 0) { - for (i in 1 until size - 1) { - if (clipMaskSupplier.getClippingFlag(z, absX + i, absY + size) and 0x12c01f8 != 0) { - break@north - } - } - check(curX, curY + 1, NORTH_FLAG, thisCost) - } - } while (false) - east@ do { - if (curX < 102 && via[curX + 1][curY] == 0 && clipMaskSupplier.getClippingFlag(z, absX + size, absY) and 0x12c0183 == 0 && clipMaskSupplier.getClippingFlag(z, absX + size, absY + (size - 1)) and 0x12c01e0 == 0) { - for (i in 1 until size - 1) { - if (clipMaskSupplier.getClippingFlag(z, absX + size, absY + i) and 0x12c01e3 != 0) { - break@east - } - } - check(curX + 1, curY, EAST_FLAG, thisCost) - } - } while (false) - southWest@ do { - if (curX > 0 && curY > 0 && via[curX - 1][curY - 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY + (size - 2)) and 0x12c0138 == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY - 1) and 0x12c010e == 0 && clipMaskSupplier.getClippingFlag(z, absX + (size - 2), absY - 1) and 0x12c0183 == 0) { - for (i in 1 until size - 1) { - if (clipMaskSupplier.getClippingFlag(z, absX - 1, absY + (i - 1)) and 0x12c013e != 0 || clipMaskSupplier.getClippingFlag(z, absX + (i - 1), absY - 1) and 0x12c018f != 0) { - break@southWest - } - } - check(curX - 1, curY - 1, SOUTH_WEST_FLAG, thisCost) - } - } while (false) - northWest@ do { - if (curX > 0 && curY < 102 && via[curX - 1][curY + 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY + 1) and 0x12c010e == 0 && clipMaskSupplier.getClippingFlag(z, absX - 1, absY + size) and 0x12c0138 == 0 && clipMaskSupplier.getClippingFlag(z, absX, absY + size) and 0x12c01e0 == 0) { - for (i in 1 until size - 1) { - if (clipMaskSupplier.getClippingFlag(z, absX - 1, absY + (i + 1)) and 0x12c013e != 0 || clipMaskSupplier.getClippingFlag(z, absX + (i - 1), absY + size) and 0x12c01f8 != 0) { - break@northWest - } - } - check(curX - 1, curY + 1, NORTH_WEST_FLAG, thisCost) - } - } while (false) - southEast@ do { - if (curX < 102 && curY > 0 && via[curX + 1][curY - 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX + 1, absY - 1) and 0x12c010e == 0 && clipMaskSupplier.getClippingFlag(z, absX + size, absY - 1) and 0x12c0183 == 0 && clipMaskSupplier.getClippingFlag(z, absX + size, absY + (size - 2)) and 0x12c01e0 == 0) { - for (i in 1 until size - 1) { - if (clipMaskSupplier.getClippingFlag(z, absX + size, absY + (i - 1)) and 0x12c01e3 != 0 || clipMaskSupplier.getClippingFlag(z, absX + (i + 1), absY - 1) and 0x12c018f != 0) { - break@southEast - } - } - check(curX + 1, curY - 1, SOUTH_EAST_FLAG, thisCost) - } - } while (false) - if (curX < 102 && curY < 102 && via[curX + 1][curY + 1] == 0 && clipMaskSupplier.getClippingFlag(z, absX + 1, absY + size) and 0x12c0138 == 0 && clipMaskSupplier.getClippingFlag(z, absX + size, absY + size) and 0x12c01e0 == 0 && clipMaskSupplier.getClippingFlag(z, absX + size, absY + 1) and 0x12c0183 == 0) { - for (i in 1 until size - 1) { - if (clipMaskSupplier.getClippingFlag(z, absX + (i + 1), absY + size) and 0x12c01f8 != 0 || clipMaskSupplier.getClippingFlag(z, absX + size, absY + (i + 1)) and 0x12c01e3 != 0) { - continue@main - } - } - check(curX + 1, curY + 1, NORTH_EAST_FLAG, thisCost) - } - } - } -} diff --git a/Server/src/main/core/game/worldevents/holiday/easter/EasterEvent.kt b/Server/src/main/core/game/worldevents/holiday/easter/EasterEvent.kt index 6b129b906..7dcae1c12 100644 --- a/Server/src/main/core/game/worldevents/holiday/easter/EasterEvent.kt +++ b/Server/src/main/core/game/worldevents/holiday/easter/EasterEvent.kt @@ -296,7 +296,9 @@ class EasterEvent : WorldEvent("easter"), TickListener, InteractionListener, Log val dir = dirs[RandomFunction.random(dirs.size)] var loc = player.location.transform(dir, 3) val path = Pathfinder.find(player, loc) - loc = Location.create(path.points.last.x, path.points.last.y, loc.z) + path.points.lastOrNull()?.let { + loc = Location.create(it.x, it.y, loc.z) + } GroundItemManager.create(Item(eggs.random()), loc, player) sendMessage(player, colorize("%RAn egg has appeared nearby.")) } @@ -317,4 +319,4 @@ class EasterEvent : WorldEvent("easter"), TickListener, InteractionListener, Log WeightedItem(Items.DRAGON_IMPLING_JAR_11256, 1, 1, 0.005) ) } -} \ No newline at end of file +} diff --git a/Server/src/test/kotlin/core/PathfinderTests.kt b/Server/src/test/kotlin/core/PathfinderTests.kt index c30c69402..fb6f31ac6 100644 --- a/Server/src/test/kotlin/core/PathfinderTests.kt +++ b/Server/src/test/kotlin/core/PathfinderTests.kt @@ -1,6 +1,7 @@ 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 @@ -17,21 +18,24 @@ 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.game.world.map.path.ClipMaskSupplier +import core.game.world.map.path.Pathfinder import core.net.packet.PacketProcessor 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() }; 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))) + companion object { + init { + TestUtils.preTestSetup() + GatheringSkillOptionListeners().defineListeners() + WoodcuttingListener().defineListeners() + BankBoothListener().defineListeners() + } + val NPC_TEST_LOC = ServerConstants.HOME_LOCATION!!.transform(2, 10, 0) } @Test fun getOccupiedTilesShouldReturnCorrectSetOfTilesThatAnObjectOccupiesAtAllRotations() { @@ -55,6 +59,166 @@ class PathfinderTests { Assertions.assertArrayEquals(arrayOf(Location.create(50,50), Location.create(49,50)), occupiedAt3) } + @Test fun smartPathfinderShouldRespectSuppliedClipMask() { + val start = Location.create(3200, 3200, 0) + val dest = Location.create(3202, 3200, 0) + val blockedDestination = ClipMaskSupplier { _, x, y -> + if (x == dest.x && y == dest.y) 0x100 else 0 + } + + val path = Pathfinder.SMART.find(start, 1, dest, 1, 1, 0, 0, 0, true, blockedDestination) + + Assertions.assertTrue(path.isSuccessful) + Assertions.assertEquals( + Location.create(3201, 3200, 0), + Location.create(path.points.last.x, path.points.last.y, 0) + ) + } + + @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)