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 playersInsideReviveTrigger = new List(); PlayerDeathManager potentialReferenceAddition; NetworkManager networkManager; private void Awake() { owner = GetComponentInParent(); ownerPlayer = GetComponentInParent(); health = GetComponentInParent(); anim = ownerPlayer.GetComponentInChildren(); reviveTrigger = GetComponentInChildren(); reviveTrigger.transform.localScale = Vector3.one * GameConstants.CharacterBalancing.ReviveTriggerRadius * 2; reviveTrigger.isTrigger = true; reviveTrigger.gameObject.SetActive(false); reviveProgressGO.SetActive(false); bleedOutProgressGO.SetActive(false); networkManager = FindObjectOfType(); } 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; if(AreAllPlayersFaintedOrDead()) { if (PhotonNetwork.IsMasterClient) { StartCoroutine(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(); 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) { reviveProgressFill.fillAmount = reviveProgress / GameConstants.CharacterBalancing.ReviveTime; bleedOutProgressFill.fillAmount = bleedOutTimer / GameConstants.CharacterBalancing.GroupBleedOutDuration; return; } if (!isFainted) return; if (playersInsideReviveTrigger.Count > 0) { for (int i = playersInsideReviveTrigger.Count - 1; i >= 0; i--) { if (playersInsideReviveTrigger[i].health.GetCurrentValue() <= 0) playersInsideReviveTrigger.RemoveAt(i); } 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(); 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 playersInsideReviveTrigger.Clear(); 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(AreAllPlayersFaintedOrDead()) { if(PhotonNetwork.IsMasterClient) { StartCoroutine(Return()); } } } if (!owner.IsMine) return; if(!isFainted) anim.OnDeath(); reviveProgressGO.SetActive(false); bleedOutProgressGO.SetActive(false); onLocalPlayerPermaDeath.Raise(); } private bool AreAllPlayersFaintedOrDead() { for (int i = 0; i < networkManager.party.Count; i++) { potentialReferenceAddition = ((RiftPlayer)networkManager.party[i].TagObject).GetComponentInChildren(); if (potentialReferenceAddition.IsFainted) continue; else return false; } return true; } 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(); reviveProgressGO.SetActive(reviveTrigger.gameObject.activeSelf); bleedOutProgressGO.SetActive(reviveTrigger.gameObject.activeSelf); } } }