Add pathfinding API
This commit is contained in:
@@ -140,6 +140,14 @@ public interface Tile extends Locatable
|
||||
*/
|
||||
boolean hasLineOfSightTo(Tile other);
|
||||
|
||||
/**
|
||||
* Computes and returns the path from this tile to another.
|
||||
*
|
||||
* @param other the other tile
|
||||
* @return the checkpoint tiles of the path, or null if no path found
|
||||
*/
|
||||
List<Tile> pathTo(Tile other);
|
||||
|
||||
/**
|
||||
* Get all the ground items for this tile
|
||||
*
|
||||
|
||||
@@ -34,6 +34,7 @@ import net.runelite.api.Client;
|
||||
import static net.runelite.api.Constants.CHUNK_SIZE;
|
||||
import static net.runelite.api.Constants.REGION_SIZE;
|
||||
import net.runelite.api.Perspective;
|
||||
import net.runelite.api.Tile;
|
||||
|
||||
/**
|
||||
* A three-dimensional point representing the coordinate of a Tile.
|
||||
@@ -436,4 +437,90 @@ public class WorldPoint
|
||||
{
|
||||
return position & (REGION_SIZE - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the checkpoint tiles of a server-sided path from this WorldPoint to another WorldPoint.
|
||||
* <p>
|
||||
* The checkpoint tiles of a path are the "corner tiles" of a path and determine the path completely.
|
||||
*
|
||||
* Note that true server-sided pathfinding uses collisiondata of the 128x128 area around this WorldPoint,
|
||||
* while the client only has access to collisiondata within the 104x104 loaded area.
|
||||
* This means that the results would differ in case the server's path goes near (or over) the border of the loaded area.
|
||||
*
|
||||
* @param client The client to compare in
|
||||
* @param other The other WorldPoint to compare with
|
||||
* @return Returns the checkpoint tiles of the path
|
||||
*/
|
||||
public List<WorldPoint> pathTo(Client client, WorldPoint other)
|
||||
{
|
||||
if (plane != other.getPlane())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
LocalPoint sourceLp = LocalPoint.fromWorld(client, x, y);
|
||||
LocalPoint targetLp = LocalPoint.fromWorld(client, other.getX(), other.getY());
|
||||
if (sourceLp == null || targetLp == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int thisX = sourceLp.getSceneX();
|
||||
int thisY = sourceLp.getSceneY();
|
||||
int otherX = targetLp.getSceneX();
|
||||
int otherY = targetLp.getSceneY();
|
||||
|
||||
Tile[][][] tiles = client.getScene().getTiles();
|
||||
Tile sourceTile = tiles[plane][thisX][thisY];
|
||||
|
||||
Tile targetTile = tiles[plane][otherX][otherY];
|
||||
List<Tile> checkpointTiles = sourceTile.pathTo(targetTile);
|
||||
if (checkpointTiles == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
List<WorldPoint> checkpointWPs = new ArrayList<>();
|
||||
for (Tile checkpointTile : checkpointTiles)
|
||||
{
|
||||
if (checkpointTile == null) {
|
||||
break;
|
||||
}
|
||||
checkpointWPs.add(checkpointTile.getWorldLocation());
|
||||
}
|
||||
return checkpointWPs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path distance from this point to a WorldPoint.
|
||||
* <p>
|
||||
* If the other point is unreachable, this method will return {@link Integer#MAX_VALUE}.
|
||||
*
|
||||
* @param client
|
||||
* @param other
|
||||
* @return Returns the path distance
|
||||
*/
|
||||
public int distanceToPath(Client client, WorldPoint other)
|
||||
{
|
||||
List<WorldPoint> checkpointWPs = this.pathTo(client, other);
|
||||
if (checkpointWPs == null)
|
||||
{
|
||||
// No path found
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
WorldPoint destinationPoint = checkpointWPs.get(checkpointWPs.size() - 1);
|
||||
if (other.getX() != destinationPoint.getX() || other.getY() != destinationPoint.getY())
|
||||
{
|
||||
// Path found but not to the requested tile
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
WorldPoint Point1 = this;
|
||||
int distance = 0;
|
||||
for (WorldPoint Point2 : checkpointWPs)
|
||||
{
|
||||
distance += Point1.distanceTo2D(Point2);
|
||||
Point1 = Point2;
|
||||
}
|
||||
return distance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +236,258 @@ public abstract class RSTileMixin implements RSTile
|
||||
return true;
|
||||
}
|
||||
|
||||
@Inject
|
||||
@Override
|
||||
public List<Tile> pathTo(Tile other)
|
||||
{
|
||||
|
||||
int z = this.getPlane();
|
||||
if (z != other.getPlane())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
CollisionData[] collisionData = client.getCollisionMaps();
|
||||
if (collisionData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int[][] directions = new int[128][128];
|
||||
int[][] distances = new int[128][128];
|
||||
int[] bufferX = new int[4096];
|
||||
int[] bufferY = new int[4096];
|
||||
|
||||
// Initialise directions and distances
|
||||
for (int i = 0; i < 128; ++i)
|
||||
{
|
||||
for (int j = 0; j < 128; ++j)
|
||||
{
|
||||
directions[i][j] = 0;
|
||||
distances[i][j] = Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
Point p1 = this.getSceneLocation();
|
||||
Point p2 = other.getSceneLocation();
|
||||
|
||||
int middleX = p1.getX();
|
||||
int middleY = p1.getY();
|
||||
int currentX = middleX;
|
||||
int currentY = middleY;
|
||||
int offsetX = 64;
|
||||
int offsetY = 64;
|
||||
// Initialise directions and distances for starting tile
|
||||
directions[offsetX][offsetY] = 99;
|
||||
distances[offsetX][offsetY] = 0;
|
||||
int index1 = 0;
|
||||
bufferX[0] = currentX;
|
||||
int index2 = 1;
|
||||
bufferY[0] = currentY;
|
||||
int[][] collisionDataFlags = collisionData[z].getFlags();
|
||||
|
||||
boolean isReachable = false;
|
||||
|
||||
while (index1 != index2)
|
||||
{
|
||||
currentX = bufferX[index1];
|
||||
currentY = bufferY[index1];
|
||||
index1 = index1 + 1 & 4095;
|
||||
// currentX is for the local coordinate while currentMapX is for the index in the directions and distances arrays
|
||||
int currentMapX = currentX - middleX + offsetX;
|
||||
int currentMapY = currentY - middleY + offsetY;
|
||||
if ((currentX == p2.getX()) && (currentY == p2.getY()))
|
||||
{
|
||||
isReachable = true;
|
||||
break;
|
||||
}
|
||||
|
||||
int currentDistance = distances[currentMapX][currentMapY] + 1;
|
||||
if (currentMapX > 0 && directions[currentMapX - 1][currentMapY] == 0 && (collisionDataFlags[currentX - 1][currentY] & 19136776) == 0)
|
||||
{
|
||||
// Able to move 1 tile west
|
||||
bufferX[index2] = currentX - 1;
|
||||
bufferY[index2] = currentY;
|
||||
index2 = index2 + 1 & 4095;
|
||||
directions[currentMapX - 1][currentMapY] = 2;
|
||||
distances[currentMapX - 1][currentMapY] = currentDistance;
|
||||
}
|
||||
|
||||
if (currentMapX < 127 && directions[currentMapX + 1][currentMapY] == 0 && (collisionDataFlags[currentX + 1][currentY] & 19136896) == 0)
|
||||
{
|
||||
// Able to move 1 tile east
|
||||
bufferX[index2] = currentX + 1;
|
||||
bufferY[index2] = currentY;
|
||||
index2 = index2 + 1 & 4095;
|
||||
directions[currentMapX + 1][currentMapY] = 8;
|
||||
distances[currentMapX + 1][currentMapY] = currentDistance;
|
||||
}
|
||||
|
||||
if (currentMapY > 0 && directions[currentMapX][currentMapY - 1] == 0 && (collisionDataFlags[currentX][currentY - 1] & 19136770) == 0)
|
||||
{
|
||||
// Able to move 1 tile south
|
||||
bufferX[index2] = currentX;
|
||||
bufferY[index2] = currentY - 1;
|
||||
index2 = index2 + 1 & 4095;
|
||||
directions[currentMapX][currentMapY - 1] = 1;
|
||||
distances[currentMapX][currentMapY - 1] = currentDistance;
|
||||
}
|
||||
|
||||
if (currentMapY < 127 && directions[currentMapX][currentMapY + 1] == 0 && (collisionDataFlags[currentX][currentY + 1] & 19136800) == 0)
|
||||
{
|
||||
// Able to move 1 tile north
|
||||
bufferX[index2] = currentX;
|
||||
bufferY[index2] = currentY + 1;
|
||||
index2 = index2 + 1 & 4095;
|
||||
directions[currentMapX][currentMapY + 1] = 4;
|
||||
distances[currentMapX][currentMapY + 1] = currentDistance;
|
||||
}
|
||||
|
||||
if (currentMapX > 0 && currentMapY > 0 && directions[currentMapX - 1][currentMapY - 1] == 0 && (collisionDataFlags[currentX - 1][currentY - 1] & 19136782) == 0 && (collisionDataFlags[currentX - 1][currentY] & 19136776) == 0 && (collisionDataFlags[currentX][currentY - 1] & 19136770) == 0)
|
||||
{
|
||||
// Able to move 1 tile south-west
|
||||
bufferX[index2] = currentX - 1;
|
||||
bufferY[index2] = currentY - 1;
|
||||
index2 = index2 + 1 & 4095;
|
||||
directions[currentMapX - 1][currentMapY - 1] = 3;
|
||||
distances[currentMapX - 1][currentMapY - 1] = currentDistance;
|
||||
}
|
||||
|
||||
if (currentMapX < 127 && currentMapY > 0 && directions[currentMapX + 1][currentMapY - 1] == 0 && (collisionDataFlags[currentX + 1][currentY - 1] & 19136899) == 0 && (collisionDataFlags[currentX + 1][currentY] & 19136896) == 0 && (collisionDataFlags[currentX][currentY - 1] & 19136770) == 0)
|
||||
{
|
||||
// Able to move 1 tile north-west
|
||||
bufferX[index2] = currentX + 1;
|
||||
bufferY[index2] = currentY - 1;
|
||||
index2 = index2 + 1 & 4095;
|
||||
directions[currentMapX + 1][currentMapY - 1] = 9;
|
||||
distances[currentMapX + 1][currentMapY - 1] = currentDistance;
|
||||
}
|
||||
|
||||
if (currentMapX > 0 && currentMapY < 127 && directions[currentMapX - 1][currentMapY + 1] == 0 && (collisionDataFlags[currentX - 1][currentY + 1] & 19136824) == 0 && (collisionDataFlags[currentX - 1][currentY] & 19136776) == 0 && (collisionDataFlags[currentX][currentY + 1] & 19136800) == 0)
|
||||
{
|
||||
// Able to move 1 tile south-east
|
||||
bufferX[index2] = currentX - 1;
|
||||
bufferY[index2] = currentY + 1;
|
||||
index2 = index2 + 1 & 4095;
|
||||
directions[currentMapX - 1][currentMapY + 1] = 6;
|
||||
distances[currentMapX - 1][currentMapY + 1] = currentDistance;
|
||||
}
|
||||
|
||||
if (currentMapX < 127 && currentMapY < 127 && directions[currentMapX + 1][currentMapY + 1] == 0 && (collisionDataFlags[currentX + 1][currentY + 1] & 19136992) == 0 && (collisionDataFlags[currentX + 1][currentY] & 19136896) == 0 && (collisionDataFlags[currentX][currentY + 1] & 19136800) == 0)
|
||||
{
|
||||
// Able to move 1 tile north-east
|
||||
bufferX[index2] = currentX + 1;
|
||||
bufferY[index2] = currentY + 1;
|
||||
index2 = index2 + 1 & 4095;
|
||||
directions[currentMapX + 1][currentMapY + 1] = 12;
|
||||
distances[currentMapX + 1][currentMapY + 1] = currentDistance;
|
||||
}
|
||||
}
|
||||
if (!isReachable)
|
||||
{
|
||||
// Try find a different reachable tile in the 21x21 area around the target tile, as close as possible to the target tile
|
||||
int upperboundDistance = Integer.MAX_VALUE;
|
||||
int pathLength = Integer.MAX_VALUE;
|
||||
int checkRange = 10;
|
||||
int approxDestinationX = p2.getX();
|
||||
int approxDestinationY = p2.getY();
|
||||
for (int i = approxDestinationX - checkRange; i <= checkRange + approxDestinationX; ++i)
|
||||
{
|
||||
for (int j = approxDestinationY - checkRange; j <= checkRange + approxDestinationY; ++j)
|
||||
{
|
||||
int currentMapX = i - middleX + offsetX;
|
||||
int currentMapY = j - middleY + offsetY;
|
||||
if (currentMapX >= 0 && currentMapY >= 0 && currentMapX < 128 && currentMapY < 128 && distances[currentMapX][currentMapY] < 100)
|
||||
{
|
||||
int deltaX = 0;
|
||||
if (i < approxDestinationX)
|
||||
{
|
||||
deltaX = approxDestinationX - i;
|
||||
}
|
||||
else if (i > approxDestinationX)
|
||||
{
|
||||
deltaX = i - (approxDestinationX);
|
||||
}
|
||||
|
||||
int deltaY = 0;
|
||||
if (j < approxDestinationY)
|
||||
{
|
||||
deltaY = approxDestinationY - j;
|
||||
}
|
||||
else if (j > approxDestinationY)
|
||||
{
|
||||
deltaY = j - (approxDestinationY);
|
||||
}
|
||||
|
||||
int distanceSquared = deltaX * deltaX + deltaY * deltaY;
|
||||
if (distanceSquared < upperboundDistance || distanceSquared == upperboundDistance && distances[currentMapX][currentMapY] < pathLength)
|
||||
{
|
||||
upperboundDistance = distanceSquared;
|
||||
pathLength = distances[currentMapX][currentMapY];
|
||||
currentX = i;
|
||||
currentY = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (upperboundDistance == Integer.MAX_VALUE)
|
||||
{
|
||||
// No path found
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Getting path from directions and distances
|
||||
bufferX[0] = currentX;
|
||||
bufferY[0] = currentY;
|
||||
int index = 1;
|
||||
int directionNew;
|
||||
int directionOld;
|
||||
for (directionNew = directionOld = directions[currentX - middleX + offsetX][currentY - middleY + offsetY]; p1.getX() != currentX || p1.getY() != currentY; directionNew = directions[currentX - middleX + offsetX][currentY - middleY + offsetY])
|
||||
{
|
||||
if (directionNew != directionOld)
|
||||
{
|
||||
// "Corner" of the path --> new checkpoint tile
|
||||
directionOld = directionNew;
|
||||
bufferX[index] = currentX;
|
||||
bufferY[index++] = currentY;
|
||||
}
|
||||
|
||||
if ((directionNew & 2) != 0)
|
||||
{
|
||||
++currentX;
|
||||
}
|
||||
else if ((directionNew & 8) != 0)
|
||||
{
|
||||
--currentX;
|
||||
}
|
||||
|
||||
if ((directionNew & 1) != 0)
|
||||
{
|
||||
++currentY;
|
||||
}
|
||||
else if ((directionNew & 4) != 0)
|
||||
{
|
||||
--currentY;
|
||||
}
|
||||
}
|
||||
|
||||
int checkpointTileNumber = 1;
|
||||
Tile[][][] tiles = client.getScene().getTiles();
|
||||
List<Tile> checkpointTiles = new ArrayList<>();
|
||||
while (index-- > 0)
|
||||
{
|
||||
checkpointTiles.add(tiles[this.getPlane()][bufferX[index]][bufferY[index]]);
|
||||
if (checkpointTileNumber == 25)
|
||||
{
|
||||
// Pathfinding only supports up to the 25 first checkpoint tiles
|
||||
break;
|
||||
}
|
||||
checkpointTileNumber++;
|
||||
}
|
||||
return checkpointTiles;
|
||||
}
|
||||
|
||||
@Inject
|
||||
@Override
|
||||
public List<TileItem> getGroundItems()
|
||||
|
||||
Reference in New Issue
Block a user