using System.Collections; using UnityEngine; using Unity.Netcode; using Colosseum.Abnormalities; #if UNITY_EDITOR using UnityEditor; #endif namespace Colosseum.Player { /// /// 플레이어 이상상태와 행동 제어 연동을 자동 검증하는 디버그 러너. /// 기절, 침묵, 사망, 리스폰 순으로 상태를 검사합니다. /// [DisallowMultipleComponent] public class PlayerAbnormalityVerificationRunner : NetworkBehaviour { [Header("References")] [SerializeField] private AbnormalityManager abnormalityManager; [SerializeField] private PlayerActionState actionState; [SerializeField] private PlayerNetworkController networkController; [Header("Test Data")] [SerializeField] private AbnormalityData stunData; [SerializeField] private AbnormalityData silenceData; [Header("Execution")] [Tooltip("에디터 플레이 시작 시 자동 검증 실행")] [SerializeField] private bool runOnStartInEditor = false; [Tooltip("각 검증 단계 사이 대기 시간")] [Min(0.05f)] [SerializeField] private float settleDelay = 0.2f; [Header("Result")] [SerializeField] private bool isRunning; [SerializeField] private bool lastRunPassed; [SerializeField] private int totalChecks; [SerializeField] private int failedChecks; [TextArea(5, 12)] [SerializeField] private string lastReport = string.Empty; private readonly System.Text.StringBuilder reportBuilder = new System.Text.StringBuilder(); public override void OnNetworkSpawn() { if (!IsOwner || !ShouldEnableRunner()) { enabled = false; return; } ResolveReferences(); LoadDefaultAssetsIfNeeded(); if (runOnStartInEditor) { StartCoroutine(RunVerificationRoutine()); } } [ContextMenu("Run Verification")] public void RunVerification() { if (!Application.isPlaying || !IsOwner || isRunning) return; StartCoroutine(RunVerificationRoutine()); } private IEnumerator RunVerificationRoutine() { if (isRunning) yield break; ResolveReferences(); LoadDefaultAssetsIfNeeded(); if (abnormalityManager == null || actionState == null || networkController == null || stunData == null || silenceData == null) { Debug.LogWarning("[AbnormalityVerification] Missing references or test data."); yield break; } isRunning = true; totalChecks = 0; failedChecks = 0; lastRunPassed = false; reportBuilder.Clear(); AppendLine("=== Player Abnormality Verification Start ==="); abnormalityManager.RemoveAllAbnormalities(); RequestRespawnRpc(); yield return new WaitForSeconds(settleDelay); Verify("초기 상태: 사망 아님", !networkController.IsDead); Verify("초기 상태: 이동 가능", actionState.CanMove); Verify("초기 상태: 스킬 사용 가능", actionState.CanUseSkills); abnormalityManager.ApplyAbnormality(stunData, gameObject); yield return new WaitForSeconds(settleDelay); Verify("기절 적용: IsStunned", abnormalityManager.IsStunned); Verify("기절 적용: ActionState.IsStunned", actionState.IsStunned); Verify("기절 적용: 이동 불가", !actionState.CanMove); Verify("기절 적용: 점프 불가", !actionState.CanJump); Verify("기절 적용: 스킬 사용 불가", !actionState.CanUseSkills); Verify("기절 적용: 이동속도 0", Mathf.Approximately(actionState.MoveSpeedMultiplier, 0f)); yield return new WaitForSeconds(stunData.duration + settleDelay); Verify("기절 해제: IsStunned false", !abnormalityManager.IsStunned); Verify("기절 해제: 이동 가능 복구", actionState.CanMove); Verify("기절 해제: 스킬 사용 가능 복구", actionState.CanUseSkills); abnormalityManager.ApplyAbnormality(silenceData, gameObject); yield return new WaitForSeconds(settleDelay); Verify("침묵 적용: IsSilenced", abnormalityManager.IsSilenced); Verify("침묵 적용: 이동 가능 유지", actionState.CanMove); Verify("침묵 적용: 점프 가능 유지", actionState.CanJump); Verify("침묵 적용: 스킬 사용 불가", !actionState.CanUseSkills); yield return new WaitForSeconds(silenceData.duration + settleDelay); Verify("침묵 해제: IsSilenced false", !abnormalityManager.IsSilenced); Verify("침묵 해제: 스킬 사용 가능 복구", actionState.CanUseSkills); abnormalityManager.ApplyAbnormality(stunData, gameObject); yield return new WaitForSeconds(settleDelay); networkController.TakeDamageRpc(networkController.Health + 1f); yield return new WaitForSeconds(settleDelay); Verify("사망 처리: IsDead", networkController.IsDead); Verify("사망 처리: 입력 불가", !actionState.CanReceiveInput); Verify("사망 처리: 이동 불가", !actionState.CanMove); Verify("사망 처리: 스킬 사용 불가", !actionState.CanUseSkills); Verify("사망 처리: 활성 이상상태 제거", abnormalityManager.ActiveAbnormalities.Count == 0); RequestRespawnRpc(); yield return new WaitForSeconds(settleDelay); Verify("리스폰: IsDead false", !networkController.IsDead); Verify("리스폰: 활성 이상상태 없음", abnormalityManager.ActiveAbnormalities.Count == 0); Verify("리스폰: 이동 가능", actionState.CanMove); Verify("리스폰: 스킬 사용 가능", actionState.CanUseSkills); lastRunPassed = failedChecks == 0; AppendLine(lastRunPassed ? "=== Verification Passed ===" : $"=== Verification Failed: {failedChecks}/{totalChecks} checks failed ==="); lastReport = reportBuilder.ToString(); Debug.Log(lastReport); isRunning = false; } [Rpc(SendTo.Server)] private void RequestRespawnRpc() { if (networkController == null) return; networkController.Respawn(); } private void ResolveReferences() { if (abnormalityManager == null) abnormalityManager = GetComponent(); if (actionState == null) actionState = GetComponent(); if (networkController == null) networkController = GetComponent(); } private void LoadDefaultAssetsIfNeeded() { #if UNITY_EDITOR if (stunData == null) { stunData = AssetDatabase.LoadAssetAtPath("Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Stun.asset"); } if (silenceData == null) { silenceData = AssetDatabase.LoadAssetAtPath("Assets/_Game/Data/Abnormalities/Data_Abnormality_Player_Silence.asset"); } #endif } private void Verify(string label, bool condition) { totalChecks++; if (!condition) { failedChecks++; } AppendLine($"{(condition ? "[PASS]" : "[FAIL]")} {label}"); } private void AppendLine(string text) { if (reportBuilder.Length > 0) { reportBuilder.AppendLine(); } reportBuilder.Append(text); } private bool ShouldEnableRunner() { #if UNITY_EDITOR return true; #else return Debug.isDebugBuild; #endif } } }