using Northbound.Data; using System.Collections.Generic; using Unity.Netcode; using UnityEngine; namespace Northbound { public class CreepCamp : NetworkBehaviour { [Header("Camp Settings")] [Tooltip("Creep prefabs available to spawn")] [SerializeField] private List creepPrefabs = new List(); [Header("Reward Settings")] [Tooltip("Resource pickup prefab to spawn when all creeps are defeated")] [SerializeField] private GameObject resourcePickupPrefab; [Tooltip("Base resource amount multiplier (actual amount = this * camp strength)")] [SerializeField] private int baseResourceAmount = 50; private float _zPosition; private float _campStrength; private float _campCostBudget; private float _spawnRadius; private int _maxSpawnAttempts; private readonly List _spawnedCreeps = new List(); private ResourcePickup _resourcePickup; private readonly Dictionary> _deathHandlers = new Dictionary>(); public override void OnNetworkSpawn() { if (IsServer) { SpawnCreeps(); } } public override void OnNetworkDespawn() { base.OnNetworkDespawn(); if (IsServer) { // 모든 이벤트 구독 해제 foreach (var kvp in _deathHandlers) { if (kvp.Key != null && kvp.Value != null) { kvp.Key.OnDeath -= kvp.Value; } } // 리스트와 딕셔너리 비우기 _spawnedCreeps.Clear(); _deathHandlers.Clear(); } } public void InitializeCamp(float zPosition, float strengthMultiplier, float costBudget, float radius, int maxAttempts) { _zPosition = zPosition; _campStrength = strengthMultiplier; _campCostBudget = costBudget; _spawnRadius = radius; _maxSpawnAttempts = maxAttempts; } public void SetCreepPrefabs(List prefabs) { creepPrefabs.Clear(); creepPrefabs.AddRange(prefabs); } private void SpawnCreeps() { if (creepPrefabs.Count == 0) { Debug.LogWarning($"[CreepCamp] No creep prefabs assigned!"); return; } // 리소스 픽업 스폰 (비활성화 상태로) SpawnResourcePickup(); float remainingCost = _campCostBudget * _campStrength; int spawnedCount = 0; for (int attempt = 0; attempt < _maxSpawnAttempts && remainingCost > 0; attempt++) { GameObject selectedCreep = SelectCreepByCost(remainingCost); if (selectedCreep == null) { Debug.LogWarning($"[CreepCamp] No affordable creeps. Remaining cost: {remainingCost:F2}"); break; } CreepData creepData = GetCreepDataFromPrefab(selectedCreep); if (creepData == null) { Debug.LogWarning($"[CreepCamp] Could not get creep data from {selectedCreep.name}"); continue; } SpawnCreep(selectedCreep); remainingCost -= creepData.cost; spawnedCount++; } Debug.Log($"[CreepCamp] Spawned {spawnedCount} creeps (Cost budget: {_campCostBudget * _campStrength:F2}, Used: {(_campCostBudget * _campStrength) - remainingCost:F2})"); } private GameObject SelectCreepByCost(float remainingCost) { List affordableCreeps = new List(); foreach (var prefab in creepPrefabs) { CreepData creepData = GetCreepDataFromPrefab(prefab); if (creepData != null && creepData.cost <= remainingCost) { affordableCreeps.Add(prefab); } } if (affordableCreeps.Count == 0) { return null; } float totalWeight = 0f; foreach (var prefab in affordableCreeps) { CreepData creepData = GetCreepDataFromPrefab(prefab); if (creepData != null) { totalWeight += creepData.weight; } } if (totalWeight == 0f) { return affordableCreeps[Random.Range(0, affordableCreeps.Count)]; } float randomValue = Random.Range(0f, totalWeight); float cumulativeWeight = 0f; foreach (var prefab in affordableCreeps) { CreepData creepData = GetCreepDataFromPrefab(prefab); if (creepData != null) { cumulativeWeight += creepData.weight; if (randomValue <= cumulativeWeight) { return prefab; } } } return affordableCreeps[Random.Range(0, affordableCreeps.Count)]; } private bool CanSpawnAnyCreep(float remainingCost) { foreach (var prefab in creepPrefabs) { CreepData creepData = GetCreepDataFromPrefab(prefab); if (creepData != null && creepData.cost <= remainingCost) { return true; } } return false; } private void SpawnResourcePickup() { if (resourcePickupPrefab == null) { Debug.LogWarning($"[CreepCamp] No resource pickup prefab assigned!"); return; } GameObject pickup = Instantiate(resourcePickupPrefab, transform.position, Quaternion.identity); _resourcePickup = pickup.GetComponent(); if (_resourcePickup == null) { Debug.LogError($"[CreepCamp] ResourcePickup component not found on prefab!"); Destroy(pickup); return; } // 캠프 강도에 비례하여 리소스 양 설정 _resourcePickup.resourceAmount = Mathf.RoundToInt(baseResourceAmount * _campStrength); // NetworkObject 추가 및 스폰 NetworkObject networkObj = pickup.GetComponent(); if (networkObj == null) { networkObj = pickup.AddComponent(); } networkObj.SpawnWithOwnership(NetworkManager.Singleton.LocalClientId); // 비활성화는 ServerRpc를 통해 처리 DisablePickupClientRpc(); Debug.Log($"[CreepCamp] Resource pickup spawned (Amount: {_resourcePickup.resourceAmount})"); } private void HandleCreepDeath(EnemyUnit deadCreep) { // 이벤트 구독 해제 (메모리 누수 방지) if (_deathHandlers.ContainsKey(deadCreep)) { if (deadCreep != null) { deadCreep.OnDeath -= _deathHandlers[deadCreep]; } _deathHandlers.Remove(deadCreep); } // 리스트에서 해당 creep 제거 _spawnedCreeps.Remove(deadCreep); Debug.Log($"[CreepCamp] Creep died. Remaining creeps: {_spawnedCreeps.Count}"); // 모든 creep이 처치되었으면 ResourcePickup 활성화 if (_spawnedCreeps.Count == 0 && _resourcePickup != null) { EnableResourcePickupClientRpc(); Debug.Log($"[CreepCamp] All creeps defeated! Resource pickup enabled."); } } [Rpc(SendTo.ClientsAndHost)] private void EnableResourcePickupClientRpc() { if (_resourcePickup != null) { _resourcePickup.gameObject.SetActive(true); } } [Rpc(SendTo.ClientsAndHost)] private void DisablePickupClientRpc() { if (_resourcePickup != null) { _resourcePickup.gameObject.SetActive(false); } } private CreepData GetCreepDataFromPrefab(GameObject prefab) { if (prefab == null) return null; CreepDataComponent component = prefab.GetComponent(); if (component != null) { return component.creepData; } return null; } private void SpawnCreep(GameObject prefab) { Vector3 spawnOffset = Random.insideUnitSphere * _spawnRadius; spawnOffset.y = 0; GameObject creep = Instantiate(prefab, transform.position + spawnOffset, Quaternion.identity); if (creep.GetComponent() == null) { var visibility = creep.AddComponent(); visibility.showInExploredAreas = false; visibility.updateInterval = 0.2f; } NetworkObject networkObj = creep.GetComponent(); if (networkObj == null) { networkObj = creep.AddComponent(); } // EnemyUnit 참조 저장 및 이벤트 구독 EnemyUnit enemyUnit = creep.GetComponent(); if (enemyUnit != null) { _spawnedCreeps.Add(enemyUnit); // Dictionary에 핸들러 저장 (메모리 누수 방지) System.Action handler = (killerId) => HandleCreepDeath(enemyUnit); _deathHandlers[enemyUnit] = handler; enemyUnit.OnDeath += handler; } else { Debug.LogWarning($"[CreepCamp] EnemyUnit component not found on creep prefab {prefab.name}"); } networkObj.SpawnWithOwnership(NetworkManager.Singleton.LocalClientId); } private void OnDrawGizmos() { Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, _spawnRadius); } private void OnDrawGizmosSelected() { Gizmos.color = new Color(1f, 0f, 0f, 0.3f); Gizmos.DrawSphere(transform.position, _spawnRadius); } } }