diff --git a/core/src/mindustry/ai/HierarchyPathFinder.java b/core/src/mindustry/ai/HierarchyPathFinder.java index 3989f949b5..012c0495e4 100644 --- a/core/src/mindustry/ai/HierarchyPathFinder.java +++ b/core/src/mindustry/ai/HierarchyPathFinder.java @@ -15,6 +15,7 @@ import mindustry.game.*; import mindustry.gen.*; import mindustry.graphics.*; import mindustry.ui.*; +import mindustry.world.*; import static mindustry.Vars.*; import static mindustry.ai.Pathfinder.*; @@ -155,50 +156,54 @@ public class HierarchyPathFinder{ } } - Lines.stroke(3f); - Draw.color(Color.orange); - int node = findClosestNode(Team.sharded.id, 0, player.tileX(), player.tileY()); - int dest = findClosestNode(Team.sharded.id, 0, World.toTile(Core.input.mouseWorldX()), World.toTile(Core.input.mouseWorldY())); - if(node != Integer.MAX_VALUE && dest != Integer.MAX_VALUE){ - var result = clusterAstar(0, node, dest); - if(result != null){ - for(int i = -1; i < result.size - 1; i++){ - int endCluster = NodeIndex.cluster(result.items[i + 1]); - int cx = endCluster % cwidth, cy = endCluster / cwidth; - int[] field = flowField(0, cx, cy, 0, dest); + if(false){ + Lines.stroke(3f); + Draw.color(Color.orange); + int node = findClosestNode(Team.sharded.id, 0, player.tileX(), player.tileY()); + int dest = findClosestNode(Team.sharded.id, 0, World.toTile(Core.input.mouseWorldX()), World.toTile(Core.input.mouseWorldY())); + if(node != Integer.MAX_VALUE && dest != Integer.MAX_VALUE){ + var result = clusterAstar(0, node, dest); + if(result != null){ + for(int i = -1; i < result.size - 1; i++){ + int endCluster = NodeIndex.cluster(result.items[i + 1]); + int cx = endCluster % cwidth, cy = endCluster / cwidth; + int[] field = flowField(0, cx, cy, 0, dest, World.toTile(Core.input.mouseWorldX()), World.toTile(Core.input.mouseWorldY())); - for(int y = 0; y < clusterSize; y++){ - for(int x = 0; x < clusterSize; x++){ - int value = field[x + y *clusterSize]; - Tmp.c1.a = 1f; - Lines.stroke(0.8f, Tmp.c1.fromHsv(value * 3f, 1f, 1f)); - Draw.alpha(0.5f); - Lines.rect((x + cx * clusterSize) * tilesize - tilesize/2f, (y + cy * clusterSize) * tilesize - tilesize/2f, tilesize, tilesize); + for(int y = 0; y < clusterSize; y++){ + for(int x = 0; x < clusterSize; x++){ + int value = field[x + y *clusterSize]; + Tmp.c1.a = 1f; + Lines.stroke(0.8f, Tmp.c1.fromHsv(value * 3f, 1f, 1f)); + Draw.alpha(0.5f); + Lines.rect((x + cx * clusterSize) * tilesize - tilesize/2f, (y + cy * clusterSize) * tilesize - tilesize/2f, tilesize, tilesize); + } } } + + Lines.stroke(3f); + Draw.color(Color.orange); + + for(int i = -1; i < result.size - 1; i++){ + int current = i == -1 ? node : result.items[i], next = result.items[i + 1]; + portalToVec(0, NodeIndex.cluster(current), NodeIndex.dir(current), NodeIndex.portal(current), Tmp.v1); + portalToVec(0, NodeIndex.cluster(next), NodeIndex.dir(next), NodeIndex.portal(next), Tmp.v2); + Lines.line(Tmp.v1.x, Tmp.v1.y, Tmp.v2.x, Tmp.v2.y); + } + + + + + //flowField(0, ) } - Lines.stroke(3f); - Draw.color(Color.orange); - - for(int i = -1; i < result.size - 1; i++){ - int current = i == -1 ? node : result.items[i], next = result.items[i + 1]; - portalToVec(0, NodeIndex.cluster(current), NodeIndex.dir(current), NodeIndex.portal(current), Tmp.v1); - portalToVec(0, NodeIndex.cluster(next), NodeIndex.dir(next), NodeIndex.portal(next), Tmp.v2); - Lines.line(Tmp.v1.x, Tmp.v1.y, Tmp.v2.x, Tmp.v2.y); - } - - - - - //flowField(0, ) + nodeToVec(dest, Tmp.v1); + Fonts.outline.draw(clusterNodeHeuristic(0, node, dest) + "", Tmp.v1.x, Tmp.v1.y); } - nodeToVec(dest, Tmp.v1); - Fonts.outline.draw(clusterNodeHeuristic(0, node, dest) + "", Tmp.v1.x, Tmp.v1.y); + Draw.reset(); } - Draw.reset(); + }); }); } @@ -631,6 +636,10 @@ public class HierarchyPathFinder{ Fx.debugLine.at(a.x, a.y, 0f, Color.blue.cpy().a(0.1f), new Vec2[]{a.cpy(), b.cpy()}); } + static void line(Vec2 a, Vec2 b, Color color){ + Fx.debugLine.at(a.x, a.y, 0f, color, new Vec2[]{a.cpy(), b.cpy()}); + } + void checkEdges(int pathCost, int current, int goal, int cx, int cy, LongSeq connections){ for(int i = 0; i < connections.size; i++){ long con = connections.items[i]; @@ -653,48 +662,63 @@ public class HierarchyPathFinder{ } //both nodes must be inside the same flow field (cx, cy) - int[] flowField(int pathCost, int cx, int cy, int nodeFrom, int nodeTo){ + int[] flowField(int pathCost, int cx, int cy, int nodeFrom, int nodeTo, int goalX, int goalY){ Cluster cluster = clusters[pathCost][cx + cy * cwidth]; - int[] weights = new int[clusterSize * clusterSize]; - byte[] searches = new byte[clusterSize * clusterSize]; + int realSize = clusterSize + 2; + + int[] weights = new int[realSize * realSize]; + byte[] searches = new byte[realSize * realSize]; PathCost pcost = ControlPathfinder.costGround; int team = Team.sharded.id; IntQueue frontier = new IntQueue(); int search = 1; - //TODO actually set up the frontier and destinations (where are you going?) - { + int + minX = cx * clusterSize - 1, + minY = cy * clusterSize - 1, + maxX = Math.min(minX + clusterSize + 1, wwidth - 1), + maxY = Math.min(minY + clusterSize + 1, wheight - 1), + toCluster = NodeIndex.cluster(nodeTo), + tocx = toCluster % cwidth, + tocy = toCluster / cwidth; + + //you're at the cluster with the goal node + if(goalX / clusterSize == cx && goalY / clusterSize == cy){ + frontier.addFirst(goalX + goalY * wwidth); + }else{ int dir = NodeIndex.dir(nodeTo), other = cluster.portals[dir].items[NodeIndex.portal(nodeTo)], otherFrom = Point2.x(other), otherTo = Point2.y(other), - ox = cx * clusterSize + offsets[dir * 2] * (clusterSize - 1), - oy = cy * clusterSize + offsets[dir * 2 + 1] * (clusterSize - 1), + ox = tocx * clusterSize + offsets[dir * 2] * (clusterSize - 1), + oy = tocy * clusterSize + offsets[dir * 2 + 1] * (clusterSize - 1), - maxX = (moveDirs[dir * 2] * otherFrom + ox), - maxY = (moveDirs[dir * 2 + 1] * otherFrom + oy), - minX = (moveDirs[dir * 2] * otherTo + ox), - minY = (moveDirs[dir * 2 + 1] * otherTo + oy) + px2 = Mathf.clamp((moveDirs[dir * 2] * otherFrom + ox), minX, maxX), + py2 = Mathf.clamp((moveDirs[dir * 2 + 1] * otherFrom + oy), minY, maxY), + px1 = Mathf.clamp((moveDirs[dir * 2] * otherTo + ox), minX, maxX), + py1 = Mathf.clamp((moveDirs[dir * 2 + 1] * otherTo + oy), minY, maxY); - ; + if(px1 >= cx * clusterSize && px2 < cx * clusterSize + clusterSize && py1 >= cy * clusterSize && py2 < cy * clusterSize){ + Log.info("inside the box"); //TODO broken + } //TODO: being zero INSIDE the cluster means that the unit will stop at the edge and not move between clusters - bad! - for(int x = minX; x <= maxX; x++){ - for(int y = minY; y <= maxY; y++){ + for(int x = px1; x <= px2; x++){ + for(int y = py1; y <= py2; y++){ frontier.addFirst(x + y * wwidth); + Fx.lightBlock.at(x * tilesize, y * tilesize, 1f, Color.orange); } } } - int minX = cx * clusterSize, minY = cy * clusterSize, maxX = Math.min(minX + clusterSize - 1, wwidth - 1), maxY = Math.min(minY + clusterSize - 1, wheight - 1); //TODO spread this out across many frames while(frontier.size > 0){ int tile = frontier.removeLast(); int baseX = tile % wwidth, baseY = tile / wwidth; - int cost = weights[(baseX - minX) + (baseY - minY) * clusterSize]; + int cost = weights[(baseX - minX) + (baseY - minY) * realSize]; if(cost != impassable){ for(Point2 point : Geometry.d4){ @@ -704,7 +728,7 @@ public class HierarchyPathFinder{ if(dx < minX || dy < minY || dx > maxX || dy > maxY) continue; int newPos = tile + point.x + point.y * wwidth; - int newPosArray = (dx - minX) + (dy - minY) * clusterSize; + int newPosArray = (dx - minX) + (dy - minY) * realSize; int otherCost = pcost.getCost(team, pathfinder.tiles[newPos]); if((weights[newPosArray] > cost + otherCost || searches[newPosArray] < search) && otherCost != impassable){ @@ -718,6 +742,72 @@ public class HierarchyPathFinder{ return weights; } + public boolean getPathPosition(Unit unit, int pathId, Vec2 destination, Vec2 out, boolean[] noResultFound){ + int cost = 0; + + int node = findClosestNode(unit.team.id, cost, unit.tileX(), unit.tileY()); + int dest = findClosestNode(unit.team.id, cost, World.toTile(destination.x), World.toTile(destination.y)); + + var result = clusterAstar(cost, node, dest); + Tile tile = unit.tileOn(); + if(result != null){ + for(int i = -1; i < result.size - 1; i++){ + int current = i == -1 ? node : result.items[i], next = result.items[i + 1]; + portalToVec(0, NodeIndex.cluster(current), NodeIndex.dir(current), NodeIndex.portal(current), Tmp.v1); + portalToVec(0, NodeIndex.cluster(next), NodeIndex.dir(next), NodeIndex.portal(next), Tmp.v2); + line(Tmp.v1, Tmp.v2, Color.orange); + } + + int cx = unit.tileX() / clusterSize, cy = unit.tileY() / clusterSize, + ox = cx * clusterSize - 1, oy = cy * clusterSize - 1; + + int nextNode = result.items[0]; + + int[] field = flowField(cost, cx, cy, node, nextNode, World.toTile(destination.x), World.toTile(destination.y)); + + if(field != null && tile != null){ + int value = field[(tile.x - ox) + (tile.y - oy) * (clusterSize + 2)]; + + Tile current = null; + int tl = 0; + for(Point2 point : Geometry.d8){ + int dx = tile.x + point.x, dy = tile.y + point.y; + + Tile other = world.tile(dx, dy); + + + if(other == null || dx < ox || dy < oy || dx >= ox + clusterSize + 2 || dy >= oy + clusterSize + 2) continue; + + int local = (dx - ox) + (dy - oy) * (clusterSize + 2); + int packed = world.packArray(dx, dy); + int otherCost = field[local]; + + if(otherCost < value && (current == null || otherCost < tl) && passable(ControlPathfinder.costGround, unit.team.id, packed) && + !(point.x != 0 && point.y != 0 && (!passable(ControlPathfinder.costGround, unit.team.id, world.packArray(tile.x + point.x, tile.y)) || + (!passable(ControlPathfinder.costGround, unit.team.id, world.packArray(tile.x, tile.y + point.y)))))){ //diagonal corner trap + + current = other; + tl = field[local]; + } + } + + if(!(current == null || tl == impassable || (cost == costGround && current.dangerous() && !tile.dangerous()))){ + out.set(current); + return true; + } + } + } + + noResultFound[0] = true; + return false; + } + + private static boolean passable(PathCost cost, int team, int pos){ + int amount = cost.getCost(team, pathfinder.tiles[pos]); + //edge case: naval reports costs of 6000+ for non-liquids, even though they are not technically passable + return amount != impassable && !(cost == costTypes.get(costNaval) && amount >= 6000); + } + private static boolean solid(int team, PathCost type, int x, int y){ return x < 0 || y < 0 || x >= wwidth || y >= wheight || solid(team, type, x + y * wwidth, true); } diff --git a/core/src/mindustry/ai/types/CommandAI.java b/core/src/mindustry/ai/types/CommandAI.java index a44b5b457c..86a5a89447 100644 --- a/core/src/mindustry/ai/types/CommandAI.java +++ b/core/src/mindustry/ai/types/CommandAI.java @@ -247,7 +247,7 @@ public class CommandAI extends AIController{ } //if you've spent 3 seconds stuck, something is wrong, move regardless - move = Vars.controlPath.getPathPosition(unit, pathId, vecMovePos, vecOut, noFound) && (!blockingUnit || timeSpentBlocked > maxBlockTime); + move = hpath.getPathPosition(unit, pathId, vecMovePos, vecOut, noFound) && (!blockingUnit || timeSpentBlocked > maxBlockTime); //we've reached the final point if the returned coordinate is equal to the supplied input isFinalPoint &= vecMovePos.epsilonEquals(vecOut, 4.1f); diff --git a/gradle.properties b/gradle.properties index f9fa0851e3..090150dba9 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=96f4f4214a +archash=e2fdbab477