using System; using UnityEngine; using Unity.Netcode; using Colosseum.Stats; using Colosseum.Combat; namespace Colosseum.Player { /// /// 플레이어 네트워크 상태 관리 (HP, MP 등) /// public class PlayerNetworkController : NetworkBehaviour, IDamageable { [Header("References")] [Tooltip("CharacterStats 컴포넌트 (없으면 자동 검색)")] [SerializeField] private CharacterStats characterStats; // 네트워크 동기화 변수 private NetworkVariable currentHealth = new NetworkVariable(100f); private NetworkVariable currentMana = new NetworkVariable(50f); private NetworkVariable isDead = new NetworkVariable(false); public float Health => currentHealth.Value; public float Mana => currentMana.Value; public float MaxHealth => characterStats != null ? characterStats.MaxHealth : 100f; public float MaxMana => characterStats != null ? characterStats.MaxMana : 50f; public CharacterStats Stats => characterStats; // 체력/마나 변경 이벤트 public event Action OnHealthChanged; // (oldValue, newValue) public event Action OnManaChanged; // (oldValue, newValue) // 사망 이벤트 public event Action OnDeath; public event Action OnDeathStateChanged; // (isDead) // IDamageable 구현 public float CurrentHealth => currentHealth.Value; public bool IsDead => isDead.Value; public override void OnNetworkSpawn() { // CharacterStats 참조 확인 if (characterStats == null) { characterStats = GetComponent(); } // 네트워크 변수 변경 콜백 등록 currentHealth.OnValueChanged += HandleHealthChanged; currentMana.OnValueChanged += HandleManaChanged; isDead.OnValueChanged += HandleDeathStateChanged; // 초기화 if (IsServer) { currentHealth.Value = MaxHealth; currentMana.Value = MaxMana; isDead.Value = false; } } public override void OnNetworkDespawn() { // 콜백 해제 currentHealth.OnValueChanged -= HandleHealthChanged; currentMana.OnValueChanged -= HandleManaChanged; isDead.OnValueChanged -= HandleDeathStateChanged; } private void HandleHealthChanged(float oldValue, float newValue) { OnHealthChanged?.Invoke(oldValue, newValue); } private void HandleManaChanged(float oldValue, float newValue) { OnManaChanged?.Invoke(oldValue, newValue); } private void HandleDeathStateChanged(bool oldValue, bool newValue) { OnDeathStateChanged?.Invoke(newValue); } /// /// 대미지 적용 (서버에서만 실행) /// [Rpc(SendTo.Server)] public void TakeDamageRpc(float damage) { if (isDead.Value) return; currentHealth.Value = Mathf.Max(0f, currentHealth.Value - damage); if (currentHealth.Value <= 0f) { HandleDeath(); } } /// /// 마나 소모 (서버에서만 실행) /// [Rpc(SendTo.Server)] public void UseManaRpc(float amount) { currentMana.Value = Mathf.Max(0f, currentMana.Value - amount); } /// /// 체력 회복 (서버에서만 실행) /// [Rpc(SendTo.Server)] public void RestoreHealthRpc(float amount) { currentHealth.Value = Mathf.Min(MaxHealth, currentHealth.Value + amount); } /// /// 마나 회복 (서버에서만 실행) /// [Rpc(SendTo.Server)] public void RestoreManaRpc(float amount) { currentMana.Value = Mathf.Min(MaxMana, currentMana.Value + amount); } /// /// 사망 처리 (서버에서만 실행) /// private void HandleDeath() { if (isDead.Value) return; isDead.Value = true; // 이동 비활성화 var movement = GetComponent(); if (movement != null) { movement.enabled = false; } // 스킬 입력 비활성화 var skillInput = GetComponent(); if (skillInput != null) { skillInput.enabled = false; } // 사망 애니메이션 재생 var animator = GetComponentInChildren(); if (animator != null) { animator.SetTrigger("Die"); } // 사망 이벤트 발생 OnDeath?.Invoke(this); Debug.Log($"[Player] Player {OwnerClientId} died!"); } /// /// 리스폰 (서버에서만 실행) /// public void Respawn() { if (!IsServer) return; isDead.Value = false; currentHealth.Value = MaxHealth; currentMana.Value = MaxMana; // 이동 재활성화 var movement = GetComponent(); if (movement != null) { movement.enabled = true; } // 스킬 입력 재활성화 var skillInput = GetComponent(); if (skillInput != null) { skillInput.enabled = true; } // 애니메이션 리셋 var animator = GetComponentInChildren(); if (animator != null) { animator.Rebind(); } Debug.Log($"[Player] Player {OwnerClientId} respawned!"); } #region IDamageable /// /// 대미지 적용 (서버에서만 호출) /// public float TakeDamage(float damage, object source = null) { if (!IsServer || isDead.Value) return 0f; float actualDamage = Mathf.Min(damage, currentHealth.Value); currentHealth.Value = Mathf.Max(0f, currentHealth.Value - damage); if (currentHealth.Value <= 0f) { HandleDeath(); } return actualDamage; } /// /// 체력 회복 (서버에서만 호출) /// public float Heal(float amount) { if (!IsServer || isDead.Value) return 0f; float actualHeal = Mathf.Min(amount, MaxHealth - currentHealth.Value); currentHealth.Value = Mathf.Min(MaxHealth, currentHealth.Value + amount); return actualHeal; } #endregion } }