From 3ad7d04b474ac1301dd31be574fcc3c5abe817b4 Mon Sep 17 00:00:00 2001 From: SepComet <2428390463@qq.com> Date: Sat, 7 Mar 2026 20:00:39 +0800 Subject: [PATCH] =?UTF-8?q?MapData=20+=20Event=20=E8=A7=A3=E8=80=A6?= =?UTF-8?q?=E5=B7=B2=E5=AE=8C=E6=88=90=E4=B8=80=E8=BD=AE=E6=94=B6=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `MapData` 已收口为纯初始化快照,不再承载 coin 写接口委托 - 已新增 `MapEntityLoadContext` - 用于把 `MapData` 快照与 coin 命令通道拆开传给地图加载 - `CombatLoadingState` 现在会组装: - `MapData` - `MapEntityLoadContext` - `CombatLoadSession` / `EntityExtension.ShowMap(...)` 已切到 `MapEntityLoadContext` - `MapEntity` 当前通过: - `MapEntityLoadContext` 获取初始快照与 coin 命令通道 - `CombatCoinChangedEventArgs` 同步后续 coin 变化 - 已新增 `MapCombatRuntimeBridge` - 收口地图侧 coin 当前值、命令调用与事件订阅 - `MapEntity` 不再自己维护 `_currentCoin` 和 coin 事件订阅样板 --- .../CombatScheduler/CombatLoadSession.cs | 28 ++--- .../CombatScheduler/CombatScheduler.cs | 7 +- .../CombatSchedulerFlowCoordinator.cs | 38 ++++-- .../CombatSettlementContext.cs | 31 ++++- .../CombatSettlementFlowService.cs | 63 +++++----- .../CombatStates/CombatFinishFormState.cs | 2 +- .../CombatStates/CombatLoadingState.cs | 4 +- .../CombatRewardSelectionState.cs | 2 +- .../CombatStates/CombatRunningPhaseState.cs | 2 +- .../CombatStates/CombatSettlementState.cs | 6 +- .../CombatWaitingForPhaseEndState.cs | 4 +- .../CombatScheduler/ICombatSchedulerHost.cs | 21 ++++ .../ICombatSchedulerHost.cs.meta | 11 ++ .../Combat/UseCase/CombatFinishFormUseCase.cs | 14 +-- docs/CodeX-TODO.md | 112 +++++++++++------- 15 files changed, 224 insertions(+), 121 deletions(-) create mode 100644 Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/ICombatSchedulerHost.cs create mode 100644 Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/ICombatSchedulerHost.cs.meta diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatLoadSession.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatLoadSession.cs index 100b76f..f6d2cb6 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatLoadSession.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatLoadSession.cs @@ -45,7 +45,7 @@ namespace GeometryTD.CustomComponent _currentMap = null; } - public bool StartLoading(DRLevel level, MapEntityLoadContext mapLoadContext, CombatScheduler scheduler, out string errorMessage) + public bool StartLoading(DRLevel level, MapEntityLoadContext mapLoadContext, ICombatSchedulerHost schedulerHost, out string errorMessage) { errorMessage = null; if (_entity == null) @@ -59,7 +59,7 @@ namespace GeometryTD.CustomComponent return false; } - if (!TryOpenCombatInfoForm(scheduler, out errorMessage)) + if (!TryOpenCombatInfoForm(schedulerHost, out errorMessage)) { return false; } @@ -253,7 +253,7 @@ namespace GeometryTD.CustomComponent return true; } - private bool TryOpenCombatInfoForm(CombatScheduler scheduler, out string errorMessage) + private bool TryOpenCombatInfoForm(ICombatSchedulerHost schedulerHost, out string errorMessage) { errorMessage = null; if (_combatInfoFormUseCase == null) @@ -263,9 +263,9 @@ namespace GeometryTD.CustomComponent } _combatInfoFormUseCase.Configure( - () => BuildCombatInfoFormRawData(scheduler), - () => scheduler != null && scheduler.CanEndCombat && scheduler.TryEndCombatByPlayer(), - () => scheduler != null && scheduler.TryDebugFail("Manual debug fail from CombatInfoForm.")); + () => BuildCombatInfoFormRawData(schedulerHost), + () => schedulerHost != null && schedulerHost.CanEndCombat && schedulerHost.TryEndCombatByPlayer(), + () => schedulerHost != null && schedulerHost.TryDebugFail("Manual debug fail from CombatInfoForm.")); int? serialId = GameEntry.UIRouter.OpenUI(UIFormType.CombatInfoForm); if (!serialId.HasValue) @@ -279,26 +279,26 @@ namespace GeometryTD.CustomComponent return true; } - private static CombatInfoFormRawData BuildCombatInfoFormRawData(CombatScheduler scheduler) + private static CombatInfoFormRawData BuildCombatInfoFormRawData(ICombatSchedulerHost schedulerHost) { - if (scheduler == null) + if (schedulerHost == null) { return null; } - DRLevel level = scheduler.CurrentLevel; + DRLevel level = schedulerHost.CurrentLevel; LevelThemeType themeType = level != null ? level.LevelThemeType : LevelThemeType.None; int levelId = level != null ? level.Id : 0; int baseHpMax = level != null ? Mathf.Max(0, level.BaseHp) : 0; return CombatInfoFormUseCase.BuildRawData( themeType, levelId, - scheduler.DisplayPhaseIndex, - scheduler.PhaseCount, - scheduler.CurrentCoin, - scheduler.CurrentBaseHp, + schedulerHost.DisplayPhaseIndex, + schedulerHost.PhaseCount, + schedulerHost.CurrentCoin, + schedulerHost.CurrentBaseHp, baseHpMax, - scheduler.CanEndCombat); + schedulerHost.CanEndCombat); } private void CloseCombatInfoForm() diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs index 86f3322..988b8b9 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs @@ -9,7 +9,7 @@ using UnityGameFramework.Runtime; namespace GeometryTD.CustomComponent { - public partial class CombatScheduler + public partial class CombatScheduler : ICombatSchedulerHost { private readonly CombatSchedulerRuntimeContext _context = new(); private readonly CombatSchedulerFlowCoordinator _flowCoordinator; @@ -237,6 +237,11 @@ namespace GeometryTD.CustomComponent return _context.CurrentState is CombatFailedState; } + void ICombatSchedulerHost.ChangeState(CombatStateBase nextState) + { + ChangeState(nextState); + } + internal void ChangeState(CombatStateBase nextState) { if (ReferenceEquals(_context.CurrentState, nextState)) diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs index d39a0bb..debc069 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs @@ -10,17 +10,21 @@ namespace GeometryTD.CustomComponent { internal sealed class CombatSchedulerFlowCoordinator { - private readonly CombatScheduler _scheduler; + private readonly ICombatSchedulerHost _schedulerHost; private readonly CombatSchedulerRuntimeContext _context; + public ICombatSchedulerHost Host => _schedulerHost; - public CombatScheduler Scheduler => _scheduler; - - public CombatSchedulerFlowCoordinator(CombatScheduler scheduler, CombatSchedulerRuntimeContext context) + public CombatSchedulerFlowCoordinator(ICombatSchedulerHost schedulerHost, CombatSchedulerRuntimeContext context) { - _scheduler = scheduler; + _schedulerHost = schedulerHost; _context = context; } + public void ChangeState(CombatStateBase nextState) + { + _schedulerHost.ChangeState(nextState); + } + public int ResolveEnemyHpRateMultiplier(int displayPhaseIndex, int phaseCount) { if (displayPhaseIndex <= 0 || phaseCount <= 0) @@ -60,7 +64,7 @@ namespace GeometryTD.CustomComponent public void EnsureCombatFinishFormUseCaseBound() { - _context.CombatFinishFormUseCase ??= new CombatFinishFormUseCase(_scheduler); + _context.CombatFinishFormUseCase ??= new CombatFinishFormUseCase(_schedulerHost); GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatFinishForm, _context.CombatFinishFormUseCase); } @@ -88,18 +92,18 @@ namespace GeometryTD.CustomComponent { if (!_context.PhaseLoopRuntime.TryEnterNextPhase(out DRLevelPhase nextPhase)) { - _scheduler.ChangeState(new CombatSettlementState(_context, this, "Combat ended after loop completion.", true)); + _schedulerHost.ChangeState(new CombatSettlementState(_context, this, "Combat ended after loop completion.", true)); return false; } _context.SpawnEntriesByPhaseId.TryGetValue(nextPhase.Id, out IReadOnlyList spawnEntries); - _scheduler.ChangeState(new CombatRunningPhaseState(_context, this, nextPhase, spawnEntries)); + _schedulerHost.ChangeState(new CombatRunningPhaseState(_context, this, nextPhase, spawnEntries)); return true; } public void EnterWaitingForPhaseEnd() { - _scheduler.ChangeState(new CombatWaitingForPhaseEndState(_context, this)); + _schedulerHost.ChangeState(new CombatWaitingForPhaseEndState(_context, this)); } public void CompleteCurrentPhase() @@ -143,7 +147,7 @@ namespace GeometryTD.CustomComponent } _context.SettlementFlowService.ApplySelectedReward(_context.SettlementContext, selectedReward); - _scheduler.ChangeState(new CombatFinishFormState(_context, this)); + _schedulerHost.ChangeState(new CombatFinishFormState(_context, this)); } public void OnFullBaseHpRewardGiveUp() @@ -153,7 +157,7 @@ namespace GeometryTD.CustomComponent return; } - _scheduler.ChangeState(new CombatFinishFormState(_context, this)); + _schedulerHost.ChangeState(new CombatFinishFormState(_context, this)); } public LevelThemeType ResolveCurrentThemeType() @@ -176,6 +180,16 @@ namespace GeometryTD.CustomComponent return _context.CombatInRunResourceManager.ApplyBaseDamage(damage); } + public bool TryConsumeCoin(int coin) + { + return _schedulerHost.TryConsumeCoin(coin); + } + + public void AddCoin(int coin) + { + _schedulerHost.AddCoin(coin); + } + public int GetCurrentBaseHp() { return Mathf.Max(0, _context.CombatInRunResourceManager.CurrentBaseHp); @@ -231,7 +245,7 @@ namespace GeometryTD.CustomComponent return; } - _scheduler.ChangeState(new CombatFailedState(_context, this, errorMessage)); + _schedulerHost.ChangeState(new CombatFailedState(_context, this, errorMessage)); } public void OnCombatFailureDialogConfirmed(object userData) diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementContext.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementContext.cs index 286f7bf..277eabf 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementContext.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementContext.cs @@ -3,6 +3,20 @@ using GeometryTD.Definition; namespace GeometryTD.CustomComponent { internal sealed class CombatSettlementContext + { + public CombatSettlementFlowState Flow { get; } = new(); + public CombatSettlementResult Result { get; } = new(); + public CombatSettlementSummary Summary { get; } = new(); + } + + internal sealed class CombatSettlementFlowState + { + public bool ShouldOpenRewardSelection; + public bool DidEnterRewardSelection; + public bool IsCommitted; + } + + internal sealed class CombatSettlementResult { public bool IsVictory; public int FinalCoin; @@ -11,12 +25,21 @@ namespace GeometryTD.CustomComponent public int DefeatedEnemyCount; public int GainedGold; public BackpackInventoryData RewardInventory; - public bool ShouldOpenRewardSelection; - public bool DidEnterRewardSelection; + public string Reason; + public CombatSettlementPenaltyResult Penalty { get; } = new(); + } + + internal sealed class CombatSettlementPenaltyResult + { public bool ShouldApplyLowBaseHpPenalty; public float LowBaseHpEndurancePenaltyValue; public int AffectedTowerCount; - public bool IsCommitted; - public string Reason; + } + + internal sealed class CombatSettlementSummary + { + public int DefeatedEnemyCount; + public int GainedGold; + public BackpackInventoryData RewardInventory; } } diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementFlowService.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementFlowService.cs index f1a1a46..11326eb 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementFlowService.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementFlowService.cs @@ -39,22 +39,25 @@ namespace GeometryTD.CustomComponent CombatSettlementContext settlementContext = new CombatSettlementContext { - IsVictory = isVictory, - FinalCoin = Mathf.Max(0, resourceManager != null ? resourceManager.CurrentCoin : 0), - FinalBaseHp = currentBaseHp, - MaxBaseHp = maxBaseHp, - DefeatedEnemyCount = Mathf.Max(0, defeatedEnemyCount), - GainedGold = Mathf.Max(0, resourceManager != null ? resourceManager.GainedGold : 0), - RewardInventory = resourceManager != null - ? resourceManager.GetRewardInventorySnapshot() - : new BackpackInventoryData(), - ShouldOpenRewardSelection = shouldOpenFullBaseHpRewardSelect, - DidEnterRewardSelection = false, - ShouldApplyLowBaseHpPenalty = appliedLowBaseHpPenalty, - LowBaseHpEndurancePenaltyValue = appliedLowBaseHpPenalty ? LowBaseHpTowerEndurancePenalty : 0f, - AffectedTowerCount = 0, - Reason = reason }; + settlementContext.Result.IsVictory = isVictory; + settlementContext.Result.FinalCoin = Mathf.Max(0, resourceManager != null ? resourceManager.CurrentCoin : 0); + settlementContext.Result.FinalBaseHp = currentBaseHp; + settlementContext.Result.MaxBaseHp = maxBaseHp; + settlementContext.Result.DefeatedEnemyCount = Mathf.Max(0, defeatedEnemyCount); + settlementContext.Result.GainedGold = Mathf.Max(0, resourceManager != null ? resourceManager.GainedGold : 0); + settlementContext.Result.RewardInventory = resourceManager != null + ? resourceManager.GetRewardInventorySnapshot() + : new BackpackInventoryData(); + settlementContext.Result.Reason = reason; + settlementContext.Result.Penalty.ShouldApplyLowBaseHpPenalty = appliedLowBaseHpPenalty; + settlementContext.Result.Penalty.LowBaseHpEndurancePenaltyValue = + appliedLowBaseHpPenalty ? LowBaseHpTowerEndurancePenalty : 0f; + settlementContext.Flow.ShouldOpenRewardSelection = shouldOpenFullBaseHpRewardSelect; + settlementContext.Flow.DidEnterRewardSelection = false; + settlementContext.Summary.DefeatedEnemyCount = settlementContext.Result.DefeatedEnemyCount; + settlementContext.Summary.GainedGold = settlementContext.Result.GainedGold; + settlementContext.Summary.RewardInventory = settlementContext.Result.RewardInventory; Log.Info( "Combat settlement resolved. Level={0}, Reason={1}, BaseHp={2}/{3}, LevelReward={4}, BonusRate={5:P0}, BonusGold={6}, FullHpRewardSelect={7}, LowHpPenalty={8}.", @@ -72,16 +75,17 @@ namespace GeometryTD.CustomComponent public void CommitSettlementInventory(CombatSettlementContext settlementContext) { - if (settlementContext == null || settlementContext.IsCommitted) + if (settlementContext == null || settlementContext.Flow.IsCommitted) { return; } - BackpackInventoryData rewardInventory = settlementContext.RewardInventory ?? new BackpackInventoryData(); + BackpackInventoryData rewardInventory = settlementContext.Result.RewardInventory ?? new BackpackInventoryData(); GameEntry.PlayerInventory?.MergeInventory(rewardInventory); - settlementContext.RewardInventory = rewardInventory; - settlementContext.AffectedTowerCount = ApplyDeferredSettlementPenalty(settlementContext); - settlementContext.IsCommitted = true; + settlementContext.Result.RewardInventory = rewardInventory; + settlementContext.Summary.RewardInventory = rewardInventory; + settlementContext.Result.Penalty.AffectedTowerCount = ApplyDeferredSettlementPenalty(settlementContext); + settlementContext.Flow.IsCommitted = true; } public bool TryPrepareRewardSelection( @@ -104,7 +108,7 @@ namespace GeometryTD.CustomComponent RewardSelectDisplayCount); if (candidateItems == null || candidateItems.Count <= 0) { - settlementContext.ShouldOpenRewardSelection = false; + settlementContext.Flow.ShouldOpenRewardSelection = false; return false; } @@ -122,7 +126,7 @@ namespace GeometryTD.CustomComponent if (rewardPool.Count <= 0) { - settlementContext.ShouldOpenRewardSelection = false; + settlementContext.Flow.ShouldOpenRewardSelection = false; return false; } @@ -138,23 +142,24 @@ namespace GeometryTD.CustomComponent RewardSelectFormRawData rawData = rewardSelectFormUseCase.CreateInitialModel(); if (rawData == null || rawData.RewardItems == null || rawData.RewardItems.Length <= 0) { - settlementContext.ShouldOpenRewardSelection = false; + settlementContext.Flow.ShouldOpenRewardSelection = false; return false; } - settlementContext.DidEnterRewardSelection = true; + settlementContext.Flow.DidEnterRewardSelection = true; GameEntry.UIRouter.OpenUI(UIFormType.RewardSelectForm, rawData); return true; } public void ApplySelectedReward(CombatSettlementContext settlementContext, RewardSelectItemRawData selectedReward) { - if (settlementContext?.RewardInventory == null || selectedReward?.SourceItem == null) + if (settlementContext?.Result.RewardInventory == null || selectedReward?.SourceItem == null) { return; } - TryAppendRewardComponent(settlementContext.RewardInventory, selectedReward.SourceItem); + TryAppendRewardComponent(settlementContext.Result.RewardInventory, selectedReward.SourceItem); + settlementContext.Summary.RewardInventory = settlementContext.Result.RewardInventory; } public void OpenCombatFinishForm( @@ -227,8 +232,8 @@ namespace GeometryTD.CustomComponent private static int ApplyDeferredSettlementPenalty(CombatSettlementContext settlementContext) { if (settlementContext == null || - !settlementContext.ShouldApplyLowBaseHpPenalty || - settlementContext.LowBaseHpEndurancePenaltyValue <= 0f) + !settlementContext.Result.Penalty.ShouldApplyLowBaseHpPenalty || + settlementContext.Result.Penalty.LowBaseHpEndurancePenaltyValue <= 0f) { return 0; } @@ -239,7 +244,7 @@ namespace GeometryTD.CustomComponent return 0; } - return inventory.ReduceAllTowerEndurance(settlementContext.LowBaseHpEndurancePenaltyValue); + return inventory.ReduceAllTowerEndurance(settlementContext.Result.Penalty.LowBaseHpEndurancePenaltyValue); } private static bool TryAppendRewardComponent(BackpackInventoryData rewardInventory, TowerCompItemData selectedItem) diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatFinishFormState.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatFinishFormState.cs index b7dee89..293af57 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatFinishFormState.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatFinishFormState.cs @@ -20,7 +20,7 @@ namespace GeometryTD.CustomComponent Context.SettlementFlowService.OpenCombatFinishForm( Context.SettlementContext, Context.CombatFinishFormUseCase); - Flow.Scheduler.ChangeState(new CombatWaitingForReturnState(Context, Flow)); + Flow.ChangeState(new CombatWaitingForReturnState(Context, Flow)); } } } diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatLoadingState.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatLoadingState.cs index f35e2d7..d4f4e88 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatLoadingState.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatLoadingState.cs @@ -22,7 +22,7 @@ namespace GeometryTD.CustomComponent } MapEntityLoadContext mapLoadContext = BuildMapLoadContext(); - if (!Context.LoadSession.StartLoading(Context.CurrentLevel, mapLoadContext, Flow.Scheduler, out string errorMessage)) + if (!Context.LoadSession.StartLoading(Context.CurrentLevel, mapLoadContext, Flow.Host, out string errorMessage)) { Flow.EnterFailureFallback($"Combat loading failed. {errorMessage}"); } @@ -66,7 +66,7 @@ namespace GeometryTD.CustomComponent participantTowerSnapshot: GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetParticipantTowerSnapshot() : null); - return new MapEntityLoadContext(mapData, Flow.Scheduler.TryConsumeCoin, Flow.Scheduler.AddCoin); + return new MapEntityLoadContext(mapData, Flow.TryConsumeCoin, Flow.AddCoin); } } } diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatRewardSelectionState.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatRewardSelectionState.cs index eeb8578..b866e78 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatRewardSelectionState.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatRewardSelectionState.cs @@ -25,7 +25,7 @@ namespace GeometryTD.CustomComponent Flow.OnFullBaseHpRewardSelected, Flow.OnFullBaseHpRewardGiveUp)) { - Flow.Scheduler.ChangeState(new CombatFinishFormState(Context, Flow)); + Flow.ChangeState(new CombatFinishFormState(Context, Flow)); } } diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatRunningPhaseState.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatRunningPhaseState.cs index f5ba188..af6daf4 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatRunningPhaseState.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatRunningPhaseState.cs @@ -62,7 +62,7 @@ namespace GeometryTD.CustomComponent if (Flow.ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory)) { - Flow.Scheduler.ChangeState(new CombatSettlementState(Context, Flow, reason, isVictory)); + Flow.ChangeState(new CombatSettlementState(Context, Flow, reason, isVictory)); return; } diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatSettlementState.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatSettlementState.cs index 09eff33..1ada035 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatSettlementState.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatSettlementState.cs @@ -26,13 +26,13 @@ namespace GeometryTD.CustomComponent Context.CurrentLevel, Context.EnemyManager.DefeatedEnemyCount, Context.CombatInRunResourceManager); - if (Context.SettlementContext.ShouldOpenRewardSelection) + if (Context.SettlementContext.Flow.ShouldOpenRewardSelection) { - Flow.Scheduler.ChangeState(new CombatRewardSelectionState(Context, Flow)); + Flow.ChangeState(new CombatRewardSelectionState(Context, Flow)); return; } - Flow.Scheduler.ChangeState(new CombatFinishFormState(Context, Flow)); + Flow.ChangeState(new CombatFinishFormState(Context, Flow)); } } } diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatWaitingForPhaseEndState.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatWaitingForPhaseEndState.cs index f4a1978..27a52aa 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatWaitingForPhaseEndState.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatWaitingForPhaseEndState.cs @@ -24,7 +24,7 @@ namespace GeometryTD.CustomComponent if (Flow.ShouldEnterSettlementFromActiveState(out string reason, out bool isVictory)) { - Flow.Scheduler.ChangeState(new CombatSettlementState(Context, Flow, reason, isVictory)); + Flow.ChangeState(new CombatSettlementState(Context, Flow, reason, isVictory)); return; } @@ -43,4 +43,4 @@ namespace GeometryTD.CustomComponent Flow.CompleteCurrentPhase(); } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/ICombatSchedulerHost.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/ICombatSchedulerHost.cs new file mode 100644 index 0000000..ffefcbc --- /dev/null +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/ICombatSchedulerHost.cs @@ -0,0 +1,21 @@ +using GeometryTD.DataTable; + +namespace GeometryTD.CustomComponent +{ + internal interface ICombatSchedulerHost + { + DRLevel CurrentLevel { get; } + int DisplayPhaseIndex { get; } + int PhaseCount { get; } + int CurrentCoin { get; } + int CurrentBaseHp { get; } + bool CanEndCombat { get; } + + void ChangeState(CombatStateBase nextState); + bool TryConsumeCoin(int coin); + void AddCoin(int coin); + bool TryEndCombatByPlayer(); + bool TryDebugFail(string errorMessage); + bool OnCombatFinishReturnRequested(); + } +} diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/ICombatSchedulerHost.cs.meta b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/ICombatSchedulerHost.cs.meta new file mode 100644 index 0000000..76cd7dc --- /dev/null +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/ICombatSchedulerHost.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ffb072614ac46464a89a01c0a2bade49 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/UI/Combat/UseCase/CombatFinishFormUseCase.cs b/Assets/GameMain/Scripts/UI/Combat/UseCase/CombatFinishFormUseCase.cs index 22c81fc..16ace9b 100644 --- a/Assets/GameMain/Scripts/UI/Combat/UseCase/CombatFinishFormUseCase.cs +++ b/Assets/GameMain/Scripts/UI/Combat/UseCase/CombatFinishFormUseCase.cs @@ -7,7 +7,7 @@ namespace GeometryTD.UI { public class CombatFinishFormUseCase : IUIUseCase { - private CombatScheduler _combatScheduler; + private ICombatSchedulerHost _combatSchedulerHost; private CombatSettlementContext _settlementContext; private bool _isSummaryPrepared; @@ -27,14 +27,14 @@ namespace GeometryTD.UI _isSummaryPrepared = true; } - public CombatFinishFormUseCase(CombatScheduler combatScheduler) + internal CombatFinishFormUseCase(ICombatSchedulerHost combatSchedulerHost) { - this._combatScheduler = combatScheduler; + _combatSchedulerHost = combatSchedulerHost; } public bool TryReturnToMenu() { - return _combatScheduler.OnCombatFinishReturnRequested(); + return _combatSchedulerHost != null && _combatSchedulerHost.OnCombatFinishReturnRequested(); } private CombatFinishFormRawData BuildModel() @@ -44,7 +44,7 @@ namespace GeometryTD.UI _settlementContext = null; } - BackpackInventoryData rewardInventory = _settlementContext?.RewardInventory; + BackpackInventoryData rewardInventory = _settlementContext?.Summary.RewardInventory; if (rewardInventory == null) { rewardInventory = InventorySeedUtility.CreateSampleInventory(); @@ -52,8 +52,8 @@ namespace GeometryTD.UI return new CombatFinishFormRawData { - DefeatedEnemyCount = Mathf.Max(0, _settlementContext?.DefeatedEnemyCount ?? 0), - GainedGold = Mathf.Max(0, _settlementContext?.GainedGold ?? 0), + DefeatedEnemyCount = Mathf.Max(0, _settlementContext?.Summary.DefeatedEnemyCount ?? 0), + GainedGold = Mathf.Max(0, _settlementContext?.Summary.GainedGold ?? 0), RewardInventory = rewardInventory, CanReturn = true }; diff --git a/docs/CodeX-TODO.md b/docs/CodeX-TODO.md index ab3cf94..659caef 100644 --- a/docs/CodeX-TODO.md +++ b/docs/CodeX-TODO.md @@ -6,7 +6,7 @@ 按 `docs/CombatNodeArchitecture.md` 继续收敛 `CombatNode` 域职责。当前骨架已经基本到位,后续重点是: - 继续保持 `CombatScheduler` 作为唯一状态机边界,避免把新业务重新堆回本体。 -- 继续完成 `MapData + Event` 解耦收尾,确认 `MapEntity` 不再反查 `CombatNode` 域运行时。 +- 在 `MapData + Event` 已收口的基础上,继续保持 `MapEntity` 不反查 `CombatNode` 域运行时。 - 稳定 `CombatSettlementContext` 的模型边界,避免流程控制字段和展示摘要继续混杂增长。 - 补 Unity 编译、PlayMode 和失败路径回归验证,把这轮结构调整真正跑通。 @@ -195,51 +195,76 @@ - `Assets/GameMain/Scripts/UI/Combat/UseCase/CombatInfoFormUseCase.cs` - `Assets/GameMain/Scripts/Event/Combat/CombatDebugFailEventArgs.cs` +### 10. MapData + Event 解耦已完成一轮收口 +- `MapData` 已收口为纯初始化快照,不再承载 coin 写接口委托 +- 已新增 `MapEntityLoadContext` + - 用于把 `MapData` 快照与 coin 命令通道拆开传给地图加载 +- `CombatLoadingState` 现在会组装: + - `MapData` + - `MapEntityLoadContext` +- `CombatLoadSession` / `EntityExtension.ShowMap(...)` 已切到 `MapEntityLoadContext` +- `MapEntity` 当前通过: + - `MapEntityLoadContext` 获取初始快照与 coin 命令通道 + - `CombatCoinChangedEventArgs` 同步后续 coin 变化 +- 已新增 `MapCombatRuntimeBridge` + - 收口地图侧 coin 当前值、命令调用与事件订阅 +- `MapEntity` 不再自己维护 `_currentCoin` 和 coin 事件订阅样板 + +当前结论: +- 地图侧已经完成“`MapData` 初始快照 + Event 同步 + 独立命令桥接”的接线 +- 当前未发现 `MapEntity` / 地图侧服务对 `CombatNodeComponent` 或 `CombatScheduler` 的运行时反查 + +关键文件: +- `Assets/GameMain/Scripts/Entity/EntityData/MapData.cs` +- `Assets/GameMain/Scripts/Entity/EntityData/MapEntityLoadContext.cs` +- `Assets/GameMain/Scripts/Scene/Map/MapCombatRuntimeBridge.cs` +- `Assets/GameMain/Scripts/Entity/EntityLogic/MapEntity.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatLoadingState.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatLoadSession.cs` +- `Assets/GameMain/Scripts/Entity/EntityExtension.cs` + +### 11. FlowCoordinator 已切到极小宿主接口 +- 已新增 `ICombatSchedulerHost` +- `CombatSchedulerFlowCoordinator` 不再直接依赖 `CombatScheduler` 具体类,而是只依赖: + - 状态切换入口 + - coin 命令转发 + - CombatInfo / FinishForm 所需的只读查询与回调 +- `CombatLoadSession` 与 `CombatFinishFormUseCase` 也已切到 `ICombatSchedulerHost` +- 状态类当前通过 `Flow.ChangeState(...)` 和 `Flow` 上的轻量转发访问宿主,不再持有 `CombatScheduler` 具体类型引用 + +关键文件: +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/ICombatSchedulerHost.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatLoadSession.cs` +- `Assets/GameMain/Scripts/UI/Combat/UseCase/CombatFinishFormUseCase.cs` + +### 12. CombatSettlementContext 已拆成 Flow / Result / Summary 分层 +- `CombatSettlementContext` 当前明确区分: + - `Flow` + - `Result` + - `Summary` +- 流程控制字段现在集中在 `Flow` +- 结算事实与惩罚事实现在集中在 `Result` +- `CombatFinishFormUseCase` 当前只消费 `Summary` +- 奖励选择与延迟提交惩罚已同步切到分层后的上下文访问路径 + +关键文件: +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementContext.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementFlowService.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatSettlementState.cs` +- `Assets/GameMain/Scripts/UI/Combat/UseCase/CombatFinishFormUseCase.cs` + ## 还没完成 -### 1. MapData + Event 解耦还没完全收口 -当前: -- `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. 需要补实际运行验证 +### 1. 需要补实际运行验证 当前改动已覆盖: - 状态机结构 - 结束链/失败链协议 - 手动失败测试入口 -- `CombatSchedulerRuntimeContext + CombatSchedulerFlowCoordinator` -- `CombatSettlementContext` 与延迟提交惩罚 +- `CombatSchedulerRuntimeContext + CombatSchedulerFlowCoordinator + ICombatSchedulerHost` +- `CombatSettlementContext` 分层与延迟提交惩罚 +- `MapData + Event + MapCombatRuntimeBridge` 但仍缺: - Unity 编译验证 @@ -249,10 +274,9 @@ ## 推荐的后续执行顺序 -1. 做 `MapData + Event` 解耦收尾,排查 `MapEntity` 和地图侧事件是否还反查 `CombatNode` -2. 评估是否给 `FlowCoordinator` 引入极小宿主接口,继续收紧 `CombatScheduler` 本体 -3. 继续整理 `CombatSettlementContext` 的字段分层 -4. 补 Unity 编译与手动回归验证 +1. 补 Unity 编译与手动回归验证 +2. 重点验证正常结束链、异常失败链、新开局无残留状态 +3. 若验证中暴露边界问题,再继续做小步收口 ## 当前做变更时要记住的约束