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