Пример #1
0
        /// <summary>
        /// Roughly how far away another region is by nearest node
        /// </summary>
        /// <param name="otherRegion"></param>
        /// <returns></returns>
        public float Distance(PathfindingRegion otherRegion)
        {
            // JANK
            var xDistance = otherRegion.OriginNode.TileRef.X - OriginNode.TileRef.X;
            var yDistance = otherRegion.OriginNode.TileRef.Y - OriginNode.TileRef.Y;

            if (xDistance > 0)
            {
                xDistance -= Width;
            }
            else if (xDistance < 0)
            {
                xDistance = Math.Abs(xDistance + otherRegion.Width);
            }

            if (yDistance > 0)
            {
                yDistance -= Height;
            }
            else if (yDistance < 0)
            {
                yDistance = Math.Abs(yDistance + otherRegion.Height);
            }

            return(PathfindingHelpers.OctileDistance(xDistance, yDistance));
        }
Пример #2
0
 /// <summary>
 /// Can the given args can traverse this region?
 /// </summary>
 /// <param name="reachableArgs"></param>
 /// <returns></returns>
 public bool RegionTraversable(ReachableArgs reachableArgs)
 {
     // The assumption is that all nodes in a region have the same pathfinding traits
     // As such we can just use the origin node for checking.
     return(PathfindingHelpers.Traversable(reachableArgs.CollisionMask, reachableArgs.Access,
                                           OriginNode));
 }
        /// <summary>
        /// Check to see if the node is a jump point (only works for cardinal directions)
        /// </summary>
        private bool IsCardinalJumpPoint(Direction direction, PathfindingNode currentNode)
        {
            PathfindingNode openNeighborOne;
            PathfindingNode closedNeighborOne;
            PathfindingNode openNeighborTwo;
            PathfindingNode closedNeighborTwo;

            switch (direction)
            {
            case Direction.North:
                openNeighborOne   = currentNode.GetNeighbor(Direction.NorthEast);
                closedNeighborOne = currentNode.GetNeighbor(Direction.East);

                openNeighborTwo   = currentNode.GetNeighbor(Direction.NorthWest);
                closedNeighborTwo = currentNode.GetNeighbor(Direction.West);
                break;

            case Direction.East:
                openNeighborOne   = currentNode.GetNeighbor(Direction.NorthEast);
                closedNeighborOne = currentNode.GetNeighbor(Direction.North);

                openNeighborTwo   = currentNode.GetNeighbor(Direction.SouthEast);
                closedNeighborTwo = currentNode.GetNeighbor(Direction.South);
                break;

            case Direction.South:
                openNeighborOne   = currentNode.GetNeighbor(Direction.SouthEast);
                closedNeighborOne = currentNode.GetNeighbor(Direction.East);

                openNeighborTwo   = currentNode.GetNeighbor(Direction.SouthWest);
                closedNeighborTwo = currentNode.GetNeighbor(Direction.West);
                break;

            case Direction.West:
                openNeighborOne   = currentNode.GetNeighbor(Direction.NorthWest);
                closedNeighborOne = currentNode.GetNeighbor(Direction.North);

                openNeighborTwo   = currentNode.GetNeighbor(Direction.SouthWest);
                closedNeighborTwo = currentNode.GetNeighbor(Direction.South);
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            if ((closedNeighborOne == null || !PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, closedNeighborOne)) &&
                (openNeighborOne != null && PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, openNeighborOne)))
            {
                return(true);
            }

            if ((closedNeighborTwo == null || !PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, closedNeighborTwo)) &&
                (openNeighborTwo != null && PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, openNeighborTwo)))
            {
                return(true);
            }

            return(false);
        }
Пример #4
0
        /// <summary>
        /// Gets all of the tiles in range that can we access
        /// </summary>
        /// If you want Dikstra then add distances.
        /// Doesn't use the JobQueue as it will generally be encapsulated by other jobs
        /// <param name="pathfindingArgs"></param>
        /// <param name="range"></param>
        /// <param name="fromStart">Whether we traverse from the starting tile or the end tile</param>
        /// <returns></returns>
        public static IEnumerable <PathfindingNode> GetNodesInRange(PathfindingArgs pathfindingArgs, bool fromStart = true)
        {
            var pathfindingSystem = EntitySystem.Get <PathfindingSystem>();
            // Don't need a priority queue given not looking for shortest path
            var             openTiles   = new Queue <PathfindingNode>();
            var             closedTiles = new HashSet <TileRef>();
            PathfindingNode startNode;

            if (fromStart)
            {
                startNode = pathfindingSystem.GetNode(pathfindingArgs.Start);
            }
            else
            {
                startNode = pathfindingSystem.GetNode(pathfindingArgs.End);
            }

            PathfindingNode currentNode;

            openTiles.Enqueue(startNode);

            while (openTiles.Count > 0)
            {
                currentNode = openTiles.Dequeue();

                foreach (var neighbor in currentNode.GetNeighbors())
                {
                    // No distances stored so can just check closed tiles here
                    if (closedTiles.Contains(neighbor.TileRef))
                    {
                        continue;
                    }
                    closedTiles.Add(currentNode.TileRef);

                    // So currently tileCost gets the octile distance between the 2 so we'll also use that for our range check
                    var tileCost  = PathfindingHelpers.GetTileCost(pathfindingArgs, startNode, neighbor);
                    var direction = PathfindingHelpers.RelativeDirection(neighbor, currentNode);

                    if (tileCost == null ||
                        tileCost > pathfindingArgs.Proximity ||
                        !PathfindingHelpers.DirectionTraversable(pathfindingArgs.CollisionMask, pathfindingArgs.Access, currentNode, direction))
                    {
                        continue;
                    }

                    openTiles.Enqueue(neighbor);
                    yield return(neighbor);
                }
            }
        }
Пример #5
0
        public async Task Test()
        {
            var server = StartServerDummyTicker();

            server.Assert(() =>
            {
                var pathfindingSystem = EntitySystem.Get <PathfindingSystem>();
                var mapMan            = IoCManager.Resolve <IMapManager>();

                // Setup
                var mapId  = mapMan.CreateMap(new MapId(1));
                var gridId = new GridId(2);
                mapMan.CreateGrid(mapId, gridId);
                var chunkTile = mapMan.GetGrid(gridId).GetTileRef(new Vector2i(0, 0));
                var chunk     = pathfindingSystem.GetChunk(chunkTile);
                Assert.That(chunk.Nodes.Length == PathfindingChunk.ChunkSize * PathfindingChunk.ChunkSize);

                // Neighbors
                var chunkNeighbors = chunk.GetNeighbors().ToList();
                Assert.That(chunkNeighbors.Count == 0);
                var neighborChunkTile = mapMan.GetGrid(gridId).GetTileRef(new Vector2i(PathfindingChunk.ChunkSize, PathfindingChunk.ChunkSize));
                var neighborChunk     = pathfindingSystem.GetChunk(neighborChunkTile);
                chunkNeighbors        = chunk.GetNeighbors().ToList();
                Assert.That(chunkNeighbors.Count == 1);

                // Directions
                Assert.That(PathfindingHelpers.RelativeDirection(neighborChunk, chunk) == Direction.NorthEast);
                Assert.That(PathfindingHelpers.RelativeDirection(chunk.Nodes[0, 1], chunk.Nodes[0, 0]) == Direction.North);

                // Nodes
                var node          = chunk.Nodes[1, 1];
                var nodeNeighbors = node.GetNeighbors().ToList();
                Assert.That(nodeNeighbors.Count == 8);

                // Bottom-left corner with no chunk neighbor
                node          = chunk.Nodes[0, 0];
                nodeNeighbors = node.GetNeighbors().ToList();
                Assert.That(nodeNeighbors.Count == 3);

                // Given we have 1 NE neighbor then NE corner should have 4 neighbors due to the 1 extra from the neighbor chunk
                node          = chunk.Nodes[PathfindingChunk.ChunkSize - 1, PathfindingChunk.ChunkSize - 1];
                nodeNeighbors = node.GetNeighbors().ToList();
                Assert.That(nodeNeighbors.Count == 4);
            });
            await server.WaitIdleAsync();
        }
Пример #6
0
        protected override async Task <Queue <TileRef>?> Process()
        {
            if (_startNode == null ||
                _endNode == null ||
                Status == JobStatus.Finished)
            {
                return(null);
            }

            // If we couldn't get a nearby node that's good enough
            if (!PathfindingHelpers.TryEndNode(ref _endNode, _pathfindingArgs))
            {
                return(null);
            }

            var frontier  = new PriorityQueue <ValueTuple <float, PathfindingNode> >(new PathfindingComparer());
            var costSoFar = new Dictionary <PathfindingNode, float>();
            var cameFrom  = new Dictionary <PathfindingNode, PathfindingNode>();

            PathfindingNode?currentNode = null;

            frontier.Add((0.0f, _startNode));
            costSoFar[_startNode] = 0.0f;
            var routeFound = false;
            var count      = 0;

            while (frontier.Count > 0)
            {
                // Handle whether we need to pause if we've taken too long
                count++;
                if (count % 20 == 0 && count > 0)
                {
                    await SuspendIfOutOfTime();

                    if (_startNode == null || _endNode == null)
                    {
                        return(null);
                    }
                }

                // Actual pathfinding here
                (_, currentNode) = frontier.Take();
                if (currentNode.Equals(_endNode))
                {
                    routeFound = true;
                    break;
                }

                foreach (var nextNode in currentNode.GetNeighbors())
                {
                    // If tile is untraversable it'll be null
                    var tileCost = PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, nextNode);
                    if (tileCost == null)
                    {
                        continue;
                    }

                    // So if we're going NE then that means either N or E needs to be free to actually get there
                    var direction = PathfindingHelpers.RelativeDirection(nextNode, currentNode);
                    if (!PathfindingHelpers.DirectionTraversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, currentNode, direction))
                    {
                        continue;
                    }

                    // f = g + h
                    // gScore is distance to the start node
                    // hScore is distance to the end node
                    var gScore = costSoFar[currentNode] + tileCost.Value;
                    if (costSoFar.TryGetValue(nextNode, out var nextValue) && gScore >= nextValue)
                    {
                        continue;
                    }

                    cameFrom[nextNode]  = currentNode;
                    costSoFar[nextNode] = gScore;
                    // pFactor is tie-breaker where the fscore is otherwise equal.
                    // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
                    // There's other ways to do it but future consideration
                    // The closer the fScore is to the actual distance then the better the pathfinder will be
                    // (i.e. somewhere between 1 and infinite)
                    // Can use hierarchical pathfinder or whatever to improve the heuristic but this is fine for now.
                    var fScore = gScore + PathfindingHelpers.OctileDistance(_endNode, nextNode) * (1.0f + 1.0f / 1000.0f);
                    frontier.Add((fScore, nextNode));
                }
            }

            if (!routeFound)
            {
                return(null);
            }

            DebugTools.AssertNotNull(currentNode);

            var route = PathfindingHelpers.ReconstructPath(cameFrom, currentNode !);

            if (route.Count == 1)
            {
                return(null);
            }

#if DEBUG
            // Need to get data into an easier format to send to the relevant clients
            if (DebugRoute != null && route.Count > 0)
            {
                var debugCameFrom = new Dictionary <TileRef, TileRef>(cameFrom.Count);
                var debugGScores  = new Dictionary <TileRef, float>(costSoFar.Count);
                foreach (var(node, parent) in cameFrom)
                {
                    debugCameFrom.Add(node.TileRef, parent.TileRef);
                }

                foreach (var(node, score) in costSoFar)
                {
                    debugGScores.Add(node.TileRef, score);
                }

                var debugRoute = new SharedAiDebug.AStarRouteDebug(
                    _pathfindingArgs.Uid,
                    route,
                    debugCameFrom,
                    debugGScores,
                    DebugTime);

                DebugRoute.Invoke(debugRoute);
            }
#endif

            return(route);
        }
Пример #7
0
        /// <summary>
        /// Check to see if the node is a jump point (only works for cardinal directions)
        /// </summary>
        private bool IsCardinalJumpPoint(Direction direction, PathfindingNode currentNode)
        {
            PathfindingNode?openNeighborOne   = null;
            PathfindingNode?closedNeighborOne = null;
            PathfindingNode?openNeighborTwo   = null;
            PathfindingNode?closedNeighborTwo = null;

            switch (direction)
            {
            case Direction.North:
                foreach (var neighbor in currentNode.GetNeighbors())
                {
                    var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
                    switch (neighborDirection)
                    {
                    case Direction.NorthEast:
                        openNeighborOne = neighbor;
                        break;

                    case Direction.East:
                        closedNeighborOne = neighbor;
                        break;

                    case Direction.NorthWest:
                        openNeighborTwo = neighbor;
                        break;

                    case Direction.West:
                        closedNeighborTwo = neighbor;
                        break;
                    }
                }
                break;

            case Direction.East:
                foreach (var neighbor in currentNode.GetNeighbors())
                {
                    var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
                    switch (neighborDirection)
                    {
                    case Direction.NorthEast:
                        openNeighborOne = neighbor;
                        break;

                    case Direction.North:
                        closedNeighborOne = neighbor;
                        break;

                    case Direction.SouthEast:
                        openNeighborTwo = neighbor;
                        break;

                    case Direction.South:
                        closedNeighborTwo = neighbor;
                        break;
                    }
                }
                break;

            case Direction.South:
                foreach (var neighbor in currentNode.GetNeighbors())
                {
                    var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
                    switch (neighborDirection)
                    {
                    case Direction.SouthEast:
                        openNeighborOne = neighbor;
                        break;

                    case Direction.East:
                        closedNeighborOne = neighbor;
                        break;

                    case Direction.SouthWest:
                        openNeighborTwo = neighbor;
                        break;

                    case Direction.West:
                        closedNeighborTwo = neighbor;
                        break;
                    }
                }
                break;

            case Direction.West:
                foreach (var neighbor in currentNode.GetNeighbors())
                {
                    var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
                    switch (neighborDirection)
                    {
                    case Direction.NorthWest:
                        openNeighborOne = neighbor;
                        break;

                    case Direction.North:
                        closedNeighborOne = neighbor;
                        break;

                    case Direction.SouthWest:
                        openNeighborTwo = neighbor;
                        break;

                    case Direction.South:
                        closedNeighborTwo = neighbor;
                        break;
                    }
                }
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            if ((closedNeighborOne == null || !PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, closedNeighborOne)) &&
                openNeighborOne != null && PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, openNeighborOne))
            {
                return(true);
            }

            if ((closedNeighborTwo == null || !PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, closedNeighborTwo)) &&
                openNeighborTwo != null && PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, openNeighborTwo))
            {
                return(true);
            }

            return(false);
        }
Пример #8
0
        protected override async Task <Queue <TileRef>?> Process()
        {
            // VERY similar to A*; main difference is with the neighbor tiles you look for jump nodes instead
            if (_startNode == null ||
                _endNode == null)
            {
                return(null);
            }

            // If we couldn't get a nearby node that's good enough
            if (!PathfindingHelpers.TryEndNode(ref _endNode, _pathfindingArgs))
            {
                return(null);
            }

            var openTiles   = new PriorityQueue <ValueTuple <float, PathfindingNode> >(new PathfindingComparer());
            var gScores     = new Dictionary <PathfindingNode, float>();
            var cameFrom    = new Dictionary <PathfindingNode, PathfindingNode>();
            var closedTiles = new HashSet <PathfindingNode>();

#if DEBUG
            var jumpNodes = new HashSet <PathfindingNode>();
#endif

            PathfindingNode?currentNode = null;
            openTiles.Add((0, _startNode));
            gScores[_startNode] = 0.0f;
            var routeFound = false;
            var count      = 0;

            while (openTiles.Count > 0)
            {
                count++;

                // JPS probably getting a lot fewer nodes than A* is
                if (count % 5 == 0 && count > 0)
                {
                    await SuspendIfOutOfTime();
                }

                (_, currentNode) = openTiles.Take();
                if (currentNode.Equals(_endNode))
                {
                    routeFound = true;
                    break;
                }

                foreach (var node in currentNode.GetNeighbors())
                {
                    var direction = PathfindingHelpers.RelativeDirection(node, currentNode);
                    var jumpNode  = GetJumpPoint(currentNode, direction, _endNode);

                    if (jumpNode != null && !closedTiles.Contains(jumpNode))
                    {
                        closedTiles.Add(jumpNode);
#if DEBUG
                        jumpNodes.Add(jumpNode);
#endif
                        // GetJumpPoint should already check if we can traverse to the node
                        var tileCost = PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, jumpNode);

                        if (tileCost == null)
                        {
                            throw new InvalidOperationException();
                        }

                        var gScore = gScores[currentNode] + tileCost.Value;

                        if (gScores.TryGetValue(jumpNode, out var nextValue) && gScore >= nextValue)
                        {
                            continue;
                        }

                        cameFrom[jumpNode] = currentNode;
                        gScores[jumpNode]  = gScore;
                        // pFactor is tie-breaker where the fscore is otherwise equal.
                        // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
                        // There's other ways to do it but future consideration
                        var fScore = gScores[jumpNode] + PathfindingHelpers.OctileDistance(_endNode, jumpNode) * (1.0f + 1.0f / 1000.0f);
                        openTiles.Add((fScore, jumpNode));
                    }
                }
            }

            if (!routeFound)
            {
                return(null);
            }

            DebugTools.AssertNotNull(currentNode);

            var route = PathfindingHelpers.ReconstructJumpPath(cameFrom, currentNode !);

            if (route.Count == 1)
            {
                return(null);
            }

#if DEBUG
            // Need to get data into an easier format to send to the relevant clients
            if (DebugRoute != null && route.Count > 0)
            {
                var debugJumpNodes = new HashSet <TileRef>(jumpNodes.Count);

                foreach (var node in jumpNodes)
                {
                    debugJumpNodes.Add(node.TileRef);
                }

                var debugRoute = new SharedAiDebug.JpsRouteDebug(
                    _pathfindingArgs.Uid,
                    route,
                    debugJumpNodes,
                    DebugTime);

                DebugRoute.Invoke(debugRoute);
            }
#endif

            return(route);
        }
Пример #9
0
        private bool IsDiagonalJumpPoint(Direction direction, PathfindingNode currentNode)
        {
            // If we're going diagonally need to check all cardinals.
            // I tried just casting direction ints and offsets to make it smaller but brain no worky.
            // From NorthEast we check (Closed / Open) S - SE, W - NW

            PathfindingNode?openNeighborOne   = null;
            PathfindingNode?closedNeighborOne = null;
            PathfindingNode?openNeighborTwo   = null;
            PathfindingNode?closedNeighborTwo = null;

            switch (direction)
            {
            case Direction.NorthEast:
                foreach (var neighbor in currentNode.GetNeighbors())
                {
                    var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
                    switch (neighborDirection)
                    {
                    case Direction.SouthEast:
                        openNeighborOne = neighbor;
                        break;

                    case Direction.South:
                        closedNeighborOne = neighbor;
                        break;

                    case Direction.NorthWest:
                        openNeighborTwo = neighbor;
                        break;

                    case Direction.West:
                        closedNeighborTwo = neighbor;
                        break;
                    }
                }
                break;

            case Direction.SouthEast:
                foreach (var neighbor in currentNode.GetNeighbors())
                {
                    var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
                    switch (neighborDirection)
                    {
                    case Direction.NorthEast:
                        openNeighborOne = neighbor;
                        break;

                    case Direction.North:
                        closedNeighborOne = neighbor;
                        break;

                    case Direction.SouthWest:
                        openNeighborTwo = neighbor;
                        break;

                    case Direction.West:
                        closedNeighborTwo = neighbor;
                        break;
                    }
                }
                break;

            case Direction.SouthWest:
                foreach (var neighbor in currentNode.GetNeighbors())
                {
                    var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
                    switch (neighborDirection)
                    {
                    case Direction.NorthWest:
                        openNeighborOne = neighbor;
                        break;

                    case Direction.North:
                        closedNeighborOne = neighbor;
                        break;

                    case Direction.SouthEast:
                        openNeighborTwo = neighbor;
                        break;

                    case Direction.East:
                        closedNeighborTwo = neighbor;
                        break;
                    }
                }
                break;

            case Direction.NorthWest:
                foreach (var neighbor in currentNode.GetNeighbors())
                {
                    var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
                    switch (neighborDirection)
                    {
                    case Direction.SouthWest:
                        openNeighborOne = neighbor;
                        break;

                    case Direction.South:
                        closedNeighborOne = neighbor;
                        break;

                    case Direction.NorthEast:
                        openNeighborTwo = neighbor;
                        break;

                    case Direction.East:
                        closedNeighborTwo = neighbor;
                        break;
                    }
                }
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            if ((closedNeighborOne == null || PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, closedNeighborOne) == null) &&
                openNeighborOne != null && PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, openNeighborOne) != null)
            {
                return(true);
            }

            if ((closedNeighborTwo == null || PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, closedNeighborTwo) == null) &&
                openNeighborTwo != null && PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, openNeighborTwo) != null)
            {
                return(true);
            }

            return(false);
        }
Пример #10
0
        private PathfindingNode?GetJumpPoint(PathfindingNode currentNode, Direction direction, PathfindingNode endNode)
        {
            var count = 0;

            while (count < 1000)
            {
                count++;
                PathfindingNode?nextNode = null;
                foreach (var node in currentNode.GetNeighbors())
                {
                    if (PathfindingHelpers.RelativeDirection(node, currentNode) == direction)
                    {
                        nextNode = node;
                        break;
                    }
                }

                // We'll do opposite DirectionTraversable just because of how the method's setup
                // Nodes should be 2-way anyway.
                if (nextNode == null ||
                    PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, nextNode) == null)
                {
                    return(null);
                }

                if (nextNode == endNode)
                {
                    return(endNode);
                }

                // Horizontal and vertical are treated the same i.e.
                // They only check in their specific direction
                // (So Going North means you check NorthWest and NorthEast to see if we're a jump point)

                // Diagonals also check the cardinal directions at the same time at the same time

                // See https://harablog.wordpress.com/2011/09/07/jump-point-search/ for original description
                switch (direction)
                {
                case Direction.East:
                    if (IsCardinalJumpPoint(direction, nextNode))
                    {
                        return(nextNode);
                    }

                    break;

                case Direction.NorthEast:
                    if (IsDiagonalJumpPoint(direction, nextNode))
                    {
                        return(nextNode);
                    }

                    if (GetJumpPoint(nextNode, Direction.North, endNode) != null || GetJumpPoint(nextNode, Direction.East, endNode) != null)
                    {
                        return(nextNode);
                    }

                    break;

                case Direction.North:
                    if (IsCardinalJumpPoint(direction, nextNode))
                    {
                        return(nextNode);
                    }

                    break;

                case Direction.NorthWest:
                    if (IsDiagonalJumpPoint(direction, nextNode))
                    {
                        return(nextNode);
                    }

                    if (GetJumpPoint(nextNode, Direction.North, endNode) != null || GetJumpPoint(nextNode, Direction.West, endNode) != null)
                    {
                        return(nextNode);
                    }

                    break;

                case Direction.West:
                    if (IsCardinalJumpPoint(direction, nextNode))
                    {
                        return(nextNode);
                    }

                    break;

                case Direction.SouthWest:
                    if (IsDiagonalJumpPoint(direction, nextNode))
                    {
                        return(nextNode);
                    }

                    if (GetJumpPoint(nextNode, Direction.South, endNode) != null || GetJumpPoint(nextNode, Direction.West, endNode) != null)
                    {
                        return(nextNode);
                    }

                    break;

                case Direction.South:
                    if (IsCardinalJumpPoint(direction, nextNode))
                    {
                        return(nextNode);
                    }

                    break;

                case Direction.SouthEast:
                    if (IsDiagonalJumpPoint(direction, nextNode))
                    {
                        return(nextNode);
                    }

                    if (GetJumpPoint(nextNode, Direction.South, endNode) != null || GetJumpPoint(nextNode, Direction.East, endNode) != null)
                    {
                        return(nextNode);
                    }

                    break;

                default:
                    throw new ArgumentOutOfRangeException(nameof(direction), direction, null);
                }

                currentNode = nextNode;
            }

            Logger.WarningS("pathfinding", "Recursion found in JPS pathfinder");
            return(null);
        }
Пример #11
0
        protected override async Task <Queue <TileRef> > Process()
        {
            if (_startNode == null ||
                _endNode == null ||
                Status == JobStatus.Finished)
            {
                return(null);
            }

            // If we couldn't get a nearby node that's good enough
            if (!PathfindingHelpers.TryEndNode(ref _endNode, _pathfindingArgs))
            {
                return(null);
            }

            var openTiles   = new PriorityQueue <ValueTuple <float, PathfindingNode> >(new PathfindingComparer());
            var gScores     = new Dictionary <PathfindingNode, float>();
            var cameFrom    = new Dictionary <PathfindingNode, PathfindingNode>();
            var closedTiles = new HashSet <PathfindingNode>();

            PathfindingNode currentNode = null;

            openTiles.Add((0.0f, _startNode));
            gScores[_startNode] = 0.0f;
            var routeFound = false;
            var count      = 0;

            while (openTiles.Count > 0)
            {
                count++;

                if (count % 20 == 0 && count > 0)
                {
                    await SuspendIfOutOfTime();
                }

                if (_startNode == null || _endNode == null)
                {
                    return(null);
                }

                (_, currentNode) = openTiles.Take();
                if (currentNode.Equals(_endNode))
                {
                    routeFound = true;
                    break;
                }

                closedTiles.Add(currentNode);

                foreach (var(direction, nextNode) in currentNode.Neighbors)
                {
                    if (closedTiles.Contains(nextNode))
                    {
                        continue;
                    }

                    // If tile is untraversable it'll be null
                    var tileCost = PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, nextNode);

                    if (tileCost == null || !PathfindingHelpers.DirectionTraversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, currentNode, direction))
                    {
                        continue;
                    }

                    var gScore = gScores[currentNode] + tileCost.Value;

                    if (gScores.TryGetValue(nextNode, out var nextValue) && gScore >= nextValue)
                    {
                        continue;
                    }

                    cameFrom[nextNode] = currentNode;
                    gScores[nextNode]  = gScore;
                    // pFactor is tie-breaker where the fscore is otherwise equal.
                    // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
                    // There's other ways to do it but future consideration
                    var fScore = gScores[nextNode] + PathfindingHelpers.OctileDistance(_endNode, nextNode) * (1.0f + 1.0f / 1000.0f);
                    openTiles.Add((fScore, nextNode));
                }
            }

            if (!routeFound)
            {
                return(null);
            }

            var route = PathfindingHelpers.ReconstructPath(cameFrom, currentNode);

            if (route.Count == 1)
            {
                return(null);
            }

#if DEBUG
            // Need to get data into an easier format to send to the relevant clients
            if (DebugRoute != null && route.Count > 0)
            {
                var debugCameFrom    = new Dictionary <TileRef, TileRef>(cameFrom.Count);
                var debugGScores     = new Dictionary <TileRef, float>(gScores.Count);
                var debugClosedTiles = new HashSet <TileRef>(closedTiles.Count);

                foreach (var(node, parent) in cameFrom)
                {
                    debugCameFrom.Add(node.TileRef, parent.TileRef);
                }

                foreach (var(node, score) in gScores)
                {
                    debugGScores.Add(node.TileRef, score);
                }

                foreach (var node in closedTiles)
                {
                    debugClosedTiles.Add(node.TileRef);
                }

                var debugRoute = new SharedAiDebug.AStarRouteDebug(
                    _pathfindingArgs.Uid,
                    route,
                    debugCameFrom,
                    debugGScores,
                    debugClosedTiles,
                    DebugTime);

                DebugRoute.Invoke(debugRoute);
            }
#endif

            return(route);
        }
        private bool IsDiagonalJumpPoint(Direction direction, PathfindingNode currentNode)
        {
            // If we're going diagonally need to check all cardinals.
            // I just just using casts int casts and offset to make it smaller but brain no workyand it wasn't working.
            // From NorthEast we check (Closed / Open) S - SE, W - NW

            PathfindingNode openNeighborOne;
            PathfindingNode closedNeighborOne;
            PathfindingNode openNeighborTwo;
            PathfindingNode closedNeighborTwo;

            switch (direction)
            {
            case Direction.NorthEast:
                openNeighborOne   = currentNode.GetNeighbor(Direction.SouthEast);
                closedNeighborOne = currentNode.GetNeighbor(Direction.South);

                openNeighborTwo   = currentNode.GetNeighbor(Direction.NorthWest);
                closedNeighborTwo = currentNode.GetNeighbor(Direction.West);
                break;

            case Direction.SouthEast:
                openNeighborOne   = currentNode.GetNeighbor(Direction.NorthEast);
                closedNeighborOne = currentNode.GetNeighbor(Direction.North);

                openNeighborTwo   = currentNode.GetNeighbor(Direction.SouthWest);
                closedNeighborTwo = currentNode.GetNeighbor(Direction.West);
                break;

            case Direction.SouthWest:
                openNeighborOne   = currentNode.GetNeighbor(Direction.NorthWest);
                closedNeighborOne = currentNode.GetNeighbor(Direction.North);

                openNeighborTwo   = currentNode.GetNeighbor(Direction.SouthEast);
                closedNeighborTwo = currentNode.GetNeighbor(Direction.East);
                break;

            case Direction.NorthWest:
                openNeighborOne   = currentNode.GetNeighbor(Direction.SouthWest);
                closedNeighborOne = currentNode.GetNeighbor(Direction.South);

                openNeighborTwo   = currentNode.GetNeighbor(Direction.NorthEast);
                closedNeighborTwo = currentNode.GetNeighbor(Direction.East);
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            if ((closedNeighborOne == null || PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, closedNeighborOne) == null) &&
                openNeighborOne != null && PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, openNeighborOne) != null)
            {
                return(true);
            }

            if ((closedNeighborTwo == null || PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, closedNeighborTwo) == null) &&
                openNeighborTwo != null && PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, openNeighborTwo) != null)
            {
                return(true);
            }

            return(false);
        }