diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs index 3926316..4dc35a9 100644 --- a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs +++ b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs @@ -31,14 +31,13 @@ public class FreezeButton : CustomActionButton Target?.cosmetics.SetOutline(active, new Il2CppSystem.Nullable(Palette.Blue)); } - public override bool IsTargetValid(PlayerControl target) + public override bool IsTargetValid(PlayerControl? target) { return true; } - public override bool Enabled(RoleBehaviour role) + public override bool Enabled(RoleBehaviour? role) { return role is FreezerRole; } - -} \ No newline at end of file +} diff --git a/MiraAPI/GameOptions/AbstractOptionGroup.cs b/MiraAPI/GameOptions/AbstractOptionGroup.cs index ffe7672..aeae257 100644 --- a/MiraAPI/GameOptions/AbstractOptionGroup.cs +++ b/MiraAPI/GameOptions/AbstractOptionGroup.cs @@ -4,31 +4,34 @@ using UnityEngine; namespace MiraAPI.GameOptions; +/// +/// Base class for option groups. An option group is a collection of options that are displayed together in the options menu. +/// public abstract class AbstractOptionGroup { - public List Options { get; } = []; + internal List Options { get; } = []; /// - /// The name of the group. Visible in options menu. + /// Gets the name of the group. Visible in options menu. /// public abstract string GroupName { get; } /// - /// A function that returns whether the group should be visible or not. + /// Gets the function that determines whether the group should be visible or not. /// public virtual Func GroupVisible => () => true; /// - /// The group color. This is used to color the group in the options menu. + /// Gets the group color. This is used to color the group in the options menu. /// public virtual Color GroupColor => Color.clear; /// - /// The role the group is associated with. This is used for the advanced role options menu. + /// Gets the role the group is associated with. This is used for the advanced role options menu. /// - public virtual Type AdvancedRole => null; + public virtual Type? AdvancedRole => null; internal bool AllOptionsHidden { get; set; } - internal CategoryHeaderMasked Header { get; set; } -} \ No newline at end of file + internal CategoryHeaderMasked? Header { get; set; } +} diff --git a/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs index 639bd97..702d75e 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs @@ -11,10 +11,7 @@ namespace MiraAPI.GameOptions.Attributes; [AttributeUsage(AttributeTargets.Property)] public abstract class ModdedOptionAttribute(string title, Type? roleType = null) : Attribute { - /// - /// Gets the IModdedOption object that this attribute is attached to. - /// - public IModdedOption? HolderOption { get; internal set; } + internal IModdedOption? HolderOption { get; set; } /// /// Gets the title of the option. diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs index a8c0162..4f7e950 100644 --- a/MiraAPI/GameOptions/IModdedOption.cs +++ b/MiraAPI/GameOptions/IModdedOption.cs @@ -1,25 +1,24 @@ -using MiraAPI.PluginLoading; -using System; +using System; using BepInEx.Configuration; using MiraAPI.Networking; +using MiraAPI.PluginLoading; using UnityEngine; namespace MiraAPI.GameOptions; -public interface IModdedOption +internal interface IModdedOption { - public uint Id { get; } - public BaseGameSetting Data { get; } - public IMiraPlugin? ParentMod { get; set; } - public Type AdvancedRole { get; set; } - public OptionBehaviour OptionBehaviour { get; } - public string Title { get; } - public StringNames StringName { get; } - public Func Visible { get; set; } - public ConfigDefinition? ConfigDefinition { get; set; } - public void ValueChanged(OptionBehaviour optionBehaviour); - public OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container); - public abstract float GetFloatData(); - public NetData GetNetData(); - public void HandleNetData(byte[] data); -} \ No newline at end of file + uint Id { get; } + string Title { get; } + StringNames StringName { get; } + IMiraPlugin? ParentMod { get; set; } + BaseGameSetting? Data { get; } + Type? AdvancedRole { get; set; } + OptionBehaviour? OptionBehaviour { get; } + Func Visible { get; set; } + ConfigDefinition? ConfigDefinition { get; set; } + OptionBehaviour? CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container); + float GetFloatData(); + NetData GetNetData(); + void HandleNetData(byte[] data); +} diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index 5e85a91..b57528e 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -1,18 +1,21 @@ -using HarmonyLib; -using MiraAPI.GameOptions.Attributes; -using Reactor.Utilities; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using BepInEx.Configuration; +using HarmonyLib; +using MiraAPI.GameOptions.Attributes; using MiraAPI.Networking; using MiraAPI.PluginLoading; using MiraAPI.Utilities; using Reactor.Networking.Rpc; +using Reactor.Utilities; namespace MiraAPI.GameOptions; +/// +/// Handles modded options. +/// public static class ModdedOptionsManager { private static readonly Dictionary OptionAttributes = new(); @@ -20,10 +23,10 @@ public static class ModdedOptionsManager internal static readonly Dictionary ModdedOptions = new(); internal static readonly List Groups = []; - + internal static uint NextId => _nextId++; private static uint _nextId = 1; - + internal static bool RegisterGroup(Type type, MiraPluginInfo pluginInfo) { if (Activator.CreateInstance(type) is not AbstractOptionGroup group) @@ -41,10 +44,10 @@ public static class ModdedOptionsManager Groups.Add(group); TypeToGroup.Add(type, group); pluginInfo.OptionGroups.Add(group); - + return true; } - + internal static void RegisterPropertyOption(Type type, PropertyInfo property, MiraPluginInfo pluginInfo) { if (!TypeToGroup.TryGetValue(type, out var group)) @@ -62,7 +65,11 @@ public static class ModdedOptionsManager RegisterOption(option, group, property.Name, pluginInfo); } - internal static void RegisterAttributeOption(Type type, ModdedOptionAttribute attribute, PropertyInfo property, MiraPluginInfo pluginInfo) + internal static void RegisterAttributeOption( + Type type, + ModdedOptionAttribute attribute, + PropertyInfo property, + MiraPluginInfo pluginInfo) { if (OptionAttributes.ContainsKey(property)) { @@ -98,7 +105,11 @@ public static class ModdedOptionsManager RegisterOption(option, group, property.Name, pluginInfo); } - private static void RegisterOption(IModdedOption option, AbstractOptionGroup group, string propertyName, MiraPluginInfo pluginInfo) + private static void RegisterOption( + IModdedOption option, + AbstractOptionGroup group, + string propertyName, + MiraPluginInfo pluginInfo) { var groupName = group.GetType().FullName; @@ -128,51 +139,56 @@ public static class ModdedOptionsManager // necessary to disable then re-enable this setting // we dont know how other plugins handle their configs // this way, all the options are saved at once, instead of one by one - foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) + var oldConfigSetting = new Dictionary(); + foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins()) { + oldConfigSetting.Add(plugin, plugin.PluginConfig.SaveOnConfigSet); plugin.PluginConfig.SaveOnConfigSet = false; } - + foreach (var netData in data) { if (!ModdedOptions.TryGetValue(netData.Id, out var option)) { continue; } - + option.HandleNetData(netData.Data); } - - foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) + + foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins()) { plugin.PluginConfig.Save(); - plugin.PluginConfig.SaveOnConfigSet = true; + plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin]; } - + if (LobbyInfoPane.Instance) { LobbyInfoPane.Instance.RefreshPane(); } } - - + + /// + /// Patches the setter of a property to update the value of the option. + /// + /// The original setter method. + /// The new object value. public static void PropertySetterPatch(MethodBase __originalMethod, object value) { - var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod().Equals(__originalMethod)).Value; - - attribute?.SetValue(value); + var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod() == __originalMethod).Value; + attribute.SetValue(value); } + /// + /// Patches the getter of a property to return the value of the option. + /// + /// The original getter method. + /// The result of the property getter. + /// False so the original getter gets skipped. public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result) { - var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod().Equals(__originalMethod)).Value; - - if (attribute == null) - { - return true; - } - + var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod() == __originalMethod).Value; __result = attribute.GetValue(); return false; } -} \ No newline at end of file +} diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs index 0be085f..56ff161 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs @@ -58,17 +58,12 @@ public class ModdedEnumOption : ModdedOption SetValue(BitConverter.ToInt32(data)); } - public override string GetHudStringText() - { - return Title + ": " + Values[Value]; - } - public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour) { return optionBehaviour.GetInt(); } - public override void OnValueChanged(int newValue) + protected override void OnValueChanged(int newValue) { DestroyableSingleton.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false); if (!OptionBehaviour) diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs index ac04d1b..5b24131 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs @@ -80,17 +80,12 @@ public class ModdedNumberOption : ModdedOption SetValue(BitConverter.ToSingle(data)); } - public override string GetHudStringText() - { - return Title + ": " + Value + Helpers.GetSuffix(SuffixType); - } - public override float GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour) { return Mathf.Clamp(optionBehaviour.GetFloat(), Min, Max); } - public override void OnValueChanged(float newValue) + protected override void OnValueChanged(float newValue) { Value = Mathf.Clamp(newValue, Min, Max); DestroyableSingleton.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(Value), false); diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index fdc6452..e1c816e 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -9,6 +9,10 @@ using UnityEngine; namespace MiraAPI.GameOptions.OptionTypes; +/// +/// Represents a modded option. +/// +/// The value type. public abstract class ModdedOption : IModdedOption { private IMiraPlugin? _parentMod; @@ -18,10 +22,20 @@ public abstract class ModdedOption : IModdedOption /// public uint Id { get; } + /// + /// Gets the title of the option. + /// + public string Title { get; } + + /// + /// Gets the StringName object of the option. + /// + public StringNames StringName { get; } + /// /// Gets the BaseGameSetting data of the option. /// - public BaseGameSetting Data { get; protected init; } + public BaseGameSetting? Data { get; protected init; } /// /// Gets or sets the parent mod of the option. @@ -51,26 +65,22 @@ public abstract class ModdedOption : IModdedOption /// /// Gets or sets the event that is invoked when the value of the option changes. /// - public Action ChangedEvent { get; set; } - - /// - /// Gets the title of the option. - /// - public string Title { get; } - - /// - /// Gets the StringName object of the option. - /// - public StringNames StringName { get; } + public Action? ChangedEvent { get; set; } /// /// Gets or sets the visibility of the option. /// public Func Visible { get; set; } - public Type AdvancedRole { get; set; } + /// + /// Gets or sets the advanced role of the option. + /// + public Type? AdvancedRole { get; set; } - public OptionBehaviour OptionBehaviour { get; set; } + /// + /// Gets or sets the option behaviour of the option. + /// + public OptionBehaviour? OptionBehaviour { get; protected set; } /// /// Gets or sets the config definition of the option. @@ -87,6 +97,12 @@ public abstract class ModdedOption : IModdedOption private ConfigDefinition? _configDefinition; + /// + /// Initializes a new instance of the class. + /// + /// The option title. + /// The default value. + /// The Role Type or null if it doesn't belong to a role. protected ModdedOption(string title, T defaultValue, Type? roleType) { Id = ModdedOptionsManager.NextId; @@ -102,18 +118,23 @@ public abstract class ModdedOption : IModdedOption } } - public void ValueChanged(OptionBehaviour optionBehaviour) + internal void ValueChanged(OptionBehaviour optionBehaviour) { SetValue(GetValueFromOptionBehaviour(optionBehaviour)); } + /// + /// Sets the value of the option. + /// + /// The new value. public void SetValue(T newValue) { var oldVal = Value; Value = newValue; - if (!Value.Equals(oldVal) && ChangedEvent != null) + + if (Value?.Equals(oldVal) == false) { - ChangedEvent.Invoke(Value); + ChangedEvent?.Invoke(Value); } if (AmongUsClient.Instance.AmHost) @@ -122,24 +143,55 @@ public abstract class ModdedOption : IModdedOption { entry.Value = Value; } - + Rpc.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()], true); } OnValueChanged(newValue); } + /// + /// Gets the float data of the option. + /// + /// A float object representing the option's value. public abstract float GetFloatData(); - + + /// + /// Gets the net data of the option. + /// + /// A NetData object representing this option's data. public abstract NetData GetNetData(); + /// + /// Handles incoming net data. + /// + /// The NetData's byte array. public abstract void HandleNetData(byte[] data); - public abstract void OnValueChanged(T newValue); + /// + /// Handles the value changed event. + /// + /// The new value. + protected abstract void OnValueChanged(T newValue); + /// + /// Gets the value from the option behaviour. + /// + /// The OptionBehaviour. + /// The value. public abstract T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour); - public abstract OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container); - - public abstract string GetHudStringText(); -} \ No newline at end of file + /// + /// Creates the option behaviour. + /// + /// The ToggleOption prefab. + /// The NumberOption prefab. + /// The StringOption prefab. + /// The options container. + /// A new OptionBehaviour for this modded option. + public abstract OptionBehaviour CreateOption( + ToggleOption toggleOpt, + NumberOption numberOpt, + StringOption stringOpt, + Transform container); +} diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs index 6b878b9..625225d 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs @@ -55,17 +55,12 @@ public class ModdedStringOption : ModdedOption SetValue(BitConverter.ToInt32(data)); } - public override string GetHudStringText() - { - return Title + ": " + Values[Value]; - } - public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour) { return optionBehaviour.GetInt(); } - public override void OnValueChanged(int newValue) + protected override void OnValueChanged(int newValue) { DestroyableSingleton.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false); if (!OptionBehaviour) diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs index f90f83f..2c3e935 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs @@ -47,17 +47,12 @@ public class ModdedToggleOption : ModdedOption SetValue(BitConverter.ToBoolean(data)); } - public override string GetHudStringText() - { - return Title + ": " + (Value ? "On" : "Off"); - } - public override bool GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour) { return optionBehaviour.GetBool(); } - public override void OnValueChanged(bool newValue) + protected override void OnValueChanged(bool newValue) { DestroyableSingleton.Instance.Notifier.AddSettingsChangeMessage(StringName, newValue ? "On" : "Off", false); if (!OptionBehaviour) diff --git a/MiraAPI/Hud/ButtonLocation.cs b/MiraAPI/Hud/ButtonLocation.cs index e08c9d2..da11737 100644 --- a/MiraAPI/Hud/ButtonLocation.cs +++ b/MiraAPI/Hud/ButtonLocation.cs @@ -1,7 +1,17 @@ namespace MiraAPI.Hud; +/// +/// The location of the custom button on the screen. +/// public enum ButtonLocation { + /// + /// Placed in the bottom left. + /// BottomLeft, - BottomRight -} \ No newline at end of file + + /// + /// Placed in the bottom right. + /// + BottomRight, +} diff --git a/MiraAPI/Hud/CustomButtonManager.cs b/MiraAPI/Hud/CustomButtonManager.cs index 90f7b6e..7dbe7ac 100644 --- a/MiraAPI/Hud/CustomButtonManager.cs +++ b/MiraAPI/Hud/CustomButtonManager.cs @@ -22,10 +22,10 @@ internal static class CustomButtonManager Logger.Error($"Failed to create button from {buttonType.Name}"); return; } - + CustomButtons.Add(button); typeof(CustomButtonSingleton<>).MakeGenericType(buttonType) .GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)! .SetValue(null, button); } -} \ No newline at end of file +} diff --git a/MiraAPI/Hud/CustomButtonSingleton.cs b/MiraAPI/Hud/CustomButtonSingleton.cs index 65d0bad..82403e7 100644 --- a/MiraAPI/Hud/CustomButtonSingleton.cs +++ b/MiraAPI/Hud/CustomButtonSingleton.cs @@ -7,10 +7,13 @@ namespace MiraAPI.Hud; /// A utility class to get the instance of a custom action button. /// /// The type of the button you are trying to access. -public class CustomButtonSingleton where T : CustomActionButton +public static class CustomButtonSingleton where T : CustomActionButton { private static T? _instance; + /// + /// Gets or sets instance of the button. + /// public static T Instance { get => _instance ??= CustomButtonManager.CustomButtons.OfType().Single(); diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index ba6f14a..d317a42 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -1,31 +1,51 @@ -using MiraAPI.Modifiers.Types; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Text; +using MiraAPI.Modifiers.Types; using MiraAPI.Utilities; using Reactor.Utilities; using Reactor.Utilities.Attributes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using TMPro; using UnityEngine; namespace MiraAPI.Modifiers; +/// +/// The component for handling modifiers. +/// [RegisterInIl2Cpp] public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) { - public List ActiveModifiers { get; private set; } + /// + /// Gets the active modifiers on the player. + /// + public ImmutableList ActiveModifiers => Modifiers.ToImmutableList(); - public PlayerControl player; + private List Modifiers { get; set; } = []; - private TextMeshPro _modifierText; + private PlayerControl _player = null!; - public void Start() + private TextMeshPro _modifierText = null!; + + internal void ClearModifiers() { - player = GetComponent(); - ActiveModifiers = []; + foreach (var modifier in Modifiers) + { + modifier.OnDeactivate(); + } - if (!player.AmOwner) + Modifiers.Clear(); + } + + private void Start() + { + _player = GetComponent(); + Modifiers = []; + + if (!_player.AmOwner) { return; } @@ -34,55 +54,56 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) _modifierText.verticalAlignment = VerticalAlignmentOptions.Top; } - public void FixedUpdate() + private void FixedUpdate() { - if (ActiveModifiers is null) return; - - foreach (var modifier in ActiveModifiers) + foreach (var modifier in Modifiers) { - if (modifier is null) continue; - modifier.FixedUpdate(); } } - public void Update() + private void Update() { - if (ActiveModifiers is null) return; - - foreach (var modifier in ActiveModifiers) + foreach (var modifier in Modifiers) { - if (modifier is null) continue; - modifier.Update(); } - var filteredModifiers = ActiveModifiers.Where(mod => !mod.HideOnUi); + if (!_modifierText || !_player.AmOwner) + { + return; + } + + var filteredModifiers = Modifiers.Where(mod => !mod.HideOnUi); var baseModifiers = filteredModifiers as BaseModifier[] ?? filteredModifiers.ToArray(); - if (player.AmOwner && baseModifiers.Any()) + if (baseModifiers.Length != 0) { var stringBuild = new StringBuilder(); foreach (var mod in baseModifiers) { - stringBuild.Append($"\n{mod.ModifierName}"); + stringBuild.Append(CultureInfo.InvariantCulture, $"\n{mod.ModifierName}"); if (mod is TimedModifier timer) { - stringBuild.Append($" ({Math.Round(timer.Duration - timer.TimeRemaining, 0)}s/{timer.Duration}s)"); + stringBuild.Append(CultureInfo.InvariantCulture, $" ({Math.Round(timer.Duration - timer.TimeRemaining, 0)}s/{timer.Duration}s)"); } } _modifierText.text = $"Modifiers:{stringBuild}"; } - else if (player.AmOwner && !baseModifiers.Any() && _modifierText.text != string.Empty) + else if (_modifierText.text != string.Empty) { _modifierText.text = string.Empty; } } + /// + /// Removes a modifier from the player. + /// + /// The modifier type. public void RemoveModifier(Type type) { - var modifier = ActiveModifiers.Find(x => x.GetType() == type); + var modifier = Modifiers.Find(x => x.GetType() == type); if (modifier is null) { @@ -93,14 +114,22 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) RemoveModifier(modifier); } + /// + /// Removes a modifier from the player. + /// + /// The modifier type. public void RemoveModifier() where T : BaseModifier { RemoveModifier(typeof(T)); } + /// + /// Removes a modifier from the player. + /// + /// The modifier ID. public void RemoveModifier(uint modifierId) { - var modifier = ActiveModifiers.Find(x => x.ModifierId == modifierId); + var modifier = Modifiers.Find(x => x.ModifierId == modifierId); if (modifier is null) { @@ -111,37 +140,54 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) RemoveModifier(modifier); } + /// + /// Removes a modifier from the player. + /// + /// The modifier object. public void RemoveModifier(BaseModifier modifier) { - if (!ActiveModifiers.Contains(modifier)) + if (!Modifiers.Contains(modifier)) { Logger.Error($"Cannot remove modifier {modifier.ModifierName} because it is not active on this player."); return; } modifier.OnDeactivate(); - ActiveModifiers.Remove(modifier); + Modifiers.Remove(modifier); - if (player.AmOwner) + if (_player.AmOwner) { HudManager.Instance.SetHudActive(true); } } - public BaseModifier AddModifier(BaseModifier modifier) + /// + /// Adds a modifier to the player. + /// + /// The modifier to add. + /// The modifier that was added. + public BaseModifier? AddModifier(BaseModifier modifier) { - if (ActiveModifiers.Contains(modifier)) + if (Modifiers.Contains(modifier)) { Logger.Error($"Player already has modifier with id {modifier.ModifierId}!"); return null; } - ActiveModifiers.Add(modifier); - modifier.Player = player; - modifier.ModifierId = ModifierManager.TypeToIdModifiers[modifier.GetType()]; + var modifierId = ModifierManager.GetModifierId(modifier.GetType()); + + if (modifierId == null) + { + Logger.Error($"Cannot add modifier {modifier.GetType().Name} because it has no ID!"); + return null; + } + + Modifiers.Add(modifier); + modifier.Player = _player; + modifier.ModifierId = modifierId.Value; modifier.OnActivate(); - if (!player.AmOwner) + if (!_player.AmOwner) { return modifier; } @@ -156,23 +202,27 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) return modifier; } - public BaseModifier AddModifier(Type type) + /// + /// Adds a modifier to the player. + /// + /// The modifier type. + /// The modifier that was added. + public BaseModifier? AddModifier(Type type) { - if (!ModifierManager.TypeToIdModifiers.TryGetValue(type, out var modifierId)) + var modifierId = ModifierManager.GetModifierId(type); + if (modifierId == null) { Logger.Error($"Cannot add modifier {type.Name} because it is not registered."); return null; } - if (ActiveModifiers.Find(x => x.ModifierId == modifierId) != null) + if (Modifiers.Find(x => x.ModifierId == modifierId) != null) { Logger.Error($"Player already has modifier with id {modifierId}!"); return null; } - var modifier = (BaseModifier)Activator.CreateInstance(type); - - if (modifier is null) + if (Activator.CreateInstance(type) is not BaseModifier modifier) { Logger.Error($"Cannot add modifier {type.Name} because it is null."); return null; @@ -183,8 +233,13 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) return modifier; } - public T AddModifier() where T : BaseModifier + /// + /// Adds a modifier to the player. + /// + /// The Type of the modifier. + /// The new modifier. + public T? AddModifier() where T : BaseModifier { return AddModifier(typeof(T)) as T; } -} \ No newline at end of file +} diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index 4a0f418..c449e72 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -1,21 +1,24 @@ -using MiraAPI.Modifiers.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using MiraAPI.Modifiers.Types; using MiraAPI.Networking; using MiraAPI.Roles; using MiraAPI.Utilities; using Reactor.Networking.Rpc; -using Reactor.Utilities.Extensions; -using System; -using System.Collections.Generic; -using System.Linq; using Reactor.Utilities; +using Reactor.Utilities.Extensions; using Random = System.Random; namespace MiraAPI.Modifiers; +/// +/// The manager for handling modifiers. +/// public static class ModifierManager { - public static readonly Dictionary IdToTypeModifiers = []; - public static readonly Dictionary TypeToIdModifiers = []; + private static readonly Dictionary IdToTypeModifierMap = []; + private static readonly Dictionary TypeToIdModifierMap = []; private static uint _nextId; @@ -25,6 +28,26 @@ public static class ModifierManager return _nextId; } + /// + /// Gets the modifier type from the id. + /// + /// The ID. + /// The Type of the modifier. + public static Type? GetModifierType(uint id) + { + return IdToTypeModifierMap.GetValueOrDefault(id); + } + + /// + /// Gets the modifier id from the type. + /// + /// The Type. + /// The ID of the modifier. + public static uint? GetModifierId(Type type) + { + return TypeToIdModifierMap.GetValueOrDefault(type); + } + internal static void RegisterModifier(Type modifierType) { if (!typeof(BaseModifier).IsAssignableFrom(modifierType)) @@ -32,8 +55,8 @@ public static class ModifierManager return; } - IdToTypeModifiers.Add(GetNextId(), modifierType); - TypeToIdModifiers.Add(modifierType, _nextId); + IdToTypeModifierMap.Add(GetNextId(), modifierType); + TypeToIdModifierMap.Add(modifierType, _nextId); } internal static void AssignModifiers(List plrs) @@ -42,34 +65,32 @@ public static class ModifierManager List filteredModifiers = []; - foreach (var modifier in IdToTypeModifiers.Where(pair => pair.Value.IsAssignableTo(typeof(GameModifier)))) + foreach (var modifier in IdToTypeModifierMap.Where(pair => pair.Value.IsAssignableTo(typeof(GameModifier)))) { - var mod = (GameModifier)Activator.CreateInstance(modifier.Value); - - if (mod is null) + if (Activator.CreateInstance(modifier.Value) is not GameModifier mod) { Logger.Error($"Failed to create instance of {modifier.Value.Name}"); continue; } - - if (!plrs.Exists(x=>IsGameModifierValid(x, mod, modifier.Key))) + + 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 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)) { - filteredModifiers.Add(TypeToIdModifiers[modifier.Value]); + filteredModifiers.Add(TypeToIdModifierMap[modifier.Value]); } } } @@ -84,15 +105,13 @@ public static class ModifierManager { var id = shuffledModifiers[0]; - var mod = (GameModifier)Activator.CreateInstance(IdToTypeModifiers[id]); - - if (mod is null) + if (Activator.CreateInstance(IdToTypeModifierMap[id]) is not GameModifier mod) { - Logger.Error($"Failed to create instance of {IdToTypeModifiers[id].Name}"); + Logger.Error($"Failed to create instance of {IdToTypeModifierMap[id].Name}"); continue; } - if (!plrs.Exists(x=>IsGameModifierValid(x, mod, id))) + if (!plrs.Exists(x => IsGameModifierValid(x, mod, id))) { shuffledModifiers.RemoveAt(0); continue; @@ -100,7 +119,7 @@ public static class ModifierManager var plr = plrs.Random(); - if (!IsGameModifierValid(plr, mod, id)) + if (plr == null || !IsGameModifierValid(plr, mod, id)) { continue; } @@ -116,7 +135,7 @@ public static class ModifierManager modifier.IsModifierValidOn(player.Data.Role) && !player.HasModifier(modifierId); } - + internal static void SyncAllModifiers(int targetId = -1) { var data = new List(); @@ -126,7 +145,7 @@ public static class ModifierManager data.Add(GetPlayerModifiers(player.Object)); } - Rpc.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray()); + Rpc.Instance.SendTo(PlayerControl.LocalPlayer, targetId, [.. data]); } internal static void HandleSyncModifiers(NetData[] data) @@ -144,20 +163,16 @@ public static class ModifierManager continue; } - foreach (var modifier in modifierComponent.ActiveModifiers) - { - modifier.OnDeactivate(); - } - modifierComponent.ActiveModifiers.Clear(); + modifierComponent.ClearModifiers(); foreach (var id in ids) { - if (!IdToTypeModifiers.TryGetValue(id, out var type)) + if (!IdToTypeModifierMap.TryGetValue(id, out var type)) { Logger.Error($"Cannot add modifier with id {id} because it is not registered."); continue; } - + modifierComponent.AddModifier(type); } } @@ -167,16 +182,16 @@ public static class ModifierManager { var bytes = new List(); var modifierComponent = player.GetComponent(); - if (!modifierComponent) + if (modifierComponent == null) { return new NetData(player.PlayerId, []); } foreach (var modifier in modifierComponent.ActiveModifiers) { - bytes.AddRange(BitConverter.GetBytes(TypeToIdModifiers[modifier.GetType()])); + bytes.AddRange(BitConverter.GetBytes(TypeToIdModifierMap[modifier.GetType()])); } - return new NetData(player.PlayerId, bytes.ToArray()); + return new NetData(player.PlayerId, [.. bytes]); } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 0bc37f1..4c47865 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -10,15 +10,21 @@ using UnityEngine.Events; namespace MiraAPI.Patches.Options; - +/// +/// Patches the GameOptionsMenu to add custom options. +/// [HarmonyPatch(typeof(GameOptionsMenu))] public static class GameOptionsMenuPatch { + /// + /// Update patch for the GameOptionsMenu. + /// + /// The GameOptionsMenu instance. [HarmonyPostfix] [HarmonyPatch(nameof(GameOptionsMenu.Update))] public static void UpdatePatch(GameOptionsMenu __instance) { - if (GameSettingMenuPatches.CurrentSelectedMod == 0) + if (GameSettingMenuPatches.SelectedModIdx == 0) { return; } @@ -71,7 +77,7 @@ public static class GameOptionsMenuPatch [HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))] public static bool SettingsPatch(GameOptionsMenu __instance) { - if (GameSettingMenuPatches.CurrentSelectedMod == 0) + if (GameSettingMenuPatches.SelectedModIdx == 0) { return true; } diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index 680cb69..13c9036 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -12,17 +12,30 @@ using Object = UnityEngine.Object; namespace MiraAPI.Patches.Options; +/// +/// Patches for the to add support for custom options. +/// [HarmonyPatch(typeof(GameSettingMenu))] public static class GameSettingMenuPatches { - public static int CurrentSelectedMod { get; private set; } + /// + /// Gets the currently selected mod index. + /// + public static int SelectedModIdx { get; private set; } + /// + /// Gets the currently selected mod. + /// public static MiraPluginInfo? SelectedMod { get; private set; } private static TextMeshPro? _text; private static Vector3 _roleBtnOgPos; + /// + /// Prefix for the method. Sets up the custom options. + /// + /// The GameSettingMenu instance. [HarmonyPrefix] [HarmonyPatch(nameof(GameSettingMenu.Start))] public static void StartPrefix(GameSettingMenu __instance) @@ -56,11 +69,12 @@ public static class GameSettingMenuPatches passiveButton.OnClick = new ButtonClickedEvent(); passiveButton.OnClick.AddListener((UnityAction)(() => { - if (CurrentSelectedMod != MiraPluginManager.Instance.RegisteredPlugins.Count) + SelectedModIdx += 1; + if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length) { - CurrentSelectedMod += 1; - UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); + SelectedModIdx = 0; } + UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); })); var backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject; @@ -70,17 +84,18 @@ public static class GameSettingMenuPatches backButton.transform.FindChild("Active").gameObject.GetComponent().flipX = backButton.transform.FindChild("Inactive").gameObject.GetComponent().flipX = true; backButton.gameObject.GetComponent().OnClick.AddListener((UnityAction)(() => { - if (CurrentSelectedMod != 0) + SelectedModIdx -= 1; + if (SelectedModIdx < 0) { - CurrentSelectedMod -= 1; - UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); + SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length; } + UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); })); } private static void UpdateText(GameSettingMenu menu, GameOptionsMenu settings, RolesSettingsMenu roles) { - if (_text is not null && CurrentSelectedMod == 0) + if (_text is not null && SelectedModIdx == 0) { _text.text = "Default"; _text.fontSizeMax = 3.2f; @@ -88,22 +103,17 @@ public static class GameSettingMenuPatches else if (_text is not null) { _text.fontSizeMax = 2.3f; - SelectedMod = MiraPluginManager.Instance.RegisteredPlugins.ElementAt(CurrentSelectedMod - 1).Value; - if (SelectedMod == null) - { - CurrentSelectedMod = 0; - UpdateText(menu, settings, roles); - } + SelectedMod = MiraPluginManager.Instance.RegisteredPlugins()[SelectedModIdx - 1]; - var name = SelectedMod?.MiraPlugin.OptionsTitleText; - _text.text = name?[..Math.Min(name.Length, 25)]; + var name = SelectedMod.MiraPlugin.OptionsTitleText; + _text.text = name[..Math.Min(name.Length, 25)]; } menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos; menu.GameSettingsButton.gameObject.SetActive(true); menu.RoleSettingsButton.gameObject.SetActive(true); - if (CurrentSelectedMod != 0) + if (SelectedModIdx != 0) { if (SelectedMod?.Options.Where(x=>x.AdvancedRole==null).ToList().Count == 0) { @@ -121,7 +131,7 @@ public static class GameSettingMenuPatches } } - if (roles?.roleChances != null && SelectedMod != null && SelectedMod.CustomRoles.Count != 0) + if (roles.roleChances != null && SelectedMod != null && SelectedMod.CustomRoles.Count != 0) { if (roles.advancedSettingChildren is not null) { diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index a952807..a74588e 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -16,10 +16,9 @@ namespace MiraAPI.Patches.Options; [HarmonyPatch(typeof(LobbyViewSettingsPane))] public static class LobbyViewPanePatches { - public static int CurrentSelectedMod { get; private set; } - public static MiraPluginInfo? SelectedMod => CurrentSelectedMod == 0 ? null : MiraPluginManager.Instance.RegisteredPlugins.ElementAt(CurrentSelectedMod-1).Value; + public static MiraPluginInfo? SelectedMod => CurrentSelectedMod == 0 ? null : MiraPluginManager.Instance.RegisteredPlugins()[CurrentSelectedMod-1]; [HarmonyPostfix] @@ -48,7 +47,7 @@ public static class LobbyViewPanePatches passiveButton.OnClick.AddListener((UnityAction)(() => { CurrentSelectedMod += 1; - if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins.Count) + if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins().Length) { CurrentSelectedMod = 0; } @@ -71,7 +70,7 @@ public static class LobbyViewPanePatches CurrentSelectedMod -= 1; if (CurrentSelectedMod < 0) { - CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins.Count; + CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins().Length; } __instance.RefreshTab(); __instance.scrollBar.ScrollToTop(); @@ -185,146 +184,145 @@ public static class LobbyViewPanePatches private static void DrawRolesTab(LobbyViewSettingsPane instance) { - if (SelectedMod == null) - { - return; - } + if (SelectedMod == null) + { + return; + } - var num = 0.95f; - var num2 = -6.53f; - var categoryHeaderMasked = - Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true); - categoryHeaderMasked.SetHeader(StringNames.RoleQuotaLabel, 61); - categoryHeaderMasked.transform.localScale = Vector3.one; - categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, 1.26f, -2f); - instance.settingsInfo.Add(categoryHeaderMasked.gameObject); + var num = 0.95f; + var num2 = -6.53f; + var categoryHeaderMasked = + Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true); + categoryHeaderMasked.SetHeader(StringNames.RoleQuotaLabel, 61); + categoryHeaderMasked.transform.localScale = Vector3.one; + categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, 1.26f, -2f); + instance.settingsInfo.Add(categoryHeaderMasked.gameObject); - var list = new List(); + var list = new List(); - foreach (var team in Enum.GetValues()) - { - var filteredRoles = SelectedMod.CustomRoles.Values - .OfType() - .Where(x => !x.HideSettings && x.Team == team).ToList(); + foreach (var team in Enum.GetValues()) + { + var filteredRoles = SelectedMod.CustomRoles.Values + .OfType() + .Where(x => !x.HideSettings && x.Team == team).ToList(); - if (filteredRoles.Count == 0) - { - continue; - } + if (filteredRoles.Count == 0) + { + continue; + } - var categoryHeaderRoleVariant = - Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true); + var categoryHeaderRoleVariant = + Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true); - switch (team) - { - case ModdedRoleTeams.Crewmate: - categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61); - break; - case ModdedRoleTeams.Impostor: - categoryHeaderRoleVariant.SetHeader(StringNames.ImpostorRolesHeader, 61); - break; - case ModdedRoleTeams.Neutral: - default: - categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61); - categoryHeaderRoleVariant.Title.text = team + " Roles"; - categoryHeaderRoleVariant.Background.color = Color.gray; - categoryHeaderRoleVariant.Title.color = Color.white; - break; - } + switch (team) + { + case ModdedRoleTeams.Crewmate: + categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61); + break; + case ModdedRoleTeams.Impostor: + categoryHeaderRoleVariant.SetHeader(StringNames.ImpostorRolesHeader, 61); + break; + default: + categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61); + categoryHeaderRoleVariant.Title.text = team + " Roles"; + categoryHeaderRoleVariant.Background.color = Color.gray; + categoryHeaderRoleVariant.Title.color = Color.white; + break; + } - categoryHeaderRoleVariant.transform.localScale = Vector3.one; - categoryHeaderRoleVariant.transform.localPosition = new Vector3(0.09f, num, -2f); - instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject); - num -= 0.696f; + categoryHeaderRoleVariant.transform.localScale = Vector3.one; + categoryHeaderRoleVariant.transform.localPosition = new Vector3(0.09f, num, -2f); + instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject); + num -= 0.696f; - foreach (var customRole in filteredRoles) - { - var roleBehaviour = customRole as RoleBehaviour; - if (roleBehaviour == null) - { - continue; - } + foreach (var customRole in filteredRoles) + { + var roleBehaviour = customRole as RoleBehaviour; + if (roleBehaviour == null) + { + continue; + } - var chancePerGame = - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleBehaviour.Role); - var numPerGame = - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role); + var chancePerGame = + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleBehaviour.Role); + var numPerGame = + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role); - var flag = numPerGame == 0; + var flag = numPerGame == 0; - var viewSettingsInfoPanelRoleVariant = - Object.Instantiate( - instance.infoPanelRoleOrigin, instance.settingsContainer, true); - viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one; - viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f); + var viewSettingsInfoPanelRoleVariant = + Object.Instantiate( + instance.infoPanelRoleOrigin, instance.settingsContainer, true); + viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one; + viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f); - var advancedRoleOptions = SelectedMod.Options - .Where(x => x.AdvancedRole == customRole.GetType()) - .ToList(); + var advancedRoleOptions = SelectedMod.Options + .Where(x => x.AdvancedRole == customRole.GetType()) + .ToList(); - if (!flag && advancedRoleOptions.Count > 0) - { - list.Add(customRole.GetType()); - } + if (!flag && advancedRoleOptions.Count > 0) + { + list.Add(customRole.GetType()); + } - viewSettingsInfoPanelRoleVariant.SetInfo(roleBehaviour.NiceName, numPerGame, chancePerGame, - 61, customRole.RoleColor, customRole.Icon.LoadAsset(), true); + viewSettingsInfoPanelRoleVariant.SetInfo(roleBehaviour.NiceName, numPerGame, chancePerGame, + 61, customRole.RoleColor, customRole.Icon.LoadAsset(), true); - viewSettingsInfoPanelRoleVariant.titleText.color = - viewSettingsInfoPanelRoleVariant.chanceTitle.color = - viewSettingsInfoPanelRoleVariant.chanceBackground.color = - viewSettingsInfoPanelRoleVariant.background.color = - customRole.RoleColor.GetAlternateColor(); - instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject); - num -= 0.664f; - } + viewSettingsInfoPanelRoleVariant.titleText.color = + viewSettingsInfoPanelRoleVariant.chanceTitle.color = + viewSettingsInfoPanelRoleVariant.chanceBackground.color = + viewSettingsInfoPanelRoleVariant.background.color = + customRole.RoleColor.GetAlternateColor(); + instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject); + num -= 0.664f; + } - } + } - if (list.Count > 0) - { - var categoryHeaderMasked2 = - Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true); - categoryHeaderMasked2.SetHeader(StringNames.RoleSettingsLabel, 61); - categoryHeaderMasked2.transform.localScale = Vector3.one; - categoryHeaderMasked2.transform.localPosition = new Vector3(-9.77f, num, -2f); - instance.settingsInfo.Add(categoryHeaderMasked2.gameObject); - num -= 1.7f; - var num3 = 0f; - for (var k = 0; k < list.Count; k++) - { - float num4; - if (k % 2 == 0) - { - num4 = -5.8f; - if (k > 0) - { - num -= num3 + 0.59f; - num3 = 0f; - } - } - else - { - num4 = 0.14999962f; - } + if (list.Count > 0) + { + var categoryHeaderMasked2 = + Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true); + categoryHeaderMasked2.SetHeader(StringNames.RoleSettingsLabel, 61); + categoryHeaderMasked2.transform.localScale = Vector3.one; + categoryHeaderMasked2.transform.localPosition = new Vector3(-9.77f, num, -2f); + instance.settingsInfo.Add(categoryHeaderMasked2.gameObject); + num -= 1.7f; + var num3 = 0f; + for (var k = 0; k < list.Count; k++) + { + float num4; + if (k % 2 == 0) + { + num4 = -5.8f; + if (k > 0) + { + num -= num3 + 0.59f; + num3 = 0f; + } + } + else + { + num4 = 0.14999962f; + } - var advancedRoleViewPanel = - Object.Instantiate(instance.advancedRolePanelOrigin, instance.settingsContainer, true); - advancedRoleViewPanel.transform.localScale = Vector3.one; - advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f); - var num5 = SetUpAdvancedRoleViewPanel(advancedRoleViewPanel, list[k], 0.59f, 61); + var advancedRoleViewPanel = + Object.Instantiate(instance.advancedRolePanelOrigin, instance.settingsContainer, true); + advancedRoleViewPanel.transform.localScale = Vector3.one; + advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f); + var num5 = SetUpAdvancedRoleViewPanel(advancedRoleViewPanel, list[k], 0.59f, 61); - if (num5 > num3) - { - num3 = num5; - } + if (num5 > num3) + { + num3 = num5; + } - instance.settingsInfo.Add(advancedRoleViewPanel.gameObject); - } - } + instance.settingsInfo.Add(advancedRoleViewPanel.gameObject); + } + } - instance.scrollBar.SetYBoundsMax(-num); + instance.scrollBar.SetYBoundsMax(-num); } private static float SetUpAdvancedRoleViewPanel(AdvancedRoleViewPanel viewPanel, Type roleType, float spacingY, int maskLayer) diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index e355438..420fff8 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -30,7 +30,7 @@ public static class RoleSettingMenuPatches var maskBg = __instance.scrollBar.transform.FindChild("MaskBg"); var hitbox = __instance.scrollBar.transform.FindChild("Hitbox"); - if (GameSettingMenuPatches.CurrentSelectedMod == 0) + if (GameSettingMenuPatches.SelectedModIdx == 0) { __instance.AllButton.transform.parent.gameObject.SetActive(true); __instance.AllButton.gameObject.SetActive(true); @@ -127,7 +127,7 @@ public static class RoleSettingMenuPatches [HarmonyPatch(nameof(RolesSettingsMenu.Update))] public static bool UpdatePatch() { - return GameSettingMenuPatches.CurrentSelectedMod == 0; + return GameSettingMenuPatches.SelectedModIdx == 0; } [HarmonyPostfix] diff --git a/MiraAPI/Patches/Options/SettingPatches.cs b/MiraAPI/Patches/Options/SettingPatches.cs index 2f2f152..1b228a3 100644 --- a/MiraAPI/Patches/Options/SettingPatches.cs +++ b/MiraAPI/Patches/Options/SettingPatches.cs @@ -1,14 +1,29 @@ -using HarmonyLib; +using System.Globalization; +using HarmonyLib; using MiraAPI.Utilities; using UnityEngine; namespace MiraAPI.Patches.Options; + +/// +/// Patches for the various game settings. +/// [HarmonyPatch] public class SettingPatches { + /// + /// Prefix for the method. Adds support for custom number suffixes. + /// + /// The FloatGameSetting instance. + /// The result of the GetValueString method. + /// The float value. + /// False to skip original method. [HarmonyPrefix] [HarmonyPatch(typeof(FloatGameSetting), nameof(FloatGameSetting.GetValueString))] - public static bool ValueStringPatch(FloatGameSetting __instance, ref string __result, [HarmonyArgument(0)] float value) + public static bool ValueStringPatch( + FloatGameSetting __instance, + ref string __result, + [HarmonyArgument(0)] float value) { var result = string.Empty; var suffix = (MiraNumberSuffixes)__instance.SuffixType; @@ -31,12 +46,15 @@ public class SettingPatches } else { - result = DestroyableSingleton.Instance.GetString(StringNames.GameSecondsAbbrev, (Il2CppSystem.Object[])( - [ - value.ToString(__instance.FormatString) - ])); + result = DestroyableSingleton.Instance.GetString( + StringNames.GameSecondsAbbrev, + (Il2CppSystem.Object[]) + [ + value.ToString(__instance.FormatString, CultureInfo.InvariantCulture) + ]); } + __result = result; return false; } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs index 87d6f55..9db0f92 100644 --- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs +++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs @@ -2,7 +2,6 @@ using HarmonyLib; using Il2CppSystem; using Il2CppSystem.Collections.Generic; -using Il2CppSystem.Text; using MiraAPI.PluginLoading; using MiraAPI.Roles; using MiraAPI.Utilities.Assets; @@ -63,7 +62,7 @@ public static class TaskAdderPatch _rolesFolder.FolderName = "Roles"; _rolesFolder.name = "RolesFolder"; - foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) + foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins()) { var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner); newFolder.FolderName = newFolder.name = plugin.PluginInfo.Metadata.Name; diff --git a/MiraAPI/PluginLoading/MiraPluginInfo.cs b/MiraAPI/PluginLoading/MiraPluginInfo.cs index e6e0b5c..c6dc714 100644 --- a/MiraAPI/PluginLoading/MiraPluginInfo.cs +++ b/MiraAPI/PluginLoading/MiraPluginInfo.cs @@ -6,16 +6,44 @@ using System.Collections.Generic; namespace MiraAPI.PluginLoading; -public class MiraPluginInfo(IMiraPlugin miraPlugin, PluginInfo info) +/// +/// Represents a Mira plugin. +/// +public class MiraPluginInfo { - public string PluginId { get; set; } = info.Metadata.GUID; - public IMiraPlugin MiraPlugin { get; set; } = miraPlugin; - public PluginInfo PluginInfo { get; set; } = info; - public ConfigFile PluginConfig { get; set; } = miraPlugin.GetConfigFile(); + internal MiraPluginInfo(IMiraPlugin miraPlugin, PluginInfo info) + { + MiraPlugin = miraPlugin; + PluginConfig = miraPlugin.GetConfigFile(); + PluginInfo = info; + PluginId = info.Metadata.GUID; + } - public readonly List OptionGroups = []; - public readonly List Options = []; + internal List OptionGroups { get; } = []; - public readonly Dictionary CustomRoles = []; - internal readonly Dictionary GameModes = []; -} \ No newline at end of file + internal List Options { get; } = []; + + internal Dictionary CustomRoles { get; } = []; + + internal Dictionary GameModes { get; } = []; + + /// + /// Gets the plugin's ID, as defined in the plugin's BepInEx metadata. + /// + public string PluginId { get; } + + /// + /// Gets the plugin's instance as an . + /// + public IMiraPlugin MiraPlugin { get; } + + /// + /// Gets the plugin's BepInEx metadata. + /// + public PluginInfo PluginInfo { get; } + + /// + /// Gets the plugin's configuration file. + /// + public ConfigFile PluginConfig { get; } +} diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 61fa292..dfbf98f 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -1,53 +1,49 @@ -using BepInEx.Unity.IL2CPP; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using BepInEx.Unity.IL2CPP; +using MiraAPI.Colors; using MiraAPI.GameOptions; using MiraAPI.GameOptions.Attributes; using MiraAPI.Hud; using MiraAPI.Modifiers; using MiraAPI.Roles; -using Reactor.Utilities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using MiraAPI.Colors; using MiraAPI.Utilities; using Reactor.Networking; +using Reactor.Utilities; namespace MiraAPI.PluginLoading; internal class MiraPluginManager { - public readonly Dictionary RegisteredPlugins = []; + private readonly Dictionary _registeredPlugins = []; - private static MiraPluginManager _instance; + internal MiraPluginInfo[] RegisteredPlugins() => [.. _registeredPlugins.Values]; - public static MiraPluginManager Instance - { - get => _instance ??= new MiraPluginManager(); - private set => _instance = value; - } + public static MiraPluginManager Instance { get; private set; } = new(); internal void Initialize() { Instance = this; IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, assembly, plugin) => { - if (!plugin.GetType().GetInterfaces().Contains(typeof(IMiraPlugin))) + if (plugin is not IMiraPlugin miraPlugin) { return; } - var info = new MiraPluginInfo(plugin as IMiraPlugin, pluginInfo); + var info = new MiraPluginInfo(miraPlugin, pluginInfo); RegisterModifierAttribute(assembly); RegisterAllOptions(assembly, info); - + RegisterRoleAttribute(assembly, info); RegisterButtonAttribute(assembly); RegisterColorClasses(assembly); - RegisteredPlugins.Add(assembly, info); + _registeredPlugins.Add(assembly, info); Logger.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API."); }; @@ -55,7 +51,7 @@ internal class MiraPluginManager public MiraPluginInfo GetPluginByGuid(string guid) { - return RegisteredPlugins.Values.First(plugin => plugin.PluginId == guid); + return _registeredPlugins.Values.First(plugin => plugin.PluginId == guid); } private static void RegisterAllOptions(Assembly assembly, MiraPluginInfo pluginInfo) @@ -68,7 +64,7 @@ internal class MiraPluginManager { continue; } - + foreach (var property in type.GetProperties()) { if (property.PropertyType.IsAssignableTo(typeof(IModdedOption))) @@ -76,7 +72,7 @@ internal class MiraPluginManager ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo); continue; } - + var attribute = property.GetCustomAttribute(); if (attribute == null) { @@ -98,22 +94,22 @@ internal class MiraPluginManager { continue; } - + if (!(typeof(RoleBehaviour).IsAssignableFrom(type) && typeof(ICustomRole).IsAssignableFrom(type))) { Logger.Error($"{type.Name} does not inherit from RoleBehaviour or ICustomRole."); continue; } - + if (!ModList.GetById(pluginInfo.PluginId).IsRequiredOnAllClients) { Logger.Error("Custom roles are only supported on all clients."); return; } - + roles.Add(type); } - + CustomRoleManager.RegisterRoleTypes(roles, pluginInfo); } @@ -138,14 +134,13 @@ internal class MiraPluginManager { continue; } - - var color = (CustomColor)property.GetValue(null); - if (color == null) + + if (property.GetValue(null) is not CustomColor color) { - Logger.Error($"Color property {property.Name} in {type.Name} is null."); + Logger.Error($"Color property {property.Name} in {type.Name} is not a CustomColor."); continue; } - + PaletteManager.CustomColors.Add(color); } } @@ -163,7 +158,6 @@ internal class MiraPluginManager } } - private static void RegisterButtonAttribute(Assembly assembly) { foreach (var type in assembly.GetTypes()) @@ -175,4 +169,4 @@ internal class MiraPluginManager } } } -} \ No newline at end of file +} diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index 9c1913d..ca34213 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -122,7 +122,7 @@ public static class CustomRoleManager /// A MiraPluginInfo object representing the parent mod of the role. public static MiraPluginInfo FindParentMod(ICustomRole role) { - return MiraPluginManager.Instance.RegisteredPlugins.First(plugin => plugin.Value.CustomRoles.ContainsValue(role as RoleBehaviour ?? throw new InvalidOperationException())).Value; + return MiraPluginManager.Instance.RegisteredPlugins().First(plugin => plugin.CustomRoles.ContainsValue(role as RoleBehaviour ?? throw new InvalidOperationException())); } /// @@ -197,7 +197,7 @@ public static class CustomRoleManager // we dont know how other plugins handle their configs // this way, all the options are saved at once, instead of one by one var oldConfigSetting = new Dictionary(); - foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) + foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins()) { oldConfigSetting.Add(plugin, plugin.PluginConfig.SaveOnConfigSet); plugin.PluginConfig.SaveOnConfigSet = false; @@ -239,7 +239,7 @@ public static class CustomRoleManager } } - foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) + foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins()) { plugin.PluginConfig.Save(); plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin]; diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 0ea5415..629e447 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -99,7 +99,7 @@ public static class Extensions if (current.Count > 0) { - chunks.Enqueue(current.ToArray()); + chunks.Enqueue([.. current]); } return chunks; @@ -112,7 +112,7 @@ public static class Extensions /// True if the OptionBehaviour is for a custom options, false otherwise. public static bool IsCustom(this OptionBehaviour optionBehaviour) { - return ModdedOptionsManager.ModdedOptions.Values.Any(opt => opt.OptionBehaviour && opt.OptionBehaviour.Equals(optionBehaviour)); + return ModdedOptionsManager.ModdedOptions.Values.Any(opt => opt.OptionBehaviour && opt.OptionBehaviour == optionBehaviour); } private static readonly Dictionary ModifierComponents = []; @@ -208,13 +208,15 @@ public static class Extensions /// The Type of the Modifier. public static void RpcRemoveModifier(this PlayerControl player) where T : BaseModifier { - if (!ModifierManager.TypeToIdModifiers.TryGetValue(typeof(T), out var id)) + var id = ModifierManager.GetModifierId(typeof(T)); + + if (id == null) { Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); return; } - player.RpcRemoveModifier(id); + player.RpcRemoveModifier(id.Value); } /// @@ -225,13 +227,13 @@ public static class Extensions [MethodRpc((uint)MiraRpc.AddModifier)] public static void RpcAddModifier(this PlayerControl target, uint modifierId) { - if (ModifierManager.IdToTypeModifiers.TryGetValue(modifierId, out var type)) + var type = ModifierManager.GetModifierType(modifierId); + if (type == null) { - target.GetModifierComponent()?.AddModifier(type); + Logger.Error($"Cannot add modifier with id {modifierId} because it is not registered."); return; } - - Logger.Error($"Cannot add modifier with id {modifierId} because it is not registered."); + target.GetModifierComponent()?.AddModifier(type); } /// @@ -241,13 +243,14 @@ public static class Extensions /// The modifier Type. public static void RpcAddModifier(this PlayerControl player) where T : BaseModifier { - if (!ModifierManager.TypeToIdModifiers.TryGetValue(typeof(T), out var id)) + var id = ModifierManager.GetModifierId(typeof(T)); + if (id == null) { Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); return; } - player.RpcAddModifier(id); + player.RpcAddModifier(id.Value); } /// diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 18aac28..e860431 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -1,5 +1,6 @@ using MiraAPI.Roles; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using TMPro; @@ -8,14 +9,35 @@ using Object = UnityEngine.Object; namespace MiraAPI.Utilities; +/// +/// A class that contains helper methods. +/// public static class Helpers { - public static readonly ContactFilter2D Filter = ContactFilter2D.CreateLegacyFilter(Constants.NotShipMask, float.MinValue, float.MaxValue); - public static PlainShipRoom GetRoom(Vector3 pos) + /// + /// NotShipMask filter for collisions. + /// + public static readonly ContactFilter2D Filter = ContactFilter2D.CreateLegacyFilter( + Constants.NotShipMask, + float.MinValue, + float.MaxValue); + + /// + /// Get the room at a specific position. + /// + /// The position. + /// The ship room if its found. + public static PlainShipRoom? GetRoom(Vector3 pos) { return ShipStatus.Instance.AllRooms.ToList().Find(room => room.roomArea.OverlapPoint(pos)); } + /// + /// Gets a list of dead bodies within a radius. + /// + /// The source location. + /// The radius to search in. + /// A list of dead bodies. public static List GetNearestDeadBodies(Vector2 source, float radius) { var results = new Il2CppSystem.Collections.Generic.List(); @@ -25,7 +47,16 @@ public static class Helpers .Select(collider2D => collider2D.GetComponent()).ToList(); } - public static List GetNearestObjectsOfType(Vector2 source, float radius, string? colliderTag = null) where T : Component + /// + /// Gets a list of objects within a radius. + /// + /// The source point. + /// The radius to search in. + /// An optional collider tag. + /// The type of the object. + /// A list of objects of type T. + public static List GetNearestObjectsOfType(Vector2 source, float radius, string? colliderTag = null) + where T : Component { var results = new Il2CppSystem.Collections.Generic.List(); Physics2D.OverlapCircle(source, radius, Filter, results); @@ -34,29 +65,49 @@ public static class Helpers .Select(collider2D => collider2D.GetComponent()).ToList(); } - public static List GetClosestPlayersInCircle(Vector2 source, float radius, bool ignoreColliders = true) + /// + /// Gets the closest players to a specific point. + /// + /// The source point. + /// The radius to search in. + /// Whether colliders should be ignored. + /// A list of Player Controls in the radius. + public static List GetClosestPlayersInCircle( + Vector2 source, + float radius, + bool ignoreColliders = true) { var newList = GetNearestObjectsOfType(source, radius); - if (ignoreColliders) + if (!ignoreColliders) { - var filteredList = new List(); - foreach (var player in newList) - { - var vector = player.GetTruePosition() - source; - var magnitude = vector.magnitude; - if (!PhysicsHelpers.AnyNonTriggersBetween(source, vector.normalized, magnitude, Constants.ShipAndObjectsMask)) - { - filteredList.Add(player); - } - } - - return filteredList; + return newList; } - return newList; + return (from player in newList + let vector = player.GetTruePosition() - source + let magnitude = vector.magnitude + where !PhysicsHelpers.AnyNonTriggersBetween( + source, + vector.normalized, + magnitude, + Constants.ShipAndObjectsMask) + select player).ToList(); } - public static List GetClosestPlayers(PlayerControl source, float distance = 2f, bool ignoreColliders = true, bool ignoreSource = true) + + /// + /// Gets the closest players to a specific player. + /// + /// The source player. + /// Distance to search in. + /// Whether to ignore colliders. + /// Whether to ignore the source player. + /// A list of PlayerControls. + public static List GetClosestPlayers( + PlayerControl source, + float distance = 2f, + bool ignoreColliders = true, + bool ignoreSource = true) { if (!ShipStatus.Instance) { @@ -69,7 +120,17 @@ public static class Helpers return ignoreSource ? players.Where(plr => plr.PlayerId != source.PlayerId).ToList() : players; } - public static List GetClosestPlayers(Vector2 source, float distance = 2f, bool ignoreColliders = true) + /// + /// Gets the closest players to a specific point. + /// + /// The source point. + /// The distance to search in. + /// Whether to ignore colliders. + /// A list of Player Controls. + public static List GetClosestPlayers( + Vector2 source, + float distance = 2f, + bool ignoreColliders = true) { if (!ShipStatus.Instance) { @@ -78,51 +139,65 @@ public static class Helpers List outputList = []; outputList.Clear(); - var allPlayers = GameData.Instance.AllPlayers.ToArray().Select(x=>x.Object); + var allPlayers = GameData.Instance.AllPlayers.ToArray().Select(x => x.Object); - foreach (var playerControl in allPlayers) - { - if (!playerControl || !playerControl.Collider.enabled) - { - continue; - } + outputList.AddRange( + from playerControl in allPlayers + where playerControl && playerControl.Collider.enabled + let vector = playerControl.GetTruePosition() - source + let magnitude = vector.magnitude + where magnitude <= distance && (ignoreColliders || !PhysicsHelpers.AnyNonTriggersBetween( + source, + vector.normalized, + magnitude, + Constants.ShipAndObjectsMask)) + select playerControl); - var vector = playerControl.GetTruePosition() - source; - var magnitude = vector.magnitude; - if (magnitude <= distance && (ignoreColliders || !PhysicsHelpers.AnyNonTriggersBetween(source, vector.normalized, magnitude, Constants.ShipAndObjectsMask))) + outputList.Sort( + delegate(PlayerControl a, PlayerControl b) { - outputList.Add(playerControl); - } - } + var magnitude2 = (a.GetTruePosition() - source).magnitude; + var magnitude3 = (b.GetTruePosition() - source).magnitude; + if (magnitude2 > magnitude3) + { + return 1; + } - outputList.Sort(delegate (PlayerControl a, PlayerControl b) - { - var magnitude2 = (a.GetTruePosition() - source).magnitude; - var magnitude3 = (b.GetTruePosition() - source).magnitude; - if (magnitude2 > magnitude3) - { - return 1; - } - if (magnitude2 < magnitude3) - { - return -1; - } - return 0; - }); + if (magnitude2 < magnitude3) + { + return -1; + } + + return 0; + }); return outputList; } - public static TextMeshPro CreateTextLabel(string name, Transform parent, -AspectPosition.EdgeAlignments alignment, Vector3 distance, float fontSize = 2f, -TextAlignmentOptions textAlignment = TextAlignmentOptions.Center) + /// + /// Creates a TextMeshPro object with the specified parameters. + /// + /// The name of the object. + /// The object parent. + /// The alignment of the TMP object. + /// The distance from the edge. + /// The font size. + /// The text alignment. + /// A new TMP object. + public static TextMeshPro CreateTextLabel( + string name, + Transform parent, + AspectPosition.EdgeAlignments alignment, + Vector3 distance, + float fontSize = 2f, + TextAlignmentOptions textAlignment = TextAlignmentOptions.Center) { var textObj = new GameObject(name) { transform = { - parent = parent + parent = parent, }, - layer = LayerMask.NameToLayer("UI") + layer = LayerMask.NameToLayer("UI"), }; var textMeshPro = textObj.AddComponent(); @@ -139,11 +214,21 @@ TextAlignmentOptions textAlignment = TextAlignmentOptions.Center) return textMeshPro; } - public static DeadBody GetBodyById(byte id) + /// + /// Gets a DeadBody by its parent ID. + /// + /// The player ID. + /// A dead body or null if its not found. + public static DeadBody? GetBodyById(byte id) { return Object.FindObjectsOfType().FirstOrDefault(body => body.ParentId == id); } + /// + /// Gets the suffix for a MiraNumberSuffixes enum. + /// + /// The MiraNumberSuffixes enum. + /// A suffix based on the enum. public static string GetSuffix(MiraNumberSuffixes suffix) { return suffix switch @@ -164,9 +249,9 @@ TextAlignmentOptions textAlignment = TextAlignmentOptions.Center) public static StringBuilder CreateForRole(ICustomRole role) { var taskStringBuilder = new StringBuilder(); - taskStringBuilder.AppendLine($"{role.RoleColor.ToTextColor()}You are a {role.RoleName}."); + taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}You are a {role.RoleName}."); taskStringBuilder.Append(""); - taskStringBuilder.AppendLine($"{role.RoleLongDescription}"); + taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription}"); return taskStringBuilder; } }