diff --git a/core/src/mindustry/ai/ControlPathfinder.java b/core/src/mindustry/ai/ControlPathfinder.java index db8be4106a..a785bc91fb 100644 --- a/core/src/mindustry/ai/ControlPathfinder.java +++ b/core/src/mindustry/ai/ControlPathfinder.java @@ -164,6 +164,19 @@ public class ControlPathfinder{ * @param pathId a unique ID for this location query, which should change every time the 'destination' vector is modified. * */ public boolean getPathPosition(Unit unit, int pathId, Vec2 destination, Vec2 out){ + return getPathPosition(unit, pathId, destination, out, null); + } + + /** + * @return whether a path is ready. + * @param pathId a unique ID for this location query, which should change every time the 'destination' vector is modified. + * @param noResultFound extra return value for storing whether no valid path to the destination exists (thanks java!) + * */ + public boolean getPathPosition(Unit unit, int pathId, Vec2 destination, Vec2 out, @Nullable boolean[] noResultFound){ + if(noResultFound != null){ + noResultFound[0] = false; + } + //uninitialized if(threads == null || !world.tiles.in(World.toTile(destination.x), World.toTile(destination.y))) return false; @@ -272,6 +285,10 @@ public class ControlPathfinder{ out.set(unit); //end of path, we're done here? reset path? what??? } + + if(noResultFound != null){ + noResultFound[0] = !req.foundEnd; + } } return req.done; diff --git a/core/src/mindustry/ai/RtsAI.java b/core/src/mindustry/ai/RtsAI.java index 33cf265cfa..930ededf6d 100644 --- a/core/src/mindustry/ai/RtsAI.java +++ b/core/src/mindustry/ai/RtsAI.java @@ -7,6 +7,7 @@ import arc.math.geom.*; import arc.struct.*; import arc.util.*; import mindustry.*; +import mindustry.ai.types.*; import mindustry.content.*; import mindustry.entities.*; import mindustry.game.EventType.*; @@ -25,7 +26,7 @@ public class RtsAI{ static final Seq targets = new Seq<>(); static final Seq squad = new Seq<>(false); static final IntSet used = new IntSet(); - static final IntSet assignedTargets = new IntSet(); + static final IntSet assignedTargets = new IntSet(), invalidTarget = new IntSet(); static final float squadRadius = 140f; static final int timeUpdate = 0, timerSpawn = 1, maxTargetsChecked = 15; @@ -233,6 +234,14 @@ public class RtsAI{ boolean anyDefend = defendPos != null || defendTarget != null; + invalidTarget.clear(); + + for(var unit : squad){ + if(unit.controller() instanceof CommandAI ai){ + invalidTarget.addAll(ai.unreachableBuildings); + } + } + var build = anyDefend ? null : findTarget(ax, ay, units.size, dps, health, units.first().flag == 0); if(build != null || anyDefend){ @@ -267,7 +276,7 @@ public class RtsAI{ for(var flag : flags){ targets.addAll(Vars.indexer.getEnemy(data.team, flag)); } - targets.removeAll(b -> assignedTargets.contains(b.id)); + targets.removeAll(b -> assignedTargets.contains(b.id) || invalidTarget.contains(b.pos())); if(targets.size == 0) return null; @@ -327,9 +336,9 @@ public class RtsAI{ float timeDestroySelf = Mathf.zero(dp) ? Float.POSITIVE_INFINITY : selfHealth / dp; //other can never be destroyed | other destroys self instantly - if(Float.isInfinite(timeDestroyOther) | Mathf.zero(timeDestroySelf)) return 0f; + if(Float.isInfinite(timeDestroyOther) || Mathf.zero(timeDestroySelf)) return 0f; //self can never be destroyed | self destroys other instantly - if(Float.isInfinite(timeDestroySelf) | Mathf.zero(timeDestroyOther)) return 1f; + if(Float.isInfinite(timeDestroySelf) || Mathf.zero(timeDestroyOther)) return 1f; //examples: // self 10 sec / other 10 sec -> can destroy target with 100 % losses -> returns 1 diff --git a/core/src/mindustry/ai/types/CommandAI.java b/core/src/mindustry/ai/types/CommandAI.java index 105351f191..eeabe95863 100644 --- a/core/src/mindustry/ai/types/CommandAI.java +++ b/core/src/mindustry/ai/types/CommandAI.java @@ -14,9 +14,12 @@ import mindustry.world.*; public class CommandAI extends AIController{ protected static final float localInterval = 40f; protected static final Vec2 vecOut = new Vec2(), flockVec = new Vec2(), separation = new Vec2(), cohesion = new Vec2(), massCenter = new Vec2(); + protected static final boolean[] noFound = {false}; public @Nullable Vec2 targetPos; public @Nullable Teamc attackTarget; + /** All encountered unreachable buildings of this AI. Why a sequence? Because contains() is very rarely called on it. */ + public IntSeq unreachableBuildings = new IntSeq(8); protected boolean stopAtTarget; protected Vec2 lastTargetPos; @@ -133,7 +136,17 @@ public class CommandAI extends AIController{ vecOut.set(targetPos); if(unit.isGrounded()){ - move = Vars.controlPath.getPathPosition(unit, pathId, targetPos, vecOut); + move = Vars.controlPath.getPathPosition(unit, pathId, targetPos, vecOut, noFound); + + //if the path is invalid, stop trying and record the end as unreachable + if(unit.team.isAI() && noFound[0]){ + if(attackTarget instanceof Building build){ + unreachableBuildings.addUnique(build.pos()); + } + attackTarget = null; + targetPos = null; + return; + } } float engageRange = unit.type.range - 10f; diff --git a/gradle.properties b/gradle.properties index 04625a23bd..434780ac63 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,4 +25,4 @@ org.gradle.caching=true #used for slow jitpack builds; TODO see if this actually works org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.connectionTimeout=100000 -archash=e319dd0056 +archash=ae63b4bdf0