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 { 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 readonly CombatInRunResourceManager _combatInRunResourceManager = new(); private readonly EnemyDropResolver _enemyDropResolver = new(); private readonly CombatSettlementFlowService _settlementFlowService = new(); private EntityComponent _entity; private DRLevel _currentLevel; private CombatFinishFormUseCase _combatFinishFormUseCase; private RewardSelectFormUseCase _rewardSelectFormUseCase; private CombatStateBase _currentState; private bool _initialized; private bool _isFinishAsVictory = true; private bool _isCompleted; private bool _nodeEnterFired; private CombatSettlementContext _settlementContext; public bool IsRunning => _currentState is CombatLoadingState or CombatRunningPhaseState or CombatWaitingForPhaseEndState; public bool IsCompleted => _isCompleted; public DRLevel CurrentLevel => _currentLevel; public DRLevelPhase CurrentPhase => _phaseLoopRuntime.CurrentPhase; public MapEntity CurrentMap => _loadSession.CurrentMap; public int DisplayPhaseIndex => _phaseLoopRuntime.DisplayPhaseIndex; public int PhaseCount => _phaseLoopRuntime.PhaseCount; public bool CanEndCombat => _phaseLoopRuntime.CanEndCombat; public int CurrentCoin => _combatInRunResourceManager.CurrentCoin; public int CurrentGold => _combatInRunResourceManager.CurrentGold; public int CurrentBaseHp => _combatInRunResourceManager.CurrentBaseHp; public int CurrentBuildTowerCount => _combatInRunResourceManager.CurrentBuildTowerCount; public int DefeatedEnemyCount => _enemyManager.DefeatedEnemyCount; public int GainedCoin => _combatInRunResourceManager.GainedCoin; public int GainedGold => _combatInRunResourceManager.GainedGold; public void OnInit() { if (!_initialized) { _entity = GameEntry.Entity; _eventBridge.Bind( OnShowEntitySuccess, OnShowEntityFailure, OnHideEntityComplete, OnOpenUIFormSuccess, OnOpenUIFormFailure, OnCloseUIFormComplete); _enemyManager.OnInit(this); _loadSession.OnInit(_entity); EnsureCombatFinishFormUseCaseBound(); EnsureRewardSelectFormUseCaseBound(); _initialized = true; } ResetRuntime(); } public bool Start( DRLevel level, IReadOnlyList phases, IReadOnlyDictionary> spawnEntriesByPhaseId) { if (!_initialized || _entity == null) { return HandleStartFailure("CombatScheduler start failed. Runtime is not initialized."); } if (level == null || phases == null || phases.Count <= 0) { return HandleStartFailure("CombatScheduler start failed. Invalid level or phase data."); } CleanupAllCombatEntities(); CloseCombatFinishForm(); CloseRewardSelectForm(); _enemyManager.EndPhase(); _enemyManager.ResetCombatStats(); ResetRuntime(); _isFinishAsVictory = true; _currentLevel = level; _combatInRunResourceManager.InitializeForCombat(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) { return HandleStartFailure($"CombatScheduler start failed. Level '{level.Id}' has no phase data."); } ChangeState(new CombatLoadingState(this)); Log.Info( "CombatScheduler started. Level={0}, PhaseCount={1}.", _currentLevel.Id, _phaseLoopRuntime.PhaseCount); return true; } public void OnUpdate(float elapseSeconds, float realElapseSeconds) { _currentState?.OnUpdate(elapseSeconds, realElapseSeconds); } public void OnDestroy() { if (!_initialized) { return; } _currentState?.OnExit(); _currentState?.OnDestroy(); _currentState = null; CleanupAllCombatEntities(); CloseCombatFinishForm(); CloseRewardSelectForm(); _enemyManager.OnDestroy(); ResetRuntime(); _eventBridge.Unbind(); _combatFinishFormUseCase = null; _rewardSelectFormUseCase = null; _entity = null; _initialized = false; } public bool TryEndCombatByPlayer() { if (_currentState is not CombatRunningPhaseState && _currentState is not CombatWaitingForPhaseEndState) { return false; } return _phaseLoopRuntime.TryRequestEndCombat(); } public void OnEnemyReachedBase(DREnemy enemy) { if (!IsRunning) { return; } int resolvedBaseDamage = enemy != null ? Mathf.Max(0, enemy.BaseDamage) : 0; if (resolvedBaseDamage <= 0) { return; } ApplyBaseDamage(resolvedBaseDamage); } public void OnEnemyDefeated(DREnemy enemy) { if (!IsRunning) { return; } EnemyDropResolveContext context = new( enemy, _phaseLoopRuntime.DisplayPhaseIndex, ResolveCurrentThemeType()); EnemyDropResolveResult result = _enemyDropResolver.Resolve(context); _combatInRunResourceManager.AddEnemyDefeatedReward(result.Coin, result.Gold); if (!result.ShouldRollOutGameItem) { return; } _combatInRunResourceManager.TryRollOutGameItemDrop( context.DisplayPhaseIndex, context.ThemeType); } public bool OnCombatFinishReturnRequested() { if (_currentState is not CombatWaitingForReturnState waitingForReturnState) { return false; } waitingForReturnState.RequestReturn(); return true; } public bool TryConsumeCoin(int coin) { return _combatInRunResourceManager.TryConsumeCoin(coin); } public void AddCoin(int coin) { _combatInRunResourceManager.AddCoin(coin); } public bool TryGetBuildTowerStats(int buildIndex, out TowerStatsData stats) { return _combatInRunResourceManager.TryGetBuildTowerStats(buildIndex, out stats); } private void ResetRuntime() { _currentState = null; _phaseBuffer.Clear(); _spawnEntriesByPhaseId.Clear(); _phaseLoopRuntime.Reset(); _loadSession.Reset(); _combatInRunResourceManager.Reset(); _settlementContext = null; _currentLevel = null; _isFinishAsVictory = true; _isCompleted = false; _nodeEnterFired = false; } private void CleanupAllCombatEntities() { _loadSession.Cleanup(); _enemyManager.CleanupTrackedEnemies(); } private void EnsureCombatFinishFormUseCaseBound() { _combatFinishFormUseCase ??= new CombatFinishFormUseCase(this); GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatFinishForm, _combatFinishFormUseCase); } private void EnsureRewardSelectFormUseCaseBound() { _rewardSelectFormUseCase ??= new RewardSelectFormUseCase(); GameEntry.UIRouter.BindUIUseCase(UIFormType.RewardSelectForm, _rewardSelectFormUseCase); } private void ChangeState(CombatStateBase nextState) { if (ReferenceEquals(_currentState, nextState)) { return; } _currentState?.OnExit(); _currentState?.OnDestroy(); _currentState = nextState; _currentState?.OnInit(); _currentState?.OnEnter(); } private bool TryBeginNextPhase() { if (!_phaseLoopRuntime.TryEnterNextPhase(out DRLevelPhase nextPhase)) { ChangeState(new CombatSettlementState(this, "Combat ended after loop completion.", true)); return false; } _spawnEntriesByPhaseId.TryGetValue(nextPhase.Id, out IReadOnlyList spawnEntries); ChangeState(new CombatRunningPhaseState(this, nextPhase, spawnEntries)); return true; } private void EnterWaitingForPhaseEnd() { ChangeState(new CombatWaitingForPhaseEndState(this)); } 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); TryBeginNextPhase(); } private bool ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory) { if (GetCurrentBaseHp() <= 0) { reason = "Combat ended because base HP reached zero."; isVictory = false; return true; } if (_phaseLoopRuntime.IsEndCombatRequested) { reason = "Combat ended by player."; isVictory = true; return true; } reason = null; isVictory = true; return false; } private void OnFullBaseHpRewardSelected(RewardSelectItemRawData selectedReward) { if (_currentState is not CombatRewardSelectionState || _settlementContext == null) { return; } _settlementFlowService.ApplySelectedReward(_settlementContext, selectedReward); ChangeState(new CombatFinishFormState(this)); } private void OnFullBaseHpRewardGiveUp() { if (_currentState is not CombatRewardSelectionState || _settlementContext == null) { return; } ChangeState(new CombatFinishFormState(this)); } private LevelThemeType ResolveCurrentThemeType() { if (_currentLevel != null) { return _currentLevel.LevelThemeType; } if (GameEntry.CombatNode != null) { return GameEntry.CombatNode.CurrentThemeType; } return LevelThemeType.None; } private int ApplyBaseDamage(int damage) { return _combatInRunResourceManager.ApplyBaseDamage(damage); } private int GetCurrentBaseHp() { return Mathf.Max(0, _combatInRunResourceManager.CurrentBaseHp); } private void CloseCombatFinishForm() { GameEntry.UIRouter.CloseUI(UIFormType.CombatFinishForm); } private void CloseRewardSelectForm() { GameEntry.UIRouter.CloseUI(UIFormType.RewardSelectForm); } private void CompleteCombatAndNotify(bool succeeded) { _isCompleted = true; _currentState = null; _combatInRunResourceManager.MarkCombatEnded(); GameEntry.CombatNode?.OnCombatEndedByScheduler(succeeded); } private bool HandleStartFailure(string errorMessage) { Log.Warning("{0}", errorMessage); _enemyManager.EndPhase(); CleanupAllCombatEntities(); CloseCombatFinishForm(); CloseRewardSelectForm(); ResetRuntime(); return false; } private void EnterFailureFallback(string errorMessage) { if (_currentState is CombatFailedState || _isCompleted) { return; } ChangeState(new CombatFailedState(this, errorMessage, true)); } private static int ResolveEnemyHpRateMultiplier(int displayPhaseIndex, int phaseCount) { if (displayPhaseIndex <= 0 || phaseCount <= 0) { return 1; } int completedLoopCount = Mathf.Max(0, (displayPhaseIndex - 1) / phaseCount); if (completedLoopCount >= 30) { return int.MaxValue; } return 1 << completedLoopCount; } #region Event Handlers private void OnShowEntitySuccess(ShowEntitySuccessEventArgs args) { var status = _loadSession.HandleShowEntitySuccess(args, out string errorMessage); if (status == CombatLoadSession.EventHandleStatus.Failed) { EnterFailureFallback(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) { EnterFailureFallback(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) { EnterFailureFallback(errorMessage); } } private void OnOpenUIFormFailure(OpenUIFormFailureEventArgs args) { var status = _loadSession.HandleOpenUIFormFailure(args, out string errorMessage); if (status == CombatLoadSession.EventHandleStatus.Failed) { EnterFailureFallback(errorMessage); } } private void OnCloseUIFormComplete(CloseUIFormCompleteEventArgs args) { _loadSession.HandleCloseUIFormComplete(args); } #endregion private void GameOverByFailure() { CompleteCombatAndNotify(false); } } }