using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Combat;
using RPG.Core;
using RPG.Movement;
using RPG.Attributes;
using System;
using GameDevTV.Utils;
namespace RPG.Control
{
public class AIController : MonoBehaviour
{
[SerializeField] float defaultChaseDistance = 5f;
[SerializeField] float chaseDistance = 5f;
[SerializeField] float suspicionTime = 5f;
[SerializeField] float WaypointDwellTime = 3f;
[SerializeField] float agroCooldownTime = 3f;
[SerializeField] float shoutDistance = 10f;
bool aggrevated = false;
// makes sure patrolSpeedFraction can only be between 0 and 1.
// incase we give it an absurd value.
[Range(0,1)]
// 20 % of maxspeed.
[SerializeField] float patrolSpeedFraction = 0.2f;
// must add patrolpath gameobject with patrolpath script in unity
// since each patrolpath is different and must be attached to only one enemy.
[SerializeField] PatrolPath patrolPath;
[SerializeField] float waypointTolerance = 1f;
Fighter fighter;
GameObject player;
Health health;
Mover mover;
LazyValue<Vector3> guardPosition;
// when we start the game, last time since guard saw player is very large, no delay.
float timeSinceLastSawPlayer = Mathf.Infinity;
// dwell at waypoints.
float timeSinceArrivedAtWaypoint = Mathf.Infinity;
// timer for when we stop aggro. start as not aggrevated.
float timeSinceAggrevated = Mathf.Infinity;
// waypoint guard should walk to.
int currentWaypointIndex = 0;
// set up all states in awake. if another start method tries to use a public method in this script
// that uses any of these states, they might not be there if initialized in start.
private void Awake()
{
fighter = GetComponent<Fighter>();
player = GameObject.FindWithTag("Player");
health = GetComponent<Health>();
mover = GetComponent<Mover>();
// must be ready before start if we use guardposition in another class start method.
guardPosition = new LazyValue<Vector3>(GetGuardPosition);
}
private Vector3 GetGuardPosition()
{
return transform.position;
}
private void Start()
{
// sets the start position of the guard.
guardPosition.ForceInit();
}
private void Update()
{
// if enemy is dead do nothing. Cant move when dead.
if (health.IsDead()) return;
// make enemy chase and attack player. Enemy are withing the range of the player and player is not dead or null.
if (isAggrevated() && fighter.CanAttack(player))
{
// reset suspicion variable when we attack.
timeSinceLastSawPlayer = 0;
// move thowards enemy to attack.
AttackBehaviour();
}
// suspicion state. guard should wait before returning to post.
else if (timeSinceLastSawPlayer < suspicionTime)
{
SuspicionBehaviour();
// set chase distance back to original chase distance after we shot a projectile and have run outside of his chasedistance.
chaseDistance = defaultChaseDistance;
}
else
{
PatrolBehaviour();
}
// updates time every frame
timeSinceLastSawPlayer += Time.deltaTime;
timeSinceArrivedAtWaypoint += Time.deltaTime;
timeSinceAggrevated += Time.deltaTime;
print("AGGREVATED" + timeSinceAggrevated + aggrevated);
// If his no longer aggroed he can aggro again.
}
public void Aggrevate()
{
// starts aggro.
timeSinceAggrevated = 0;
}
private void PatrolBehaviour()
{
// no patrol path, just use start position.
Vector3 nextPosition = guardPosition.value;
// if the guard has a patrol path.
if(patrolPath != null)
{
// when we are close enough to a waypoint.
if (AtWaypoint())
{
timeSinceArrivedAtWaypoint = 0;
// we get the next waypoint index.
CycleWayPoint();
}
// we get the vector, the position of the waypoint.
nextPosition = GetCurrentWaypoint();
}
// moves guard to next waypoint after guard has dwelled. Enemy will stop
// following us when we are out of range or dead. And moves back to nextposition, last waypoint.
if (timeSinceArrivedAtWaypoint > WaypointDwellTime)
{
mover.StartMoveAction(nextPosition, patrolSpeedFraction);
}
}
// when we are close to a waypoint return true.
private bool AtWaypoint()
{
float distanceToWaypoint = Vector3.Distance(transform.position, GetCurrentWaypoint());
return distanceToWaypoint < waypointTolerance;
}
// Gets next index. if the guard is already at last waypoint, currentWayPointIndex
// is the last waypoint, we get the first index for the first waypoint.
private void CycleWayPoint()
{
currentWaypointIndex = patrolPath.GetNextIndex(currentWaypointIndex);
}
// Get waypoints position at index.
private Vector3 GetCurrentWaypoint()
{
return patrolPath.GetWayPoint(currentWaypointIndex);
}
private void SuspicionBehaviour()
{
// cancel action, stops to be suspicious.
GetComponent<ActionScheduler>().CancelCurrentAction();
}
private void AttackBehaviour()
{
fighter.Attack(player);
// only aggrevate nearby enemies if you havent already done so.
if (aggrevated == false)
{
AggrevateNearbyEnemies();
aggrevated = true;
}
else if (aggrevated == true && timeSinceAggrevated > agroCooldownTime)
{
aggrevated = false;
timeSinceAggrevated = Mathf.Infinity;
}
}
// Aggro enemies within a radius of aggrevated enemy.
private void AggrevateNearbyEnemies()
{
// Find all enemies within a certain radius. Centered on enemy, radius out from enemy, direction of sphere(just around enemy),
// max distance(0 dont wont to move sphere anywhere no distance just a radius around enemy) we get an array of raycasthits.
RaycastHit[] hits = Physics.SphereCastAll(transform.position, shoutDistance, Vector3.up, 0);
foreach (RaycastHit hit in hits)
{
AIController ai = hit.transform.GetComponent<AIController>();
// If what we hit has not an AIController we continue with for loop.
if (ai == null) continue;
// else we aggrevate enemy.
ai.Aggrevate();
}
}
// Enemy is aggrevated or not. True if aggrevated.
private bool isAggrevated()
{
// distance between enemy and player.
float distanceToPlayer = Vector3.Distance(player.transform.position, transform.position);
// returns true if we are withing chasedistance or if enemy is still aggrevated.
return distanceToPlayer < chaseDistance || timeSinceAggrevated < agroCooldownTime;
}
// unity will call this when you want to draw gizmos.
// draws a sphere around the enemy.
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.blue;
// draw a lined sphere. center of sphere, radius.
Gizmos.DrawWireSphere(transform.position, chaseDistance);
}
public void SetChaseDistance(float weaponRange)
{
chaseDistance = weaponRange;
}
}
}