refactor 7:
- 拆分 CombatScheduler,将公用方法与字段拆分为:
- FlowCoordinator
- RuntimeContext
- 地图侧现在收口为“MapData 初始快照 + CombatCoinChangedEventArgs 同步 + 独立命令桥接”
This commit is contained in:
parent
eb818e6295
commit
b38088c3ea
|
|
@ -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,13 +2,14 @@ using UnityGameFramework.Runtime;
|
|||
|
||||
namespace GeometryTD.CustomComponent
|
||||
{
|
||||
public partial class CombatScheduler
|
||||
{
|
||||
private sealed class CombatFailedState : CombatStateBase
|
||||
internal sealed class CombatFailedState : CombatStateBase
|
||||
{
|
||||
private readonly string _errorMessage;
|
||||
|
||||
public CombatFailedState(CombatScheduler scheduler, string errorMessage) : base(scheduler)
|
||||
public CombatFailedState(
|
||||
CombatSchedulerRuntimeContext context,
|
||||
CombatSchedulerFlowCoordinator flow,
|
||||
string errorMessage) : base(context, flow)
|
||||
{
|
||||
_errorMessage = errorMessage;
|
||||
}
|
||||
|
|
@ -17,18 +18,17 @@ namespace GeometryTD.CustomComponent
|
|||
{
|
||||
Log.Error(
|
||||
"CombatScheduler failed. LevelId={0}, {1}",
|
||||
Scheduler._currentLevel != null ? Scheduler._currentLevel.Id : 0,
|
||||
Context.CurrentLevel != null ? Context.CurrentLevel.Id : 0,
|
||||
_errorMessage);
|
||||
Scheduler._enemyManager.EndPhase();
|
||||
Scheduler.CloseCombatFinishForm();
|
||||
Scheduler.CloseRewardSelectForm();
|
||||
Scheduler.OpenCombatFailureDialog(_errorMessage);
|
||||
Context.EnemyManager.EndPhase();
|
||||
Flow.CloseCombatFinishForm();
|
||||
Flow.CloseRewardSelectForm();
|
||||
Flow.OpenCombatFailureDialog(_errorMessage);
|
||||
}
|
||||
|
||||
public override void OnExit()
|
||||
{
|
||||
Scheduler.CloseDialogForm();
|
||||
}
|
||||
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(CombatScheduler scheduler) : base(scheduler)
|
||||
public CombatFinishFormState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
|
||||
: base(context, flow)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnEnter()
|
||||
{
|
||||
if (Scheduler._settlementContext == null)
|
||||
if (Context.SettlementContext == null)
|
||||
{
|
||||
Scheduler.EnterFailureFallback("Combat finish form failed. Settlement context is missing.");
|
||||
Flow.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,26 +6,25 @@ using UnityEngine;
|
|||
|
||||
namespace GeometryTD.CustomComponent
|
||||
{
|
||||
public partial class CombatScheduler
|
||||
internal sealed class CombatLoadingState : CombatStateBase
|
||||
{
|
||||
private sealed class CombatLoadingState : CombatStateBase
|
||||
{
|
||||
public CombatLoadingState(CombatScheduler scheduler) : base(scheduler)
|
||||
public CombatLoadingState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
|
||||
: base(context, flow)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnEnter()
|
||||
{
|
||||
if (Scheduler._currentLevel == null)
|
||||
if (Context.CurrentLevel == null)
|
||||
{
|
||||
Scheduler.EnterFailureFallback("Combat loading failed. Current level is null.");
|
||||
Flow.EnterFailureFallback("Combat loading failed. Current level is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
MapData mapData = BuildMapData();
|
||||
if (!Scheduler._loadSession.StartLoading(Scheduler._currentLevel, mapData, Scheduler, out string errorMessage))
|
||||
MapEntityLoadContext mapLoadContext = BuildMapLoadContext();
|
||||
if (!Context.LoadSession.StartLoading(Context.CurrentLevel, mapLoadContext, Flow.Scheduler, out string errorMessage))
|
||||
{
|
||||
Scheduler.EnterFailureFallback($"Combat loading failed. {errorMessage}");
|
||||
Flow.EnterFailureFallback($"Combat loading failed. {errorMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -34,42 +33,40 @@ namespace GeometryTD.CustomComponent
|
|||
_ = elapseSeconds;
|
||||
_ = realElapseSeconds;
|
||||
|
||||
if (!Scheduler._loadSession.IsReady)
|
||||
if (!Context.LoadSession.IsReady)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Scheduler.TryBeginNextPhase();
|
||||
Flow.TryBeginNextPhase();
|
||||
}
|
||||
|
||||
private MapData BuildMapData()
|
||||
private MapEntityLoadContext BuildMapLoadContext()
|
||||
{
|
||||
List<TowerStatsData> buildTowerStatsSnapshot = new();
|
||||
for (int i = 0; i < Scheduler._combatInRunResourceManager.CurrentBuildTowerCount; i++)
|
||||
for (int i = 0; i < Context.CombatInRunResourceManager.CurrentBuildTowerCount; i++)
|
||||
{
|
||||
if (Scheduler._combatInRunResourceManager.TryGetBuildTowerStats(i, out TowerStatsData stats) &&
|
||||
if (Context.CombatInRunResourceManager.TryGetBuildTowerStats(i, out TowerStatsData stats) &&
|
||||
stats != null)
|
||||
{
|
||||
buildTowerStatsSnapshot.Add(stats);
|
||||
}
|
||||
}
|
||||
|
||||
return new MapData(
|
||||
MapData mapData = new MapData(
|
||||
entityId: 0,
|
||||
typeId: 0,
|
||||
levelId: Scheduler._currentLevel.Id,
|
||||
levelId: Context.CurrentLevel.Id,
|
||||
position: Vector3.zero,
|
||||
initialCoin: Scheduler._combatInRunResourceManager.CurrentCoin,
|
||||
initialCoin: Context.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);
|
||||
}
|
||||
: 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(CombatScheduler scheduler) : base(scheduler)
|
||||
public CombatRewardSelectionState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
|
||||
: base(context, flow)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnEnter()
|
||||
{
|
||||
if (Scheduler._settlementContext == null)
|
||||
if (Context.SettlementContext == null)
|
||||
{
|
||||
Scheduler.EnterFailureFallback("Combat reward selection failed. Settlement context is missing.");
|
||||
Flow.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))
|
||||
Flow.EnsureRewardSelectFormUseCaseBound();
|
||||
if (!Context.SettlementFlowService.TryPrepareRewardSelection(
|
||||
Context.SettlementContext,
|
||||
Context.CombatInRunResourceManager,
|
||||
Context.PhaseLoopRuntime.DisplayPhaseIndex,
|
||||
Flow.ResolveCurrentThemeType(),
|
||||
Context.RewardSelectFormUseCase,
|
||||
Flow.OnFullBaseHpRewardSelected,
|
||||
Flow.OnFullBaseHpRewardGiveUp))
|
||||
{
|
||||
Scheduler.ChangeState(new CombatFinishFormState(Scheduler));
|
||||
Flow.Scheduler.ChangeState(new CombatFinishFormState(Context, Flow));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnExit()
|
||||
{
|
||||
Scheduler.CloseRewardSelectForm();
|
||||
}
|
||||
Flow.CloseRewardSelectForm();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,16 @@ using UnityGameFramework.Runtime;
|
|||
|
||||
namespace GeometryTD.CustomComponent
|
||||
{
|
||||
public partial class CombatScheduler
|
||||
{
|
||||
private sealed class CombatRunningPhaseState : CombatStateBase
|
||||
internal sealed class CombatRunningPhaseState : CombatStateBase
|
||||
{
|
||||
private readonly DRLevelPhase _phase;
|
||||
private readonly IReadOnlyList<DRLevelSpawnEntry> _spawnEntries;
|
||||
|
||||
public CombatRunningPhaseState(
|
||||
CombatScheduler scheduler,
|
||||
CombatSchedulerRuntimeContext context,
|
||||
CombatSchedulerFlowCoordinator flow,
|
||||
DRLevelPhase phase,
|
||||
IReadOnlyList<DRLevelSpawnEntry> spawnEntries) : base(scheduler)
|
||||
IReadOnlyList<DRLevelSpawnEntry> spawnEntries) : base(context, flow)
|
||||
{
|
||||
_phase = phase;
|
||||
_spawnEntries = spawnEntries;
|
||||
|
|
@ -23,54 +22,53 @@ namespace GeometryTD.CustomComponent
|
|||
|
||||
public override void OnEnter()
|
||||
{
|
||||
Scheduler._enemyManager.BeginPhase(_phase, _spawnEntries);
|
||||
Context.EnemyManager.BeginPhase(_phase, _spawnEntries);
|
||||
GameEntry.Event.Fire(
|
||||
Scheduler,
|
||||
Flow,
|
||||
CombatProcessEventArgs.Create(
|
||||
Scheduler._phaseLoopRuntime.DisplayPhaseIndex,
|
||||
Scheduler._phaseLoopRuntime.PhaseCount));
|
||||
Context.PhaseLoopRuntime.DisplayPhaseIndex,
|
||||
Context.PhaseLoopRuntime.PhaseCount));
|
||||
GameEntry.Event.Fire(
|
||||
Scheduler,
|
||||
Flow,
|
||||
CombatEnemyHpRateChangedEventArgs.Create(
|
||||
ResolveEnemyHpRateMultiplier(
|
||||
Scheduler._phaseLoopRuntime.DisplayPhaseIndex,
|
||||
Scheduler._phaseLoopRuntime.PhaseCount)));
|
||||
Flow.ResolveEnemyHpRateMultiplier(
|
||||
Context.PhaseLoopRuntime.DisplayPhaseIndex,
|
||||
Context.PhaseLoopRuntime.PhaseCount)));
|
||||
|
||||
if (!Scheduler._nodeEnterFired)
|
||||
if (!Context.NodeEnterFired)
|
||||
{
|
||||
Scheduler._nodeEnterFired = true;
|
||||
GameEntry.Event.Fire(Scheduler, NodeEnterEventArgs.Create());
|
||||
Context.NodeEnterFired = true;
|
||||
GameEntry.Event.Fire(Flow, 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,
|
||||
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 (Scheduler._phaseLoopRuntime.CurrentPhase == null)
|
||||
if (Context.PhaseLoopRuntime.CurrentPhase == null)
|
||||
{
|
||||
Scheduler.EnterFailureFallback("CombatScheduler update failed. Current phase is null.");
|
||||
Flow.EnterFailureFallback("CombatScheduler update failed. Current phase is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
Scheduler._phaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
|
||||
Scheduler._enemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
Context.PhaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
|
||||
Context.EnemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
if (Scheduler.ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory))
|
||||
if (Flow.ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory))
|
||||
{
|
||||
Scheduler.ChangeState(new CombatSettlementState(Scheduler, reason, isVictory));
|
||||
Flow.Scheduler.ChangeState(new CombatSettlementState(Context, Flow, reason, isVictory));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Scheduler._enemyManager.IsPhaseSpawnCompleted)
|
||||
if (Context.EnemyManager.IsPhaseSpawnCompleted)
|
||||
{
|
||||
Scheduler.EnterWaitingForPhaseEnd();
|
||||
}
|
||||
Flow.EnterWaitingForPhaseEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
namespace GeometryTD.CustomComponent
|
||||
{
|
||||
public partial class CombatScheduler
|
||||
{
|
||||
private sealed class CombatSettlementState : CombatStateBase
|
||||
internal sealed class CombatSettlementState : CombatStateBase
|
||||
{
|
||||
private readonly string _reason;
|
||||
private readonly bool _isVictory;
|
||||
|
||||
public CombatSettlementState(CombatScheduler scheduler, string reason, bool isVictory) : base(scheduler)
|
||||
public CombatSettlementState(
|
||||
CombatSchedulerRuntimeContext context,
|
||||
CombatSchedulerFlowCoordinator flow,
|
||||
string reason,
|
||||
bool isVictory) : base(context, flow)
|
||||
{
|
||||
_reason = reason;
|
||||
_isVictory = isVictory;
|
||||
|
|
@ -15,23 +17,22 @@ namespace GeometryTD.CustomComponent
|
|||
|
||||
public override void OnEnter()
|
||||
{
|
||||
Scheduler._enemyManager.EndPhase();
|
||||
Scheduler._enemyManager.CleanupTrackedEnemies();
|
||||
Scheduler._isFinishAsVictory = _isVictory;
|
||||
Scheduler._settlementContext = Scheduler._settlementFlowService.BuildSettlementContext(
|
||||
Context.EnemyManager.EndPhase();
|
||||
Context.EnemyManager.CleanupTrackedEnemies();
|
||||
Context.IsFinishAsVictory = _isVictory;
|
||||
Context.SettlementContext = Context.SettlementFlowService.BuildSettlementContext(
|
||||
_reason,
|
||||
_isVictory,
|
||||
Scheduler._currentLevel,
|
||||
Scheduler._enemyManager.DefeatedEnemyCount,
|
||||
Scheduler._combatInRunResourceManager);
|
||||
if (Scheduler._settlementContext.ShouldOpenRewardSelection)
|
||||
Context.CurrentLevel,
|
||||
Context.EnemyManager.DefeatedEnemyCount,
|
||||
Context.CombatInRunResourceManager);
|
||||
if (Context.SettlementContext.ShouldOpenRewardSelection)
|
||||
{
|
||||
Scheduler.ChangeState(new CombatRewardSelectionState(Scheduler));
|
||||
Flow.Scheduler.ChangeState(new CombatRewardSelectionState(Context, Flow));
|
||||
return;
|
||||
}
|
||||
|
||||
Scheduler.ChangeState(new CombatFinishFormState(Scheduler));
|
||||
}
|
||||
Flow.Scheduler.ChangeState(new CombatFinishFormState(Context, Flow));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
namespace GeometryTD.CustomComponent
|
||||
{
|
||||
public partial class CombatScheduler
|
||||
internal abstract class CombatStateBase
|
||||
{
|
||||
private abstract class CombatStateBase
|
||||
{
|
||||
protected CombatScheduler Scheduler { get; }
|
||||
protected CombatSchedulerRuntimeContext Context { get; }
|
||||
protected CombatSchedulerFlowCoordinator Flow { get; }
|
||||
|
||||
protected CombatStateBase(CombatScheduler scheduler)
|
||||
protected CombatStateBase(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
|
||||
{
|
||||
Scheduler = scheduler;
|
||||
Context = context;
|
||||
Flow = flow;
|
||||
}
|
||||
|
||||
public virtual void OnInit()
|
||||
|
|
@ -31,5 +31,4 @@ namespace GeometryTD.CustomComponent
|
|||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
using GeometryTD.DataTable;
|
||||
|
||||
namespace GeometryTD.CustomComponent
|
||||
{
|
||||
public partial class CombatScheduler
|
||||
internal sealed class CombatWaitingForPhaseEndState : CombatStateBase
|
||||
{
|
||||
private sealed class CombatWaitingForPhaseEndState : CombatStateBase
|
||||
{
|
||||
public CombatWaitingForPhaseEndState(CombatScheduler scheduler) : base(scheduler)
|
||||
public CombatWaitingForPhaseEndState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
|
||||
: base(context, flow)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -12,35 +13,34 @@ namespace GeometryTD.CustomComponent
|
|||
{
|
||||
_ = realElapseSeconds;
|
||||
|
||||
var currentPhase = Scheduler._phaseLoopRuntime.CurrentPhase;
|
||||
DRLevelPhase currentPhase = Context.PhaseLoopRuntime.CurrentPhase;
|
||||
if (currentPhase == null)
|
||||
{
|
||||
Scheduler.EnterFailureFallback("CombatScheduler waiting phase failed. Current phase is null.");
|
||||
Flow.EnterFailureFallback("CombatScheduler waiting phase failed. Current phase is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
Scheduler._phaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
|
||||
Context.PhaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
|
||||
|
||||
if (Scheduler.ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory))
|
||||
if (Flow.ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory))
|
||||
{
|
||||
Scheduler.ChangeState(new CombatSettlementState(Scheduler, reason, isVictory));
|
||||
Flow.Scheduler.ChangeState(new CombatSettlementState(Context, Flow, reason, isVictory));
|
||||
return;
|
||||
}
|
||||
|
||||
PhaseEndConditionContext context = new(
|
||||
PhaseEndConditionContext conditionContext = new(
|
||||
currentPhase,
|
||||
Scheduler._phaseLoopRuntime.CurrentPhaseElapsed,
|
||||
Scheduler._enemyManager.IsPhaseSpawnCompleted,
|
||||
Scheduler._enemyManager.AliveEnemyCount,
|
||||
Scheduler._enemyManager.HasAliveBoss);
|
||||
Context.PhaseLoopRuntime.CurrentPhaseElapsed,
|
||||
Context.EnemyManager.IsPhaseSpawnCompleted,
|
||||
Context.EnemyManager.AliveEnemyCount,
|
||||
Context.EnemyManager.HasAliveBoss);
|
||||
IPhaseEndCondition endCondition = PhaseEndConditionFactory.Create(currentPhase.EndType);
|
||||
if (!endCondition.ShouldExit(context))
|
||||
if (!endCondition.ShouldExit(conditionContext))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Scheduler.CompleteCurrentPhase();
|
||||
}
|
||||
Flow.CompleteCurrentPhase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
namespace GeometryTD.CustomComponent
|
||||
{
|
||||
public partial class CombatScheduler
|
||||
{
|
||||
private sealed class CombatWaitingForReturnState : CombatStateBase
|
||||
internal sealed class CombatWaitingForReturnState : CombatStateBase
|
||||
{
|
||||
private bool _returnRequested;
|
||||
|
||||
public CombatWaitingForReturnState(CombatScheduler scheduler) : base(scheduler)
|
||||
public CombatWaitingForReturnState(CombatSchedulerRuntimeContext context, CombatSchedulerFlowCoordinator flow)
|
||||
: base(context, flow)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -25,11 +24,10 @@ namespace GeometryTD.CustomComponent
|
|||
return;
|
||||
}
|
||||
|
||||
Scheduler._loadSession.Cleanup();
|
||||
Scheduler.CloseCombatFinishForm();
|
||||
Scheduler.CloseRewardSelectForm();
|
||||
Scheduler.CompleteNormalCombatAndNotify(Scheduler._isFinishAsVictory);
|
||||
}
|
||||
Context.LoadSession.Cleanup();
|
||||
Flow.CloseCombatFinishForm();
|
||||
Flow.CloseRewardSelectForm();
|
||||
Flow.CompleteNormalCombatAndNotify(Context.IsFinishAsVictory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,6 @@ namespace GeometryTD.Entity.EntityData
|
|||
[SerializeField] private BackpackInventoryData _inventorySnapshot;
|
||||
[SerializeField] private TowerItemData[] _participantTowerSnapshot = Array.Empty<TowerItemData>();
|
||||
|
||||
[NonSerialized] private Func<int, bool> _tryConsumeCoin;
|
||||
[NonSerialized] private Action<int> _addCoin;
|
||||
|
||||
public MapData(int entityId, int levelId, Vector3 position) : this(entityId, 0, levelId, position)
|
||||
{
|
||||
}
|
||||
|
|
@ -36,9 +33,7 @@ namespace GeometryTD.Entity.EntityData
|
|||
int initialCoin,
|
||||
IReadOnlyList<TowerStatsData> buildTowerStatsSnapshot,
|
||||
BackpackInventoryData inventorySnapshot,
|
||||
IReadOnlyList<TowerItemData> participantTowerSnapshot,
|
||||
Func<int, bool> tryConsumeCoin,
|
||||
Action<int> addCoin) : base(entityId, typeId)
|
||||
IReadOnlyList<TowerItemData> participantTowerSnapshot) : base(entityId, typeId)
|
||||
{
|
||||
_levelId = levelId;
|
||||
Position = position;
|
||||
|
|
@ -48,8 +43,6 @@ namespace GeometryTD.Entity.EntityData
|
|||
? InventoryCloneUtility.CloneInventory(inventorySnapshot)
|
||||
: null;
|
||||
SetParticipantTowerSnapshot(participantTowerSnapshot);
|
||||
_tryConsumeCoin = tryConsumeCoin;
|
||||
_addCoin = addCoin;
|
||||
}
|
||||
|
||||
public int LevelId
|
||||
|
|
@ -70,34 +63,6 @@ namespace GeometryTD.Entity.EntityData
|
|||
: null;
|
||||
public IReadOnlyList<TowerItemData> ParticipantTowerSnapshot => _participantTowerSnapshot;
|
||||
|
||||
public void BindCombatCallbacks(Func<int, bool> tryConsumeCoin, Action<int> addCoin)
|
||||
{
|
||||
_tryConsumeCoin = tryConsumeCoin;
|
||||
_addCoin = addCoin;
|
||||
}
|
||||
|
||||
public bool TryConsumeCoin(int coin)
|
||||
{
|
||||
int requiredCoin = Mathf.Max(0, coin);
|
||||
if (requiredCoin <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _tryConsumeCoin != null && _tryConsumeCoin.Invoke(requiredCoin);
|
||||
}
|
||||
|
||||
public void AddCoin(int coin)
|
||||
{
|
||||
int amount = Mathf.Max(0, coin);
|
||||
if (amount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_addCoin?.Invoke(amount);
|
||||
}
|
||||
|
||||
public bool TryGetBuildTowerStats(int buildIndex, out TowerStatsData stats)
|
||||
{
|
||||
stats = null;
|
||||
|
|
@ -141,9 +106,7 @@ namespace GeometryTD.Entity.EntityData
|
|||
_initialCoin,
|
||||
_buildTowerStatsSnapshot,
|
||||
_inventorySnapshot,
|
||||
_participantTowerSnapshot,
|
||||
_tryConsumeCoin,
|
||||
_addCoin);
|
||||
_participantTowerSnapshot);
|
||||
}
|
||||
|
||||
public void SetParticipantTowerSnapshot(IReadOnlyList<TowerItemData> participantTowerSnapshot)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
|
||||
namespace GeometryTD.Entity.EntityData
|
||||
{
|
||||
public sealed class MapEntityLoadContext
|
||||
{
|
||||
public MapEntityLoadContext(MapData initialMapData, Func<int, bool> tryConsumeCoin, Action<int> addCoin)
|
||||
{
|
||||
InitialMapData = initialMapData;
|
||||
TryConsumeCoin = tryConsumeCoin;
|
||||
AddCoin = addCoin;
|
||||
}
|
||||
|
||||
public MapData InitialMapData { get; }
|
||||
public Func<int, bool> TryConsumeCoin { get; }
|
||||
public Action<int> AddCoin { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4ba39a6e9c0948b386b8b0189e3541da
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -56,6 +56,17 @@ namespace GeometryTD
|
|||
|
||||
public static void ShowMap(this EntityComponent entityComponent, MapData data, string mapAssetName)
|
||||
{
|
||||
ShowMap(entityComponent, new MapEntityLoadContext(data, null, null), mapAssetName);
|
||||
}
|
||||
|
||||
public static void ShowMap(this EntityComponent entityComponent, MapEntityLoadContext loadContext)
|
||||
{
|
||||
ShowMap(entityComponent, loadContext, null);
|
||||
}
|
||||
|
||||
public static void ShowMap(this EntityComponent entityComponent, MapEntityLoadContext loadContext, string mapAssetName)
|
||||
{
|
||||
MapData data = loadContext?.InitialMapData;
|
||||
if (data == null)
|
||||
{
|
||||
Log.Warning("Map data is invalid.");
|
||||
|
|
@ -64,7 +75,7 @@ namespace GeometryTD
|
|||
|
||||
string resolvedMapAssetName = string.IsNullOrEmpty(mapAssetName) ? data.LevelId.ToString() : mapAssetName;
|
||||
string mapAssetPath = AssetUtility.GetLevelMapAsset(resolvedMapAssetName);
|
||||
entityComponent.ShowEntity(data.Id, typeof(MapEntity), mapAssetPath, "Map", Constant.AssetPriority.MapAsset, data);
|
||||
entityComponent.ShowEntity(data.Id, typeof(MapEntity), mapAssetPath, "Map", Constant.AssetPriority.MapAsset, loadContext);
|
||||
}
|
||||
|
||||
private static void ShowEntity(this EntityComponent entityComponent, Type logicType, string entityGroup,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.CustomComponent;
|
||||
using GeometryTD.CustomEvent;
|
||||
using GameFramework.Event;
|
||||
using GeometryTD.Map;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Entity.EntityData;
|
||||
|
|
@ -37,10 +35,8 @@ namespace GeometryTD.Entity
|
|||
private CombatSelectInputService _combatSelectInputService;
|
||||
private TowerPlacementService _towerPlacementService;
|
||||
private TowerSelectionPresenter _towerSelectionPresenter;
|
||||
|
||||
private CombatSelectUseCaseConfigurator _combatSelectUseCaseConfigurator;
|
||||
private int _currentCoin;
|
||||
private bool _isCoinEventSubscribed;
|
||||
private MapCombatRuntimeBridge _combatRuntimeBridge;
|
||||
|
||||
public IReadOnlyList<Vector3Int> PathCells => _mapTopologyService != null
|
||||
? _mapTopologyService.PathCells
|
||||
|
|
@ -110,22 +106,21 @@ namespace GeometryTD.Entity
|
|||
InitializeMapTopologyService();
|
||||
InitializeTowerPlacementService();
|
||||
InitializeTowerSelectionPresenter();
|
||||
InitializeCombatRuntimeBridge();
|
||||
}
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
_mapData = userData as MapData;
|
||||
MapEntityLoadContext loadContext = ResolveLoadContext(userData);
|
||||
_mapData = loadContext?.InitialMapData;
|
||||
if (_mapData == null)
|
||||
{
|
||||
Log.Warning("MapData is invalid for map entity '{0}'.", Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentCoin = Mathf.Max(0, _mapData.InitialCoin);
|
||||
SubscribeCombatEvents();
|
||||
}
|
||||
|
||||
base.OnShow(_mapData);
|
||||
|
||||
_combatRuntimeBridge?.Initialize(loadContext, name);
|
||||
|
||||
RefreshTiles();
|
||||
ConfigureCombatSelectUseCase();
|
||||
|
|
@ -134,7 +129,7 @@ namespace GeometryTD.Entity
|
|||
|
||||
protected override void OnHide(bool isShutdown, object userData)
|
||||
{
|
||||
UnsubscribeCombatEvents();
|
||||
_combatRuntimeBridge?.Reset();
|
||||
HideCombatSelectForm();
|
||||
_towerPlacementService?.HideAndClearAllPlacedTowers();
|
||||
ClearSelectionState();
|
||||
|
|
@ -240,6 +235,14 @@ namespace GeometryTD.Entity
|
|||
}
|
||||
}
|
||||
|
||||
private void InitializeCombatRuntimeBridge()
|
||||
{
|
||||
if (_combatRuntimeBridge == null)
|
||||
{
|
||||
_combatRuntimeBridge = new MapCombatRuntimeBridge();
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureCombatSelectUseCase()
|
||||
{
|
||||
_combatSelectFormUseCase.SetCoinProvider(GetCurrentCoin);
|
||||
|
|
@ -257,6 +260,21 @@ namespace GeometryTD.Entity
|
|||
_mapData != null ? _mapData.ParticipantTowerSnapshot : null);
|
||||
}
|
||||
|
||||
private MapEntityLoadContext ResolveLoadContext(object userData)
|
||||
{
|
||||
if (userData is MapEntityLoadContext loadContext)
|
||||
{
|
||||
return loadContext;
|
||||
}
|
||||
|
||||
if (userData is MapData legacyMapData)
|
||||
{
|
||||
return new MapEntityLoadContext(legacyMapData, null, null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void HandleCombatSelectInput()
|
||||
{
|
||||
if (!_enableCombatSelectInput || !Input.GetMouseButtonDown(0))
|
||||
|
|
@ -379,38 +397,6 @@ namespace GeometryTD.Entity
|
|||
GameEntry.UIRouter.CloseUI(UIFormType.CombatSelectForm);
|
||||
}
|
||||
|
||||
private void SubscribeCombatEvents()
|
||||
{
|
||||
if (_isCoinEventSubscribed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Subscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
|
||||
_isCoinEventSubscribed = true;
|
||||
}
|
||||
|
||||
private void UnsubscribeCombatEvents()
|
||||
{
|
||||
if (!_isCoinEventSubscribed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Unsubscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
|
||||
_isCoinEventSubscribed = false;
|
||||
}
|
||||
|
||||
private void OnCombatCoinChanged(object sender, GameEventArgs e)
|
||||
{
|
||||
if (e is not CombatCoinChangedEventArgs args)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentCoin = Mathf.Max(0, args.CurrentCoin);
|
||||
}
|
||||
|
||||
private int GetCurrentBuildTowerCount()
|
||||
{
|
||||
if (_mapData == null)
|
||||
|
|
@ -434,12 +420,12 @@ namespace GeometryTD.Entity
|
|||
return false;
|
||||
}
|
||||
|
||||
return _mapData.TryConsumeCoin(requiredCoin);
|
||||
return _combatRuntimeBridge != null && _combatRuntimeBridge.TryConsumeCoin(requiredCoin);
|
||||
}
|
||||
|
||||
private int GetCurrentCoin()
|
||||
{
|
||||
return Mathf.Max(0, _currentCoin);
|
||||
return Mathf.Max(0, _combatRuntimeBridge != null ? _combatRuntimeBridge.CurrentCoin : 0);
|
||||
}
|
||||
|
||||
private void AddCoin(int coin)
|
||||
|
|
@ -450,7 +436,7 @@ namespace GeometryTD.Entity
|
|||
return;
|
||||
}
|
||||
|
||||
_mapData?.AddCoin(amount);
|
||||
_combatRuntimeBridge?.AddCoin(amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
using System;
|
||||
using GameFramework.Event;
|
||||
using GeometryTD.CustomEvent;
|
||||
using GeometryTD.Entity.EntityData;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.Map
|
||||
{
|
||||
public sealed class MapCombatRuntimeBridge
|
||||
{
|
||||
private Func<int, bool> _tryConsumeCoin;
|
||||
private Action<int> _addCoin;
|
||||
private bool _isCoinEventSubscribed;
|
||||
|
||||
public int CurrentCoin { get; private set; }
|
||||
|
||||
public void Initialize(MapEntityLoadContext loadContext, string mapName)
|
||||
{
|
||||
Reset();
|
||||
|
||||
MapData mapData = loadContext?.InitialMapData;
|
||||
CurrentCoin = Mathf.Max(0, mapData != null ? mapData.InitialCoin : 0);
|
||||
_tryConsumeCoin = loadContext?.TryConsumeCoin;
|
||||
_addCoin = loadContext?.AddCoin;
|
||||
|
||||
if (_tryConsumeCoin == null || _addCoin == null)
|
||||
{
|
||||
Log.Warning(
|
||||
"Map combat runtime bridge has incomplete callbacks. Map='{0}', TryConsumeCoin={1}, AddCoin={2}.",
|
||||
mapName,
|
||||
_tryConsumeCoin != null,
|
||||
_addCoin != null);
|
||||
}
|
||||
|
||||
GameEntry.Event.Subscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
|
||||
_isCoinEventSubscribed = true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
if (_isCoinEventSubscribed)
|
||||
{
|
||||
GameEntry.Event.Unsubscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
|
||||
_isCoinEventSubscribed = false;
|
||||
}
|
||||
|
||||
_tryConsumeCoin = null;
|
||||
_addCoin = null;
|
||||
CurrentCoin = 0;
|
||||
}
|
||||
|
||||
public bool TryConsumeCoin(int cost)
|
||||
{
|
||||
int requiredCoin = Mathf.Max(0, cost);
|
||||
if (requiredCoin <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _tryConsumeCoin != null && _tryConsumeCoin.Invoke(requiredCoin);
|
||||
}
|
||||
|
||||
public void AddCoin(int coin)
|
||||
{
|
||||
int amount = Mathf.Max(0, coin);
|
||||
if (amount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_addCoin?.Invoke(amount);
|
||||
}
|
||||
|
||||
private void OnCombatCoinChanged(object sender, GameEventArgs e)
|
||||
{
|
||||
if (e is not CombatCoinChangedEventArgs args)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentCoin = Mathf.Max(0, args.CurrentCoin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8309cfa985d6427c83ac5fc2b7e92d31
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -109,9 +109,8 @@ namespace GeometryTD.Map
|
|||
}
|
||||
|
||||
float minDistance = float.MaxValue;
|
||||
for (int i = 0; i < _pathCells.Count; i++)
|
||||
foreach (var candidate in _pathCells)
|
||||
{
|
||||
Vector3Int candidate = _pathCells[i];
|
||||
float distance = (tilemap.GetCellCenterWorld(candidate) - worldPosition).sqrMagnitude;
|
||||
if (distance >= minDistance)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,32 +4,35 @@
|
|||
|
||||
## 当前目标
|
||||
|
||||
按 `docs/CombatNodeArchitecture.md` 收敛 `CombatNode` 域职责,重点是:
|
||||
- `CombatScheduler` 收敛为“状态机管理器”,不再继续堆业务细节。
|
||||
- 局内 `Coin / Gold / BaseHp / Loot Backpack / BuildTowerSnapshots` 由 `CombatInRunResourceManager` 作为唯一真值来源。
|
||||
- `EnemyManager` 只上报敌人事件,不直接决定资源入账或状态切换。
|
||||
- `PhaseEndType` 退出条件从 `PhaseLoopRuntime` 中抽离到 `IPhaseEndCondition` 实现类。
|
||||
- 结束链、加载链、奖励选择链逐步从 `CombatScheduler.cs` 本体挪到状态类或专用服务。
|
||||
按 `docs/CombatNodeArchitecture.md` 继续收敛 `CombatNode` 域职责。当前骨架已经基本到位,后续重点是:
|
||||
- 继续保持 `CombatScheduler` 作为唯一状态机边界,避免把新业务重新堆回本体。
|
||||
- 继续完成 `MapData + Event` 解耦收尾,确认 `MapEntity` 不再反查 `CombatNode` 域运行时。
|
||||
- 稳定 `CombatSettlementContext` 的模型边界,避免流程控制字段和展示摘要继续混杂增长。
|
||||
- 补 Unity 编译、PlayMode 和失败路径回归验证,把这轮结构调整真正跑通。
|
||||
|
||||
## 已完成
|
||||
|
||||
### 1. 状态类拆分完成
|
||||
- `CombatScheduler.cs` 内部的嵌套状态类已经迁移到 `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatStates/`。
|
||||
- 保留了 `partial class CombatScheduler + 嵌套类` 的结构。
|
||||
- 旧 `CombatState` 已统一替换为 `CombatStateBase`。
|
||||
### 1. 状态类已收口到 CombatScheduler/CombatStates
|
||||
- 状态类当前位于 `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/`。
|
||||
- `CombatStateBase` 已统一改为 `Context + Flow` 双引用模式。
|
||||
- 各状态不再直接访问 `CombatScheduler._xxx` 私有字段,而是通过:
|
||||
- `CombatSchedulerRuntimeContext`
|
||||
- `CombatSchedulerFlowCoordinator`
|
||||
|
||||
关键文件:
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatStates/CombatStateBase.cs`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatStates/*.cs`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatStateBase.cs`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/*.cs`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerRuntimeContext.cs`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs`
|
||||
|
||||
### 2. 第一轮目标命名和骨架已建立
|
||||
### 2. 第一轮目标命名、骨架与接线已建立
|
||||
- `CombatResourceManager` 已重命名为 `CombatInRunResourceManager`。
|
||||
- 已新增掉落解析骨架:
|
||||
- `EnemyDropResolveContext`
|
||||
- `EnemyDropResolveResult`
|
||||
- `EnemyDropResolver`
|
||||
- 已新增 phase end 骨架:
|
||||
- 已新增 phase end 骨架并接入等待退出状态:
|
||||
- `IPhaseEndCondition`
|
||||
- `PhaseEndConditionContext`
|
||||
- `PhaseEndConditionFactory`
|
||||
|
|
@ -88,7 +91,22 @@
|
|||
- `TryConsumeCoin(...)`
|
||||
- `AddCoin(...)`
|
||||
- `TryGetBuildTowerStats(...)`
|
||||
- 失败调试入口:
|
||||
- `TryDebugFail(...)`
|
||||
- 不再通过 `CombatNodeComponent` 读写 baseHp/coin 真值
|
||||
- 当前主职责已收紧为:
|
||||
- 生命周期入口
|
||||
- 状态切换入口
|
||||
- 对外公开查询/操作接口
|
||||
- 敌人事件公共入口
|
||||
- 事件桥回调入口
|
||||
|
||||
#### CombatScheduler 内部实现细化
|
||||
- 已新增 `CombatSchedulerRuntimeContext`
|
||||
- 承载共享运行时字段与共享服务引用
|
||||
- 已新增 `CombatSchedulerFlowCoordinator`
|
||||
- 承载多个状态共用的流程辅助方法
|
||||
- 当前 `CombatScheduler` 本体不再直接堆所有共享字段和公用流程辅助
|
||||
|
||||
#### CombatNodeComponent
|
||||
- 不再持有这些字段:
|
||||
|
|
@ -105,85 +123,161 @@
|
|||
|
||||
关键文件:
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerRuntimeContext.cs`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatNodeComponent.cs`
|
||||
|
||||
### 5. EnemyManager 事件边界已改为只上报敌人事实
|
||||
- `EnemyManager` 现在只向 `CombatScheduler` 上报:
|
||||
- `OnEnemyDefeated(DREnemy enemy)`
|
||||
- `OnEnemyReachedBase(DREnemy enemy)`
|
||||
- coin/gold/baseDamage 的公共副作用已统一收口到 `CombatScheduler`
|
||||
- `EnemyDropResolver + CombatInRunResourceManager` 已接到公共敌人事件入口上
|
||||
|
||||
关键文件:
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyManager.cs`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/EnemyDropResolver.cs`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs`
|
||||
|
||||
### 6. PhaseEndCondition 已正式接入 WaitingForPhaseEnd
|
||||
- `CombatWaitingForPhaseEndState` 已改为通过:
|
||||
- `PhaseEndConditionFactory.Create(...)`
|
||||
- `IPhaseEndCondition.ShouldExit(...)`
|
||||
判定 phase 结束
|
||||
- `PhaseLoopRuntime` 当前只保留 phase runtime 数据与 phase 进入逻辑,不再负责 `PhaseEndType` 判定
|
||||
|
||||
关键文件:
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatWaitingForPhaseEndState.cs`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseEndConditions/`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseLoopRuntime.cs`
|
||||
|
||||
### 7. 结束链与异常失败链已做协议收口
|
||||
- 正常结束:
|
||||
- `NodeCompleteEventArgs` 只在 `WaitingForReturn` 完成后发布
|
||||
- 异常失败:
|
||||
- 使用 `CombatFailedState`
|
||||
- 弹出单按钮 `DialogForm`
|
||||
- 点击 `Return Menu` 后发布 `CombatFailureReturnEventArgs`
|
||||
- `ProcedureMenu` 已区分正常结束与异常失败两条返回协议
|
||||
|
||||
关键文件:
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatWaitingForReturnState.cs`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatFailedState.cs`
|
||||
- `Assets/GameMain/Scripts/Event/Combat/CombatFailureReturnEventArgs.cs`
|
||||
- `Assets/GameMain/Scripts/Procedure/ProcedureMenu.cs`
|
||||
|
||||
### 8. CombatSettlementContext 已收紧为结束链共享上下文
|
||||
- 当前由 `Settlement -> RewardSelection -> FinishForm -> WaitingForReturn` 共享
|
||||
- 已显式承载:
|
||||
- 结算事实
|
||||
- 奖励背包
|
||||
- 奖励选择相关流程标记
|
||||
- 低血惩罚相关事实
|
||||
- 低血惩罚已改为“先记录事实,提交阶段再统一落库”,不再在结算构造期直接写库存
|
||||
- `CombatFinishForm` 当前只消费它真正展示需要的摘要,不再把额外结算事实继续灌进 UI Context
|
||||
|
||||
关键文件:
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementContext.cs`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementFlowService.cs`
|
||||
- `Assets/GameMain/Scripts/UI/Combat/UseCase/CombatFinishFormUseCase.cs`
|
||||
|
||||
### 9. CombatInfoForm 已补手动失败测试入口
|
||||
- `CombatInfoForm.OnFailButtonClick()` 可手动触发异常失败链
|
||||
- 新增 `CombatDebugFailEventArgs`
|
||||
- 当前可直接用于测试:
|
||||
- `CombatFailedState`
|
||||
- 失败 Dialog
|
||||
- `CombatFailureReturnEventArgs`
|
||||
|
||||
关键文件:
|
||||
- `Assets/GameMain/Scripts/UI/Combat/View/CombatInfoForm.cs`
|
||||
- `Assets/GameMain/Scripts/UI/Combat/Controller/CombatInfoFormController.cs`
|
||||
- `Assets/GameMain/Scripts/UI/Combat/UseCase/CombatInfoFormUseCase.cs`
|
||||
- `Assets/GameMain/Scripts/Event/Combat/CombatDebugFailEventArgs.cs`
|
||||
|
||||
## 还没完成
|
||||
|
||||
### 1. EnemyManager 事件边界还没改干净
|
||||
当前仍然存在的问题:
|
||||
- `EnemyManager` 还在自己计算 `droppedCoin / droppedGold / baseDamage`
|
||||
- 然后把这些原始数值直接传给 `CombatScheduler`
|
||||
|
||||
目标状态:
|
||||
- `EnemyManager` 只上报:
|
||||
- `OnEnemyDefeated(DREnemy enemy)`
|
||||
- `OnEnemyReachedBase(DREnemy enemy)`
|
||||
- `CombatScheduler` 统一调用:
|
||||
- `EnemyDropResolver`
|
||||
- `CombatInRunResourceManager`
|
||||
|
||||
下一步建议:
|
||||
- 先改 `EnemyManager` 上报签名
|
||||
- 再把 coin/gold/baseDamage 的公共副作用挪到 `CombatScheduler`
|
||||
|
||||
### 2. PhaseEndCondition 骨架已建,但还没接入
|
||||
当前仍然是旧逻辑:
|
||||
- `PhaseLoopRuntime.ShouldEndCurrentPhase(...)` 还在使用
|
||||
- `CombatWaitingForPhaseEndState` 还没有改成通过 `IPhaseEndCondition` 判定
|
||||
|
||||
目标状态:
|
||||
- `PhaseLoopRuntime` 只保留 phase runtime 数据
|
||||
- `CombatWaitingForPhaseEndState` 通过 `PhaseEndConditionFactory.Create(...)` 获取当前判定器
|
||||
- `PhaseEndType` 的规则不再写在 `PhaseLoopRuntime` 里
|
||||
|
||||
### 3. CombatScheduler 本体仍然过重
|
||||
当前仍然还在 `CombatScheduler.cs` 里的业务:
|
||||
- 结算上下文构建
|
||||
- 基地血量结算修正
|
||||
- 奖励选择 UI 准备
|
||||
- FinishForm 打开逻辑
|
||||
- 一部分加载和清理编排
|
||||
|
||||
目标状态:
|
||||
- `Loading` 负责加载和 `MapData` 组装
|
||||
- `Settlement` 负责结算上下文和奖励准入判断
|
||||
- `RewardSelection` 只处理选择流程
|
||||
- `FinishForm` 只处理展示
|
||||
- `WaitingForReturn` 只处理回退和最终清理
|
||||
|
||||
### 4. MapData + Event 解耦还没开始
|
||||
### 1. MapData + Event 解耦还没完全收口
|
||||
当前:
|
||||
- `MapData` 只有 `LevelId`
|
||||
- `MapEntity` 仍通过 `GameEntry.CombatNode` 反查 coin / build stats
|
||||
- `CombatLoadingState` 已经会组装更完整的 `MapData`
|
||||
- `MapEntity` 已经主要从 `MapData` 取初始上下文
|
||||
- 但还需要继续确认地图侧事件和运行时依赖是否已经完全摆脱对 `CombatNode` 域的反查
|
||||
|
||||
目标状态:
|
||||
- `CombatLoadingState` 从 `CombatInRunResourceManager` 读取快照
|
||||
- `CombatLoadingState` 组装更完整的 `MapData`
|
||||
- `MapEntity` 后续通过 `MapData + Event` 获取上下文,而不是反查 `CombatNode`
|
||||
|
||||
### 2. CombatScheduler 本体仍然还有收紧空间
|
||||
当前:
|
||||
- 生命周期入口、状态切换入口、对外公开接口、事件桥回调入口仍在 `CombatScheduler.cs`
|
||||
- 共享字段和共享流程已经拆到了 `RuntimeContext + FlowCoordinator`
|
||||
- 但 `FlowCoordinator` 目前仍直接依赖 `CombatScheduler` 宿主对象
|
||||
|
||||
目标状态:
|
||||
- 对外公开接口与内部宿主职责进一步分离
|
||||
- 若继续收紧,可评估是否引入极小宿主接口,只暴露:
|
||||
- `ChangeState(...)`
|
||||
- 必要的回调/转发入口
|
||||
- 继续避免把新业务重新塞回 `CombatScheduler.cs`
|
||||
|
||||
### 3. CombatSettlementContext 还可以继续整理字段分层
|
||||
当前:
|
||||
- `CombatSettlementContext` 已能支撑结束链
|
||||
- 但字段仍是平铺增长
|
||||
|
||||
目标状态:
|
||||
- 更明确地区分:
|
||||
- 流程控制字段
|
||||
- 结算事实字段
|
||||
- 展示摘要字段
|
||||
- 避免后续继续把 UI 需求和流程控制混在一起
|
||||
|
||||
### 4. 需要补实际运行验证
|
||||
当前改动已覆盖:
|
||||
- 状态机结构
|
||||
- 结束链/失败链协议
|
||||
- 手动失败测试入口
|
||||
- `CombatSchedulerRuntimeContext + CombatSchedulerFlowCoordinator`
|
||||
- `CombatSettlementContext` 与延迟提交惩罚
|
||||
|
||||
但仍缺:
|
||||
- Unity 编译验证
|
||||
- PlayMode/手点验证
|
||||
- 失败路径回收验证
|
||||
- 新开局后无残留状态验证
|
||||
|
||||
## 推荐的后续执行顺序
|
||||
|
||||
1. 改 `EnemyManager` 上报边界,接入 `EnemyDropResolver`
|
||||
2. 接入 `IPhaseEndCondition`,移除 `PhaseLoopRuntime.ShouldEndCurrentPhase(...)`
|
||||
3. 把结算链逻辑从 `CombatScheduler.cs` 继续剥离到状态类
|
||||
4. 开始做 `MapData + Event` 解耦
|
||||
1. 做 `MapData + Event` 解耦收尾,排查 `MapEntity` 和地图侧事件是否还反查 `CombatNode`
|
||||
2. 评估是否给 `FlowCoordinator` 引入极小宿主接口,继续收紧 `CombatScheduler` 本体
|
||||
3. 继续整理 `CombatSettlementContext` 的字段分层
|
||||
4. 补 Unity 编译与手动回归验证
|
||||
|
||||
## 当前做变更时要记住的约束
|
||||
|
||||
- 状态切换只能通过 `CombatScheduler.ChangeState(...)`
|
||||
- 不要把新业务继续堆回 `CombatScheduler.cs`
|
||||
- 不要把新业务继续堆回 `CombatScheduler.cs`,优先考虑:
|
||||
- 状态私有逻辑
|
||||
- `CombatSchedulerFlowCoordinator`
|
||||
- 现有独立服务
|
||||
- `CombatNodeComponent` 现在应该保持轻量 facade,不要再把 coin/baseHp/build snapshot 回流到它
|
||||
- coin/baseHp 变化事件应继续由 `CombatInRunResourceManager` 发布
|
||||
- `Failed` 只处理异常失败,不处理“基地血量归零”的正常失败路径
|
||||
- 状态类应继续只通过 `CombatSchedulerRuntimeContext + CombatSchedulerFlowCoordinator` 访问共享状态与共享流程
|
||||
|
||||
## 当前关键入口文件速查
|
||||
|
||||
- 状态机宿主:
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs`
|
||||
- 共享运行时承载:
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerRuntimeContext.cs`
|
||||
- 共享流程协调:
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs`
|
||||
- 局内资源真值:
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatInRunResourceManager.cs`
|
||||
- 状态类:
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatStates/`
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/`
|
||||
- 加载服务:
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatLoadSession.cs`
|
||||
- phase runtime:
|
||||
|
|
@ -194,6 +288,10 @@
|
|||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyManager.cs`
|
||||
- CombatNode 入口 facade:
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatNodeComponent.cs`
|
||||
- 结束链共享上下文:
|
||||
- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementContext.cs`
|
||||
- 异常失败返回事件:
|
||||
- `Assets/GameMain/Scripts/Event/Combat/CombatFailureReturnEventArgs.cs`
|
||||
|
||||
## 备注
|
||||
|
||||
|
|
|
|||
|
|
@ -65,8 +65,12 @@
|
|||
- `CombatFailedState`
|
||||
|
||||
实现约束:
|
||||
- 上述状态类作为 `CombatScheduler` 的嵌套类实现。
|
||||
- 共享数据与共享服务统一放在 `CombatScheduler` 上。
|
||||
- 上述状态类可以作为 `CombatScheduler` 的嵌套类实现,也可以拆成独立文件;但必须只服务于 `CombatScheduler` 状态机,不形成独立业务边界。
|
||||
- 共享数据与共享服务统一收口到 `CombatScheduler` 内部持有的运行时承载体,不允许散落在各状态类中。
|
||||
- 若 `CombatScheduler` 体量过大,允许在其内部实现中继续拆出:
|
||||
- `CombatSchedulerRuntimeContext`:承载共享运行时字段与共享服务引用
|
||||
- `CombatSchedulerFlowCoordinator`:承载多个状态共用的流程辅助方法
|
||||
- 上述拆分只属于 `CombatScheduler` 的内部实现细化,不改变 `CombatScheduler` 作为唯一状态机边界的职责。
|
||||
- 所有状态切换只能通过 `CombatScheduler.ChangeState(...)` 完成。
|
||||
- 状态类不能彼此直接操控。
|
||||
|
||||
|
|
@ -223,6 +227,19 @@
|
|||
- 跟踪加载成功/失败状态。
|
||||
- 对外提供 `CurrentMap` 与 `IsReady`。
|
||||
|
||||
### 4.1.x CombatSchedulerRuntimeContext / CombatSchedulerFlowCoordinator(实现细化)
|
||||
|
||||
当前实现允许:
|
||||
- 用 `CombatSchedulerRuntimeContext` 承载所有状态共享的运行时字段与共享服务引用。
|
||||
- 用 `CombatSchedulerFlowCoordinator` 承载多个状态共用的流程辅助逻辑。
|
||||
|
||||
约束:
|
||||
- 两者都必须由 `CombatScheduler` 持有并统一管理生命周期。
|
||||
- 两者都不替代 `CombatScheduler` 对外暴露状态机边界。
|
||||
- `RuntimeContext` 不负责状态切换。
|
||||
- `FlowCoordinator` 不持有独立业务真值,只能围绕共享运行时做编排辅助。
|
||||
- 状态类只允许通过 `RuntimeContext + FlowCoordinator` 访问共享状态与共享流程,不应再直接耦合其他状态实现细节。
|
||||
|
||||
### 4.2 PhaseLoopRuntime
|
||||
|
||||
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseLoopRuntime.cs`
|
||||
|
|
|
|||
Loading…
Reference in New Issue