From cb794c3fc23f29dff5601bc7f99d5a943ac3425e Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Tue, 14 Oct 2025 14:35:20 -0400
Subject: [PATCH] make kill anim patch update resilient
---
.../Events/KillAnimationMurderEventPatch.cs | 35 +++++++++++++++++++
.../Patches/Events/KillAnimationPatches.cs | 30 ----------------
MiraAPI/Utilities/StateMachineWrapper.cs | 35 ++++++++++++++++++-
3 files changed, 69 insertions(+), 31 deletions(-)
create mode 100644 MiraAPI/Patches/Events/KillAnimationMurderEventPatch.cs
delete mode 100644 MiraAPI/Patches/Events/KillAnimationPatches.cs
diff --git a/MiraAPI/Patches/Events/KillAnimationMurderEventPatch.cs b/MiraAPI/Patches/Events/KillAnimationMurderEventPatch.cs
new file mode 100644
index 0000000..2618153
--- /dev/null
+++ b/MiraAPI/Patches/Events/KillAnimationMurderEventPatch.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using HarmonyLib;
+using Il2CppInterop.Runtime.InteropTypes;
+using MiraAPI.Events;
+using MiraAPI.Events.Vanilla.Gameplay;
+using MiraAPI.Utilities;
+
+namespace MiraAPI.Patches.Events;
+
+///
+/// Patch to invoke AfterMurderEvent after a kill animation is performed.
+///
+[HarmonyPatch]
+public static class KillAnimationMurderEventPatch
+{
+ public static MethodBase TargetMethod()
+ {
+ return Helpers.GetStateMachineMoveNext(nameof(KillAnimation.CoPerformKill))!;
+ }
+
+ public static void Postfix(Il2CppObjectBase __instance)
+ {
+ var wrapper = new StateMachineWrapper(__instance);
+ if (wrapper.GetState() != 1)
+ {
+ return;
+ }
+
+ var source = wrapper.GetParameter("source");
+ var target = wrapper.GetParameter("target");
+
+ var afterMurderEvent = new AfterMurderEvent(source, target, Helpers.GetBodyById(target.PlayerId));
+ MiraEventManager.InvokeEvent(afterMurderEvent);
+ }
+}
diff --git a/MiraAPI/Patches/Events/KillAnimationPatches.cs b/MiraAPI/Patches/Events/KillAnimationPatches.cs
deleted file mode 100644
index d8c6336..0000000
--- a/MiraAPI/Patches/Events/KillAnimationPatches.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using HarmonyLib;
-using MiraAPI.Events;
-using MiraAPI.Events.Vanilla.Gameplay;
-using MiraAPI.Utilities;
-
-namespace MiraAPI.Patches.Events;
-
-///
-/// General patches for the KillAnimation class.
-///
-[HarmonyPatch]
-public static class KillAnimationPatches
-{
- ///
- /// Used to trigger the .
- ///
- [HarmonyPostfix]
- [HarmonyPatch(typeof(KillAnimation._CoPerformKill_d__2), nameof(KillAnimation._CoPerformKill_d__2.MoveNext))]
- public static void KillAnimPostfix(KillAnimation._CoPerformKill_d__2 __instance)
- {
- // Checking if state is -1 to wait for the end of coroutine.
- if (__instance.__1__state != -1)
- {
- return;
- }
-
- var afterMurderEvent = new AfterMurderEvent(__instance.source, __instance.target, Helpers.GetBodyById(__instance.target.PlayerId));
- MiraEventManager.InvokeEvent(afterMurderEvent);
- }
-}
diff --git a/MiraAPI/Utilities/StateMachineWrapper.cs b/MiraAPI/Utilities/StateMachineWrapper.cs
index f8d9d8b..a760e8e 100644
--- a/MiraAPI/Utilities/StateMachineWrapper.cs
+++ b/MiraAPI/Utilities/StateMachineWrapper.cs
@@ -1,4 +1,6 @@
-using System.Reflection;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
@@ -11,8 +13,11 @@ namespace MiraAPI.Utilities;
public class StateMachineWrapper where T : Il2CppObjectBase
{
private readonly Il2CppObjectBase _stateMachine;
+
+ // normally it is fields, but IL2CPP turns them into properties
private readonly PropertyInfo _thisProperty;
private readonly PropertyInfo _stateProperty;
+ private readonly Dictionary _propertyCache;
private T? _parentInstance;
@@ -32,6 +37,13 @@ public class StateMachineWrapper where T : Il2CppObjectBase
var type = _stateMachine.GetType();
_thisProperty = AccessTools.Property(type, "__4__this");
_stateProperty = AccessTools.Property(type, "__1__state");
+
+ if (_thisProperty == null || _stateProperty == null)
+ {
+ throw new MissingMemberException($"Could not find required properties in type '{type}'.");
+ }
+
+ _propertyCache = [];
}
///
@@ -39,4 +51,25 @@ public class StateMachineWrapper where T : Il2CppObjectBase
///
/// The current state as an integer.
public int GetState() => (int)_stateProperty.GetValue(_stateMachine)!;
+
+ ///
+ /// Gets a parameter from the state machine by its name.
+ ///
+ /// The name of the parameter to retrieve.
+ /// The type of the parameter to retrieve.
+ /// >The value of the specified parameter.
+ /// Thrown if the specified parameter does not exist.
+ public TField GetParameter(string parameterName)
+ {
+ if (_propertyCache.TryGetValue(parameterName, out var property))
+ {
+ return (TField)property.GetValue(_stateMachine)!;
+ }
+
+ property = AccessTools.Property(_stateMachine.GetType(), parameterName)
+ ?? throw new MissingFieldException($"Could not find parameter '{parameterName}' in state machine of type '{_stateMachine.GetType()}'.");
+
+ _propertyCache[parameterName] = property;
+ return (TField)property.GetValue(_stateMachine)!;
+ }
}