//breadth-first fill algorithm, funny note: years ago when I was learning pathfinding I misread the name to be breath-first, hence the name
    public static void FillBreath(ref BreathArea breath, ref List <ThingController> monstersList, bool cullByTrueDistance = false)
    {
        int   maxDistance = breath.maxDistance;
        int   arraySize   = breath.size;
        Vec2I StartPoint  = breath.position;

        breath.Invalidate();

        if (GetHeat(StartPoint).x == -1)
        {
            return;
        }

        //init arrays
        bool[,] closedCheck = new bool[arraySize, arraySize];
        bool[,] openCheck   = new bool[arraySize, arraySize];

        //set start point
        SingleLinkedList <Vec2I> openList = new SingleLinkedList <Vec2I>();

        openList.InsertFront(StartPoint);
        openCheck[maxDistance, maxDistance] = true;

        breath.exist[maxDistance, maxDistance]     = true;
        breath.steps[maxDistance, maxDistance]     = 0;
        breath.distance[maxDistance, maxDistance]  = 0;
        breath.direction[maxDistance, maxDistance] = 0;

        List <ThingController> monsters = new List <ThingController>();

        SingleLinkedList <int> randomNeighbors = new SingleLinkedList <int>();
        int maxStepDistance = maxDistance * 10;

        while (openList.Count > 0)
        {
            //get top of heap
            Vec2I current         = openList.RemoveHead();
            int   ax              = current.x - StartPoint.x + maxDistance;
            int   ay              = current.y - StartPoint.y + maxDistance;
            int   currentDistance = breath.distance[ax, ay];
            int   currentSteps    = breath.steps[ax, ay];
            closedCheck[ax, ay] = true;

            TheGrid.GetNearbyMonsters(current, 0).Perform((n) => { monsters.Add(n.Data); });

            if (cullByTrueDistance)
            {
                if (currentDistance >= maxStepDistance)
                {
                    continue;
                }
            }

            Vector3 currentHeat = GetHeat(current);

            //no propagation through solids
            if (currentHeat.x >= 1f)
            {
                if (current != StartPoint)
                {
                    continue;
                }
            }

            //don't hassle, shuffle
            for (int i = 1; i < 9; i++)
            {
                if (Random.value > .5f)
                {
                    randomNeighbors.InsertFront(i);
                }
                else
                {
                    randomNeighbors.InsertBack(i);
                }
            }

            while (randomNeighbors.Count > 0)
            {
                int   i        = randomNeighbors.RemoveHead();
                Vec2I neighbor = current + Vec2I.directions[i];

                //calculate array position
                int arrayX = neighbor.x - StartPoint.x + maxDistance;
                int arrayY = neighbor.y - StartPoint.y + maxDistance;

                //cull disallowed
                if (AxMath.RogueDistance(neighbor, StartPoint) > maxDistance)
                {
                    continue;
                }
                if (openCheck[arrayX, arrayY])
                {
                    continue;
                }
                if (closedCheck[arrayX, arrayY])
                {
                    continue;
                }

                if (!CanPath(neighbor))
                {
                    continue;
                }

                openList.InsertBack(neighbor);
                openCheck[arrayX, arrayY] = true;

                //reverse direction to point towards the source of breath
                int p = i + 4;
                if (p > 8)
                {
                    p -= 8;
                }

                breath.exist[arrayX, arrayY]     = true;
                breath.direction[arrayX, arrayY] = p;
                breath.distance[arrayX, arrayY]  = currentDistance + StepDistance(i);
                breath.steps[arrayX, arrayY]     = currentSteps + 1;
            }
        }

        monstersList = monsters;
    }
    //same as previous, but take into account movement cost of cells
    public static void FillPlayerBreath(ref BreathArea breath, ref List <ThingController> monstersList, bool cullByTrueDistance = false)
    {
        int   maxDistance = breath.maxDistance;
        int   arraySize   = breath.size;
        Vec2I StartPoint  = breath.position;

        breath.Invalidate();
        monstersList.Clear();

        if (GetHeat(StartPoint).x == -1)
        {
            return;
        }

        //init arrays
        bool[,] closedCheck   = new bool[arraySize, arraySize];
        bool[,] openCheck     = new bool[arraySize, arraySize];
        PathStep[,] openArray = new PathStep[arraySize, arraySize];

        //set start point
        BinaryHeap <PathStep> openList = new BinaryHeap <PathStep>(arraySize * arraySize);

        openList.Add(new PathStep(StartPoint, 0));
        openCheck[maxDistance, maxDistance] = true;

        breath.exist[maxDistance, maxDistance]     = true;
        breath.steps[maxDistance, maxDistance]     = 0;
        breath.distance[maxDistance, maxDistance]  = 0;
        breath.direction[maxDistance, maxDistance] = 0;

        List <ThingController> monsters = new List <ThingController>();

        int maxStepDistance = maxDistance * 10;

        while (openList.ItemCount > 0)
        {
            //get top of heap
            PathStep current         = openList.RemoveFirst();
            int      ax              = current.position.x - StartPoint.x + maxDistance;
            int      ay              = current.position.y - StartPoint.y + maxDistance;
            int      currentDistance = breath.distance[ax, ay];
            int      currentSteps    = breath.steps[ax, ay];
            closedCheck[ax, ay] = true;

            TheGrid.GetNearbyMonsters(current.position, 0).Perform((n) => { monsters.Add(n.Data); });

            if (cullByTrueDistance)
            {
                if (currentDistance >= maxStepDistance)
                {
                    continue;
                }
            }

            Vector3 currentHeat = GetHeat(current.position);

            //no propagation through solids
            if (currentHeat.x >= 1f)
            {
                if (current.position != StartPoint)
                {
                    continue;
                }
            }

            for (int i = 1; i < 9; i++)
            {
                Vec2I neighbor = current.position + Vec2I.directions[i];

                //calculate array position
                int arrayX = neighbor.x - StartPoint.x + maxDistance;
                int arrayY = neighbor.y - StartPoint.y + maxDistance;

                //cull disallowed
                if (AxMath.RogueDistance(neighbor, StartPoint) > maxDistance)
                {
                    continue;
                }
                if (openCheck[arrayX, arrayY])
                {
                    continue;
                }
                if (closedCheck[arrayX, arrayY])
                {
                    continue;
                }

                if (!CanPath(neighbor))
                {
                    continue;
                }

                if (HasLedge(current.position, neighbor, false))
                {
                    continue;
                }

                //calculate cost
                int travelCost = current.travelCost + GetTravelCost(neighbor);
                int heuristic  = AxMath.WeightedDistance(neighbor, StartPoint);
                int fullCost   = travelCost + heuristic;

                //reverse direction to point towards the source of breath
                int p = i + 4;
                if (p > 8)
                {
                    p -= 8;
                }

                //check if we can update parent to better
                if (openCheck[arrayX, arrayY])
                {
                    if (openArray[arrayX, arrayY].travelCost > travelCost)
                    {
                        openArray[arrayX, arrayY].travelCost = travelCost;
                        openArray[arrayX, arrayY].heuristic  = heuristic;
                        openArray[arrayX, arrayY].fullCost   = fullCost;
                        breath.direction[arrayX, arrayY]     = p;
                        breath.distance[arrayX, arrayY]      = currentDistance + StepDistance(i);
                        breath.steps[arrayX, arrayY]         = currentSteps + 1;
                        openList.UpdateItem(openArray[arrayX, arrayY]);
                        continue;
                    }
                    else
                    {
                        continue;
                    }
                }

                //priority sorted by heap
                PathStep step = new PathStep(neighbor, travelCost, heuristic);
                openList.Add(step);
                openArray[arrayX, arrayY] = step;
                openCheck[arrayX, arrayY] = true;

                breath.exist[arrayX, arrayY]     = true;
                breath.direction[arrayX, arrayY] = p;
                breath.distance[arrayX, arrayY]  = currentDistance + StepDistance(i);
                breath.steps[arrayX, arrayY]     = currentSteps + 1;
            }
        }

        monstersList = monsters;
    }
    //optimized A* pathfinding
    public static bool GetPath(Vec2I StartPoint, Vec2I EndPoint, int maxDistance, out Vec2I[] path)
    {
        if (StartPoint == EndPoint)
        {
            path    = new Vec2I[1];
            path[0] = EndPoint;
            return(true);
        }

        path = null;

        if (AxMath.RogueDistance(StartPoint, EndPoint) > maxDistance)
        {
            return(false);
        }
        if (GetHeat(StartPoint).x == -1)
        {
            return(false);
        }
        if (GetHeat(EndPoint).x == -1)
        {
            return(false);
        }

        //init arrays
        int arraySize = maxDistance * 2 + 1;

        bool[,] closedCheck   = new bool[arraySize, arraySize];
        bool[,] openCheck     = new bool[arraySize, arraySize];
        Vec2I[,] parents      = new Vec2I[arraySize, arraySize];
        PathStep[,] openArray = new PathStep[arraySize, arraySize];

        //set start point
        BinaryHeap <PathStep> openList = new BinaryHeap <PathStep>(arraySize * arraySize);

        openList.Add(new PathStep(StartPoint, AxMath.WeightedDistance(StartPoint, EndPoint)));
        openCheck[maxDistance, maxDistance] = true;
        parents[maxDistance, maxDistance]   = StartPoint;

        bool found = false;

        while (openList.ItemCount > 0)
        {
            //get top of heap
            PathStep current = openList.RemoveFirst();
            closedCheck[current.position.x - StartPoint.x + maxDistance, current.position.y - StartPoint.y + maxDistance] = true;

            foreach (Vec2I neighbor in current.position.neighbors)
            {
                //calculate array position
                int arrayX = neighbor.x - StartPoint.x + maxDistance;
                int arrayY = neighbor.y - StartPoint.y + maxDistance;

                //cull disallowed
                if (AxMath.RogueDistance(neighbor, StartPoint) > maxDistance)
                {
                    continue;
                }
                if (closedCheck[arrayX, arrayY])
                {
                    continue;
                }

                //found target
                if (neighbor == EndPoint)
                {
                    parents[arrayX, arrayY] = current.position;
                    found = true;
                    goto finalize;
                }

                if (!CanPath(neighbor))
                {
                    continue;
                }

                //calculate cost
                int travelCost = current.travelCost + AxMath.WeightedDistance(current.position, neighbor);
                int heuristic  = AxMath.WeightedDistance(neighbor, EndPoint);
                int fullCost   = travelCost + heuristic;

                //check if we can update parent to better
                if (openCheck[arrayX, arrayY])
                {
                    if (openArray[arrayX, arrayY].travelCost > travelCost)
                    {
                        openArray[arrayX, arrayY].travelCost = travelCost;
                        openArray[arrayX, arrayY].heuristic  = heuristic;
                        openArray[arrayX, arrayY].fullCost   = fullCost;
                        parents[arrayX, arrayY] = current.position;
                        openList.UpdateItem(openArray[arrayX, arrayY]);
                        continue;
                    }
                    else
                    {
                        continue;
                    }
                }

                //priority sorted by heap
                PathStep step = new PathStep(neighbor, travelCost, heuristic);
                openList.Add(step);
                openCheck[arrayX, arrayY] = true;
                openArray[arrayX, arrayY] = step;
                parents[arrayX, arrayY]   = current.position;
            }
        }

finalize:
        if (found)
        {
            SingleLinkedList <Vec2I> list = new SingleLinkedList <Vec2I>();

            Vec2I current = EndPoint;
            while (current != StartPoint)
            {
                list.InsertFront(current);
                current = parents[current.x - StartPoint.x + maxDistance, current.y - StartPoint.y + maxDistance];
            }
            //list.InsertFront(current); //adds the starting point to the path
            path = list.ToArray();
            return(true);
        }

        return(false);
    }