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); } ///