469 lines
19 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|