329 lines
13 KiB
C#
329 lines
13 KiB
C#
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<RewardSelectItemRawData> onRewardSelected,
|
|
Action onGiveUp)
|
|
{
|
|
if (settlementContext == null || resourceManager == null || rewardSelectFormUseCase == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
IReadOnlyList<TowerCompItemData> candidateItems = resourceManager.RollSettlementRewardCandidates(
|
|
displayPhaseIndex,
|
|
themeType,
|
|
RewardSelectDisplayCount);
|
|
if (candidateItems == null || candidateItems.Count <= 0)
|
|
{
|
|
settlementContext.Flow.ShouldOpenRewardSelection = false;
|
|
return false;
|
|
}
|
|
|
|
List<RewardSelectItemRawData> rewardPool = new List<RewardSelectItemRawData>(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<TagType>(),
|
|
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;
|
|
}
|
|
}
|
|
}
|