using System; using System.Collections.Generic; using GeometryTD.DataTable; using GeometryTD.Definition; using GeometryTD.UI; using UnityEngine; using UnityGameFramework.Runtime; 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; 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; } 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 bool TryPrepareRewardSelection( CombatSettlementContext settlementContext, EnemyDropResolver enemyDropResolver, int displayPhaseIndex, LevelThemeType themeType, RewardSelectFormUseCase rewardSelectFormUseCase, Action onRewardSelected, Action onGiveUp) { if (settlementContext == null || enemyDropResolver == null || rewardSelectFormUseCase == null) { return false; } IReadOnlyList candidateItems = enemyDropResolver.RollSettlementRewardCandidates( displayPhaseIndex, themeType, RewardSelectDisplayCount); if (candidateItems == null || candidateItems.Count <= 0) { settlementContext.Flags.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.Flags.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.Flags.ShouldOpenRewardSelection = false; return false; } settlementContext.Flags.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 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 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; } } }