RiftMayhem/Assets/Scripts/Player/PlayerDeathManager.cs
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

281 lines
8.1 KiB
C#

using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerDeathManager : MonoBehaviour, IPunObservable
{
[SerializeField] private GameEvent onLocalPlayerCheatedDeath;
[SerializeField] private GameEvent onLocalPlayerFainted;
[SerializeField] private GameEvent onLocalPlayerPermaDeath;
[SerializeField] private GameEvent onLocalPlayerRevived;
[Space]
[SerializeField] private GameObject reviveProgressGO;
[SerializeField] private GameObject bleedOutProgressGO;
[SerializeField] private Image reviveProgressFill;
[SerializeField] private Image bleedOutProgressFill;
private bool hasUsedCheatDeath = false;
public bool HasUsedCheatDeath => hasUsedCheatDeath;
private bool isFainted = false;
public bool IsFainted => isFainted;
private float bleedOutTimer = 0f;
public float BleedOutTimer => bleedOutTimer;
private float reviveProgress = 0f;
public float ReviveProgress => reviveProgress;
PhotonView owner;
RiftPlayer ownerPlayer;
Health health;
SphereCollider reviveTrigger;
CharacterAnimatorController anim;
RiftPlayer possibleReviver;
public List<RiftPlayer> playersInsideReviveTrigger = new List<RiftPlayer>();
private void Awake()
{
owner = GetComponentInParent<PhotonView>();
ownerPlayer = GetComponentInParent<RiftPlayer>();
health = GetComponentInParent<Health>();
anim = ownerPlayer.GetComponentInChildren<CharacterAnimatorController>();
reviveTrigger = GetComponentInChildren<SphereCollider>();
reviveTrigger.transform.localScale = Vector3.one * GameConstants.CharacterBalancing.ReviveTriggerRadius * 2;
reviveTrigger.isTrigger = true;
reviveTrigger.gameObject.SetActive(false);
reviveProgressGO.SetActive(false);
bleedOutProgressGO.SetActive(false);
}
private void Start()
{
if (!owner.IsMine) return;
health.onDeath.AddListener(OnDeath);
}
private void OnDeath()
{
if (PhotonNetwork.CurrentRoom.PlayerCount <= 1)
{
HandleSoloDeath();
}
else
{
HandleGroupDeath();
}
}
private void HandleSoloDeath()
{
if (!hasUsedCheatDeath)
{
CheatDeath();
}
else
{
Die();
}
}
private void HandleGroupDeath()
{
EnterFaintedState();
}
private void CheatDeath()
{
hasUsedCheatDeath = true;
health.SetCurrentValue(health.GetMaxValue() * GameConstants.CharacterBalancing.SoloCheatDeathHealthPercent);
StartCoroutine(SoloInvulnerability());
if (!owner.IsMine) return;
onLocalPlayerCheatedDeath.Raise();
}
private IEnumerator SoloInvulnerability()
{
// Implement invulnerability logic
health.SetInvulnerabilityState(true);
yield return new WaitForSeconds(GameConstants.CharacterBalancing.SoloCheatDeathInvulnerabilityDuration);
// Remove invulnerability
health.SetInvulnerabilityState(false);
}
private void EnterFaintedState()
{
isFainted = true;
reviveTrigger.gameObject.SetActive(true);
// Disable player movement and abilities
// Change player visuals to "fainted" state
health.SetIsDeadState(true);
StartCoroutine(BleedOutTimerCoroutine());
if (!owner.IsMine) return;
reviveProgressGO.SetActive(true);
bleedOutProgressGO.SetActive(true);
anim.OnDeath();
onLocalPlayerFainted.Raise();
}
private IEnumerator BleedOutTimerCoroutine()
{
bleedOutTimer = GameConstants.CharacterBalancing.GroupBleedOutDuration;
bleedOutProgressFill.fillAmount = bleedOutTimer / GameConstants.CharacterBalancing.GroupBleedOutDuration;
while (bleedOutTimer > 0 && isFainted)
{
bleedOutTimer -= Time.deltaTime;
bleedOutProgressFill.fillAmount = bleedOutTimer / GameConstants.CharacterBalancing.GroupBleedOutDuration;
yield return null;
}
if (isFainted)
{
Die();
}
}
private void OnTriggerEnter(Collider other)
{
if (!isFainted) return;
possibleReviver = other.GetComponentInParent<RiftPlayer>();
if (possibleReviver == null) return;
if (possibleReviver == ownerPlayer) return;
if (playersInsideReviveTrigger.Contains(possibleReviver)) return;
Debug.Log($"R: added {possibleReviver.name} as a reviver");
playersInsideReviveTrigger.Add(possibleReviver);
}
private void Update()
{
if (!owner.IsMine)
{
reviveProgressGO.SetActive(reviveTrigger.gameObject.activeSelf);
bleedOutProgressGO.SetActive(reviveTrigger.gameObject.activeSelf);
reviveProgressFill.fillAmount = reviveProgress / GameConstants.CharacterBalancing.ReviveTime;
bleedOutProgressFill.fillAmount = bleedOutTimer / GameConstants.CharacterBalancing.GroupBleedOutDuration;
return;
}
if (!isFainted) return;
if (playersInsideReviveTrigger.Count > 0)
{
reviveProgress += GameConstants.CharacterBalancing.ReviveSpeed * playersInsideReviveTrigger.Count * Time.deltaTime;
reviveProgressFill.fillAmount = reviveProgress / GameConstants.CharacterBalancing.ReviveTime;
if (reviveProgress >= GameConstants.CharacterBalancing.ReviveTime)
{
Revive();
}
}
}
private void OnTriggerExit(Collider other)
{
if (!isFainted) return;
possibleReviver = other.GetComponentInParent<RiftPlayer>();
if (possibleReviver == null) return;
if (possibleReviver == ownerPlayer) return;
if (!playersInsideReviveTrigger.Contains(possibleReviver)) return;
Debug.Log($"R: removed {possibleReviver.name} from revivers pool");
playersInsideReviveTrigger.Remove(possibleReviver);
}
private void Revive()
{
isFainted = false;
reviveTrigger.gameObject.SetActive(false);
reviveProgress = 0;
health.SetIsDeadState(false);
health.SetCurrentValue(health.GetMaxValue() * GameConstants.CharacterBalancing.ReviveHealthPercent);
// Re-enable player movement and abilities
// Restore normal player visuals
if (!owner.IsMine) return;
reviveProgressGO.SetActive(false);
bleedOutProgressGO.SetActive(false);
anim.OnRevived();
onLocalPlayerRevived.Raise();
}
private void Die()
{
health.SetIsDeadState(true);
// Implement full death logic (respawn, etc.)
if (PhotonNetwork.CurrentRoom.PlayerCount <= 1)
{
//Player perma death on the job, wait X to show info, then PhotonNetwork.LoadLevel(RiftHuntersInn)
StartCoroutine(Return());
}
else
{
//if players alive > 0
//wait motherfucker
//else
//all dead, host load level
}
if (!owner.IsMine) return;
if(!isFainted)
anim.OnDeath();
reviveProgressGO.SetActive(false);
bleedOutProgressGO.SetActive(false);
onLocalPlayerPermaDeath.Raise();
}
IEnumerator Return()
{
yield return new WaitForSeconds(GameConstants.GameBalancing.PermaDeathInfoTime);
if (PhotonNetwork.IsMasterClient) PhotonNetwork.LoadLevel("4-RiftHuntersInn");
}
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if(stream.IsWriting)
{
stream.SendNext(isFainted);
stream.SendNext(reviveTrigger.gameObject.activeSelf);
stream.SendNext(reviveProgress);
stream.SendNext(bleedOutTimer);
}
else if(stream.IsReading)
{
isFainted = (bool)stream.ReceiveNext();
reviveTrigger.gameObject.SetActive((bool)stream.ReceiveNext());
reviveProgress = (float)stream.ReceiveNext();
bleedOutTimer = (float)stream.ReceiveNext();
}
}
}