refactor 7:

- 拆分 CombatScheduler,将公用方法与字段拆分为:
    - FlowCoordinator
    - RuntimeContext
- 地图侧现在收口为“MapData 初始快照 + CombatCoinChangedEventArgs 同步 + 独立命令桥接”
This commit is contained in:
2026-03-07 19:42:17 +08:00
parent eb818e6295
commit b38088c3ea
25 changed files with 1031 additions and 786 deletions
@@ -45,7 +45,7 @@ namespace GeometryTD.CustomComponent
_currentMap = null;
}
public bool StartLoading(DRLevel level, MapData mapData, CombatScheduler scheduler, out string errorMessage)
public bool StartLoading(DRLevel level, MapEntityLoadContext mapLoadContext, CombatScheduler scheduler, out string errorMessage)
{
errorMessage = null;
if (_entity == null)
@@ -54,7 +54,7 @@ namespace GeometryTD.CustomComponent
return false;
}
if (!TryShowMap(level, mapData, out errorMessage))
if (!TryShowMap(level, mapLoadContext, out errorMessage))
{
return false;
}
@@ -224,7 +224,7 @@ namespace GeometryTD.CustomComponent
}
}
private bool TryShowMap(DRLevel level, MapData mapData, out string errorMessage)
private bool TryShowMap(DRLevel level, MapEntityLoadContext mapLoadContext, out string errorMessage)
{
errorMessage = null;
if (level == null)
@@ -242,10 +242,14 @@ namespace GeometryTD.CustomComponent
}
_loadingMapEntityId = _entity.GenerateSerialId();
MapData resolvedMapData = mapData != null
? mapData.CloneForEntity(_loadingMapEntityId, Vector3.zero)
MapData resolvedMapData = mapLoadContext?.InitialMapData != null
? mapLoadContext.InitialMapData.CloneForEntity(_loadingMapEntityId, Vector3.zero)
: new MapData(_loadingMapEntityId, level.Id, Vector3.zero);
_entity.ShowMap(resolvedMapData, mapAssetName);
MapEntityLoadContext resolvedLoadContext = new MapEntityLoadContext(
resolvedMapData,
mapLoadContext?.TryConsumeCoin,
mapLoadContext?.AddCoin);
_entity.ShowMap(resolvedLoadContext, mapAssetName);
return true;
}
@@ -11,65 +11,54 @@ namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
{
private readonly List<DRLevelPhase> _phaseBuffer = new();
private readonly Dictionary<int, IReadOnlyList<DRLevelSpawnEntry>> _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 readonly CombatSchedulerRuntimeContext _context = new();
private readonly CombatSchedulerFlowCoordinator _flowCoordinator;
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 CombatScheduler()
{
_flowCoordinator = new CombatSchedulerFlowCoordinator(this, _context);
}
public bool IsRunning =>
_currentState is CombatLoadingState or CombatRunningPhaseState or CombatWaitingForPhaseEndState;
_context.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 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)
{
_entity = GameEntry.Entity;
_eventBridge.Bind(
_context.Entity = GameEntry.Entity;
_context.EventBridge.Bind(
OnShowEntitySuccess,
OnShowEntityFailure,
OnHideEntityComplete,
OnOpenUIFormSuccess,
OnOpenUIFormFailure,
OnCloseUIFormComplete);
_enemyManager.OnInit(this);
_loadSession.OnInit(_entity);
EnsureCombatFinishFormUseCaseBound();
EnsureRewardSelectFormUseCaseBound();
_context.EnemyManager.OnInit(this);
_context.LoadSession.OnInit(_context.Entity);
_flowCoordinator.EnsureCombatFinishFormUseCaseBound();
_flowCoordinator.EnsureRewardSelectFormUseCaseBound();
_initialized = true;
}
ResetRuntime();
_flowCoordinator.ResetRuntime();
}
public bool Start(
@@ -77,32 +66,33 @@ namespace GeometryTD.CustomComponent
IReadOnlyList<DRLevelPhase> phases,
IReadOnlyDictionary<int, IReadOnlyList<DRLevelSpawnEntry>> spawnEntriesByPhaseId)
{
if (!_initialized || _entity == null)
if (!_initialized || _context.Entity == null)
{
return HandleStartFailure("CombatScheduler start failed. Runtime is not initialized.");
return _flowCoordinator.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.");
return _flowCoordinator.HandleStartFailure("CombatScheduler start failed. Invalid level or phase data.");
}
CleanupAllCombatEntities();
CloseCombatFinishForm();
CloseRewardSelectForm();
_enemyManager.EndPhase();
_enemyManager.ResetCombatStats();
ResetRuntime();
_isFinishAsVictory = true;
_flowCoordinator.CleanupAllCombatEntities();
_flowCoordinator.CloseCombatFinishForm();
_flowCoordinator.CloseRewardSelectForm();
_flowCoordinator.CloseDialogForm();
_context.EnemyManager.EndPhase();
_context.EnemyManager.ResetCombatStats();
_flowCoordinator.ResetRuntime();
_context.IsFinishAsVictory = true;
_currentLevel = level;
_combatInRunResourceManager.InitializeForCombat(level);
_context.CurrentLevel = level;
_context.CombatInRunResourceManager.InitializeForCombat(level);
for (int i = 0; i < phases.Count; i++)
{
DRLevelPhase phase = phases[i];
if (phase != null)
{
_phaseBuffer.Add(phase);
_context.PhaseBuffer.Add(phase);
}
}
@@ -110,27 +100,27 @@ namespace GeometryTD.CustomComponent
{
foreach (var pair in spawnEntriesByPhaseId)
{
_spawnEntriesByPhaseId[pair.Key] = pair.Value;
_context.SpawnEntriesByPhaseId[pair.Key] = pair.Value;
}
}
_phaseLoopRuntime.SetPhases(_phaseBuffer);
if (_phaseLoopRuntime.PhaseCount <= 0)
_context.PhaseLoopRuntime.SetPhases(_context.PhaseBuffer);
if (_context.PhaseLoopRuntime.PhaseCount <= 0)
{
return HandleStartFailure($"CombatScheduler start failed. Level '{level.Id}' has no phase data.");
return _flowCoordinator.HandleStartFailure($"CombatScheduler start failed. Level '{level.Id}' has no phase data.");
}
ChangeState(new CombatLoadingState(this));
ChangeState(new CombatLoadingState(_context, _flowCoordinator));
Log.Info(
"CombatScheduler started. Level={0}, PhaseCount={1}.",
_currentLevel.Id,
_phaseLoopRuntime.PhaseCount);
_context.CurrentLevel.Id,
_context.PhaseLoopRuntime.PhaseCount);
return true;
}
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_currentState?.OnUpdate(elapseSeconds, realElapseSeconds);
_context.CurrentState?.OnUpdate(elapseSeconds, realElapseSeconds);
}
public void OnDestroy()
@@ -140,31 +130,32 @@ namespace GeometryTD.CustomComponent
return;
}
_currentState?.OnExit();
_currentState?.OnDestroy();
_currentState = null;
CleanupAllCombatEntities();
CloseCombatFinishForm();
CloseRewardSelectForm();
_enemyManager.OnDestroy();
ResetRuntime();
_eventBridge.Unbind();
_combatFinishFormUseCase = null;
_rewardSelectFormUseCase = null;
_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;
_entity = null;
_context.Entity = null;
_initialized = false;
}
public bool TryEndCombatByPlayer()
{
if (_currentState is not CombatRunningPhaseState &&
_currentState is not CombatWaitingForPhaseEndState)
if (_context.CurrentState is not CombatRunningPhaseState &&
_context.CurrentState is not CombatWaitingForPhaseEndState)
{
return false;
}
return _phaseLoopRuntime.TryRequestEndCombat();
return _context.PhaseLoopRuntime.TryRequestEndCombat();
}
public void OnEnemyReachedBase(DREnemy enemy)
@@ -180,7 +171,7 @@ namespace GeometryTD.CustomComponent
return;
}
ApplyBaseDamage(resolvedBaseDamage);
_flowCoordinator.ApplyBaseDamage(resolvedBaseDamage);
}
public void OnEnemyDefeated(DREnemy enemy)
@@ -192,24 +183,24 @@ namespace GeometryTD.CustomComponent
EnemyDropResolveContext context = new(
enemy,
_phaseLoopRuntime.DisplayPhaseIndex,
ResolveCurrentThemeType());
EnemyDropResolveResult result = _enemyDropResolver.Resolve(context);
_combatInRunResourceManager.AddEnemyDefeatedReward(result.Coin, result.Gold);
_context.PhaseLoopRuntime.DisplayPhaseIndex,
_flowCoordinator.ResolveCurrentThemeType());
EnemyDropResolveResult result = _context.EnemyDropResolver.Resolve(context);
_context.CombatInRunResourceManager.AddEnemyDefeatedReward(result.Coin, result.Gold);
if (!result.ShouldRollOutGameItem)
{
return;
}
_combatInRunResourceManager.TryRollOutGameItemDrop(
_context.CombatInRunResourceManager.TryRollOutGameItemDrop(
context.DisplayPhaseIndex,
context.ThemeType);
}
public bool OnCombatFinishReturnRequested()
{
if (_currentState is not CombatWaitingForReturnState waitingForReturnState)
if (_context.CurrentState is not CombatWaitingForReturnState waitingForReturnState)
{
return false;
}
@@ -220,294 +211,63 @@ namespace GeometryTD.CustomComponent
public bool TryConsumeCoin(int coin)
{
return _combatInRunResourceManager.TryConsumeCoin(coin);
return _context.CombatInRunResourceManager.TryConsumeCoin(coin);
}
public void AddCoin(int coin)
{
_combatInRunResourceManager.AddCoin(coin);
_context.CombatInRunResourceManager.AddCoin(coin);
}
public bool TryGetBuildTowerStats(int buildIndex, out TowerStatsData stats)
{
return _combatInRunResourceManager.TryGetBuildTowerStats(buildIndex, out stats);
return _context.CombatInRunResourceManager.TryGetBuildTowerStats(buildIndex, out stats);
}
public bool TryDebugFail(string errorMessage)
{
if (_isCompleted || _currentState == null || _currentState is CombatFailedState)
if (_context.IsCompleted || _context.CurrentState == null || _context.CurrentState is CombatFailedState)
{
return false;
}
EnterFailureFallback(string.IsNullOrWhiteSpace(errorMessage)
_flowCoordinator.EnterFailureFallback(string.IsNullOrWhiteSpace(errorMessage)
? "Manual debug fail."
: errorMessage);
return _currentState is CombatFailedState;
return _context.CurrentState is CombatFailedState;
}
private void ResetRuntime()
internal void ChangeState(CombatStateBase nextState)
{
_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 OpenCombatFailureDialog(string errorMessage)
{
CloseDialogForm();
GameEntry.UIRouter.OpenUI(UIFormType.DialogForm, new DialogFormRawData
{
Mode = 1,
Title = "Combat Error",
Message = string.IsNullOrWhiteSpace(errorMessage) ? "Combat failed unexpectedly." : errorMessage,
PauseGame = false,
ConfirmText = "Return Menu",
OnClickConfirm = OnCombatFailureDialogConfirmed
});
}
private void ChangeState(CombatStateBase nextState)
{
if (ReferenceEquals(_currentState, nextState))
if (ReferenceEquals(_context.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<DRLevelSpawnEntry> 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 CloseDialogForm()
{
GameEntry.UIRouter.CloseUI(UIFormType.DialogForm);
}
private void CompleteCombat(bool succeeded)
{
_isCompleted = true;
_currentState = null;
_combatInRunResourceManager.MarkCombatEnded();
GameEntry.CombatNode?.OnCombatEndedByScheduler(succeeded);
}
private void CompleteNormalCombatAndNotify(bool succeeded)
{
CompleteCombat(succeeded);
GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create());
}
private void CompleteFailureCombatAndNotify()
{
CleanupAllCombatEntities();
CloseCombatFinishForm();
CloseRewardSelectForm();
CloseDialogForm();
CompleteCombat(false);
GameEntry.Event.Fire(this, CombatFailureReturnEventArgs.Create());
}
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));
}
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;
}
private void OnCombatFailureDialogConfirmed(object userData)
{
_ = userData;
if (_currentState is not CombatFailedState || _isCompleted)
{
return;
}
CompleteFailureCombatAndNotify();
_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 = _loadSession.HandleShowEntitySuccess(args, out string errorMessage);
var status = _context.LoadSession.HandleShowEntitySuccess(args, out string errorMessage);
if (status == CombatLoadSession.EventHandleStatus.Failed)
{
EnterFailureFallback(errorMessage);
_flowCoordinator.EnterFailureFallback(errorMessage);
return;
}
if (status == CombatLoadSession.EventHandleStatus.Succeeded)
{
MapEntity map = _loadSession.CurrentMap;
MapEntity map = _context.LoadSession.CurrentMap;
Log.Info(
"Map ready. LevelId={0}, PathCells={1}, FoundationCells={2}, Spawners={3}, House={4}.",
_currentLevel != null ? _currentLevel.Id : 0,
_context.CurrentLevel != null ? _context.CurrentLevel.Id : 0,
map.PathCells.Count,
map.FoundationCells.Count,
map.Spawners.Length,
@@ -517,39 +277,39 @@ namespace GeometryTD.CustomComponent
private void OnShowEntityFailure(ShowEntityFailureEventArgs args)
{
var status = _loadSession.HandleShowEntityFailure(args, out string errorMessage);
var status = _context.LoadSession.HandleShowEntityFailure(args, out string errorMessage);
if (status == CombatLoadSession.EventHandleStatus.Failed)
{
EnterFailureFallback(errorMessage);
_flowCoordinator.EnterFailureFallback(errorMessage);
}
}
private void OnHideEntityComplete(HideEntityCompleteEventArgs args)
{
_loadSession.HandleHideEntityComplete(args);
_context.LoadSession.HandleHideEntityComplete(args);
}
private void OnOpenUIFormSuccess(OpenUIFormSuccessEventArgs args)
{
var status = _loadSession.HandleOpenUIFormSuccess(args, out string errorMessage);
var status = _context.LoadSession.HandleOpenUIFormSuccess(args, out string errorMessage);
if (status == CombatLoadSession.EventHandleStatus.Failed)
{
EnterFailureFallback(errorMessage);
_flowCoordinator.EnterFailureFallback(errorMessage);
}
}
private void OnOpenUIFormFailure(OpenUIFormFailureEventArgs args)
{
var status = _loadSession.HandleOpenUIFormFailure(args, out string errorMessage);
var status = _context.LoadSession.HandleOpenUIFormFailure(args, out string errorMessage);
if (status == CombatLoadSession.EventHandleStatus.Failed)
{
EnterFailureFallback(errorMessage);
_flowCoordinator.EnterFailureFallback(errorMessage);
}
}
private void OnCloseUIFormComplete(CloseUIFormCompleteEventArgs args)
{
_loadSession.HandleCloseUIFormComplete(args);
_context.LoadSession.HandleCloseUIFormComplete(args);
}
#endregion
@@ -0,0 +1,256 @@
using System.Collections.Generic;
using GeometryTD.CustomEvent;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using GeometryTD.UI;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
internal sealed class CombatSchedulerFlowCoordinator
{
private readonly CombatScheduler _scheduler;
private readonly CombatSchedulerRuntimeContext _context;
public CombatScheduler Scheduler => _scheduler;
public CombatSchedulerFlowCoordinator(CombatScheduler scheduler, CombatSchedulerRuntimeContext context)
{
_scheduler = scheduler;
_context = context;
}
public 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;
}
public void ResetRuntime()
{
_context.CurrentState = null;
_context.PhaseBuffer.Clear();
_context.SpawnEntriesByPhaseId.Clear();
_context.PhaseLoopRuntime.Reset();
_context.LoadSession.Reset();
_context.CombatInRunResourceManager.Reset();
_context.SettlementContext = null;
_context.CurrentLevel = null;
_context.IsFinishAsVictory = true;
_context.IsCompleted = false;
_context.NodeEnterFired = false;
}
public void CleanupAllCombatEntities()
{
_context.LoadSession.Cleanup();
_context.EnemyManager.CleanupTrackedEnemies();
}
public void EnsureCombatFinishFormUseCaseBound()
{
_context.CombatFinishFormUseCase ??= new CombatFinishFormUseCase(_scheduler);
GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatFinishForm, _context.CombatFinishFormUseCase);
}
public void EnsureRewardSelectFormUseCaseBound()
{
_context.RewardSelectFormUseCase ??= new RewardSelectFormUseCase();
GameEntry.UIRouter.BindUIUseCase(UIFormType.RewardSelectForm, _context.RewardSelectFormUseCase);
}
public void OpenCombatFailureDialog(string errorMessage)
{
CloseDialogForm();
GameEntry.UIRouter.OpenUI(UIFormType.DialogForm, new DialogFormRawData
{
Mode = 1,
Title = "Combat Error",
Message = string.IsNullOrWhiteSpace(errorMessage) ? "Combat failed unexpectedly." : errorMessage,
PauseGame = false,
ConfirmText = "Return Menu",
OnClickConfirm = OnCombatFailureDialogConfirmed
});
}
public bool TryBeginNextPhase()
{
if (!_context.PhaseLoopRuntime.TryEnterNextPhase(out DRLevelPhase nextPhase))
{
_scheduler.ChangeState(new CombatSettlementState(_context, this, "Combat ended after loop completion.", true));
return false;
}
_context.SpawnEntriesByPhaseId.TryGetValue(nextPhase.Id, out IReadOnlyList<DRLevelSpawnEntry> spawnEntries);
_scheduler.ChangeState(new CombatRunningPhaseState(_context, this, nextPhase, spawnEntries));
return true;
}
public void EnterWaitingForPhaseEnd()
{
_scheduler.ChangeState(new CombatWaitingForPhaseEndState(_context, this));
}
public void CompleteCurrentPhase()
{
_context.EnemyManager.EndPhase();
Log.Info(
"CombatScheduler phase completed. Level={0}, Phase={1}, Elapsed={2:F2}s.",
_context.CurrentLevel != null ? _context.CurrentLevel.Id : 0,
_context.PhaseLoopRuntime.CurrentPhase != null ? _context.PhaseLoopRuntime.CurrentPhase.Id : 0,
_context.PhaseLoopRuntime.CurrentPhaseElapsed);
TryBeginNextPhase();
}
public bool ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory)
{
if (GetCurrentBaseHp() <= 0)
{
reason = "Combat ended because base HP reached zero.";
isVictory = false;
return true;
}
if (_context.PhaseLoopRuntime.IsEndCombatRequested)
{
reason = "Combat ended by player.";
isVictory = true;
return true;
}
reason = null;
isVictory = true;
return false;
}
public void OnFullBaseHpRewardSelected(RewardSelectItemRawData selectedReward)
{
if (_context.CurrentState is not CombatRewardSelectionState || _context.SettlementContext == null)
{
return;
}
_context.SettlementFlowService.ApplySelectedReward(_context.SettlementContext, selectedReward);
_scheduler.ChangeState(new CombatFinishFormState(_context, this));
}
public void OnFullBaseHpRewardGiveUp()
{
if (_context.CurrentState is not CombatRewardSelectionState || _context.SettlementContext == null)
{
return;
}
_scheduler.ChangeState(new CombatFinishFormState(_context, this));
}
public LevelThemeType ResolveCurrentThemeType()
{
if (_context.CurrentLevel != null)
{
return _context.CurrentLevel.LevelThemeType;
}
if (GameEntry.CombatNode != null)
{
return GameEntry.CombatNode.CurrentThemeType;
}
return LevelThemeType.None;
}
public int ApplyBaseDamage(int damage)
{
return _context.CombatInRunResourceManager.ApplyBaseDamage(damage);
}
public int GetCurrentBaseHp()
{
return Mathf.Max(0, _context.CombatInRunResourceManager.CurrentBaseHp);
}
public void CloseCombatFinishForm()
{
GameEntry.UIRouter.CloseUI(UIFormType.CombatFinishForm);
}
public void CloseRewardSelectForm()
{
GameEntry.UIRouter.CloseUI(UIFormType.RewardSelectForm);
}
public void CloseDialogForm()
{
GameEntry.UIRouter.CloseUI(UIFormType.DialogForm);
}
public void CompleteNormalCombatAndNotify(bool succeeded)
{
CompleteCombat(succeeded);
GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create());
}
public void CompleteFailureCombatAndNotify()
{
CleanupAllCombatEntities();
CloseCombatFinishForm();
CloseRewardSelectForm();
CloseDialogForm();
CompleteCombat(false);
GameEntry.Event.Fire(this, CombatFailureReturnEventArgs.Create());
}
public bool HandleStartFailure(string errorMessage)
{
Log.Warning("{0}", errorMessage);
_context.EnemyManager.EndPhase();
CleanupAllCombatEntities();
CloseCombatFinishForm();
CloseRewardSelectForm();
CloseDialogForm();
ResetRuntime();
return false;
}
public void EnterFailureFallback(string errorMessage)
{
if (_context.CurrentState is CombatFailedState || _context.IsCompleted)
{
return;
}
_scheduler.ChangeState(new CombatFailedState(_context, this, errorMessage));
}
public void OnCombatFailureDialogConfirmed(object userData)
{
_ = userData;
if (_context.CurrentState is not CombatFailedState || _context.IsCompleted)
{
return;
}
CompleteFailureCombatAndNotify();
}
private void CompleteCombat(bool succeeded)
{
_context.IsCompleted = true;
_context.CurrentState = null;
_context.CombatInRunResourceManager.MarkCombatEnded();
GameEntry.CombatNode?.OnCombatEndedByScheduler(succeeded);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 672e14aaea7d4135874ef7f990eb78f2
timeCreated: 1772865001
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using GeometryTD.DataTable;
using GeometryTD.Entity;
using GeometryTD.UI;
using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
internal sealed class CombatSchedulerRuntimeContext
{
public List<DRLevelPhase> PhaseBuffer { get; } = new();
public Dictionary<int, IReadOnlyList<DRLevelSpawnEntry>> SpawnEntriesByPhaseId { get; } = new();
public EnemyManager EnemyManager { get; } = new();
public PhaseLoopRuntime PhaseLoopRuntime { get; } = new();
public CombatLoadSession LoadSession { get; } = new();
public CombatEventBridge EventBridge { get; } = new();
public CombatInRunResourceManager CombatInRunResourceManager { get; } = new();
public EnemyDropResolver EnemyDropResolver { get; } = new();
public CombatSettlementFlowService SettlementFlowService { get; } = new();
public EntityComponent Entity { get; set; }
public DRLevel CurrentLevel { get; set; }
public CombatFinishFormUseCase CombatFinishFormUseCase { get; set; }
public RewardSelectFormUseCase RewardSelectFormUseCase { get; set; }
public CombatStateBase CurrentState { get; set; }
public bool IsFinishAsVictory { get; set; } = true;
public bool IsCompleted { get; set; }
public bool NodeEnterFired { get; set; }
public CombatSettlementContext SettlementContext { get; set; }
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ebdf96ddef6945efa0e1446597a9c776
timeCreated: 1772865000
@@ -2,33 +2,33 @@ using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatFailedState : CombatStateBase
{
private sealed class CombatFailedState : CombatStateBase
private readonly string _errorMessage;
public CombatFailedState(
CombatSchedulerRuntimeContext context,
CombatSchedulerFlowCoordinator flow,
string errorMessage) : base(context, flow)
{
private readonly string _errorMessage;
_errorMessage = errorMessage;
}
public CombatFailedState(CombatScheduler scheduler, string errorMessage) : base(scheduler)
{
_errorMessage = errorMessage;
}
public override void OnEnter()
{
Log.Error(
"CombatScheduler failed. LevelId={0}, {1}",
Context.CurrentLevel != null ? Context.CurrentLevel.Id : 0,
_errorMessage);
Context.EnemyManager.EndPhase();
Flow.CloseCombatFinishForm();
Flow.CloseRewardSelectForm();
Flow.OpenCombatFailureDialog(_errorMessage);
}
public override void OnEnter()
{
Log.Error(
"CombatScheduler failed. LevelId={0}, {1}",
Scheduler._currentLevel != null ? Scheduler._currentLevel.Id : 0,
_errorMessage);
Scheduler._enemyManager.EndPhase();
Scheduler.CloseCombatFinishForm();
Scheduler.CloseRewardSelectForm();
Scheduler.OpenCombatFailureDialog(_errorMessage);
}
public override void OnExit()
{
Scheduler.CloseDialogForm();
}
public override void OnExit()
{
Flow.CloseDialogForm();
}
}
}
@@ -1,28 +1,26 @@
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatFinishFormState : CombatStateBase
{
private sealed class CombatFinishFormState : CombatStateBase
public CombatFinishFormState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
: base(context, flow)
{
public CombatFinishFormState(CombatScheduler scheduler) : base(scheduler)
}
public override void OnEnter()
{
if (Context.SettlementContext == null)
{
Flow.EnterFailureFallback("Combat finish form failed. Settlement context is missing.");
return;
}
public override void OnEnter()
{
if (Scheduler._settlementContext == null)
{
Scheduler.EnterFailureFallback("Combat finish form failed. Settlement context is missing.");
return;
}
Scheduler._settlementFlowService.CommitSettlementInventory(Scheduler._settlementContext);
Scheduler.EnsureCombatFinishFormUseCaseBound();
Scheduler._settlementFlowService.OpenCombatFinishForm(
Scheduler._settlementContext,
Scheduler._combatFinishFormUseCase);
Scheduler.ChangeState(new CombatWaitingForReturnState(Scheduler));
}
Context.SettlementFlowService.CommitSettlementInventory(Context.SettlementContext);
Flow.EnsureCombatFinishFormUseCaseBound();
Context.SettlementFlowService.OpenCombatFinishForm(
Context.SettlementContext,
Context.CombatFinishFormUseCase);
Flow.Scheduler.ChangeState(new CombatWaitingForReturnState(Context, Flow));
}
}
}
@@ -6,70 +6,67 @@ using UnityEngine;
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatLoadingState : CombatStateBase
{
private sealed class CombatLoadingState : CombatStateBase
public CombatLoadingState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
: base(context, flow)
{
public CombatLoadingState(CombatScheduler scheduler) : base(scheduler)
}
public override void OnEnter()
{
if (Context.CurrentLevel == null)
{
Flow.EnterFailureFallback("Combat loading failed. Current level is null.");
return;
}
public override void OnEnter()
MapEntityLoadContext mapLoadContext = BuildMapLoadContext();
if (!Context.LoadSession.StartLoading(Context.CurrentLevel, mapLoadContext, Flow.Scheduler, out string errorMessage))
{
if (Scheduler._currentLevel == null)
{
Scheduler.EnterFailureFallback("Combat loading failed. Current level is null.");
return;
}
Flow.EnterFailureFallback($"Combat loading failed. {errorMessage}");
}
}
MapData mapData = BuildMapData();
if (!Scheduler._loadSession.StartLoading(Scheduler._currentLevel, mapData, Scheduler, out string errorMessage))
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_ = elapseSeconds;
_ = realElapseSeconds;
if (!Context.LoadSession.IsReady)
{
return;
}
Flow.TryBeginNextPhase();
}
private MapEntityLoadContext BuildMapLoadContext()
{
List<TowerStatsData> buildTowerStatsSnapshot = new();
for (int i = 0; i < Context.CombatInRunResourceManager.CurrentBuildTowerCount; i++)
{
if (Context.CombatInRunResourceManager.TryGetBuildTowerStats(i, out TowerStatsData stats) &&
stats != null)
{
Scheduler.EnterFailureFallback($"Combat loading failed. {errorMessage}");
buildTowerStatsSnapshot.Add(stats);
}
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_ = elapseSeconds;
_ = realElapseSeconds;
if (!Scheduler._loadSession.IsReady)
{
return;
}
Scheduler.TryBeginNextPhase();
}
private MapData BuildMapData()
{
List<TowerStatsData> buildTowerStatsSnapshot = new();
for (int i = 0; i < Scheduler._combatInRunResourceManager.CurrentBuildTowerCount; i++)
{
if (Scheduler._combatInRunResourceManager.TryGetBuildTowerStats(i, out TowerStatsData stats) &&
stats != null)
{
buildTowerStatsSnapshot.Add(stats);
}
}
return new MapData(
entityId: 0,
typeId: 0,
levelId: Scheduler._currentLevel.Id,
position: Vector3.zero,
initialCoin: Scheduler._combatInRunResourceManager.CurrentCoin,
buildTowerStatsSnapshot: buildTowerStatsSnapshot,
inventorySnapshot: GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetInventorySnapshot()
: null,
participantTowerSnapshot: GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetParticipantTowerSnapshot()
: null,
tryConsumeCoin: Scheduler.TryConsumeCoin,
addCoin: Scheduler.AddCoin);
}
MapData mapData = new MapData(
entityId: 0,
typeId: 0,
levelId: Context.CurrentLevel.Id,
position: Vector3.zero,
initialCoin: Context.CombatInRunResourceManager.CurrentCoin,
buildTowerStatsSnapshot: buildTowerStatsSnapshot,
inventorySnapshot: GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetInventorySnapshot()
: null,
participantTowerSnapshot: GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetParticipantTowerSnapshot()
: null);
return new MapEntityLoadContext(mapData, Flow.Scheduler.TryConsumeCoin, Flow.Scheduler.AddCoin);
}
}
}
@@ -1,39 +1,37 @@
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatRewardSelectionState : CombatStateBase
{
private sealed class CombatRewardSelectionState : CombatStateBase
public CombatRewardSelectionState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
: base(context, flow)
{
public CombatRewardSelectionState(CombatScheduler scheduler) : base(scheduler)
}
public override void OnEnter()
{
if (Context.SettlementContext == null)
{
Flow.EnterFailureFallback("Combat reward selection failed. Settlement context is missing.");
return;
}
public override void OnEnter()
Flow.EnsureRewardSelectFormUseCaseBound();
if (!Context.SettlementFlowService.TryPrepareRewardSelection(
Context.SettlementContext,
Context.CombatInRunResourceManager,
Context.PhaseLoopRuntime.DisplayPhaseIndex,
Flow.ResolveCurrentThemeType(),
Context.RewardSelectFormUseCase,
Flow.OnFullBaseHpRewardSelected,
Flow.OnFullBaseHpRewardGiveUp))
{
if (Scheduler._settlementContext == null)
{
Scheduler.EnterFailureFallback("Combat reward selection failed. Settlement context is missing.");
return;
}
Scheduler.EnsureRewardSelectFormUseCaseBound();
if (!Scheduler._settlementFlowService.TryPrepareRewardSelection(
Scheduler._settlementContext,
Scheduler._combatInRunResourceManager,
Scheduler._phaseLoopRuntime.DisplayPhaseIndex,
Scheduler.ResolveCurrentThemeType(),
Scheduler._rewardSelectFormUseCase,
Scheduler.OnFullBaseHpRewardSelected,
Scheduler.OnFullBaseHpRewardGiveUp))
{
Scheduler.ChangeState(new CombatFinishFormState(Scheduler));
}
Flow.Scheduler.ChangeState(new CombatFinishFormState(Context, Flow));
}
}
public override void OnExit()
{
Scheduler.CloseRewardSelectForm();
}
public override void OnExit()
{
Flow.CloseRewardSelectForm();
}
}
}
@@ -5,72 +5,70 @@ using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatRunningPhaseState : CombatStateBase
{
private sealed class CombatRunningPhaseState : CombatStateBase
private readonly DRLevelPhase _phase;
private readonly IReadOnlyList<DRLevelSpawnEntry> _spawnEntries;
public CombatRunningPhaseState(
CombatSchedulerRuntimeContext context,
CombatSchedulerFlowCoordinator flow,
DRLevelPhase phase,
IReadOnlyList<DRLevelSpawnEntry> spawnEntries) : base(context, flow)
{
private readonly DRLevelPhase _phase;
private readonly IReadOnlyList<DRLevelSpawnEntry> _spawnEntries;
_phase = phase;
_spawnEntries = spawnEntries;
}
public CombatRunningPhaseState(
CombatScheduler scheduler,
DRLevelPhase phase,
IReadOnlyList<DRLevelSpawnEntry> spawnEntries) : base(scheduler)
public override void OnEnter()
{
Context.EnemyManager.BeginPhase(_phase, _spawnEntries);
GameEntry.Event.Fire(
Flow,
CombatProcessEventArgs.Create(
Context.PhaseLoopRuntime.DisplayPhaseIndex,
Context.PhaseLoopRuntime.PhaseCount));
GameEntry.Event.Fire(
Flow,
CombatEnemyHpRateChangedEventArgs.Create(
Flow.ResolveEnemyHpRateMultiplier(
Context.PhaseLoopRuntime.DisplayPhaseIndex,
Context.PhaseLoopRuntime.PhaseCount)));
if (!Context.NodeEnterFired)
{
_phase = phase;
_spawnEntries = spawnEntries;
Context.NodeEnterFired = true;
GameEntry.Event.Fire(Flow, NodeEnterEventArgs.Create());
}
public override void OnEnter()
Log.Info(
"CombatScheduler phase started. Level={0}, Phase={1}, EndType={2}, Entries={3}.",
Context.CurrentLevel != null ? Context.CurrentLevel.Id : 0,
Context.PhaseLoopRuntime.DisplayPhaseIndex,
_phase.EndType,
_spawnEntries != null ? _spawnEntries.Count : 0);
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
if (Context.PhaseLoopRuntime.CurrentPhase == null)
{
Scheduler._enemyManager.BeginPhase(_phase, _spawnEntries);
GameEntry.Event.Fire(
Scheduler,
CombatProcessEventArgs.Create(
Scheduler._phaseLoopRuntime.DisplayPhaseIndex,
Scheduler._phaseLoopRuntime.PhaseCount));
GameEntry.Event.Fire(
Scheduler,
CombatEnemyHpRateChangedEventArgs.Create(
ResolveEnemyHpRateMultiplier(
Scheduler._phaseLoopRuntime.DisplayPhaseIndex,
Scheduler._phaseLoopRuntime.PhaseCount)));
if (!Scheduler._nodeEnterFired)
{
Scheduler._nodeEnterFired = true;
GameEntry.Event.Fire(Scheduler, NodeEnterEventArgs.Create());
}
Log.Info(
"CombatScheduler phase started. Level={0}, Phase={1}, EndType={2}, Entries={3}.",
Scheduler._currentLevel != null ? Scheduler._currentLevel.Id : 0,
Scheduler._phaseLoopRuntime.DisplayPhaseIndex,
_phase.EndType,
_spawnEntries != null ? _spawnEntries.Count : 0);
Flow.EnterFailureFallback("CombatScheduler update failed. Current phase is null.");
return;
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
Context.PhaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
Context.EnemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
if (Flow.ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory))
{
if (Scheduler._phaseLoopRuntime.CurrentPhase == null)
{
Scheduler.EnterFailureFallback("CombatScheduler update failed. Current phase is null.");
return;
}
Flow.Scheduler.ChangeState(new CombatSettlementState(Context, Flow, reason, isVictory));
return;
}
Scheduler._phaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
Scheduler._enemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
if (Scheduler.ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory))
{
Scheduler.ChangeState(new CombatSettlementState(Scheduler, reason, isVictory));
return;
}
if (Scheduler._enemyManager.IsPhaseSpawnCompleted)
{
Scheduler.EnterWaitingForPhaseEnd();
}
if (Context.EnemyManager.IsPhaseSpawnCompleted)
{
Flow.EnterWaitingForPhaseEnd();
}
}
}
@@ -1,37 +1,38 @@
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatSettlementState : CombatStateBase
{
private sealed class CombatSettlementState : CombatStateBase
private readonly string _reason;
private readonly bool _isVictory;
public CombatSettlementState(
CombatSchedulerRuntimeContext context,
CombatSchedulerFlowCoordinator flow,
string reason,
bool isVictory) : base(context, flow)
{
private readonly string _reason;
private readonly bool _isVictory;
_reason = reason;
_isVictory = isVictory;
}
public CombatSettlementState(CombatScheduler scheduler, string reason, bool isVictory) : base(scheduler)
public override void OnEnter()
{
Context.EnemyManager.EndPhase();
Context.EnemyManager.CleanupTrackedEnemies();
Context.IsFinishAsVictory = _isVictory;
Context.SettlementContext = Context.SettlementFlowService.BuildSettlementContext(
_reason,
_isVictory,
Context.CurrentLevel,
Context.EnemyManager.DefeatedEnemyCount,
Context.CombatInRunResourceManager);
if (Context.SettlementContext.ShouldOpenRewardSelection)
{
_reason = reason;
_isVictory = isVictory;
Flow.Scheduler.ChangeState(new CombatRewardSelectionState(Context, Flow));
return;
}
public override void OnEnter()
{
Scheduler._enemyManager.EndPhase();
Scheduler._enemyManager.CleanupTrackedEnemies();
Scheduler._isFinishAsVictory = _isVictory;
Scheduler._settlementContext = Scheduler._settlementFlowService.BuildSettlementContext(
_reason,
_isVictory,
Scheduler._currentLevel,
Scheduler._enemyManager.DefeatedEnemyCount,
Scheduler._combatInRunResourceManager);
if (Scheduler._settlementContext.ShouldOpenRewardSelection)
{
Scheduler.ChangeState(new CombatRewardSelectionState(Scheduler));
return;
}
Scheduler.ChangeState(new CombatFinishFormState(Scheduler));
}
Flow.Scheduler.ChangeState(new CombatFinishFormState(Context, Flow));
}
}
}
@@ -1,35 +1,34 @@
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal abstract class CombatStateBase
{
private abstract class CombatStateBase
protected CombatSchedulerRuntimeContext Context { get; }
protected CombatSchedulerFlowCoordinator Flow { get; }
protected CombatStateBase(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
{
protected CombatScheduler Scheduler { get; }
Context = context;
Flow = flow;
}
protected CombatStateBase(CombatScheduler scheduler)
{
Scheduler = scheduler;
}
public virtual void OnInit()
{
}
public virtual void OnInit()
{
}
public virtual void OnEnter()
{
}
public virtual void OnEnter()
{
}
public virtual void OnExit()
{
}
public virtual void OnExit()
{
}
public virtual void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
}
public virtual void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
}
public virtual void OnDestroy()
{
}
public virtual void OnDestroy()
{
}
}
}
@@ -1,46 +1,46 @@
using GeometryTD.DataTable;
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatWaitingForPhaseEndState : CombatStateBase
{
private sealed class CombatWaitingForPhaseEndState : CombatStateBase
public CombatWaitingForPhaseEndState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
: base(context, flow)
{
public CombatWaitingForPhaseEndState(CombatScheduler scheduler) : base(scheduler)
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_ = realElapseSeconds;
DRLevelPhase currentPhase = Context.PhaseLoopRuntime.CurrentPhase;
if (currentPhase == null)
{
Flow.EnterFailureFallback("CombatScheduler waiting phase failed. Current phase is null.");
return;
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
Context.PhaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
if (Flow.ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory))
{
_ = realElapseSeconds;
var currentPhase = Scheduler._phaseLoopRuntime.CurrentPhase;
if (currentPhase == null)
{
Scheduler.EnterFailureFallback("CombatScheduler waiting phase failed. Current phase is null.");
return;
}
Scheduler._phaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
if (Scheduler.ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory))
{
Scheduler.ChangeState(new CombatSettlementState(Scheduler, reason, isVictory));
return;
}
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;
}
Scheduler.CompleteCurrentPhase();
Flow.Scheduler.ChangeState(new CombatSettlementState(Context, Flow, reason, isVictory));
return;
}
PhaseEndConditionContext conditionContext = new(
currentPhase,
Context.PhaseLoopRuntime.CurrentPhaseElapsed,
Context.EnemyManager.IsPhaseSpawnCompleted,
Context.EnemyManager.AliveEnemyCount,
Context.EnemyManager.HasAliveBoss);
IPhaseEndCondition endCondition = PhaseEndConditionFactory.Create(currentPhase.EndType);
if (!endCondition.ShouldExit(conditionContext))
{
return;
}
Flow.CompleteCurrentPhase();
}
}
}
}
@@ -1,35 +1,33 @@
namespace GeometryTD.CustomComponent
{
public partial class CombatScheduler
internal sealed class CombatWaitingForReturnState : CombatStateBase
{
private sealed class CombatWaitingForReturnState : CombatStateBase
private bool _returnRequested;
public CombatWaitingForReturnState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
: base(context, flow)
{
private bool _returnRequested;
}
public CombatWaitingForReturnState(CombatScheduler scheduler) : base(scheduler)
public void RequestReturn()
{
_returnRequested = true;
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_ = elapseSeconds;
_ = realElapseSeconds;
if (!_returnRequested)
{
return;
}
public void RequestReturn()
{
_returnRequested = true;
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_ = elapseSeconds;
_ = realElapseSeconds;
if (!_returnRequested)
{
return;
}
Scheduler._loadSession.Cleanup();
Scheduler.CloseCombatFinishForm();
Scheduler.CloseRewardSelectForm();
Scheduler.CompleteNormalCombatAndNotify(Scheduler._isFinishAsVictory);
}
Context.LoadSession.Cleanup();
Flow.CloseCombatFinishForm();
Flow.CloseRewardSelectForm();
Flow.CompleteNormalCombatAndNotify(Context.IsFinishAsVictory);
}
}
}