public NullableVector3 FindCover(Transform attacker, NavMeshAgent na, float coverCheckRadius, int numberOfChecks, LayerMask coverCriteria)
    {
        NullableVector3 cover     = null;
        NavMeshPath     coverPath = null;

        for (int i = 0; i < numberOfChecks; i++)
        {
            // Obtains a random position within a certain vicinity of the agent
            Vector3    randomPosition = ai.transform.position + Random.insideUnitSphere * coverCheckRadius;
            NavMeshHit coverCheck;
            // Checks if there is an actual point on the navmesh close to the randomly selected position
            if (NavMesh.SamplePosition(randomPosition, out coverCheck, na.height * 2, NavMesh.AllAreas))
            {
                if (AIFunction.LineOfSight(coverCheck.position, attacker, coverCriteria) == false) // If line of sight is not established
                {
                    // Ensures that the agent can actually move to the cover position.
                    NavMeshPath newPathToTest = new NavMeshPath();
                    if (na.CalculatePath(coverCheck.position, newPathToTest))
                    {
                        // Checks if the new cover position is easier to get to than the old one.
                        if (cover == null || AIFunction.NavMeshPathLength(newPathToTest) < AIFunction.NavMeshPathLength(coverPath)) // Use OR statement, and check navmesh path cost between transform.position and the cover point currently being checked.
                        {
                            // If so, new cover position is established, and navmesh path is stored for next comparison
                            cover     = new NullableVector3(coverCheck.position);
                            coverPath = newPathToTest;
                        }
                    }
                }
            }
        }

        return(cover);
    }
    public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if (currentCover != null)
        {
            Debug.Log("Enemy is taking cover");

            if (AIFunction.LineOfSight(currentCover.position, attacker, coverCriteria))
            {
                Debug.Log("Cover is compromised");
                // Reset and find a new cover point
                currentCover = null;
                //currentCover = FindCover(attacker, ai.na, coverCheckRadius, numberOfChecks, coverCriteria);
            }
        }

        if (currentCover == null)
        {
            currentCover = FindCover(attacker, ai.na, coverCheckRadius, numberOfChecks, coverCriteria);
        }

        if (currentCover != null)
        {
            Debug.Log("Setting destination for " + ai.name + " from TakeCover behaviour");
            ai.na.SetDestination(currentCover.position);
        }
    }
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateEnter(animator, stateInfo, layerIndex);

        attacker = ai.currentTarget.transform; // Placeholder, replace with a better way to detect an attacker

        currentCover = FindCover(attacker, ai.na, coverCheckRadius, numberOfChecks, coverCriteria);
    }
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateEnter(animator, stateInfo, layerIndex);

        Debug.Log(ai.name + " is entering dodge state");

        safeLocation = FindAvoidPosition();
        // If the NPC cannot find a safe location, end the state machine behaviour. The NPC will be forced to tank the attack, as there is nowhere for it to dodge to.
        if (safeLocation == null)
        {
            EndDodge();
        }
    }
    public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        #region Check validity of destination, and return null if no longer valid
        if (currentDestination != null)
        {
            float distance = Vector3.Distance(currentDestination.position, TargetLocation); // Obtains distance between agent and target

            Debug.DrawLine(TargetLocation, currentDestination.position, new Color(1, 0.5f, 0));

            // Checks if the AI can no longer see the target from their desired position, or if they are too close or too far
            bool lineOfSightLost = !AIFunction.LineOfSightCheckWithExceptions(TargetLocation, currentDestination.position, coverCriteria, ai.characterData.health.hitboxes, ai.currentTarget.health.hitboxes);
            bool tooClose        = distance < minimumMoveRange;
            bool tooFar          = distance > maximumMoveRange;
            if (lineOfSightLost || tooClose || tooFar)
            {
                // The AI cannot engage with the current target, position is nulled.
                currentDestination = null;
            }
            //Debug.Log(ai.name + " pathfinding status: " + lineOfSightLost + ", " + tooClose + ", " + tooFar);
        }
        #endregion

        #region Find new destination if there is none
        // If there is no position assigned, search for one.
        if (currentDestination == null)
        {
            //Debug.Log("Finding destination normally");
            currentDestination = FindFollowPosition(TargetLocation, minimumDestinationRange, maximumDestinationRange, numberOfChecks);

            //Debug.Log("Current destination = " + currentDestination);
        }
        #endregion

        #region Travel to destination
        // If a valid position is found the agent must travel to it.
        if (currentDestination != null)
        {
            Debug.DrawLine(ai.transform.position, currentDestination.position, Color.magenta);
            ai.na.SetDestination(currentDestination.position);
            Debug.DrawLine(currentDestination.position, ai.na.destination, new Color(1, 0.75f, 0), 1);
        }
        #endregion
    }
    public NullableVector3 FindAvoidPosition() // Should I have separate versions for dodging vs. taking cover? I might need this based on whether the enemy is aggressive or skittish
    {
        NullableVector3 newSafeLocation = null;
        float           maxPathDistance = maxMoveDistance;


        Vector3[] test = AIFunction.PositionsAroundPointInSpiral(ai.transform.position + ai.transform.up, ai.transform.up, minCheckRadius, maxCheckRadius, 3, 15);

        for (int i = 0; i < numberOfChecks; i++)
        {
            // Samples a random position around the target, normalises it, and randomises the magnitude to a point in betwen the min and max radii.
            // If I simply multiply by the max check radius, the position may be too close.
            Vector3 randomPosition = ai.transform.position + Random.insideUnitSphere.normalized * Random.Range(minCheckRadius, maxCheckRadius);
            // Normalising the Random.insideUnitSphere ensures the magnitude (and therefore distance value) is always 1, and the distance is calculated correctly.

            NavMeshHit followCheck;
            // Checks if there is an actual point on the navmesh close to the randomly selected position
            if (NavMesh.SamplePosition(randomPosition, out followCheck, ai.na.height * 2, NavMesh.AllAreas))
            {
                // Checks if the location is safe from the attack
                if (ai.attackToDodge.IsPositionSafe(followCheck.position, ai.characterData.health.hitboxes) == false)
                {
                    // Creates a new path for reference
                    NavMeshPath nmp = new NavMeshPath();
                    // If the agent can actually move to the location
                    if (ai.na.CalculatePath(followCheck.position, nmp) && nmp.status == NavMeshPathStatus.PathComplete)
                    {
                        float distance = AIFunction.NavMeshPathLength(nmp); // Check navmesh path cost between transform.position and the cover point currently being checked.

                        if (distance < maxPathDistance)                     // If the NPC is willing to travel that distance to the dodge zone, or if this distance is shorter than that of the previous route.
                        {
                            // If so, new cover position is established, and navmesh path is stored for next comparison
                            newSafeLocation = new NullableVector3(followCheck.position);
                            maxPathDistance = distance;
                        }
                    }
                }
            }
        }

        return(newSafeLocation);
    }
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateEnter(animator, stateInfo, layerIndex);

        dodgeLocation = Dodge();
    }
    public NullableVector3 FindFollowPosition(Vector3 targetPosition, float minimumRange, float maximumRange, int numberOfChecks)
    {
        NullableVector3 newFollowPosition = null;
        float           currentPathLength = float.MaxValue;

        /*
         * int noValidPosition = 0;
         * int noLineOfSight = 0;
         * int noValidPath = 0;
         * int incompletePath = 0;
         * int inefficient = 0;
         */
        for (int i = 0; i < numberOfChecks; i++)
        {
            // Samples a random position around the target, outside minimumRange and inside maximumRange.
            // Normalising the Random.insideUnitSphere magnitude then multiplying it again by another random value allows me to ensure that the distance of the point is random but still within certain distance requirements.
            Vector3 randomPosition = targetPosition + Random.insideUnitSphere.normalized * Random.Range(minimumRange, maximumRange);

            NavMeshHit followCheck;
            // Checks if there is an actual point on the navmesh close to the randomly selected position
            if (NavMesh.SamplePosition(randomPosition, out followCheck, ai.na.height * 2, NavMesh.AllAreas))
            {
                Debug.DrawLine(randomPosition, followCheck.position, Colours.darkGreen);

                // Checks if line of sight is established between the new position and target. The agent is still pursuing and attacking the target, but they are just staying cautious.
                if (AIFunction.LineOfSightCheckWithExceptions(targetPosition, followCheck.position, coverCriteria, ai.characterData.health.hitboxes, ai.currentTarget.health.hitboxes))
                {
                    // Ensures that a path can be sampled to the destination.
                    NavMeshPath nmp = new NavMeshPath();
                    if (ai.na.CalculatePath(followCheck.position, nmp))
                    {
                        // Checks to make sure a whole path can be found.
                        // This if statement could probably be added to the previous one with the && operator
                        if (nmp.status == NavMeshPathStatus.PathComplete)
                        {
                            // Checks if the new cover position is a shorter route to get to than the old one.
                            // Use OR statement, and check navmesh path cost between transform.position and the cover point currently being checked.
                            float length = AIFunction.NavMeshPathLength(nmp);
                            if (newFollowPosition == null || length < currentPathLength)
                            {
                                // If so, new cover position is established, and navmesh path is stored for next comparison
                                newFollowPosition = new NullableVector3(followCheck.position);
                                currentPathLength = length;
                            }

                            /*
                             * else
                             * {
                             *  inefficient++;
                             * }
                             */
                        }

                        /*
                         * else
                         * {
                         *  incompletePath++;
                         * }
                         */
                    }

                    /*
                     * else
                     * {
                     *  noValidPath++;
                     * }
                     */
                }

                /*
                 * else
                 * {
                 *  noLineOfSight++;
                 * }
                 */
            }

            /*
             * else
             * {
             *  noValidPosition++;
             * }
             */
        }

        //Debug.Log("Path result - " + (newFollowPosition != null).ToString() + ", " + noValidPosition + " sampling errors, " + noLineOfSight + " line of sight fails, " + noValidPath + " pathing fails, " + incompletePath + " incomplete paths, and " + inefficient + " inefficient paths.");

        return(newFollowPosition);
    }