using Photon.Pun; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; using UnityEngine.Events; public class NPCControllerBase : MonoBehaviour { [Header("Settings:")] [SerializeField] protected float projectileRange; [SerializeField] protected float meleeRange; [SerializeField] protected float distanceToChangePatrolDestination; [SerializeField] protected float patrolAgentSpeed; [SerializeField] protected float chasingAgentSpeed; [SerializeField] protected float timeBetweenAttacks = 1f; //change in editor public float ProjectileRange => projectileRange; public float MeleeRange => meleeRange; [HideInInspector] public PhotonView photonView; [HideInInspector] public Taggable myTag; [HideInInspector] public NPCAnimatorControllerBase animatorController; [HideInInspector] public NPCAbilityPriorityManager abilityPriorityManager; [HideInInspector] public AbilityCooldownTracker abilityCooldownTracker; protected NavMeshAgent agent; public List possibleTargets = new List(); public Taggable currentTarget; protected BaseAbility ability; protected float targetDistance = float.MaxValue; protected float distance; protected Taggable resultTarget; protected Vector3 patrolDestination = new Vector3(); protected float counter = 0f; protected Health health; protected Mana mana; public Health Health => health; public Mana Mana => mana; protected bool isDead = false; protected bool waitingForAttackAnimation = false; protected Health possibleTargetHealth; public UnityEvent onPossibleTargetEnteredSight = new UnityEvent(); public UnityEvent onPossibleTargetExitedSight = new UnityEvent(); protected virtual void Awake() { myTag = GetComponentInParent(); agent = GetComponentInParent(); photonView = GetComponentInParent(); health = GetComponent(); mana = GetComponent(); abilityPriorityManager = GetComponentInChildren(); abilityCooldownTracker = GetComponentInChildren(); animatorController = GetComponentInChildren(); if (!photonView.IsMine) return; onPossibleTargetEnteredSight.AddListener(OnNewTargetIdentified); onPossibleTargetExitedSight.AddListener(OnPossibleExistingTargetLost); } protected virtual void Start() { if (!photonView.IsMine) return; isDead = false; counter = timeBetweenAttacks / 2f; health.onDeath.AddListener(OnDeath); } protected virtual void Update() { if (!photonView.IsMine) return; if (isDead) return; counter += Time.deltaTime; if (HasTarget()) { ChasingUpdate(); } else { PatrollingUpdate(); } } #region Checks /// /// /// possibleTargets.Count > 0 public virtual bool HasAvailableTargets() { return possibleTargets.Count > 0; } /// /// /// currentTarget != null public virtual bool HasTarget() { return currentTarget != null; } /// /// /// possibleTargets.Contains(currentTarget) public virtual bool HasVisionOfCurrentTarget() { return possibleTargets.Contains(currentTarget); } /// /// /// agent.destination != null public virtual bool HasDestination() { return agent.destination != null; } /// /// /// /// /// Distance between agent.transform.position and positionCheck less than distanceCheck public virtual bool IsCloseEnough(Vector3 positionCheck, float distanceCheck) { return Vector3.Distance(agent.transform.position, positionCheck) <= distanceCheck; } /// /// /// counter >= timeBetweenAttacks public virtual bool IsReadyToAttack() { return counter >= timeBetweenAttacks; } #endregion protected virtual void ResetCounterOnAttackPerformed() { counter = 0; } protected virtual Taggable GetClosestTarget() { targetDistance = float.MaxValue; resultTarget = null; Debug.Log("CLOSEST COUNT: " + possibleTargets.Count); for (int i = 0; i < possibleTargets.Count; i++) { possibleTargetHealth = possibleTargets[i].GetComponent(); if (possibleTargetHealth.GetCurrentValue() <= 0) continue; distance = Vector3.Distance(possibleTargets[i].transform.position, agent.transform.position); if (distance < targetDistance) { targetDistance = distance; resultTarget = possibleTargets[i]; } } return resultTarget; } protected virtual Taggable GetFurthestAwayTarget() { targetDistance = 0f; resultTarget = null; for (int i = 0; i < possibleTargets.Count; i++) { possibleTargetHealth = possibleTargets[i].GetComponent(); if (possibleTargetHealth.GetCurrentValue() <= 0) continue; distance = Vector3.Distance(possibleTargets[i].transform.position, agent.transform.position); if (distance > targetDistance) { targetDistance = distance; resultTarget = possibleTargets[i]; } } return resultTarget; } protected virtual void UpdateCurrentTarget(Taggable target) { if (target == null) { PatrolNewPosition(); return; } currentTarget = target; SetupAgentStats(currentTarget.transform.position, true); } protected virtual void UpdatePatrolTarget(Vector3 destination) { SetupAgentStats(destination); } protected virtual void SetupAgentStats(Vector3 destination, bool chasing = false) { if (isDead) return; agent.speed = chasing ? chasingAgentSpeed : patrolAgentSpeed; patrolDestination = destination; patrolDestination.y = 0f; agent.SetDestination(patrolDestination); } protected virtual void SetAgentMoving(bool isMoving) { if (isDead) return; agent.isStopped = !isMoving; } protected virtual void OnNewTargetIdentified() { if (HasTarget()) { OnNewTargetIdentifiedAndHasTarget(); } else //no current target { OnNewTargetIdentifiedAndNoCurrentTarget(); } } protected virtual void OnPossibleExistingTargetLost() { if (HasTarget()) { if (HasVisionOfCurrentTarget()) //current target inside sight (possibleTargets list) { OnPossibleTargetLostAndHasTargetAndVision(); } else //current target outside sight (possibleTargets list) { OnPossibleTargetLostAndHasTargetButNoVision(); } } else //no current target { if (HasAvailableTargets()) { OnPossibleTargetLostAndHasNoCurrentTargetButHasAvailableTargets(); } else //no available targets in sight { OnPossibleTargetLostAndHasNoCurrentTargetAndNoAvailableTargets(); } } } protected virtual void OnNewTargetIdentifiedAndHasTarget() { //someone entered sight, npc already has a target } protected virtual void OnNewTargetIdentifiedAndNoCurrentTarget() { //someone entered sight, npc does not have a target yet } protected virtual void OnPossibleTargetLostAndHasTargetAndVision() { //someone exited sight, npc already has target and vision } protected virtual void OnPossibleTargetLostAndHasTargetButNoVision() { //someone exited sight, npc already has target but no vision of it (possibly his target was the one getting out of sight) currentTarget = null; } protected virtual void OnPossibleTargetLostAndHasNoCurrentTargetButHasAvailableTargets() { //someone exited sight, npc has no target yet, there are available targets to pick } protected virtual void OnPossibleTargetLostAndHasNoCurrentTargetAndNoAvailableTargets() { //someone exited sight, npc has no target yet, there are NO available targets } protected virtual void TryAttack() { } protected virtual void ChasingUpdate() { if (IsReadyToAttack()) { TryAttack(); } else { SetupAgentStats(currentTarget.transform.position, true); if (agent.remainingDistance > agent.stoppingDistance) SetAgentMoving(true); else SetAgentMoving(false); } } protected virtual void PatrollingUpdate() { if (!HasAvailableTargets()) { if (currentTarget != null) { currentTarget = null; } if (agent.destination == null) { PatrolNewPosition(); } else if (agent.remainingDistance < distanceToChangePatrolDestination) { PatrolNewPosition(); } } else { Debug.Log("Patrolling update, available targets, waiting for sight to do its job"); } } public virtual void OnAttackAnimationEventTriggered() { //execute ability } public virtual void OnDeathAnimationEventTriggered() { DestroyAfterEffect(); } protected virtual void OnDeath() { photonView.RPC(nameof(RPC_OnDeath), RpcTarget.All); } [PunRPC] protected virtual void RPC_OnDeath(bool lootDropped = false) { if (isDead) return; Debug.Log($"{this.gameObject.name} died!"); isDead = true; agent.enabled = false; if (!photonView.IsMine) return; animatorController.SetDead(); } protected virtual void DestroyAfterEffect() { if (!photonView.IsMine) return; PhotonNetwork.Destroy(this.gameObject); } protected virtual void PatrolNewPosition() { agent.speed = patrolAgentSpeed; patrolDestination.x = Random.Range(-5, 5); patrolDestination.y = 0f; patrolDestination.z = Random.Range(-5, 5); UpdatePatrolTarget(transform.position + patrolDestination); SetAgentMoving(true); } }