From 044b47cfd59d8f9d30d9d29db618b0f1cee0962f Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 3 Jul 2025 23:29:05 -0400 Subject: [PATCH] Unit avoidance for wave pathfinder (mostly broken) --- core/src/mindustry/ai/Pathfinder.java | 52 ++++++++++++++++++- .../entities/units/AIController.java | 2 +- gradle.properties | 2 +- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/core/src/mindustry/ai/Pathfinder.java b/core/src/mindustry/ai/Pathfinder.java index 2c950e8636..70886f5f4f 100644 --- a/core/src/mindustry/ai/Pathfinder.java +++ b/core/src/mindustry/ai/Pathfinder.java @@ -107,6 +107,8 @@ public class Pathfinder implements Runnable{ boolean needsRefresh; + int[][] avoidance; + public Pathfinder(){ clearCache(); @@ -121,6 +123,7 @@ public class Pathfinder implements Runnable{ mainList = new Seq<>(); clearCache(); + avoidance = new int[4][tiles.length]; for(int i = 0; i < tiles.length; i++){ Tile tile = world.tiles.geti(i); @@ -143,6 +146,38 @@ public class Pathfinder implements Runnable{ 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(TileChangeEvent.class, event -> { @@ -359,6 +394,11 @@ public class Pathfinder implements Runnable{ /** Gets next tile to travel to. Main thread only. */ 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; //uninitialized flowfields are not applicable @@ -390,6 +430,7 @@ public class Pathfinder implements Runnable{ int value = values[apos]; var points = diagonals ? Geometry.d8 : Geometry.d4; + int[] avoid = collisionLayer == -1 ? null : avoidance[collisionLayer]; Tile current = null; int tl = 0; @@ -400,11 +441,13 @@ public class Pathfinder implements Runnable{ if(other == null) continue; 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 current = other; - tl = values[packed]; + tl = cost; } } @@ -655,6 +698,11 @@ public class Pathfinder implements Runnable{ 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(){ return hasComplete && completeWeights != null; } diff --git a/core/src/mindustry/entities/units/AIController.java b/core/src/mindustry/entities/units/AIController.java index 666522f54d..f474fa1bea 100644 --- a/core/src/mindustry/entities/units/AIController.java +++ b/core/src/mindustry/entities/units/AIController.java @@ -136,7 +136,7 @@ public class AIController implements UnitController{ Tile tile = unit.tileOn(); 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; diff --git a/gradle.properties b/gradle.properties index a8e0be2bf0..7cd0af7dd7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,4 +26,4 @@ org.gradle.caching=true org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.connectionTimeout=100000 android.enableR8.fullMode=false -archash=9b55a5d628 +archash=60e47d0f1b