diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseLoopRuntime.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseLoopRuntime.cs index 5a132e2..c61024b 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseLoopRuntime.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseLoopRuntime.cs @@ -1,8 +1,5 @@ using System.Collections.Generic; -using System.Globalization; using GeometryTD.DataTable; -using GeometryTD.Definition; -using UnityEngine; namespace GeometryTD.CustomComponent { @@ -71,31 +68,6 @@ namespace GeometryTD.CustomComponent _currentPhaseElapsed += elapseSeconds; } - public bool ShouldEndCurrentPhase(bool isPhaseSpawnCompleted, int aliveEnemyCount) - { - if (_currentPhase == null) - { - return false; - } - - switch (_currentPhase.EndType) - { - case PhaseEndType.TimeElapsed: - return _currentPhaseElapsed >= ResolveTimeElapsedThreshold(_currentPhase); - case PhaseEndType.EnemiesCleared: - case PhaseEndType.BossDead: - return isPhaseSpawnCompleted && aliveEnemyCount <= 0; - case PhaseEndType.None: - default: - if (_currentPhase.DurationSeconds > 0 && _currentPhaseElapsed >= _currentPhase.DurationSeconds) - { - return true; - } - - return isPhaseSpawnCompleted && aliveEnemyCount <= 0; - } - } - public bool TryEnterNextPhase(out DRLevelPhase nextPhase) { nextPhase = null; @@ -125,21 +97,5 @@ namespace GeometryTD.CustomComponent nextPhase = _currentPhase; return true; } - - private static float ResolveTimeElapsedThreshold(DRLevelPhase phase) - { - if (!string.IsNullOrWhiteSpace(phase.EndParam) && - float.TryParse(phase.EndParam, NumberStyles.Float, CultureInfo.InvariantCulture, out float parsed)) - { - return parsed; - } - - if (phase.DurationSeconds > 0) - { - return phase.DurationSeconds; - } - - return 0f; - } } } diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatStates/CombatWaitingForPhaseEndState.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatStates/CombatWaitingForPhaseEndState.cs index f1f9c74..b3da1ad 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatStates/CombatWaitingForPhaseEndState.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatStates/CombatWaitingForPhaseEndState.cs @@ -12,7 +12,8 @@ namespace GeometryTD.CustomComponent { _ = realElapseSeconds; - if (Scheduler._phaseLoopRuntime.CurrentPhase == null) + var currentPhase = Scheduler._phaseLoopRuntime.CurrentPhase; + if (currentPhase == null) { Scheduler.EnterFailureFallback("CombatScheduler waiting phase failed. Current phase is null."); return; @@ -26,9 +27,14 @@ namespace GeometryTD.CustomComponent return; } - if (!Scheduler._phaseLoopRuntime.ShouldEndCurrentPhase( - Scheduler._enemyManager.IsPhaseSpawnCompleted, - Scheduler._enemyManager.AliveEnemyCount)) + PhaseEndConditionContext context = new( + currentPhase, + Scheduler._phaseLoopRuntime.CurrentPhaseElapsed, + Scheduler._enemyManager.IsPhaseSpawnCompleted, + Scheduler._enemyManager.AliveEnemyCount, + Scheduler._enemyManager.HasAliveBoss); + IPhaseEndCondition endCondition = PhaseEndConditionFactory.Create(currentPhase.EndType); + if (!endCondition.ShouldExit(context)) { return; } @@ -37,4 +43,4 @@ namespace GeometryTD.CustomComponent } } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyLifecycleTracker.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyLifecycleTracker.cs index 5546d32..9408783 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyLifecycleTracker.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyLifecycleTracker.cs @@ -6,22 +6,28 @@ namespace GeometryTD.CustomComponent { internal sealed class EnemyLifecycleTracker { + private readonly HashSet _aliveBossEntityIds = new(); private readonly HashSet _trackedEnemyEntityIds = new(); private readonly Dictionary _trackedEnemyConfigByEntityId = new(); + private readonly Dictionary _bossFlagsByEntityId = new(); public int AliveEnemyCount { get; private set; } + public bool HasAliveBoss => _aliveBossEntityIds.Count > 0; public void Reset() { + _aliveBossEntityIds.Clear(); + _bossFlagsByEntityId.Clear(); _trackedEnemyEntityIds.Clear(); _trackedEnemyConfigByEntityId.Clear(); AliveEnemyCount = 0; } - public void TrackEnemy(int entityId, DREnemy enemyConfig) + public void TrackEnemy(int entityId, DREnemy enemyConfig, bool isBoss) { _trackedEnemyEntityIds.Add(entityId); _trackedEnemyConfigByEntityId[entityId] = enemyConfig; + _bossFlagsByEntityId[entityId] = isBoss; } public bool Contains(int entityId) @@ -34,11 +40,17 @@ namespace GeometryTD.CustomComponent if (_trackedEnemyEntityIds.Contains(entityId)) { AliveEnemyCount++; + if (_bossFlagsByEntityId.TryGetValue(entityId, out bool isBoss) && isBoss) + { + _aliveBossEntityIds.Add(entityId); + } } } public void HandleShowFailure(int entityId) { + _aliveBossEntityIds.Remove(entityId); + _bossFlagsByEntityId.Remove(entityId); _trackedEnemyEntityIds.Remove(entityId); _trackedEnemyConfigByEntityId.Remove(entityId); } @@ -48,11 +60,15 @@ namespace GeometryTD.CustomComponent enemyConfig = null; if (!_trackedEnemyEntityIds.Remove(entityId)) { + _aliveBossEntityIds.Remove(entityId); + _bossFlagsByEntityId.Remove(entityId); _trackedEnemyConfigByEntityId.Remove(entityId); return false; } _trackedEnemyConfigByEntityId.TryGetValue(entityId, out enemyConfig); + _aliveBossEntityIds.Remove(entityId); + _bossFlagsByEntityId.Remove(entityId); _trackedEnemyConfigByEntityId.Remove(entityId); AliveEnemyCount = Mathf.Max(0, AliveEnemyCount - 1); return true; diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyManager.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyManager.cs index 29dbd2a..877c017 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyManager.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyManager.cs @@ -24,6 +24,7 @@ namespace GeometryTD.CustomComponent public int AliveEnemyCount => _enemyLifecycleTracker.AliveEnemyCount; public int DefeatedEnemyCount => _defeatedEnemyCount; + public bool HasAliveBoss => _enemyLifecycleTracker.HasAliveBoss; public bool IsPhaseSpawnCompleted => _enemySpawnDirector.IsPhaseSpawnCompleted; public bool IsPhaseRunning => _enemySpawnDirector.IsPhaseRunning; @@ -150,11 +151,12 @@ namespace GeometryTD.CustomComponent } int scaledBaseHp = _enemyConfigService.ResolveScaledEnemyBaseHp(enemyConfig.BaseHp, _combatScheduler); + bool isBoss = entry.EntryType == Definition.EntryType.Boss; for (int i = 0; i < spawnCount; i++) { int enemyEntityId = _entity.GenerateSerialId(); - _enemyLifecycleTracker.TrackEnemy(enemyEntityId, enemyConfig); + _enemyLifecycleTracker.TrackEnemy(enemyEntityId, enemyConfig, isBoss); EnemyData enemyData = new EnemyData( enemyEntityId, enemyConfig.EntityId,