Implemented A* pathfinding for SmartPathfinder

This commit is contained in:
Avi Weinstock
2023-03-26 03:59:07 +00:00
committed by Ryan
parent ffb7486a01
commit b9050c0e3c
8 changed files with 183 additions and 15 deletions
+1 -1
View File
@@ -437,7 +437,7 @@ potential-bugs:
- '*.CanIgnoreReturnValue'
ignoreFunctionCall: []
ImplicitDefaultLocale:
active: true
active: false
ImplicitUnitReturnType:
active: false
allowExplicitReturnType: true
@@ -12,7 +12,7 @@ import core.tools.Log;
import core.tools.SystemLogger;
import java.util.Deque;
import java.util.LinkedList;
import java.util.ArrayDeque;
import static core.api.ContentAPIKt.log;
@@ -25,7 +25,7 @@ public final class WalkingQueue {
/**
* The walking queue.
*/
private final Deque<Point> walkingQueue = new LinkedList<Point>();
private final Deque<Point> walkingQueue = new ArrayDeque<Point>();
/**
* The entity.
@@ -90,6 +90,7 @@ object ServerConfigParser {
enable_doubling_money_scammers = data.getBoolean("world.enable_doubling_money_scammers", false),
wild_pvp_enabled = data.getBoolean("world.wild_pvp_enabled"),
jad_practice_enabled = data.getBoolean("world.jad_practice_enabled"),
smartpathfinder_bfs = data.getBoolean("world.smartpathfinder_bfs", false),
message_model = data.getString("world.motw_identifier").toInt(),
message_string = data.getString("world.motw_text").replace("@name", ServerConstants.SERVER_NAME)
)
@@ -82,6 +82,7 @@ class GameSettings
var enable_doubling_money_scammers: Boolean,
var wild_pvp_enabled: Boolean,
var jad_practice_enabled: Boolean,
var smartpathfinder_bfs: Boolean,
/**"Lobby" interface
* The message of the week models to display
@@ -136,6 +137,7 @@ class GameSettings
val enable_doubling_money_scammers = if(data.containsKey("enable_doubling_money_scammers")) data["enable_doubling_money_scammers"] as Boolean else false
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 smartpathfinder_bfs = if(data.containsKey("smartpathfinder_bfs")) data["smartpathfinder_bfs"] 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()
val message_of_the_week_text = data["message_of_the_week_text"].toString()
@@ -165,6 +167,7 @@ class GameSettings
enable_doubling_money_scammers,
wild_pvp_enabled,
jad_practice_enabled,
smartpathfinder_bfs,
message_of_the_week_identifier,
message_of_the_week_text
)
@@ -188,4 +191,4 @@ class GameSettings
return properties
}
}
}
}
@@ -15,7 +15,9 @@ import kotlin.concurrent.schedule
class ImmerseWorld : StartupListener {
override fun startup() {
spawnBots()
if(GameWorld.settings?.max_adv_bots!! > 0) {
spawnBots()
}
}
companion object {
@@ -1,5 +1,7 @@
package core.game.world.map;
import core.game.world.map.path.ClipMaskSupplier;
/**
* Represents a direction.
* @author Emperor
@@ -254,6 +256,29 @@ public enum Direction {
return true;
}
public boolean canMoveFrom(int z, int x, int y, ClipMaskSupplier clipMaskSupplier) {
int dx, dy;
boolean ret = true;
for (int f : traversal) {
switch(f) {
case 0x12c0120: dx = 0; dy = 1; break; // north
case 0x12c0180: dx = 1; dy = 0; break; // east
case 0x12c01e0: dx = 1; dy = 1; break; // northeast
case 0x12c0102: dx = 0; dy = -1; break; // south
case 0x12c0183: dx = 1; dy = -1; break; // southeast
case 0x12c0108: dx = -1; dy = 0; break; // west
case 0x12c010e: dx = -1; dy = -1; break; // southwest
case 0x12c0138: dx = -1; dy = 1; break; // northwest
default: return false;
}
int flag = clipMaskSupplier.getClippingFlag(z, x+dx, y+dy);
if ((flag & f) != 0) {
ret = false;
}
}
return ret;
}
/**
* Sets the traversal.
* @param traversal The traversal to set.
@@ -261,4 +286,4 @@ public enum Direction {
private void setTraversal(int[] traversal) {
this.traversal = traversal;
}
}
}
@@ -4,6 +4,7 @@ import core.game.node.Node;
import core.game.node.entity.Entity;
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;
@@ -73,6 +74,20 @@ public abstract class Pathfinder {
*/
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;
}
}
/**
* Finds a path from the location to the end location.
* @param location The start location.
@@ -689,4 +704,4 @@ public abstract class Pathfinder {
}
return false;
}
}
}
@@ -1,8 +1,17 @@
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 java.util.Comparator
import java.util.PriorityQueue
import java.io.File
import javax.imageio.ImageIO
import java.awt.image.BufferedImage
class SmartPathfinder
/**
* Constructs a new `SmartPathfinder` `Object`.
@@ -76,12 +85,14 @@ internal constructor() : Pathfinder() {
* @param dir The direction.
* @param currentCost The current cost.
*/
fun check(x: Int, y: Int, dir: Int, currentCost: Int) {
queueX[writePathPosition] = x
queueY[writePathPosition] = y
via[x][y] = dir
cost[x][y] = currentCost
writePathPosition = writePathPosition + 1 and 0xfff
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, end: Location?, sizeX: Int, sizeY: Int, rotation: Int, type: Int, walkingFlag: Int, near: Boolean, clipMaskSupplier: ClipMaskSupplier?): Path {
@@ -106,13 +117,29 @@ internal constructor() : Pathfinder() {
check(curX, curY, 99, 0)
try {
if (moverSize < 2) {
checkSingleTraversal(end, sizeX, sizeY, type, rotation, walkingFlag, location, clipMaskSupplier!!)
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
@@ -174,8 +201,20 @@ internal constructor() : Pathfinder() {
} 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]
@@ -190,6 +229,89 @@ internal constructor() : Pathfinder() {
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.
@@ -443,4 +565,4 @@ internal constructor() : Pathfinder() {
}
}
}
}
}