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 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 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);
    }