diff --git a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs
new file mode 100644
index 0000000..c96507a
--- /dev/null
+++ b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs
@@ -0,0 +1,24 @@
+using MiraAPI.Modifiers;
+using MiraAPI.Modifiers.Types;
+
+namespace MiraAPI.Example.Modifiers;
+
+[RegisterModifier]
+public class HighPriorityModifier : GameModifier
+{
+ public override string ModifierName => "High Priority";
+ public override int GetAssignmentChance()
+ {
+ return 100;
+ }
+
+ public override int GetAmountPerGame()
+ {
+ return 1;
+ }
+
+ public override int Priority()
+ {
+ return 100;
+ }
+}
diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs
index 640417a..4799f2e 100644
--- a/MiraAPI/Hud/CustomActionButton.cs
+++ b/MiraAPI/Hud/CustomActionButton.cs
@@ -284,7 +284,7 @@ public abstract class CustomActionButton
/// This method takes into account cooldowns, effects, and uses, before calling the method.
/// It can be overridden for custom behavior.
///
- protected virtual void ClickHandler()
+ public virtual void ClickHandler()
{
if (!CanUse())
{
diff --git a/MiraAPI/Hud/CustomPlayerMenu.cs b/MiraAPI/Hud/CustomPlayerMenu.cs
index b2cb47a..7c861ec 100644
--- a/MiraAPI/Hud/CustomPlayerMenu.cs
+++ b/MiraAPI/Hud/CustomPlayerMenu.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
+using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Patches.Stubs;
using Reactor.Utilities.Attributes;
using Reactor.Utilities.Extensions;
@@ -84,6 +85,7 @@ public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr)
///
/// Function to determine if player should show in the custom menu.
/// Onclick action for player.
+ [HideFromIl2Cpp]
public void Begin(Func playerMatch, Action onClick)
{
MinigameStubs.Begin(this, null);
diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs
index 6150074..8b91d21 100644
--- a/MiraAPI/Modifiers/ModifierComponent.cs
+++ b/MiraAPI/Modifiers/ModifierComponent.cs
@@ -255,4 +255,24 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
{
return AddModifier(typeof(T)) as T;
}
+
+ ///
+ /// Checks if a player has an active or queued modifier by its ID.
+ ///
+ /// The Modifier ID.
+ /// True if the Modifier is present, false otherwise.
+ public bool HasModifier(uint id)
+ {
+ return ActiveModifiers.Exists(x => x.ModifierId == id) || _toAdd.Exists(x => x.ModifierId == id);
+ }
+
+ ///
+ /// Checks if a player has an active or queued modifier by its type.
+ ///
+ /// The Type of the Modifier.
+ /// True if the Modifier is present, false otherwise.
+ public bool HasModifier()
+ {
+ return ActiveModifiers.Exists(x => x is T) || _toAdd.Exists(x => x is T);
+ }
}
diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs
index 773da63..fab840c 100644
--- a/MiraAPI/Modifiers/ModifierManager.cs
+++ b/MiraAPI/Modifiers/ModifierManager.cs
@@ -19,6 +19,7 @@ public static class ModifierManager
{
private static readonly Dictionary IdToTypeModifierMap = [];
private static readonly Dictionary TypeToIdModifierMap = [];
+ private static readonly Dictionary> PrioritiesToIdsMap = [];
private static uint _nextId;
@@ -57,75 +58,90 @@ public static class ModifierManager
IdToTypeModifierMap.Add(GetNextId(), modifierType);
TypeToIdModifierMap.Add(modifierType, _nextId);
+
+ if (!typeof(GameModifier).IsAssignableFrom(modifierType))
+ {
+ return;
+ }
+
+ var mod = Activator.CreateInstance(modifierType) as GameModifier;
+ var priority = mod!.Priority();
+
+ if (!PrioritiesToIdsMap.TryGetValue(priority, out var list))
+ {
+ PrioritiesToIdsMap[priority] = list = [];
+ }
+
+ list.Add(_nextId);
}
internal static void AssignModifiers(List plrs)
{
var rand = new Random();
- List filteredModifiers = [];
-
- foreach (var modifier in IdToTypeModifierMap.Where(pair => pair.Value.IsAssignableTo(typeof(GameModifier))))
+ foreach (var priority in PrioritiesToIdsMap.Keys.OrderByDescending(x => x))
{
- if (Activator.CreateInstance(modifier.Value) is not GameModifier mod)
+ var filteredModifiers = new List();
+
+ foreach (var id in PrioritiesToIdsMap[priority])
{
- Logger.Error($"Failed to create instance of {modifier.Value.Name}");
- continue;
- }
+ var mod = Activator.CreateInstance(IdToTypeModifierMap[id]) as GameModifier;
+ var chance = Math.Clamp(mod!.GetAssignmentChance(), 0, 100);
+ var count = mod.GetAmountPerGame();
- if (!plrs.Exists(x => IsGameModifierValid(x, mod, modifier.Key)))
- {
- Logger.Warning("No players are valid for modifier: " + mod.ModifierName);
- continue;
- }
-
- var maxCount = plrs.Count(x => IsGameModifierValid(x, mod, modifier.Key));
-
- var num = Math.Clamp(mod.GetAmountPerGame(), 0, maxCount);
- var chance = mod.GetAssignmentChance();
-
- for (var i = 0; i < num; i++)
- {
- var randomNum = rand.Next(100);
-
- if (randomNum < Math.Clamp(chance, 0, 100))
+ if (chance == 0 || count == 0)
{
- filteredModifiers.Add(TypeToIdModifierMap[modifier.Value]);
+ 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);
+ }
}
}
- }
- var shuffledModifiers = filteredModifiers.Randomize();
- if (shuffledModifiers.Count > plrs.Count)
- {
- shuffledModifiers = shuffledModifiers.GetRange(0, plrs.Count);
- }
-
- while (shuffledModifiers.Count > 0)
- {
- var id = shuffledModifiers[0];
-
- if (Activator.CreateInstance(IdToTypeModifierMap[id]) is not GameModifier mod)
+ if (filteredModifiers.Count == 0)
{
- Logger.Error($"Failed to create instance of {IdToTypeModifierMap[id].Name}");
+ Logger.Warning($"No filtered modifiers for priority {priority}");
continue;
}
- if (!plrs.Exists(x => IsGameModifierValid(x, mod, id)))
+ var shuffledList = filteredModifiers.Randomize();
+
+ if (shuffledList.Count > plrs.Count)
{
- shuffledModifiers.RemoveAt(0);
- continue;
+ shuffledList = shuffledList.GetRange(0, plrs.Count);
}
- var plr = plrs.Random();
-
- if (plr == null || !IsGameModifierValid(plr, mod, id))
+ foreach (var id in shuffledList)
{
- continue;
- }
+ var mod = Activator.CreateInstance(IdToTypeModifierMap[id]) as GameModifier;
+ var plr = plrs.Where(x => IsGameModifierValid(x, mod!, id)).Random();
- shuffledModifiers.RemoveAt(0);
- plr.RpcAddModifier(id);
+ if (plr == null)
+ {
+ Logger.Warning($"Somehow valid players for modifier {mod!.ModifierName} disappeared");
+ }
+ else
+ {
+ plr.RpcAddModifier(id);
+ }
+ }
}
}
diff --git a/MiraAPI/Modifiers/Types/GameModifier.cs b/MiraAPI/Modifiers/Types/GameModifier.cs
index 60c5f56..a7b1fe1 100644
--- a/MiraAPI/Modifiers/Types/GameModifier.cs
+++ b/MiraAPI/Modifiers/Types/GameModifier.cs
@@ -17,6 +17,12 @@ public abstract class GameModifier : BaseModifier
/// An int value greater than or equal to zero.
public abstract int GetAmountPerGame();
+ ///
+ /// Gets the priority at which the modifier will spawn. The higher the value, the higher up on the assignment list.
+ ///
+ /// An int value greater than or equal to -1.
+ public virtual int Priority() => -1;
+
///
/// Determines whether the modifier is valid on a role.
///
@@ -27,6 +33,7 @@ public abstract class GameModifier : BaseModifier
///
/// Determines whether the player won the game with this modifier.
///
+ /// The reason why the game ended.
/// True if the player won, false if they lost. Return null to use the player's default win condition.
public virtual bool? DidWin(GameOverReason reason) => null;
}
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index f7c667a..ef5d1f2 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -194,7 +194,7 @@ public static class Extensions
public static bool HasModifier(this PlayerControl? player) where T : BaseModifier
{
return player?.GetModifierComponent() != null &&
- player.GetModifierComponent()!.ActiveModifiers.Exists(x => x is T);
+ player.GetModifierComponent()!.HasModifier();
}
///
@@ -206,7 +206,7 @@ public static class Extensions
public static bool HasModifier(this PlayerControl? player, uint id)
{
return player?.GetModifierComponent() != null &&
- player.GetModifierComponent()!.ActiveModifiers.Exists(x => x.ModifierId == id);
+ player.GetModifierComponent()!.HasModifier(id);
}
///