using System.Collections.Generic; using GeometryTD.CustomEvent; using GeometryTD.DataTable; using GeometryTD.Definition; using GeometryTD.Entity; using GeometryTD.UI; using UnityEngine; using UnityGameFramework.Runtime; namespace GeometryTD.CustomComponent { public partial class CombatScheduler : ICombatSchedulerHost { private readonly CombatSchedulerRuntimeContext _context = new(); private readonly CombatSchedulerFlowCoordinator _flowCoordinator; private bool _initialized; public CombatScheduler() { _flowCoordinator = new CombatSchedulerFlowCoordinator(this, _context); } public bool IsRunning => _context.CurrentState is CombatLoadingState or CombatRunningPhaseState or CombatWaitingForPhaseEndState; public bool IsCompleted => _context.IsCompleted; public DRLevel CurrentLevel => _context.CurrentLevel; public DRLevelPhase CurrentPhase => _context.PhaseLoopRuntime.CurrentPhase; public MapEntity CurrentMap => _context.LoadSession.CurrentMap; public int DisplayPhaseIndex => _context.PhaseLoopRuntime.DisplayPhaseIndex; public int PhaseCount => _context.PhaseLoopRuntime.PhaseCount; public bool CanEndCombat => _context.PhaseLoopRuntime.CanEndCombat; public int CurrentCoin => _context.CombatInRunResourceManager.CurrentCoin; public int CurrentGold => _context.CombatInRunResourceManager.CurrentGold; public int CurrentBaseHp => _context.CombatInRunResourceManager.CurrentBaseHp; public int CurrentBuildTowerCount => _context.CombatInRunResourceManager.CurrentBuildTowerCount; public int DefeatedEnemyCount => _context.EnemyManager.DefeatedEnemyCount; public int GainedCoin => _context.CombatInRunResourceManager.GainedCoin; public int GainedGold => _context.CombatInRunResourceManager.GainedGold; public void OnInit() { if (!_initialized) { _context.Entity = GameEntry.Entity; _context.EventBridge.Bind( OnShowEntitySuccess, OnShowEntityFailure, OnHideEntityComplete, OnOpenUIFormSuccess, OnOpenUIFormFailure, OnCloseUIFormComplete); _context.EnemyManager.OnInit(this); _context.LoadSession.OnInit(_context.Entity); _flowCoordinator.EnsureCombatFinishFormUseCaseBound(); _flowCoordinator.EnsureRewardSelectFormUseCaseBound(); _initialized = true; } _flowCoordinator.ResetRuntime(); } public bool Start( DRLevel level, IReadOnlyList phases, IReadOnlyDictionary> spawnEntriesByPhaseId) { if (!_initialized || _context.Entity == null) { return _flowCoordinator.HandleStartFailure("CombatScheduler start failed. Runtime is not initialized."); } if (level == null || phases == null || phases.Count <= 0) { return _flowCoordinator.HandleStartFailure("CombatScheduler start failed. Invalid level or phase data."); } _flowCoordinator.CleanupAllCombatEntities(); _flowCoordinator.CloseCombatFinishForm(); _flowCoordinator.CloseRewardSelectForm(); _flowCoordinator.CloseDialogForm(); _context.EnemyManager.EndPhase(); _context.EnemyManager.ResetCombatStats(); _flowCoordinator.ResetRuntime(); _context.IsFinishAsVictory = true; _context.CurrentLevel = level; _context.CombatInRunResourceManager.InitializeForCombat(level); for (int i = 0; i < phases.Count; i++) { DRLevelPhase phase = phases[i]; if (phase != null) { _context.PhaseBuffer.Add(phase); } } if (spawnEntriesByPhaseId != null) { foreach (var pair in spawnEntriesByPhaseId) { _context.SpawnEntriesByPhaseId[pair.Key] = pair.Value; } } _context.PhaseLoopRuntime.SetPhases(_context.PhaseBuffer); if (_context.PhaseLoopRuntime.PhaseCount <= 0) { return _flowCoordinator.HandleStartFailure($"CombatScheduler start failed. Level '{level.Id}' has no phase data."); } ChangeState(new CombatLoadingState(_context, _flowCoordinator)); Log.Info( "CombatScheduler started. Level={0}, PhaseCount={1}.", _context.CurrentLevel.Id, _context.PhaseLoopRuntime.PhaseCount); return true; } public void OnUpdate(float elapseSeconds, float realElapseSeconds) { _context.CurrentState?.OnUpdate(elapseSeconds, realElapseSeconds); } public void OnDestroy() { if (!_initialized) { return; } _context.CurrentState?.OnExit(); _context.CurrentState?.OnDestroy(); _context.CurrentState = null; _flowCoordinator.CleanupAllCombatEntities(); _flowCoordinator.CloseCombatFinishForm(); _flowCoordinator.CloseRewardSelectForm(); _flowCoordinator.CloseDialogForm(); _context.EnemyManager.OnDestroy(); _flowCoordinator.ResetRuntime(); _context.EventBridge.Unbind(); _context.CombatFinishFormUseCase = null; _context.RewardSelectFormUseCase = null; _context.Entity = null; _initialized = false; } public bool TryEndCombatByPlayer() { if (_context.CurrentState is not CombatRunningPhaseState && _context.CurrentState is not CombatWaitingForPhaseEndState) { return false; } return _context.PhaseLoopRuntime.TryRequestEndCombat(); } public void OnEnemyReachedBase(DREnemy enemy) { if (!IsRunning) { return; } int resolvedBaseDamage = enemy != null ? Mathf.Max(0, enemy.BaseDamage) : 0; if (resolvedBaseDamage <= 0) { return; } _flowCoordinator.ApplyBaseDamage(resolvedBaseDamage); } public void OnEnemyDefeated(DREnemy enemy) { if (!IsRunning) { return; } EnemyDropResolveContext context = new( enemy, _context.PhaseLoopRuntime.DisplayPhaseIndex, _flowCoordinator.ResolveCurrentThemeType()); EnemyDropResolveResult result = _context.EnemyDropResolver.Resolve(context); _context.CombatInRunResourceManager.AddEnemyDefeatedReward(result.Coin, result.Gold); if (!result.ShouldRollOutGameItem) { return; } _context.CombatInRunResourceManager.TryRollOutGameItemDrop( context.DisplayPhaseIndex, context.ThemeType); } public bool OnCombatFinishReturnRequested() { if (_context.CurrentState is not CombatWaitingForReturnState waitingForReturnState) { return false; } waitingForReturnState.RequestReturn(); return true; } public bool TryConsumeCoin(int coin) { return _context.CombatInRunResourceManager.TryConsumeCoin(coin); } public void AddCoin(int coin) { _context.CombatInRunResourceManager.AddCoin(coin); } public bool TryGetBuildTowerStats(int buildIndex, out TowerStatsData stats) { return _context.CombatInRunResourceManager.TryGetBuildTowerStats(buildIndex, out stats); } public bool TryDebugFail(string errorMessage) { if (_context.IsCompleted || _context.CurrentState == null || _context.CurrentState is CombatFailedState) { return false; } _flowCoordinator.EnterFailureFallback(string.IsNullOrWhiteSpace(errorMessage) ? "Manual debug fail." : errorMessage); return _context.CurrentState is CombatFailedState; } void ICombatSchedulerHost.ChangeState(CombatStateBase nextState) { ChangeState(nextState); } internal void ChangeState(CombatStateBase nextState) { if (ReferenceEquals(_context.CurrentState, nextState)) { return; } _context.CurrentState?.OnExit(); _context.CurrentState?.OnDestroy(); _context.CurrentState = nextState; _context.CurrentState?.OnInit(); _context.CurrentState?.OnEnter(); } #region Event Handlers private void OnShowEntitySuccess(ShowEntitySuccessEventArgs args) { var status = _context.LoadSession.HandleShowEntitySuccess(args, out string errorMessage); if (status == CombatLoadSession.EventHandleStatus.Failed) { _flowCoordinator.EnterFailureFallback(errorMessage); return; } if (status == CombatLoadSession.EventHandleStatus.Succeeded) { MapEntity map = _context.LoadSession.CurrentMap; Log.Info( "Map ready. LevelId={0}, PathCells={1}, FoundationCells={2}, Spawners={3}, House={4}.", _context.CurrentLevel != null ? _context.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 = _context.LoadSession.HandleShowEntityFailure(args, out string errorMessage); if (status == CombatLoadSession.EventHandleStatus.Failed) { _flowCoordinator.EnterFailureFallback(errorMessage); } } private void OnHideEntityComplete(HideEntityCompleteEventArgs args) { _context.LoadSession.HandleHideEntityComplete(args); } private void OnOpenUIFormSuccess(OpenUIFormSuccessEventArgs args) { var status = _context.LoadSession.HandleOpenUIFormSuccess(args, out string errorMessage); if (status == CombatLoadSession.EventHandleStatus.Failed) { _flowCoordinator.EnterFailureFallback(errorMessage); } } private void OnOpenUIFormFailure(OpenUIFormFailureEventArgs args) { var status = _context.LoadSession.HandleOpenUIFormFailure(args, out string errorMessage); if (status == CombatLoadSession.EventHandleStatus.Failed) { _flowCoordinator.EnterFailureFallback(errorMessage); } } private void OnCloseUIFormComplete(CloseUIFormCompleteEventArgs args) { _context.LoadSession.HandleCloseUIFormComplete(args); } #endregion } }