diff --git a/HKMP/Game/Client/Entity/Entity.cs b/HKMP/Game/Client/Entity/Entity.cs index 5b2bebb..9b1bfef 100644 --- a/HKMP/Game/Client/Entity/Entity.cs +++ b/HKMP/Game/Client/Entity/Entity.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -275,6 +276,7 @@ private void ProcessHostFsm(PlayMakerFSM fsm) { for (var i = 0; i < fsm.FsmStates.Length; i++) { var state = fsm.FsmStates[i]; + var stateName = state.Name; for (var j = 0; j < state.Actions.Length; j++) { var action = state.Actions[j]; @@ -286,18 +288,56 @@ private void ProcessHostFsm(PlayMakerFSM fsm) { continue; } - _hookedActions[action] = new HookedEntityAction { - Action = action, - FsmIndex = _fsms.Host.IndexOf(fsm), - StateIndex = i, - ActionIndex = j - }; - // Logger.Info($"Created hooked action: {action.GetType()}, {_fsms.Host.IndexOf(fsm)}, {state.Name}, {j}"); - - if (!_hookedTypes.Contains(action.GetType())) { - _hookedTypes.Add(action.GetType()); + Func actionFunc; + var stateIndex = i; + var actionIndex = j; + + // Because it can happen that the action from a state is not properly initialized (usually when the + // entity is made active later in the scene and uses an FSM template), we need to check it here + // and create a coroutine that waits for FSM to properly initialize all its states and only then + // continue. When continuing, we no longer use the same instance of the action and state, so we + // use a function that re-obtains the correct action to make a hook from + // This is all complicated logic simply because it happens (empirically) once for the Grimm boss fight + if (action.Fsm == null) { + actionFunc = () => fsm.FsmStates[stateIndex].Actions[actionIndex]; + var checkFunc = () => actionFunc.Invoke().Fsm == null; + var sceneName = fsm.gameObject.scene.name; + + Logger.Debug($"Registering delayed hook for action that is not valid: {action.GetType()}, {_fsms.Host.IndexOf(fsm)}, {stateName}, {actionIndex}"); + + MonoBehaviourUtil.Instance.StartCoroutine(WaitForActionInitialization()); + IEnumerator WaitForActionInitialization() { + while (checkFunc.Invoke()) { + if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name != sceneName) { + yield break; + } + + yield return new WaitForSeconds(0.1f); + } + + Logger.Debug("Delayed hook action is now valid, continuing..."); + CreateHookedAction(); + } + } else { + actionFunc = () => action; + CreateHookedAction(); + } - FsmActionHooks.RegisterFsmStateActionType(action.GetType(), OnActionEntered); + void CreateHookedAction() { + var hookedAction = actionFunc.Invoke(); + + _hookedActions[hookedAction] = new HookedEntityAction { + Action = hookedAction, + FsmIndex = _fsms.Host.IndexOf(fsm), + StateIndex = stateIndex, + ActionIndex = actionIndex + }; + Logger.Info( + $"Created hooked action: {hookedAction.GetType()}, {_fsms.Host.IndexOf(fsm)}, {stateName}, {actionIndex}"); + + if (_hookedTypes.Add(hookedAction.GetType())) { + FsmActionHooks.RegisterFsmStateActionType(hookedAction.GetType(), OnActionEntered); + } } } } diff --git a/HKMP/Game/Client/Entity/EntityInitializer.cs b/HKMP/Game/Client/Entity/EntityInitializer.cs index 8327f46..f9e6168 100644 --- a/HKMP/Game/Client/Entity/EntityInitializer.cs +++ b/HKMP/Game/Client/Entity/EntityInitializer.cs @@ -78,9 +78,14 @@ public static void InitializeFsm(PlayMakerFSM fsm) { // Now we can loop over the states in the same order as our "InitStateNames" array foreach (var state in statesToInit) { Logger.Debug($"Found initialization state: {state.Name}, executing actions"); + + // The index of the state in the FSM, NOT in the list of states to initialize + var stateIndex = Array.IndexOf(fsm.FsmStates, state); // Go over each action and try to execute it by applying empty data to it - foreach (var action in state.Actions) { + for (var actionIndex = 0; actionIndex < state.Actions.Length; actionIndex++) { + var action = state.Actions[actionIndex]; + if (!action.Enabled) { continue; } @@ -93,15 +98,24 @@ public static void InitializeFsm(PlayMakerFSM fsm) { if (action.Fsm == null) { Logger.Debug("Initializing FSM and action.Fsm is null, starting coroutine"); + var finalActionIndex = actionIndex; + var sceneName = fsm.gameObject.scene.name; + var actionFunc = () => fsm.FsmStates[stateIndex].Actions[finalActionIndex]; + var checkFunc = () => actionFunc.Invoke().Fsm == null; + MonoBehaviourUtil.Instance.StartCoroutine(WaitForActionInitialization()); IEnumerator WaitForActionInitialization() { - while (action.Fsm == null) { + while (checkFunc.Invoke()) { + if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name != sceneName) { + yield break; + } + yield return new WaitForSeconds(0.1f); } - Logger.Debug("Initializing FSM action completed"); + Logger.Debug($"Initializing FSM action completed, executing action: {actionFunc.Invoke().GetType()}"); - EntityFsmActions.ApplyNetworkDataFromAction(null, action); + EntityFsmActions.ApplyNetworkDataFromAction(null, actionFunc.Invoke()); } continue;