using GameFramework.Event; using GameFramework.Fsm; using GameFramework.Procedure; using GeometryTD.CustomEvent; using GeometryTD.Definition; using GeometryTD.UI; using UnityGameFramework.Runtime; namespace GeometryTD.Procedure { public class ProcedureMain : ProcedureBase { public override bool UseNativeDialog => false; private RepoFormUseCase _repoFormUseCase; private NodeMapFormUseCase _nodeMapFormUseCase; private RunState _currentRunState; #region FSM protected override void OnEnter(IFsm procedureOwner) { 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); GameEntry.EventNode.OnInit(); GameEntry.CombatNode.OnInit(LevelThemeType.Plain); GameEntry.ShopNode.OnInit(); GameEntry.PlayerInventory?.OnInit(); _currentRunState = RunStateFactory.CreateFixedRun( LevelThemeType.Plain, GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null); _repoFormUseCase = new RepoFormUseCase(); GameEntry.UIRouter.BindUIUseCase(UIFormType.RepoForm, _repoFormUseCase); _nodeMapFormUseCase = new NodeMapFormUseCase(); _nodeMapFormUseCase.SetRunState(_currentRunState); GameEntry.UIRouter.BindUIUseCase(UIFormType.NodeMapForm, _nodeMapFormUseCase); OpenHubUI(); } protected override void OnUpdate(IFsm procedureOwner, float elapseSeconds, float realElapseSeconds) { base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds); GameEntry.CombatNode.OnUpdate(elapseSeconds, realElapseSeconds); } protected override void OnLeave(IFsm procedureOwner, bool isShutdown) { 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); _repoFormUseCase = null; _nodeMapFormUseCase = null; _currentRunState = null; 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 (currentNode != null && ((args.NodeId > 0 && args.NodeId != currentNode.NodeId) || (args.NodeType != RunNodeType.None && args.NodeType != currentNode.NodeType) || (args.SequenceIndex >= 0 && args.SequenceIndex != currentNode.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, currentNode.NodeType, currentNode.SequenceIndex); } 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); GameEntry.UIRouter.CloseUI(UIFormType.NodeMapForm); GameEntry.UIRouter.CloseUI(UIFormType.MainForm); } 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; } BackpackInventoryData snapshot = args.InventorySnapshotAfterNode; if (snapshot == null && GameEntry.PlayerInventory != null) { snapshot = GameEntry.PlayerInventory.GetInventorySnapshot(); } AdvanceRunState(args.Succeeded, snapshot); OpenHubUI(); } private void OnCombatFailureReturn(object sender, GameEventArgs e) { if (!(e is CombatFailureReturnEventArgs)) { return; } BackpackInventoryData snapshot = GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null; AdvanceRunState(false, snapshot); OpenHubUI(); } private void OnNodeMapNodeEnterRequested(object sender, GameEventArgs e) { if (!(sender is NodeMapForm) || !(e is NodeMapNodeEnterRequestedEventArgs args)) { return; } RunNodeState currentNode = _currentRunState?.CurrentNode; if (_currentRunState == null || !_currentRunState.CanEnterCurrentNode || currentNode == null) { Log.Warning("ProcedureMain.OnNodeMapNodeEnterRequested() ignored. Current run has no enterable node."); 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: if (!HasAvailableParticipantTower()) { Log.Warning("ProcedureMain blocked combat start. No participant tower is available."); return; } GameEntry.CombatNode.StartCombat( currentNode.LinkedLevelId, _currentRunState.RunId, currentNode.NodeId, currentNode.NodeType, currentNode.SequenceIndex); return; case RunNodeType.Event: GameEntry.EventNode.StartEvent( _currentRunState.RunId, currentNode.NodeId, currentNode.NodeType, currentNode.SequenceIndex); return; case RunNodeType.Shop: GameEntry.ShopNode.StartShop( _currentRunState.RunId, currentNode.NodeId, currentNode.NodeType, currentNode.SequenceIndex); return; default: Log.Warning("ProcedureMain.OnNodeMapNodeEnterRequested() encountered unsupported node type: {0}.", currentNode.NodeType); return; } } private void AdvanceRunState(bool succeeded, BackpackInventoryData snapshot) { if (_currentRunState == null) { return; } if (!RunStateAdvanceService.TryCompleteCurrentNode(_currentRunState, succeeded, snapshot)) { return; } if (_currentRunState.IsCompleted) { Log.Info("ProcedureMain run completed. RunId={0}", _currentRunState.RunId); return; } RunNodeState nextNode = _currentRunState.CurrentNode; if (nextNode != null) { Log.Info( "ProcedureMain advanced run. RunId={0}, NextNodeType={1}, SequenceIndex={2}, LevelId={3}.", _currentRunState.RunId, nextNode.NodeType, nextNode.SequenceIndex, nextNode.LinkedLevelId); } } private static bool HasAvailableParticipantTower() { return GameEntry.PlayerInventory != null && GameEntry.PlayerInventory.GetParticipantTowerSnapshot().Count > 0; } private void OpenHubUI() { _nodeMapFormUseCase?.SetRunState(_currentRunState); GameEntry.UIRouter.OpenUI(UIFormType.NodeMapForm); GameEntry.UIRouter.OpenUI(UIFormType.MainForm); } } }