补全 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
@@ -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;