refactor 5:
- CombatSettlementFlowService.cs 负责结算上下文构建、基地血量奖励修正、奖励选择准备、奖励追加、结算背包提交、FinishForm 摘要准备。
- CombatSettlementContext.cs 变成独立共享上下文,不再作为 CombatScheduler 内部私有类。
- 状态链改成各司其职:
- CombatSettlementState.cs 负责结束战斗现场并构建结算上下文。
- CombatRewardSelectionState.cs 只负责进入奖励选择流程。
- CombatFinishFormState.cs 只负责提交结算背包并打开 FinishForm。
- CombatScheduler.cs 删除了大块结算/奖励构建细节,保留状态切换、共享运行时和少量桥接回调。
This commit is contained in:
parent
e488a2ca0f
commit
ccb4738b96
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GeometryTD.CustomEvent;
|
using GeometryTD.CustomEvent;
|
||||||
using GeometryTD.DataTable;
|
using GeometryTD.DataTable;
|
||||||
|
|
@ -12,13 +11,6 @@ namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
public partial class CombatScheduler
|
public partial class CombatScheduler
|
||||||
{
|
{
|
||||||
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;
|
|
||||||
|
|
||||||
private readonly List<DRLevelPhase> _phaseBuffer = new();
|
private readonly List<DRLevelPhase> _phaseBuffer = new();
|
||||||
private readonly Dictionary<int, IReadOnlyList<DRLevelSpawnEntry>> _spawnEntriesByPhaseId = new();
|
private readonly Dictionary<int, IReadOnlyList<DRLevelSpawnEntry>> _spawnEntriesByPhaseId = new();
|
||||||
private readonly EnemyManager _enemyManager = new();
|
private readonly EnemyManager _enemyManager = new();
|
||||||
|
|
@ -27,6 +19,7 @@ namespace GeometryTD.CustomComponent
|
||||||
private readonly CombatEventBridge _eventBridge = new();
|
private readonly CombatEventBridge _eventBridge = new();
|
||||||
private readonly CombatInRunResourceManager _combatInRunResourceManager = new();
|
private readonly CombatInRunResourceManager _combatInRunResourceManager = new();
|
||||||
private readonly EnemyDropResolver _enemyDropResolver = new();
|
private readonly EnemyDropResolver _enemyDropResolver = new();
|
||||||
|
private readonly CombatSettlementFlowService _settlementFlowService = new();
|
||||||
|
|
||||||
private EntityComponent _entity;
|
private EntityComponent _entity;
|
||||||
private DRLevel _currentLevel;
|
private DRLevel _currentLevel;
|
||||||
|
|
@ -37,7 +30,7 @@ namespace GeometryTD.CustomComponent
|
||||||
private bool _isFinishAsVictory = true;
|
private bool _isFinishAsVictory = true;
|
||||||
private bool _isCompleted;
|
private bool _isCompleted;
|
||||||
private bool _nodeEnterFired;
|
private bool _nodeEnterFired;
|
||||||
private SettlementContext _settlementContext;
|
private CombatSettlementContext _settlementContext;
|
||||||
|
|
||||||
public bool IsRunning =>
|
public bool IsRunning =>
|
||||||
_currentState is CombatLoadingState or CombatRunningPhaseState or CombatWaitingForPhaseEndState;
|
_currentState is CombatLoadingState or CombatRunningPhaseState or CombatWaitingForPhaseEndState;
|
||||||
|
|
@ -343,179 +336,6 @@ namespace GeometryTD.CustomComponent
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenCombatFinishForm(SettlementContext settlementContext)
|
|
||||||
{
|
|
||||||
EnsureCombatFinishFormUseCaseBound();
|
|
||||||
_combatFinishFormUseCase.SetSummary(
|
|
||||||
settlementContext.DefeatedEnemyCount,
|
|
||||||
settlementContext.GainedGold,
|
|
||||||
settlementContext.RewardInventory);
|
|
||||||
GameEntry.UIRouter.OpenUI(UIFormType.CombatFinishForm);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SettlementContext BuildSettlementContext(string reason, bool isVictory)
|
|
||||||
{
|
|
||||||
int defeatedEnemyCount = _enemyManager.DefeatedEnemyCount;
|
|
||||||
|
|
||||||
_enemyManager.EndPhase();
|
|
||||||
_enemyManager.CleanupTrackedEnemies();
|
|
||||||
_isFinishAsVictory = isVictory;
|
|
||||||
|
|
||||||
bool shouldOpenFullBaseHpRewardSelect = false;
|
|
||||||
ApplySettlementModifierByBaseHp(isVictory, out shouldOpenFullBaseHpRewardSelect);
|
|
||||||
|
|
||||||
SettlementContext settlementContext = new SettlementContext
|
|
||||||
{
|
|
||||||
DefeatedEnemyCount = Mathf.Max(0, defeatedEnemyCount),
|
|
||||||
GainedGold = Mathf.Max(0, _combatInRunResourceManager.GainedGold),
|
|
||||||
RewardInventory = _combatInRunResourceManager.GetRewardInventorySnapshot(),
|
|
||||||
ShouldOpenRewardSelection = shouldOpenFullBaseHpRewardSelect,
|
|
||||||
Reason = reason
|
|
||||||
};
|
|
||||||
|
|
||||||
Log.Info(
|
|
||||||
"CombatScheduler entered finish flow. Level={0}. Reason={1}",
|
|
||||||
_currentLevel != null ? _currentLevel.Id : 0,
|
|
||||||
reason);
|
|
||||||
|
|
||||||
return settlementContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CommitSettlementInventory(SettlementContext settlementContext)
|
|
||||||
{
|
|
||||||
if (settlementContext == null || settlementContext.IsCommitted)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BackpackInventoryData rewardInventory = settlementContext.RewardInventory ?? new BackpackInventoryData();
|
|
||||||
GameEntry.PlayerInventory?.MergeInventory(rewardInventory);
|
|
||||||
settlementContext.RewardInventory = rewardInventory;
|
|
||||||
settlementContext.IsCommitted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplySettlementModifierByBaseHp(bool isVictory, out bool shouldOpenFullBaseHpRewardSelect)
|
|
||||||
{
|
|
||||||
shouldOpenFullBaseHpRewardSelect = false;
|
|
||||||
if (!isVictory)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int levelRewardGold = _currentLevel != null ? Mathf.Max(0, _currentLevel.RewardGold) : 0;
|
|
||||||
int currentBaseHp;
|
|
||||||
int maxBaseHp;
|
|
||||||
float bonusRate = 0f;
|
|
||||||
bool appliedLowBaseHpPenalty = false;
|
|
||||||
|
|
||||||
ResolveBaseHpSnapshot(out currentBaseHp, out maxBaseHp);
|
|
||||||
if (maxBaseHp > 0 && currentBaseHp >= maxBaseHp)
|
|
||||||
{
|
|
||||||
bonusRate = FullBaseHpGoldBonusRate;
|
|
||||||
shouldOpenFullBaseHpRewardSelect = true;
|
|
||||||
}
|
|
||||||
else if (maxBaseHp > 0)
|
|
||||||
{
|
|
||||||
float hpRate = (float)Mathf.Clamp(currentBaseHp, 0, maxBaseHp) / maxBaseHp;
|
|
||||||
if (hpRate >= HighBaseHpThreshold)
|
|
||||||
{
|
|
||||||
bonusRate = HighBaseHpGoldBonusRate;
|
|
||||||
}
|
|
||||||
else if (hpRate < MidBaseHpThreshold)
|
|
||||||
{
|
|
||||||
appliedLowBaseHpPenalty = ApplyLowBaseHpPenalty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int goldForBonusCalculation = Mathf.Max(0, _combatInRunResourceManager.GainedGold) + levelRewardGold;
|
|
||||||
int bonusGold = bonusRate > 0f ? Mathf.FloorToInt(goldForBonusCalculation * bonusRate) : 0;
|
|
||||||
int settlementGold = levelRewardGold + bonusGold;
|
|
||||||
_combatInRunResourceManager.AddSettlementGold(settlementGold);
|
|
||||||
|
|
||||||
Log.Info(
|
|
||||||
"Combat settlement resolved. BaseHp={0}/{1}, LevelReward={2}, BonusRate={3:P0}, BonusGold={4}, FullHpRewardSelect={5}, LowHpPenalty={6}.",
|
|
||||||
currentBaseHp,
|
|
||||||
maxBaseHp,
|
|
||||||
levelRewardGold,
|
|
||||||
bonusRate,
|
|
||||||
bonusGold,
|
|
||||||
shouldOpenFullBaseHpRewardSelect,
|
|
||||||
appliedLowBaseHpPenalty);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResolveBaseHpSnapshot(out int currentBaseHp, out int maxBaseHp)
|
|
||||||
{
|
|
||||||
currentBaseHp = Mathf.Max(0, GetCurrentBaseHp());
|
|
||||||
maxBaseHp = _combatInRunResourceManager.MaxBaseHp;
|
|
||||||
if (maxBaseHp > 0)
|
|
||||||
{
|
|
||||||
currentBaseHp = Mathf.Clamp(currentBaseHp, 0, maxBaseHp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ApplyLowBaseHpPenalty()
|
|
||||||
{
|
|
||||||
PlayerInventoryComponent inventory = GameEntry.PlayerInventory;
|
|
||||||
if (inventory == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int affectedTowerCount = inventory.ReduceAllTowerEndurance(LowBaseHpTowerEndurancePenalty);
|
|
||||||
return affectedTowerCount > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryPrepareRewardSelection(SettlementContext settlementContext)
|
|
||||||
{
|
|
||||||
IReadOnlyList<TowerCompItemData> candidateItems = _combatInRunResourceManager.RollSettlementRewardCandidates(
|
|
||||||
_phaseLoopRuntime.DisplayPhaseIndex,
|
|
||||||
ResolveCurrentThemeType(),
|
|
||||||
RewardSelectDisplayCount);
|
|
||||||
if (candidateItems == null || candidateItems.Count <= 0)
|
|
||||||
{
|
|
||||||
settlementContext.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.ShouldOpenRewardSelection = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureRewardSelectFormUseCaseBound();
|
|
||||||
_rewardSelectFormUseCase.SetCallbacks(OnFullBaseHpRewardSelected, OnFullBaseHpRewardGiveUp);
|
|
||||||
_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.ShouldOpenRewardSelection = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
GameEntry.UIRouter.OpenUI(UIFormType.RewardSelectForm, rawData);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnFullBaseHpRewardSelected(RewardSelectItemRawData selectedReward)
|
private void OnFullBaseHpRewardSelected(RewardSelectItemRawData selectedReward)
|
||||||
{
|
{
|
||||||
if (_currentState is not CombatRewardSelectionState || _settlementContext == null)
|
if (_currentState is not CombatRewardSelectionState || _settlementContext == null)
|
||||||
|
|
@ -523,11 +343,7 @@ namespace GeometryTD.CustomComponent
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_settlementContext.RewardInventory != null && selectedReward?.SourceItem != null)
|
_settlementFlowService.ApplySelectedReward(_settlementContext, selectedReward);
|
||||||
{
|
|
||||||
TryAppendRewardComponent(_settlementContext.RewardInventory, selectedReward.SourceItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
ChangeState(new CombatFinishFormState(this));
|
ChangeState(new CombatFinishFormState(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -541,87 +357,6 @@ namespace GeometryTD.CustomComponent
|
||||||
ChangeState(new CombatFinishFormState(this));
|
ChangeState(new CombatFinishFormState(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private LevelThemeType ResolveCurrentThemeType()
|
private LevelThemeType ResolveCurrentThemeType()
|
||||||
{
|
{
|
||||||
if (_currentLevel != null)
|
if (_currentLevel != null)
|
||||||
|
|
@ -770,15 +505,5 @@ namespace GeometryTD.CustomComponent
|
||||||
CompleteCombatAndNotify(false);
|
CompleteCombatAndNotify(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class SettlementContext
|
|
||||||
{
|
|
||||||
public int DefeatedEnemyCount;
|
|
||||||
public int GainedGold;
|
|
||||||
public BackpackInventoryData RewardInventory;
|
|
||||||
public bool ShouldOpenRewardSelection;
|
|
||||||
public bool IsCommitted;
|
|
||||||
public string Reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomComponent
|
||||||
|
{
|
||||||
|
internal sealed class CombatSettlementContext
|
||||||
|
{
|
||||||
|
public int DefeatedEnemyCount;
|
||||||
|
public int GainedGold;
|
||||||
|
public BackpackInventoryData RewardInventory;
|
||||||
|
public bool ShouldOpenRewardSelection;
|
||||||
|
public bool IsCommitted;
|
||||||
|
public string Reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f00e1b4791824896b50b52f3c94308f2
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,315 @@
|
||||||
|
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(
|
||||||
|
string reason,
|
||||||
|
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
|
||||||
|
{
|
||||||
|
DefeatedEnemyCount = Mathf.Max(0, defeatedEnemyCount),
|
||||||
|
GainedGold = Mathf.Max(0, resourceManager != null ? resourceManager.GainedGold : 0),
|
||||||
|
RewardInventory = resourceManager != null
|
||||||
|
? resourceManager.GetRewardInventorySnapshot()
|
||||||
|
: new BackpackInventoryData(),
|
||||||
|
ShouldOpenRewardSelection = shouldOpenFullBaseHpRewardSelect,
|
||||||
|
Reason = reason
|
||||||
|
};
|
||||||
|
|
||||||
|
Log.Info(
|
||||||
|
"Combat settlement resolved. Level={0}, Reason={1}, BaseHp={2}/{3}, LevelReward={4}, BonusRate={5:P0}, BonusGold={6}, FullHpRewardSelect={7}, LowHpPenalty={8}.",
|
||||||
|
currentLevel != null ? currentLevel.Id : 0,
|
||||||
|
reason,
|
||||||
|
currentBaseHp,
|
||||||
|
maxBaseHp,
|
||||||
|
levelRewardGold,
|
||||||
|
bonusRate,
|
||||||
|
bonusGold,
|
||||||
|
shouldOpenFullBaseHpRewardSelect,
|
||||||
|
appliedLowBaseHpPenalty);
|
||||||
|
return settlementContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CommitSettlementInventory(CombatSettlementContext settlementContext)
|
||||||
|
{
|
||||||
|
if (settlementContext == null || settlementContext.IsCommitted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BackpackInventoryData rewardInventory = settlementContext.RewardInventory ?? new BackpackInventoryData();
|
||||||
|
GameEntry.PlayerInventory?.MergeInventory(rewardInventory);
|
||||||
|
settlementContext.RewardInventory = rewardInventory;
|
||||||
|
settlementContext.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.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.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.ShouldOpenRewardSelection = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameEntry.UIRouter.OpenUI(UIFormType.RewardSelectForm, rawData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplySelectedReward(CombatSettlementContext settlementContext, RewardSelectItemRawData selectedReward)
|
||||||
|
{
|
||||||
|
if (settlementContext?.RewardInventory == null || selectedReward?.SourceItem == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TryAppendRewardComponent(settlementContext.RewardInventory, selectedReward.SourceItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenCombatFinishForm(
|
||||||
|
CombatSettlementContext settlementContext,
|
||||||
|
CombatInRunResourceManager resourceManager,
|
||||||
|
CombatFinishFormUseCase combatFinishFormUseCase)
|
||||||
|
{
|
||||||
|
if (settlementContext == null || combatFinishFormUseCase == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
settlementContext.GainedGold = Mathf.Max(0, resourceManager != null ? resourceManager.GainedGold : settlementContext.GainedGold);
|
||||||
|
combatFinishFormUseCase.SetSummary(
|
||||||
|
settlementContext.DefeatedEnemyCount,
|
||||||
|
settlementContext.GainedGold,
|
||||||
|
settlementContext.RewardInventory);
|
||||||
|
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 = ApplyLowBaseHpPenalty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 bool ApplyLowBaseHpPenalty()
|
||||||
|
{
|
||||||
|
PlayerInventoryComponent inventory = GameEntry.PlayerInventory;
|
||||||
|
if (inventory == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int affectedTowerCount = inventory.ReduceAllTowerEndurance(LowBaseHpTowerEndurancePenalty);
|
||||||
|
return affectedTowerCount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ac4d5b5da086454e917694f8000fc8fc
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
public partial class CombatScheduler
|
public partial class CombatScheduler
|
||||||
|
|
@ -18,9 +16,12 @@ namespace GeometryTD.CustomComponent
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Scheduler.CommitSettlementInventory(Scheduler._settlementContext);
|
Scheduler._settlementFlowService.CommitSettlementInventory(Scheduler._settlementContext);
|
||||||
Scheduler._settlementContext.GainedGold = Mathf.Max(0, Scheduler._combatInRunResourceManager.GainedGold);
|
Scheduler.EnsureCombatFinishFormUseCaseBound();
|
||||||
Scheduler.OpenCombatFinishForm(Scheduler._settlementContext);
|
Scheduler._settlementFlowService.OpenCombatFinishForm(
|
||||||
|
Scheduler._settlementContext,
|
||||||
|
Scheduler._combatInRunResourceManager,
|
||||||
|
Scheduler._combatFinishFormUseCase);
|
||||||
Scheduler.ChangeState(new CombatWaitingForReturnState(Scheduler));
|
Scheduler.ChangeState(new CombatWaitingForReturnState(Scheduler));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,15 @@ namespace GeometryTD.CustomComponent
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Scheduler.TryPrepareRewardSelection(Scheduler._settlementContext))
|
Scheduler.EnsureRewardSelectFormUseCaseBound();
|
||||||
|
if (!Scheduler._settlementFlowService.TryPrepareRewardSelection(
|
||||||
|
Scheduler._settlementContext,
|
||||||
|
Scheduler._combatInRunResourceManager,
|
||||||
|
Scheduler._phaseLoopRuntime.DisplayPhaseIndex,
|
||||||
|
Scheduler.ResolveCurrentThemeType(),
|
||||||
|
Scheduler._rewardSelectFormUseCase,
|
||||||
|
Scheduler.OnFullBaseHpRewardSelected,
|
||||||
|
Scheduler.OnFullBaseHpRewardGiveUp))
|
||||||
{
|
{
|
||||||
Scheduler.ChangeState(new CombatFinishFormState(Scheduler));
|
Scheduler.ChangeState(new CombatFinishFormState(Scheduler));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,15 @@ namespace GeometryTD.CustomComponent
|
||||||
|
|
||||||
public override void OnEnter()
|
public override void OnEnter()
|
||||||
{
|
{
|
||||||
Scheduler._settlementContext = Scheduler.BuildSettlementContext(_reason, _isVictory);
|
Scheduler._enemyManager.EndPhase();
|
||||||
|
Scheduler._enemyManager.CleanupTrackedEnemies();
|
||||||
|
Scheduler._isFinishAsVictory = _isVictory;
|
||||||
|
Scheduler._settlementContext = Scheduler._settlementFlowService.BuildSettlementContext(
|
||||||
|
_reason,
|
||||||
|
_isVictory,
|
||||||
|
Scheduler._currentLevel,
|
||||||
|
Scheduler._enemyManager.DefeatedEnemyCount,
|
||||||
|
Scheduler._combatInRunResourceManager);
|
||||||
if (Scheduler._settlementContext.ShouldOpenRewardSelection)
|
if (Scheduler._settlementContext.ShouldOpenRewardSelection)
|
||||||
{
|
{
|
||||||
Scheduler.ChangeState(new CombatRewardSelectionState(Scheduler));
|
Scheduler.ChangeState(new CombatRewardSelectionState(Scheduler));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue