refactor 6:

- CombatInfoFormUseCase.cs 改成回调驱动:
    - 不再直接读 GameEntry.CombatNode
    - 由 CombatLoadSession.cs 在打开 CombatInfoForm 前注入 modelProvider 和 TryEndCombat 回调
- CombatSelectFormUseCase.cs 的默认 coin provider 改成返回 0,不再偷偷 fallback 到 GameEntry.CombatNode
- CombatFinishFormUseCase.cs 去掉了未准备 summary 时对 GameEntry.CombatNode 的兜底读取
- MapData.cs 进一步补充了战斗初始快照:
    - InventorySnapshot
    - ParticipantTowerSnapshot
- CombatLoadingState.cs 现在会把这些背包/参战塔快照也一起打进 MapData
- MapEntity.cs 配置建塔面板时不再直接读 PlayerInventory,改为用 MapData 里的快照
This commit is contained in:
SepComet 2026-03-07 15:22:24 +08:00
parent ca7b2f2dca
commit ab7c7172b8
7 changed files with 105 additions and 47 deletions

View File

@ -45,7 +45,7 @@ namespace GeometryTD.CustomComponent
_currentMap = null; _currentMap = null;
} }
public bool StartLoading(DRLevel level, MapData mapData, out string errorMessage) public bool StartLoading(DRLevel level, MapData mapData, CombatScheduler scheduler, out string errorMessage)
{ {
errorMessage = null; errorMessage = null;
if (_entity == null) if (_entity == null)
@ -59,7 +59,7 @@ namespace GeometryTD.CustomComponent
return false; return false;
} }
if (!TryOpenCombatInfoForm(out errorMessage)) if (!TryOpenCombatInfoForm(scheduler, out errorMessage))
{ {
return false; return false;
} }
@ -249,7 +249,7 @@ namespace GeometryTD.CustomComponent
return true; return true;
} }
private bool TryOpenCombatInfoForm(out string errorMessage) private bool TryOpenCombatInfoForm(CombatScheduler scheduler, out string errorMessage)
{ {
errorMessage = null; errorMessage = null;
if (_combatInfoFormUseCase == null) if (_combatInfoFormUseCase == null)
@ -258,6 +258,10 @@ namespace GeometryTD.CustomComponent
GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatInfoForm, _combatInfoFormUseCase); GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatInfoForm, _combatInfoFormUseCase);
} }
_combatInfoFormUseCase.Configure(
() => BuildCombatInfoFormRawData(scheduler),
() => scheduler != null && scheduler.CanEndCombat && scheduler.TryEndCombatByPlayer());
int? serialId = GameEntry.UIRouter.OpenUI(UIFormType.CombatInfoForm); int? serialId = GameEntry.UIRouter.OpenUI(UIFormType.CombatInfoForm);
if (!serialId.HasValue) if (!serialId.HasValue)
{ {
@ -270,6 +274,28 @@ namespace GeometryTD.CustomComponent
return true; return true;
} }
private static CombatInfoFormRawData BuildCombatInfoFormRawData(CombatScheduler scheduler)
{
if (scheduler == null)
{
return null;
}
DRLevel level = scheduler.CurrentLevel;
LevelThemeType themeType = level != null ? level.LevelThemeType : LevelThemeType.None;
int levelId = level != null ? level.Id : 0;
int baseHpMax = level != null ? Mathf.Max(0, level.BaseHp) : 0;
return CombatInfoFormUseCase.BuildRawData(
themeType,
levelId,
scheduler.DisplayPhaseIndex,
scheduler.PhaseCount,
scheduler.CurrentCoin,
scheduler.CurrentBaseHp,
baseHpMax,
scheduler.CanEndCombat);
}
private void CloseCombatInfoForm() private void CloseCombatInfoForm()
{ {
GameEntry.UIRouter.CloseUI(UIFormType.CombatInfoForm); GameEntry.UIRouter.CloseUI(UIFormType.CombatInfoForm);

View File

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using GeometryTD.Definition; using GeometryTD.Definition;
using GeometryTD.Entity.EntityData; using GeometryTD.Entity.EntityData;
using GeometryTD.UI;
using UnityEngine; using UnityEngine;
namespace GeometryTD.CustomComponent namespace GeometryTD.CustomComponent
@ -22,7 +23,7 @@ namespace GeometryTD.CustomComponent
} }
MapData mapData = BuildMapData(); MapData mapData = BuildMapData();
if (!Scheduler._loadSession.StartLoading(Scheduler._currentLevel, mapData, out string errorMessage)) if (!Scheduler._loadSession.StartLoading(Scheduler._currentLevel, mapData, Scheduler, out string errorMessage))
{ {
Scheduler.EnterFailureFallback($"Combat loading failed. {errorMessage}"); Scheduler.EnterFailureFallback($"Combat loading failed. {errorMessage}");
} }
@ -60,6 +61,12 @@ namespace GeometryTD.CustomComponent
position: Vector3.zero, position: Vector3.zero,
initialCoin: Scheduler._combatInRunResourceManager.CurrentCoin, initialCoin: Scheduler._combatInRunResourceManager.CurrentCoin,
buildTowerStatsSnapshot: buildTowerStatsSnapshot, buildTowerStatsSnapshot: buildTowerStatsSnapshot,
inventorySnapshot: GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetInventorySnapshot()
: null,
participantTowerSnapshot: GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetParticipantTowerSnapshot()
: null,
tryConsumeCoin: Scheduler.TryConsumeCoin, tryConsumeCoin: Scheduler.TryConsumeCoin,
addCoin: Scheduler.AddCoin); addCoin: Scheduler.AddCoin);
} }

View File

@ -12,6 +12,8 @@ namespace GeometryTD.Entity.EntityData
[SerializeField] private int _levelId = 0; [SerializeField] private int _levelId = 0;
[SerializeField] private int _initialCoin = 0; [SerializeField] private int _initialCoin = 0;
[SerializeField] private TowerStatsData[] _buildTowerStatsSnapshot = Array.Empty<TowerStatsData>(); [SerializeField] private TowerStatsData[] _buildTowerStatsSnapshot = Array.Empty<TowerStatsData>();
[SerializeField] private BackpackInventoryData _inventorySnapshot;
[SerializeField] private TowerItemData[] _participantTowerSnapshot = Array.Empty<TowerItemData>();
[NonSerialized] private Func<int, bool> _tryConsumeCoin; [NonSerialized] private Func<int, bool> _tryConsumeCoin;
[NonSerialized] private Action<int> _addCoin; [NonSerialized] private Action<int> _addCoin;
@ -33,6 +35,8 @@ namespace GeometryTD.Entity.EntityData
Vector3 position, Vector3 position,
int initialCoin, int initialCoin,
IReadOnlyList<TowerStatsData> buildTowerStatsSnapshot, IReadOnlyList<TowerStatsData> buildTowerStatsSnapshot,
BackpackInventoryData inventorySnapshot,
IReadOnlyList<TowerItemData> participantTowerSnapshot,
Func<int, bool> tryConsumeCoin, Func<int, bool> tryConsumeCoin,
Action<int> addCoin) : base(entityId, typeId) Action<int> addCoin) : base(entityId, typeId)
{ {
@ -40,6 +44,10 @@ namespace GeometryTD.Entity.EntityData
Position = position; Position = position;
_initialCoin = Mathf.Max(0, initialCoin); _initialCoin = Mathf.Max(0, initialCoin);
SetBuildTowerStatsSnapshot(buildTowerStatsSnapshot); SetBuildTowerStatsSnapshot(buildTowerStatsSnapshot);
_inventorySnapshot = inventorySnapshot != null
? InventoryCloneUtility.CloneInventory(inventorySnapshot)
: null;
SetParticipantTowerSnapshot(participantTowerSnapshot);
_tryConsumeCoin = tryConsumeCoin; _tryConsumeCoin = tryConsumeCoin;
_addCoin = addCoin; _addCoin = addCoin;
} }
@ -57,6 +65,10 @@ namespace GeometryTD.Entity.EntityData
} }
public int CurrentBuildTowerCount => _buildTowerStatsSnapshot != null ? _buildTowerStatsSnapshot.Length : 0; public int CurrentBuildTowerCount => _buildTowerStatsSnapshot != null ? _buildTowerStatsSnapshot.Length : 0;
public BackpackInventoryData InventorySnapshot => _inventorySnapshot != null
? InventoryCloneUtility.CloneInventory(_inventorySnapshot)
: null;
public IReadOnlyList<TowerItemData> ParticipantTowerSnapshot => _participantTowerSnapshot;
public void BindCombatCallbacks(Func<int, bool> tryConsumeCoin, Action<int> addCoin) public void BindCombatCallbacks(Func<int, bool> tryConsumeCoin, Action<int> addCoin)
{ {
@ -128,8 +140,25 @@ namespace GeometryTD.Entity.EntityData
position, position,
_initialCoin, _initialCoin,
_buildTowerStatsSnapshot, _buildTowerStatsSnapshot,
_inventorySnapshot,
_participantTowerSnapshot,
_tryConsumeCoin, _tryConsumeCoin,
_addCoin); _addCoin);
} }
public void SetParticipantTowerSnapshot(IReadOnlyList<TowerItemData> participantTowerSnapshot)
{
if (participantTowerSnapshot == null || participantTowerSnapshot.Count <= 0)
{
_participantTowerSnapshot = Array.Empty<TowerItemData>();
return;
}
_participantTowerSnapshot = new TowerItemData[participantTowerSnapshot.Count];
for (int i = 0; i < participantTowerSnapshot.Count; i++)
{
_participantTowerSnapshot[i] = InventoryCloneUtility.CloneTower(participantTowerSnapshot[i]);
}
}
} }
} }

View File

@ -243,12 +243,6 @@ namespace GeometryTD.Entity
private void ConfigureCombatSelectUseCase() private void ConfigureCombatSelectUseCase()
{ {
_combatSelectFormUseCase.SetCoinProvider(GetCurrentCoin); _combatSelectFormUseCase.SetCoinProvider(GetCurrentCoin);
BackpackInventoryData inventorySnapshot = GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetInventorySnapshot()
: null;
IReadOnlyList<TowerItemData> participantTowers = GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetParticipantTowerSnapshot()
: null;
_combatSelectUseCaseConfigurator?.Configure( _combatSelectUseCaseConfigurator?.Configure(
_combatSelectFormUseCase, _combatSelectFormUseCase,
GetCurrentCoin, GetCurrentCoin,
@ -259,8 +253,8 @@ namespace GeometryTD.Entity
_destroyGain, _destroyGain,
_buildTowerCosts, _buildTowerCosts,
GetCurrentBuildTowerCount(), GetCurrentBuildTowerCount(),
inventorySnapshot, _mapData != null ? _mapData.InventorySnapshot : null,
participantTowers); _mapData != null ? _mapData.ParticipantTowerSnapshot : null);
} }
private void HandleCombatSelectInput() private void HandleCombatSelectInput()

View File

@ -45,12 +45,8 @@ namespace GeometryTD.UI
{ {
if (!_isSummaryPrepared) if (!_isSummaryPrepared)
{ {
_defeatedEnemyCount = GameEntry.CombatNode != null _defeatedEnemyCount = 0;
? Mathf.Max(0, GameEntry.CombatNode.LastDefeatedEnemyCount) _gainedGold = 0;
: 0;
_gainedGold = GameEntry.CombatNode != null
? Mathf.Max(0, GameEntry.CombatNode.LastGainedGold)
: 0;
_rewardInventory = null; _rewardInventory = null;
} }

View File

@ -1,9 +1,14 @@
using System;
using GeometryTD.Definition; using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.UI namespace GeometryTD.UI
{ {
public class CombatInfoFormUseCase : IUIUseCase public class CombatInfoFormUseCase : IUIUseCase
{ {
private Func<CombatInfoFormRawData> _modelProvider;
private Func<bool> _tryEndCombat;
public CombatInfoFormRawData CreateInitialModel() public CombatInfoFormRawData CreateInitialModel()
{ {
return BuildModel(); return BuildModel();
@ -14,43 +19,49 @@ namespace GeometryTD.UI
return BuildModel(); return BuildModel();
} }
public void Configure(Func<CombatInfoFormRawData> modelProvider, Func<bool> tryEndCombat)
{
_modelProvider = modelProvider;
_tryEndCombat = tryEndCombat;
}
public bool TryEndCombat() public bool TryEndCombat()
{ {
if (GameEntry.CombatNode == null || !GameEntry.CombatNode.CanEndCombat) if (_tryEndCombat == null)
{ {
return false; return false;
} }
return GameEntry.CombatNode.TryEndCombatByPlayer(); return _tryEndCombat.Invoke();
} }
private static CombatInfoFormRawData BuildModel() private CombatInfoFormRawData BuildModel()
{ {
if (GameEntry.CombatNode == null) return _modelProvider != null ? _modelProvider.Invoke() : null;
{
return null;
} }
var level = GameEntry.CombatNode.CurrentLevel; public static CombatInfoFormRawData BuildRawData(
LevelThemeType themeType = level != null ? level.LevelThemeType : GameEntry.CombatNode.CurrentThemeType; LevelThemeType themeType,
int levelId = level != null ? level.Id : 0; int levelId,
int baseHpMax = level != null ? level.BaseHp : 0; int currentPhaseIndex,
int enemyHpRateMultiplier = ResolveEnemyHpRateMultiplier( int totalPhaseCount,
GameEntry.CombatNode.CurrentPhaseIndex, int coin,
GameEntry.CombatNode.CurrentLevelPhaseCount); int baseHp,
int baseHpMax,
bool canEnd)
{
return new CombatInfoFormRawData return new CombatInfoFormRawData
{ {
LevelThemeType = themeType, LevelThemeType = themeType,
LevelId = levelId, LevelId = levelId,
CurrentPhaseIndex = GameEntry.CombatNode.CurrentPhaseIndex, CurrentPhaseIndex = Mathf.Max(0, currentPhaseIndex),
TotalPhaseCount = GameEntry.CombatNode.CurrentLevelPhaseCount, TotalPhaseCount = Mathf.Max(0, totalPhaseCount),
Coin = GameEntry.CombatNode.CurrentCoin, Coin = Mathf.Max(0, coin),
BaseHp = GameEntry.CombatNode.CurrentBaseHp, BaseHp = Mathf.Max(0, baseHp),
BaseHpMax = baseHpMax, BaseHpMax = Mathf.Max(0, baseHpMax),
EnemyHpRateMultiplier = enemyHpRateMultiplier, EnemyHpRateMultiplier = ResolveEnemyHpRateMultiplier(currentPhaseIndex, totalPhaseCount),
CanPause = true, CanPause = true,
CanEnd = GameEntry.CombatNode.CanEndCombat CanEnd = canEnd
}; };
} }
@ -61,7 +72,7 @@ namespace GeometryTD.UI
return 1; return 1;
} }
int completedLoopCount = UnityEngine.Mathf.Max(0, (currentPhaseIndex - 1) / totalPhaseCount); int completedLoopCount = Mathf.Max(0, (currentPhaseIndex - 1) / totalPhaseCount);
if (completedLoopCount >= 30) if (completedLoopCount >= 30)
{ {
return int.MaxValue; return int.MaxValue;

View File

@ -268,15 +268,10 @@ namespace GeometryTD.UI
} }
private static int DefaultCoinProvider() private static int DefaultCoinProvider()
{
if (GameEntry.CombatNode == null)
{ {
return 0; return 0;
} }
return Mathf.Max(0, GameEntry.CombatNode.CurrentCoin);
}
private static bool CanExecute(Option option, int currentCoin) private static bool CanExecute(Option option, int currentCoin)
{ {
if (option == null) if (option == null)