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();