补全 S1 测试

- 调整 CombatNode 正常结束与异常的变量语义,现在是 DidCombatWin 与 Exception
- 补充测试样例
- 完成 S1 目标
This commit is contained in:
SepComet
2026-03-08 21:10:03 +08:00
parent 1023239880
commit 793a87c171
29 changed files with 393 additions and 113 deletions
@@ -22,6 +22,7 @@ namespace UnityGameFramework.Editor
"UnityGameFramework.Runtime",
#endif
"Assembly-CSharp",
"GeometryTD.Runtime"
};
private static readonly string[] RuntimeOrEditorAssemblyNames =
+2 -1
View File
@@ -2,6 +2,7 @@
"name": "GeometryTD.Runtime",
"rootNamespace": "GeometryTD",
"references": [
"UnityGameFramework.Editor",
"UnityGameFramework.Runtime",
"Unity.InputSystem",
"Unity.TextMeshPro"
@@ -15,4 +16,4 @@
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
}
@@ -91,7 +91,7 @@ namespace GeometryTD.CustomComponent
_runtime.EnemyManager.EndPhase();
_runtime.EnemyManager.ResetCombatStats();
_coordinator.ResetRuntime();
_runtime.IsFinishAsVictory = true;
_runtime.DidCombatWin = true;
_runtime.CurrentLevel = level;
_runtime.RunId = runId;
@@ -53,7 +53,7 @@ namespace GeometryTD.CustomComponent
_runtime.EnemyDropResolver.Reset();
_runtime.SettlementContext = null;
_runtime.CurrentLevel = null;
_runtime.IsFinishAsVictory = true;
_runtime.DidCombatWin = true;
_runtime.IsCompleted = false;
_runtime.NodeEnterFired = false;
_runtime.RunId = null;
@@ -124,21 +124,21 @@ namespace GeometryTD.CustomComponent
TryBeginNextPhase();
}
public bool ShouldEnterSettlementFromActiveState(out bool isVictory)
public bool ShouldEnterSettlementFromActiveState(out bool didCombatWin)
{
if (GetCurrentBaseHp() <= 0)
{
isVictory = false;
didCombatWin = false;
return true;
}
if (_runtime.PhaseLoopRuntime.IsEndCombatRequested)
{
isVictory = true;
didCombatWin = true;
return true;
}
isVictory = true;
didCombatWin = true;
return false;
}
@@ -198,9 +198,9 @@ namespace GeometryTD.CustomComponent
GameEntry.UIRouter.CloseUI(UIFormType.DialogForm);
}
public void CompleteNormalCombatAndNotify(bool succeeded)
public void CompleteNormalCombatAndNotify(bool didCombatWin)
{
CompleteCombat(succeeded);
CompleteCombat(didCombatWin);
GameEntry.Event.Fire(
this,
NodeCompleteEventArgs.Create(
@@ -208,7 +208,8 @@ namespace GeometryTD.CustomComponent
_runtime.NodeId,
_runtime.NodeType,
_runtime.SequenceIndex,
succeeded,
RunNodeCompletionStatus.Completed,
didCombatWin,
GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null));
}
@@ -219,7 +220,16 @@ namespace GeometryTD.CustomComponent
CloseRewardSelectForm();
CloseDialogForm();
CompleteCombat(false);
GameEntry.Event.Fire(this, CombatFailureReturnEventArgs.Create());
GameEntry.Event.Fire(
this,
NodeCompleteEventArgs.Create(
_runtime.RunId,
_runtime.NodeId,
_runtime.NodeType,
_runtime.SequenceIndex,
RunNodeCompletionStatus.Exception,
false,
GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null));
}
public bool HandleStartFailure(string errorMessage)
@@ -26,7 +26,7 @@ namespace GeometryTD.CustomComponent
public RewardSelectFormUseCase RewardSelectFormUseCase { get; set; }
public CombatStateBase CurrentState { get; set; }
public Action<bool> CombatEndedCallback { get; set; }
public bool IsFinishAsVictory { get; set; } = true;
public bool DidCombatWin { get; set; } = true;
public bool IsCompleted { get; set; }
public bool NodeEnterFired { get; set; }
public CombatSettlementContext SettlementContext { get; set; }
@@ -18,7 +18,7 @@ namespace GeometryTD.CustomComponent
internal sealed class CombatSettlementResult
{
public bool IsVictory;
public bool DidCombatWin;
public int FinalCoin;
public int FinalBaseHp;
public int MaxBaseHp;
@@ -18,14 +18,14 @@ namespace GeometryTD.CustomComponent
private const float LowBaseHpTowerEndurancePenalty = 10f;
public CombatSettlementContext BuildSettlementContext(
bool isVictory,
bool didCombatWin,
DRLevel currentLevel,
int defeatedEnemyCount,
CombatRunResourceStore resourceStore)
{
bool shouldOpenFullBaseHpRewardSelect = false;
ResolveSettlementByBaseHp(
isVictory,
didCombatWin,
currentLevel,
resourceStore,
out int currentBaseHp,
@@ -39,7 +39,7 @@ namespace GeometryTD.CustomComponent
CombatSettlementContext settlementContext = new CombatSettlementContext
{
};
settlementContext.Result.IsVictory = isVictory;
settlementContext.Result.DidCombatWin = didCombatWin;
settlementContext.Result.FinalCoin = Mathf.Max(0, resourceStore != null ? resourceStore.CurrentCoin : 0);
settlementContext.Result.FinalBaseHp = currentBaseHp;
settlementContext.Result.MaxBaseHp = maxBaseHp;
@@ -173,7 +173,7 @@ namespace GeometryTD.CustomComponent
}
private static void ResolveSettlementByBaseHp(
bool isVictory,
bool didCombatWin,
DRLevel currentLevel,
CombatRunResourceStore resourceStore,
out int currentBaseHp,
@@ -197,7 +197,7 @@ namespace GeometryTD.CustomComponent
appliedLowBaseHpPenalty = false;
shouldOpenFullBaseHpRewardSelect = false;
if (!isVictory || resourceStore == null)
if (!didCombatWin || resourceStore == null)
{
return;
}
@@ -66,9 +66,9 @@ namespace GeometryTD.CustomComponent
Runtime.PhaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
Runtime.EnemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
if (Coordinator.ShouldEnterSettlementFromActiveState(out bool isVictory))
if (Coordinator.ShouldEnterSettlementFromActiveState(out bool didCombatWin))
{
Coordinator.ChangeState(new CombatSettlementState(Runtime, Coordinator, isVictory));
Coordinator.ChangeState(new CombatSettlementState(Runtime, Coordinator, didCombatWin));
return;
}
@@ -2,23 +2,23 @@ namespace GeometryTD.CustomComponent
{
internal sealed class CombatSettlementState : CombatStateBase
{
private readonly bool _isVictory;
private readonly bool _didCombatWin;
public CombatSettlementState(
CombatSchedulerRuntime runtime,
CombatSchedulerCoordinator coordinator,
bool isVictory) : base(runtime, coordinator)
bool didCombatWin) : base(runtime, coordinator)
{
_isVictory = isVictory;
_didCombatWin = didCombatWin;
}
public override void OnEnter()
{
Runtime.EnemyManager.EndPhase();
Runtime.EnemyManager.CleanupTrackedEnemies();
Runtime.IsFinishAsVictory = _isVictory;
Runtime.DidCombatWin = _didCombatWin;
Runtime.SettlementContext = Runtime.CombatSettlementService.BuildSettlementContext(
_isVictory,
_didCombatWin,
Runtime.CurrentLevel,
Runtime.EnemyManager.DefeatedEnemyCount,
Runtime.CombatRunResourceStore);
@@ -22,9 +22,9 @@ namespace GeometryTD.CustomComponent
Runtime.PhaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
if (Coordinator.ShouldEnterSettlementFromActiveState(out bool isVictory))
if (Coordinator.ShouldEnterSettlementFromActiveState(out bool didCombatWin))
{
Coordinator.ChangeState(new CombatSettlementState(Runtime, Coordinator, isVictory));
Coordinator.ChangeState(new CombatSettlementState(Runtime, Coordinator, didCombatWin));
return;
}
@@ -34,7 +34,7 @@ namespace GeometryTD.CustomComponent
Runtime.LoadSession.Cleanup();
Coordinator.CloseCombatFinishForm();
Coordinator.CloseRewardSelectForm();
Coordinator.CompleteNormalCombatAndNotify(Runtime.IsFinishAsVictory);
Coordinator.CompleteNormalCombatAndNotify(Runtime.DidCombatWin);
}
}
}
@@ -94,6 +94,7 @@ namespace GeometryTD.CustomComponent
_activeNodeId,
_activeNodeType,
_activeSequenceIndex,
RunNodeCompletionStatus.Completed,
true,
GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null));
ClearActiveNodeContext();
@@ -59,6 +59,7 @@ namespace GeometryTD.CustomComponent
_activeNodeId,
_activeNodeType,
_activeSequenceIndex,
RunNodeCompletionStatus.Completed,
true,
GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null));
ClearActiveNodeContext();
@@ -114,12 +114,10 @@ namespace GeometryTD.Entity
{
GameEntry.HPBar?.ShowHPBar(this, (float)previousHealth / _maxHealth,
(float)_currentHealth / _maxHealth);
Log.Info($"ShowBar: {_currentHealth}/{_maxHealth}");
}
if (_currentHealth <= 0)
{
Log.Info("Enemy Dead");
_killedEnemyEntityIds.Add(Id);
RequestDespawn();
}
@@ -1,21 +0,0 @@
using GameFramework;
using GameFramework.Event;
namespace GeometryTD.CustomEvent
{
public class CombatFailureReturnEventArgs : GameEventArgs
{
public static int EventId => typeof(CombatFailureReturnEventArgs).GetHashCode();
public override int Id => EventId;
public static CombatFailureReturnEventArgs Create()
{
return ReferencePool.Acquire<CombatFailureReturnEventArgs>();
}
public override void Clear()
{
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 7db1bf4f6f5f435d8d3086510e178d1d
timeCreated: 1772863200
@@ -20,7 +20,9 @@ namespace GeometryTD.CustomEvent
public int SequenceIndex { get; private set; }
public bool Succeeded { get; private set; }
public RunNodeCompletionStatus CompletionStatus { get; private set; }
public bool CombatWon { get; private set; }
public BackpackInventoryData InventorySnapshotAfterNode { get; private set; }
@@ -30,7 +32,7 @@ namespace GeometryTD.CustomEvent
public static NodeCompleteEventArgs Create()
{
return Create(null, 0, RunNodeType.None, -1, true, null);
return Create(null, 0, RunNodeType.None, -1, RunNodeCompletionStatus.Completed, true, null);
}
public static NodeCompleteEventArgs Create(
@@ -38,7 +40,8 @@ namespace GeometryTD.CustomEvent
int nodeId,
RunNodeType nodeType,
int sequenceIndex,
bool succeeded,
RunNodeCompletionStatus completionStatus,
bool combatWon,
BackpackInventoryData inventorySnapshotAfterNode)
{
var args = ReferencePool.Acquire<NodeCompleteEventArgs>();
@@ -46,7 +49,8 @@ namespace GeometryTD.CustomEvent
args.NodeId = nodeId;
args.NodeType = nodeType;
args.SequenceIndex = sequenceIndex;
args.Succeeded = succeeded;
args.CompletionStatus = completionStatus;
args.CombatWon = combatWon;
args.InventorySnapshotAfterNode = InventoryCloneUtility.CloneInventory(inventorySnapshotAfterNode);
return args;
@@ -58,7 +62,8 @@ namespace GeometryTD.CustomEvent
NodeId = 0;
NodeType = RunNodeType.None;
SequenceIndex = -1;
Succeeded = false;
CompletionStatus = RunNodeCompletionStatus.None;
CombatWon = false;
InventorySnapshotAfterNode = null;
}
}
@@ -18,19 +18,26 @@ namespace GeometryTD.Procedure
public enum ProcedureMainRunAdvanceResult
{
NoChange = 0,
NodeFailed = 1,
NodeException = 1,
AdvancedToNextNode = 2,
RunCompleted = 3
}
public enum ProcedureMainRunCompletionResult
{
NoChange = 0,
ShowCompletionDialog = 1,
ReturnToMenu = 2
}
public static class ProcedureMainRunFlowService
{
public static ProcedureMainRunAdvanceResult TryAdvanceRun(
RunState runState,
bool succeeded,
RunNodeCompletionStatus completionStatus,
BackpackInventoryData snapshot)
{
if (!RunStateAdvanceService.TryCompleteCurrentNode(runState, succeeded, snapshot))
if (!RunStateAdvanceService.TryCompleteCurrentNode(runState, completionStatus, snapshot))
{
return ProcedureMainRunAdvanceResult.NoChange;
}
@@ -40,9 +47,31 @@ namespace GeometryTD.Procedure
return ProcedureMainRunAdvanceResult.RunCompleted;
}
return succeeded
? ProcedureMainRunAdvanceResult.AdvancedToNextNode
: ProcedureMainRunAdvanceResult.NodeFailed;
return completionStatus == RunNodeCompletionStatus.Exception
? ProcedureMainRunAdvanceResult.NodeException
: ProcedureMainRunAdvanceResult.AdvancedToNextNode;
}
}
public static class ProcedureMainRunCompletionService
{
public static ProcedureMainRunCompletionResult TryEnterCompletedPendingFinish(bool isCompletionDialogShown)
{
return isCompletionDialogShown
? ProcedureMainRunCompletionResult.NoChange
: ProcedureMainRunCompletionResult.ShowCompletionDialog;
}
public static ProcedureMainRunCompletionResult TryConfirmReturnToMenu(
ProcedureMainFlowPhase flowPhase,
bool isReturnToMenuPending)
{
if (flowPhase != ProcedureMainFlowPhase.RunCompletedPendingFinish || isReturnToMenuPending)
{
return ProcedureMainRunCompletionResult.NoChange;
}
return ProcedureMainRunCompletionResult.ReturnToMenu;
}
}
@@ -54,6 +83,8 @@ namespace GeometryTD.Procedure
private NodeMapFormUseCase _nodeMapFormUseCase;
private RunState _currentRunState;
private ProcedureMainFlowPhase _flowPhase = ProcedureMainFlowPhase.Hub;
private bool _isRunCompleteDialogShown;
private bool _isReturnToMenuPending;
#region FSM
@@ -61,7 +92,6 @@ namespace GeometryTD.Procedure
{
base.OnEnter(procedureOwner);
GameEntry.Event.Subscribe(CombatFailureReturnEventArgs.EventId, OnCombatFailureReturn);
GameEntry.Event.Subscribe(NodeCompleteEventArgs.EventId, OnNodeComplete);
GameEntry.Event.Subscribe(NodeEnterEventArgs.EventId, OnNodeEnter);
GameEntry.Event.Subscribe(NodeMapNodeEnterRequestedEventArgs.EventId, OnNodeMapNodeEnterRequested);
@@ -83,6 +113,8 @@ namespace GeometryTD.Procedure
_nodeMapFormUseCase = new NodeMapFormUseCase();
_nodeMapFormUseCase.SetRunState(_currentRunState);
GameEntry.UIRouter.BindUIUseCase(UIFormType.NodeMapForm, _nodeMapFormUseCase);
_isRunCompleteDialogShown = false;
_isReturnToMenuPending = false;
EnterHubFlow();
}
@@ -92,6 +124,14 @@ namespace GeometryTD.Procedure
{
base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);
if (_isReturnToMenuPending)
{
_isReturnToMenuPending = false;
procedureOwner.SetData<VarInt32>("NextSceneId", (int)SceneType.Menu);
ChangeState<ProcedureChangeScene>(procedureOwner);
return;
}
GameEntry.CombatNode.OnUpdate(elapseSeconds, realElapseSeconds);
}
@@ -99,16 +139,18 @@ namespace GeometryTD.Procedure
{
GameEntry.CombatNode.OnShutdown();
GameEntry.Event.Unsubscribe(NodeMapNodeEnterRequestedEventArgs.EventId, OnNodeMapNodeEnterRequested);
GameEntry.Event.Unsubscribe(CombatFailureReturnEventArgs.EventId, OnCombatFailureReturn);
GameEntry.Event.Unsubscribe(NodeEnterEventArgs.EventId, OnNodeEnter);
GameEntry.Event.Unsubscribe(NodeCompleteEventArgs.EventId, OnNodeComplete);
GameEntry.UIRouter.CloseUI(UIFormType.RepoForm);
GameEntry.UIRouter.CloseUI(UIFormType.NodeMapForm);
GameEntry.UIRouter.CloseUI(UIFormType.MainForm);
GameEntry.UIRouter.CloseUI(UIFormType.DialogForm);
_repoFormUseCase = null;
_nodeMapFormUseCase = null;
_currentRunState = null;
_flowPhase = ProcedureMainFlowPhase.Hub;
_isRunCompleteDialogShown = false;
_isReturnToMenuPending = false;
base.OnLeave(procedureOwner, isShutdown);
}
@@ -178,26 +220,23 @@ namespace GeometryTD.Procedure
return;
}
Log.Info(
"ProcedureMain.OnNodeComplete() accepted. RunId={0}, NodeId={1}, NodeType={2}, SequenceIndex={3}, CompletionStatus={4}, CombatWon={5}.",
string.IsNullOrWhiteSpace(args.RunId) ? _currentRunState?.RunId : args.RunId,
args.NodeId,
args.NodeType,
args.SequenceIndex,
args.CompletionStatus,
args.CombatWon);
BackpackInventoryData snapshot = args.InventorySnapshotAfterNode;
if (snapshot == null && GameEntry.PlayerInventory != null)
{
snapshot = GameEntry.PlayerInventory.GetInventorySnapshot();
}
HandleRunAdvanceResult(ProcedureMainRunFlowService.TryAdvanceRun(_currentRunState, args.Succeeded, snapshot));
}
private void OnCombatFailureReturn(object sender, GameEventArgs e)
{
if (!(e is CombatFailureReturnEventArgs))
{
return;
}
BackpackInventoryData snapshot = GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetInventorySnapshot()
: null;
HandleRunAdvanceResult(ProcedureMainRunFlowService.TryAdvanceRun(_currentRunState, false, snapshot));
HandleRunAdvanceResult(
ProcedureMainRunFlowService.TryAdvanceRun(_currentRunState, args.CompletionStatus, snapshot));
}
private void OnNodeMapNodeEnterRequested(object sender, GameEventArgs e)
@@ -275,9 +314,9 @@ namespace GeometryTD.Procedure
{
case ProcedureMainRunAdvanceResult.NoChange:
return;
case ProcedureMainRunAdvanceResult.NodeFailed:
case ProcedureMainRunAdvanceResult.NodeException:
Log.Info(
"ProcedureMain current node failed. RunId={0}, NodeId={1}, NodeType={2}, SequenceIndex={3}.",
"ProcedureMain current node entered exception state. RunId={0}, NodeId={1}, NodeType={2}, SequenceIndex={3}.",
_currentRunState?.RunId,
_currentRunState?.CurrentNode?.NodeId ?? 0,
_currentRunState?.CurrentNode?.NodeType ?? RunNodeType.None,
@@ -322,6 +361,14 @@ namespace GeometryTD.Procedure
_nodeMapFormUseCase?.SetRunState(_currentRunState);
CloseHubUI();
Log.Info("ProcedureMain run completed. RunId={0}", _currentRunState?.RunId);
ProcedureMainRunCompletionResult result =
ProcedureMainRunCompletionService.TryEnterCompletedPendingFinish(_isRunCompleteDialogShown);
if (result == ProcedureMainRunCompletionResult.ShowCompletionDialog)
{
_isRunCompleteDialogShown = true;
OpenRunCompleteDialog();
}
}
private void CloseHubUI()
@@ -345,5 +392,37 @@ namespace GeometryTD.Procedure
nextNode.SequenceIndex,
nextNode.LinkedLevelId);
}
private void OpenRunCompleteDialog()
{
GameEntry.UIRouter.OpenUI(UIFormType.DialogForm, new DialogFormRawData
{
Mode = 1,
Title = "Run Complete",
Message = "Boss node completed. This run is finished and will return to the main menu.",
PauseGame = false,
ConfirmText = "Return to Menu",
OnClickConfirm = OnRunCompleteDialogConfirmed
});
}
private void OnRunCompleteDialogConfirmed(object userData)
{
_ = userData;
ProcedureMainRunCompletionResult result = ProcedureMainRunCompletionService.TryConfirmReturnToMenu(
_flowPhase,
_isReturnToMenuPending);
if (result != ProcedureMainRunCompletionResult.ReturnToMenu)
{
Log.Warning(
"ProcedureMain ignored run completion confirm. FlowPhase={0}, ReturnPending={1}.",
_flowPhase,
_isReturnToMenuPending);
return;
}
_isReturnToMenuPending = true;
}
}
}
@@ -19,10 +19,17 @@ namespace GeometryTD.Procedure
Locked = 0,
Available = 1,
Completed = 2,
Failed = 3,
Exception = 3,
Skipped = 4
}
public enum RunNodeCompletionStatus
{
None = 0,
Completed = 1,
Exception = 2
}
[Serializable]
public sealed class RunNodeSeed
{
@@ -6,7 +6,7 @@ namespace GeometryTD.Procedure
{
public static bool TryCompleteCurrentNode(
RunState runState,
bool succeeded,
RunNodeCompletionStatus completionStatus,
BackpackInventoryData inventorySnapshotAfterNode)
{
if (runState == null || runState.IsCompleted)
@@ -22,12 +22,17 @@ namespace GeometryTD.Procedure
runState.ReplaceInventorySnapshot(inventorySnapshotAfterNode);
if (!succeeded)
if (completionStatus == RunNodeCompletionStatus.Exception)
{
currentNode.Status = RunNodeStatus.Failed;
currentNode.Status = RunNodeStatus.Exception;
return true;
}
if (completionStatus != RunNodeCompletionStatus.Completed)
{
return false;
}
currentNode.Status = RunNodeStatus.Completed;
int nextIndex = runState.CurrentNodeIndex + 1;
@@ -10,7 +10,7 @@ namespace GeometryTD.UI
public bool IsLocked;
public bool IsCurrent;
public bool IsCompleted;
public bool IsFailed;
public bool IsException;
public bool CanClick;
}
}
@@ -143,7 +143,7 @@ namespace GeometryTD.UI
IsLocked = node != null && node.Status == RunNodeStatus.Locked,
IsCurrent = node != null && node.IsCurrentNode,
IsCompleted = node != null && node.Status == RunNodeStatus.Completed,
IsFailed = node != null && node.Status == RunNodeStatus.Failed,
IsException = node != null && node.Status == RunNodeStatus.Exception,
CanClick = node != null && node.CanEnter
};
}
@@ -144,8 +144,8 @@ namespace GeometryTD.UI
return "可进入";
case RunNodeStatus.Completed:
return "已完成";
case RunNodeStatus.Failed:
return "失败";
case RunNodeStatus.Exception:
return "异常";
case RunNodeStatus.Skipped:
return "跳过";
default:
@@ -19,7 +19,7 @@ namespace GeometryTD.UI
[SerializeField] private Color _lockedColor = Color.gray;
[SerializeField] private Color _activeColor = Color.cyan;
[SerializeField] private Color _passedColor = Color.green;
[SerializeField] private Color _failedColor = Color.red;
[SerializeField] private Color _exceptionColor = Color.red;
[SerializeField] private Color _defaultIconColor = Color.white;
[SerializeField] private Color _lockedIconColor = Color.gray;
@@ -44,9 +44,9 @@ namespace GeometryTD.UI
{
targetColor = _passedColor;
}
else if (context.IsFailed)
else if (context.IsException)
{
targetColor = _failedColor;
targetColor = _exceptionColor;
}
else if (context.IsCurrent)
{
@@ -0,0 +1,139 @@
using GeometryTD.Definition;
using GeometryTD.Procedure;
using NUnit.Framework;
namespace GeometryTD.Tests.EditMode
{
public sealed class ProcedureMainServicesTests
{
[Test]
public void TryAdvanceRun_Returns_AdvancedToNextNode_For_Normal_Completion()
{
RunState runState = CreateTwoNodeRun();
ProcedureMainRunAdvanceResult result = ProcedureMainRunFlowService.TryAdvanceRun(
runState,
RunNodeCompletionStatus.Completed,
new BackpackInventoryData { Gold = 88 });
Assert.That(result, Is.EqualTo(ProcedureMainRunAdvanceResult.AdvancedToNextNode));
Assert.That(runState.IsCompleted, Is.False);
Assert.That(runState.Nodes[0].Status, Is.EqualTo(RunNodeStatus.Completed));
Assert.That(runState.CurrentNodeIndex, Is.EqualTo(1));
Assert.That(runState.CurrentNode.Status, Is.EqualTo(RunNodeStatus.Available));
Assert.That(runState.RunInventorySnapshot.Gold, Is.EqualTo(88));
}
[Test]
public void TryAdvanceRun_Returns_NodeException_For_Exception_Fallback()
{
RunState runState = CreateTwoNodeRun();
ProcedureMainRunAdvanceResult result = ProcedureMainRunFlowService.TryAdvanceRun(
runState,
RunNodeCompletionStatus.Exception,
new BackpackInventoryData { Gold = 5 });
Assert.That(result, Is.EqualTo(ProcedureMainRunAdvanceResult.NodeException));
Assert.That(runState.IsCompleted, Is.False);
Assert.That(runState.CurrentNodeIndex, Is.EqualTo(0));
Assert.That(runState.CurrentNode.Status, Is.EqualTo(RunNodeStatus.Exception));
Assert.That(runState.Nodes[1].Status, Is.EqualTo(RunNodeStatus.Locked));
Assert.That(runState.RunInventorySnapshot.Gold, Is.EqualTo(5));
}
[Test]
public void TryAdvanceRun_Returns_RunCompleted_When_Last_Node_Finishes()
{
RunState runState = RunStateFactory.Create(
LevelThemeType.Plain,
new BackpackInventoryData { Gold = 30 },
new[]
{
new RunNodeSeed { NodeId = 901, NodeType = RunNodeType.BossCombat, LinkedLevelId = 4 }
});
ProcedureMainRunAdvanceResult result = ProcedureMainRunFlowService.TryAdvanceRun(
runState,
RunNodeCompletionStatus.Completed,
new BackpackInventoryData { Gold = 123 });
Assert.That(result, Is.EqualTo(ProcedureMainRunAdvanceResult.RunCompleted));
Assert.That(runState.IsCompleted, Is.True);
Assert.That(runState.CurrentNodeIndex, Is.EqualTo(1));
Assert.That(runState.CompletedNodeCount, Is.EqualTo(1));
Assert.That(runState.RunInventorySnapshot.Gold, Is.EqualTo(123));
}
[Test]
public void TryAdvanceRun_Returns_NoChange_When_Run_Cannot_Advance()
{
ProcedureMainRunAdvanceResult nullRunResult = ProcedureMainRunFlowService.TryAdvanceRun(
null,
RunNodeCompletionStatus.Completed,
new BackpackInventoryData { Gold = 1 });
Assert.That(nullRunResult, Is.EqualTo(ProcedureMainRunAdvanceResult.NoChange));
RunState completedRun = RunStateFactory.Create(
LevelThemeType.Plain,
new BackpackInventoryData { Gold = 10 },
new RunNodeSeed[0]);
ProcedureMainRunAdvanceResult completedRunResult = ProcedureMainRunFlowService.TryAdvanceRun(
completedRun,
RunNodeCompletionStatus.Completed,
new BackpackInventoryData { Gold = 20 });
Assert.That(completedRunResult, Is.EqualTo(ProcedureMainRunAdvanceResult.NoChange));
Assert.That(completedRun.RunInventorySnapshot.Gold, Is.EqualTo(10));
}
[Test]
public void TryEnterCompletedPendingFinish_Shows_Dialog_Only_Once()
{
ProcedureMainRunCompletionResult firstResult =
ProcedureMainRunCompletionService.TryEnterCompletedPendingFinish(false);
ProcedureMainRunCompletionResult secondResult =
ProcedureMainRunCompletionService.TryEnterCompletedPendingFinish(true);
Assert.That(firstResult, Is.EqualTo(ProcedureMainRunCompletionResult.ShowCompletionDialog));
Assert.That(secondResult, Is.EqualTo(ProcedureMainRunCompletionResult.NoChange));
}
[Test]
public void TryConfirmReturnToMenu_Returns_Menu_Only_In_Completed_Pending_Finish()
{
ProcedureMainRunCompletionResult validResult =
ProcedureMainRunCompletionService.TryConfirmReturnToMenu(
ProcedureMainFlowPhase.RunCompletedPendingFinish,
false);
ProcedureMainRunCompletionResult hubResult =
ProcedureMainRunCompletionService.TryConfirmReturnToMenu(
ProcedureMainFlowPhase.Hub,
false);
ProcedureMainRunCompletionResult pendingResult =
ProcedureMainRunCompletionService.TryConfirmReturnToMenu(
ProcedureMainFlowPhase.RunCompletedPendingFinish,
true);
Assert.That(validResult, Is.EqualTo(ProcedureMainRunCompletionResult.ReturnToMenu));
Assert.That(hubResult, Is.EqualTo(ProcedureMainRunCompletionResult.NoChange));
Assert.That(pendingResult, Is.EqualTo(ProcedureMainRunCompletionResult.NoChange));
}
private static RunState CreateTwoNodeRun()
{
return RunStateFactory.Create(
LevelThemeType.Plain,
new BackpackInventoryData { Gold = 50 },
new[]
{
new RunNodeSeed { NodeId = 101, NodeType = RunNodeType.Combat, LinkedLevelId = 1 },
new RunNodeSeed { NodeId = 102, NodeType = RunNodeType.Event }
});
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bb7e69f8c974497c9431d4e1d9e6e8b4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+18 -9
View File
@@ -59,7 +59,7 @@ namespace GeometryTD.Tests.EditMode
bool firstCompleted = RunStateAdvanceService.TryCompleteCurrentNode(
runState,
true,
RunNodeCompletionStatus.Completed,
new BackpackInventoryData { Gold = 80 });
Assert.That(firstCompleted, Is.True);
@@ -68,8 +68,14 @@ namespace GeometryTD.Tests.EditMode
Assert.That(runState.Nodes[1].Status, Is.EqualTo(RunNodeStatus.Available));
Assert.That(runState.RunInventorySnapshot.Gold, Is.EqualTo(80));
RunStateAdvanceService.TryCompleteCurrentNode(runState, true, new BackpackInventoryData { Gold = 95 });
RunStateAdvanceService.TryCompleteCurrentNode(runState, true, new BackpackInventoryData { Gold = 130 });
RunStateAdvanceService.TryCompleteCurrentNode(
runState,
RunNodeCompletionStatus.Completed,
new BackpackInventoryData { Gold = 95 });
RunStateAdvanceService.TryCompleteCurrentNode(
runState,
RunNodeCompletionStatus.Completed,
new BackpackInventoryData { Gold = 130 });
Assert.That(runState.IsCompleted, Is.True);
Assert.That(runState.CurrentNodeIndex, Is.EqualTo(3));
@@ -78,7 +84,7 @@ namespace GeometryTD.Tests.EditMode
}
[Test]
public void AdvanceService_Failure_Marks_Current_Node_Failed_Without_Completing_Run()
public void AdvanceService_Exception_Marks_Current_Node_Exception_Without_Completing_Run()
{
RunState runState = RunStateFactory.Create(
LevelThemeType.Plain,
@@ -90,13 +96,13 @@ namespace GeometryTD.Tests.EditMode
bool result = RunStateAdvanceService.TryCompleteCurrentNode(
runState,
false,
RunNodeCompletionStatus.Exception,
new BackpackInventoryData { Gold = 5 });
Assert.That(result, Is.True);
Assert.That(runState.IsCompleted, Is.False);
Assert.That(runState.CurrentNodeIndex, Is.EqualTo(0));
Assert.That(runState.CurrentNode.Status, Is.EqualTo(RunNodeStatus.Failed));
Assert.That(runState.CurrentNode.Status, Is.EqualTo(RunNodeStatus.Exception));
Assert.That(runState.RunInventorySnapshot.Gold, Is.EqualTo(5));
}
@@ -109,6 +115,7 @@ namespace GeometryTD.Tests.EditMode
7,
RunNodeType.Shop,
3,
RunNodeCompletionStatus.Completed,
true,
inventory);
@@ -118,7 +125,8 @@ namespace GeometryTD.Tests.EditMode
Assert.That(eventArgs.NodeId, Is.EqualTo(7));
Assert.That(eventArgs.NodeType, Is.EqualTo(RunNodeType.Shop));
Assert.That(eventArgs.SequenceIndex, Is.EqualTo(3));
Assert.That(eventArgs.Succeeded, Is.True);
Assert.That(eventArgs.CompletionStatus, Is.EqualTo(RunNodeCompletionStatus.Completed));
Assert.That(eventArgs.CombatWon, Is.True);
Assert.That(eventArgs.InventorySnapshotAfterNode.Gold, Is.EqualTo(66));
eventArgs.Clear();
@@ -127,7 +135,8 @@ namespace GeometryTD.Tests.EditMode
Assert.That(eventArgs.NodeId, Is.EqualTo(0));
Assert.That(eventArgs.NodeType, Is.EqualTo(RunNodeType.None));
Assert.That(eventArgs.SequenceIndex, Is.EqualTo(-1));
Assert.That(eventArgs.Succeeded, Is.False);
Assert.That(eventArgs.CompletionStatus, Is.EqualTo(RunNodeCompletionStatus.None));
Assert.That(eventArgs.CombatWon, Is.False);
Assert.That(eventArgs.InventorySnapshotAfterNode, Is.Null);
}
@@ -189,7 +198,7 @@ namespace GeometryTD.Tests.EditMode
{
bool advanced = RunStateAdvanceService.TryCompleteCurrentNode(
runState,
true,
RunNodeCompletionStatus.Completed,
new BackpackInventoryData { Gold = 100 + i });
Assert.That(advanced, Is.True);