From 26aaa945a9cb18469d807105421057a73325e19c Mon Sep 17 00:00:00 2001 From: SepComet <202308010230@stu.csust.edu.cn> Date: Fri, 13 Mar 2026 09:46:59 +0800 Subject: [PATCH] G3 finish --- .../CombatSettlementService.cs | 219 +----------------- .../CombatNode/CombatSettlementCalculator.cs | 141 +++++++++++ .../CombatSettlementCalculator.cs.meta | 11 + .../CombatNode/CombatSettlementCommitter.cs | 83 +++++++ .../CombatSettlementCommitter.cs.meta | 11 + .../UseCase/RewardSelectFormUseCase.cs | 32 +++ docs/CodeX-TODO.md | 59 ++--- docs/CombatNodeArchitecture.md | 28 ++- 8 files changed, 344 insertions(+), 240 deletions(-) create mode 100644 Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCalculator.cs create mode 100644 Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCalculator.cs.meta create mode 100644 Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCommitter.cs create mode 100644 Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCommitter.cs.meta diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementService.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementService.cs index 333ac1e..fffbde2 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementService.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementService.cs @@ -11,10 +11,8 @@ namespace GeometryTD.CustomComponent public sealed class CombatSettlementService { private const int RewardSelectDisplayCount = 3; - private const float FullBaseHpGoldBonusRate = 0.3f; - private const float HighBaseHpGoldBonusRate = 0.1f; - private const float HighBaseHpThreshold = 0.8f; - private const float SettlementTowerEnduranceLoss = 1f; + private readonly CombatSettlementCalculator _calculator = new(); + private readonly CombatSettlementCommitter _committer = new(); public CombatSettlementContext BuildSettlementContext( bool didCombatWin, @@ -22,63 +20,16 @@ namespace GeometryTD.CustomComponent int defeatedEnemyCount, CombatRunResourceStore resourceStore) { - bool shouldOpenFullBaseHpRewardSelect = false; - ResolveSettlementByBaseHp( + return _calculator.BuildSettlementContext( didCombatWin, currentLevel, - resourceStore, - out int currentBaseHp, - out int maxBaseHp, - out int levelRewardGold, - out float bonusRate, - out int bonusGold, - out shouldOpenFullBaseHpRewardSelect); - - CombatSettlementContext settlementContext = new CombatSettlementContext - { - }; - settlementContext.Result.DidCombatWin = didCombatWin; - settlementContext.Result.FinalCoin = Mathf.Max(0, resourceStore != null ? resourceStore.CurrentCoin : 0); - settlementContext.Result.FinalBaseHp = currentBaseHp; - settlementContext.Result.MaxBaseHp = maxBaseHp; - settlementContext.Result.DefeatedEnemyCount = Mathf.Max(0, defeatedEnemyCount); - settlementContext.Result.GainedGold = Mathf.Max(0, resourceStore != null ? resourceStore.GainedGold : 0); - settlementContext.Result.RewardInventory = resourceStore != null - ? resourceStore.GetRewardInventorySnapshot() - : new BackpackInventoryData(); - PopulateEnduranceSettlement(settlementContext, resourceStore); - settlementContext.Flags.ShouldOpenRewardSelection = shouldOpenFullBaseHpRewardSelect; - settlementContext.Flags.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}, BaseHp={1}/{2}, LevelReward={3}, BonusRate={4:P0}, BonusGold={5}, FullHpRewardSelect={6}, EnduranceTargets={7}.", - currentLevel != null ? currentLevel.Id : 0, - currentBaseHp, - maxBaseHp, - levelRewardGold, - bonusRate, - bonusGold, - shouldOpenFullBaseHpRewardSelect, - settlementContext.Result.Endurance.TargetTowerInstanceIds.Count); - return settlementContext; + defeatedEnemyCount, + resourceStore); } public void CommitSettlementInventory(CombatSettlementContext settlementContext) { - if (settlementContext == null || settlementContext.Flags.IsCommitted) - { - return; - } - - BackpackInventoryData rewardInventory = settlementContext.Result.RewardInventory ?? new BackpackInventoryData(); - GameEntry.PlayerInventory?.MergeInventory(rewardInventory); - settlementContext.Result.RewardInventory = rewardInventory; - settlementContext.Summary.RewardInventory = rewardInventory; - settlementContext.Result.Endurance.AffectedTowerCount = ApplyDeferredSettlementEndurance(settlementContext); - settlementContext.Flags.IsCommitted = true; + _committer.CommitSettlementInventory(settlementContext); } public bool TryPrepareRewardSelection( @@ -104,26 +55,9 @@ namespace GeometryTD.CustomComponent return false; } - List rewardPool = new List(candidateItems.Count); - foreach (var item in candidateItems) - { - if (item == null) - { - continue; - } - - rewardPool.Add(BuildRewardSelectRawData(item)); - } - - if (rewardPool.Count <= 0) - { - settlementContext.Flags.ShouldOpenRewardSelection = false; - return false; - } - rewardSelectFormUseCase.SetCallbacks(onRewardSelected, onGiveUp); - rewardSelectFormUseCase.ConfigureRewardPool( - rewardPool, + rewardSelectFormUseCase.ConfigureRewardCandidates( + candidateItems, displayCount: RewardSelectDisplayCount, refreshCost: 0, allowRefreshOnce: false, @@ -144,13 +78,7 @@ namespace GeometryTD.CustomComponent public void ApplySelectedReward(CombatSettlementContext settlementContext, RewardSelectItemRawData selectedReward) { - if (settlementContext?.Result.RewardInventory == null || selectedReward?.SourceItem == null) - { - return; - } - - TryAppendRewardComponent(settlementContext.Result.RewardInventory, selectedReward.SourceItem); - settlementContext.Summary.RewardInventory = settlementContext.Result.RewardInventory; + _committer.ApplySelectedReward(settlementContext, selectedReward); } public void OpenCombatFinishForm( @@ -166,134 +94,5 @@ namespace GeometryTD.CustomComponent GameEntry.UIRouter.OpenUI(UIFormType.CombatFinishForm); } - private static void ResolveSettlementByBaseHp( - bool didCombatWin, - DRLevel currentLevel, - CombatRunResourceStore resourceStore, - out int currentBaseHp, - out int maxBaseHp, - out int levelRewardGold, - out float bonusRate, - out int bonusGold, - out bool shouldOpenFullBaseHpRewardSelect) - { - currentBaseHp = Mathf.Max(0, resourceStore != null ? resourceStore.CurrentBaseHp : 0); - maxBaseHp = resourceStore != null ? Mathf.Max(0, resourceStore.MaxBaseHp) : 0; - if (maxBaseHp > 0) - { - currentBaseHp = Mathf.Clamp(currentBaseHp, 0, maxBaseHp); - } - - levelRewardGold = currentLevel != null ? Mathf.Max(0, currentLevel.RewardGold) : 0; - bonusRate = 0f; - bonusGold = 0; - shouldOpenFullBaseHpRewardSelect = false; - - if (!didCombatWin || resourceStore == null) - { - return; - } - - if (maxBaseHp > 0 && currentBaseHp >= maxBaseHp) - { - bonusRate = FullBaseHpGoldBonusRate; - shouldOpenFullBaseHpRewardSelect = true; - } - else if (maxBaseHp > 0) - { - float hpRate = (float)currentBaseHp / maxBaseHp; - if (hpRate >= HighBaseHpThreshold) - { - bonusRate = HighBaseHpGoldBonusRate; - } - } - - int goldForBonusCalculation = Mathf.Max(0, resourceStore.GainedGold) + levelRewardGold; - bonusGold = bonusRate > 0f ? Mathf.FloorToInt(goldForBonusCalculation * bonusRate) : 0; - int settlementGold = levelRewardGold + bonusGold; - resourceStore.AddSettlementGold(settlementGold); - } - - private static void PopulateEnduranceSettlement( - CombatSettlementContext settlementContext, - CombatRunResourceStore resourceStore) - { - if (settlementContext == null) - { - return; - } - - CombatSettlementEnduranceResult enduranceResult = settlementContext.Result.Endurance; - enduranceResult.TargetTowerInstanceIds.Clear(); - enduranceResult.EnduranceLossPerComponent = SettlementTowerEnduranceLoss; - - IReadOnlyList participantTowerIds = resourceStore?.GetParticipantTowerInstanceIdSnapshot(); - if (participantTowerIds == null || participantTowerIds.Count <= 0) - { - return; - } - - for (int i = 0; i < participantTowerIds.Count; i++) - { - long towerId = participantTowerIds[i]; - if (towerId > 0) - { - enduranceResult.TargetTowerInstanceIds.Add(towerId); - } - } - } - - private static int ApplyDeferredSettlementEndurance(CombatSettlementContext settlementContext) - { - if (settlementContext == null || - settlementContext.Result.Endurance.EnduranceLossPerComponent <= 0f || - settlementContext.Result.Endurance.TargetTowerInstanceIds.Count <= 0) - { - return 0; - } - - PlayerInventoryComponent inventory = GameEntry.PlayerInventory; - if (inventory == null) - { - return 0; - } - - return inventory.ReduceTowerEndurance( - settlementContext.Result.Endurance.TargetTowerInstanceIds, - settlementContext.Result.Endurance.EnduranceLossPerComponent); - } - - private static bool TryAppendRewardComponent(BackpackInventoryData rewardInventory, TowerCompItemData selectedItem) - { - if (rewardInventory == null || selectedItem == null) - { - return false; - } - - if (selectedItem is MuzzleCompItemData muzzleComp) - { - rewardInventory.MuzzleComponents.Add(muzzleComp); - return true; - } - - if (selectedItem is BearingCompItemData bearingComp) - { - rewardInventory.BearingComponents.Add(bearingComp); - return true; - } - - if (selectedItem is BaseCompItemData baseComp) - { - rewardInventory.BaseComponents.Add(baseComp); - return true; - } - - return false; - } - - private static RewardSelectItemRawData BuildRewardSelectRawData(TowerCompItemData item) - { - return RewardSelectItemRawDataBuilder.Build(item); - } } } diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCalculator.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCalculator.cs new file mode 100644 index 0000000..0fa0fc3 --- /dev/null +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCalculator.cs @@ -0,0 +1,141 @@ +using System.Collections.Generic; +using GeometryTD.DataTable; +using GeometryTD.Definition; +using UnityEngine; +using UnityGameFramework.Runtime; + +namespace GeometryTD.CustomComponent +{ + public sealed class CombatSettlementCalculator + { + private const float FullBaseHpGoldBonusRate = 0.3f; + private const float HighBaseHpGoldBonusRate = 0.1f; + private const float HighBaseHpThreshold = 0.8f; + private const float SettlementTowerEnduranceLoss = 1f; + + public CombatSettlementContext BuildSettlementContext( + bool didCombatWin, + DRLevel currentLevel, + int defeatedEnemyCount, + CombatRunResourceStore resourceStore) + { + bool shouldOpenFullBaseHpRewardSelect = false; + ResolveSettlementByBaseHp( + didCombatWin, + currentLevel, + resourceStore, + out int currentBaseHp, + out int maxBaseHp, + out int levelRewardGold, + out float bonusRate, + out int bonusGold, + out shouldOpenFullBaseHpRewardSelect); + + CombatSettlementContext settlementContext = new CombatSettlementContext(); + settlementContext.Result.DidCombatWin = didCombatWin; + settlementContext.Result.FinalCoin = Mathf.Max(0, resourceStore != null ? resourceStore.CurrentCoin : 0); + settlementContext.Result.FinalBaseHp = currentBaseHp; + settlementContext.Result.MaxBaseHp = maxBaseHp; + settlementContext.Result.DefeatedEnemyCount = Mathf.Max(0, defeatedEnemyCount); + settlementContext.Result.GainedGold = Mathf.Max(0, resourceStore != null ? resourceStore.GainedGold : 0); + settlementContext.Result.RewardInventory = resourceStore != null + ? resourceStore.GetRewardInventorySnapshot() + : new BackpackInventoryData(); + PopulateEnduranceSettlement(settlementContext, resourceStore); + settlementContext.Flags.ShouldOpenRewardSelection = shouldOpenFullBaseHpRewardSelect; + settlementContext.Flags.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}, BaseHp={1}/{2}, LevelReward={3}, BonusRate={4:P0}, BonusGold={5}, FullHpRewardSelect={6}, EnduranceTargets={7}.", + currentLevel != null ? currentLevel.Id : 0, + currentBaseHp, + maxBaseHp, + levelRewardGold, + bonusRate, + bonusGold, + shouldOpenFullBaseHpRewardSelect, + settlementContext.Result.Endurance.TargetTowerInstanceIds.Count); + return settlementContext; + } + + private static void ResolveSettlementByBaseHp( + bool didCombatWin, + DRLevel currentLevel, + CombatRunResourceStore resourceStore, + out int currentBaseHp, + out int maxBaseHp, + out int levelRewardGold, + out float bonusRate, + out int bonusGold, + out bool shouldOpenFullBaseHpRewardSelect) + { + currentBaseHp = Mathf.Max(0, resourceStore != null ? resourceStore.CurrentBaseHp : 0); + maxBaseHp = resourceStore != null ? Mathf.Max(0, resourceStore.MaxBaseHp) : 0; + if (maxBaseHp > 0) + { + currentBaseHp = Mathf.Clamp(currentBaseHp, 0, maxBaseHp); + } + + levelRewardGold = currentLevel != null ? Mathf.Max(0, currentLevel.RewardGold) : 0; + bonusRate = 0f; + bonusGold = 0; + shouldOpenFullBaseHpRewardSelect = false; + + if (!didCombatWin || resourceStore == null) + { + return; + } + + if (maxBaseHp > 0 && currentBaseHp >= maxBaseHp) + { + bonusRate = FullBaseHpGoldBonusRate; + shouldOpenFullBaseHpRewardSelect = true; + } + else if (maxBaseHp > 0) + { + float hpRate = (float)currentBaseHp / maxBaseHp; + if (hpRate >= HighBaseHpThreshold) + { + bonusRate = HighBaseHpGoldBonusRate; + } + } + + int goldForBonusCalculation = Mathf.Max(0, resourceStore.GainedGold) + levelRewardGold; + bonusGold = bonusRate > 0f ? Mathf.FloorToInt(goldForBonusCalculation * bonusRate) : 0; + int settlementGold = levelRewardGold + bonusGold; + resourceStore.AddSettlementGold(settlementGold); + } + + private static void PopulateEnduranceSettlement( + CombatSettlementContext settlementContext, + CombatRunResourceStore resourceStore) + { + if (settlementContext == null) + { + return; + } + + CombatSettlementEnduranceResult enduranceResult = settlementContext.Result.Endurance; + enduranceResult.TargetTowerInstanceIds.Clear(); + enduranceResult.EnduranceLossPerComponent = SettlementTowerEnduranceLoss; + + IReadOnlyList participantTowerIds = resourceStore?.GetParticipantTowerInstanceIdSnapshot(); + if (participantTowerIds == null || participantTowerIds.Count <= 0) + { + return; + } + + for (int i = 0; i < participantTowerIds.Count; i++) + { + long towerId = participantTowerIds[i]; + if (towerId > 0) + { + enduranceResult.TargetTowerInstanceIds.Add(towerId); + } + } + } + } +} diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCalculator.cs.meta b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCalculator.cs.meta new file mode 100644 index 0000000..0972b86 --- /dev/null +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCalculator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c8aa78b2f9ff4eb4bf83b8c4f652ce24 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCommitter.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCommitter.cs new file mode 100644 index 0000000..f4897c7 --- /dev/null +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCommitter.cs @@ -0,0 +1,83 @@ +using GeometryTD.Definition; +using GeometryTD.UI; +using UnityGameFramework.Runtime; + +namespace GeometryTD.CustomComponent +{ + public sealed class CombatSettlementCommitter + { + public void CommitSettlementInventory(CombatSettlementContext settlementContext) + { + if (settlementContext == null || settlementContext.Flags.IsCommitted) + { + return; + } + + BackpackInventoryData rewardInventory = settlementContext.Result.RewardInventory ?? new BackpackInventoryData(); + GameEntry.PlayerInventory?.MergeInventory(rewardInventory); + settlementContext.Result.RewardInventory = rewardInventory; + settlementContext.Summary.RewardInventory = rewardInventory; + settlementContext.Result.Endurance.AffectedTowerCount = ApplyDeferredSettlementEndurance(settlementContext); + settlementContext.Flags.IsCommitted = true; + } + + public void ApplySelectedReward(CombatSettlementContext settlementContext, RewardSelectItemRawData selectedReward) + { + if (settlementContext?.Result.RewardInventory == null || selectedReward?.SourceItem == null) + { + return; + } + + TryAppendRewardComponent(settlementContext.Result.RewardInventory, selectedReward.SourceItem); + settlementContext.Summary.RewardInventory = settlementContext.Result.RewardInventory; + } + + private static int ApplyDeferredSettlementEndurance(CombatSettlementContext settlementContext) + { + if (settlementContext == null || + settlementContext.Result.Endurance.EnduranceLossPerComponent <= 0f || + settlementContext.Result.Endurance.TargetTowerInstanceIds.Count <= 0) + { + return 0; + } + + PlayerInventoryComponent inventory = GameEntry.PlayerInventory; + if (inventory == null) + { + return 0; + } + + return inventory.ReduceTowerEndurance( + settlementContext.Result.Endurance.TargetTowerInstanceIds, + settlementContext.Result.Endurance.EnduranceLossPerComponent); + } + + private static bool TryAppendRewardComponent(BackpackInventoryData rewardInventory, TowerCompItemData selectedItem) + { + if (rewardInventory == null || selectedItem == null) + { + return false; + } + + if (selectedItem is MuzzleCompItemData muzzleComp) + { + rewardInventory.MuzzleComponents.Add(muzzleComp); + return true; + } + + if (selectedItem is BearingCompItemData bearingComp) + { + rewardInventory.BearingComponents.Add(bearingComp); + return true; + } + + if (selectedItem is BaseCompItemData baseComp) + { + rewardInventory.BaseComponents.Add(baseComp); + return true; + } + + return false; + } + } +} diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCommitter.cs.meta b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCommitter.cs.meta new file mode 100644 index 0000000..1011f03 --- /dev/null +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCommitter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1e4e69cf76f34e5baec16562d765fb85 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/UI/General/UseCase/RewardSelectFormUseCase.cs b/Assets/GameMain/Scripts/UI/General/UseCase/RewardSelectFormUseCase.cs index cdfd8eb..8be124b 100644 --- a/Assets/GameMain/Scripts/UI/General/UseCase/RewardSelectFormUseCase.cs +++ b/Assets/GameMain/Scripts/UI/General/UseCase/RewardSelectFormUseCase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using GeometryTD.Definition; using UnityEngine; using Random = UnityEngine.Random; @@ -51,6 +52,37 @@ namespace GeometryTD.UI _currentModel = null; } + public void ConfigureRewardCandidates( + IReadOnlyList rewardCandidates, + int displayCount = 3, + int refreshCost = 0, + bool allowRefreshOnce = true, + bool allowGiveUp = true, + string tipText = null) + { + _rewardPool.Clear(); + if (rewardCandidates != null) + { + foreach (var item in rewardCandidates) + { + if (item == null) + { + continue; + } + + _rewardPool.Add(RewardSelectItemRawDataBuilder.Build(item)); + } + } + + _displayCount = Mathf.Max(1, displayCount); + _refreshCost = Mathf.Max(0, refreshCost); + _allowRefreshOnce = allowRefreshOnce; + _allowGiveUp = allowGiveUp; + _tipText = string.IsNullOrWhiteSpace(tipText) ? "Select one reward" : tipText; + _hasRefreshed = false; + _currentModel = null; + } + public void SetCallbacks(Action onRewardSelected, Action onGiveUp = null) { _onRewardSelected = onRewardSelected; diff --git a/docs/CodeX-TODO.md b/docs/CodeX-TODO.md index 3e4d2f0..f82824c 100644 --- a/docs/CodeX-TODO.md +++ b/docs/CodeX-TODO.md @@ -1,6 +1,6 @@ # CodeX TODO -最后更新:2026-03-12 +最后更新:2026-03-13 > 目标:把当前“随机组件产出 / 掉落 / 商店 / 3 选 1 奖励候选 / Tag 初始化入口”从分散实现收口成清晰模块。 > 原则:先收运行时入口,再收重复领域逻辑,最后收 UI 编排;不把纯规则层误做成 `GameFrameworkComponent`。 @@ -8,7 +8,7 @@ ## 这次重构要解决的问题 - 商店、敌人掉落、满血 3 选 1 候选都在各自生成组件实例,逻辑分散,后续改规则要改多处。 -- `ShopFormUseCase`、`EnemyDropResolver`、`CombatSettlementService` 都同时承担了“流程 + 规则 + 数据组装”的混合职责。 +- `ShopFormUseCase`、旧 `EnemyDropResolver`、`CombatSettlementService` 都曾同时承担“流程 + 规则 + 数据组装”的混合职责。 - `runSeed` 目前只稳定了 Tag 生成,没有稳定整个组件产出流程。 - Tag 模块本身已经分成 `Generation / Aggregation / Combat / Metadata / Presentation`,但初始化入口还散在流程代码里。 @@ -47,22 +47,22 @@ ### 组件层 -- `Assets/GameMain/Scripts/CustomComponent/InventoryGenerationComponent.cs` -- `Assets/GameMain/Scripts/CustomComponent/TagRegistryComponent.cs` +- `Assets/GameMain/Scripts/CustomComponent/InventoryGeneration/InventoryGenerationComponent.cs` +- `Assets/GameMain/Scripts/CustomComponent/TagRegistry/TagRegistryComponent.cs` ### 领域模块层 -- `Assets/GameMain/Scripts/Definition/InventoryGeneration/ComponentItemFactory.cs` +- `Assets/GameMain/Scripts/Factory/ComponentItemFactory.cs` - 唯一负责“从配置行构造 `TowerCompItemData`”。 -- `Assets/GameMain/Scripts/Definition/InventoryGeneration/ShopGoodsBuilder.cs` +- `Assets/GameMain/Scripts/CustomComponent/InventoryGeneration/ShopGoodsBuilder.cs` - 只负责商店货物生成。 -- `Assets/GameMain/Scripts/Definition/InventoryGeneration/DropPoolRoller.cs` +- `Assets/GameMain/Scripts/CustomComponent/InventoryGeneration/DropPoolRoller.cs` - 只负责掉落池筛选、权重抽样、稀有度曲线。 -- `Assets/GameMain/Scripts/Definition/InventoryGeneration/RewardCandidateBuilder.cs` +- `Assets/GameMain/Scripts/CustomComponent/InventoryGeneration/RewardCandidateBuilder.cs` - 只负责 3 选 1 奖励候选生成。 -- `Assets/GameMain/Scripts/Definition/Combat/Settlement/CombatSettlementCalculator.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCalculator.cs` - 只负责结算计算。 -- `Assets/GameMain/Scripts/Definition/Combat/Settlement/CombatSettlementCommitter.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCommitter.cs` - 只负责把结算结果提交到玩家库存。 ## 现有职责迁移目标 @@ -81,9 +81,9 @@ - `CombatScheduler` - 不再直接持有复杂掉落生成细节。 - 只在敌人死亡时调用 `GameEntry.InventoryGeneration.ResolveEnemyDrop(...)`。 -- `EnemyDropResolver` - - 逐步被拆空或移除。 - - 其内部职责拆给: +- 旧 `EnemyDropResolver` + - 已从主链路移除,并已删除旧实现文件。 + - 其职责已拆给: - `DropPoolRoller` - `ComponentItemFactory` - `RewardCandidateBuilder` @@ -126,25 +126,26 @@ | 状态 | ID | 任务 | 目标 | |-----|-------|----------------------------------------|---------------| -| [ ] | G2-01 | 新增 `DropPoolRoller` | 收口掉落池与稀有度抽样 | -| [ ] | G2-02 | 新增 `RewardCandidateBuilder` | 收口 3 选 1 候选生成 | -| [ ] | G2-03 | 让战斗掉落改走 `InventoryGenerationComponent` | 战斗不再自己构造组件 | -| [ ] | G2-04 | 让满血奖励候选改走统一入口 | 掉落与奖励候选共用产出体系 | +| [x] | G2-01 | 新增 `DropPoolRoller` | 收口掉落池与稀有度抽样 | +| [x] | G2-02 | 新增 `RewardCandidateBuilder` | 收口 3 选 1 候选生成 | +| [x] | G2-03 | 让战斗掉落改走 `InventoryGenerationComponent` | 战斗不再自己构造组件 | +| [x] | G2-04 | 让满血奖励候选改走统一入口 | 掉落与奖励候选共用产出体系 | ### G2 验收标准 - 敌人死亡后仍能正常掉 coin、gold、组件。 - 满血奖励 3 选 1 仍能正常打开并选择。 - 掉落与奖励候选不再重复维护组件实例构造逻辑。 +- `CombatScheduler` 与 `CombatSettlementService` 已改为统一调用 `GameEntry.InventoryGeneration`。 ## 阶段 G3 - 收口结算模块边界 | 状态 | ID | 任务 | 目标 | |-----|-------|---------------------------------|-----------------| -| [ ] | G3-01 | 新增 `CombatSettlementCalculator` | 只保留结算计算 | -| [ ] | G3-02 | 新增 `CombatSettlementCommitter` | 只保留库存提交 | -| [ ] | G3-03 | 精简 `CombatSettlementService` | 只剩流程编排 | -| [ ] | G3-04 | 清理奖励 RawData 重复映射 | 候选生成与 UI 组装边界清晰 | +| [x] | G3-01 | 新增 `CombatSettlementCalculator` | 只保留结算计算 | +| [x] | G3-02 | 新增 `CombatSettlementCommitter` | 只保留库存提交 | +| [x] | G3-03 | 精简 `CombatSettlementService` | 只剩流程编排 | +| [x] | G3-04 | 清理奖励 RawData 重复映射 | 候选生成与 UI 组装边界清晰 | ### G3 验收标准 @@ -185,21 +186,21 @@ ### 第一批直接改动文件 - `Assets/GameMain/Scripts/UI/Shop/UseCase/ShopFormUseCase.cs` -- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/EnemyDrop/EnemyDropResolver.cs` - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSettlementService.cs` - `Assets/GameMain/Scripts/Procedure/Base/ProcedurePreload.cs` - `Assets/GameMain/Scripts/Base/GameEntry.Custom.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs` ### 第二批新增文件 -- `Assets/GameMain/Scripts/CustomComponent/InventoryGenerationComponent.cs` -- `Assets/GameMain/Scripts/CustomComponent/TagRegistryComponent.cs` +- `Assets/GameMain/Scripts/CustomComponent/InventoryGeneration/InventoryGenerationComponent.cs` +- `Assets/GameMain/Scripts/CustomComponent/TagRegistry/TagRegistryComponent.cs` - `Assets/GameMain/Scripts/Factory/ComponentItemFactory.cs` -- `Assets/GameMain/Scripts/CustomComponent/ShopGoodsBuilder.cs` -- `Assets/GameMain/Scripts/CustomComponent/DropPoolRoller.cs` -- `Assets/GameMain/Scripts/CustomComponent/RewardCandidateBuilder.cs` -- `Assets/GameMain/Scripts/Definition/Combat/Settlement/CombatSettlementCalculator.cs` -- `Assets/GameMain/Scripts/Definition/Combat/Settlement/CombatSettlementCommitter.cs` +- `Assets/GameMain/Scripts/CustomComponent/InventoryGeneration/ShopGoodsBuilder.cs` +- `Assets/GameMain/Scripts/CustomComponent/InventoryGeneration/DropPoolRoller.cs` +- `Assets/GameMain/Scripts/CustomComponent/InventoryGeneration/RewardCandidateBuilder.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCalculator.cs` +- `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCommitter.cs` ## 本次重构的限制 diff --git a/docs/CombatNodeArchitecture.md b/docs/CombatNodeArchitecture.md index f2ce6f1..f9d9fc1 100644 --- a/docs/CombatNodeArchitecture.md +++ b/docs/CombatNodeArchitecture.md @@ -219,6 +219,7 @@ - `Manager`:只用于子域 Facade/聚合入口,例如 `EnemyManager`。 - `Coordinator`:只用于跨状态、跨服务的流程编排,不持有独立业务真值。 - `Service`:只用于聚焦业务行为,不承担框架事件桥接或异步句柄跟踪。 +- `Calculator`:只用于纯计算与结果组装,不直接提交状态或驱动 UI。 - `Session`:只用于一次加载/交互过程的生命周期对象。 - `Bridge`:只用于框架边界适配器。 - `Runtime`:只用于运行时可变状态承载。 @@ -316,7 +317,32 @@ - `CombatScheduler` 与结算状态链只调用统一入口,不直接访问掉落池滚动或组件实例构造细节。 - `InventoryGenerationComponent` 负责组件实例生成、Tag 随机上下文以及 `runSeed/sequenceIndex` 相关上下文。 -### 4.6 IPhaseEndCondition +### 4.6 CombatSettlementCalculator + +文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCalculator.cs` + +目标职责: +- 只负责结算计算与 `CombatSettlementContext` 组装。 +- 负责基地血量奖励、奖励选择准入、奖励背包快照与耐久扣减目标计算。 + +约束: +- 不直接并包到玩家库存。 +- 不直接打开 UI。 +- 不承担奖励候选生成。 + +### 4.7 CombatSettlementCommitter + +文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCommitter.cs` + +目标职责: +- 只负责把结算结果提交到玩家库存。 +- 负责结算背包并包与延迟耐久扣减落地。 + +约束: +- 不重新计算结算上下文。 +- 不直接生成奖励候选或打开 UI。 + +### 4.8 IPhaseEndCondition 目标职责: - 作为 `PhaseEndType` 判定接口。