diff --git a/HKMP/Game/Client/ClientManager.cs b/HKMP/Game/Client/ClientManager.cs index 48dbc10..28fe1be 100644 --- a/HKMP/Game/Client/ClientManager.cs +++ b/HKMP/Game/Client/ClientManager.cs @@ -190,7 +190,7 @@ ModSettings modSettings _saveManager.Initialize(); new PauseManager(netClient).RegisterHooks(); - new GamePatcher().RegisterHooks(); + new GamePatcher(netClient).RegisterHooks(); new FsmPatcher().RegisterHooks(); _commandManager = new ClientCommandManager(); diff --git a/HKMP/Game/Client/GamePatcher.cs b/HKMP/Game/Client/GamePatcher.cs index d592825..793293c 100644 --- a/HKMP/Game/Client/GamePatcher.cs +++ b/HKMP/Game/Client/GamePatcher.cs @@ -1,5 +1,7 @@ using System; using System.Reflection; +using GlobalEnums; +using Hkmp.Networking.Client; using Modding; using Mono.Cecil.Cil; using MonoMod.Cil; @@ -19,10 +21,19 @@ internal class GamePatcher { /// private const BindingFlags BindingFlags = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance; + /// + /// The NetClient instance to check if we are connected to a server. + /// + private readonly NetClient _netClient; + /// /// The IL Hook for the bridge lever method. /// private ILHook _bridgeLeverIlHook; + + public GamePatcher(NetClient netClient) { + _netClient = netClient; + } /// /// Register the hooks. @@ -41,6 +52,8 @@ public void RegisterHooks() { _bridgeLeverIlHook = new ILHook(type.GetMethod("MoveNext", BindingFlags), BridgeLeverOnOpenBridge); On.HutongGames.PlayMaker.Actions.CallMethodProper.DoMethodCall += CallMethodProperOnDoMethodCall; + + IL.CameraLockArea.IsInApplicableGameState += CameraLockAreaOnIsInApplicableGameState; } /// @@ -58,6 +71,8 @@ public void DeregisterHooks() { _bridgeLeverIlHook?.Dispose(); On.HutongGames.PlayMaker.Actions.CallMethodProper.DoMethodCall -= CallMethodProperOnDoMethodCall; + + IL.CameraLockArea.IsInApplicableGameState -= CameraLockAreaOnIsInApplicableGameState; } /// @@ -473,4 +488,66 @@ HutongGames.PlayMaker.Actions.CallMethodProper self orig(self); } } + + /// + /// IL Hook for the 'IsInApplicableGameState' method in 'CameraLockArea'. This is used to add a check + /// for being in the pause menu and connected to a server. Otherwise, the camera will sometimes not lock while + /// in the pause menu during host transfers. + /// + private void CameraLockAreaOnIsInApplicableGameState(ILContext il) { + try { + // Create a cursor for this context + var c = new ILCursor(il); + + // Goto after the first 'gameState' check in the method + // IL_0011: ldloc.0 // unsafeInstance + // IL_0012: ldfld valuetype GlobalEnums.GameState GameManager::gameState + // IL_0017: ldc.i4.4 + // IL_0018: beq.s IL_0024 + c.GotoNext( + MoveType.After, + i => i.MatchLdloc(0), + i => i.MatchLdfld(typeof(global::GameManager), "gameState"), + i => i.MatchLdcI4(4), + i => i.MatchBeq(out _) + ); + + // Define a label for branching to if the conditions fail + var afterChecksLabel = c.DefineLabel(); + + // Load GameManager 'unsafeInstance' onto evaluation stack + c.Emit(OpCodes.Ldloc, 0); + // Check if the game state is paused and push a boolean onto the stack based on the result + c.EmitDelegate>(gm => gm.gameState == GameState.PAUSED); + // If the game state is not paused, we branch to the last check in the method + c.Emit(OpCodes.Brfalse, afterChecksLabel); + + // Check if the NetClient is connected and push a boolean onto the stack based on the result + c.EmitDelegate(() => _netClient.IsConnected); + // If we are not connected, we branch to the last check in the method + c.Emit(OpCodes.Brfalse, afterChecksLabel); + + var returnTrueLabel = c.DefineLabel(); + + // If the previous two checks succeeded, we branch to the return true label + c.Emit(OpCodes.Br, returnTrueLabel); + + // Mark the label after our new checks but before the last check of the method + c.MarkLabel(afterChecksLabel); + + // Goto before the 'return true' IL so we can branch here if our checks succeed + // IL_0024: ldc.i4.1 + // IL_0025: ret + c.GotoNext( + MoveType.Before, + i => i.MatchLdcI4(1), + i => i.MatchRet() + ); + + // Mark the label for return true here + c.MarkLabel(returnTrueLabel); + } catch (Exception e) { + Logger.Error($"Could not change CameraLockArea#IsInApplicableGameState IL: \n{e}"); + } + } }