using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; using Unity.Netcode; using Colosseum.Abnormalities; #if UNITY_EDITOR using UnityEditor; #endif namespace Colosseum.Player { /// /// 로컬 플레이어가 자신에게 이상상태를 적용/해제할 수 있는 디버그 HUD. /// 이상상태 이름, 에셋 이름, 인덱스로 검색해 적용할 수 있습니다. /// [DisallowMultipleComponent] public class PlayerAbnormalityDebugHUD : NetworkBehaviour { [Header("References")] [Tooltip("이상상태 관리자")] [SerializeField] private AbnormalityManager abnormalityManager; [Tooltip("플레이어 네트워크 상태")] [SerializeField] private PlayerNetworkController networkController; [Header("Display")] [Tooltip("시작 시 HUD 표시 여부")] [SerializeField] private bool showOnStart = false; [Tooltip("로그 출력 여부")] [SerializeField] private bool debugLogs = true; [Header("Catalog")] [Tooltip("디버그 HUD에서 검색 가능한 이상상태 목록")] [SerializeField] private List abnormalityCatalog = new List(); private Rect windowRect = new Rect(20f, 20f, 420f, 520f); private Vector2 catalogScroll; private string abnormalityInput = string.Empty; private string statusMessage = "이상상태 이름, 에셋 이름, 인덱스를 입력하세요."; private bool isVisible; private InputSystem_Actions inputActions; private void Awake() { if (abnormalityManager == null) abnormalityManager = GetComponent(); if (networkController == null) networkController = GetComponent(); } public override void OnNetworkSpawn() { if (!IsOwner || !ShouldEnableDebugHud()) { enabled = false; return; } isVisible = showOnStart; RefreshCatalog(); InitializeInputActions(); if (debugLogs) { Debug.Log("[AbnormalityDebugHUD] DebugHUD 액션으로 HUD를 열고, 이상상태 이름/에셋명/인덱스로 자신에게 적용할 수 있습니다."); } } public override void OnNetworkDespawn() { CleanupInputActions(); } private void OnGUI() { if (!IsOwner || !isVisible || !ShouldEnableDebugHud()) return; windowRect = GUI.Window(GetInstanceID(), windowRect, DrawWindow, "Abnormality Debug HUD"); } private void DrawWindow(int windowId) { GUILayout.BeginVertical(); GUILayout.Label($"사망 상태: {(networkController != null && networkController.IsDead ? "Dead" : "Alive")}"); GUILayout.Label("입력 예시: 기절 / Data_Abnormality_Player_Stun / 0"); GUI.SetNextControlName("AbnormalityInputField"); abnormalityInput = GUILayout.TextField(abnormalityInput ?? string.Empty); GUILayout.BeginHorizontal(); if (GUILayout.Button("적용", GUILayout.Height(28f))) { ApplyFromInput(); } if (GUILayout.Button("해제", GUILayout.Height(28f))) { RemoveFromInput(); } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); if (GUILayout.Button("모두 해제", GUILayout.Height(24f))) { if (abnormalityManager != null) { abnormalityManager.RemoveAllAbnormalities(); SetStatus("활성 이상상태를 모두 해제했습니다."); } } if (GUILayout.Button("즉사", GUILayout.Height(24f))) { KillSelf(); } if (GUILayout.Button("리스폰", GUILayout.Height(24f))) { RequestRespawnRpc(); } GUILayout.EndHorizontal(); GUILayout.Space(6f); GUILayout.Label($"상태: {statusMessage}"); GUILayout.Space(6f); GUILayout.Label("활성 이상상태"); DrawActiveAbnormalities(); GUILayout.Space(6f); GUILayout.Label("카탈로그"); catalogScroll = GUILayout.BeginScrollView(catalogScroll, GUILayout.Height(220f)); for (int i = 0; i < abnormalityCatalog.Count; i++) { AbnormalityData data = abnormalityCatalog[i]; if (data == null) continue; GUILayout.BeginHorizontal(); GUILayout.Label($"[{i}] {data.abnormalityName} ({data.name})", GUILayout.Width(280f)); if (GUILayout.Button("적용", GUILayout.Width(50f))) { ApplyAbnormality(data); } if (GUILayout.Button("해제", GUILayout.Width(50f))) { RemoveAbnormality(data); } GUILayout.EndHorizontal(); } GUILayout.EndScrollView(); GUILayout.EndVertical(); GUI.DragWindow(new Rect(0f, 0f, 10000f, 20f)); } private void OnEnable() { if (IsOwner && inputActions != null) { inputActions.Player.Enable(); } } private void OnDisable() { CleanupInputActions(); } private void InitializeInputActions() { if (inputActions == null) { inputActions = new InputSystem_Actions(); inputActions.Player.DebugHUD.performed += OnDebugHudPerformed; } inputActions.Player.Enable(); } private void CleanupInputActions() { if (inputActions != null) { inputActions.Player.Disable(); } } private void OnDebugHudPerformed(InputAction.CallbackContext context) { if (!IsOwner) return; isVisible = !isVisible; } private void DrawActiveAbnormalities() { if (abnormalityManager == null || abnormalityManager.ActiveAbnormalities.Count == 0) { GUILayout.Label("- 없음"); return; } for (int i = 0; i < abnormalityManager.ActiveAbnormalities.Count; i++) { var active = abnormalityManager.ActiveAbnormalities[i]; if (active == null || active.Data == null) continue; string durationText = active.Data.IsPermanent ? "영구" : $"{active.RemainingDuration:F1}s"; GUILayout.Label($"- {active.Data.abnormalityName} / {durationText}"); } } private void ApplyFromInput() { if (!TryResolveAbnormality(abnormalityInput, out AbnormalityData data, out string message)) { SetStatus(message); return; } ApplyAbnormality(data); } private void RemoveFromInput() { if (!TryResolveAbnormality(abnormalityInput, out AbnormalityData data, out string message)) { SetStatus(message); return; } RemoveAbnormality(data); } private void ApplyAbnormality(AbnormalityData data) { if (data == null) { SetStatus("적용할 이상상태를 찾지 못했습니다."); return; } if (abnormalityManager == null) { SetStatus("AbnormalityManager 참조가 없습니다."); return; } abnormalityManager.ApplyAbnormality(data, gameObject); SetStatus($"'{data.abnormalityName}' 적용 요청을 보냈습니다."); } private void RemoveAbnormality(AbnormalityData data) { if (data == null) { SetStatus("해제할 이상상태를 찾지 못했습니다."); return; } if (abnormalityManager == null) { SetStatus("AbnormalityManager 참조가 없습니다."); return; } abnormalityManager.RemoveAbnormality(data); SetStatus($"'{data.abnormalityName}' 해제 요청을 보냈습니다."); } private bool TryResolveAbnormality(string input, out AbnormalityData resolved, out string message) { resolved = null; message = string.Empty; string normalizedInput = NormalizeIdentifier(input); if (string.IsNullOrWhiteSpace(normalizedInput)) { message = "이상상태 이름, 에셋 이름, 인덱스를 입력하세요."; return false; } if (int.TryParse(normalizedInput, out int index)) { if (index >= 0 && index < abnormalityCatalog.Count && abnormalityCatalog[index] != null) { resolved = abnormalityCatalog[index]; return true; } message = $"인덱스 {index} 에 해당하는 이상상태가 없습니다."; return false; } List partialMatches = new List(); for (int i = 0; i < abnormalityCatalog.Count; i++) { AbnormalityData candidate = abnormalityCatalog[i]; if (candidate == null) continue; string normalizedName = NormalizeIdentifier(candidate.abnormalityName); string normalizedAssetName = NormalizeIdentifier(candidate.name); if (normalizedInput == normalizedName || normalizedInput == normalizedAssetName) { resolved = candidate; return true; } if (normalizedName.Contains(normalizedInput) || normalizedAssetName.Contains(normalizedInput)) { partialMatches.Add(candidate); } } if (partialMatches.Count == 1) { resolved = partialMatches[0]; return true; } if (partialMatches.Count > 1) { message = BuildAmbiguousMessage(partialMatches); return false; } message = $"'{input}' 에 해당하는 이상상태를 찾지 못했습니다."; return false; } private string BuildAmbiguousMessage(List matches) { int previewCount = Mathf.Min(3, matches.Count); List previewNames = new List(previewCount); for (int i = 0; i < previewCount; i++) { AbnormalityData match = matches[i]; previewNames.Add(match != null ? match.abnormalityName : "null"); } string preview = string.Join(", ", previewNames); if (matches.Count > previewCount) { preview += ", ..."; } return $"여러 후보가 있습니다: {preview}"; } private string NormalizeIdentifier(string value) { return string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim().Replace(" ", string.Empty).ToLowerInvariant(); } private void SetStatus(string message) { statusMessage = message; if (debugLogs) { Debug.Log($"[AbnormalityDebugHUD] {message}"); } } private void KillSelf() { if (networkController == null) { SetStatus("PlayerNetworkController 참조가 없습니다."); return; } if (networkController.IsDead) { SetStatus("이미 사망한 상태입니다."); return; } networkController.TakeDamageRpc(networkController.Health + 1f); SetStatus("즉사 요청을 보냈습니다."); } [Rpc(SendTo.Server)] private void RequestRespawnRpc() { if (networkController == null) return; networkController.Respawn(); } private bool ShouldEnableDebugHud() { #if UNITY_EDITOR return true; #else return Debug.isDebugBuild; #endif } private void RefreshCatalog() { abnormalityCatalog.RemoveAll(data => data == null); #if UNITY_EDITOR string[] guids = AssetDatabase.FindAssets("t:AbnormalityData", new[] { "Assets/_Game/Data/Abnormalities" }); List loadedAssets = new List(guids.Length); for (int i = 0; i < guids.Length; i++) { string path = AssetDatabase.GUIDToAssetPath(guids[i]); AbnormalityData data = AssetDatabase.LoadAssetAtPath(path); if (data != null) { loadedAssets.Add(data); } } loadedAssets.Sort((left, right) => string.Compare(left != null ? left.name : string.Empty, right != null ? right.name : string.Empty, StringComparison.OrdinalIgnoreCase)); abnormalityCatalog = loadedAssets; #endif } } }