using System.Collections.Generic; using GameFramework.Event; using GeometryTD.DataTable; using GeometryTD.Entity; using GeometryTD.Entity.EntityData; using UnityEngine; using UnityGameFramework.Runtime; using Random = UnityEngine.Random; namespace GeometryTD.CustomComponent { public class EnemyManager { private readonly List _trackedEnemyIdBuffer = new(); private readonly EnemySpawnDirector _enemySpawnDirector = new(); private readonly EnemyConfigService _enemyConfigService = new(); private readonly SpawnerResolver _spawnerResolver = new(); private readonly EnemyLifecycleTracker _enemyLifecycleTracker = new(); private CombatScheduler _combatScheduler; private EntityComponent _entity; private int _defeatedEnemyCount; private bool _initialized; public int AliveEnemyCount => _enemyLifecycleTracker.AliveEnemyCount; public int DefeatedEnemyCount => _defeatedEnemyCount; public bool IsPhaseSpawnCompleted => _enemySpawnDirector.IsPhaseSpawnCompleted; public bool IsPhaseRunning => _enemySpawnDirector.IsPhaseRunning; public void OnInit(CombatScheduler combatScheduler) { _combatScheduler = combatScheduler; if (_initialized) { return; } _entity = GameEntry.Entity; _defeatedEnemyCount = 0; _enemySpawnDirector.Reset(); _enemyConfigService.Reset(); _spawnerResolver.Reset(); _trackedEnemyIdBuffer.Clear(); _enemyLifecycleTracker.Reset(); GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess); GameEntry.Event.Subscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure); GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete); _initialized = true; } public void BeginPhase(DRLevelPhase phase, IReadOnlyList spawnEntries) { if (!_initialized || _combatScheduler == null) { return; } _ = phase; EndPhase(); _spawnerResolver.RefreshCache(_combatScheduler, true); _enemySpawnDirector.BeginPhase(spawnEntries); } public void OnUpdate(float elapseSeconds, float realElapseSeconds) { if (!_initialized || _combatScheduler == null || !_enemySpawnDirector.IsPhaseRunning) { return; } _spawnerResolver.RefreshCache(_combatScheduler, false); _enemySpawnDirector.OnUpdate(elapseSeconds, SpawnEnemies); } public void EndPhase() { _enemySpawnDirector.EndPhase(); } public void OnDestroy() { if (!_initialized) { _combatScheduler = null; return; } CleanupTrackedEnemies(); EndPhase(); GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess); GameEntry.Event.Unsubscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure); GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete); _spawnerResolver.Reset(); _trackedEnemyIdBuffer.Clear(); _enemyLifecycleTracker.Reset(); _defeatedEnemyCount = 0; _enemyConfigService.Reset(); _combatScheduler = null; _initialized = false; } public void ResetCombatStats() { _defeatedEnemyCount = 0; } public void CleanupTrackedEnemies() { _enemyLifecycleTracker.CopyTrackedEntityIdsTo(_trackedEnemyIdBuffer); if (_trackedEnemyIdBuffer.Count <= 0) { return; } _enemyLifecycleTracker.Reset(); if (_entity == null) { return; } for (int i = 0; i < _trackedEnemyIdBuffer.Count; i++) { int trackedEnemyEntityId = _trackedEnemyIdBuffer[i]; if (_entity.HasEntity(trackedEnemyEntityId) || _entity.IsLoadingEntity(trackedEnemyEntityId)) { _entity.HideEntity(trackedEnemyEntityId); } } } private void SpawnEnemies(DRLevelSpawnEntry entry, int spawnCount) { if (spawnCount <= 0) { return; } if (!_spawnerResolver.TryResolveSpawnPath(_combatScheduler, entry.SpawnPointId, out IReadOnlyList pathPoints)) { return; } DREnemy enemyConfig = _enemyConfigService.GetEnemyConfig(entry.EnemyId); if (enemyConfig == null) { return; } int scaledBaseHp = _enemyConfigService.ResolveScaledEnemyBaseHp(enemyConfig.BaseHp, _combatScheduler); for (int i = 0; i < spawnCount; i++) { int enemyEntityId = _entity.GenerateSerialId(); _enemyLifecycleTracker.TrackEnemy(enemyEntityId, enemyConfig); EnemyData enemyData = new EnemyData( enemyEntityId, enemyConfig.EntityId, pathPoints[0], scaledBaseHp, enemyConfig.Speed, pathPoints); _entity.ShowEnemy(enemyData); } } private void OnShowEntitySuccess(object sender, GameEventArgs e) { if (!(e is ShowEntitySuccessEventArgs ne)) return; if (ne.EntityLogicType == typeof(EnemyEntity) && _enemyLifecycleTracker.Contains(ne.Entity.Id)) { _enemyLifecycleTracker.HandleShowSuccess(ne.Entity.Id); } } private void OnShowEntityFailure(object sender, GameEventArgs e) { if (!(e is ShowEntityFailureEventArgs ne)) { return; } if (ne.EntityLogicType != typeof(EnemyEntity)) { return; } _enemyLifecycleTracker.HandleShowFailure(ne.EntityId); } private void OnHideEntityComplete(object sender, GameEventArgs e) { if (!(e is HideEntityCompleteEventArgs ne)) { return; } if (!_enemyLifecycleTracker.TryHandleHideComplete(ne.EntityId, out DREnemy enemyConfig)) { return; } bool wasKilled = EnemyEntity.TryConsumeKilledFlag(ne.EntityId); bool isCombatRunning = _combatScheduler != null && _combatScheduler.IsRunning; int baseDamage = 0; int droppedCoin = 0; int droppedGold = 0; if (enemyConfig != null) { baseDamage = Mathf.Max(0, enemyConfig.BaseDamage); if (wasKilled) { droppedCoin = Mathf.Max(0, enemyConfig.DropCoin); float dropRate = enemyConfig.DropPercent > 1f ? Mathf.Clamp01(enemyConfig.DropPercent * 0.01f) : Mathf.Clamp01(enemyConfig.DropPercent); if (enemyConfig.DropGold > 0 && dropRate > 0f && Random.value <= dropRate) { droppedGold = Mathf.Max(0, enemyConfig.DropGold); } } } if (isCombatRunning && wasKilled) { _defeatedEnemyCount++; _combatScheduler.OnEnemyDefeatedRewardResolved(droppedCoin, droppedGold); } else if (isCombatRunning && baseDamage > 0) { _combatScheduler.OnEnemyReachedBase(baseDamage); } } } }