Implemented A* pathfinding for SmartPathfinder
This commit is contained in:
+1
-1
@@ -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() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user