From 89cd32d4a4a669b733c6056c3888aa9ab6e99815 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 1 Apr 2025 13:09:00 -0400 Subject: [PATCH 01/13] fix round start event --- MiraAPI/ModCompatibility.cs | 15 ++++++++--- .../Patches/Events/SpawnInMinigamePatch.cs | 15 +++++++++++ .../Patches/Events/SubmergedSpawnGamePatch.cs | 26 +++++++++++++++++++ MiraAPI/Patches/Roles/EjectionPatches.cs | 16 +++--------- 4 files changed, 56 insertions(+), 16 deletions(-) create mode 100644 MiraAPI/Patches/Events/SpawnInMinigamePatch.cs create mode 100644 MiraAPI/Patches/Events/SubmergedSpawnGamePatch.cs diff --git a/MiraAPI/ModCompatibility.cs b/MiraAPI/ModCompatibility.cs index 4163037..23744ab 100644 --- a/MiraAPI/ModCompatibility.cs +++ b/MiraAPI/ModCompatibility.cs @@ -1,4 +1,6 @@ -using BepInEx.Unity.IL2CPP; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using BepInEx.Unity.IL2CPP; namespace MiraAPI; @@ -13,7 +15,14 @@ public static class ModCompatibility public const string SubmergedId = "Submerged"; /// - /// Gets a value indicating whether the Submerged mod is loaded. + /// Checks if the Submerged mod is loaded. /// - public static bool SubmergedLoaded => IL2CPPChainloader.Instance.Plugins.TryGetValue(SubmergedId, out _); + /// Submerged mod assembly if loaded, null otherwise. + /// True if the Submerged mod is loaded, false otherwise. + public static bool SubmergedLoaded([NotNullWhen(true)] out Assembly? submergedAssembly) + { + var result = IL2CPPChainloader.Instance.Plugins.TryGetValue(SubmergedId, out var plugin); + submergedAssembly = result ? plugin?.Instance.GetType().Assembly : null; + return result; + } } diff --git a/MiraAPI/Patches/Events/SpawnInMinigamePatch.cs b/MiraAPI/Patches/Events/SpawnInMinigamePatch.cs new file mode 100644 index 0000000..b726999 --- /dev/null +++ b/MiraAPI/Patches/Events/SpawnInMinigamePatch.cs @@ -0,0 +1,15 @@ +using HarmonyLib; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla.Gameplay; + +namespace MiraAPI.Patches.Events; + +[HarmonyPatch(typeof(SpawnInMinigame), nameof(SpawnInMinigame.Close))] +internal static class SpawnInMinigamePatch +{ + public static void Postfix() + { + var @event = new RoundStartEvent(false); + MiraEventManager.InvokeEvent(@event); + } +} diff --git a/MiraAPI/Patches/Events/SubmergedSpawnGamePatch.cs b/MiraAPI/Patches/Events/SubmergedSpawnGamePatch.cs new file mode 100644 index 0000000..d20c426 --- /dev/null +++ b/MiraAPI/Patches/Events/SubmergedSpawnGamePatch.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla.Gameplay; + +namespace MiraAPI.Patches.Events; + +[HarmonyPatch] +internal static class SubmergedSpawnGamePatch +{ + public static IEnumerable TargetMethods() + { + if (ModCompatibility.SubmergedLoaded(out var subAss) && + subAss.GetType("Submerged.SpawnIn.SubmarineSelectSpawn") is { } selectSpawn) + { + yield return AccessTools.Method(selectSpawn, "OnDestroy"); + } + } + + public static void Postfix() + { + var @event = new RoundStartEvent(false); + MiraEventManager.InvokeEvent(@event); + } +} diff --git a/MiraAPI/Patches/Roles/EjectionPatches.cs b/MiraAPI/Patches/Roles/EjectionPatches.cs index ec1d123..74f0d26 100644 --- a/MiraAPI/Patches/Roles/EjectionPatches.cs +++ b/MiraAPI/Patches/Roles/EjectionPatches.cs @@ -12,7 +12,7 @@ namespace MiraAPI.Patches.Roles; /// Patches for custom ejection messages. /// [HarmonyPatch(typeof(ExileController))] -public static class EjectionPatches +internal static class EjectionPatches { [HarmonyPostfix] [HarmonyPatch(nameof(ExileController.Begin))] @@ -35,19 +35,9 @@ public static class EjectionPatches __instance.completeString = role.GetCustomEjectionMessage(__instance.initData.networkedPlayer); } - [HarmonyPatch] - internal static class WrapUpPatch + [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))] + public static class WrapUpPatch { - public static IEnumerable TargetMethods() - { - yield return AccessTools.Method(typeof(ExileController), nameof(ExileController.WrapUp)); - yield return AccessTools.Method(typeof(AirshipExileController), nameof(AirshipExileController.WrapUpAndSpawn)); - if (ModCompatibility.SubmergedLoaded && AccessTools.TypeByName("Submerged.ExileCutscene.SubmergedExileController") is { } type) - { - yield return AccessTools.Method(type, "WrapUpAndSpawn"); - } - } - public static void Postfix() { var @event = new RoundStartEvent(false); From ce330325e48f9e4261d17f974e60c7dee2127a12 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 1 Apr 2025 17:03:13 -0400 Subject: [PATCH 02/13] fix broken submerged compat patch --- MiraAPI/Patches/Events/SubmergedSpawnGamePatch.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MiraAPI/Patches/Events/SubmergedSpawnGamePatch.cs b/MiraAPI/Patches/Events/SubmergedSpawnGamePatch.cs index d20c426..d2664b9 100644 --- a/MiraAPI/Patches/Events/SubmergedSpawnGamePatch.cs +++ b/MiraAPI/Patches/Events/SubmergedSpawnGamePatch.cs @@ -9,6 +9,11 @@ namespace MiraAPI.Patches.Events; [HarmonyPatch] internal static class SubmergedSpawnGamePatch { + public static bool Prepare() + { + return ModCompatibility.SubmergedLoaded(out _); + } + public static IEnumerable TargetMethods() { if (ModCompatibility.SubmergedLoaded(out var subAss) && From 8c763e2a57e58ca915b89a8db5db1faa9df56654 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 1 Apr 2025 21:22:34 -0400 Subject: [PATCH 03/13] fix mira event priorities --- MiraAPI/Events/MiraEventManager.cs | 46 ++++++++++++---------- MiraAPI/Events/MiraEventWrapper.cs | 2 +- MiraAPI/Events/RegisterEventAttribute.cs | 4 +- MiraAPI/PluginLoading/MiraPluginManager.cs | 1 - 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/MiraAPI/Events/MiraEventManager.cs b/MiraAPI/Events/MiraEventManager.cs index b375504..52df8dd 100644 --- a/MiraAPI/Events/MiraEventManager.cs +++ b/MiraAPI/Events/MiraEventManager.cs @@ -21,7 +21,7 @@ public static class MiraEventManager public static bool InvokeEvent(T eventInstance) where T : MiraEvent { EventWrappers.TryGetValue(typeof(T), out var handlers); - if (handlers == null) + if (handlers == null || handlers.Count == 0) { return false; } @@ -43,7 +43,7 @@ public static class MiraEventManager public static bool InvokeEvent(MiraEvent eventInstance, Type type) { EventWrappers.TryGetValue(type, out var handlers); - if (handlers == null) + if (handlers == null || handlers.Count == 0) { return false; } @@ -61,47 +61,51 @@ public static class MiraEventManager /// /// The type of event. /// The MethodInfo of the event handler. - /// The priority of the event handler. Higher values are called first. + /// The priority of the event handler. Lower values are called first. public static void RegisterEventHandler(Type type, MethodInfo methodInfo, int priority = 0) { if (!type.IsSubclassOf(typeof(MiraEvent))) { - Logger.Error($"Type must be a subclass of MiraEvent: {nameof(type)}"); + Logger.Error($"Type must be a subclass of MiraEvent: {type.FullName}"); return; } - if (!EventWrappers.TryGetValue(type, out var handlers)) - { - handlers = []; - EventWrappers.Add(type, handlers); - } + EventWrappers.TryAdd(type, []); + var handlers = EventWrappers[type]; var @delegate = Delegate.CreateDelegate(typeof(Action<>).MakeGenericType(type), methodInfo); - handlers.Add(new MiraEventWrapper(@delegate, priority)); + var eventWrapper = new MiraEventWrapper(@delegate, priority); + + var index = handlers.BinarySearch(eventWrapper, Comparer.Create((a, b) => a.Priority.CompareTo(b.Priority))); + + if (index < 0) + { + index = ~index; + } + + handlers.Insert(index, eventWrapper); } /// /// Register an event. /// /// The callback method/handler for the event. - /// The priority of the event handler. Higher values are called first. + /// The priority of the event handler. Lower values are called first. /// Type of event. public static void RegisterEventHandler(Action handler, int priority = 0) where T : MiraEvent { - if (!EventWrappers.ContainsKey(typeof(T))) - { - EventWrappers.Add(typeof(T), []); - } + EventWrappers.TryAdd(typeof(T), []); var handlers = EventWrappers[typeof(T)]; - handlers.Add(new MiraEventWrapper(handler, priority)); - } + var eventWrapper = new MiraEventWrapper(handler, priority); - internal static void SortAllHandlers() - { - foreach (var handlers in EventWrappers.Values) + var index = handlers.BinarySearch(eventWrapper, Comparer.Create((a, b) => a.Priority.CompareTo(b.Priority))); + + if (index < 0) { - handlers.Sort((a, b) => a.Priority.CompareTo(b.Priority)); + index = ~index; } + + handlers.Insert(index, eventWrapper); } } diff --git a/MiraAPI/Events/MiraEventWrapper.cs b/MiraAPI/Events/MiraEventWrapper.cs index 0251ea9..7e754c3 100644 --- a/MiraAPI/Events/MiraEventWrapper.cs +++ b/MiraAPI/Events/MiraEventWrapper.cs @@ -13,7 +13,7 @@ public class MiraEventWrapper public Delegate EventHandler { get; } /// - /// Gets the priority of the handler. Higher values are called first. + /// Gets the priority of the handler. Lower values are called first. /// public int Priority { get; } diff --git a/MiraAPI/Events/RegisterEventAttribute.cs b/MiraAPI/Events/RegisterEventAttribute.cs index 6988d79..169c125 100644 --- a/MiraAPI/Events/RegisterEventAttribute.cs +++ b/MiraAPI/Events/RegisterEventAttribute.cs @@ -9,14 +9,14 @@ namespace MiraAPI.Events; public class RegisterEventAttribute : Attribute { /// - /// Gets the priority of the event. + /// Gets the priority of the event. Lower values are called first. /// public int Priority { get; } /// /// Initializes a new instance of the class. /// - /// The priority of the event. + /// The priority of the event. Lower values are called first. public RegisterEventAttribute(int priority = 0) { Priority = priority; diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index a40ee75..12ff874 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -109,7 +109,6 @@ public sealed class MiraPluginManager Logger.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API."); }; IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors; - IL2CPPChainloader.Instance.Finished += MiraEventManager.SortAllHandlers; IL2CPPChainloader.Instance.Finished += () => { CustomButtonManager.Buttons = new ReadOnlyCollection(CustomButtonManager.CustomButtons); From 3a36acd13147cd51f251e96bf2a08facf008148a Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Thu, 3 Apr 2025 20:37:09 -0400 Subject: [PATCH 04/13] rework modifier assignment --- MiraAPI/Modifiers/ModifierManager.cs | 88 ++++++++++++---------------- 1 file changed, 38 insertions(+), 50 deletions(-) diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index 5f6bbff..8b67b4f 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -103,68 +103,56 @@ public static class ModifierManager { var rand = new Random(); - foreach (var priority in PrioritiesToIdsMap.Keys.OrderByDescending(x => x)) + // Filter and sort modifiers by descending priority. + var modifiers = IdToTypeModifierMap + .Select(x => Activator.CreateInstance(x.Value) as GameModifier) + .OfType() + .Where(x => x.GetAmountPerGame() > 0 && x.GetAssignmentChance() > 0) + .OrderByDescending(x => x.Priority()) + .ToArray(); + + foreach (var modifier in modifiers) { - var filteredModifiers = new List(); + var assignments = modifier.GetAmountPerGame(); - foreach (var id in PrioritiesToIdsMap[priority]) + var validPlayers = plrs.Where(x => IsGameModifierValid(x, modifier, modifier.TypeId)).ToList(); + if (validPlayers.Count == 0) { - var mod = Activator.CreateInstance(IdToTypeModifierMap[id]) as GameModifier; - var chance = Math.Clamp(mod!.GetAssignmentChance(), 0, 100); - var count = mod.GetAmountPerGame(); - - if (chance == 0 || count == 0) - { - continue; - } - - var maxCount = plrs.Count(x => IsGameModifierValid(x, mod, id)); - - if (maxCount == 0) - { - Logger.Warning($"No players are valid for {mod.ModifierName}"); - continue; - } - - var num = Math.Clamp(count, 0, maxCount); - - for (var i = 0; i < num; i++) - { - var randomNum = rand.Next(100); - - if (randomNum < chance) - { - filteredModifiers.Add(id); - } - } - } - - if (filteredModifiers.Count == 0) - { - Logger.Warning($"No filtered modifiers for priority {priority}"); + Logger.Warning($"No valid players for modifier {modifier.ModifierName}"); continue; } - var shuffledList = filteredModifiers.Randomize(); + assignments = Math.Min(assignments, validPlayers.Count); + var availablePlayers = new List(validPlayers); - if (shuffledList.Count > plrs.Count) + for (var i = 0; i < assignments; i++) { - shuffledList = shuffledList.GetRange(0, plrs.Count); - } - - foreach (var id in shuffledList) - { - var mod = Activator.CreateInstance(IdToTypeModifierMap[id]) as GameModifier; - var plr = plrs.Where(x => IsGameModifierValid(x, mod!, id)).Random(); - - if (!plr || plr == null) + var chance = Math.Clamp(modifier.GetAssignmentChance(), 0, 100); + if (rand.Next(100) >= chance) { - Logger.Warning($"Somehow valid players for modifier {mod!.ModifierName} disappeared"); + continue; } - else + + var candidates = availablePlayers + .Where(x => IsGameModifierValid(x, modifier, modifier.TypeId)) + .ToList(); + + if (candidates.Count == 0) { - plr.RpcAddModifier(id); + Logger.Warning( + $"No available players for modifier {modifier.ModifierName} at assignment {i + 1}"); + break; } + + var plr = candidates.Random(); + if (plr == null) + { + Logger.Warning($"Valid player for modifier {modifier.ModifierName} disappeared"); + continue; + } + + plr.RpcAddModifier(modifier.TypeId); + availablePlayers.Remove(plr); } } } From b2fabdb1392ae7431143d97951787c4569bd62b5 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Fri, 4 Apr 2025 09:33:59 -0400 Subject: [PATCH 05/13] add check inactive variants of hasmodifier --- MiraAPI/Modifiers/ModifierComponent.cs | 57 +++++++++++++++++++++++++ MiraAPI/Modifiers/ModifierExtensions.cs | 22 +++++----- MiraAPI/Modifiers/ModifierManager.cs | 1 + 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index f8859e3..6335059 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -507,4 +507,61 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) { return ActiveModifiers.Exists(x => x.UniqueId == id); } + + /// + /// Checks if a player has an active modifier by its type. + /// + /// Whether to check inactive modifiers (those pending to be added or removed). + /// The predicate to check the modifier. + /// The Type of the Modifier. + /// True if the Modifier is present, false otherwise. + [HideFromIl2Cpp] + public bool HasModifier(bool checkInactive, Func? predicate=null) where T : BaseModifier + { + return ActiveModifiers.Exists(MatchExpr) || (checkInactive && (_toAdd.Exists(MatchExpr) || _toRemove.Exists(MatchExpr))); + bool MatchExpr(BaseModifier bm) => bm is T modifier && (predicate == null || predicate(modifier)); + } + + /// + /// Checks if a player has an active modifier by its type. + /// + /// The modifier type. + /// Whether to check inactive modifiers (those pending to be added or removed). + /// The predicate to check the modifier. + /// True if the Modifier is present, false otherwise. + [HideFromIl2Cpp] + public bool HasModifier(Type type, bool checkInactive, Func? predicate=null) + { + return ActiveModifiers.Exists(MatchExpr) || (checkInactive && (_toAdd.Exists(MatchExpr) || _toRemove.Exists(MatchExpr))); + bool MatchExpr(BaseModifier bm) => bm.GetType() == type && (predicate == null || predicate(bm)); + } + + /// + /// Checks if a player has an active modifier by its type ID. + /// + /// The modifier's type ID. + /// Whether to check inactive modifiers (those pending to be added or removed). + /// The predicate to check the modifier. + /// True if the modifier is present, false otherwise. + [HideFromIl2Cpp] + public bool HasModifier(uint id, bool checkInactive, Func? predicate=null) + { + var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException( + $"Cannot get modifier with id {id} because it is not registered."); + + return HasModifier(type, checkInactive, predicate); + } + + /// + /// Checks if a player has an active modifier by its unique ID. + /// + /// The modifier's guid. + /// Whether to check inactive modifiers (those pending to be added or removed). + /// True if the modifier is present, false otherwise. + [HideFromIl2Cpp] + public bool HasModifier(Guid id, bool checkInactive) + { + return ActiveModifiers.Exists(MatchExpr) || (checkInactive && (_toAdd.Exists(MatchExpr) || _toRemove.Exists(MatchExpr))); + bool MatchExpr(BaseModifier bm) => bm.UniqueId == id; + } } diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs index ca49808..c028fa3 100644 --- a/MiraAPI/Modifiers/ModifierExtensions.cs +++ b/MiraAPI/Modifiers/ModifierExtensions.cs @@ -180,6 +180,17 @@ public static class ModifierExtensions return player.GetModifierComponent().HasModifier(typeId, predicate); } + /// + /// Checks if the player has a specific modifier by its GUID. + /// + /// The PlayerControl instance. + /// The unique ID of the modifier. + /// True if the player has the modifier, false otherwise. + public static bool HasModifier(this PlayerControl player, Guid uniqueId) + { + return player.GetModifierComponent().HasModifier(uniqueId); + } + /// /// Clears all modifiers from a player. /// @@ -204,17 +215,6 @@ public static class ModifierExtensions } } - /// - /// Checks if the player has a specific modifier by its GUID. - /// - /// The PlayerControl instance. - /// The unique ID of the modifier. - /// True if the player has the modifier, false otherwise. - public static bool HasModifier(this PlayerControl player, Guid uniqueId) - { - return player.GetModifierComponent().HasModifier(uniqueId); - } - /// /// Tries to get a modifier by its type. /// diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index 8b67b4f..9f61239 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -105,6 +105,7 @@ public static class ModifierManager // Filter and sort modifiers by descending priority. var modifiers = IdToTypeModifierMap + .Where(x=>x.Value.IsAssignableTo(typeof(GameModifier))) .Select(x => Activator.CreateInstance(x.Value) as GameModifier) .OfType() .Where(x => x.GetAmountPerGame() > 0 && x.GetAssignmentChance() > 0) From efd176f7a4e8c3805f0eb4ff9bc0db62c32ff775 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Fri, 4 Apr 2025 12:18:37 -0400 Subject: [PATCH 06/13] fix timer bug related to effects --- MiraAPI/Hud/CustomActionButton.cs | 42 ++++++++++++++++++------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index 6a721da..e215fda 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -417,9 +417,12 @@ public abstract class CustomActionButton /// The local PlayerControl. public virtual void FixedUpdateHandler(PlayerControl playerControl) { - if (Timer >= 0 && !TimerPaused) + if (Timer >= 0) { - Timer -= Time.deltaTime; + if (!TimerPaused) + { + Timer -= Time.deltaTime; + } } else if (HasEffect && EffectActive) { @@ -428,25 +431,28 @@ public abstract class CustomActionButton OnEffectEnd(); } - if (CanUse()) + if (Button) { - Button?.SetEnabled(); - } - else - { - Button?.SetDisabled(); - } + if (CanUse()) + { + Button.SetEnabled(); + } + else + { + Button.SetDisabled(); + } - if (EffectActive) - { - Button?.SetFillUp(Timer, EffectDuration); + if (EffectActive) + { + Button.SetFillUp(Timer, EffectDuration); - Button!.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString(NumberFormatInfo.InvariantInfo); - Button!.cooldownTimerText.gameObject.SetActive(true); - } - else - { - Button?.SetCoolDown(Timer, Cooldown); + Button.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString(NumberFormatInfo.InvariantInfo); + Button.cooldownTimerText.gameObject.SetActive(true); + } + else + { + Button.SetCoolDown(Timer, Cooldown); + } } FixedUpdate(playerControl); From f43a54213407a7ef391fafe58e3ddc4a1e579565 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Fri, 4 Apr 2025 12:34:41 -0400 Subject: [PATCH 07/13] fix voting never ends bug --- MiraAPI/Patches/Voting/MeetingHudPatches.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MiraAPI/Patches/Voting/MeetingHudPatches.cs b/MiraAPI/Patches/Voting/MeetingHudPatches.cs index 3f0df63..dc5f02b 100644 --- a/MiraAPI/Patches/Voting/MeetingHudPatches.cs +++ b/MiraAPI/Patches/Voting/MeetingHudPatches.cs @@ -53,6 +53,17 @@ internal static class MeetingHudPatches MiraEventManager.InvokeEvent(new VotingCompleteEvent(__instance)); } + [HarmonyPostfix] + [HarmonyPatch(nameof(MeetingHud.ForceSkipAll))] + public static void MeetingHudForceSkipAllPatch(MeetingHud __instance) + { + foreach (var plr in PlayerControl.AllPlayerControls) + { + var voteData = plr.GetVoteData(); + voteData.SetRemainingVotes(0); + } + } + [HarmonyPrefix] [HarmonyPatch(nameof(MeetingHud.Select))] public static bool SelectPatch(MeetingHud __instance, int suspectStateIdx) From bd0aa68cb89b90490897849999950091d36b31ba Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Fri, 4 Apr 2025 12:42:12 -0400 Subject: [PATCH 08/13] slight logic adjustment to fix inactive modifier checks --- MiraAPI/Modifiers/ModifierComponent.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 6335059..918c1a4 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -511,14 +511,14 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) /// /// Checks if a player has an active modifier by its type. /// - /// Whether to check inactive modifiers (those pending to be added or removed). + /// Whether to check inactive modifiers (those pending to be added). /// The predicate to check the modifier. /// The Type of the Modifier. /// True if the Modifier is present, false otherwise. [HideFromIl2Cpp] public bool HasModifier(bool checkInactive, Func? predicate=null) where T : BaseModifier { - return ActiveModifiers.Exists(MatchExpr) || (checkInactive && (_toAdd.Exists(MatchExpr) || _toRemove.Exists(MatchExpr))); + return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr)); bool MatchExpr(BaseModifier bm) => bm is T modifier && (predicate == null || predicate(modifier)); } @@ -526,13 +526,13 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) /// Checks if a player has an active modifier by its type. /// /// The modifier type. - /// Whether to check inactive modifiers (those pending to be added or removed). + /// Whether to check inactive modifiers (those pending to be added). /// The predicate to check the modifier. /// True if the Modifier is present, false otherwise. [HideFromIl2Cpp] public bool HasModifier(Type type, bool checkInactive, Func? predicate=null) { - return ActiveModifiers.Exists(MatchExpr) || (checkInactive && (_toAdd.Exists(MatchExpr) || _toRemove.Exists(MatchExpr))); + return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr)); bool MatchExpr(BaseModifier bm) => bm.GetType() == type && (predicate == null || predicate(bm)); } @@ -540,7 +540,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) /// Checks if a player has an active modifier by its type ID. /// /// The modifier's type ID. - /// Whether to check inactive modifiers (those pending to be added or removed). + /// Whether to check inactive modifiers (those pending to be added). /// The predicate to check the modifier. /// True if the modifier is present, false otherwise. [HideFromIl2Cpp] @@ -556,12 +556,12 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) /// Checks if a player has an active modifier by its unique ID. /// /// The modifier's guid. - /// Whether to check inactive modifiers (those pending to be added or removed). + /// Whether to check inactive modifiers (those pending to be added). /// True if the modifier is present, false otherwise. [HideFromIl2Cpp] public bool HasModifier(Guid id, bool checkInactive) { - return ActiveModifiers.Exists(MatchExpr) || (checkInactive && (_toAdd.Exists(MatchExpr) || _toRemove.Exists(MatchExpr))); + return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr)); bool MatchExpr(BaseModifier bm) => bm.UniqueId == id; } } From aef4d6daf1d9ffec46a3d58f3ea0fab18352efe3 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Fri, 4 Apr 2025 13:08:35 -0400 Subject: [PATCH 09/13] actually fix meetinghud vote issue --- MiraAPI/Patches/Voting/MeetingHudPatches.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MiraAPI/Patches/Voting/MeetingHudPatches.cs b/MiraAPI/Patches/Voting/MeetingHudPatches.cs index dc5f02b..d873f56 100644 --- a/MiraAPI/Patches/Voting/MeetingHudPatches.cs +++ b/MiraAPI/Patches/Voting/MeetingHudPatches.cs @@ -41,21 +41,21 @@ internal static class MeetingHudPatches [HarmonyPostfix] [HarmonyPatch(nameof(MeetingHud.OnDestroy))] - public static void MeetingHudOnDestroyPatch(MeetingHud __instance) + public static void OnDestroyPatch(MeetingHud __instance) { MiraEventManager.InvokeEvent(new EndMeetingEvent(__instance)); } [HarmonyPostfix] [HarmonyPatch(nameof(MeetingHud.VotingComplete))] - public static void MeetingHudVotingCompletePatch(MeetingHud __instance) + public static void VotingCompletePatch(MeetingHud __instance) { MiraEventManager.InvokeEvent(new VotingCompleteEvent(__instance)); } - [HarmonyPostfix] + [HarmonyPrefix] [HarmonyPatch(nameof(MeetingHud.ForceSkipAll))] - public static void MeetingHudForceSkipAllPatch(MeetingHud __instance) + public static void ForceSkipAllPatch(MeetingHud __instance) { foreach (var plr in PlayerControl.AllPlayerControls) { From 21bec074fac562a79a024d16278aa6ba76da3fa6 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Fri, 4 Apr 2025 18:25:57 -0400 Subject: [PATCH 10/13] improve GetAlivePlayers --- MiraAPI/Utilities/Helpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 513789e..7d8ce9b 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -19,7 +19,7 @@ public static class Helpers /// A list of alive players. public static List GetAlivePlayers() { - return PlayerControl.AllPlayerControls.ToArray().Where(x => !x.Data.IsDead).ToList(); + return [.. GameData.Instance.AllPlayers.ToArray().Where(x => !x.IsDead && !x.Disconnected && x.Object).Select(x=>x.Object)]; } /// From fcb693e89938864a403c38eb7c23877a5fcf5c14 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Fri, 4 Apr 2025 18:26:16 -0400 Subject: [PATCH 11/13] fix modifier adding/removing AGAIN --- MiraAPI/Modifiers/ModifierComponent.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 918c1a4..988eac6 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -67,14 +67,18 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) private void FixedUpdate() { + var removed = _toRemove.Count > 0; foreach (var modifier in _toRemove.ToArray()) { + _toRemove.Remove(modifier); modifier.OnDeactivate(); Modifiers.Remove(modifier); } + var added = _toAdd.Count > 0; foreach (var modifier in _toAdd.ToArray()) { + _toAdd.Remove(modifier); Modifiers.Add(modifier); modifier.Initialized = true; modifier.OnActivate(); @@ -85,14 +89,12 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) } } - if (_toAdd.Count > 0 || _toRemove.Count > 0) + if (removed || added) { if (_player.AmOwner && HudManager.InstanceExists) { HudManager.Instance.SetHudActive(_player, _player.Data.Role, HudManager.Instance.TaskPanel.isActiveAndEnabled); } - _toAdd.Clear(); - _toRemove.Clear(); ActiveModifiers = Modifiers.ToImmutableList(); } From f1846375ca11209a7cb469d85fa230600d091d28 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Sat, 5 Apr 2025 13:06:35 -0400 Subject: [PATCH 12/13] Fix end game stuff --- .../Patches/Modifiers/EndGameDidWinPatch.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs b/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs index a86383d..1a7046c 100644 --- a/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs +++ b/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs @@ -2,13 +2,15 @@ using HarmonyLib; using MiraAPI.Modifiers; using MiraAPI.Modifiers.Types; +using MiraAPI.Utilities; +using Reactor.Utilities; namespace MiraAPI.Patches.Modifiers; -[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CoEndGame))] +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))] public static class EndGameDidWinPatch { - public static void Prefix() + public static void Postfix() { var gameOverReason = EndGameResult.CachedGameOverReason; EndGameResult.CachedWinners.Clear(); @@ -17,18 +19,26 @@ public static class EndGameDidWinPatch for (var i = 0; i < GameData.Instance.PlayerCount; i++) { var networkedPlayerInfo = players[i]; - if (networkedPlayerInfo == null) + if (!networkedPlayerInfo) { continue; } var didWin = networkedPlayerInfo.Role.DidWin(gameOverReason); - var modifierWin = networkedPlayerInfo.Object?.GetModifierComponent().GetModifiers() - .FirstOrDefault(x => x.DidWin(gameOverReason) != null)?.DidWin(gameOverReason); - if (modifierWin != null) + bool? modifierDidWin = null; + foreach (var modifier in networkedPlayerInfo.Object.GetModifiers().OrderByDescending(x => x.Priority())) { - didWin = modifierWin.Value; + var result = modifier.DidWin(gameOverReason); + if (!result.HasValue) continue; + + modifierDidWin = result.Value; + break; + } + + if (modifierDidWin.HasValue) + { + didWin = modifierDidWin.Value; } if (didWin) From ddfad0752d45ac3d4e928471e2ee9e343e748b56 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sat, 5 Apr 2025 14:30:52 -0400 Subject: [PATCH 13/13] proper fix for meeting vote issue (I HATE IL2CPP) --- MiraAPI/Patches/Voting/MeetingHudPatches.cs | 25 +++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/MiraAPI/Patches/Voting/MeetingHudPatches.cs b/MiraAPI/Patches/Voting/MeetingHudPatches.cs index d873f56..316fc7f 100644 --- a/MiraAPI/Patches/Voting/MeetingHudPatches.cs +++ b/MiraAPI/Patches/Voting/MeetingHudPatches.cs @@ -35,6 +35,7 @@ internal static class MeetingHudPatches modifier.OnMeetingStart(); } } + var @event = new StartMeetingEvent(__instance); MiraEventManager.InvokeEvent(@event); } @@ -53,10 +54,30 @@ internal static class MeetingHudPatches MiraEventManager.InvokeEvent(new VotingCompleteEvent(__instance)); } + // this is necessary because the actual ForceSkipAll method is inlined. [HarmonyPrefix] - [HarmonyPatch(nameof(MeetingHud.ForceSkipAll))] - public static void ForceSkipAllPatch(MeetingHud __instance) + [HarmonyPatch(nameof(MeetingHud.Update))] + public static void ForceSkipPatch(MeetingHud __instance) { + if (__instance.state is not (MeetingHud.VoteStates.NotVoted or MeetingHud.VoteStates.Voted)) + { + return; + } + + var logicOptionsNormal = GameManager.Instance.LogicOptions.Cast(); + var votingTime = logicOptionsNormal.GetVotingTime(); + if (votingTime <= 0) + { + return; + } + + var num2 = __instance.discussionTimer - logicOptionsNormal.GetDiscussionTime(); + + if (!AmongUsClient.Instance.AmHost || num2 < votingTime) + { + return; + } + foreach (var plr in PlayerControl.AllPlayerControls) { var voteData = plr.GetVoteData();