using Unity.Netcode; using UnityEngine; namespace Northbound { /// /// 플레이어가 자원을 건내받아 게임의 전역 자원으로 관리하는 중앙 허브 /// public class Core : NetworkBehaviour, IInteractable, IDamageable, ITeamMember { [Header("Core Settings")] public int maxStorageCapacity = 1000; // 코어의 최대 저장 용량 public bool unlimitedStorage = false; // 무제한 저장소 [Header("Health")] public int maxHealth = 1000; public GameObject damageEffectPrefab; public GameObject destroyEffectPrefab; [Header("Deposit Settings")] public bool depositAll = true; // true: 전부 건네기, false: 일부만 건네기 public int depositAmountPerInteraction = 10; // depositAll이 false일 때 한 번에 건네는 양 [Header("Animation")] public string interactionAnimationTrigger = "Deposit"; // 플레이어 애니메이션 트리거 [Header("Equipment")] public EquipmentData equipmentData = null; // 도구 필요 없음 [Header("Visual")] public GameObject depositEffectPrefab; public Transform effectSpawnPoint; private NetworkVariable _totalResources = new NetworkVariable( 0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server ); private NetworkVariable _currentHealth = new NetworkVariable( 0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server ); public int TotalResources => _totalResources.Value; public int MaxStorageCapacity => maxStorageCapacity; public int CurrentHealth => _currentHealth.Value; public int MaxHealth => maxHealth; public override void OnNetworkSpawn() { if (IsServer) { _totalResources.Value = 0; _currentHealth.Value = maxHealth; } _currentHealth.OnValueChanged += OnHealthChanged; } public override void OnNetworkDespawn() { _currentHealth.OnValueChanged -= OnHealthChanged; } private void OnHealthChanged(int previousValue, int newValue) { } #region ITeamMember Implementation public TeamType GetTeam() { return TeamType.Player; // 코어는 플레이어 팀 } public void SetTeam(TeamType team) { // 코어의 팀은 변경할 수 없음 (항상 플레이어 팀) Debug.LogWarning("[Core] 코어의 팀은 변경할 수 없습니다."); } #endregion #region IDamageable Implementation public void TakeDamage(int damage, ulong attackerId) { if (!IsServer) return; if (_currentHealth.Value <= 0) return; int actualDamage = Mathf.Min(damage, _currentHealth.Value); _currentHealth.Value -= actualDamage; Debug.Log($"[Core] 코어가 {actualDamage} 데미지를 받았습니다. 남은 체력: {_currentHealth.Value}/{maxHealth}"); // 데미지 이펙트 ShowDamageEffectClientRpc(); // 체력이 0이 되면 게임 오버 if (_currentHealth.Value <= 0) { OnCoreDestroyed(); } } private void OnCoreDestroyed() { if (!IsServer) return; // 파괴 이펙트 ShowDestroyEffectClientRpc(); // 게임 오버 로직 (추후 구현) // GameManager.Instance?.OnGameOver(); } [Rpc(SendTo.ClientsAndHost)] private void ShowDamageEffectClientRpc() { if (damageEffectPrefab != null) { GameObject effect = Instantiate(damageEffectPrefab, transform.position + Vector3.up * 2f, Quaternion.identity); Destroy(effect, 2f); } } [Rpc(SendTo.ClientsAndHost)] private void ShowDestroyEffectClientRpc() { if (destroyEffectPrefab != null) { GameObject effect = Instantiate(destroyEffectPrefab, transform.position, Quaternion.identity); Destroy(effect, 5f); } } #endregion #region Resource Management /// /// 자원을 소비할 수 있는지 확인 /// public bool CanConsumeResource(int amount) { return _totalResources.Value >= amount; } /// /// 자원 소비 (서버에서만) /// [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] public void ConsumeResourceServerRpc(int amount) { if (!CanConsumeResource(amount)) { Debug.LogWarning($"[Core] 자원이 부족합니다. 필요: {amount}, 보유: {_totalResources.Value}"); return; } int previousAmount = _totalResources.Value; _totalResources.Value -= amount; } /// /// 자원 추가 (서버에서만) /// public void AddResource(int amount) { if (!IsServer) return; if (!unlimitedStorage) { int availableSpace = maxStorageCapacity - _totalResources.Value; amount = Mathf.Min(amount, availableSpace); } if (amount > 0) { _totalResources.Value += amount; } } #endregion #region IInteractable Implementation public bool CanInteract(ulong playerId) { // 저장소가 가득 찼는지 확인 (무제한이 아닐 때) if (!unlimitedStorage && _totalResources.Value >= maxStorageCapacity) return false; // 플레이어가 자원을 가지고 있는지 확인 if (NetworkManager.Singleton != null && NetworkManager.Singleton.ConnectedClients.TryGetValue(playerId, out var client)) { if (client.PlayerObject != null) { var playerInventory = client.PlayerObject.GetComponent(); if (playerInventory != null) { // 플레이어가 자원을 가지고 있어야 함 return playerInventory.CurrentResourceAmount > 0; } } } return false; } public void Interact(ulong playerId) { if (!CanInteract(playerId)) return; DepositResourceServerRpc(playerId); } public string GetInteractionPrompt() { if (unlimitedStorage) { return "[E] Deposit (No Limit)"; } else { return $"[E] Deposit ({_totalResources.Value}/{maxStorageCapacity})"; } } public string GetInteractionAnimation() { return interactionAnimationTrigger; } public EquipmentData GetEquipmentData() { return equipmentData; } public Transform GetTransform() { return transform; } [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] private void DepositResourceServerRpc(ulong playerId) { if (!CanInteract(playerId)) return; var resourceManager = ServerResourceManager.Instance; if (resourceManager == null) { Debug.LogWarning("ServerResourceManager 인스턴스를 찾을 수 없습니다."); return; } int playerResourceAmount = resourceManager.GetPlayerResourceAmount(playerId); if (playerResourceAmount <= 0) { return; } int depositAmount; if (depositAll) { depositAmount = playerResourceAmount; } else { depositAmount = Mathf.Min(depositAmountPerInteraction, playerResourceAmount); } if (!unlimitedStorage) { int availableSpace = maxStorageCapacity - _totalResources.Value; depositAmount = Mathf.Min(depositAmount, availableSpace); } if (depositAmount <= 0) { return; } resourceManager.RemoveResource(playerId, depositAmount); UpdatePlayerResourcesClientRpc(playerId); _totalResources.Value += depositAmount; ShowDepositEffectClientRpc(); } [Rpc(SendTo.ClientsAndHost)] private void UpdatePlayerResourcesClientRpc(ulong playerId) { var playerObject = NetworkManager.Singleton.ConnectedClients[playerId].PlayerObject; if (playerObject != null) { var playerInventory = playerObject.GetComponent(); if (playerInventory != null) { playerInventory.RequestResourceUpdateServerRpc(); } } } [Rpc(SendTo.ClientsAndHost)] private void ShowDepositEffectClientRpc() { if (depositEffectPrefab != null && effectSpawnPoint != null) { GameObject effect = Instantiate(depositEffectPrefab, effectSpawnPoint.position, effectSpawnPoint.rotation); Destroy(effect, 2f); } } #endregion } }