geometry-tower-defense/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMain.cs

469 lines
19 KiB
C#

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;
}
}
}