Implemented A* pathfinding for SmartPathfinder
This commit is contained in:
+1
-1
@@ -437,7 +437,7 @@ potential-bugs:
|
|||||||
- '*.CanIgnoreReturnValue'
|
- '*.CanIgnoreReturnValue'
|
||||||
ignoreFunctionCall: []
|
ignoreFunctionCall: []
|
||||||
ImplicitDefaultLocale:
|
ImplicitDefaultLocale:
|
||||||
active: true
|
active: false
|
||||||
ImplicitUnitReturnType:
|
ImplicitUnitReturnType:
|
||||||
active: false
|
active: false
|
||||||
allowExplicitReturnType: true
|
allowExplicitReturnType: true
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import core.tools.Log;
|
|||||||
import core.tools.SystemLogger;
|
import core.tools.SystemLogger;
|
||||||
|
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.LinkedList;
|
import java.util.ArrayDeque;
|
||||||
|
|
||||||
import static core.api.ContentAPIKt.log;
|
import static core.api.ContentAPIKt.log;
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ public final class WalkingQueue {
|
|||||||
/**
|
/**
|
||||||
* The walking queue.
|
* The walking queue.
|
||||||
*/
|
*/
|
||||||
private final Deque<Point> walkingQueue = new LinkedList<Point>();
|
private final Deque<Point> walkingQueue = new ArrayDeque<Point>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The entity.
|
* The entity.
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ object ServerConfigParser {
|
|||||||
enable_doubling_money_scammers = data.getBoolean("world.enable_doubling_money_scammers", false),
|
enable_doubling_money_scammers = data.getBoolean("world.enable_doubling_money_scammers", false),
|
||||||
wild_pvp_enabled = data.getBoolean("world.wild_pvp_enabled"),
|
wild_pvp_enabled = data.getBoolean("world.wild_pvp_enabled"),
|
||||||
jad_practice_enabled = data.getBoolean("world.jad_practice_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_model = data.getString("world.motw_identifier").toInt(),
|
||||||
message_string = data.getString("world.motw_text").replace("@name", ServerConstants.SERVER_NAME)
|
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 enable_doubling_money_scammers: Boolean,
|
||||||
var wild_pvp_enabled: Boolean,
|
var wild_pvp_enabled: Boolean,
|
||||||
var jad_practice_enabled: Boolean,
|
var jad_practice_enabled: Boolean,
|
||||||
|
var smartpathfinder_bfs: Boolean,
|
||||||
|
|
||||||
/**"Lobby" interface
|
/**"Lobby" interface
|
||||||
* The message of the week models to display
|
* 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 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 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 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 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_identifier = data["message_of_the_week_identifier"].toString().toInt()
|
||||||
val message_of_the_week_text = data["message_of_the_week_text"].toString()
|
val message_of_the_week_text = data["message_of_the_week_text"].toString()
|
||||||
@@ -165,6 +167,7 @@ class GameSettings
|
|||||||
enable_doubling_money_scammers,
|
enable_doubling_money_scammers,
|
||||||
wild_pvp_enabled,
|
wild_pvp_enabled,
|
||||||
jad_practice_enabled,
|
jad_practice_enabled,
|
||||||
|
smartpathfinder_bfs,
|
||||||
message_of_the_week_identifier,
|
message_of_the_week_identifier,
|
||||||
message_of_the_week_text
|
message_of_the_week_text
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import kotlin.concurrent.schedule
|
|||||||
class ImmerseWorld : StartupListener {
|
class ImmerseWorld : StartupListener {
|
||||||
|
|
||||||
override fun startup() {
|
override fun startup() {
|
||||||
spawnBots()
|
if(GameWorld.settings?.max_adv_bots!! > 0) {
|
||||||
|
spawnBots()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package core.game.world.map;
|
package core.game.world.map;
|
||||||
|
|
||||||
|
import core.game.world.map.path.ClipMaskSupplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a direction.
|
* Represents a direction.
|
||||||
* @author Emperor
|
* @author Emperor
|
||||||
@@ -254,6 +256,29 @@ public enum Direction {
|
|||||||
return true;
|
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.
|
* Sets the traversal.
|
||||||
* @param traversal The traversal to set.
|
* @param traversal The traversal to set.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import core.game.node.Node;
|
|||||||
import core.game.node.entity.Entity;
|
import core.game.node.entity.Entity;
|
||||||
import core.game.node.item.GroundItem;
|
import core.game.node.item.GroundItem;
|
||||||
import core.game.node.scenery.Scenery;
|
import core.game.node.scenery.Scenery;
|
||||||
|
import core.game.world.map.Direction;
|
||||||
import core.game.world.map.Location;
|
import core.game.world.map.Location;
|
||||||
import core.game.world.map.RegionManager;
|
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 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.
|
* Finds a path from the location to the end location.
|
||||||
* @param location The start location.
|
* @param location The start location.
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
package core.game.world.map.path
|
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.Location
|
||||||
import core.game.world.map.Point
|
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
|
class SmartPathfinder
|
||||||
/**
|
/**
|
||||||
* Constructs a new `SmartPathfinder` `Object`.
|
* Constructs a new `SmartPathfinder` `Object`.
|
||||||
@@ -76,12 +85,14 @@ internal constructor() : Pathfinder() {
|
|||||||
* @param dir The direction.
|
* @param dir The direction.
|
||||||
* @param currentCost The current cost.
|
* @param currentCost The current cost.
|
||||||
*/
|
*/
|
||||||
fun check(x: Int, y: Int, dir: Int, currentCost: Int) {
|
fun check(x: Int, y: Int, dir: Int, currentCost: Int, diagonalPenalty: Int = 0) {
|
||||||
queueX[writePathPosition] = x
|
if(cost[x][y] > currentCost + diagonalPenalty) {
|
||||||
queueY[writePathPosition] = y
|
queueX[writePathPosition] = x
|
||||||
via[x][y] = dir
|
queueY[writePathPosition] = y
|
||||||
cost[x][y] = currentCost
|
via[x][y] = dir
|
||||||
writePathPosition = writePathPosition + 1 and 0xfff
|
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 {
|
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)
|
check(curX, curY, 99, 0)
|
||||||
try {
|
try {
|
||||||
if (moverSize < 2) {
|
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) {
|
} else if (moverSize == 2) {
|
||||||
checkDoubleTraversal(end, sizeX, sizeY, type, rotation, walkingFlag, location, clipMaskSupplier!!)
|
checkDoubleTraversal(end, sizeX, sizeY, type, rotation, walkingFlag, location, clipMaskSupplier!!)
|
||||||
} else {
|
} else {
|
||||||
checkVariableTraversal(end, moverSize, sizeX, sizeY, type, rotation, walkingFlag, location, clipMaskSupplier!!)
|
checkVariableTraversal(end, moverSize, sizeX, sizeY, type, rotation, walkingFlag, location, clipMaskSupplier!!)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {}
|
} 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 (!foundPath) {
|
||||||
if (near) {
|
if (near) {
|
||||||
var fullCost = 1000
|
var fullCost = 1000
|
||||||
@@ -174,8 +201,20 @@ internal constructor() : Pathfinder() {
|
|||||||
} else if (directionFlag and NORTH_FLAG != 0) {
|
} else if (directionFlag and NORTH_FLAG != 0) {
|
||||||
curY--
|
curY--
|
||||||
}
|
}
|
||||||
|
if(debugImg != null) {
|
||||||
|
debugImg.setRGB(3*105+curX, 103-curY, 0x0000ff)
|
||||||
|
}
|
||||||
directionFlag = via[curX][curY]
|
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--
|
val size = readPosition--
|
||||||
var absX = location.x + queueX[readPosition]
|
var absX = location.x + queueX[readPosition]
|
||||||
var absY = location.y + queueY[readPosition]
|
var absY = location.y + queueY[readPosition]
|
||||||
@@ -190,6 +229,89 @@ internal constructor() : Pathfinder() {
|
|||||||
return path
|
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.
|
* Checks possible traversal for a size 1 entity.
|
||||||
* @param end The destination location.
|
* @param end The destination location.
|
||||||
|
|||||||
Reference in New Issue
Block a user