using UnityEngine;
using System.Collections;
using UnityEngine.AI;
public class OperatorAI : MonoBehaviour
{
public Transform player;
[SerializeField] Animator animator;
public LayerMask groundMask, playerMask;
[SerializeField] public NavMeshAgent navMeshAgent;
[SerializeField] private enum AIState { Idle, Chase, Attack, Disorient, Alert }
[SerializeField] private AIState currentState;
[SerializeField] float chaseRange;
[SerializeField] float attackRange;
[SerializeField] float walkPointRange = 20;
private float chaseRangeSquared;
private float attackRangeSquared;
[SerializeField] private bool chasingPlayer = false;
[Header("State Vars")]
[SerializeField] bool pathAvailable;
NavMeshPath navMeshPath;
[Header("Idle State Variables")]
public Vector3 walkPoint;
public Vector2 idleDelay = new Vector2(3.15f, 5);
[SerializeField] bool walkPointSet, hasStopped;
public bool hasSetWalkPoints = false;
[SerializeField] Transform[] fixedWalkPoints;
[SerializeField] int idleAnims = 1;
public delegate void PlayerDetected();
public event PlayerDetected OnPlayerDetected;
private float disorientDuration;
private float disorientTimer;
private Vector3 lastKnownPlayerLocation;
private State currentStateInstance;
//-------------------------------------------------------------------------------------
#region Main Stuff
private void Awake()
{
navMeshPath = new NavMeshPath();
}
private void Start()
{
currentState = AIState.Idle;
chaseRangeSquared = Mathf.Pow(chaseRange, 2f);
attackRangeSquared = Mathf.Pow(attackRange, 2f);
// Create instances of the states
State idleState = new IdleState(this);
State chaseState = new ChaseState(this);
State attackState = new AttackState(this);
State disorientState = new DisorientState(this);
State alertState = new AlertState(this);
// Set the initial state
currentStateInstance = idleState;
SetStateIdle(); //maybe setting it twice would work
}
private void Update()
{
currentStateInstance.Update();
}
private void LateUpdate()
{
animator.SetBool("isWalking", navMeshAgent.velocity.magnitude >= 0.75f);
}
private void ChangeState(State newState)
{
if (currentStateInstance != null)
{
currentStateInstance.Exit();
}
currentStateInstance = newState;
currentStateInstance.Enter();
}
private bool IsPlayerInRange(float rangeSquared)
{
// Calculate the squared distance between the enemy and the player
Vector3 playerPosition = player.position;
Vector3 enemyPosition = transform.position;
float distanceSquared = (playerPosition - enemyPosition).sqrMagnitude;
// Check if the squared distance is within the desired range
return distanceSquared <= rangeSquared;
}
private bool IsPlayerInAttackRange()
{
// Calculate the distance between the enemy and the player
float distance = Vector3.Distance(transform.position, player.position);
// Check if the player is within the attack range
return distance <= attackRange;
}
bool CalculateNewPath()
{
navMeshAgent.CalculatePath(walkPoint, navMeshPath);
if (navMeshPath.status != NavMeshPathStatus.PathComplete) return false; else return true;
}
public void StartDisorient(float duration)
{
if (currentState != AIState.Disorient)
{
currentState = AIState.Disorient;
disorientDuration = duration;
disorientTimer = Time.time + duration;
StartCoroutine(DisorientCoroutine());
}
}
private IEnumerator DisorientCoroutine()
{
while (Time.time < disorientTimer)
{
yield return null;
}
SetStateAlert();
}
void SearchWalkPoint()
{
if (hasSetWalkPoints)
{
int randomPoint = Random.Range(0, fixedWalkPoints.Length);
walkPoint = fixedWalkPoints[randomPoint].position;
}
else
{
float randomZ = Random.Range(-walkPointRange, walkPointRange);
float randomX = Random.Range(-walkPointRange, walkPointRange);
walkPoint = new Vector3(transform.position.x + randomX, transform.position.y, transform.position.z + randomZ);
}
if (Physics.Raycast(walkPoint, -transform.up, 1.7f, groundMask))
{
walkPointSet = true;
}
if (CalculateNewPath() == true) pathAvailable = true; else pathAvailable = false;
}
public void SetStateIdle()
{
ChangeState(new IdleState(this));
}
public void SetStateChase()
{
ChangeState(new ChaseState(this));
}
public void SetStateAttack()
{
ChangeState(new AttackState(this));
}
public void SetStateDisorient()
{
ChangeState(new DisorientState(this));
}
public void SetStateAlert()
{
ChangeState(new AlertState(this));
}
public bool IsChasingPlayer()
{
return chasingPlayer;
}
public void StartChasingPlayer()
{
chasingPlayer = true;
}
public void StopChasingPlayer()
{
chasingPlayer = false;
}
#endregion
//=--=-=-=--------------------------------------=========================================----------
#region State Classes
private abstract class State
{
protected OperatorAI owner;
public State(OperatorAI owner)
{
this.owner = owner;
}
public abstract void Enter(); // When state gets entered into
public abstract void Update(); // While state runs
public abstract void Exit(); // When state gets exited from
}
//-----------------------------------------------------------------------------------------
// Idle
private class IdleState : State
{
public IdleState(OperatorAI owner) : base(owner) { }
public override void Enter()
{
Debug.Log("Idling");
}
public override void Update()
{
if (!owner.walkPointSet || !owner.pathAvailable) owner.SearchWalkPoint();
Vector3 distToWalkPoint = owner.transform.position - owner.walkPoint;
if (distToWalkPoint.magnitude <= 1f)
{
owner.walkPointSet = false;
}
// Transition to the chase state if the player is within a certain range
if (owner.IsPlayerInRange(owner.chaseRangeSquared) && !owner.IsChasingPlayer())
{
// Trigger the player detection event
owner.OnPlayerDetected?.Invoke();
owner.SetStateChase();
}
if (owner.walkPointSet && owner.pathAvailable && !owner.hasStopped)
{
owner.hasStopped = true;
owner.StartCoroutine(IdleToDestination(owner.walkPoint, owner.idleDelay.x, owner.idleDelay.y));
return;
}
}
IEnumerator IdleToDestination(Vector3 position, float delayTimeStart = 0f, float delayTimeEnd = 0f)
{
float _delay = Random.Range(delayTimeStart, delayTimeEnd);
int _idle = Random.Range(0, owner.idleAnims);
owner.animator.SetFloat("IdleAnim", _idle);
yield return new WaitForSeconds(_delay);
owner.navMeshAgent.SetDestination(position);
owner.hasStopped = false;
}
public override void Exit()
{
// Perform exit behavior
}
}
//-----------------------------------------------------------------------------------------
// Chase
private class ChaseState : State
{
public ChaseState(OperatorAI owner) : base(owner) { }
public override void Enter()
{
// Perform enter behavior
Debug.Log("Chasing");
owner.StartChasingPlayer(); // Set the chasing flag
}
public override void Update()
{
owner.navMeshAgent.SetDestination(owner.player.position);
// Transition to the attack state if the player is within attack range
if (owner.IsPlayerInAttackRange())
{
owner.SetStateAttack();
}
// Transition back to the idle state if the player moves out of range
else if (!owner.IsPlayerInRange(owner.chaseRangeSquared))
{
owner.StopChasingPlayer(); // Clear the chasing flag
owner.SetStateIdle();
}
}
public override void Exit()
{
owner.StopChasingPlayer();
}
}
//-----------------------------------------------------------------------------------------
// Attack
private class AttackState : State
{
public AttackState(OperatorAI owner) : base(owner) { }
public override void Enter()
{
Debug.Log("Attacking");
owner.animator.SetBool("isAttacking",true);
owner.navMeshAgent.SetDestination(owner.transform.position);
}
public override void Update()
{
// Transition back to the chase state if the player moves out of attack range
if (!owner.IsPlayerInAttackRange())
{
owner.SetStateChase();
}
}
public override void Exit()
{
owner.animator.SetBool("isAttacking",false);
}
}
//-----------------------------------------------------------------------------------------
// Disorient
private class DisorientState : State
{
private bool disorientFinished = false;
public DisorientState(OperatorAI owner) : base(owner) { }
public override void Enter()
{
// Perform enter behavior
Debug.Log("Disoriented");
owner.animator.SetBool("hasBeenFlashed",true);
owner.SetStateAlert();
owner.lastKnownPlayerLocation = owner.player.position; // Store the last known player location
}
public override void Update()
{
if (Time.time >= owner.disorientTimer)
{
disorientFinished = true;
}
}
public override void Exit()
{
owner.animator.SetBool("hasBeenFlashed",false);
}
public bool IsDisorientFinished()
{
return disorientFinished;
}
}
//-----------------------------------------------------------------------------------------
// Alert
private class AlertState : State
{
public AlertState(OperatorAI owner) : base(owner) { }
private NavMeshAgent navMeshAgent;
public override void Enter()
{
// Perform enter behavior
Debug.Log("Alert");
owner.animator.SetBool("isAlerted",true);
// Example: Use a NavMeshAgent to navigate towards the location
navMeshAgent = owner.navMeshAgent;
navMeshAgent.SetDestination(owner.lastKnownPlayerLocation);
Debug.Log("Moving to last known player location: " + owner.lastKnownPlayerLocation);
}
public override void Update()
{
if (owner.currentState != AIState.Disorient)
{
owner.SetStateIdle();
return;
}
if (owner.currentState == AIState.Disorient)
{
DisorientState disorientState = owner.currentStateInstance as DisorientState;
if (disorientState != null && disorientState.IsDisorientFinished())
{
owner.SetStateAlert();
navMeshAgent.SetDestination(owner.lastKnownPlayerLocation);
}
}
if (owner.IsPlayerInRange(owner.chaseRange))
{
owner.SetStateChase();
}
}
public override void Exit()
{
Debug.Log("Never knew we uhh we uhh");
}
}
// For easy debugging ofc
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, attackRange);
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(transform.position, chaseRange);
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, walkPointRange);
try
{
Gizmos.DrawLine(gameObject.transform.position, player.position);
}
catch { };
}
#endregion
}