diff --git a/.editorconfig b/.editorconfig index 017cd3a..e091cf9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,2 +1 @@ -[*.{cs,vb}] -dotnet_diagnostic.CA1707.severity = none \ No newline at end of file +[*.{cs,vb}] \ No newline at end of file diff --git a/MiraAPI.Example/Buttons/NeutralKillerButton.cs b/MiraAPI.Example.Extension/Buttons/NeutralKillerButton.cs similarity index 81% rename from MiraAPI.Example/Buttons/NeutralKillerButton.cs rename to MiraAPI.Example.Extension/Buttons/NeutralKillerButton.cs index d38253a..b4825dc 100644 --- a/MiraAPI.Example/Buttons/NeutralKillerButton.cs +++ b/MiraAPI.Example.Extension/Buttons/NeutralKillerButton.cs @@ -1,17 +1,20 @@ -using MiraAPI.Example.GameOver; -using MiraAPI.Example.Roles; +using MiraAPI.Example.Extension.GameOver; +using MiraAPI.Example.Extension.Roles; using MiraAPI.GameEnd; using MiraAPI.Hud; using MiraAPI.Utilities.Assets; using UnityEngine; -namespace MiraAPI.Example.Buttons; +namespace MiraAPI.Example.Extension.Buttons; public class NeutralKillerButton : CustomActionButton { public override string Name => "Win Game"; + public override float Cooldown => 0f; + public override LoadableAsset Sprite => ExampleAssets.ExampleButton; + protected override void OnClick() { CustomGameOver.Trigger([PlayerControl.LocalPlayer.Data]); diff --git a/MiraAPI.Example.Extension/ExampleExtensionPlugin.cs b/MiraAPI.Example.Extension/ExampleExtensionPlugin.cs new file mode 100644 index 0000000..38f022c --- /dev/null +++ b/MiraAPI.Example.Extension/ExampleExtensionPlugin.cs @@ -0,0 +1,31 @@ +using System; +using BepInEx; +using BepInEx.Unity.IL2CPP; +using HarmonyLib; +using MiraAPI.PluginLoading; +using Reactor; +using Reactor.Networking; +using Reactor.Networking.Attributes; + +namespace MiraAPI.Example.Extension; + +[BepInAutoPlugin("mira.example.extension", "MiraExampleExtensionMod")] +[BepInProcess("Among Us.exe")] +[BepInDependency(ReactorPlugin.Id)] +[BepInDependency(MiraApiPlugin.Id)] +[BepInDependency(ExamplePlugin.Id)] +[ReactorModFlags(ModFlags.RequireOnAllClients)] +public partial class ExampleExtensionPlugin : BasePlugin, IMiraExtension +{ + public Harmony Harmony { get; } = new(Id); + + public override void Load() + { + Harmony.PatchAll(); + } + + public Type GetBasePluginType() + { + return typeof(ExamplePlugin); + } +} diff --git a/MiraAPI.Example/GameOver/NeutralKillerGameOver.cs b/MiraAPI.Example.Extension/GameOver/NeutralKillerGameOver.cs similarity index 86% rename from MiraAPI.Example/GameOver/NeutralKillerGameOver.cs rename to MiraAPI.Example.Extension/GameOver/NeutralKillerGameOver.cs index 351d593..0ffc26f 100644 --- a/MiraAPI.Example/GameOver/NeutralKillerGameOver.cs +++ b/MiraAPI.Example.Extension/GameOver/NeutralKillerGameOver.cs @@ -1,9 +1,9 @@ -using MiraAPI.Example.Roles; +using MiraAPI.Example.Extension.Roles; using MiraAPI.GameEnd; using MiraAPI.Utilities; using UnityEngine; -namespace MiraAPI.Example.GameOver; +namespace MiraAPI.Example.Extension.GameOver; public class NeutralKillerGameOver : CustomGameOver { diff --git a/MiraAPI.Example.Extension/MiraAPI.Example.Extension.csproj b/MiraAPI.Example.Extension/MiraAPI.Example.Extension.csproj new file mode 100644 index 0000000..8970ff9 --- /dev/null +++ b/MiraAPI.Example.Extension/MiraAPI.Example.Extension.csproj @@ -0,0 +1,14 @@ + + + 1.0.0 + Example extension for Mira API + Angxl, XtraCube + + + + + + + + + diff --git a/MiraAPI.Example.Extension/Modifiers/ExtensionModifier.cs b/MiraAPI.Example.Extension/Modifiers/ExtensionModifier.cs new file mode 100644 index 0000000..1569039 --- /dev/null +++ b/MiraAPI.Example.Extension/Modifiers/ExtensionModifier.cs @@ -0,0 +1,16 @@ +using MiraAPI.Example.Extension.Options; +using MiraAPI.GameOptions; +using MiraAPI.Modifiers.Types; + +namespace MiraAPI.Example.Extension.Modifiers; + +public class ExtensionModifier : GameModifier +{ + public override string ModifierName => "Extension Modifier"; + + public override string GetDescription() => "This is from the extension mod!"; + + public override int GetAssignmentChance() => (int)OptionGroupSingleton.Instance.ModifierChance; + + public override int GetAmountPerGame() => (int)OptionGroupSingleton.Instance.ModifierLimit; +} diff --git a/MiraAPI.Example.Extension/Options/ExtendedFreezerOptions.cs b/MiraAPI.Example.Extension/Options/ExtendedFreezerOptions.cs new file mode 100644 index 0000000..4222194 --- /dev/null +++ b/MiraAPI.Example.Extension/Options/ExtendedFreezerOptions.cs @@ -0,0 +1,13 @@ +using MiraAPI.Example.Roles; +using MiraAPI.GameOptions; +using MiraAPI.GameOptions.Attributes; + +namespace MiraAPI.Example.Extension.Options; + +public class ExtendedFreezerOptions : AbstractOptionGroup +{ + public override string GroupName => "Extended Freezer Role Options"; + + [ModdedNumberOption("Freeze Uses", 1, 5)] + public float FreezeUses { get; set; } = 1; +} diff --git a/MiraAPI.Example.Extension/Options/ExtensionModifierOptions.cs b/MiraAPI.Example.Extension/Options/ExtensionModifierOptions.cs new file mode 100644 index 0000000..33b6d41 --- /dev/null +++ b/MiraAPI.Example.Extension/Options/ExtensionModifierOptions.cs @@ -0,0 +1,17 @@ +using MiraAPI.Example.Extension.Modifiers; +using MiraAPI.GameOptions; +using MiraAPI.GameOptions.Attributes; +using MiraAPI.Utilities; + +namespace MiraAPI.Example.Extension.Options; + +public class ExtensionModifierOptions : AbstractOptionGroup +{ + public override string GroupName => "Extension Modifier Options"; + + [ModdedNumberOption("Modifier Chance", 0, 100, 10, MiraNumberSuffixes.Percent)] + public float ModifierChance { get; set; } = 100f; + + [ModdedNumberOption("Modifier Limit", 0, 10)] + public float ModifierLimit { get; set; } = 0; +} diff --git a/MiraAPI.Example.Extension/Patches/FreezeButtonPatch.cs b/MiraAPI.Example.Extension/Patches/FreezeButtonPatch.cs new file mode 100644 index 0000000..adadd1e --- /dev/null +++ b/MiraAPI.Example.Extension/Patches/FreezeButtonPatch.cs @@ -0,0 +1,15 @@ +using HarmonyLib; +using MiraAPI.Example.Buttons.Freezer; +using MiraAPI.Example.Extension.Options; +using MiraAPI.GameOptions; + +namespace MiraAPI.Example.Extension.Patches; + +[HarmonyPatch(typeof(FreezeButton), nameof(FreezeButton.MaxUses), MethodType.Getter)] +public static class FreezeButtonPatch +{ + public static void Postfix(ref int __result) + { + __result = (int)OptionGroupSingleton.Instance.FreezeUses; + } +} diff --git a/MiraAPI.Example/Roles/NeutralKillerRole.cs b/MiraAPI.Example.Extension/Roles/NeutralKillerRole.cs similarity index 87% rename from MiraAPI.Example/Roles/NeutralKillerRole.cs rename to MiraAPI.Example.Extension/Roles/NeutralKillerRole.cs index 55fdc51..79c6d6d 100644 --- a/MiraAPI.Example/Roles/NeutralKillerRole.cs +++ b/MiraAPI.Example.Extension/Roles/NeutralKillerRole.cs @@ -1,16 +1,20 @@ -using MiraAPI.Example.GameOver; +using MiraAPI.Example.Extension.GameOver; using MiraAPI.GameEnd; using MiraAPI.Roles; using UnityEngine; -namespace MiraAPI.Example.Roles; +namespace MiraAPI.Example.Extension.Roles; public class NeutralKillerRole : ImpostorRole, ICustomRole { public string RoleName => "Outcast Killer"; + public string RoleDescription => "Outcast who can kill."; - public string RoleLongDescription => RoleDescription; + + public string RoleLongDescription => this.RoleDescription; + public Color RoleColor => Color.magenta; + public ModdedRoleTeams Team => ModdedRoleTeams.Custom; public CustomRoleConfiguration Configuration => new(this) diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs index 3b7363a..323bb2f 100644 --- a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs +++ b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs @@ -16,7 +16,7 @@ public class FreezeButton : CustomActionButton public override float Cooldown => OptionGroupSingleton.Instance.FreezeDuration; - public override int MaxUses => (int)OptionGroupSingleton.Instance.FreezeUses; + public override int MaxUses => 1; public override LoadableAsset Sprite => ExampleAssets.ExampleButton; diff --git a/MiraAPI.Example/ExampleEventHandlers.cs b/MiraAPI.Example/ExampleEventHandlers.cs index 079fb9f..0a48ed1 100644 --- a/MiraAPI.Example/ExampleEventHandlers.cs +++ b/MiraAPI.Example/ExampleEventHandlers.cs @@ -9,10 +9,7 @@ using MiraAPI.Events.Vanilla.Player; using MiraAPI.Example.Buttons.Freezer; using MiraAPI.Example.Roles; using MiraAPI.Utilities; -using MiraAPI.Utilities.Assets; using Reactor.Utilities; -using TMPro; -using UnityEngine; namespace MiraAPI.Example; @@ -21,7 +18,7 @@ public static class ExampleEventHandlers public static void Initialize() { // You can register event handlers with the MiraEventManager class. - MiraEventManager.RegisterEventHandler(@event => + var handle = MiraEventManager.RegisterEventHandler(@event => { Logger.Info($"{@event.Source.Data.PlayerName} is about to kill {@event.Target.Data.PlayerName}"); }); @@ -35,6 +32,9 @@ public static class ExampleEventHandlers { Logger.Info($"{@event.Player.Data.PlayerName} completed {@event.Task.TaskType.ToString()}"); }); + + // You can also unregister event handlers using the handle returned from RegisterEventHandler. + MiraEventManager.UnregisterEventHandler(handle); } // If you want to add extra votes to a player, do something like this. @@ -51,7 +51,7 @@ public static class ExampleEventHandlers [RegisterEvent(15)] public static void HandleVoteEvent(HandleVoteEvent @event) { - if (@event.VoteData.Owner.Data.Role is not NeutralKillerRole) return; + if (@event.VoteData.Owner.Data.Role is not FreezerRole) return; @event.VoteData.SetRemainingVotes(0); diff --git a/MiraAPI.Example/Modifiers/CaptainModifier.cs b/MiraAPI.Example/Modifiers/CaptainModifier.cs index 0a889d1..9e20b56 100644 --- a/MiraAPI.Example/Modifiers/CaptainModifier.cs +++ b/MiraAPI.Example/Modifiers/CaptainModifier.cs @@ -1,12 +1,9 @@ -using Il2CppSystem.Collections.Generic; -using MiraAPI.Example.Options.Modifiers; +using MiraAPI.Example.Options.Modifiers; using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.Modifiers.Types; using MiraAPI.Utilities.Assets; -using Reactor.Utilities.Extensions; using UnityEngine; -using Random = System.Random; namespace MiraAPI.Example.Modifiers; diff --git a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs index e1ad218..f2a1e03 100644 --- a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs +++ b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs @@ -1,6 +1,4 @@ using MiraAPI.Modifiers.Types; -using MiraAPI.Utilities.Assets; -using UnityEngine; namespace MiraAPI.Example.Modifiers; diff --git a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs index 1473068..1f9677d 100644 --- a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs +++ b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs @@ -11,7 +11,4 @@ public class FreezerRoleSettings : AbstractOptionGroup [ModdedNumberOption("Freeze Duration", 1, 15, 1, MiraNumberSuffixes.Seconds)] public float FreezeDuration { get; set; } = 5; - - [ModdedNumberOption("Freeze Uses", 1, 5)] - public float FreezeUses { get; set; } = 1; } diff --git a/MiraAPI.Example/Roles/ChameleonRole.cs b/MiraAPI.Example/Roles/ChameleonRole.cs index 6f485d7..c1d7779 100644 --- a/MiraAPI.Example/Roles/ChameleonRole.cs +++ b/MiraAPI.Example/Roles/ChameleonRole.cs @@ -1,4 +1,5 @@ using AmongUs.GameOptions; +using MiraAPI.Patches.Stubs; using MiraAPI.Roles; using Reactor.Utilities; using TMPro; @@ -14,6 +15,8 @@ public class ChameloenRole : CrewmateRole, ICustomRole public Color RoleColor => Palette.AcceptedGreen; public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; + private bool _shouldHide; + public CustomRoleConfiguration Configuration => new(this) { OptionsScreenshot = ExampleAssets.Banner, @@ -22,35 +25,54 @@ public class ChameloenRole : CrewmateRole, ICustomRole public override void Initialize(PlayerControl player) { - Player = player; - Logger.Error("Initializing ChamelonRole for player: " + player.PlayerId); + Logger.Info("Initializing ChamelonRole for player: " + player.PlayerId); + RoleBehaviourStubs.Initialize(this, player); + _shouldHide = true; } - public void PlayerControlFixedUpdate(PlayerControl playerControl) + public void FixedUpdate() { - if (playerControl.MyPhysics.Velocity.magnitude > 0) + if (!Player || !_shouldHide) { - SpriteRenderer rend = playerControl.cosmetics.currentBodySprite.BodySprite; - TextMeshPro tmp = playerControl.cosmetics.nameText; + return; + } + + if (Player.MyPhysics.Velocity.magnitude > 0) + { + var rend = Player.cosmetics.currentBodySprite.BodySprite; + var tmp = Player.cosmetics.nameText; tmp.color = Color.Lerp(tmp.color, new Color(tmp.color.r, tmp.color.g, tmp.color.b, 1), Time.deltaTime * 4f); rend.color = Color.Lerp(rend.color, new Color(1, 1, 1, 1), Time.deltaTime * 4f); - foreach (var cosmetic in playerControl.cosmetics.transform.GetComponentsInChildren()) + foreach (var cosmetic in Player.cosmetics.transform.GetComponentsInChildren()) { cosmetic.color = Color.Lerp(cosmetic.color, new Color(1, 1, 1, 1), Time.deltaTime * 4f); } } else { - SpriteRenderer rend = playerControl.cosmetics.currentBodySprite.BodySprite; - TextMeshPro tmp = playerControl.cosmetics.nameText; - tmp.color = Color.Lerp(tmp.color, new Color(tmp.color.r, tmp.color.g, tmp.color.b, playerControl.AmOwner ? 0.3f : 0), Time.deltaTime * 4f); - rend.color = Color.Lerp(rend.color, new Color(1, 1, 1, playerControl.AmOwner ? 0.3f : 0), Time.deltaTime * 4f); + SpriteRenderer rend = Player.cosmetics.currentBodySprite.BodySprite; + TextMeshPro tmp = Player.cosmetics.nameText; + tmp.color = Color.Lerp(tmp.color, new Color(tmp.color.r, tmp.color.g, tmp.color.b, Player.AmOwner ? 0.3f : 0), Time.deltaTime * 4f); + rend.color = Color.Lerp(rend.color, new Color(1, 1, 1, Player.AmOwner ? 0.3f : 0), Time.deltaTime * 4f); - foreach (var cosmetic in playerControl.cosmetics.transform.GetComponentsInChildren()) + foreach (var cosmetic in Player.cosmetics.transform.GetComponentsInChildren()) { - cosmetic.color = Color.Lerp(cosmetic.color, new Color(1, 1, 1, playerControl.AmOwner ? 0.3f : 0), Time.deltaTime * 4f); + cosmetic.color = Color.Lerp(cosmetic.color, new Color(1, 1, 1, Player.AmOwner ? 0.3f : 0), Time.deltaTime * 4f); } } } + + public override void Deinitialize(PlayerControl targetPlayer) + { + Logger.Info("Deinitializing ChamelonRole for player: " + targetPlayer.PlayerId); + RoleBehaviourStubs.Deinitialize(this, targetPlayer); + _shouldHide = false; + foreach (var cosmetic in Player.cosmetics.transform.GetComponentsInChildren(true)) + { + cosmetic.color = Color.white; + } + Player.cosmetics.currentBodySprite.BodySprite.color = Color.white; + Player.cosmetics.nameText.color = Color.white; + } } diff --git a/MiraAPI.sln b/MiraAPI.sln index 5b2a2c0..7e73464 100644 --- a/MiraAPI.sln +++ b/MiraAPI.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiraAPI", "MiraAPI\MiraAPI. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiraAPI.Example", "MiraAPI.Example\MiraAPI.Example.csproj", "{19B76855-16CC-43CC-A0A6-5A7FFB43F077}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiraAPI.Example.Extension", "MiraAPI.Example.Extension\MiraAPI.Example.Extension.csproj", "{526FF017-80E1-4094-804D-741AF35AA226}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {19B76855-16CC-43CC-A0A6-5A7FFB43F077}.Debug|Any CPU.Build.0 = Debug|Any CPU {19B76855-16CC-43CC-A0A6-5A7FFB43F077}.Release|Any CPU.ActiveCfg = Release|Any CPU {19B76855-16CC-43CC-A0A6-5A7FFB43F077}.Release|Any CPU.Build.0 = Release|Any CPU + {526FF017-80E1-4094-804D-741AF35AA226}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {526FF017-80E1-4094-804D-741AF35AA226}.Debug|Any CPU.Build.0 = Debug|Any CPU + {526FF017-80E1-4094-804D-741AF35AA226}.Release|Any CPU.ActiveCfg = Release|Any CPU + {526FF017-80E1-4094-804D-741AF35AA226}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MiraAPI/Events/MiraEventHandle.cs b/MiraAPI/Events/MiraEventHandle.cs new file mode 100644 index 0000000..70514c4 --- /dev/null +++ b/MiraAPI/Events/MiraEventHandle.cs @@ -0,0 +1,22 @@ +using System; + +namespace MiraAPI.Events; + +/// +/// Event handle to use when unregistering an event. +/// +public class MiraEventHandle +{ + /// + /// Gets the type of event. + /// + public Type EventType { get; } + + internal MiraEventWrapper EventWrapper { get; } + + internal MiraEventHandle(Type type, MiraEventWrapper eventWrapper) + { + EventType = type; + EventWrapper = eventWrapper; + } +} diff --git a/MiraAPI/Events/MiraEventManager.cs b/MiraAPI/Events/MiraEventManager.cs index 52df8dd..0878bd5 100644 --- a/MiraAPI/Events/MiraEventManager.cs +++ b/MiraAPI/Events/MiraEventManager.cs @@ -28,7 +28,14 @@ public static class MiraEventManager foreach (var handler in handlers) { - ((Action)handler.EventHandler).Invoke(eventInstance); + try + { + ((Action)handler.EventHandler).Invoke(eventInstance); + } + catch (Exception ex) + { + Logger.Error($"Error invoking event handler for {typeof(T).Name}: {ex.ToString()}"); + } } return true; @@ -50,7 +57,14 @@ public static class MiraEventManager foreach (var handler in handlers) { - handler.EventHandler.DynamicInvoke(eventInstance); + try + { + handler.EventHandler.DynamicInvoke(eventInstance); + } + catch (Exception ex) + { + Logger.Error($"Error invoking event handler for {type.Name}: {ex.ToString()}"); + } } return true; @@ -62,12 +76,12 @@ public static class MiraEventManager /// The type of event. /// The MethodInfo of the event handler. /// The priority of the event handler. Lower values are called first. - public static void RegisterEventHandler(Type type, MethodInfo methodInfo, int priority = 0) + /// An event handle to use when unregistering the event. + public static MiraEventHandle RegisterEventHandler(Type type, MethodInfo methodInfo, int priority = 0) { if (!type.IsSubclassOf(typeof(MiraEvent))) { - Logger.Error($"Type must be a subclass of MiraEvent: {type.FullName}"); - return; + throw new InvalidOperationException($"Type must be a subclass of MiraEvent: {type.FullName}"); } EventWrappers.TryAdd(type, []); @@ -84,6 +98,7 @@ public static class MiraEventManager } handlers.Insert(index, eventWrapper); + return new MiraEventHandle(type, eventWrapper); } /// @@ -92,7 +107,8 @@ public static class MiraEventManager /// The callback method/handler for the event. /// 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 + /// An event handle to use when unregistering the event. + public static MiraEventHandle RegisterEventHandler(Action handler, int priority = 0) where T : MiraEvent { EventWrappers.TryAdd(typeof(T), []); @@ -107,5 +123,30 @@ public static class MiraEventManager } handlers.Insert(index, eventWrapper); + return new MiraEventHandle(typeof(T), eventWrapper); + } + + /// + /// Unregister an event using the event handle. + /// + /// A handle to the event. + /// True if the event was unregistered successfully, false otherwise. + public static bool UnregisterEventHandler(MiraEventHandle eventHandle) + { + if (!EventWrappers.TryGetValue(eventHandle.EventType, out var handlers)) + { + return false; + } + + if (!handlers.Remove(eventHandle.EventWrapper)) + { + return false; + } + + if (handlers.Count == 0) + { + EventWrappers.Remove(eventHandle.EventType); + } + return true; } } diff --git a/MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs b/MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs index fcf9df4..0f0da3b 100644 --- a/MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs +++ b/MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs @@ -15,14 +15,21 @@ public class AfterMurderEvent : MiraEvent /// public PlayerControl Target { get; } + /// + /// Gets the player's body, if it exists. + /// + public DeadBody? DeadBody { get; } + /// /// Initializes a new instance of the class. /// /// The killer. /// The killed player. - public AfterMurderEvent(PlayerControl source, PlayerControl target) + /// The player's vody, if it exists. + public AfterMurderEvent(PlayerControl source, PlayerControl target, DeadBody? deadBody) { Source = source; Target = target; + DeadBody = deadBody; } } diff --git a/MiraAPI/Events/Vanilla/Map/UpdateSystemEvent.cs b/MiraAPI/Events/Vanilla/Map/UpdateSystemEvent.cs index 6b6d907..5b70090 100644 --- a/MiraAPI/Events/Vanilla/Map/UpdateSystemEvent.cs +++ b/MiraAPI/Events/Vanilla/Map/UpdateSystemEvent.cs @@ -24,6 +24,7 @@ public class UpdateSystemEvent : MiraCancelableEvent /// Initializes a new instance of the class. /// /// The SystemType being updated. + /// The player that is updating the system. /// Amount to update System to. public UpdateSystemEvent(SystemTypes systemType, PlayerControl player, byte amount) { diff --git a/MiraAPI/Events/Vanilla/Player/PlayerDeathEvent.cs b/MiraAPI/Events/Vanilla/Player/PlayerDeathEvent.cs index e787b13..9b1ae49 100644 --- a/MiraAPI/Events/Vanilla/Player/PlayerDeathEvent.cs +++ b/MiraAPI/Events/Vanilla/Player/PlayerDeathEvent.cs @@ -15,14 +15,21 @@ public class PlayerDeathEvent : MiraEvent /// public DeathReason DeathReason { get; } + /// + /// Gets the dead body associated with the player, if any. + /// + public DeadBody? DeadBody { get; } + /// /// Initializes a new instance of the class. /// /// The player who died. /// The reason the player died. - public PlayerDeathEvent(PlayerControl player, DeathReason reason) + /// The player's body, if it exists. + public PlayerDeathEvent(PlayerControl player, DeathReason reason, DeadBody? deadBody) { Player = player; DeathReason = reason; + DeadBody = deadBody; } } diff --git a/MiraAPI/GameModes/CustomGameModeManager.cs b/MiraAPI/GameModes/CustomGameModeManager.cs index e4305f5..5ce22e0 100644 --- a/MiraAPI/GameModes/CustomGameModeManager.cs +++ b/MiraAPI/GameModes/CustomGameModeManager.cs @@ -21,7 +21,7 @@ public static class CustomGameModeManager } /// - /// Current gamemode. + /// Gets the current gamemode. /// public static CustomGameMode? ActiveMode { get; internal set; } = new DefaultMode(); diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index 4f7c583..ef5284e 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -45,7 +45,9 @@ public static class ModdedOptionsManager pluginInfo.InternalOptionGroups.Add(group); typeof(OptionGroupSingleton<>).MakeGenericType(type) +#pragma warning disable S3011 .GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)! +#pragma warning restore S3011 .SetValue(null, group); return true; @@ -173,7 +175,7 @@ public static class ModdedOptionsManager /// /// The original setter method. /// The new object value. - // ReSharper disable once InconsistentNaming + // ReSharper disable InconsistentNaming public static void PropertySetterPatch(MethodBase __originalMethod, object value) { var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod() == __originalMethod).Value; @@ -186,8 +188,8 @@ public static class ModdedOptionsManager /// The original getter method. /// The result of the property getter. /// False so the original getter gets skipped. - // ReSharper disable InconsistentNaming public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result) + // ReSharper restore InconsistentNaming { var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod() == __originalMethod).Value; __result = attribute.GetValue(); diff --git a/MiraAPI/GameOptions/ModifierOptionGroup.cs b/MiraAPI/GameOptions/ModifierOptionGroup.cs index cfa2eff..3db6566 100644 --- a/MiraAPI/GameOptions/ModifierOptionGroup.cs +++ b/MiraAPI/GameOptions/ModifierOptionGroup.cs @@ -2,7 +2,9 @@ namespace MiraAPI.GameOptions; +#pragma warning disable CA1852 internal class ModifierOptionGroup : AbstractOptionGroup +#pragma warning restore CA1852 { public override string GroupName { get; } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index 94f6f3d..b56524a 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -27,7 +27,7 @@ public abstract class ModdedOption : IModdedOption public StringNames StringName { get; } /// - public BaseGameSetting Data { get; protected init; } = null!; + public BaseGameSetting Data { get; protected set; } = null!; /// public IMiraPlugin? ParentMod diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index e215fda..7e0c0e2 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -180,7 +180,7 @@ public abstract class CustomActionButton if (!moveButton) return; - if (HudManagerPatches._bottomLeft == null || HudManagerPatches._bottomRight == null) + if (HudManagerPatches.BottomLeft == null || HudManagerPatches.BottomRight == null) { return; } @@ -188,17 +188,17 @@ public abstract class CustomActionButton switch (location) { case ButtonLocation.BottomLeft: - var gridArrange = HudManagerPatches._bottomLeft.GetComponent(); - var aspectPosition = HudManagerPatches._bottomLeft.GetComponent(); + var gridArrange = HudManagerPatches.BottomLeft.GetComponent(); + var aspectPosition = HudManagerPatches.BottomLeft.GetComponent(); - Button.transform.SetParent(HudManagerPatches._bottomLeft.transform); + Button.transform.SetParent(HudManagerPatches.BottomLeft.transform); gridArrange.Start(); gridArrange.ArrangeChilds(); aspectPosition.AdjustPosition(); break; case ButtonLocation.BottomRight: - Button.transform.SetParent(HudManagerPatches._bottomRight); + Button.transform.SetParent(HudManagerPatches.BottomRight); break; } } @@ -435,11 +435,11 @@ public abstract class CustomActionButton { if (CanUse()) { - Button.SetEnabled(); + Button!.SetEnabled(); } else { - Button.SetDisabled(); + Button!.SetDisabled(); } if (EffectActive) diff --git a/MiraAPI/Hud/CustomButtonManager.cs b/MiraAPI/Hud/CustomButtonManager.cs index 9b176d2..e576595 100644 --- a/MiraAPI/Hud/CustomButtonManager.cs +++ b/MiraAPI/Hud/CustomButtonManager.cs @@ -31,7 +31,9 @@ public static class CustomButtonManager CustomButtons.Add(button); pluginInfo.InternalButtons.Add(button); typeof(CustomButtonSingleton<>).MakeGenericType(buttonType) +#pragma warning disable S3011 .GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)! +#pragma warning restore S3011 .SetValue(null, button); ButtonEventTypes.Add(buttonType, typeof(MiraButtonClickEvent<>).MakeGenericType(buttonType)); diff --git a/MiraAPI/Hud/CustomButtonSingleton.cs b/MiraAPI/Hud/CustomButtonSingleton.cs index b338e61..daa5467 100644 --- a/MiraAPI/Hud/CustomButtonSingleton.cs +++ b/MiraAPI/Hud/CustomButtonSingleton.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Diagnostics.CodeAnalysis; +using System.Linq; namespace MiraAPI.Hud; @@ -13,7 +14,6 @@ public static class CustomButtonSingleton where T : CustomActionButton /// /// Gets the instance of the button. /// -#pragma warning disable CA1000 + [SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "This is a utility class to get the instance of a custom action button.")] public static T Instance => _instance ??= CustomButtonManager.CustomButtons.OfType().Single(); -#pragma warning restore CA1000 } diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 1a83838..5c29dc2 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -3,14 +3,11 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Text; using Il2CppInterop.Runtime.Attributes; using MiraAPI.Modifiers.ModifierDisplay; using MiraAPI.Modifiers.Types; -using MiraAPI.Utilities; using Reactor.Utilities; using Reactor.Utilities.Attributes; -using TMPro; using UnityEngine; namespace MiraAPI.Modifiers; @@ -72,7 +69,15 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) foreach (var modifier in _toRemove.ToArray()) { _toRemove.Remove(modifier); - modifier.OnDeactivate(); + try + { + modifier.OnDeactivate(); + } + catch (Exception e) + { + Logger.Error($"Error while deactivating modifier {modifier.ModifierName}: {e.ToString()}"); + } + Modifiers.Remove(modifier); } @@ -82,7 +87,14 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) _toAdd.Remove(modifier); Modifiers.Add(modifier); modifier.Initialized = true; - modifier.OnActivate(); + try + { + modifier.OnActivate(); + } + catch (Exception e) + { + Logger.Error($"Error while activating modifier {modifier.ModifierName}: {e.ToString()}"); + } if (modifier is TimedModifier { AutoStart: true } timer) { @@ -102,7 +114,14 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) foreach (var modifier in ActiveModifiers) { - modifier.FixedUpdate(); + try + { + modifier.FixedUpdate(); + } + catch (Exception e) + { + Logger.Error($"Error while (fixed) updating modifier {modifier.ModifierName}: {e.ToString()}"); + } } if (_player.AmOwner && ModifierDisplay && HudManager.InstanceExists) @@ -115,7 +134,14 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) { foreach (var modifier in ActiveModifiers) { - modifier.Update(); + try + { + modifier.Update(); + } + catch (Exception e) + { + Logger.Error($"Error while updating modifier {modifier.ModifierName}: {e.ToString()}"); + } } } diff --git a/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs b/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs index fe23ecc..f1a614e 100644 --- a/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs +++ b/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs @@ -17,7 +17,7 @@ namespace MiraAPI.Modifiers.ModifierDisplay; /// The code used to display Mira modifiers. /// [RegisterInIl2Cpp] -public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr) +public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr) { /// /// Gets the instance of the Modifier Display. @@ -34,8 +34,8 @@ public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr) private PassiveButton _nextButton = null!; private PassiveButton _backButton = null!; - private int _currentPage = 0; - private const int _itemsPerPage = 3; + private int _currentPage; + private const int ItemsPerPage = 3; private readonly Dictionary _modifiers = []; @@ -142,10 +142,10 @@ public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr) private void UpdatePage() { - var totalPages = Mathf.CeilToInt((float)_modifiers.Count / _itemsPerPage); + var totalPages = Mathf.CeilToInt((float)_modifiers.Count / ItemsPerPage); _currentPage = Mathf.Clamp(_currentPage, 0, Mathf.Max(0, totalPages - 1)); - if (_modifiers.Count <= _itemsPerPage) + if (_modifiers.Count <= ItemsPerPage) { _modifiers.Do(x => x.Value.gameObject.SetActive(true)); _pagination.gameObject.SetActive(false); @@ -162,8 +162,8 @@ public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr) _backButton.gameObject.SetActive(true); _modifiers.Do(x => x.Value.gameObject.SetActive(false)); - var start = _currentPage * _itemsPerPage; - var end = Mathf.Min(start + _itemsPerPage, _modifiers.Count); + var start = _currentPage * ItemsPerPage; + var end = Mathf.Min(start + ItemsPerPage, _modifiers.Count); var modifiers = _modifiers.ToArray(); for (var i = start; i < end; i++) diff --git a/MiraAPI/Modifiers/ModifierDisplay/ModifierUiComponent.cs b/MiraAPI/Modifiers/ModifierDisplay/ModifierUiComponent.cs index a17dcc3..ef8b0bb 100644 --- a/MiraAPI/Modifiers/ModifierDisplay/ModifierUiComponent.cs +++ b/MiraAPI/Modifiers/ModifierDisplay/ModifierUiComponent.cs @@ -9,7 +9,7 @@ namespace MiraAPI.Modifiers.ModifierDisplay; /// The code placed on every Modifier HUD object. Used to handle updating. /// [RegisterInIl2Cpp] -public class ModifierUiComponent(nint ptr) : MonoBehaviour(ptr) +public class ModifierUiComponent(nint cppPtr) : MonoBehaviour(cppPtr) { /// /// Gets the modifier which this component is for. @@ -17,12 +17,13 @@ public class ModifierUiComponent(nint ptr) : MonoBehaviour(ptr) [HideFromIl2Cpp] public BaseModifier? Modifier { get; internal set; } + // ReSharper disable InconsistentNaming private SpriteRenderer modBg = null!; private TextMeshPro desc = null!; private SpriteRenderer icon = null!; private TextMeshPro nameText = null!; private RectTransform descRect = null!; - + // ReSharper restore InconsistentNaming private void Awake() { if (Modifier == null) return; diff --git a/MiraAPI/Modifiers/ModifierFactory.cs b/MiraAPI/Modifiers/ModifierFactory.cs index 8f8d005..4f84db0 100644 --- a/MiraAPI/Modifiers/ModifierFactory.cs +++ b/MiraAPI/Modifiers/ModifierFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using MiraAPI.Utilities; @@ -11,7 +12,7 @@ namespace MiraAPI.Modifiers; /// public static class ModifierFactory { - private static readonly Dictionary<(Type, Type[]), Func> _constructorCache = []; + private static readonly Dictionary<(Type, Type[]), Func> ConstructorCache = []; private static Func CreateConstructor(Type type, params object[] args) { @@ -45,13 +46,13 @@ public static class ModifierFactory /// An instance of the modifier. public static BaseModifier CreateInstance(Type type, params object[] args) { - var argTypes = args.Select(arg => arg?.GetType() ?? typeof(object)).ToArray(); + var argTypes = args.Select(arg => arg.GetType()).ToArray(); var key = (type, argTypes); - if (!_constructorCache.TryGetValue(key, out var constructor)) + if (!ConstructorCache.TryGetValue(key, out var constructor)) { constructor = CreateConstructor(type, args); - _constructorCache[key] = constructor; + ConstructorCache[key] = constructor; } return constructor(args); @@ -95,6 +96,7 @@ public static class ModifierFactory where T : BaseModifier /// /// Parameters for the constructor. /// An instance of the modifier. + [SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "This is a factory class.")] public static T CreateInstance(params object[] args) { return Constructor(args); diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index 369314f..ac14094 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -5,7 +5,6 @@ using System.Runtime.Serialization; using MiraAPI.Modifiers.Types; using MiraAPI.PluginLoading; using MiraAPI.Roles; -using MiraAPI.Utilities; using Reactor.Utilities; using Reactor.Utilities.Extensions; using Random = System.Random; diff --git a/MiraAPI/Networking/CustomMurderRpc.cs b/MiraAPI/Networking/CustomMurderRpc.cs index 5879ebf..4a7bc30 100644 --- a/MiraAPI/Networking/CustomMurderRpc.cs +++ b/MiraAPI/Networking/CustomMurderRpc.cs @@ -264,7 +264,7 @@ public static class CustomMurderRpc deadBody.enabled = true; } - var afterMurderEvent = new AfterMurderEvent(source, target); + var afterMurderEvent = new AfterMurderEvent(source, target, deadBody); MiraEventManager.InvokeEvent(afterMurderEvent); if (!isParticipant) diff --git a/MiraAPI/Networking/MiraRpc.cs b/MiraAPI/Networking/MiraRpc.cs index 29703d7..28c6c7a 100644 --- a/MiraAPI/Networking/MiraRpc.cs +++ b/MiraAPI/Networking/MiraRpc.cs @@ -46,7 +46,7 @@ public enum MiraRpc : uint RemoveVote, /// - /// Custom RPC to replace Among Us CastVote + /// Custom RPC to replace Among Us CastVote. /// CastVote, } diff --git a/MiraAPI/Patches/.editorconfig b/MiraAPI/Patches/.editorconfig new file mode 100644 index 0000000..1afcd96 --- /dev/null +++ b/MiraAPI/Patches/.editorconfig @@ -0,0 +1,5 @@ +[*.{cs,vb}] +dotnet_diagnostic.CA1707.severity = none +dotnet_diagnostic.CS1591.severity = none +dotnet_diagnostic.SA1611.severity = none +dotnet_diagnostic.SA1615.severity = none \ No newline at end of file diff --git a/MiraAPI/Patches/Events/KillAnimationPatches.cs b/MiraAPI/Patches/Events/KillAnimationPatches.cs index 3572ff8..5e7b3ef 100644 --- a/MiraAPI/Patches/Events/KillAnimationPatches.cs +++ b/MiraAPI/Patches/Events/KillAnimationPatches.cs @@ -1,6 +1,7 @@ using HarmonyLib; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; +using MiraAPI.Utilities; namespace MiraAPI.Patches.Events; @@ -19,7 +20,7 @@ public static class KillAnimationPatches [HarmonyPatch(typeof(KillAnimation), nameof(KillAnimation.CoPerformKill))] public static void KillAnimPostfix(PlayerControl source, PlayerControl target) { - var afterMurderEvent = new AfterMurderEvent(source, target); + var afterMurderEvent = new AfterMurderEvent(source, target, Helpers.GetBodyById(target.PlayerId)); MiraEventManager.InvokeEvent(afterMurderEvent); } } diff --git a/MiraAPI/Patches/Hud/ButtonResetPatches.cs b/MiraAPI/Patches/Hud/ButtonResetPatches.cs index b9756ce..0859bea 100644 --- a/MiraAPI/Patches/Hud/ButtonResetPatches.cs +++ b/MiraAPI/Patches/Hud/ButtonResetPatches.cs @@ -4,7 +4,7 @@ using MiraAPI.Hud; namespace MiraAPI.Patches.Hud; /// -/// Reset button patches +/// Reset button patches. /// [HarmonyPatch] public static class ButtonResetPatches diff --git a/MiraAPI/Patches/HudManagerPatches.cs b/MiraAPI/Patches/HudManagerPatches.cs index fa057d4..27a8026 100644 --- a/MiraAPI/Patches/HudManagerPatches.cs +++ b/MiraAPI/Patches/HudManagerPatches.cs @@ -13,9 +13,9 @@ namespace MiraAPI.Patches; public static class HudManagerPatches { // Custom buttons parent. - internal static GameObject? _bottomLeft; - internal static Transform? _bottomRight; - internal static Transform? _buttons; + public static GameObject? BottomLeft { get; private set; } + public static Transform? BottomRight { get; private set; } + public static Transform? Buttons { get; private set; } /* /// @@ -36,30 +36,30 @@ public static class HudManagerPatches [HarmonyPatch(nameof(HudManager.Start))] public static void StartPostfix(HudManager __instance) { - if (_buttons == null) + if (Buttons == null) { - _buttons = __instance.transform.Find("Buttons"); + Buttons = __instance.transform.Find("Buttons"); } - if (_bottomRight == null) + if (BottomRight == null) { - _bottomRight = _buttons.Find("BottomRight"); + BottomRight = Buttons.Find("BottomRight"); } - if (_bottomLeft == null) + if (BottomLeft == null) { - _bottomLeft = Object.Instantiate(_bottomRight.gameObject, _buttons); + BottomLeft = Object.Instantiate(BottomRight.gameObject, Buttons); } - foreach (var t in _bottomLeft.GetComponentsInChildren(true)) + foreach (var t in BottomLeft.GetComponentsInChildren(true)) { t.gameObject.Destroy(); } - var gridArrange = _bottomLeft.GetComponent(); - var aspectPosition = _bottomLeft.GetComponent(); + var gridArrange = BottomLeft.GetComponent(); + var aspectPosition = BottomLeft.GetComponent(); - _bottomLeft.name = "BottomLeft"; + BottomLeft.name = "BottomLeft"; gridArrange.Alignment = GridArrange.StartAlign.Right; aspectPosition.Alignment = AspectPosition.EdgeAlignments.LeftBottom; @@ -67,8 +67,8 @@ public static class HudManagerPatches { var location = button.Location switch { - ButtonLocation.BottomLeft => _bottomLeft.transform, - ButtonLocation.BottomRight => _bottomRight, + ButtonLocation.BottomLeft => BottomLeft.transform, + ButtonLocation.BottomRight => BottomRight, _ => null, }; diff --git a/MiraAPI/Patches/Menu/NameplatesTabPatches.cs b/MiraAPI/Patches/Menu/NameplatesTabPatches.cs index fee0ac7..d66c8f3 100644 --- a/MiraAPI/Patches/Menu/NameplatesTabPatches.cs +++ b/MiraAPI/Patches/Menu/NameplatesTabPatches.cs @@ -108,7 +108,7 @@ public static class NameplatesTabPatches var x = (NamePlateViewData viewdata) => { colorChip.transform.GetChild(1).GetComponent().sprite = viewdata?.Image; - //(colorChip as NameplateChip).image.sprite = viewdata?.Image; + // (colorChip as NameplateChip).image.sprite = viewdata?.Image; }; __instance.StartCoroutine(AddressableAssetExtensions.CoLoadAssetAsync(__instance, namePlate.GetAssetReference(), x)); colorChip.transform.localPosition = new Vector3(position.x, position.y, -1f); diff --git a/MiraAPI/Patches/MinigameBeginErrorPatch.cs b/MiraAPI/Patches/MinigameBeginErrorPatch.cs index 863be42..ce59415 100644 --- a/MiraAPI/Patches/MinigameBeginErrorPatch.cs +++ b/MiraAPI/Patches/MinigameBeginErrorPatch.cs @@ -3,7 +3,7 @@ namespace MiraAPI.Patches; /// -/// Used to fix the Minigame crash errors (credits Submerged/PH Gaming for the fix) +/// Used to fix the Minigame crash errors (credits Submerged/PH Gaming for the fix). /// [HarmonyPatch] public static class MinigameBeginErrorPatch diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index c252c6e..b80388d 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -31,7 +31,7 @@ internal static class GameSettingMenuPatches private static PassiveButton? _modifiersButton; private static PassiveButton? _smallRolesButton; - private static Dictionary OptionScrolls = []; + private static Dictionary OptionsPositions { get; } = []; [HarmonyPostfix] [HarmonyPatch(nameof(GameSettingMenu.ChangeTab))] @@ -115,11 +115,11 @@ internal static class GameSettingMenuPatches passiveButton.OnClick.AddListener( (UnityAction)(() => { - RoleSettingMenuPatches.Scrolls[SelectedModIdx] = - __instance.RoleSettingsTab.scrollBar.Inner.localPosition; - OptionScrolls[SelectedModIdx] = - __instance.GameSettingsTab.scrollBar.Inner.localPosition; - + if (!__instance.RoleSettingsTab.AdvancedRolesSettings.active) + { + RoleSettingMenuPatches.RolePositions[SelectedModIdx] = __instance.RoleSettingsTab.scrollBar.Inner.localPosition; + } + OptionsPositions[SelectedModIdx] = __instance.GameSettingsTab.scrollBar.Inner.localPosition; SelectedModIdx += 1; if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins.Length) { @@ -138,11 +138,11 @@ internal static class GameSettingMenuPatches backButton.gameObject.GetComponent().OnClick.AddListener( (UnityAction)(() => { - RoleSettingMenuPatches.Scrolls[SelectedModIdx] = - __instance.RoleSettingsTab.scrollBar.Inner.localPosition; - OptionScrolls[SelectedModIdx] = - __instance.GameSettingsTab.scrollBar.Inner.localPosition; - + if (!__instance.RoleSettingsTab.AdvancedRolesSettings.active) + { + RoleSettingMenuPatches.RolePositions[SelectedModIdx] = __instance.RoleSettingsTab.scrollBar.Inner.localPosition; + } + OptionsPositions[SelectedModIdx] = __instance.GameSettingsTab.scrollBar.Inner.localPosition; SelectedModIdx -= 1; if (SelectedModIdx < 0) { @@ -411,43 +411,6 @@ internal static class GameSettingMenuPatches private static void CleanupTab(GameOptionsMenu settings, RolesSettingsMenu roles) { - if (roles.roleChances != null) - { - CleanupRoleSettings(roles); - } - - if (settings.Children != null) - { - CleanupSettings(settings); - } - - if (_modifiersTab?.Children?.Count > 0) - { - CleanupSettings(_modifiersTab); - } - - void CleanupSettings(GameOptionsMenu gameOptMenu) - { - ClearOptions(gameOptMenu.Children); - gameOptMenu.Children = null; - - gameOptMenu.settingsContainer - .GetComponentsInChildren() - .ToList() - .ForEach(header => header.gameObject.DestroyImmediate()); - - gameOptMenu.Initialize(); - if (OptionScrolls.TryGetValue(SelectedModIdx, out var pos)) - { - gameOptMenu.scrollBar.Inner.localPosition = pos; - gameOptMenu.scrollBar.UpdateScrollBars(); - } - else - { - gameOptMenu.scrollBar.ScrollToTop(); - } - } - void CleanupRoleSettings(RolesSettingsMenu rolesMenu) { if (rolesMenu.advancedSettingChildren != null) @@ -472,18 +435,52 @@ internal static class GameSettingMenuPatches rolesMenu.AdvancedRolesSettings.gameObject.SetActive(false); rolesMenu.RoleChancesSettings.gameObject.SetActive(true); rolesMenu.SetQuotaTab(); - if (SelectedModIdx == 0) + if (RoleSettingMenuPatches.RolePositions.TryGetValue(SelectedModIdx, out var pos)) { - if (RoleSettingMenuPatches.Scrolls.TryGetValue(0, out var pos)) - { - rolesMenu.scrollBar.Inner.localPosition = pos; - rolesMenu.scrollBar.UpdateScrollBars(); - } - else - { - rolesMenu.scrollBar.ScrollToTop(); - } + rolesMenu.scrollBar.Inner.localPosition = pos; + rolesMenu.scrollBar.UpdateScrollBars(); } + else + { + rolesMenu.scrollBar.ScrollToTop(); + } + } + + void CleanupSettings(GameOptionsMenu gameOptMenu) + { + ClearOptions(gameOptMenu.Children); + gameOptMenu.Children = null; + + gameOptMenu.settingsContainer + .GetComponentsInChildren() + .ToList() + .ForEach(header => header.gameObject.DestroyImmediate()); + + gameOptMenu.Initialize(); + if (OptionsPositions.TryGetValue(SelectedModIdx, out var pos)) + { + gameOptMenu.scrollBar.Inner.localPosition = pos; + gameOptMenu.scrollBar.UpdateScrollBars(); + } + else + { + gameOptMenu.scrollBar.ScrollToTop(); + } + } + + if (roles.roleChances != null && SelectedMod?.CustomRoles.Count > 0) + { + CleanupRoleSettings(roles); + } + + if (settings.Children != null && SelectedMod?.OptionGroups.Count > 0) + { + CleanupSettings(settings); + } + + if (_modifiersTab?.Children?.Count > 0) + { + CleanupSettings(_modifiersTab); } } } diff --git a/MiraAPI/Patches/Options/NotificationPopperPatch.cs b/MiraAPI/Patches/Options/NotificationPopperPatch.cs index 02efe2b..e6d394a 100644 --- a/MiraAPI/Patches/Options/NotificationPopperPatch.cs +++ b/MiraAPI/Patches/Options/NotificationPopperPatch.cs @@ -2,6 +2,8 @@ using HarmonyLib; using Il2CppSystem; using MiraAPI.Roles; +using UnityEngine; +using Object = Il2CppSystem.Object; namespace MiraAPI.Patches.Options; @@ -23,15 +25,13 @@ public static class NotificationPopperPatch return true; } - var text = teamType == RoleTeamTypes.Crewmate - ? Palette.CrewmateSettingChangeText.ToTextColor() - : Palette.ImpostorRed.ToTextColor(); + var textColor = customRole.OptionsMenuColor.ToTextColor(); var item = TranslationController.Instance.GetString( StringNames.LobbyChangeSettingNotificationRole, string.Concat( "", - text, + textColor, TranslationController.Instance.GetString(key, Array.Empty()), "" ), diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 8984694..550b3ab 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -22,10 +22,11 @@ namespace MiraAPI.Patches.Options; [HarmonyPatch(typeof(RolesSettingsMenu))] public static class RoleSettingMenuPatches { - public static Dictionary Scrolls { get; } = []; - private static Dictionary RoleGroupHidden { get; } = []; - private static List Headers { get; } = []; - private static List RoleOptionSettings { get; } = []; + public static Dictionary RolePositions { get; set; } = []; + + private static Dictionary RoleGroupHidden { get; set; } = []; + private static List Headers { get; set; } = []; + private static List RoleOptionSettings { get; set; } = []; private static float ScrollerNum { get; set; } = 0.522f; @@ -233,7 +234,7 @@ public static class RoleSettingMenuPatches private static void SetScrollBounds(this Scroller scroller) { scroller.CalculateAndSetYBounds(1 + 1.5f * Headers.Count + RoleOptionSettings.Count, 1f, 6f, 0.43f); - if (Scrolls.TryGetValue(GameSettingMenuPatches.SelectedModIdx, out var scroll)) + if (RolePositions.TryGetValue(GameSettingMenuPatches.SelectedModIdx, out var scroll)) { scroller.Inner.localPosition = scroll; scroller.UpdateScrollBars(); @@ -353,7 +354,6 @@ public static class RoleSettingMenuPatches newOpt.Initialize(); } - Scrolls[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition; __instance.scrollBar.CalculateAndSetYBounds(__instance.advancedSettingChildren.Count + 3, 1f, 6f, 0.45f); __instance.scrollBar.ScrollToTop(); } @@ -366,7 +366,7 @@ public static class RoleSettingMenuPatches return; } - Scrolls[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition; + RolePositions[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition; __instance.roleDescriptionText.text = customRole.RoleLongDescription; __instance.roleTitleText.text = TranslationController.Instance.GetString( diff --git a/MiraAPI/Patches/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs index 0f53f9f..01d83c2 100644 --- a/MiraAPI/Patches/PlayerControlPatches.cs +++ b/MiraAPI/Patches/PlayerControlPatches.cs @@ -5,7 +5,7 @@ using MiraAPI.Events.Vanilla.Gameplay; using MiraAPI.Events.Vanilla.Player; using MiraAPI.Hud; using MiraAPI.Modifiers; -using MiraAPI.Roles; +using MiraAPI.Utilities; using MiraAPI.Voting; using Reactor.Utilities.Extensions; @@ -41,7 +41,7 @@ internal static class PlayerControlPatches // ReSharper disable once InconsistentNaming public static void PlayerControlDiePostfix(PlayerControl __instance, DeathReason reason) { - var deathEvent = new PlayerDeathEvent(__instance, reason); + var deathEvent = new PlayerDeathEvent(__instance, reason, Helpers.GetBodyById(__instance.PlayerId)); MiraEventManager.InvokeEvent(deathEvent); var modifiersComponent = __instance.GetComponent(); @@ -81,11 +81,6 @@ internal static class PlayerControlPatches // ReSharper disable once InconsistentNaming public static void PlayerControlFixedUpdatePostfix(PlayerControl __instance) { - if (__instance.Data?.Role is ICustomRole customRole) - { - customRole.PlayerControlFixedUpdate(__instance); - } - if (!__instance.AmOwner) { return; diff --git a/MiraAPI/Patches/Roles/EjectionPatches.cs b/MiraAPI/Patches/Roles/EjectionPatches.cs index 73bf0a7..85641eb 100644 --- a/MiraAPI/Patches/Roles/EjectionPatches.cs +++ b/MiraAPI/Patches/Roles/EjectionPatches.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Reflection; -using HarmonyLib; +using HarmonyLib; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; using MiraAPI.Events.Vanilla.Meeting; diff --git a/MiraAPI/Patches/Roles/GameStartupPatch.cs b/MiraAPI/Patches/Roles/GameStartupPatch.cs index 9bb2a54..6ab234d 100644 --- a/MiraAPI/Patches/Roles/GameStartupPatch.cs +++ b/MiraAPI/Patches/Roles/GameStartupPatch.cs @@ -10,7 +10,7 @@ public static class GameStartupPatch private static bool _runOnce; /// - /// This is used for registering roles when the game opens, might be a janky solution, but it works + /// This is used for registering roles when the game opens, might be a janky solution, but it works. /// [HarmonyPostfix] [HarmonyPatch(nameof(MainMenuManager.Start))] diff --git a/MiraAPI/Patches/Roles/HauntMenuPatch.cs b/MiraAPI/Patches/Roles/HauntMenuPatch.cs index b174165..a7618a6 100644 --- a/MiraAPI/Patches/Roles/HauntMenuPatch.cs +++ b/MiraAPI/Patches/Roles/HauntMenuPatch.cs @@ -24,4 +24,4 @@ public static class HauntMenuMinigamePatch __instance.FilterText.text = role.NiceName; __instance.FilterText.color = color; } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Roles/HudManagerPatches.cs b/MiraAPI/Patches/Roles/HudManagerPatches.cs index 6bf8b27..29affce 100644 --- a/MiraAPI/Patches/Roles/HudManagerPatches.cs +++ b/MiraAPI/Patches/Roles/HudManagerPatches.cs @@ -62,8 +62,6 @@ public static class HudManagerPatches if (role is ICustomRole { Configuration.RoleHintType: RoleHintType.RoleTab } customRole) { - customRole.HudUpdate(__instance); - if (_roleTab == null) { _roleTab = CustomRoleManager.CreateRoleTab(customRole); diff --git a/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs b/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs index e2ddb4a..49840ee 100644 --- a/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs +++ b/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs @@ -12,7 +12,7 @@ namespace MiraAPI.Patches.Roles; public static class RoleOptionsCollectionPatch { /// - /// Set the role chance for custom Launchpad roles based on config + /// Set the role chance for custom Launchpad roles based on config. /// /// Return false to skip original method, true to not. [HarmonyPrefix] @@ -42,7 +42,7 @@ public static class RoleOptionsCollectionPatch } /// - /// Set the amount for custom Launchpad roles based on config + /// Set the amount for custom Launchpad roles based on config. /// /// Return false to skip original method, true to not. [HarmonyPrefix] diff --git a/MiraAPI/Patches/Roles/SabotageButtonPatch.cs b/MiraAPI/Patches/Roles/SabotageButtonPatch.cs index 6706235..258af83 100644 --- a/MiraAPI/Patches/Roles/SabotageButtonPatch.cs +++ b/MiraAPI/Patches/Roles/SabotageButtonPatch.cs @@ -4,7 +4,7 @@ using MiraAPI.Roles; namespace MiraAPI.Patches.Roles; [HarmonyPatch(typeof(SabotageButton))] -public class SabotageButtonPatch +public static class SabotageButtonPatch { /// /// Patches the Sabotage button to check if the player's custom role can use sabotage. diff --git a/MiraAPI/Patches/Voting/MeetingHudPatches.cs b/MiraAPI/Patches/Voting/MeetingHudPatches.cs index 809485e..2dceb0b 100644 --- a/MiraAPI/Patches/Voting/MeetingHudPatches.cs +++ b/MiraAPI/Patches/Voting/MeetingHudPatches.cs @@ -8,7 +8,6 @@ using MiraAPI.Events.Vanilla.Meeting.Voting; using MiraAPI.Modifiers; using MiraAPI.Utilities; using MiraAPI.Voting; -using Reactor.Utilities; namespace MiraAPI.Patches.Voting; diff --git a/MiraAPI/PluginLoading/IMiraExtension.cs b/MiraAPI/PluginLoading/IMiraExtension.cs new file mode 100644 index 0000000..1ab75d0 --- /dev/null +++ b/MiraAPI/PluginLoading/IMiraExtension.cs @@ -0,0 +1,8 @@ +using System; + +namespace MiraAPI.PluginLoading; + +public interface IMiraExtension +{ + public Type GetBasePluginType(); +} diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index b62fbf1..1056c39 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; +using BepInEx; using BepInEx.Unity.IL2CPP; using MiraAPI.Colors; using MiraAPI.Events; @@ -37,91 +38,29 @@ public sealed class MiraPluginManager Instance = this; IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, assembly, plugin) => { - if (plugin is not IMiraPlugin miraPlugin) + if (plugin is IMiraPlugin miraPlugin) { - return; + var miraInfo = new MiraPluginInfo(miraPlugin, pluginInfo); + RegisterPlugin(miraInfo, assembly); + _registeredPlugins.Add(assembly, miraInfo); } - - var info = new MiraPluginInfo(miraPlugin, pluginInfo); - var roles = new List(); - - var oldConfigSetting = info.PluginConfig.SaveOnConfigSet; - info.PluginConfig.SaveOnConfigSet = false; - - foreach (var type in assembly.GetTypes()) + if (plugin is IMiraExtension miraExtension) { - if (type.GetCustomAttribute() != null) + var basePlugin = miraExtension.GetBasePluginType(); + var basePluginInfo = _registeredPlugins.Values.FirstOrDefault(x => x.MiraPlugin.GetType() == basePlugin); + if (basePluginInfo == null) { - continue; + Logger.Error($"Base plugin {basePlugin.Name} not found for extension {pluginInfo.Metadata.GUID}."); + return; } - foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public)) - { - var eventAttribute = method.GetCustomAttribute(); - if (eventAttribute != null) - { - var parameters = method.GetParameters(); - if (parameters.Length != 1 || !parameters[0].ParameterType.IsSubclassOf(typeof(MiraEvent))) - { - Logger.Error($"Invalid event registration method {method.Name} in {type.Name}"); - continue; - } - - var paramType = parameters[0].ParameterType; - MiraEventManager.RegisterEventHandler(paramType, method, eventAttribute.Priority); - } - } - - if (RegisterModifier(type, info)) - { - continue; - } - - if (RegisterOptions(type, info)) - { - continue; - } - - if (RegisterRoleAttribute(type, info, out var role)) - { - roles.Add(role); - continue; - } - - if (RegisterButtonAttribute(type, info)) - { - continue; - } - - if (RegisterGameOver(type)) - { - continue; - } - - RegisterColorClasses(type); + RegisterPlugin(basePluginInfo, assembly); } - - info.PluginConfig.Save(); - info.PluginConfig.SaveOnConfigSet = oldConfigSetting; - - info.InternalOptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority)); - QueuedRoleRegistrations.Add(info, roles); - - _registeredPlugins.Add(assembly, info); - - info.SavePublicCollections(); - PresetManager.CreateDefaultPreset(info); - PresetManager.LoadPresets(info); - Logger.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API."); }; IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors; IL2CPPChainloader.Instance.Finished += () => { - // Save all buttons into a read-only collection for easy access CustomButtonManager.Buttons = new ReadOnlyCollection(CustomButtonManager.CustomButtons); - - // Cache all the registered plugins into an array for easy access - RegisteredPlugins = [.._registeredPlugins.Values]; }; } @@ -135,6 +74,88 @@ public sealed class MiraPluginManager return Instance._registeredPlugins.Values.FirstOrDefault(plugin => plugin.PluginId == pluginId); } + private void RegisterPlugin(MiraPluginInfo info, Assembly assembly) + { + var roles = new List(); + + var oldConfigSetting = info.PluginConfig.SaveOnConfigSet; + info.PluginConfig.SaveOnConfigSet = false; + + foreach (var type in assembly.GetTypes()) + { + if (type.GetCustomAttribute() != null) + { + continue; + } + + foreach (var method in type.GetMethods()) + { + var eventAttribute = method.GetCustomAttribute(); + if (eventAttribute == null) + { + continue; + } + + if (!method.IsStatic) + { + Logger.Error($"Event method {method.Name} in {type.Name} must be static."); + continue; + } + + var parameters = method.GetParameters(); + if (parameters.Length != 1 || !parameters[0].ParameterType.IsSubclassOf(typeof(MiraEvent))) + { + Logger.Error($"Invalid event registration method {method.Name} in {type.Name}"); + continue; + } + + var paramType = parameters[0].ParameterType; + MiraEventManager.RegisterEventHandler(paramType, method, eventAttribute.Priority); + } + + if (RegisterModifier(type, info)) + { + continue; + } + + if (RegisterOptions(type, info)) + { + continue; + } + + if (RegisterRole(type, info, out var role)) + { + roles.Add(role); + continue; + } + + if (RegisterButton(type, info)) + { + continue; + } + + if (RegisterGameOver(type)) + { + continue; + } + + RegisterColorClasses(type); + } + + info.PluginConfig.Save(); + info.PluginConfig.SaveOnConfigSet = oldConfigSetting; + + info.InternalOptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority)); + + QueuedRoleRegistrations.TryAdd(info, []); + QueuedRoleRegistrations[info].AddRange(roles); + + info.SavePublicCollections(); + PresetManager.CreateDefaultPreset(info); + PresetManager.LoadPresets(info); + Logger.Info($"Registering mod {info.PluginId} with Mira API."); + } + private static bool RegisterGameOver(Type type) { try @@ -164,6 +185,12 @@ public sealed class MiraPluginManager foreach (var property in type.GetProperties()) { + if (property.GetMethod?.IsStatic == true) + { + Logger.Error($"Option property {property.Name} in {type.Name} must not be static."); + continue; + } + if (property.PropertyType.IsAssignableTo(typeof(IModdedOption))) { ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo); @@ -179,16 +206,21 @@ public sealed class MiraPluginManager ModdedOptionsManager.RegisterAttributeOption(type, attribute, property, pluginInfo); } + foreach (var field in type.GetFields().Where(f=>f.FieldType.IsAssignableTo(typeof(IModdedOption)))) + { + Logger.Error($"{field.Name} is a field, not a property. Use properties for options."); + } + return true; } catch (Exception e) { - Logger.Error($"Failed to register options for {type.Name}: {e}"); + Logger.Error($"Failed to register options for {type.Name}: {e.ToString()}"); } return false; } - private static bool RegisterRoleAttribute(Type type, MiraPluginInfo pluginInfo, [NotNullWhen(true)] out Type? role) + private static bool RegisterRole(Type type, MiraPluginInfo pluginInfo, [NotNullWhen(true)] out Type? role) { role = null; try @@ -244,6 +276,11 @@ public sealed class MiraPluginManager PaletteManager.CustomColors.Add(color); } + + foreach (var field in type.GetFields().Where(f=>f.FieldType.IsAssignableTo(typeof(CustomColor)))) + { + Logger.Error($"{field.Name} is a field, not a property. Use properties for colors."); + } } catch (Exception e) { @@ -264,7 +301,7 @@ public sealed class MiraPluginManager } } - private static bool RegisterButtonAttribute(Type type, MiraPluginInfo pluginInfo) + private static bool RegisterButton(Type type, MiraPluginInfo pluginInfo) { try { diff --git a/MiraAPI/Roles/CustomRoleSingleton.cs b/MiraAPI/Roles/CustomRoleSingleton.cs index 808202c..90867af 100644 --- a/MiraAPI/Roles/CustomRoleSingleton.cs +++ b/MiraAPI/Roles/CustomRoleSingleton.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Diagnostics.CodeAnalysis; +using System.Linq; namespace MiraAPI.Roles; @@ -8,12 +9,11 @@ namespace MiraAPI.Roles; /// The role you are trying to access. public static class CustomRoleSingleton where T : ICustomRole { - private static ICustomRole? _instance; + private static T? _instance; /// /// Gets the instance of the role. /// -#pragma warning disable CA1000 - public static ICustomRole Instance => _instance ??= CustomRoleManager.CustomRoles.Values.OfType().Single(); -#pragma warning restore CA1000 + [SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "This is a utility class to get the instance of a custom role.")] + public static T Instance => _instance ??= CustomRoleManager.CustomRoles.Values.OfType().Single(); } diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index 8f69e3d..51f8de8 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -73,14 +73,6 @@ public interface ICustomRole : IOptionable /// MiraPluginInfo ParentMod => CustomRoleManager.FindParentMod(this); - /// - /// This method runs on the PlayerControl.FixedUpdate method for ALL players with this role. - /// - /// The PlayerControl that has this role. - void PlayerControlFixedUpdate(PlayerControl playerControl) - { - } - internal ConfigDefinition NumConfigDefinition => new("Roles", $"Num {GetType().FullName}"); internal ConfigDefinition ChanceConfigDefinition => new("Roles", $"Chance {GetType().FullName}"); @@ -222,14 +214,6 @@ public interface ICustomRole : IOptionable return true; } - /// - /// This method runs on the HudManager.Update method ONLY when the LOCAL player has this role. - /// - /// Reference to HudManager instance. - void HudUpdate(HudManager hudManager) - { - } - /// /// Gets a custom ejection message for the role. Return null to use the default message. /// diff --git a/MiraAPI/Roles/ModdedRoleTeams.cs b/MiraAPI/Roles/ModdedRoleTeams.cs index 0d8c6a1..8661060 100644 --- a/MiraAPI/Roles/ModdedRoleTeams.cs +++ b/MiraAPI/Roles/ModdedRoleTeams.cs @@ -1,8 +1,22 @@ namespace MiraAPI.Roles; +/// +/// Enum for modded role teams. +/// public enum ModdedRoleTeams { + /// + /// The crewmate team. + /// Crewmate, + + /// + /// The impostor team. + /// Impostor, + + /// + /// Custom team, catchall for any other teams. + /// Custom, } diff --git a/MiraAPI/Roles/RoleId.cs b/MiraAPI/Roles/RoleId.cs index 05f9cd0..91044fb 100644 --- a/MiraAPI/Roles/RoleId.cs +++ b/MiraAPI/Roles/RoleId.cs @@ -2,9 +2,18 @@ namespace MiraAPI.Roles; +/// +/// Utility class to get the ID of a role. +/// public static class RoleId { - public static ushort Get() where T : RoleBehaviour + /// + /// Gets the ID of a role. + /// + /// The type of the role. + /// The role ID as a ushort. + /// Thrown when the role is not registered. + public static ushort Get() where T : ICustomRole { if (!CustomRoleManager.RoleIds.TryGetValue(typeof(T), out var roleId)) { @@ -14,6 +23,12 @@ public static class RoleId return roleId; } + /// + /// Gets the ID of a role. + /// + /// The type of the role. + /// The ID as a ushort. + /// Thrown when the role is not registered. public static ushort Get(Type type) { if (!CustomRoleManager.RoleIds.TryGetValue(type, out var roleId)) diff --git a/MiraAPI/Utilities/Assets/LoadableResourceAsset.cs b/MiraAPI/Utilities/Assets/LoadableResourceAsset.cs index 47ce04a..5678997 100644 --- a/MiraAPI/Utilities/Assets/LoadableResourceAsset.cs +++ b/MiraAPI/Utilities/Assets/LoadableResourceAsset.cs @@ -7,6 +7,7 @@ namespace MiraAPI.Utilities.Assets; /// A utility class for loading assets from embedded resources. /// /// The path to the embedded resource. +/// The pixels per unit for the loaded sprite. public class LoadableResourceAsset(string path, float pixelsPerUnit=100f) : LoadableAsset { private readonly Assembly _assembly = Assembly.GetCallingAssembly(); @@ -14,9 +15,9 @@ public class LoadableResourceAsset(string path, float pixelsPerUnit=100f) : Load /// public override Sprite LoadAsset() { - if (LoadedAsset != null) + if (LoadedAsset) { - return LoadedAsset; + return LoadedAsset!; } return LoadedAsset = SpriteTools.LoadSpriteFromPath(path, _assembly, pixelsPerUnit); diff --git a/MiraAPI/Utilities/Assets/SpriteTools.cs b/MiraAPI/Utilities/Assets/SpriteTools.cs index 667d0c9..dc668ec 100644 --- a/MiraAPI/Utilities/Assets/SpriteTools.cs +++ b/MiraAPI/Utilities/Assets/SpriteTools.cs @@ -13,11 +13,12 @@ public static class SpriteTools /// /// Loads and returns a texture from a resource path using the specified assembly. /// - /// The path to the resource within the assembly. - /// The assembly from which to load the resource. - /// A object loaded from the specified resource path. - /// Thrown when the resource cannot be found in the specified assembly. - public static Texture2D LoadTextureFromResourcePath(string resourcePath, Assembly assembly) + /// The path to the resource. + /// The assembly containing the resource. + /// The pixels per unit for the loaded sprite. + /// A sprite made from the resource. + /// The resource cannot be found. + public static Sprite LoadSpriteFromPath(string resourcePath, Assembly assembly, float pixelsPerUnit) { var tex = new Texture2D(1, 1, TextureFormat.ARGB32, false); var myStream = assembly.GetManifestResourceStream(resourcePath); diff --git a/MiraAPI/Utilities/MiraNumberSuffixes.cs b/MiraAPI/Utilities/MiraNumberSuffixes.cs index 20ed21c..2f55226 100644 --- a/MiraAPI/Utilities/MiraNumberSuffixes.cs +++ b/MiraAPI/Utilities/MiraNumberSuffixes.cs @@ -1,8 +1,27 @@ namespace MiraAPI.Utilities; + +/// +/// Suffixes used for options numbers. +/// public enum MiraNumberSuffixes { + /// + /// No suffix. + /// None, + + /// + /// Multiplier suffix (x). + /// Multiplier, + + /// + /// Seconds suffix (s). + /// Seconds, + + /// + /// Percentage suffix (%). + /// Percent, }