- Players can now die (finally) - Solo players have a single cheat death per scene (reviving automatically after the first death) - group players have revive mechanic, where a player faints when his health gets to 0, creating a revive circle around him, other players can stand on it to revive him. if after x seconds they don't get revived they bleed out and stay perma death until scene changes or all players die - Multiple VFX added using post processing for cheat death, fainting, reviving, and perma death events. - stopped players from moving and pressing keys when dead - enemies now change target if they try to attack a dead/fainted target.
395 lines
11 KiB
C#
395 lines
11 KiB
C#
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<Taggable> possibleTargets = new List<Taggable>();
|
|
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<Taggable>();
|
|
agent = GetComponentInParent<NavMeshAgent>();
|
|
photonView = GetComponentInParent<PhotonView>();
|
|
health = GetComponent<Health>();
|
|
mana = GetComponent<Mana>();
|
|
abilityPriorityManager = GetComponentInChildren<NPCAbilityPriorityManager>();
|
|
abilityCooldownTracker = GetComponentInChildren<AbilityCooldownTracker>();
|
|
|
|
animatorController = GetComponentInChildren<NPCAnimatorControllerBase>();
|
|
|
|
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
|
|
/// <summary>
|
|
/// </summary>
|
|
/// <returns>possibleTargets.Count > 0</returns>
|
|
public virtual bool HasAvailableTargets()
|
|
{
|
|
return possibleTargets.Count > 0;
|
|
}
|
|
/// <summary>
|
|
/// </summary>
|
|
/// <returns>currentTarget != null</returns>
|
|
public virtual bool HasTarget()
|
|
{
|
|
return currentTarget != null;
|
|
}
|
|
/// <summary>
|
|
/// </summary>
|
|
/// <returns>possibleTargets.Contains(currentTarget)</returns>
|
|
public virtual bool HasVisionOfCurrentTarget()
|
|
{
|
|
return possibleTargets.Contains(currentTarget);
|
|
}
|
|
/// <summary>
|
|
/// </summary>
|
|
/// <returns>agent.destination != null</returns>
|
|
public virtual bool HasDestination()
|
|
{
|
|
return agent.destination != null;
|
|
}
|
|
/// <summary>
|
|
/// </summary>
|
|
/// <param name="positionCheck"></param>
|
|
/// <param name="distanceCheck"></param>
|
|
/// <returns> Distance between agent.transform.position and positionCheck less than distanceCheck</returns>
|
|
public virtual bool IsCloseEnough(Vector3 positionCheck, float distanceCheck)
|
|
{
|
|
return Vector3.Distance(agent.transform.position, positionCheck) <= distanceCheck;
|
|
}
|
|
/// <summary>
|
|
/// </summary>
|
|
/// <returns> counter >= timeBetweenAttacks</returns>
|
|
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<Health>();
|
|
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<Health>();
|
|
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);
|
|
}
|
|
|
|
}
|