diff --git a/HKMP/Animation/AnimationManager.cs b/HKMP/Animation/AnimationManager.cs
index b877f9a..931b3cd 100644
--- a/HKMP/Animation/AnimationManager.cs
+++ b/HKMP/Animation/AnimationManager.cs
@@ -455,9 +455,6 @@ ServerSettings serverSettings
// Register when the player dies to send the animation
ModHooks.BeforePlayerDeadHook += OnDeath;
-
- // Register IL hook for changing the behaviour of tink effects
- IL.TinkEffect.OnTriggerEnter2D += TinkEffectOnTriggerEnter2D;
// Set the server settings for all animation effects
foreach (var effect in AnimationEffects.Values) {
@@ -1134,42 +1131,4 @@ public static AnimationClip GetCurrentAnimationClip() {
return 0;
}
-
- ///
- /// IL hook to change the TinkEffect OnTriggerEnter2D to not trigger on remote players.
- /// This method will insert IL to check whether the player responsible for the attack is the local player.
- ///
- private void TinkEffectOnTriggerEnter2D(ILContext il) {
- try {
- // Create a cursor for this context
- var c = new ILCursor(il);
-
- // Find the first return instruction in the method to branch to later
- var retInstr = il.Instrs.First(i => i.MatchRet());
-
- // Load the 'collision' argument onto the stack
- c.Emit(OpCodes.Ldarg_1);
-
- // Emit a delegate that pops the TinkEffect from the stack, checks whether the parent
- // of the effect is the knight and pushes a bool on the stack based on this
- c.EmitDelegate>(collider => {
- var parent = collider.transform.parent;
- if (parent == null) {
- return true;
- }
-
- parent = parent.parent;
- if (parent == null) {
- return true;
- }
-
- return parent.gameObject.name != "Knight";
- });
-
- // Based on the bool we pushed to the stack earlier, we conditionally branch to the return instruction
- c.Emit(OpCodes.Brtrue, retInstr);
- } catch (Exception e) {
- Logger.Error($"Could not change TinkEffect#OnTriggerEnter2D IL:\n{e}");
- }
- }
}
diff --git a/HKMP/Game/Client/ClientManager.cs b/HKMP/Game/Client/ClientManager.cs
index b120090..48dbc10 100644
--- a/HKMP/Game/Client/ClientManager.cs
+++ b/HKMP/Game/Client/ClientManager.cs
@@ -190,6 +190,7 @@ ModSettings modSettings
_saveManager.Initialize();
new PauseManager(netClient).RegisterHooks();
+ new GamePatcher().RegisterHooks();
new FsmPatcher().RegisterHooks();
_commandManager = new ClientCommandManager();
diff --git a/HKMP/Game/Client/Entity/EntityManager.cs b/HKMP/Game/Client/Entity/EntityManager.cs
index f16a5c8..b5ab3b7 100644
--- a/HKMP/Game/Client/Entity/EntityManager.cs
+++ b/HKMP/Game/Client/Entity/EntityManager.cs
@@ -65,17 +65,6 @@ public EntityManager(NetClient netClient) {
UnityEngine.SceneManagement.SceneManager.activeSceneChanged += OnSceneChanged;
FindGameObject.Find += OnFindGameObject;
-
- On.BridgeLever.OnTriggerEnter2D += BridgeLeverOnTriggerEnter2D;
-
- var type = typeof(BridgeLever).GetNestedType("d__13",
- BindingFlags.NonPublic | BindingFlags.Instance);
-
- // TODO: store this hook and unregister if entity system is not used
- new ILHook(
- type.GetMethod("MoveNext", BindingFlags.NonPublic | BindingFlags.Instance),
- BridgeLeverOnOpenBridge
- );
}
///
@@ -512,123 +501,4 @@ private void OnFindGameObject(FindGameObject.orig_Find orig, HutongGames.PlayMak
Logger.Debug(" Name did not match any entity");
}
-
- ///
- /// Whether the local player hit the bridge lever.
- ///
- private bool _localPlayerBridgeLever;
-
- ///
- /// On Hook that stores a boolean depending on whether the local player hit the bridge lever or not. Used in the
- /// IL Hook below.
- ///
- private void BridgeLeverOnTriggerEnter2D(On.BridgeLever.orig_OnTriggerEnter2D orig, BridgeLever self, Collider2D collision) {
- Logger.Debug("BridgeLeverOnTriggerEnter2D");
-
- var activated = ReflectionHelper.GetField(self, "activated");
-
- Logger.Debug($" activated: {activated}, collision tag: {collision.tag}");
- if (!activated && collision.tag == "Nail Attack") {
- _localPlayerBridgeLever = collision.transform.parent?.parent?.tag == "Player";
- Logger.Debug($" Bridge lever hit: bool: {_localPlayerBridgeLever}");
- }
-
- orig(self, collision);
- }
-
- ///
- /// IL Hook to modify the OpenBridge method of BridgeLever to exclude locking players in place that did not hit
- /// the lever.
- ///
- private void BridgeLeverOnOpenBridge(ILContext il) {
- Logger.Debug("BridgeLeverOnOpenBridge IL");
- try {
- // Create a cursor for this context
- var c = new ILCursor(il);
-
- // Define the collection of instructions that matches the FreezeMoment call
- Func[] freezeMomentInstructions = [
- i => i.MatchCall(typeof(global::GameManager), "get_instance"),
- i => i.MatchLdcI4(1),
- i => i.MatchCallvirt(typeof(global::GameManager), "FreezeMoment")
- ];
-
- // Goto after the FreezeMoment call
- c.GotoNext(MoveType.Before, freezeMomentInstructions);
-
- // Emit a delegate that puts the boolean on the stack
- c.EmitDelegate(() => _localPlayerBridgeLever);
-
- // Define the label to branch to
- var afterFreezeLabel = c.DefineLabel();
-
- // Then emit an instruction that branches to after the freeze if the boolean is false
- c.Emit(OpCodes.Brfalse, afterFreezeLabel);
-
- // Goto after the FreezeMoment call
- c.GotoNext(MoveType.After, freezeMomentInstructions);
-
- // Mark the label after the FreezeMoment call so we branch here
- c.MarkLabel(afterFreezeLabel);
-
- // Goto after the rumble call
- c.GotoNext(
- MoveType.After,
- i => i.MatchCall(typeof(GameCameras), "get_instance"),
- i => i.MatchLdfld(typeof(GameCameras), "cameraShakeFSM"),
- i => i.MatchLdstr("RumblingMed"),
- i => i.MatchLdcI4(1),
- i => i.MatchCall(typeof(FSMUtility), "SetBool")
- );
-
- // Emit a delegate that puts the boolean on the stack
- c.EmitDelegate(() => _localPlayerBridgeLever);
-
- // Define the label to branch to
- var afterRoarEnterLabel = c.DefineLabel();
-
- // Emit another instruction that branches over the roar enter FSM calls
- c.Emit(OpCodes.Brfalse, afterRoarEnterLabel);
-
- // Goto after the roar enter call
- c.GotoNext(
- MoveType.After,
- i => i.MatchLdstr("ROAR ENTER"),
- i => i.MatchLdcI4(0),
- i => i.MatchCall(typeof(FSMUtility), "SendEventToGameObject")
- );
-
- // Mark the label after the Roar Enter call so we branch here
- c.MarkLabel(afterRoarEnterLabel);
-
- // Define the collection of instructions that matches the roar exit FSM call
- Func[] roarExitInstructions = [
- i => i.MatchCall(typeof(HeroController), "get_instance"),
- i => i.MatchCallvirt(typeof(UnityEngine.Component), "get_gameObject"),
- i => i.MatchLdstr("ROAR EXIT"),
- i => i.MatchLdcI4(0),
- i => i.MatchCall(typeof(FSMUtility), "SendEventToGameObject")
- ];
-
- // Goto before the roar exit FSM call
- c.GotoNext(MoveType.Before, roarExitInstructions);
-
- // Emit a delegate that puts the boolean on the stack
- c.EmitDelegate(() => _localPlayerBridgeLever);
-
- // Define the label to branch to
- var afterRoarExitLabel = c.DefineLabel();
-
- // Emit the last instruction to branch over the roar exit call
- c.Emit(OpCodes.Brfalse, afterRoarExitLabel);
-
- // Goto after the roar exit FSM call
- c.GotoNext(MoveType.After, roarExitInstructions);
-
- // Mark the label so we branch here
- c.MarkLabel(afterRoarExitLabel);
- } catch (Exception e) {
- Logger.Error($"Could not change BridgeLever#OnOpenBridge IL: \n{e}");
- }
- }
}
diff --git a/HKMP/Game/Client/GamePatcher.cs b/HKMP/Game/Client/GamePatcher.cs
new file mode 100644
index 0000000..d4b4d84
--- /dev/null
+++ b/HKMP/Game/Client/GamePatcher.cs
@@ -0,0 +1,258 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using Modding;
+using Mono.Cecil.Cil;
+using MonoMod.Cil;
+using MonoMod.RuntimeDetour;
+using UnityEngine;
+using Logger = Hkmp.Logging.Logger;
+
+namespace Hkmp.Game.Client;
+
+///
+/// Class that manager patches such as IL and On hooks that are standalone patches for the multiplayer to function
+/// correctly.
+///
+internal class GamePatcher {
+ ///
+ /// The binding flags for obtaining certain types for hooking.
+ ///
+ private const BindingFlags BindingFlags = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance;
+
+ ///
+ /// The IL Hook for the bridge lever method.
+ ///
+ private ILHook _bridgeLeverIlHook;
+
+ ///
+ /// Register the hooks.
+ ///
+ public void RegisterHooks() {
+ // Register IL hook for changing the behaviour of tink effects
+ IL.TinkEffect.OnTriggerEnter2D += TinkEffectOnTriggerEnter2D;
+
+ IL.HealthManager.TakeDamage += HealthManagerOnTakeDamage;
+
+ On.BridgeLever.OnTriggerEnter2D += BridgeLeverOnTriggerEnter2D;
+
+ var type = typeof(BridgeLever).GetNestedType("d__13", BindingFlags);
+ _bridgeLeverIlHook = new ILHook(type.GetMethod("MoveNext", BindingFlags), BridgeLeverOnOpenBridge);
+ }
+
+ ///
+ /// De-register the hooks.
+ ///
+ public void DeregisterHooks() {
+ IL.HealthManager.TakeDamage -= HealthManagerOnTakeDamage;
+
+ On.BridgeLever.OnTriggerEnter2D -= BridgeLeverOnTriggerEnter2D;
+
+ _bridgeLeverIlHook?.Dispose();
+ }
+
+ ///
+ /// IL hook to change the TinkEffect OnTriggerEnter2D to not trigger on remote players.
+ /// This method will insert IL to check whether the player responsible for the attack is the local player.
+ ///
+ private void TinkEffectOnTriggerEnter2D(ILContext il) {
+ try {
+ // Create a cursor for this context
+ var c = new ILCursor(il);
+
+ // Find the first return instruction in the method to branch to later
+ var retInstr = il.Instrs.First(i => i.MatchRet());
+
+ // Load the 'collision' argument onto the stack
+ c.Emit(OpCodes.Ldarg_1);
+
+ // Emit a delegate that pops the TinkEffect from the stack, checks whether the parent
+ // of the effect is the knight and pushes a bool on the stack based on this
+ c.EmitDelegate>(collider => {
+ var parent = collider.transform.parent;
+ if (parent == null) {
+ return true;
+ }
+
+ parent = parent.parent;
+ if (parent == null) {
+ return true;
+ }
+
+ return parent.gameObject.name != "Knight";
+ });
+
+ // Based on the bool we pushed to the stack earlier, we conditionally branch to the return instruction
+ c.Emit(OpCodes.Brtrue, retInstr);
+ } catch (Exception e) {
+ Logger.Error($"Could not change TinkEffect#OnTriggerEnter2D IL:\n{e}");
+ }
+ }
+
+ ///
+ /// IL Hook to modify the behaviour of the TakeDamage method in HealthManager. This modification adds a
+ /// conditional branch in case the nail swing from the HitInstance was from a remote player to ensure that
+ /// soul is not gained for remote hits.
+ ///
+ private void HealthManagerOnTakeDamage(ILContext il) {
+ try {
+ // Create a cursor for this context
+ var c = new ILCursor(il);
+
+ // Goto the next virtual call to HeroController.SoulGain()
+ c.GotoNext(i => i.MatchCallvirt(typeof(HeroController), "SoulGain"));
+
+ // Move the cursor to before the call and call virtual instructions
+ c.Index -= 1;
+
+ // Emit the instruction to load the first parameter (hitInstance) onto the stack
+ c.Emit(OpCodes.Ldarg_1);
+
+ // Emit a delegate that takes the hitInstance parameter from the stack and pushes a boolean on the stack
+ // that indicates whether the hitInstance was from a remote player's nail swing
+ c.EmitDelegate>(hitInstance => {
+ if (hitInstance.Source == null || hitInstance.Source.transform == null) {
+ return false;
+ }
+
+ // Find the top-level parent of the hit instance
+ var transform = hitInstance.Source.transform;
+ while (transform.parent != null) {
+ transform = transform.parent;
+ }
+
+ var go = transform.gameObject;
+
+ return go.tag != "Player";
+ });
+
+ // Define a label for the branch instruction
+ var afterLabel = c.DefineLabel();
+
+ // Emit the branch (on true) instruction with the label
+ c.Emit(OpCodes.Brtrue, afterLabel);
+
+ // Move the cursor after the SoulGain method call
+ c.Index += 2;
+
+ // Mark the label here, so we branch after the SoulGain method call on true
+ c.MarkLabel(afterLabel);
+ } catch (Exception e) {
+ Logger.Error($"Could not change HealthManager#TakeDamage IL:\n{e}");
+ }
+ }
+
+ ///
+ /// Whether the local player hit the bridge lever.
+ ///
+ private bool _localPlayerBridgeLever;
+
+ ///
+ /// On Hook that stores a boolean depending on whether the local player hit the bridge lever or not. Used in the
+ /// IL Hook below.
+ ///
+ private void BridgeLeverOnTriggerEnter2D(On.BridgeLever.orig_OnTriggerEnter2D orig, BridgeLever self, Collider2D collision) {
+ var activated = ReflectionHelper.GetField(self, "activated");
+
+ if (!activated && collision.tag == "Nail Attack") {
+ _localPlayerBridgeLever = collision.transform.parent?.parent?.tag == "Player";
+ }
+
+ orig(self, collision);
+ }
+
+ ///
+ /// IL Hook to modify the OpenBridge method of BridgeLever to exclude locking players in place that did not hit
+ /// the lever.
+ ///
+ private void BridgeLeverOnOpenBridge(ILContext il) {
+ try {
+ // Create a cursor for this context
+ var c = new ILCursor(il);
+
+ // Define the collection of instructions that matches the FreezeMoment call
+ Func[] freezeMomentInstructions = [
+ i => i.MatchCall(typeof(global::GameManager), "get_instance"),
+ i => i.MatchLdcI4(1),
+ i => i.MatchCallvirt(typeof(global::GameManager), "FreezeMoment")
+ ];
+
+ // Goto after the FreezeMoment call
+ c.GotoNext(MoveType.Before, freezeMomentInstructions);
+
+ // Emit a delegate that puts the boolean on the stack
+ c.EmitDelegate(() => _localPlayerBridgeLever);
+
+ // Define the label to branch to
+ var afterFreezeLabel = c.DefineLabel();
+
+ // Then emit an instruction that branches to after the freeze if the boolean is false
+ c.Emit(OpCodes.Brfalse, afterFreezeLabel);
+
+ // Goto after the FreezeMoment call
+ c.GotoNext(MoveType.After, freezeMomentInstructions);
+
+ // Mark the label after the FreezeMoment call so we branch here
+ c.MarkLabel(afterFreezeLabel);
+
+ // Goto after the rumble call
+ c.GotoNext(
+ MoveType.After,
+ i => i.MatchCall(typeof(GameCameras), "get_instance"),
+ i => i.MatchLdfld(typeof(GameCameras), "cameraShakeFSM"),
+ i => i.MatchLdstr("RumblingMed"),
+ i => i.MatchLdcI4(1),
+ i => i.MatchCall(typeof(FSMUtility), "SetBool")
+ );
+
+ // Emit a delegate that puts the boolean on the stack
+ c.EmitDelegate(() => _localPlayerBridgeLever);
+
+ // Define the label to branch to
+ var afterRoarEnterLabel = c.DefineLabel();
+
+ // Emit another instruction that branches over the roar enter FSM calls
+ c.Emit(OpCodes.Brfalse, afterRoarEnterLabel);
+
+ // Goto after the roar enter call
+ c.GotoNext(
+ MoveType.After,
+ i => i.MatchLdstr("ROAR ENTER"),
+ i => i.MatchLdcI4(0),
+ i => i.MatchCall(typeof(FSMUtility), "SendEventToGameObject")
+ );
+
+ // Mark the label after the Roar Enter call so we branch here
+ c.MarkLabel(afterRoarEnterLabel);
+
+ // Define the collection of instructions that matches the roar exit FSM call
+ Func[] roarExitInstructions = [
+ i => i.MatchCall(typeof(HeroController), "get_instance"),
+ i => i.MatchCallvirt(typeof(UnityEngine.Component), "get_gameObject"),
+ i => i.MatchLdstr("ROAR EXIT"),
+ i => i.MatchLdcI4(0),
+ i => i.MatchCall(typeof(FSMUtility), "SendEventToGameObject")
+ ];
+
+ // Goto before the roar exit FSM call
+ c.GotoNext(MoveType.Before, roarExitInstructions);
+
+ // Emit a delegate that puts the boolean on the stack
+ c.EmitDelegate(() => _localPlayerBridgeLever);
+
+ // Define the label to branch to
+ var afterRoarExitLabel = c.DefineLabel();
+
+ // Emit the last instruction to branch over the roar exit call
+ c.Emit(OpCodes.Brfalse, afterRoarExitLabel);
+
+ // Goto after the roar exit FSM call
+ c.GotoNext(MoveType.After, roarExitInstructions);
+
+ // Mark the label so we branch here
+ c.MarkLabel(afterRoarExitLabel);
+ } catch (Exception e) {
+ Logger.Error($"Could not change BridgeLever#OnOpenBridge IL: \n{e}");
+ }
+ }
+}
diff --git a/HKMP/Game/Client/PlayerManager.cs b/HKMP/Game/Client/PlayerManager.cs
index 165d334..ff2ec59 100644
--- a/HKMP/Game/Client/PlayerManager.cs
+++ b/HKMP/Game/Client/PlayerManager.cs
@@ -109,8 +109,6 @@ Dictionary playerData
OnPlayerTeamUpdate);
packetManager.RegisterClientPacketHandler(ClientPacketId.PlayerSkinUpdate,
OnPlayerSkinUpdate);
-
- IL.HealthManager.TakeDamage += HealthManagerOnTakeDamage;
}
///
@@ -784,57 +782,4 @@ private void ToggleBodyDamage(ClientPlayerData playerData, bool enabled) {
playerObject.GetComponent().enabled = false;
}
}
-
- ///
- /// IL Hook to modify the behaviour of the TakeDamage method in HealthManager. This modification adds a
- /// conditional branch in case the nail swing from the HitInstance was from a remote player to ensure that
- /// soul is not gained for remote hits.
- ///
- private void HealthManagerOnTakeDamage(ILContext il) {
- try {
- // Create a cursor for this context
- var c = new ILCursor(il);
-
- // Goto the next virtual call to HeroController.SoulGain()
- c.GotoNext(i => i.MatchCallvirt(typeof(HeroController), "SoulGain"));
-
- // Move the cursor to before the call and call virtual instructions
- c.Index -= 1;
-
- // Emit the instruction to load the first parameter (hitInstance) onto the stack
- c.Emit(OpCodes.Ldarg_1);
-
- // Emit a delegate that takes the hitInstance parameter from the stack and pushes a boolean on the stack
- // that indicates whether the hitInstance was from a remote player's nail swing
- c.EmitDelegate>(hitInstance => {
- if (hitInstance.Source == null || hitInstance.Source.transform == null) {
- return false;
- }
-
- // Find the top-level parent of the hit instance
- var transform = hitInstance.Source.transform;
- while (transform.parent != null) {
- transform = transform.parent;
- }
-
- var go = transform.gameObject;
-
- return go.tag != "Player";
- });
-
- // Define a label for the branch instruction
- var afterLabel = c.DefineLabel();
-
- // Emit the branch (on true) instruction with the label
- c.Emit(OpCodes.Brtrue, afterLabel);
-
- // Move the cursor after the SoulGain method call
- c.Index += 2;
-
- // Mark the label here, so we branch after the SoulGain method call on true
- c.MarkLabel(afterLabel);
- } catch (Exception e) {
- Logger.Error($"Could not change HealthManager#TakeDamage IL:\n{e}");
- }
- }
}