using System; using UnityEngine; using Unity.Netcode; using Colosseum.Stats; namespace Colosseum.Weapons { /// /// 무기 장착을 관리하는 컴포넌트. /// 무기 장착 시 스탯 보너스를 적용하고 배율을 제공하며, 무기 외형을 표시합니다. /// 메시 이름으로 소켓을 자동 검색합니다. /// public class WeaponEquipment : NetworkBehaviour { [Header("References")] [Tooltip("CharacterStats 컴포넌트 (없으면 자동 검색)")] [SerializeField] private CharacterStats characterStats; [Header("Socket Names (메시 이름)")] [Tooltip("오른손 메시 이름")] [SerializeField] private string rightHandName = "Hand_R"; [Tooltip("왼손 메시 이름")] [SerializeField] private string leftHandName = "Hand_L"; [Tooltip("등 메시 이름")] [SerializeField] private string backName = "Spine"; [Tooltip("허리 메시 이름")] [SerializeField] private string hipName = "Hip"; [Tooltip("양손 메시 이름 (기본값: 오른손 사용)")] [SerializeField] private string twoHandedName = ""; [Header("Starting Weapon")] [Tooltip("시작 무기 (선택)")] [SerializeField] private WeaponData startingWeapon; // 캐싱된 소켓 Transform들 private Transform rightHandSocket; private Transform leftHandSocket; private Transform backSocket; private Transform hipSocket; private Transform twoHandedSocket; // 현재 장착 중인 무기 private WeaponData currentWeapon; // 현재 생성된 무기 인스턴스 private GameObject currentWeaponInstance; // 현재 적용된 스탯 수정자들 (해제 시 제거용) private readonly System.Collections.Generic.Dictionary activeModifiers = new System.Collections.Generic.Dictionary(); // 무기 장착 상태 동기화 private NetworkVariable equippedWeaponId = new NetworkVariable(-1); public WeaponData CurrentWeapon => currentWeapon; public bool HasWeaponEquipped => currentWeapon != null; public GameObject CurrentWeaponInstance => currentWeaponInstance; // 배율 프로퍼티 (무기 없으면 기본값 1.0) public float DamageMultiplier => currentWeapon != null ? currentWeapon.DamageMultiplier : 1f; public float RangeMultiplier => currentWeapon != null ? currentWeapon.RangeMultiplier : 1f; public float ManaCostMultiplier => currentWeapon != null ? currentWeapon.ManaCostMultiplier : 1f; // 이벤트 public event Action OnWeaponEquipped; public event Action OnWeaponUnequipped; private void Awake() { // CharacterStats 참조 확인 if (characterStats == null) { characterStats = GetComponent(); } // 소켓 자동 검색 CacheSockets(); } public override void OnNetworkSpawn() { // 네트워크 변수 변경 콜백 equippedWeaponId.OnValueChanged += HandleEquippedWeaponChanged; // 서버에서 시작 무기 장착 if (IsServer && startingWeapon != null) { EquipWeapon(startingWeapon); } } public override void OnNetworkDespawn() { equippedWeaponId.OnValueChanged -= HandleEquippedWeaponChanged; } /// /// 메시 이름으로 소켓 Transform 캐싱 /// private void CacheSockets() { rightHandSocket = FindDeepChild(rightHandName); leftHandSocket = FindDeepChild(leftHandName); backSocket = FindDeepChild(backName); hipSocket = FindDeepChild(hipName); // 양손은 별도 이름 없으면 오른손 사용 if (!string.IsNullOrEmpty(twoHandedName)) { twoHandedSocket = FindDeepChild(twoHandedName); } else { twoHandedSocket = rightHandSocket; } Debug.Log($"[WeaponEquipment] Sockets cached - R:{rightHandSocket != null}, L:{leftHandSocket != null}, Back:{backSocket != null}, Hip:{hipSocket != null}"); } /// /// 이름으로 자식 Transform 재귀 검색 /// private Transform FindDeepChild(string name) { if (string.IsNullOrEmpty(name)) return null; // BFS로 검색 var queue = new System.Collections.Generic.Queue(); queue.Enqueue(transform); while (queue.Count > 0) { Transform current = queue.Dequeue(); if (current.name == name) { return current; } foreach (Transform child in current) { queue.Enqueue(child); } } return null; } private void HandleEquippedWeaponChanged(int oldValue, int newValue) { // -1이면 무기 해제, 그 외에는 무기 장착됨 // (GetInstanceID()는 음수를 반환할 수 있으므로 >= 0 체크 사용 불가) if (newValue == -1) { UnequipWeaponInternal(); } // 클라이언트에서는 서버에서 이미 장착된 무기 정보를 받아야 함 // TODO: WeaponDatabase에서 ID로 WeaponData 조회 } /// /// 무기 장착 (서버에서만 호출) /// public void EquipWeapon(WeaponData weapon) { if (weapon == null) { Debug.LogWarning("[WeaponEquipment] EquipWeapon called with null weapon"); return; } if (!IsServer) { Debug.LogWarning("[WeaponEquipment] EquipWeapon can only be called on server"); return; } // 기존 무기 해제 if (currentWeapon != null) { UnequipWeapon(); } currentWeapon = weapon; // 스탯 보너스 적용 ApplyStatBonuses(weapon); // 무기 외형 생성 및 부착 SpawnWeaponVisuals(weapon); // 네트워크 동기화 (간단한 ID 사용, 실제로는 WeaponDatabase 필요) equippedWeaponId.Value = weapon.GetInstanceID(); // 이벤트 발생 OnWeaponEquipped?.Invoke(weapon); Debug.Log($"[WeaponEquipment] Equipped: {weapon.WeaponName} at {weapon.WeaponSlot}"); } /// /// 무기 해제 (서버에서만 호출) /// public void UnequipWeapon() { if (currentWeapon == null) return; if (!IsServer) { Debug.LogWarning("[WeaponEquipment] UnequipWeapon can only be called on server"); return; } WeaponData previousWeapon = currentWeapon; // 스탯 보너스 제거 RemoveStatBonuses(); // 무기 외형 제거 DespawnWeaponVisuals(); currentWeapon = null; equippedWeaponId.Value = -1; // 이벤트 발생 OnWeaponUnequipped?.Invoke(previousWeapon); Debug.Log($"[WeaponEquipment] Unequipped: {previousWeapon.WeaponName}"); } /// /// 내부 해제 로직 (클라이언트 동기화용) /// private void UnequipWeaponInternal() { if (currentWeapon == null) return; WeaponData previousWeapon = currentWeapon; RemoveStatBonuses(); DespawnWeaponVisuals(); currentWeapon = null; OnWeaponUnequipped?.Invoke(previousWeapon); } /// /// 무기의 스탯 보너스 적용 /// private void ApplyStatBonuses(WeaponData weapon) { if (characterStats == null) return; // 모든 스탯 타입에 대해 보너스 적용 foreach (StatType statType in System.Enum.GetValues(typeof(StatType))) { int bonus = weapon.GetStatBonus(statType); if (bonus != 0) { var stat = characterStats.GetStat(statType); if (stat != null) { var modifier = new StatModifier(bonus, StatModType.Flat, weapon); stat.AddModifier(modifier); activeModifiers[statType] = modifier; Debug.Log($"[WeaponEquipment] Applied {statType} +{bonus}"); } } } } /// /// 무기의 스탯 보너스 제거 /// private void RemoveStatBonuses() { if (characterStats == null) return; // 각 스탯에서 무기로부터 추가된 수정자 제거 foreach (StatType statType in System.Enum.GetValues(typeof(StatType))) { var stat = characterStats.GetStat(statType); if (stat != null && activeModifiers.TryGetValue(statType, out StatModifier modifier)) { stat.RemoveModifier(modifier); Debug.Log($"[WeaponEquipment] Removed {statType} modifier"); } } activeModifiers.Clear(); } /// /// 무기 외형 생성 및 부착 /// private void SpawnWeaponVisuals(WeaponData weapon) { if (weapon == null || weapon.WeaponPrefab == null) return; // 적절한 소켓 찾기 Transform socket = GetSocketForSlot(weapon.WeaponSlot); if (socket == null) { Debug.LogWarning($"[WeaponEquipment] No socket found for slot: {weapon.WeaponSlot}"); return; } // 무기 인스턴스 생성 currentWeaponInstance = Instantiate(weapon.WeaponPrefab, socket); currentWeaponInstance.transform.localPosition = weapon.PositionOffset; currentWeaponInstance.transform.localRotation = Quaternion.Euler(weapon.RotationOffset); // 소켓 스케일 보정 (부모 스케일이 작은 경우 무기도 작아지는 문제 해결) Vector3 scaleCompensation = new Vector3( socket.lossyScale.x != 0 ? 1f / socket.lossyScale.x : 1f, socket.lossyScale.y != 0 ? 1f / socket.lossyScale.y : 1f, socket.lossyScale.z != 0 ? 1f / socket.lossyScale.z : 1f ); currentWeaponInstance.transform.localScale = Vector3.Scale(weapon.Scale, scaleCompensation); currentWeaponInstance = Instantiate(weapon.WeaponPrefab, socket); currentWeaponInstance.transform.localPosition = weapon.PositionOffset; currentWeaponInstance.transform.localRotation = Quaternion.Euler(weapon.RotationOffset); currentWeaponInstance.transform.localScale = weapon.Scale; // 디버그: 스케일 정보 출력 Debug.Log($"[WeaponEquipment] Weapon instantiated - LocalScale: {currentWeaponInstance.transform.localScale}, LossyScale: {currentWeaponInstance.transform.lossyScale}"); Debug.Log($"[WeaponEquipment] Socket: {socket.name}, Socket scale: {socket.lossyScale}"); Debug.Log($"[WeaponEquipment] Position offset: {weapon.PositionOffset}, Rotation offset: {weapon.RotationOffset}"); // 네트워크 동기화를 위해 Spawn (서버에서만) if (IsServer && currentWeaponInstance.TryGetComponent(out var networkObject)) { networkObject.Spawn(true); } Debug.Log($"[WeaponEquipment] Spawned weapon visual: {weapon.WeaponName}"); } /// /// 무기 외형 제거 /// private void DespawnWeaponVisuals() { if (currentWeaponInstance == null) return; // 네트워크 Object면 Despawn if (currentWeaponInstance.TryGetComponent(out var networkObject) && networkObject.IsSpawned) { if (IsServer) { networkObject.Despawn(true); } } else { Destroy(currentWeaponInstance); } currentWeaponInstance = null; Debug.Log("[WeaponEquipment] Despawned weapon visual"); } /// /// 슬롯 타입에 맞는 소켓 Transform 반환 /// private Transform GetSocketForSlot(WeaponSlot slot) { return slot switch { WeaponSlot.RightHand => rightHandSocket, WeaponSlot.LeftHand => leftHandSocket, WeaponSlot.Back => backSocket, WeaponSlot.Hip => hipSocket, WeaponSlot.TwoHanded => twoHandedSocket != null ? twoHandedSocket : rightHandSocket, _ => rightHandSocket, }; } /// /// 서버에 무기 장착 요청 /// [Rpc(SendTo.Server)] public void RequestEquipWeaponRpc(int weaponInstanceId) { // TODO: WeaponDatabase에서 ID로 WeaponData 조회 후 EquipWeapon 호출 Debug.Log($"[WeaponEquipment] Client requested weapon equip: {weaponInstanceId}"); } /// /// 서버에 무기 해제 요청 /// [Rpc(SendTo.Server)] public void RequestUnequipWeaponRpc() { UnequipWeapon(); } } }