using System; using System.Collections.Generic; using GeometryTD.DataTable; using GeometryTD.Definition; using GeometryTD.UI; using UnityEngine; using UnityGameFramework.Runtime; namespace GeometryTD.CustomComponent { internal sealed class CombatSettlementFlowService { 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 MidBaseHpThreshold = 0.5f; private const float LowBaseHpTowerEndurancePenalty = 10f; public CombatSettlementContext BuildSettlementContext( bool isVictory, DRLevel currentLevel, int defeatedEnemyCount, CombatInRunResourceManager resourceManager) { bool shouldOpenFullBaseHpRewardSelect = false; ResolveSettlementByBaseHp( isVictory, currentLevel, resourceManager, out int currentBaseHp, out int maxBaseHp, out int levelRewardGold, out float bonusRate, out int bonusGold, out bool appliedLowBaseHpPenalty, out shouldOpenFullBaseHpRewardSelect); CombatSettlementContext settlementContext = new CombatSettlementContext { }; 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.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}, BaseHp={1}/{2}, LevelReward={3}, BonusRate={4:P0}, BonusGold={5}, FullHpRewardSelect={6}, LowHpPenalty={7}.", currentLevel != null ? currentLevel.Id : 0, currentBaseHp, maxBaseHp, levelRewardGold, bonusRate, bonusGold, shouldOpenFullBaseHpRewardSelect, appliedLowBaseHpPenalty); return settlementContext; } public void CommitSettlementInventory(CombatSettlementContext settlementContext) { if (settlementContext == null || settlementContext.Flow.IsCommitted) { return; } BackpackInventoryData rewardInventory = settlementContext.Result.RewardInventory ?? new BackpackInventoryData(); GameEntry.PlayerInventory?.MergeInventory(rewardInventory); settlementContext.Result.RewardInventory = rewardInventory; settlementContext.Summary.RewardInventory = rewardInventory; settlementContext.Result.Penalty.AffectedTowerCount = ApplyDeferredSettlementPenalty(settlementContext); settlementContext.Flow.IsCommitted = true; } public bool TryPrepareRewardSelection( CombatSettlementContext settlementContext, CombatInRunResourceManager resourceManager, int displayPhaseIndex, LevelThemeType themeType, RewardSelectFormUseCase rewardSelectFormUseCase, Action onRewardSelected, Action onGiveUp) { if (settlementContext == null || resourceManager == null || rewardSelectFormUseCase == null) { return false; } IReadOnlyList candidateItems = resourceManager.RollSettlementRewardCandidates( displayPhaseIndex, themeType, RewardSelectDisplayCount); if (candidateItems == null || candidateItems.Count <= 0) { settlementContext.Flow.ShouldOpenRewardSelection = false; return false; } List rewardPool = new List(candidateItems.Count); for (int i = 0; i < candidateItems.Count; i++) { TowerCompItemData item = candidateItems[i]; if (item == null) { continue; } rewardPool.Add(BuildRewardSelectRawData(item)); } if (rewardPool.Count <= 0) { settlementContext.Flow.ShouldOpenRewardSelection = false; return false; } rewardSelectFormUseCase.SetCallbacks(onRewardSelected, onGiveUp); rewardSelectFormUseCase.ConfigureRewardPool( rewardPool, displayCount: RewardSelectDisplayCount, refreshCost: 0, allowRefreshOnce: false, allowGiveUp: false, tipText: "基地满血奖励:请选择 1 个组件"); RewardSelectFormRawData rawData = rewardSelectFormUseCase.CreateInitialModel(); if (rawData == null || rawData.RewardItems == null || rawData.RewardItems.Length <= 0) { settlementContext.Flow.ShouldOpenRewardSelection = false; return false; } settlementContext.Flow.DidEnterRewardSelection = true; GameEntry.UIRouter.OpenUI(UIFormType.RewardSelectForm, rawData); return 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; } public void OpenCombatFinishForm( CombatSettlementContext settlementContext, CombatFinishFormUseCase combatFinishFormUseCase) { if (settlementContext == null || combatFinishFormUseCase == null) { return; } combatFinishFormUseCase.SetSummary(settlementContext); GameEntry.UIRouter.OpenUI(UIFormType.CombatFinishForm); } private static void ResolveSettlementByBaseHp( bool isVictory, DRLevel currentLevel, CombatInRunResourceManager resourceManager, out int currentBaseHp, out int maxBaseHp, out int levelRewardGold, out float bonusRate, out int bonusGold, out bool appliedLowBaseHpPenalty, out bool shouldOpenFullBaseHpRewardSelect) { currentBaseHp = Mathf.Max(0, resourceManager != null ? resourceManager.CurrentBaseHp : 0); maxBaseHp = resourceManager != null ? Mathf.Max(0, resourceManager.MaxBaseHp) : 0; if (maxBaseHp > 0) { currentBaseHp = Mathf.Clamp(currentBaseHp, 0, maxBaseHp); } levelRewardGold = currentLevel != null ? Mathf.Max(0, currentLevel.RewardGold) : 0; bonusRate = 0f; bonusGold = 0; appliedLowBaseHpPenalty = false; shouldOpenFullBaseHpRewardSelect = false; if (!isVictory || resourceManager == 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; } else if (hpRate < MidBaseHpThreshold) { appliedLowBaseHpPenalty = true; } } int goldForBonusCalculation = Mathf.Max(0, resourceManager.GainedGold) + levelRewardGold; bonusGold = bonusRate > 0f ? Mathf.FloorToInt(goldForBonusCalculation * bonusRate) : 0; int settlementGold = levelRewardGold + bonusGold; resourceManager.AddSettlementGold(settlementGold); } private static int ApplyDeferredSettlementPenalty(CombatSettlementContext settlementContext) { if (settlementContext == null || !settlementContext.Result.Penalty.ShouldApplyLowBaseHpPenalty || settlementContext.Result.Penalty.LowBaseHpEndurancePenaltyValue <= 0f) { return 0; } PlayerInventoryComponent inventory = GameEntry.PlayerInventory; if (inventory == null) { return 0; } return inventory.ReduceAllTowerEndurance(settlementContext.Result.Penalty.LowBaseHpEndurancePenaltyValue); } 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 new RewardSelectItemRawData { RewardId = item.InstanceId, SlotType = item.SlotType, Title = item.Name, TypeText = BuildRewardTypeText(item.SlotType), Description = BuildRewardDescription(item), Rarity = item.Rarity, Tags = item.Tags != null ? (TagType[])item.Tags.Clone() : Array.Empty(), Icon = null, IsSelectable = true, SourceItem = item }; } private static string BuildRewardTypeText(TowerCompSlotType slotType) { return slotType switch { TowerCompSlotType.Muzzle => "Muzzle Component", TowerCompSlotType.Bearing => "Bearing Component", TowerCompSlotType.Base => "Base Component", TowerCompSlotType.Accessory => "Accessory", _ => "Component" }; } private static string BuildRewardDescription(TowerCompItemData item) { if (item is MuzzleCompItemData muzzle) { int damage = muzzle.AttackDamage != null && muzzle.AttackDamage.Length > 0 ? muzzle.AttackDamage[0] : 0; return $"Damage: {damage}, Spread: {muzzle.DamageRandomRate:P0}"; } if (item is BearingCompItemData bearing) { float range = bearing.AttackRange != null && bearing.AttackRange.Length > 0 ? bearing.AttackRange[0] : 0f; float rotateSpeed = bearing.RotateSpeed != null && bearing.RotateSpeed.Length > 0 ? bearing.RotateSpeed[0] : 0f; return $"Range: {range:0.##}, Rotate Speed: {rotateSpeed:0.##}"; } if (item is BaseCompItemData baseComp) { float attackSpeed = baseComp.AttackSpeed != null && baseComp.AttackSpeed.Length > 0 ? baseComp.AttackSpeed[0] : 0f; return $"Attack Speed: {attackSpeed:0.##}, Property: {baseComp.AttackPropertyType}"; } return string.Empty; } } }