diff --git a/Directory.Build.props b/Directory.Build.props index ac800b3..9cc1b57 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,7 +8,7 @@ true - 0.3.0 + 0.3.1 dev All Of Us, XtraCube, Angxl diff --git a/MiraAPI/Patches/DetectiveNotesMinigamePatch.cs b/MiraAPI/Patches/DetectiveNotesMinigamePatch.cs new file mode 100644 index 0000000..1324dde --- /dev/null +++ b/MiraAPI/Patches/DetectiveNotesMinigamePatch.cs @@ -0,0 +1,44 @@ +using System.Linq; +using AmongUs.GameOptions; +using HarmonyLib; +using Il2CppSystem.Collections.Generic; +using MiraAPI.Roles; +using UnityEngine; + +namespace MiraAPI.Patches; + +[HarmonyPatch(typeof(DetectiveNotesMinigame), nameof(DetectiveNotesMinigame.OpenBodyPopup))] +public static class DetectiveNotesMinigamePatch +{ + public static void Prefix(DetectiveNotesMinigame __instance, bool active) + { + __instance.impostorTypePopup.SetActive(active); + if (!active) + { + ControllerManager.Instance.CloseOverlayMenu(__instance.ImpostorOverlay); + ControllerManager.Instance.SetUpSelectables(ControllerManager.Instance.CurrentUiState, __instance.ControllerSelectable[0], __instance.ControllerSelectable); + ControllerManager.Instance.SetCurrentSelected(__instance.ControllerSelectable[2]); + return; + } + if (__instance.impostorButton != null) + { + ControllerManager.Instance.OpenOverlayMenu(__instance.ImpostorOverlay, null, __instance.ImpostorIconControllerSelectables[0], __instance.ImpostorIconControllerSelectables, false); + return; + } + __instance.impostorButton = new List(); + int num = 0; + var allRoles = DestroyableSingleton.Instance.AllRoles; + var customRoles = CustomRoleManager.CustomRoleBehaviours; + foreach (var role in allRoles) + { + if (role.TeamType != RoleTeamTypes.Impostor || role.Role == RoleTypes.ImpostorGhost || customRoles.Contains(role) ) + continue; + GameObject gameObject = Object.Instantiate(__instance.impostorTypePrefab, __instance.impostorTypeParent); + __instance.impostorButton.Add(gameObject); + gameObject.GetComponent().Initialize(__instance, role); + num++; + __instance.ImpostorIconControllerSelectables.Add(gameObject.GetComponent()); + } + ControllerManager.Instance.OpenOverlayMenu(__instance.ImpostorOverlay, null, __instance.ImpostorIconControllerSelectables[0], __instance.ImpostorIconControllerSelectables, false); + } +} \ No newline at end of file diff --git a/MiraAPI/Patches/IntroCutscenePatches.cs b/MiraAPI/Patches/IntroCutscenePatches.cs index 8105ad1..5495251 100644 --- a/MiraAPI/Patches/IntroCutscenePatches.cs +++ b/MiraAPI/Patches/IntroCutscenePatches.cs @@ -1,8 +1,12 @@ -using HarmonyLib; +using System.Linq; +using System.Reflection; +using HarmonyLib; +using Il2CppInterop.Runtime.InteropTypes; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; using MiraAPI.Roles; using MiraAPI.Utilities; +using Reactor.Utilities; namespace MiraAPI.Patches; @@ -29,14 +33,29 @@ public static class IntroCutscenePatches MiraEventManager.InvokeEvent(@event); } - [HarmonyPostfix] - [HarmonyPatch(typeof(IntroCutscene._ShowRole_d__40), nameof(IntroCutscene._ShowRole_d__40.MoveNext))] - [HarmonyPriority(Priority.Last)] - public static void ShowRolePatch(IntroCutscene._ShowRole_d__40 __instance) + [HarmonyPatch] + public static class IntroCutsceneShowRolePatch { - var realInstance = __instance.__4__this; - var @event = new IntroRoleRevealEvent(realInstance); - MiraEventManager.InvokeEvent(@event); + public static MethodBase TargetMethod() + { + return Helpers.GetStateMachineMoveNext("ShowRole")!; + } + + public static void Postfix(Il2CppObjectBase __instance) + { + var wrapper = new StateMachineWrapper(__instance); + // run before the first yield + if (wrapper.GetState() != 1) + { + return; + } + + var introCutscene = wrapper.Instance; + + Logger.Info("IntroCutscene ShowRole reached"); + var @event = new IntroRoleRevealEvent(introCutscene); + MiraEventManager.InvokeEvent(@event); + } } [HarmonyPrefix] @@ -66,21 +85,54 @@ public static class IntroCutscenePatches } } - [HarmonyPostfix] - [HarmonyPatch(typeof(IntroCutscene._CoBegin_d__34), nameof(IntroCutscene._CoBegin_d__34.MoveNext))] - public static void GameBeginPatch(IntroCutscene._CoBegin_d__34 __instance) + [HarmonyPatch] + public static class IntroCutsceneDestroyPatch { - if (__instance.__1__state != -1) + private static bool _usedFallback; + + public static MethodBase TargetMethod() { - return; + var onDestroy = AccessTools.Method(typeof(IntroCutscene), "OnDestroy"); + if (onDestroy != null) + { + _usedFallback = false; + Logger.Info("Using OnDestroy for IntroCutsceneDestroyPatch"); + return onDestroy; + } + + _usedFallback = true; + return Helpers.GetStateMachineMoveNext("CoBegin")!; } - MiraEventManager.InvokeEvent(new IntroEndEvent(__instance.__4__this)); + public static void Postfix(Il2CppObjectBase __instance) + { + IntroCutscene introCutscene; - var @event = new BeforeRoundStartEvent(true); - MiraEventManager.InvokeEvent(@event); + if (_usedFallback) + { + var wrapper = new StateMachineWrapper(__instance); + // run after the final yield + if (wrapper.GetState() != -1) + { + return; + } + introCutscene = wrapper.Instance; + } + else + { + introCutscene = __instance.Cast(); + } - if (@event.IsCancelled) return; - MiraEventManager.InvokeEvent(new RoundStartEvent(true)); + + Logger.Info("IntroCutscene ended"); + + MiraEventManager.InvokeEvent(new IntroEndEvent(introCutscene)); + + var @event = new BeforeRoundStartEvent(true); + MiraEventManager.InvokeEvent(@event); + + if (@event.IsCancelled) return; + MiraEventManager.InvokeEvent(new RoundStartEvent(true)); + } } } diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index a845fb4..3df7113 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -1,15 +1,16 @@ -using System; -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; +using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; -using MiraAPI.Keybinds; using MiraAPI.Roles; using MiraAPI.Utilities.Assets; +using Reactor.Utilities; using Rewired; using TMPro; using UnityEngine; +using MethodBase = System.Reflection.MethodBase; using Object = UnityEngine.Object; namespace MiraAPI.Utilities; @@ -38,6 +39,31 @@ public static class Helpers return keybindIcon; } + public static MethodBase? GetStateMachineMoveNext(string methodName) + { + var typeName = typeof(T).FullName; + var showRoleStateMachine = + typeof(T) + .GetNestedTypes() + .FirstOrDefault(x=>x.Name.Contains(methodName)); + + if (showRoleStateMachine == null) + { + Logger.Error($"Failed to find {methodName} state machine for {typeName}"); + return null; + } + + var moveNext = AccessTools.Method(showRoleStateMachine, "MoveNext"); + if (moveNext == null) + { + Logger.Error($"Failed to find MoveNext method for {typeName}.{methodName}"); + return null; + } + + Logger.Info($"Found {methodName}.MoveNext"); + return moveNext; + } + /// /// Creates a draggable scroller. Add items into the scroller.Inner transform. This does not automatically position children. /// diff --git a/MiraAPI/Utilities/StateMachineWrapper.cs b/MiraAPI/Utilities/StateMachineWrapper.cs new file mode 100644 index 0000000..f8d9d8b --- /dev/null +++ b/MiraAPI/Utilities/StateMachineWrapper.cs @@ -0,0 +1,42 @@ +using System.Reflection; +using HarmonyLib; +using Il2CppInterop.Runtime.InteropTypes; + +namespace MiraAPI.Utilities; + +/// +/// A wrapper for state machine objects to access their parent instance and state. +/// +/// The type of the parent class that owns the state machine. +public class StateMachineWrapper where T : Il2CppObjectBase +{ + private readonly Il2CppObjectBase _stateMachine; + private readonly PropertyInfo _thisProperty; + private readonly PropertyInfo _stateProperty; + + private T? _parentInstance; + + /// + /// Gets the instance of the parent class that owns the state machine. + /// + public T Instance => _parentInstance ??= (T)_thisProperty.GetValue(_stateMachine)!; + + /// + /// Initializes a new instance of the class. + /// + /// The state machine instance to wrap. + public StateMachineWrapper(Il2CppObjectBase stateMachine) + { + _stateMachine = stateMachine; + + var type = _stateMachine.GetType(); + _thisProperty = AccessTools.Property(type, "__4__this"); + _stateProperty = AccessTools.Property(type, "__1__state"); + } + + /// + /// Gets the current state of the state machine. + /// + /// The current state as an integer. + public int GetState() => (int)_stateProperty.GetValue(_stateMachine)!; +}