using Supercell.Laser.Titan.Math; using System; using System.Collections.Generic; namespace Supercell.Laser.Logic.Battle.Level { public class LogicPathFinder { private const int TileSize = 300; private readonly byte[,] _grid; private readonly byte[,] _navGrid; private readonly int _width; private readonly int _height; private readonly bool _allowDiagonals; public LogicPathFinder(TileMap tileMap, bool allowDiagonals = true, int agentRadiusWorld = 0) { _allowDiagonals = allowDiagonals; _width = Math.Max(1, tileMap.LogicWidth / TileSize); _height = Math.Max(1, tileMap.LogicHeight / TileSize); _grid = new byte[_width, _height]; for (int x = 0; x < _width; x++) { for (int y = 0; y < _height; y++) { var tile = tileMap.GetTile(x, y, true); bool blocked = tile?.Data?.BlocksMovement == true; _grid[x, y] = (byte)(blocked ? 0 : 1); } } int clearanceTiles = Math.Max(0, (int)Math.Ceiling(agentRadiusWorld / (double)TileSize)); _navGrid = Dilate(_grid, clearanceTiles); } public List FindPath(LogicVector2 startPos, LogicVector2 goalPos) { var start = ToTile(startPos); var goal = ToTile(goalPos); if (!IsWalkable(start.X, start.Y) || !IsWalkable(goal.X, goal.Y)) return null; var open = new PriorityQueue(); var cameFrom = new Dictionary(); var gScore = new Dictionary { [start] = 0 }; open.Enqueue(new PathNode(start, 0), 0); while (open.Count > 0) { var current = open.Dequeue().Pos; if (current.Equals(goal)) return Reconstruct(cameFrom, current); foreach (var neighbor in GetNeighbors(current)) { int stepCost = (neighbor.X != current.X && neighbor.Y != current.Y) ? 14 : 10; int tentative = gScore[current] + stepCost; if (!gScore.ContainsKey(neighbor) || tentative < gScore[neighbor]) { cameFrom[neighbor] = current; gScore[neighbor] = tentative; int f = tentative + Heuristic(neighbor, goal); open.Enqueue(new PathNode(neighbor, f), f); } } } return null; } public bool IsPassable(LogicVector2 pos) { var t = ToTile(pos); return IsWalkable(t.X, t.Y); } private int Heuristic(Point a, Point b) { int dx = Math.Abs(a.X - b.X); int dy = Math.Abs(a.Y - b.Y); return 10 * (_allowDiagonals ? Math.Max(dx, dy) : dx + dy); } private IEnumerable GetNeighbors(Point p) { for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { if (dx == 0 && dy == 0) continue; if (!_allowDiagonals && dx != 0 && dy != 0) continue; int nx = p.X + dx; int ny = p.Y + dy; var to = new Point(nx, ny); if (CanStep(p, to)) yield return to; } } } private bool IsWalkable(int x, int y) { return x >= 0 && y >= 0 && x < _width && y < _height && _navGrid[x, y] == 1; } private bool CanStep(Point from, Point to) { if (!IsWalkable(to.X, to.Y)) return false; int dx = to.X - from.X; int dy = to.Y - from.Y; if (dx != 0 && dy != 0) { if (!_allowDiagonals) return false; if (!IsWalkable(from.X + dx, from.Y)) return false; if (!IsWalkable(from.X, from.Y + dy)) return false; } return true; } private List Reconstruct(Dictionary cameFrom, Point current) { var path = new List { ToWorld(current) }; while (cameFrom.ContainsKey(current)) { current = cameFrom[current]; path.Insert(0, ToWorld(current)); } return path; } public LogicVector2 GetNextVisibleNode(LogicVector2 startPos, List path) { if (path == null || path.Count == 0) return startPos; var start = ToTile(startPos); LogicVector2 target = path[^1]; for (int i = path.Count - 1; i >= 0; i--) { var node = ToTile(path[i]); if (HasLineOfSight(start, node)) { target = path[i]; break; } } return target; } private bool HasLineOfSight(Point a, Point b) => HasLineOfSightSupercover(a, b); private bool HasLineOfSightSupercover(Point a, Point b) { if (!IsWalkable(a.X, a.Y) || !IsWalkable(b.X, b.Y)) return false; int x0 = a.X, y0 = a.Y; int x1 = b.X, y1 = b.Y; int dx = Math.Abs(x1 - x0); int dy = Math.Abs(y1 - y0); int sx = x0 < x1 ? 1 : -1; int sy = y0 < y1 ? 1 : -1; int x = x0, y = y0; if (dx >= dy) { int err = dx / 2; for (int i = 0; i < dx; i++) { x += sx; err -= dy; if (err < 0) { if (!IsWalkable(x, y + sy)) return false; y += sy; err += dx; } if (!IsWalkable(x, y)) return false; } } else { int err = dy / 2; for (int i = 0; i < dy; i++) { y += sy; err -= dx; if (err < 0) { if (!IsWalkable(x + sx, y)) return false; x += sx; err += dy; } if (!IsWalkable(x, y)) return false; } } return true; } private Point ToTile(LogicVector2 pos) => new Point(Math.Clamp(pos.X / TileSize, 0, _width - 1), Math.Clamp(pos.Y / TileSize, 0, _height - 1)); private LogicVector2 ToWorld(Point p) => new LogicVector2(p.X * TileSize + TileSize / 2, p.Y * TileSize + TileSize / 2); private static byte[,] Dilate(byte[,] grid, int r) { int w = grid.GetLength(0); int h = grid.GetLength(1); if (r <= 0) { var copy = new byte[w, h]; Array.Copy(grid, copy, grid.Length); return copy; } var res = new byte[w, h]; for (int x = 0; x < w; x++) for (int y = 0; y < h; y++) res[x, y] = 1; for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { if (grid[x, y] == 0) { for (int dx = -r; dx <= r; dx++) { for (int dy = -r; dy <= r; dy++) { if (Math.Max(Math.Abs(dx), Math.Abs(dy)) > r) continue; int xx = x + dx, yy = y + dy; if (xx >= 0 && yy >= 0 && xx < w && yy < h) res[xx, yy] = 0; } } } } } return res; } private readonly struct PathNode { public Point Pos { get; } public int F { get; } public PathNode(Point pos, int f) { Pos = pos; F = f; } } private readonly struct Point { public int X { get; } public int Y { get; } public Point(int x, int y) { X = x; Y = y; } public override bool Equals(object obj) => obj is Point o && X == o.X && Y == o.Y; public override int GetHashCode() => (X * 397) ^ Y; } } }