Unit avoidance for wave pathfinder (mostly broken)

This commit is contained in:
Anuken 2025-07-03 23:29:05 -04:00
parent 5340de4f62
commit 044b47cfd5
3 changed files with 52 additions and 4 deletions

View file

@ -107,6 +107,8 @@ public class Pathfinder implements Runnable{
boolean needsRefresh; boolean needsRefresh;
int[][] avoidance;
public Pathfinder(){ public Pathfinder(){
clearCache(); clearCache();
@ -121,6 +123,7 @@ public class Pathfinder implements Runnable{
mainList = new Seq<>(); mainList = new Seq<>();
clearCache(); clearCache();
avoidance = new int[4][tiles.length];
for(int i = 0; i < tiles.length; i++){ for(int i = 0; i < tiles.length; i++){
Tile tile = world.tiles.geti(i); Tile tile = world.tiles.geti(i);
@ -143,6 +146,38 @@ public class Pathfinder implements Runnable{
start(); start();
}); });
//TODO: the before/after game update avoidance code is very slow, but it works as a concept. future versions can do the clearing/area definitions in another thread
Events.run(Trigger.beforeGameUpdate, () -> {
for(var unit : Groups.unit){
if(!unit.isFlying()){
int layer = unit.collisionLayer();
if(layer < avoidance.length){
float scaling = 2f;
int r = Math.max(1, Mathf.ceil(unit.hitSize * 0.6f / tilesize * scaling));
var arr = avoidance[layer];
float rad2 = (unit.hitSize * unitCollisionRadiusScale / tilesize * scaling) * (unit.hitSize * unitCollisionRadiusScale / tilesize * scaling);
for(int dx = -r; dx <= r; dx++){
for(int dy = -r; dy <= r; dy++){
int x = dx + unit.tileX(), y = dy + unit.tileY();
if(x >= 0 && y >= 0 && x < wwidth && y < wheight && (dx*dx + dy*dy) <= rad2){
arr[x + y * wwidth] = Math.max(arr[x + y * wwidth], unit.id);
}
}
}
}
}
}
});
Events.run(Trigger.afterGameUpdate, () -> {
for(var arr : avoidance){
Arrays.fill(arr, 0);
}
});
Events.on(ResetEvent.class, event -> stop()); Events.on(ResetEvent.class, event -> stop());
Events.on(TileChangeEvent.class, event -> { Events.on(TileChangeEvent.class, event -> {
@ -359,6 +394,11 @@ public class Pathfinder implements Runnable{
/** Gets next tile to travel to. Main thread only. */ /** Gets next tile to travel to. Main thread only. */
public @Nullable Tile getTargetTile(Tile tile, Flowfield path, boolean diagonals){ public @Nullable Tile getTargetTile(Tile tile, Flowfield path, boolean diagonals){
return getTargetTile(tile, path, diagonals, -1, 0);
}
/** Gets next tile to travel to. Main thread only. */
public @Nullable Tile getTargetTile(Tile tile, Flowfield path, boolean diagonals, int collisionLayer, int unitId){
if(tile == null) return null; if(tile == null) return null;
//uninitialized flowfields are not applicable //uninitialized flowfields are not applicable
@ -390,6 +430,7 @@ public class Pathfinder implements Runnable{
int value = values[apos]; int value = values[apos];
var points = diagonals ? Geometry.d8 : Geometry.d4; var points = diagonals ? Geometry.d8 : Geometry.d4;
int[] avoid = collisionLayer == -1 ? null : avoidance[collisionLayer];
Tile current = null; Tile current = null;
int tl = 0; int tl = 0;
@ -400,11 +441,13 @@ public class Pathfinder implements Runnable{
if(other == null) continue; if(other == null) continue;
int packed = dx/res + dy/res * ww; int packed = dx/res + dy/res * ww;
int avoidance = avoid == null || unitId == 0 ? 0 : avoid[packed] > unitId ? 1 : 0;
int cost = values[packed] + avoidance;
if(values[packed] < value && (current == null || values[packed] < tl) && path.passable(packed) && if(cost < value && avoidance == 0 && (current == null || cost < tl) && path.passable(packed) &&
!(point.x != 0 && point.y != 0 && (!path.passable(((tile.x + point.x)/res + tile.y/res*ww)) || !path.passable((tile.x/res + (tile.y + point.y)/res*ww))))){ //diagonal corner trap !(point.x != 0 && point.y != 0 && (!path.passable(((tile.x + point.x)/res + tile.y/res*ww)) || !path.passable((tile.x/res + (tile.y + point.y)/res*ww))))){ //diagonal corner trap
current = other; current = other;
tl = values[packed]; tl = cost;
} }
} }
@ -655,6 +698,11 @@ public class Pathfinder implements Runnable{
return pathfinder.getTargetTile(from, this); return pathfinder.getTargetTile(from, this);
} }
/** @return the next tile to travel to for this flowfield. Main thread only. */
public @Nullable Tile getNextTile(Tile from, int collisionlayer, int unitId){
return pathfinder.getTargetTile(from, this, true, collisionlayer, unitId);
}
public boolean hasCompleteWeights(){ public boolean hasCompleteWeights(){
return hasComplete && completeWeights != null; return hasComplete && completeWeights != null;
} }

View file

@ -136,7 +136,7 @@ public class AIController implements UnitController{
Tile tile = unit.tileOn(); Tile tile = unit.tileOn();
if(tile == null) return; if(tile == null) return;
Tile targetTile = pathfinder.getField(unit.team, costType, pathTarget).getNextTile(tile); Tile targetTile = pathfinder.getField(unit.team, costType, pathTarget).getNextTile(tile, unit.collisionLayer(), unit.id);
if((tile == targetTile && stopAtTargetTile) || !unit.canPass(targetTile.x, targetTile.y)) return; if((tile == targetTile && stopAtTargetTile) || !unit.canPass(targetTile.x, targetTile.y)) return;

View file

@ -26,4 +26,4 @@ org.gradle.caching=true
org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.socketTimeout=100000
org.gradle.internal.http.connectionTimeout=100000 org.gradle.internal.http.connectionTimeout=100000
android.enableR8.fullMode=false android.enableR8.fullMode=false
archash=9b55a5d628 archash=60e47d0f1b