RSMod pathfinder port

This commit is contained in:
dam 2026-05-05 21:25:56 +03:00
parent dd56c211de
commit 0c196aa9e3
No known key found for this signature in database
GPG key ID: 4AF4E722399663FB
16 changed files with 502 additions and 1776 deletions

View file

@ -63,6 +63,12 @@
<version>[1.4.0,)</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.rsmod</groupId>
<artifactId>rsmod-pathfinder</artifactId>
<version>4.2.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>

View file

@ -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;
}

View file

@ -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

View file

@ -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
}
/**

View file

@ -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)

View file

@ -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

View file

@ -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<Int, Region> = HashMap()
private val RSMOD_PROJECTILE_REGIONS = HashSet<Int>()
@JvmStatic val CLIPPING_FLAGS = HashMap<Int, Array<Int>>()
@JvmStatic val PROJECTILE_FLAGS = HashMap<Int, Array<Int>>()
@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<Int> {
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)
}
}
}
}
/**

View file

@ -179,7 +179,9 @@ public final class RegionFlags {
public void addFlag(int x, int y, int clipdata) {
int current = getFlag(x, y);
Pair<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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;
}
}
}

View file

@ -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.. <br> 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<Point> 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<Point> 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<Point> 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<Point> 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];
}
}

View file

@ -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);
}
}

View file

@ -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<Point> 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<Point> 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];
}
}

View file

@ -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)
}
}
}

View file

@ -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
}
}

View file

@ -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<IntArray> = Array(104) { IntArray(104) }
/**
* The cost array.
*/
private var cost: Array<IntArray> = 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<UInt> {
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)
}
}
}
}

View file

@ -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)
)
}
}
}

View file

@ -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<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)