Pedro Gomes 2773ef7d6e Player Death Update
- 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.
2024-07-20 19:49:14 +01:00

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