using System.Collections.Generic; using GeometryTD.DataTable; using GeometryTD.Definition; using GeometryTD.Entity; using GeometryTD.UI; using UnityEngine; using UnityGameFramework.Runtime; namespace GeometryTD.CustomComponent { public class CombatScheduler { private enum SchedulerState : byte { Idle = 0, WaitingForLoading = 1, RunningPhase = 2, WaitingForFinishReturn = 3, Completed = 4, Failed = 5 } private readonly List _phaseBuffer = new(); private readonly Dictionary> _spawnEntriesByPhaseId = new(); private readonly EnemyManager _enemyManager = new(); private readonly PhaseLoopRuntime _phaseLoopRuntime = new(); private readonly CombatLoadSession _loadSession = new(); private readonly CombatEventBridge _eventBridge = new(); private EntityComponent _entity; private DRLevel _currentLevel; private CombatFinishFormUseCase _combatFinishFormUseCase; private SchedulerState _state = SchedulerState.Idle; private bool _initialized; private int _gainedCoin; private int _gainedGold; public bool IsRunning => _state == SchedulerState.WaitingForLoading || _state == SchedulerState.RunningPhase; public bool IsCompleted => _state == SchedulerState.Completed; public DRLevel CurrentLevel => _currentLevel; public DRLevelPhase CurrentPhase => _phaseLoopRuntime.CurrentPhase; public MapEntity CurrentMap => _loadSession.CurrentMap; public int DisplayPhaseIndex => _phaseLoopRuntime.DisplayPhaseIndex; public bool CanEndCombat => _phaseLoopRuntime.CanEndCombat; public int DefeatedEnemyCount => _enemyManager.DefeatedEnemyCount; public int GainedCoin => _gainedCoin; public int GainedGold => _gainedGold; public void OnInit() { if (!_initialized) { _entity = GameEntry.Entity; _eventBridge.Bind( OnShowEntitySuccess, OnShowEntityFailure, OnHideEntityComplete, OnOpenUIFormSuccess, OnOpenUIFormFailure, OnCloseUIFormComplete); _enemyManager.OnInit(this); _loadSession.OnInit(_entity); EnsureCombatFinishFormUseCaseBound(); _initialized = true; } ResetRuntime(); } public void Start( DRLevel level, IReadOnlyList phases, IReadOnlyDictionary> spawnEntriesByPhaseId) { if (!_initialized || _entity == null) { _state = SchedulerState.Failed; Log.Warning("CombatScheduler start failed. Runtime is not initialized."); return; } if (level == null || phases == null || phases.Count <= 0) { _state = SchedulerState.Failed; Log.Warning("CombatScheduler start failed. Invalid level or phase data."); return; } CleanupAllCombatEntities(); CloseCombatFinishForm(); _enemyManager.EndPhase(); _enemyManager.ResetCombatStats(); ResetRuntime(); _gainedCoin = 0; _gainedGold = 0; _currentLevel = level; for (int i = 0; i < phases.Count; i++) { DRLevelPhase phase = phases[i]; if (phase != null) { _phaseBuffer.Add(phase); } } if (spawnEntriesByPhaseId != null) { foreach (var pair in spawnEntriesByPhaseId) { _spawnEntriesByPhaseId[pair.Key] = pair.Value; } } _phaseLoopRuntime.SetPhases(_phaseBuffer); if (_phaseLoopRuntime.PhaseCount <= 0) { _state = SchedulerState.Failed; Log.Warning("CombatScheduler start failed. Level '{0}' has no phase data.", level.Id); return; } if (!_loadSession.StartLoading(level, out string loadError)) { _state = SchedulerState.Failed; Log.Warning("CombatScheduler start failed. {0}", loadError); return; } _state = SchedulerState.WaitingForLoading; Log.Info("CombatScheduler started. Level={0}, PhaseCount={1}.", _currentLevel.Id, _phaseLoopRuntime.PhaseCount); } public void OnUpdate(float elapseSeconds, float realElapseSeconds) { switch (_state) { case SchedulerState.WaitingForLoading: if (!_loadSession.IsReady) { return; } BeginNextPhase(); return; case SchedulerState.RunningPhase: UpdateCurrentPhase(elapseSeconds, realElapseSeconds); return; default: return; } } public void OnDestroy() { if (!_initialized) { return; } CleanupAllCombatEntities(); CloseCombatFinishForm(); _enemyManager.OnDestroy(); ResetRuntime(); _eventBridge.Unbind(); _combatFinishFormUseCase = null; _entity = null; _initialized = false; } public bool TryEndCombatByPlayer() { if (_state != SchedulerState.RunningPhase) { return false; } if (!_phaseLoopRuntime.TryRequestEndCombat()) { return false; } EnterFinishFlow("Combat ended by player."); return true; } private void RefreshCombatInfoForm() { _loadSession.RefreshCombatInfoForm(_currentLevel); } private void UpdateCurrentPhase(float elapseSeconds, float realElapseSeconds) { DRLevelPhase currentPhase = _phaseLoopRuntime.CurrentPhase; if (currentPhase == null) { _state = SchedulerState.Failed; Log.Warning("CombatScheduler update failed. Current phase is null."); return; } _phaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds); _enemyManager.OnUpdate(elapseSeconds, realElapseSeconds); if (!_phaseLoopRuntime.ShouldEndCurrentPhase(_enemyManager.IsPhaseSpawnCompleted, _enemyManager.AliveEnemyCount)) { return; } CompleteCurrentPhase(); } private void CompleteCurrentPhase() { _enemyManager.EndPhase(); Log.Info( "CombatScheduler phase completed. Level={0}, Phase={1}, Elapsed={2:F2}s.", _currentLevel != null ? _currentLevel.Id : 0, _phaseLoopRuntime.CurrentPhase != null ? _phaseLoopRuntime.CurrentPhase.Id : 0, _phaseLoopRuntime.CurrentPhaseElapsed); BeginNextPhase(); } private void BeginNextPhase() { if (!_phaseLoopRuntime.TryEnterNextPhase(out DRLevelPhase nextPhase)) { EnterFinishFlow("Combat ended after loop completion."); return; } RefreshCombatInfoForm(); _spawnEntriesByPhaseId.TryGetValue(nextPhase.Id, out IReadOnlyList spawnEntries); _enemyManager.BeginPhase(nextPhase, spawnEntries); _state = SchedulerState.RunningPhase; Log.Info( "CombatScheduler phase started. Level={0}, Phase={1}, EndType={2}, Entries={3}.", _currentLevel != null ? _currentLevel.Id : 0, _phaseLoopRuntime.DisplayPhaseIndex, nextPhase.EndType, spawnEntries != null ? spawnEntries.Count : 0); } private void EnterFinishFlow(string reason) { int defeatedEnemyCount = _enemyManager.DefeatedEnemyCount; // Step 1: stop runtime and clear enemy entities only. _enemyManager.EndPhase(); _state = SchedulerState.WaitingForFinishReturn; _enemyManager.CleanupTrackedEnemies(); Log.Info( "CombatScheduler entered finish flow. Level={0}. Reason={1}", _currentLevel != null ? _currentLevel.Id : 0, reason); OpenCombatFinishForm(defeatedEnemyCount, _gainedGold); } private void ResetRuntime() { _phaseBuffer.Clear(); _spawnEntriesByPhaseId.Clear(); _phaseLoopRuntime.Reset(); _loadSession.Reset(); _currentLevel = null; _state = SchedulerState.Idle; } private void CleanupAllCombatEntities() { _loadSession.Cleanup(); _enemyManager.CleanupTrackedEnemies(); } private void EnsureCombatFinishFormUseCaseBound() { _combatFinishFormUseCase ??= new CombatFinishFormUseCase(this); GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatFinishForm, _combatFinishFormUseCase); } private void OpenCombatFinishForm(int defeatedEnemyCount, int gainedGold) { EnsureCombatFinishFormUseCaseBound(); _combatFinishFormUseCase.SetSummary( defeatedEnemyCount, gainedGold); GameEntry.UIRouter.OpenUI(UIFormType.CombatFinishForm); } public void OnEnemyDefeatedRewardResolved(int gainedCoin, int gainedGold) { int coin = Mathf.Max(0, gainedCoin); int gold = Mathf.Max(0, gainedGold); _gainedCoin += coin; _gainedGold += gold; GameEntry.CombatNode?.ApplyEnemyDropReward(coin, gold); } private void CloseCombatFinishForm() { GameEntry.UIRouter.CloseUI(UIFormType.CombatFinishForm); } public bool OnCombatFinishReturnRequested() { if (_state != SchedulerState.WaitingForFinishReturn) { return false; } // Step 2: clear remaining map/UI resources and close finish form. _loadSession.Cleanup(); CloseCombatFinishForm(); _state = SchedulerState.Completed; GameEntry.CombatNode.EndCombat(); return true; } #region Event Handlers private void OnShowEntitySuccess(ShowEntitySuccessEventArgs args) { var status = _loadSession.HandleShowEntitySuccess(args, out string errorMessage); if (status == CombatLoadSession.EventHandleStatus.Failed) { _state = SchedulerState.Failed; Log.Error("CombatScheduler failed. {0}", errorMessage); return; } if (status == CombatLoadSession.EventHandleStatus.Succeeded) { MapEntity map = _loadSession.CurrentMap; Log.Info( "Map ready. LevelId={0}, PathCells={1}, FoundationCells={2}, Spawners={3}, House={4}.", _currentLevel != null ? _currentLevel.Id : 0, map.PathCells.Count, map.FoundationCells.Count, map.Spawners.Length, map.House != null ? map.House.name : "None"); } } private void OnShowEntityFailure(ShowEntityFailureEventArgs args) { var status = _loadSession.HandleShowEntityFailure(args, out string errorMessage); if (status == CombatLoadSession.EventHandleStatus.Failed) { _state = SchedulerState.Failed; Log.Error("CombatScheduler failed. LevelId={0}, {1}", _currentLevel != null ? _currentLevel.Id : 0, errorMessage); } } private void OnHideEntityComplete(HideEntityCompleteEventArgs args) { _loadSession.HandleHideEntityComplete(args); } private void OnOpenUIFormSuccess(OpenUIFormSuccessEventArgs args) { var status = _loadSession.HandleOpenUIFormSuccess(args, out string errorMessage); if (status == CombatLoadSession.EventHandleStatus.Failed) { _state = SchedulerState.Failed; Log.Error("CombatScheduler failed. {0}", errorMessage); } } private void OnOpenUIFormFailure(OpenUIFormFailureEventArgs args) { var status = _loadSession.HandleOpenUIFormFailure(args, out string errorMessage); if (status == CombatLoadSession.EventHandleStatus.Failed) { _state = SchedulerState.Failed; Log.Error("CombatScheduler failed. {0}", errorMessage); } } private void OnCloseUIFormComplete(CloseUIFormCompleteEventArgs args) { _loadSession.HandleCloseUIFormComplete(args); } #endregion } }