diff --git a/core/src/mindustry/ai/HierarchyPathFinder.java b/core/src/mindustry/ai/HierarchyPathFinder.java index 4bc2efbf0a..e120bfefd2 100644 --- a/core/src/mindustry/ai/HierarchyPathFinder.java +++ b/core/src/mindustry/ai/HierarchyPathFinder.java @@ -295,6 +295,8 @@ public class HierarchyPathFinder implements Runnable{ }catch(Exception ignored){} //probably has some concurrency issues when iterating but I don't care, this is for debugging } }); + + Draw.reset(); }); } } @@ -1055,14 +1057,16 @@ public class HierarchyPathFinder implements Runnable{ int maxIterations = 30; //TODO higher/lower number? is this still too slow? int i = 0; boolean recalc = false; + unit.hitboxTile(Tmp.r3); + //tile rect size has tile size factored in, since the ray cannot have thickness + float tileRectSize = tilesize + Tmp.r3.height; //TODO last pos can change if the flowfield changes. if(initialTileOn.pos() != request.lastTile || request.lastTargetTile == null){ - //TODO tanks have weird behavior near edges of walls, as they try to avoid them boolean anyNearSolid = false; //find the next tile until one near a solid block is discovered - while(i ++ < maxIterations && !anyNearSolid){ + while(i ++ < maxIterations){ int value = getCost(fieldCache, old, tileOn.x, tileOn.y); Tile current = null; @@ -1083,16 +1087,16 @@ public class HierarchyPathFinder implements Runnable{ } //check for corner preventing movement - if((checkCorner(unit, tileOn, other, dir - 1) || checkCorner(unit, tileOn, other, dir + 1)) && - (checkSolid(unit, tileOn, dir - 2) || checkSolid(unit, tileOn, dir + 2))){ //there must be a tile to the left or right to keep the unit from going back and forth forever + //if((checkCorner(unit, tileOn, other, dir - 1) || checkCorner(unit, tileOn, other, dir + 1)) && + // (checkSolid(unit, tileOn, dir - 2) || checkSolid(unit, tileOn, dir + 2))){ //there must be a tile to the left or right to keep the unit from going back and forth forever - recalc = true; + //recalc = true; //keep moving even if it's blocked - any = true; - continue; - } + //any = true; + // continue; + //} - if(otherCost < value && otherCost != impassable && (otherCost != 0 || packed == destPos) && (current == null || otherCost < minCost) && passable(unit.team.id, cost, packed) && + if((value == 0 || otherCost < value) && otherCost != impassable && (otherCost != 0 || packed == destPos) && (current == null || otherCost < minCost) && passable(unit.team.id, cost, packed) && //diagonal corner trap !( (!passable(team, cost, world.packArray(tileOn.x + point.x, tileOn.y)) || @@ -1104,13 +1108,28 @@ public class HierarchyPathFinder implements Runnable{ } } + //TODO raycast spam = extremely slow if(!(current == null || (costId == costGround && current.dangerous() && !tileOn.dangerous()))){ - tileOn = current; - any = true; - if(current.array() == destPos){ + //when anyNearSolid is false, no solid tiles have been encountered anywhere so far, so raycasting is a waste of time + if(anyNearSolid && !tileOn.dangerous() && raycastRect(unit.x, unit.y, current.x * tilesize, current.y * tilesize, team, cost, initialTileOn.x, initialTileOn.y, current.x, current.y, tileRectSize)){ + + //TODO this may be a mistake + if(tileOn == initialTileOn){ + recalc = true; + any = true; + } + break; + }else{ + tileOn = current; + any = true; + + if(current.array() == destPos){ + break; + } } + }else{ break; } @@ -1156,24 +1175,6 @@ public class HierarchyPathFinder implements Runnable{ initializePathRequest(request, request.team, request.costId, request.unit.tileX(), request.unit.tileY(), request.destination % wwidth, request.destination / wwidth); } - private boolean checkSolid(Unit unit, Tile tile, int dir){ - var p = Geometry.d8[Mathf.mod(dir, 8)]; - return !unit.canPass(tile.x + p.x, tile.y + p.y); - } - - private boolean checkCorner(Unit unit, Tile tile, Tile next, int dir){ - Tile other = tile.nearby(Geometry.d8[Mathf.mod(dir, 8)]); - if(other == null){ - return true; - } - - if(!unit.canPass(other.x, other.y)){ - return Geometry.raycastRect(unit.x, unit.y, next.worldx(), next.worldy(), Tmp.r1.setCentered(other.worldx(), other.worldy(), tilesize).grow(Math.min(unit.hitSize * 0.66f, 7.6f))) != null; - } - - return false; - } - private int getCost(FieldCache cache, FieldCache old, int x, int y){ //fall back to the old flowfield when possible - it's best not to use partial results from the base cache if(old != null){ @@ -1244,6 +1245,47 @@ public class HierarchyPathFinder implements Runnable{ return true; } + private static boolean overlap(int team, PathCost type, int x, int y, float startX, float startY, float endX, float endY, float rectSize){ + if(x < 0 || y < 0 || x >= wwidth || y >= wheight) return false; + if(!passable(team, type, x + y * wwidth)){ + return Intersector.intersectSegmentRectangleFast(startX, startY, endX, endY, x * tilesize - rectSize/2f, y * tilesize - rectSize/2f, rectSize, rectSize); + } + return false; + } + + private static boolean raycastRect(float startX, float startY, float endX, float endY, int team, PathCost type, int x1, int y1, int x2, int y2, float rectSize){ + int ww = wwidth, wh = wheight; + int x = x1, dx = Math.abs(x2 - x), sx = x < x2 ? 1 : -1; + int y = y1, dy = Math.abs(y2 - y), sy = y < y2 ? 1 : -1; + int e2, err = dx - dy; + + while(x >= 0 && y >= 0 && x < ww && y < wh){ + if( + !passable(team, type, x + y * wwidth) || + overlap(team, type, x + 1, y, startX, startY, endX, endY, rectSize) || + overlap(team, type, x - 1, y, startX, startY, endX, endY, rectSize) || + overlap(team, type, x, y + 1, startX, startY, endX, endY, rectSize) || + overlap(team, type, x, y - 1, startX, startY, endX, endY, rectSize) + ) return true; + + if(x == x2 && y == y2) return false; + + //diagonal ver + e2 = 2 * err; + if(e2 > -dy){ + err -= dy; + x += sx; + } + + if(e2 < dx){ + err += dx; + y += sy; + } + } + + return true; + } + private static boolean avoid(int team, PathCost type, int tilePos){ int cost = cost(team, type, tilePos); return cost == impassable || cost >= 2; diff --git a/core/src/mindustry/ai/types/CommandAI.java b/core/src/mindustry/ai/types/CommandAI.java index 3be38e515a..042852e05f 100644 --- a/core/src/mindustry/ai/types/CommandAI.java +++ b/core/src/mindustry/ai/types/CommandAI.java @@ -205,6 +205,8 @@ public class CommandAI extends AIController{ } } + boolean alwaysArrive = false; + if(targetPos != null){ boolean move = true, isFinalPoint = commandQueue.size == 0; vecOut.set(targetPos); @@ -251,6 +253,8 @@ public class CommandAI extends AIController{ //if you've spent 3 seconds stuck, something is wrong, move regardless move = hpath.getPathPosition(unit, vecMovePos, targetPos, vecOut, noFound) && (!blockingUnit || timeSpentBlocked > maxBlockTime); + //rare case where unit must be perfectly aligned (happens with 1-tile gaps) + alwaysArrive = vecOut.epsilonEquals(unit.tileX() * tilesize, unit.tileY() * tilesize); //we've reached the final point if the returned coordinate is equal to the supplied input isFinalPoint &= vecMovePos.epsilonEquals(vecOut, 4.1f); @@ -278,7 +282,7 @@ public class CommandAI extends AIController{ attackTarget != null && unit.within(attackTarget, engageRange) && stance != UnitStance.ram ? engageRange : unit.isGrounded() ? 0f : attackTarget != null && stance != UnitStance.ram ? engageRange : - 0f, unit.isFlying() ? 40f : 100f, false, null, isFinalPoint); + 0f, unit.isFlying() ? 40f : 100f, false, null, isFinalPoint || alwaysArrive); } } diff --git a/core/src/mindustry/entities/units/AIController.java b/core/src/mindustry/entities/units/AIController.java index 009e56d6ad..6025ea611c 100644 --- a/core/src/mindustry/entities/units/AIController.java +++ b/core/src/mindustry/entities/units/AIController.java @@ -341,7 +341,7 @@ public class AIController implements UnitController{ vec.setLength(speed * length); } - //do not move when infinite vectors are used or if its zero. + //ignore invalid movement values if(vec.isNaN() || vec.isInfinite() || vec.isZero()) return; if(!unit.type.omniMovement && unit.type.rotateMoveFirst){ diff --git a/gradle.properties b/gradle.properties index 25be0e37a2..025014ab41 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=8a2decd656 +archash=e812c7a008