using UnityEngine; using UnityEngine.InputSystem; using Unity.Netcode; using Colosseum.Skills; using Colosseum.Weapons; namespace Colosseum.Player { /// /// 플레이어 스킬 입력 처리. /// 논타겟 방식: 입력 시 즉시 스킬 시전 /// [RequireComponent(typeof(PlayerActionState))] public class PlayerSkillInput : NetworkBehaviour { [Header("Skill Slots")] [Tooltip("각 슬롯에 등록할 스킬 데이터 (6개 + 추가 슬롯)")] [SerializeField] private SkillData[] skillSlots = new SkillData[7]; [Header("References")] [Tooltip("SkillController (없으면 자동 검색)")] [SerializeField] private SkillController skillController; [Tooltip("PlayerNetworkController (없으면 자동 검색)")] [SerializeField] private PlayerNetworkController networkController; [Tooltip("WeaponEquipment (없으면 자동 검색)")] [SerializeField] private WeaponEquipment weaponEquipment; [Tooltip("행동 상태 관리자 (없으면 자동 검색)")] [SerializeField] private PlayerActionState actionState; private InputSystem_Actions inputActions; public SkillData[] SkillSlots => skillSlots; public override void OnNetworkSpawn() { if (!IsOwner) { enabled = false; return; } // SkillController 참조 확인 if (skillController == null) { skillController = GetComponent(); if (skillController == null) { Debug.LogError("PlayerSkillInput: SkillController not found!"); enabled = false; return; } } // PlayerNetworkController 참조 확인 if (networkController == null) { networkController = GetComponent(); } // WeaponEquipment 참조 확인 if (weaponEquipment == null) { weaponEquipment = GetComponent(); } if (actionState == null) { actionState = GetOrCreateActionState(); } InitializeInputActions(); } private void InitializeInputActions() { if (inputActions == null) { inputActions = new InputSystem_Actions(); inputActions.Player.Skill1.performed += OnSkill1Performed; inputActions.Player.Skill2.performed += OnSkill2Performed; inputActions.Player.Skill3.performed += OnSkill3Performed; inputActions.Player.Skill4.performed += OnSkill4Performed; inputActions.Player.Skill5.performed += OnSkill5Performed; inputActions.Player.Skill6.performed += OnSkill6Performed; inputActions.Player.Evade.performed += OnEvadePerformed; } inputActions.Player.Enable(); } public override void OnNetworkDespawn() { CleanupInputActions(); } private void OnDisable() { CleanupInputActions(); } private void OnEnable() { if (IsOwner && inputActions != null) { inputActions.Player.Enable(); } } private void CleanupInputActions() { if (inputActions != null) { inputActions.Player.Disable(); } } /// /// 스킬 입력 처리 /// private void OnSkillInput(int slotIndex) { if (slotIndex < 0 || slotIndex >= skillSlots.Length) return; SkillData skill = skillSlots[slotIndex]; if (skill == null) { Debug.Log($"Skill slot {slotIndex + 1} is empty"); return; } // 사망 상태 체크 if (actionState != null && !actionState.CanStartSkill(skill)) return; // 로컬 체크 (빠른 피드백용) if (skillController.IsExecutingSkill) { Debug.Log($"Already executing skill"); return; } if (skillController.IsOnCooldown(skill)) { Debug.Log($"Skill {skill.SkillName} is on cooldown"); return; } // 마나 비용 체크 (무기 배율 적용) float actualManaCost = GetActualManaCost(skill); if (networkController != null && networkController.Mana < actualManaCost) { Debug.Log($"Not enough mana for skill: {skill.SkillName}"); return; } // 서버에 스킬 실행 요청 RequestSkillExecutionRpc(slotIndex); } /// /// 서버에 스킬 실행 요청 /// [Rpc(SendTo.Server)] private void RequestSkillExecutionRpc(int slotIndex) { if (slotIndex < 0 || slotIndex >= skillSlots.Length) return; SkillData skill = skillSlots[slotIndex]; if (skill == null) return; // 서버에서 다시 검증 if (actionState != null && !actionState.CanStartSkill(skill)) return; if (skillController.IsExecutingSkill || skillController.IsOnCooldown(skill)) return; // 마나 비용 체크 (무기 배율 적용) float actualManaCost = GetActualManaCost(skill); if (networkController != null && networkController.Mana < actualManaCost) return; // 마나 소모 (무기 배율 적용) if (networkController != null && actualManaCost > 0) { networkController.UseManaRpc(actualManaCost); } // 모든 클라이언트에 스킬 실행 전파 BroadcastSkillExecutionRpc(slotIndex); } /// /// 모든 클라이언트에 스킬 실행 전파 /// [Rpc(SendTo.ClientsAndHost)] private void BroadcastSkillExecutionRpc(int slotIndex) { if (slotIndex < 0 || slotIndex >= skillSlots.Length) return; SkillData skill = skillSlots[slotIndex]; if (skill == null) return; // 모든 클라이언트에서 스킬 실행 (애니메이션 포함) skillController.ExecuteSkill(skill); } /// /// 무기 마나 배율이 적용된 실제 마나 비용 계산 /// private float GetActualManaCost(SkillData skill) { if (skill == null) return 0f; float baseCost = skill.ManaCost; float multiplier = weaponEquipment != null ? weaponEquipment.ManaCostMultiplier : 1f; return baseCost * multiplier; } /// /// 스킬 슬롯 접근자 /// public SkillData GetSkill(int slotIndex) { if (slotIndex < 0 || slotIndex >= skillSlots.Length) return null; return skillSlots[slotIndex]; } /// /// 스킬 슬롯 변경 /// public void SetSkill(int slotIndex, SkillData skill) { if (slotIndex < 0 || slotIndex >= skillSlots.Length) return; skillSlots[slotIndex] = skill; } /// /// 남은 쿨타임 조회 /// public float GetRemainingCooldown(int slotIndex) { SkillData skill = GetSkill(slotIndex); if (skill == null) return 0f; return skillController.GetRemainingCooldown(skill); } /// /// 스킬 사용 가능 여부 /// public bool CanUseSkill(int slotIndex) { SkillData skill = GetSkill(slotIndex); if (skill == null) return false; if (actionState != null && !actionState.CanStartSkill(skill)) return false; return !skillController.IsOnCooldown(skill) && !skillController.IsExecutingSkill; } /// /// 디버그용 스킬 시전 진입점입니다. /// 로컬 플레이어 검증 시 지정한 슬롯의 스킬을 즉시 요청합니다. /// public void DebugCastSkill(int slotIndex) { if (!IsOwner) return; OnSkillInput(slotIndex); } private void OnSkill1Performed(InputAction.CallbackContext context) => OnSkillInput(0); private void OnSkill2Performed(InputAction.CallbackContext context) => OnSkillInput(1); private void OnSkill3Performed(InputAction.CallbackContext context) => OnSkillInput(2); private void OnSkill4Performed(InputAction.CallbackContext context) => OnSkillInput(3); private void OnSkill5Performed(InputAction.CallbackContext context) => OnSkillInput(4); private void OnSkill6Performed(InputAction.CallbackContext context) => OnSkillInput(5); private void OnEvadePerformed(InputAction.CallbackContext context) => OnSkillInput(6); private PlayerActionState GetOrCreateActionState() { var foundState = GetComponent(); if (foundState != null) return foundState; return gameObject.AddComponent(); } } }