init
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.Definition;
|
||||
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public static class FixedRunNodeSequenceBuilder
|
||||
{
|
||||
private const int FixedNodeCount = 10;
|
||||
private static readonly int[] PlainCombatLevelCycle = { 1, 2, 3, 1 };
|
||||
|
||||
public static IReadOnlyList<RunNodeSeed> Build(LevelThemeType themeType)
|
||||
{
|
||||
if (themeType != LevelThemeType.Plain)
|
||||
{
|
||||
return new List<RunNodeSeed>();
|
||||
}
|
||||
|
||||
List<RunNodeSeed> nodeSeeds = new List<RunNodeSeed>(FixedNodeCount)
|
||||
{
|
||||
CreateNodeSeed(1, 0, RunNodeType.Combat, themeType, PlainCombatLevelCycle[0]),
|
||||
CreateNodeSeed(2, 1, RunNodeType.Event, themeType, 0),
|
||||
CreateNodeSeed(3, 2, RunNodeType.Combat, themeType, PlainCombatLevelCycle[1]),
|
||||
CreateNodeSeed(4, 3, RunNodeType.Shop, themeType, 0),
|
||||
CreateNodeSeed(5, 4, RunNodeType.Combat, themeType, PlainCombatLevelCycle[2]),
|
||||
CreateNodeSeed(6, 5, RunNodeType.Event, themeType, 0),
|
||||
CreateNodeSeed(7, 6, RunNodeType.Combat, themeType, PlainCombatLevelCycle[3]),
|
||||
CreateNodeSeed(8, 7, RunNodeType.Shop, themeType, 0),
|
||||
CreateNodeSeed(9, 8, RunNodeType.Event, themeType, 0),
|
||||
CreateNodeSeed(10, 9, RunNodeType.BossCombat, themeType, 4)
|
||||
};
|
||||
|
||||
return nodeSeeds;
|
||||
}
|
||||
|
||||
private static RunNodeSeed CreateNodeSeed(
|
||||
int nodeId,
|
||||
int sequenceIndex,
|
||||
RunNodeType nodeType,
|
||||
LevelThemeType themeType,
|
||||
int linkedLevelId)
|
||||
{
|
||||
return new RunNodeSeed
|
||||
{
|
||||
NodeId = nodeId,
|
||||
SequenceIndex = sequenceIndex,
|
||||
NodeType = nodeType,
|
||||
ThemeType = themeType,
|
||||
LinkedLevelId = linkedLevelId
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
using GameFramework.Event;
|
||||
using GameFramework.Fsm;
|
||||
using GameFramework.Procedure;
|
||||
using System;
|
||||
using GeometryTD.CustomEvent;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Factory;
|
||||
using GeometryTD.UI;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public class ProcedureMain : ProcedureBase
|
||||
{
|
||||
private const int MaxParticipantTowerCount = 4;
|
||||
|
||||
public override bool UseNativeDialog => false;
|
||||
|
||||
private RepoFormUseCase _repoFormUseCase;
|
||||
private NodeMapFormUseCase _nodeMapFormUseCase;
|
||||
private RunState _currentRunState;
|
||||
private ProcedureMainFlowPhase _flowPhase = ProcedureMainFlowPhase.Hub;
|
||||
private bool _isRunCompleteDialogShown;
|
||||
private bool _isReturnToMenuPending;
|
||||
|
||||
#region FSM
|
||||
|
||||
protected override void OnEnter(IFsm<IProcedureManager> procedureOwner)
|
||||
{
|
||||
base.OnEnter(procedureOwner);
|
||||
|
||||
GameEntry.Event.Subscribe(NodeCompleteEventArgs.EventId, OnNodeComplete);
|
||||
GameEntry.Event.Subscribe(NodeEnterEventArgs.EventId, OnNodeEnter);
|
||||
GameEntry.Event.Subscribe(NodeMapNodeEnterRequestedEventArgs.EventId, OnNodeMapNodeEnterRequested);
|
||||
|
||||
GameEntry.TagRegistry.OnInit();
|
||||
GameEntry.EventNode.OnInit();
|
||||
GameEntry.CombatNode.OnInit(LevelThemeType.Plain);
|
||||
GameEntry.ShopNode.OnInit();
|
||||
|
||||
string runId = Guid.NewGuid().ToString("N");
|
||||
int runSeed = RunStateFactory.CreateRunSeed();
|
||||
BackpackInventoryData initialInventory = InventorySeedUtility.CreateSampleInventory(runSeed);
|
||||
GameEntry.PlayerInventory?.OnInit(initialInventory);
|
||||
|
||||
_currentRunState = RunStateFactory.CreateFixedRun(
|
||||
LevelThemeType.Plain,
|
||||
GameEntry.PlayerInventory != null
|
||||
? GameEntry.PlayerInventory.GetInventorySnapshot()
|
||||
: initialInventory,
|
||||
runId,
|
||||
runSeed);
|
||||
|
||||
_repoFormUseCase = new RepoFormUseCase();
|
||||
GameEntry.UIRouter.BindUIUseCase(UIFormType.RepoForm, _repoFormUseCase);
|
||||
|
||||
_nodeMapFormUseCase = new NodeMapFormUseCase();
|
||||
_nodeMapFormUseCase.SetRunState(_currentRunState);
|
||||
GameEntry.UIRouter.BindUIUseCase(UIFormType.NodeMapForm, _nodeMapFormUseCase);
|
||||
_isRunCompleteDialogShown = false;
|
||||
_isReturnToMenuPending = false;
|
||||
|
||||
EnterHubFlow();
|
||||
}
|
||||
|
||||
protected override void OnUpdate(IFsm<IProcedureManager> procedureOwner, float elapseSeconds,
|
||||
float realElapseSeconds)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
protected override void OnLeave(IFsm<IProcedureManager> procedureOwner, bool isShutdown)
|
||||
{
|
||||
GameEntry.CombatNode.OnShutdown();
|
||||
GameEntry.Event.Unsubscribe(NodeMapNodeEnterRequestedEventArgs.EventId, OnNodeMapNodeEnterRequested);
|
||||
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);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
private void OnNodeEnter(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(e is NodeEnterEventArgs args))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(args.RunId) &&
|
||||
_currentRunState != null &&
|
||||
!string.Equals(args.RunId, _currentRunState.RunId))
|
||||
{
|
||||
Log.Warning(
|
||||
"ProcedureMain.OnNodeEnter() ignored. EventRunId={0}, CurrentRunId={1}.",
|
||||
args.RunId,
|
||||
_currentRunState.RunId);
|
||||
return;
|
||||
}
|
||||
|
||||
RunNodeState currentNode = _currentRunState?.CurrentNode;
|
||||
if (!ProcedureMainNodeEventGuardService.MatchesCurrentNode(
|
||||
_currentRunState,
|
||||
args.NodeId,
|
||||
args.NodeType,
|
||||
args.SequenceIndex))
|
||||
{
|
||||
Log.Warning(
|
||||
"ProcedureMain.OnNodeEnter() node mismatch. EventNodeId={0}, EventNodeType={1}, EventSequenceIndex={2}; CurrentNodeId={3}, CurrentNodeType={4}, CurrentSequenceIndex={5}.",
|
||||
args.NodeId,
|
||||
args.NodeType,
|
||||
args.SequenceIndex,
|
||||
currentNode?.NodeId ?? 0,
|
||||
currentNode?.NodeType ?? RunNodeType.None,
|
||||
currentNode?.SequenceIndex ?? -1);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Info(
|
||||
"ProcedureMain.OnNodeEnter() accepted. RunId={0}, NodeId={1}, NodeType={2}, SequenceIndex={3}.",
|
||||
string.IsNullOrWhiteSpace(args.RunId) ? _currentRunState?.RunId : args.RunId,
|
||||
args.NodeId,
|
||||
args.NodeType,
|
||||
args.SequenceIndex);
|
||||
|
||||
EnterNodeFlow();
|
||||
}
|
||||
|
||||
private void OnNodeComplete(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(e is NodeCompleteEventArgs args))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(args.RunId) &&
|
||||
_currentRunState != null &&
|
||||
!string.Equals(args.RunId, _currentRunState.RunId))
|
||||
{
|
||||
Log.Warning(
|
||||
"ProcedureMain.OnNodeComplete() ignored. EventRunId={0}, CurrentRunId={1}.",
|
||||
args.RunId,
|
||||
_currentRunState.RunId);
|
||||
return;
|
||||
}
|
||||
|
||||
RunNodeState currentNode = _currentRunState?.CurrentNode;
|
||||
if (!ProcedureMainNodeEventGuardService.MatchesCurrentNode(
|
||||
_currentRunState,
|
||||
args.NodeId,
|
||||
args.NodeType,
|
||||
args.SequenceIndex))
|
||||
{
|
||||
Log.Warning(
|
||||
"ProcedureMain.OnNodeComplete() node mismatch. EventNodeId={0}, EventNodeType={1}, EventSequenceIndex={2}; CurrentNodeId={3}, CurrentNodeType={4}, CurrentSequenceIndex={5}.",
|
||||
args.NodeId,
|
||||
args.NodeType,
|
||||
args.SequenceIndex,
|
||||
currentNode?.NodeId ?? 0,
|
||||
currentNode?.NodeType ?? RunNodeType.None,
|
||||
currentNode?.SequenceIndex ?? -1);
|
||||
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);
|
||||
|
||||
RunNodeCompletionSnapshot completionSnapshot = args.CompletionSnapshot != null
|
||||
? args.CompletionSnapshot.Clone()
|
||||
: _currentRunState?.CreateCurrentNodeContext()?.CreateCompletionSnapshot(null);
|
||||
BackpackInventoryData inventorySnapshot = completionSnapshot?.InventorySnapshot;
|
||||
if (inventorySnapshot == null && GameEntry.PlayerInventory != null)
|
||||
{
|
||||
inventorySnapshot = GameEntry.PlayerInventory.GetInventorySnapshot();
|
||||
}
|
||||
|
||||
if (completionSnapshot != null)
|
||||
{
|
||||
completionSnapshot.InventorySnapshot = inventorySnapshot;
|
||||
}
|
||||
|
||||
ProcedureMainParticipantTowerCleanupResult cleanupResult =
|
||||
ProcedureMainParticipantTowerCleanupService.RemoveBrokenParticipantTowers(
|
||||
inventorySnapshot,
|
||||
MaxParticipantTowerCount);
|
||||
if (cleanupResult.HasAnyRemovedTower && GameEntry.PlayerInventory != null)
|
||||
{
|
||||
GameEntry.PlayerInventory.ReplaceInventorySnapshot(inventorySnapshot);
|
||||
}
|
||||
|
||||
if (completionSnapshot != null)
|
||||
{
|
||||
completionSnapshot.InventorySnapshot = inventorySnapshot;
|
||||
}
|
||||
|
||||
ProcedureMainRunAdvanceResult advanceResult =
|
||||
ProcedureMainRunFlowService.TryAdvanceRun(_currentRunState, args.CompletionStatus, completionSnapshot);
|
||||
HandleRunAdvanceResult(advanceResult);
|
||||
if (cleanupResult.HasAnyRemovedTower &&
|
||||
advanceResult != ProcedureMainRunAdvanceResult.RunCompleted)
|
||||
{
|
||||
OpenRemovedParticipantTowerDialog(cleanupResult);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNodeMapNodeEnterRequested(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(sender is NodeMapForm) || !(e is NodeMapNodeEnterRequestedEventArgs args))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RunNodeState currentNode = _currentRunState?.CurrentNode;
|
||||
if (_currentRunState == null ||
|
||||
_flowPhase != ProcedureMainFlowPhase.Hub ||
|
||||
!_currentRunState.CanEnterCurrentNode ||
|
||||
currentNode == null)
|
||||
{
|
||||
Log.Warning(
|
||||
"ProcedureMain.OnNodeMapNodeEnterRequested() ignored. FlowPhase={0}, HasEnterableNode={1}.",
|
||||
_flowPhase,
|
||||
_currentRunState != null && _currentRunState.CanEnterCurrentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.SequenceIndex != currentNode.SequenceIndex || args.NodeType != currentNode.NodeType)
|
||||
{
|
||||
Log.Warning(
|
||||
"ProcedureMain.OnNodeMapNodeEnterRequested() ignored. Requested={0}#{1}, CurrentNode={2}#{3}.",
|
||||
args.NodeType,
|
||||
args.SequenceIndex,
|
||||
currentNode.NodeType,
|
||||
currentNode.SequenceIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (currentNode.NodeType)
|
||||
{
|
||||
case RunNodeType.Combat:
|
||||
case RunNodeType.BossCombat:
|
||||
ProcedureMainCombatEntryValidationResult validationResult =
|
||||
ProcedureMainCombatEntryValidationService.Validate(
|
||||
GameEntry.PlayerInventory != null
|
||||
? GameEntry.PlayerInventory.GetInventorySnapshot()
|
||||
: null);
|
||||
if (!validationResult.CanEnterCombat)
|
||||
{
|
||||
LogCombatEntryBlocked(currentNode, validationResult);
|
||||
OpenBlockedCombatDialog(validationResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameEntry.CombatNode.CurrentThemeType != currentNode.ThemeType)
|
||||
{
|
||||
GameEntry.CombatNode.OnInit(currentNode.ThemeType);
|
||||
}
|
||||
|
||||
GameEntry.CombatNode.StartCombat(
|
||||
currentNode.LinkedLevelId,
|
||||
_currentRunState.CreateCurrentNodeContext());
|
||||
return;
|
||||
case RunNodeType.Event:
|
||||
GameEntry.EventNode.StartEvent(_currentRunState.CreateCurrentNodeContext());
|
||||
return;
|
||||
case RunNodeType.Shop:
|
||||
GameEntry.ShopNode.StartShop(_currentRunState.CreateCurrentNodeContext());
|
||||
return;
|
||||
default:
|
||||
Log.Warning("ProcedureMain.OnNodeMapNodeEnterRequested() encountered unsupported node type: {0}.",
|
||||
currentNode.NodeType);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenBlockedCombatDialog(ProcedureMainCombatEntryValidationResult validationResult)
|
||||
{
|
||||
GameEntry.UIRouter.CloseUI(UIFormType.DialogForm);
|
||||
GameEntry.UIRouter.OpenUI(
|
||||
UIFormType.DialogForm,
|
||||
ProcedureMainCombatEntryValidationService.BuildBlockedCombatDialogRawData(validationResult));
|
||||
}
|
||||
|
||||
private void LogCombatEntryBlocked(
|
||||
RunNodeState currentNode,
|
||||
ProcedureMainCombatEntryValidationResult validationResult)
|
||||
{
|
||||
switch (validationResult?.BlockReason)
|
||||
{
|
||||
case ProcedureMainCombatEntryBlockReason.InventoryUnavailable:
|
||||
Log.Warning(
|
||||
"ProcedureMain blocked combat start. RunId={0}, NodeId={1}, NodeType={2}, SequenceIndex={3}, Reason={4}.",
|
||||
_currentRunState?.RunId,
|
||||
currentNode?.NodeId ?? 0,
|
||||
currentNode?.NodeType ?? RunNodeType.None,
|
||||
currentNode?.SequenceIndex ?? -1,
|
||||
ProcedureMainCombatEntryBlockReason.InventoryUnavailable);
|
||||
return;
|
||||
case ProcedureMainCombatEntryBlockReason.NoValidParticipantTower:
|
||||
Log.Warning(
|
||||
"ProcedureMain blocked combat start. RunId={0}, NodeId={1}, NodeType={2}, SequenceIndex={3}, Reason={4}, InvalidParticipantTowers={5}.",
|
||||
_currentRunState?.RunId,
|
||||
currentNode?.NodeId ?? 0,
|
||||
currentNode?.NodeType ?? RunNodeType.None,
|
||||
currentNode?.SequenceIndex ?? -1,
|
||||
ProcedureMainCombatEntryBlockReason.NoValidParticipantTower,
|
||||
ProcedureMainCombatEntryValidationService.BuildInvalidParticipantTowerLog(
|
||||
validationResult.ValidationSummary));
|
||||
return;
|
||||
default:
|
||||
Log.Warning(
|
||||
"ProcedureMain blocked combat start. RunId={0}, NodeId={1}, NodeType={2}, SequenceIndex={3}, Reason=Unknown.",
|
||||
_currentRunState?.RunId,
|
||||
currentNode?.NodeId ?? 0,
|
||||
currentNode?.NodeType ?? RunNodeType.None,
|
||||
currentNode?.SequenceIndex ?? -1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleRunAdvanceResult(ProcedureMainRunAdvanceResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case ProcedureMainRunAdvanceResult.NoChange:
|
||||
return;
|
||||
case ProcedureMainRunAdvanceResult.NodeException:
|
||||
Log.Info(
|
||||
"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,
|
||||
_currentRunState?.CurrentNode?.SequenceIndex ?? -1);
|
||||
EnterHubFlow();
|
||||
return;
|
||||
case ProcedureMainRunAdvanceResult.AdvancedToNextNode:
|
||||
LogNextNode();
|
||||
EnterHubFlow();
|
||||
return;
|
||||
case ProcedureMainRunAdvanceResult.RunCompleted:
|
||||
EnterRunCompletedPendingFinish();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnterHubFlow()
|
||||
{
|
||||
_flowPhase = ProcedureMainFlowPhase.Hub;
|
||||
_nodeMapFormUseCase?.SetRunState(_currentRunState);
|
||||
GameEntry.UIRouter.OpenUI(UIFormType.NodeMapForm);
|
||||
GameEntry.UIRouter.OpenUI(UIFormType.MainForm);
|
||||
}
|
||||
|
||||
private void EnterNodeFlow()
|
||||
{
|
||||
_flowPhase = ProcedureMainFlowPhase.NodeActive;
|
||||
CloseHubUI();
|
||||
}
|
||||
|
||||
private void EnterRunCompletedPendingFinish()
|
||||
{
|
||||
_flowPhase = ProcedureMainFlowPhase.RunCompletedPendingFinish;
|
||||
_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()
|
||||
{
|
||||
GameEntry.UIRouter.CloseUI(UIFormType.NodeMapForm);
|
||||
GameEntry.UIRouter.CloseUI(UIFormType.MainForm);
|
||||
}
|
||||
|
||||
private void LogNextNode()
|
||||
{
|
||||
RunNodeState nextNode = _currentRunState?.CurrentNode;
|
||||
if (nextNode == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Info(
|
||||
"ProcedureMain advanced run. RunId={0}, NextNodeType={1}, SequenceIndex={2}, LevelId={3}.",
|
||||
_currentRunState.RunId,
|
||||
nextNode.NodeType,
|
||||
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 OpenRemovedParticipantTowerDialog(ProcedureMainParticipantTowerCleanupResult cleanupResult)
|
||||
{
|
||||
GameEntry.UIRouter.CloseUI(UIFormType.DialogForm);
|
||||
GameEntry.UIRouter.OpenUI(
|
||||
UIFormType.DialogForm,
|
||||
ProcedureMainParticipantTowerCleanupService.BuildRemovedTowerDialogRawData(cleanupResult));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using GeometryTD.Definition;
|
||||
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public sealed class ProcedureMainCombatEntryValidationResult
|
||||
{
|
||||
public bool CanEnterCombat => BlockReason == ProcedureMainCombatEntryBlockReason.None;
|
||||
|
||||
public ProcedureMainCombatEntryBlockReason BlockReason { get; set; }
|
||||
|
||||
public CombatParticipantTowerValidationSummary ValidationSummary { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System.Text;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.UI;
|
||||
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public static class ProcedureMainCombatEntryValidationService
|
||||
{
|
||||
public static ProcedureMainCombatEntryValidationResult Validate(BackpackInventoryData inventory)
|
||||
{
|
||||
if (inventory == null)
|
||||
{
|
||||
return new ProcedureMainCombatEntryValidationResult
|
||||
{
|
||||
BlockReason = ProcedureMainCombatEntryBlockReason.InventoryUnavailable,
|
||||
ValidationSummary = new CombatParticipantTowerValidationSummary()
|
||||
};
|
||||
}
|
||||
|
||||
CombatParticipantTowerValidationSummary summary =
|
||||
CombatParticipantTowerValidationService.ValidateParticipantTowers(inventory);
|
||||
|
||||
return new ProcedureMainCombatEntryValidationResult
|
||||
{
|
||||
BlockReason = summary.HasAnyValidParticipantTower
|
||||
? ProcedureMainCombatEntryBlockReason.None
|
||||
: ProcedureMainCombatEntryBlockReason.NoValidParticipantTower,
|
||||
ValidationSummary = summary
|
||||
};
|
||||
}
|
||||
|
||||
public static string BuildInvalidParticipantTowerLog(
|
||||
CombatParticipantTowerValidationSummary summary)
|
||||
{
|
||||
if (summary?.InvalidResults == null || summary.InvalidResults.Count <= 0)
|
||||
{
|
||||
return "none";
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < summary.InvalidResults.Count; i++)
|
||||
{
|
||||
CombatParticipantTowerValidationResult result = summary.InvalidResults[i];
|
||||
if (result == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
builder.Append('#');
|
||||
builder.Append(result.TowerInstanceId);
|
||||
builder.Append(':');
|
||||
builder.Append(result.FailureReason);
|
||||
}
|
||||
|
||||
return builder.Length > 0 ? builder.ToString() : "none";
|
||||
}
|
||||
|
||||
public static DialogFormRawData BuildBlockedCombatDialogRawData(
|
||||
ProcedureMainCombatEntryValidationResult validationResult)
|
||||
{
|
||||
return new DialogFormRawData
|
||||
{
|
||||
Mode = 1,
|
||||
Title = "无法进入战斗",
|
||||
Message = BuildBlockedCombatDialogMessage(validationResult),
|
||||
PauseGame = false,
|
||||
ConfirmText = "知道了"
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildBlockedCombatDialogMessage(
|
||||
ProcedureMainCombatEntryValidationResult validationResult)
|
||||
{
|
||||
if (validationResult == null)
|
||||
{
|
||||
return "当前无法确认出战信息,请稍后重试。";
|
||||
}
|
||||
|
||||
switch (validationResult.BlockReason)
|
||||
{
|
||||
case ProcedureMainCombatEntryBlockReason.InventoryUnavailable:
|
||||
return "当前无法读取库存快照,暂时不能进入战斗。请重新进入本轮流程后再试。";
|
||||
case ProcedureMainCombatEntryBlockReason.NoValidParticipantTower:
|
||||
return BuildNoValidParticipantTowerMessage(validationResult.ValidationSummary);
|
||||
default:
|
||||
return "当前出战校验未通过,暂时不能进入战斗。";
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildNoValidParticipantTowerMessage(
|
||||
CombatParticipantTowerValidationSummary summary)
|
||||
{
|
||||
if (summary?.InvalidResults == null || summary.InvalidResults.Count <= 0)
|
||||
{
|
||||
return "参战区至少需要 1 座完整装配了枪口、轴承、底座的塔,才能进入战斗。";
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append("参战区没有可出战的完整塔。");
|
||||
for (int i = 0; i < summary.InvalidResults.Count; i++)
|
||||
{
|
||||
CombatParticipantTowerValidationResult result = summary.InvalidResults[i];
|
||||
if (result == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append('\n');
|
||||
builder.Append("塔 #");
|
||||
builder.Append(result.TowerInstanceId);
|
||||
builder.Append(' ');
|
||||
builder.Append(CombatParticipantTowerValidationText.GetFailureReasonMessage(result.FailureReason));
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public static class ProcedureMainNodeEventGuardService
|
||||
{
|
||||
public static bool MatchesCurrentNode(
|
||||
RunState runState,
|
||||
int nodeId,
|
||||
RunNodeType nodeType,
|
||||
int sequenceIndex)
|
||||
{
|
||||
RunNodeState currentNode = runState?.CurrentNode;
|
||||
if (currentNode == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (nodeId <= 0 || nodeId == currentNode.NodeId) &&
|
||||
(nodeType == RunNodeType.None || nodeType == currentNode.NodeType) &&
|
||||
(sequenceIndex < 0 || sequenceIndex == currentNode.SequenceIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.UI;
|
||||
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public sealed class ProcedureMainParticipantTowerCleanupResult
|
||||
{
|
||||
public List<CombatParticipantTowerValidationResult> RemovedResults { get; } = new();
|
||||
|
||||
public bool HasAnyRemovedTower => RemovedResults.Count > 0;
|
||||
}
|
||||
|
||||
public static class ProcedureMainParticipantTowerCleanupService
|
||||
{
|
||||
public static ProcedureMainParticipantTowerCleanupResult RemoveBrokenParticipantTowers(
|
||||
BackpackInventoryData inventory,
|
||||
int maxParticipantCount)
|
||||
{
|
||||
ProcedureMainParticipantTowerCleanupResult result = new ProcedureMainParticipantTowerCleanupResult();
|
||||
if (inventory == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
CombatParticipantTowerValidationSummary summary =
|
||||
CombatParticipantTowerValidationService.ValidateParticipantTowers(inventory);
|
||||
if (summary.InvalidResults == null || summary.InvalidResults.Count <= 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
HashSet<long> removedTowerIds = new HashSet<long>();
|
||||
for (int i = 0; i < summary.InvalidResults.Count; i++)
|
||||
{
|
||||
CombatParticipantTowerValidationResult invalidResult = summary.InvalidResults[i];
|
||||
if (invalidResult == null ||
|
||||
invalidResult.TowerInstanceId <= 0 ||
|
||||
!IsBrokenFailureReason(invalidResult.FailureReason) ||
|
||||
!removedTowerIds.Add(invalidResult.TowerInstanceId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (InventoryParticipantUtility.TryRemoveParticipantTower(
|
||||
inventory,
|
||||
invalidResult.TowerInstanceId,
|
||||
maxParticipantCount))
|
||||
{
|
||||
result.RemovedResults.Add(invalidResult);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static DialogFormRawData BuildRemovedTowerDialogRawData(
|
||||
ProcedureMainParticipantTowerCleanupResult cleanupResult)
|
||||
{
|
||||
return new DialogFormRawData
|
||||
{
|
||||
Mode = 1,
|
||||
Title = "出战塔已损坏",
|
||||
Message = BuildRemovedTowerDialogMessage(cleanupResult),
|
||||
PauseGame = false,
|
||||
ConfirmText = "知道了"
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildRemovedTowerDialogMessage(
|
||||
ProcedureMainParticipantTowerCleanupResult cleanupResult)
|
||||
{
|
||||
if (cleanupResult == null || !cleanupResult.HasAnyRemovedTower)
|
||||
{
|
||||
return "当前没有需要移出参战区的损坏防御塔。";
|
||||
}
|
||||
|
||||
System.Text.StringBuilder builder = new System.Text.StringBuilder();
|
||||
builder.Append("以下防御塔已损坏,已自动移出参战区:");
|
||||
for (int i = 0; i < cleanupResult.RemovedResults.Count; i++)
|
||||
{
|
||||
CombatParticipantTowerValidationResult removedResult = cleanupResult.RemovedResults[i];
|
||||
if (removedResult == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append('\n');
|
||||
builder.Append("塔 #");
|
||||
builder.Append(removedResult.TowerInstanceId);
|
||||
builder.Append(' ');
|
||||
builder.Append(CombatParticipantTowerValidationText.GetFailureReasonMessage(removedResult.FailureReason));
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static bool IsBrokenFailureReason(CombatParticipantTowerValidationFailureReason failureReason)
|
||||
{
|
||||
return failureReason == CombatParticipantTowerValidationFailureReason.BrokenMuzzleComponent ||
|
||||
failureReason == CombatParticipantTowerValidationFailureReason.BrokenBearingComponent ||
|
||||
failureReason == CombatParticipantTowerValidationFailureReason.BrokenBaseComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using GeometryTD.Definition;
|
||||
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using GeometryTD.Definition;
|
||||
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public static class ProcedureMainRunFlowService
|
||||
{
|
||||
public static ProcedureMainRunAdvanceResult TryAdvanceRun(
|
||||
RunState runState,
|
||||
RunNodeCompletionStatus completionStatus,
|
||||
RunNodeCompletionSnapshot completionSnapshot)
|
||||
{
|
||||
if (!RunStateAdvanceService.TryCompleteCurrentNode(runState, completionStatus, completionSnapshot))
|
||||
{
|
||||
return ProcedureMainRunAdvanceResult.NoChange;
|
||||
}
|
||||
|
||||
if (runState != null && runState.IsCompleted)
|
||||
{
|
||||
return ProcedureMainRunAdvanceResult.RunCompleted;
|
||||
}
|
||||
|
||||
return completionStatus == RunNodeCompletionStatus.Exception
|
||||
? ProcedureMainRunAdvanceResult.NodeException
|
||||
: ProcedureMainRunAdvanceResult.AdvancedToNextNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Factory;
|
||||
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public enum RunNodeType
|
||||
{
|
||||
None = 0,
|
||||
Combat = 1,
|
||||
Event = 2,
|
||||
Shop = 3,
|
||||
BossCombat = 4
|
||||
}
|
||||
|
||||
public enum RunNodeStatus
|
||||
{
|
||||
Locked = 0,
|
||||
Available = 1,
|
||||
Completed = 2,
|
||||
Exception = 3,
|
||||
Skipped = 4
|
||||
}
|
||||
|
||||
public enum RunNodeCompletionStatus
|
||||
{
|
||||
None = 0,
|
||||
Completed = 1,
|
||||
Exception = 2
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class RunNodeSeed
|
||||
{
|
||||
public int NodeId { get; set; }
|
||||
public RunNodeType NodeType { get; set; }
|
||||
public LevelThemeType ThemeType { get; set; }
|
||||
public int LinkedLevelId { get; set; }
|
||||
public int SequenceIndex { get; set; } = -1;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class RunNodeState
|
||||
{
|
||||
public int NodeId { get; internal set; }
|
||||
public RunNodeType NodeType { get; internal set; }
|
||||
public LevelThemeType ThemeType { get; internal set; }
|
||||
public int LinkedLevelId { get; internal set; }
|
||||
public RunNodeStatus Status { get; internal set; }
|
||||
public int SequenceIndex { get; internal set; }
|
||||
|
||||
internal RunNodeState Clone()
|
||||
{
|
||||
return new RunNodeState
|
||||
{
|
||||
NodeId = NodeId,
|
||||
NodeType = NodeType,
|
||||
ThemeType = ThemeType,
|
||||
LinkedLevelId = LinkedLevelId,
|
||||
Status = Status,
|
||||
SequenceIndex = SequenceIndex
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class RunItemState
|
||||
{
|
||||
public int ItemId { get; set; }
|
||||
public int StackCount { get; set; }
|
||||
|
||||
internal RunItemState Clone()
|
||||
{
|
||||
return new RunItemState
|
||||
{
|
||||
ItemId = ItemId,
|
||||
StackCount = StackCount
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class RunNodeExecutionContext
|
||||
{
|
||||
public string RunId { get; set; }
|
||||
public int RunSeed { get; set; }
|
||||
public int NodeId { get; set; }
|
||||
public RunNodeType NodeType { get; set; }
|
||||
public int SequenceIndex { get; set; }
|
||||
public LevelThemeType ThemeType { get; set; }
|
||||
public int ThemeStageIndex { get; set; }
|
||||
public List<LevelThemeType> CurrentThemePool { get; set; } = new List<LevelThemeType>();
|
||||
public List<LevelThemeType> ThemeHistory { get; set; } = new List<LevelThemeType>();
|
||||
public int CurrentNodeContinueChallengeLayer { get; set; }
|
||||
public List<RunItemState> RunItems { get; set; } = new List<RunItemState>();
|
||||
|
||||
internal RunNodeExecutionContext Clone()
|
||||
{
|
||||
return new RunNodeExecutionContext
|
||||
{
|
||||
RunId = RunId,
|
||||
RunSeed = RunSeed,
|
||||
NodeId = NodeId,
|
||||
NodeType = NodeType,
|
||||
SequenceIndex = SequenceIndex,
|
||||
ThemeType = ThemeType,
|
||||
ThemeStageIndex = ThemeStageIndex,
|
||||
CurrentThemePool = RunStateCloneUtility.CloneThemeList(CurrentThemePool),
|
||||
ThemeHistory = RunStateCloneUtility.CloneThemeList(ThemeHistory),
|
||||
CurrentNodeContinueChallengeLayer = CurrentNodeContinueChallengeLayer,
|
||||
RunItems = RunStateCloneUtility.CloneRunItems(RunItems)
|
||||
};
|
||||
}
|
||||
|
||||
public RunNodeCompletionSnapshot CreateCompletionSnapshot(BackpackInventoryData inventorySnapshot)
|
||||
{
|
||||
return new RunNodeCompletionSnapshot
|
||||
{
|
||||
InventorySnapshot = InventoryCloneUtility.CloneInventory(inventorySnapshot),
|
||||
ThemeType = ThemeType,
|
||||
ThemeStageIndex = ThemeStageIndex,
|
||||
CurrentThemePool = RunStateCloneUtility.CloneThemeList(CurrentThemePool),
|
||||
ThemeHistory = RunStateCloneUtility.CloneThemeList(ThemeHistory),
|
||||
CurrentNodeContinueChallengeLayer = CurrentNodeContinueChallengeLayer,
|
||||
RunItems = RunStateCloneUtility.CloneRunItems(RunItems)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class RunNodeCompletionSnapshot
|
||||
{
|
||||
public BackpackInventoryData InventorySnapshot { get; set; }
|
||||
public LevelThemeType ThemeType { get; set; }
|
||||
public int ThemeStageIndex { get; set; }
|
||||
public List<LevelThemeType> CurrentThemePool { get; set; } = new List<LevelThemeType>();
|
||||
public List<LevelThemeType> ThemeHistory { get; set; } = new List<LevelThemeType>();
|
||||
public int CurrentNodeContinueChallengeLayer { get; set; }
|
||||
public List<RunItemState> RunItems { get; set; } = new List<RunItemState>();
|
||||
|
||||
internal RunNodeCompletionSnapshot Clone()
|
||||
{
|
||||
return new RunNodeCompletionSnapshot
|
||||
{
|
||||
InventorySnapshot = InventoryCloneUtility.CloneInventory(InventorySnapshot),
|
||||
ThemeType = ThemeType,
|
||||
ThemeStageIndex = ThemeStageIndex,
|
||||
CurrentThemePool = RunStateCloneUtility.CloneThemeList(CurrentThemePool),
|
||||
ThemeHistory = RunStateCloneUtility.CloneThemeList(ThemeHistory),
|
||||
CurrentNodeContinueChallengeLayer = CurrentNodeContinueChallengeLayer,
|
||||
RunItems = RunStateCloneUtility.CloneRunItems(RunItems)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class RunState
|
||||
{
|
||||
private readonly List<RunNodeState> _nodes;
|
||||
|
||||
internal RunState(
|
||||
string runId,
|
||||
int runSeed,
|
||||
LevelThemeType themeType,
|
||||
List<RunNodeState> nodes,
|
||||
BackpackInventoryData runInventorySnapshot)
|
||||
{
|
||||
RunId = string.IsNullOrWhiteSpace(runId) ? Guid.NewGuid().ToString("N") : runId;
|
||||
RunSeed = runSeed == 0 ? RunStateFactory.CreateRunSeed() : runSeed;
|
||||
ThemeType = themeType;
|
||||
_nodes = nodes ?? new List<RunNodeState>();
|
||||
RunInventorySnapshot = InventoryCloneUtility.CloneInventory(runInventorySnapshot);
|
||||
ThemeStageIndex = 0;
|
||||
CurrentThemePool = CreateDefaultThemeList(themeType);
|
||||
ThemeHistory = CreateDefaultThemeList(themeType);
|
||||
CurrentNodeContinueChallengeLayer = 0;
|
||||
RunItems = new List<RunItemState>();
|
||||
CurrentNodeIndex = _nodes.Count > 0 ? 0 : -1;
|
||||
IsCompleted = _nodes.Count <= 0;
|
||||
}
|
||||
|
||||
public string RunId { get; internal set; }
|
||||
|
||||
public int RunSeed { get; internal set; }
|
||||
|
||||
public LevelThemeType ThemeType { get; internal set; }
|
||||
|
||||
public int CurrentNodeIndex { get; internal set; }
|
||||
|
||||
public bool IsCompleted { get; internal set; }
|
||||
|
||||
public BackpackInventoryData RunInventorySnapshot { get; internal set; }
|
||||
|
||||
public int ThemeStageIndex { get; internal set; }
|
||||
|
||||
public List<LevelThemeType> CurrentThemePool { get; internal set; }
|
||||
|
||||
public List<LevelThemeType> ThemeHistory { get; internal set; }
|
||||
|
||||
public int CurrentNodeContinueChallengeLayer { get; internal set; }
|
||||
|
||||
public List<RunItemState> RunItems { get; internal set; }
|
||||
|
||||
public IReadOnlyList<RunNodeState> Nodes => _nodes;
|
||||
|
||||
public int NodeCount => _nodes.Count;
|
||||
|
||||
public RunNodeState CurrentNode
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CurrentNodeIndex < 0 || CurrentNodeIndex >= _nodes.Count)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _nodes[CurrentNodeIndex];
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanEnterCurrentNode => !IsCompleted && CurrentNode != null && CurrentNode.Status == RunNodeStatus.Available;
|
||||
|
||||
public RunNodeState GetNodeBySequenceIndex(int sequenceIndex)
|
||||
{
|
||||
if (sequenceIndex < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (RunNodeState nodeState in _nodes)
|
||||
{
|
||||
if (nodeState.SequenceIndex == sequenceIndex)
|
||||
{
|
||||
return nodeState;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool IsBossNode(RunNodeState nodeState)
|
||||
{
|
||||
return nodeState != null && nodeState.NodeType == RunNodeType.BossCombat;
|
||||
}
|
||||
|
||||
public int CompletedNodeCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
foreach (var nodeState in _nodes)
|
||||
{
|
||||
if (nodeState.Status == RunNodeStatus.Completed)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
internal void ReplaceInventorySnapshot(BackpackInventoryData inventorySnapshot)
|
||||
{
|
||||
RunInventorySnapshot = InventoryCloneUtility.CloneInventory(inventorySnapshot);
|
||||
}
|
||||
|
||||
public RunNodeExecutionContext CreateCurrentNodeContext()
|
||||
{
|
||||
RunNodeState currentNode = CurrentNode;
|
||||
if (currentNode == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new RunNodeExecutionContext
|
||||
{
|
||||
RunId = RunId,
|
||||
RunSeed = RunSeed,
|
||||
NodeId = currentNode.NodeId,
|
||||
NodeType = currentNode.NodeType,
|
||||
SequenceIndex = currentNode.SequenceIndex,
|
||||
ThemeType = ThemeType,
|
||||
ThemeStageIndex = ThemeStageIndex,
|
||||
CurrentThemePool = RunStateCloneUtility.CloneThemeList(CurrentThemePool),
|
||||
ThemeHistory = RunStateCloneUtility.CloneThemeList(ThemeHistory),
|
||||
CurrentNodeContinueChallengeLayer = CurrentNodeContinueChallengeLayer,
|
||||
RunItems = RunStateCloneUtility.CloneRunItems(RunItems)
|
||||
};
|
||||
}
|
||||
|
||||
public void ApplyCompletionSnapshot(RunNodeCompletionSnapshot completionSnapshot)
|
||||
{
|
||||
RunInventorySnapshot = InventoryCloneUtility.CloneInventory(completionSnapshot.InventorySnapshot);
|
||||
ThemeType = completionSnapshot.ThemeType;
|
||||
ThemeStageIndex = completionSnapshot.ThemeStageIndex;
|
||||
CurrentThemePool = RunStateCloneUtility.CloneThemeList(completionSnapshot.CurrentThemePool);
|
||||
ThemeHistory = RunStateCloneUtility.CloneThemeList(completionSnapshot.ThemeHistory);
|
||||
CurrentNodeContinueChallengeLayer = completionSnapshot.CurrentNodeContinueChallengeLayer;
|
||||
RunItems = RunStateCloneUtility.CloneRunItems(completionSnapshot.RunItems);
|
||||
}
|
||||
|
||||
private static List<LevelThemeType> CreateDefaultThemeList(LevelThemeType themeType)
|
||||
{
|
||||
List<LevelThemeType> themes = new List<LevelThemeType>();
|
||||
if (themeType != LevelThemeType.None)
|
||||
{
|
||||
themes.Add(themeType);
|
||||
}
|
||||
|
||||
return themes;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class RunStateCloneUtility
|
||||
{
|
||||
internal static List<LevelThemeType> CloneThemeList(IReadOnlyList<LevelThemeType> source)
|
||||
{
|
||||
List<LevelThemeType> cloned = new List<LevelThemeType>();
|
||||
if (source == null)
|
||||
{
|
||||
return cloned;
|
||||
}
|
||||
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
cloned.Add(source[i]);
|
||||
}
|
||||
|
||||
return cloned;
|
||||
}
|
||||
|
||||
internal static List<RunItemState> CloneRunItems(IReadOnlyList<RunItemState> source)
|
||||
{
|
||||
List<RunItemState> cloned = new List<RunItemState>();
|
||||
if (source == null)
|
||||
{
|
||||
return cloned;
|
||||
}
|
||||
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
RunItemState item = source[i];
|
||||
if (item != null)
|
||||
{
|
||||
cloned.Add(item.Clone());
|
||||
}
|
||||
}
|
||||
|
||||
return cloned;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public static class RunStateAdvanceService
|
||||
{
|
||||
public static bool TryCompleteCurrentNode(
|
||||
RunState runState,
|
||||
RunNodeCompletionStatus completionStatus,
|
||||
RunNodeCompletionSnapshot completionSnapshot)
|
||||
{
|
||||
if (runState == null || runState.IsCompleted)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RunNodeState currentNode = runState.CurrentNode;
|
||||
if (currentNode == null || currentNode.Status != RunNodeStatus.Available)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
runState.ApplyCompletionSnapshot(completionSnapshot);
|
||||
|
||||
if (completionStatus == RunNodeCompletionStatus.Exception)
|
||||
{
|
||||
currentNode.Status = RunNodeStatus.Exception;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (completionStatus != RunNodeCompletionStatus.Completed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
currentNode.Status = RunNodeStatus.Completed;
|
||||
|
||||
int nextIndex = runState.CurrentNodeIndex + 1;
|
||||
if (nextIndex >= runState.Nodes.Count)
|
||||
{
|
||||
runState.CurrentNodeContinueChallengeLayer = 0;
|
||||
runState.CurrentNodeIndex = runState.Nodes.Count;
|
||||
runState.IsCompleted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
runState.CurrentNodeIndex = nextIndex;
|
||||
runState.CurrentNodeContinueChallengeLayer = 0;
|
||||
RunNodeState nextNode = runState.CurrentNode;
|
||||
if (nextNode != null && nextNode.Status == RunNodeStatus.Locked)
|
||||
{
|
||||
nextNode.Status = RunNodeStatus.Available;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user