diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index ca76373..6c87428 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -46,6 +46,6 @@ jobs:
files: MiraAPI/bin/Release/net6.0/MiraAPI.dll
- name: Push NuGet package
- if: github.repository == 'All-Of-Us-Mods/MiraAPI' && github.ref == 'refs/heads/master' && github.ref_type == 'tag'
+ if: github.repository == 'All-Of-Us-Mods/MiraAPI' && github.ref == 'refs/heads/master'
run: |
dotnet nuget push {MiraAPI/bin/Release/AllOfUs.MiraAPI.*.nupkg} --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index 097874d..2431ce4 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -8,7 +8,7 @@
true
- 0.1.3
+ 0.1.4
dev
All Of Us, XtraCube, Angxl
@@ -33,6 +33,11 @@
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
$(MSBuildThisFileDirectory)\stylecop.ruleset
diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs
index 3926316..f8de0ab 100644
--- a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs
+++ b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs
@@ -21,9 +21,9 @@ public class FreezeButton : CustomActionButton
Target?.RpcAddModifier();
}
- public override PlayerControl GetTarget()
+ public override PlayerControl? GetTarget()
{
- return PlayerControl.LocalPlayer.Data.Role.FindClosestTarget();
+ return PlayerControl.LocalPlayer.GetClosestPlayer(true, Distance, false);
}
public override void SetOutline(bool active)
@@ -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.Example/Buttons/MeetingButton.cs b/MiraAPI.Example/Buttons/MeetingButton.cs
index 639e663..4b4f087 100644
--- a/MiraAPI.Example/Buttons/MeetingButton.cs
+++ b/MiraAPI.Example/Buttons/MeetingButton.cs
@@ -1,10 +1,11 @@
-using MiraAPI.Example;
-using MiraAPI.Example.Modifiers;
+using MiraAPI.Example.Modifiers;
using MiraAPI.Hud;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
+namespace MiraAPI.Example.Buttons;
+
[RegisterButton]
public class MeetingButton : CustomActionButton
{
@@ -18,7 +19,7 @@ public class MeetingButton : CustomActionButton
public override LoadableAsset Sprite => ExampleAssets.ExampleButton;
- public override bool Enabled(RoleBehaviour role)
+ public override bool Enabled(RoleBehaviour? role)
{
return PlayerControl.LocalPlayer.HasModifier();
}
diff --git a/MiraAPI.Example/ExamplePlugin.cs b/MiraAPI.Example/ExamplePlugin.cs
index 79ddabd..89ba051 100644
--- a/MiraAPI.Example/ExamplePlugin.cs
+++ b/MiraAPI.Example/ExamplePlugin.cs
@@ -22,6 +22,5 @@ public partial class ExamplePlugin : BasePlugin, IMiraPlugin
public override void Load()
{
Harmony.PatchAll();
- AddComponent();
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI.Example/MiraDebugWindow.cs b/MiraAPI.Example/MiraDebugWindow.cs
index 0332776..9b0fa7e 100644
--- a/MiraAPI.Example/MiraDebugWindow.cs
+++ b/MiraAPI.Example/MiraDebugWindow.cs
@@ -1,6 +1,4 @@
-using MiraAPI.Example.Modifiers;
-using MiraAPI.Utilities;
-using Reactor.Utilities.Attributes;
+using Reactor.Utilities.Attributes;
using Reactor.Utilities.ImGui;
using System;
using UnityEngine;
@@ -21,11 +19,11 @@ public class MiraDebugWindow(IntPtr ptr) : MonoBehaviour(ptr)
}
})
{
- Enabled = true
+ Enabled = true,
};
public void OnGUI()
{
DebuggingWindow.OnGUI();
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI.Example/Modifiers/CaptainModifier.cs b/MiraAPI.Example/Modifiers/CaptainModifier.cs
index 80d9224..09c733c 100644
--- a/MiraAPI.Example/Modifiers/CaptainModifier.cs
+++ b/MiraAPI.Example/Modifiers/CaptainModifier.cs
@@ -7,6 +7,7 @@ namespace MiraAPI.Example.Modifiers;
public class CaptainModifier : GameModifier
{
public override string ModifierName => "Captain";
+
public override int GetAmountPerGame()
{
return 1;
@@ -16,4 +17,4 @@ public class CaptainModifier : GameModifier
{
return 100;
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs b/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs
index d112406..12a87ec 100644
--- a/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs
+++ b/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs
@@ -1,5 +1,8 @@
-using MiraAPI.Modifiers;
+using Il2CppSystem;
+using MiraAPI.Example.Roles;
+using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
+using UnityEngine;
namespace MiraAPI.Example.Modifiers.Freezer;
@@ -14,15 +17,25 @@ public class FreezeModifier : TimedModifier
public override void OnActivate()
{
- if (Player.AmOwner)
+ if (Player?.AmOwner == true)
{
Player.moveable = false;
}
}
+ public override void FixedUpdate()
+ {
+ base.FixedUpdate();
+
+ if (Player?.AmOwner == true || PlayerControl.LocalPlayer.Data.Role is FreezerRole)
+ {
+ Player?.cosmetics.SetOutline(true, new Nullable(Palette.LightBlue));
+ }
+ }
+
public override void OnTimerComplete()
{
- if (Player.AmOwner)
+ if (Player?.AmOwner == true)
{
Player.moveable = true;
}
diff --git a/MiraAPI.Example/Options/ExampleOptions.cs b/MiraAPI.Example/Options/ExampleOptions.cs
index 13a7dd7..9e9fa59 100644
--- a/MiraAPI.Example/Options/ExampleOptions.cs
+++ b/MiraAPI.Example/Options/ExampleOptions.cs
@@ -19,16 +19,13 @@ public class ExampleOptions : AbstractOptionGroup
[ModdedNumberOption("Number Opt", min: 0, max: 10, suffixType: MiraNumberSuffixes.Percent)]
public float NumberOpt { get; set; } = 4f;
- [ModdedStringOption("String Opt", ["Hello", "Hey", "Bye"])]
- public int StringOpt { get; set; } = 2;
-
- [ModdedEnumOption("Best API", typeof(BestApiEnum), ["Mira API", "Mitochondria", "Reactor"])]
- public BestApiEnum EnumOpt { get; set; } = BestApiEnum.MiraAPI;
+ [ModdedEnumOption("Best API", typeof(BestApi), ["Mira API", "Mitochondria", "Reactor"])]
+ public BestApi Opt { get; set; } = BestApi.MiraAPI;
}
-public enum BestApiEnum
+public enum BestApi
{
MiraAPI,
Mitochondria,
- Reactor
-}
\ No newline at end of file
+ Reactor,
+}
diff --git a/MiraAPI.Example/Options/ExampleOptions2.cs b/MiraAPI.Example/Options/ExampleOptions2.cs
index 594bb36..c521ea8 100644
--- a/MiraAPI.Example/Options/ExampleOptions2.cs
+++ b/MiraAPI.Example/Options/ExampleOptions2.cs
@@ -1,5 +1,6 @@
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.OptionTypes;
+using Reactor.Utilities;
namespace MiraAPI.Example.Options;
@@ -11,17 +12,18 @@ public class ExampleOptions2 : AbstractOptionGroup
public ModdedToggleOption ToggleOpt2 { get; } = new("Toggle Option 2", false)
{
- Visible = () => OptionGroupSingleton.Instance.ToggleOpt1.Value
+ Visible = () => OptionGroupSingleton.Instance.ToggleOpt1.Value,
};
- public ModdedStringOption StringOpt { get; } = new("String Opt", 0, ["Choice 1", "Choice 2", "Choice 3"]);
-
- public ModdedEnumOption EnumOpt { get; } = new("Enum Opt", 0, typeof(TestingEnum));
+ public ModdedEnumOption EnumOpt { get; } = new("Enum Opt", 0, typeof(TestingData))
+ {
+ ChangedEvent = x => Logger.Info($"changed Enum Opt to {x}"),
+ };
}
-public enum TestingEnum
+public enum TestingData
{
Happy,
Sad,
- Neutral
-}
\ No newline at end of file
+ Neutral,
+}
diff --git a/MiraAPI.Example/Options/Roles/TeleporterOptions.cs b/MiraAPI.Example/Options/Roles/TeleporterOptions.cs
index 3e51e33..b8bb823 100644
--- a/MiraAPI.Example/Options/Roles/TeleporterOptions.cs
+++ b/MiraAPI.Example/Options/Roles/TeleporterOptions.cs
@@ -20,4 +20,4 @@ public class TeleporterOptions : AbstractOptionGroup
[ModdedNumberOption("Zoom Distance", 4, 15)]
public float ZoomDistance { get; set; } = 6;
-}
\ No newline at end of file
+}
diff --git a/MiraAPI.Example/Roles/FreezerRole.cs b/MiraAPI.Example/Roles/FreezerRole.cs
index e649b1a..55e22c0 100644
--- a/MiraAPI.Example/Roles/FreezerRole.cs
+++ b/MiraAPI.Example/Roles/FreezerRole.cs
@@ -1,4 +1,5 @@
-using MiraAPI.Roles;
+using Il2CppInterop.Runtime.Attributes;
+using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using UnityEngine;
@@ -12,6 +13,7 @@ public class FreezerRole : ImpostorRole, ICustomRole
public string RoleDescription => RoleLongDescription;
public Color RoleColor => Palette.Blue;
public ModdedRoleTeams Team => ModdedRoleTeams.Impostor;
+ [HideFromIl2Cpp]
public LoadableAsset OptionsScreenshot => ExampleAssets.Banner;
public int MaxPlayers => 2;
-}
\ No newline at end of file
+}
diff --git a/MiraAPI.Example/Roles/TeleporterRole.cs b/MiraAPI.Example/Roles/TeleporterRole.cs
index be7528b..c45f942 100644
--- a/MiraAPI.Example/Roles/TeleporterRole.cs
+++ b/MiraAPI.Example/Roles/TeleporterRole.cs
@@ -1,4 +1,5 @@
-using MiraAPI.Roles;
+using Il2CppInterop.Runtime.Attributes;
+using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using UnityEngine;
@@ -12,6 +13,6 @@ public class TeleporterRole : CrewmateRole, ICustomRole
public string RoleDescription => RoleLongDescription;
public Color RoleColor => new Color32(221, 176, 152, 255);
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
+ [HideFromIl2Cpp]
public LoadableAsset OptionsScreenshot => ExampleAssets.Banner;
-
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/Colors/CustomColor.cs b/MiraAPI/Colors/CustomColor.cs
index 3e3df97..7bbe87a 100644
--- a/MiraAPI/Colors/CustomColor.cs
+++ b/MiraAPI/Colors/CustomColor.cs
@@ -3,21 +3,42 @@ using Reactor.Localization.Utilities;
using UnityEngine;
namespace MiraAPI.Colors;
+
+///
+/// Represents a custom color with a main color and a shadow color.
+///
+/// The name of the option.
+/// The main color.
+/// The shadow color.
public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor)
{
+ ///
+ /// Gets the main color.
+ ///
public Color32 MainColor { get; } = mainColor;
+
+ ///
+ /// Gets the shadow color.
+ ///
public Color32 ShadowColor { get; } = shadowColor;
+
+ ///
+ /// Gets the name of the color.
+ ///
public StringNames Name { get; } = name;
+ ///
public CustomColor(StringNames name, Color32 mainColor) : this(name, mainColor, mainColor.GetShadowColor(60))
{
}
+ ///
public CustomColor(string name, Color32 mainColor) : this(name, mainColor, mainColor.GetShadowColor(60))
{
}
+ ///
public CustomColor(string name, Color32 mainColor, Color32 shadowColor) : this(CustomStringName.CreateAndRegister(name), mainColor, shadowColor)
{
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/Colors/PaletteManager.cs b/MiraAPI/Colors/PaletteManager.cs
index a94de66..c788353 100644
--- a/MiraAPI/Colors/PaletteManager.cs
+++ b/MiraAPI/Colors/PaletteManager.cs
@@ -4,18 +4,29 @@ using HarmonyLib;
namespace MiraAPI.Colors;
-internal static class PaletteManager
+///
+/// Used to register and track custom colors.
+///
+public static class PaletteManager
{
- public static readonly List CustomColors = [];
-
- public static void RegisterAllColors()
- {
- var colors = CustomColors.Select(x => x.MainColor);
- var shadowColors = CustomColors.Select(x => x.ShadowColor);
- var stringNames = CustomColors.Select(x => x.Name);
+ ///
+ /// Gets all registered custom colors.
+ ///
+ public static CustomColor[] RegisteredColors => [.. CustomColors];
- Palette.PlayerColors = Palette.PlayerColors.ToArray().AddRangeToArray(colors.ToArray());
- Palette.ShadowColors = Palette.ShadowColors.ToArray().AddRangeToArray(shadowColors.ToArray());
- Palette.ColorNames = Palette.ColorNames.ToArray().AddRangeToArray(stringNames.ToArray());
- }
-}
\ No newline at end of file
+ internal static readonly List CustomColors = [];
+
+ internal static void RegisterAllColors()
+ {
+ var colors = CustomColors.Select(x => x.MainColor).ToArray();
+ var shadowColors = CustomColors.Select(x => x.ShadowColor).ToArray();
+ var stringNames = CustomColors.Select(x => x.Name).ToArray();
+
+ Palette.PlayerColors = Palette.PlayerColors.ToArray().AddRangeToArray(colors);
+ Palette.ShadowColors = Palette.ShadowColors.ToArray().AddRangeToArray(shadowColors);
+ Palette.ColorNames = Palette.ColorNames.ToArray().AddRangeToArray(stringNames);
+
+ Palette.TextColors = Palette.TextColors.ToArray().AddRangeToArray(colors);
+ Palette.TextOutlineColors = Palette.TextOutlineColors.ToArray().AddRangeToArray(shadowColors);
+ }
+}
diff --git a/MiraAPI/Colors/RegisterCustomColorsAttribute.cs b/MiraAPI/Colors/RegisterCustomColorsAttribute.cs
index 93d99b7..0e9ace7 100644
--- a/MiraAPI/Colors/RegisterCustomColorsAttribute.cs
+++ b/MiraAPI/Colors/RegisterCustomColorsAttribute.cs
@@ -2,5 +2,8 @@
namespace MiraAPI.Colors;
+///
+/// Used to mark a class for custom colors registration.
+///
[AttributeUsage(AttributeTargets.Class)]
-public class RegisterCustomColorsAttribute : Attribute;
\ No newline at end of file
+public class RegisterCustomColorsAttribute : Attribute;
diff --git a/MiraAPI/GameModes/CustomGameMode.cs b/MiraAPI/GameModes/CustomGameMode.cs
index c030fb4..c7cf639 100644
--- a/MiraAPI/GameModes/CustomGameMode.cs
+++ b/MiraAPI/GameModes/CustomGameMode.cs
@@ -3,74 +3,82 @@
namespace MiraAPI.GameModes;
///
-/// Base class for custom gamemodes
+/// Base class for custom gamemodes.
///
public abstract class CustomGameMode
{
///
- /// Game mode name
+ /// Gets the game mode name.
///
public abstract string Name { get; }
///
- /// Game mode description
+ /// Gets the game mode description.
///
public abstract string Description { get; }
///
- /// Game mode ID
+ /// Gets the game mode ID.
///
public abstract int Id { get; }
///
- /// Called when Intro Cutscene is destroyed
+ /// Called when Intro Cutscene is destroyed.
///
- public virtual void Initialize() { }
+ public virtual void Initialize()
+ {
+ }
///
- /// Called on HudManager.Start()
+ /// Called on HudManager.Start().
///
- /// Instance of HudManager
- public virtual void HudStart(HudManager instance) { }
+ /// Instance of HudManager.
+ public virtual void HudStart(HudManager instance)
+ {
+ }
///
- /// Called every frame on HudManager.Update()
+ /// Called every frame on HudManager.Update().
///
- /// Instance of HudManager
- public virtual void HudUpdate(HudManager instance) { }
+ /// Instance of HudManager.
+ public virtual void HudUpdate(HudManager instance)
+ {
+ }
///
- /// Called when a player is killed
+ /// Called when a player is killed.
///
- /// PlayerControl that was killed
- public virtual void OnDeath(PlayerControl player) { }
+ /// PlayerControl that was killed.
+ public virtual void OnDeath(PlayerControl player)
+ {
+ }
///
- /// Calculate Game End logic
+ /// Calculate Game End logic.
///
- /// Should original game end logic be used
- /// Instance of LogicGameFlowNormal
+ /// Should original game end logic be used.
+ /// Instance of LogicGameFlowNormal.
public virtual void CheckGameEnd(out bool runOriginal, LogicGameFlowNormal instance)
{
runOriginal = true;
}
///
- /// Custom role assignment
+ /// Custom role assignment.
///
- /// Should original role assignment be used
- /// Instance of LogicRoleSelection
+ /// Should original role assignment be used.
+ /// Instance of LogicRoleSelection.
public virtual void AssignRoles(out bool runOriginal, LogicRoleSelectionNormal instance)
{
runOriginal = true;
}
///
- /// Custom murder logic
+ /// Custom murder logic.
///
- /// Should original murder logic be used
- /// Result of custom murder logic
- /// Target player for murder
+ /// Should the original murder logic be used.
+ /// Result of custom murder logic.
+ /// Target player for murder.
public virtual void CanKill(out bool runOriginal, out bool result, PlayerControl target)
{
result = false;
@@ -78,61 +86,69 @@ public abstract class CustomGameMode
}
///
- /// Should Roles Settings be available when this gamemode is selected?
+ /// Should Roles Settings be available when this gamemode is selected.
///
- public virtual bool CanAccessRolesTab() => true;
+ /// True if Role Settings are enabled in this game mode.
+ public virtual bool AreRoleSettingsEnabled() => true;
///
- /// Should Game Settings be available when this gamemode is selected?
+ /// Should Game Settings be available when this gamemode is selected.
///
- public virtual bool CanAccessSettingsTab() => true;
+ /// True if Game Settings are enabled in this mode.
+ public virtual bool AreGameSettingsEnabled() => true;
///
- /// Custom winner selection
+ /// Custom winner selection.
///
- /// List of winners
- public virtual List CalculateWinners() => null;
+ /// List of winners or null.
+ public virtual List? CalculateWinners() => null;
///
- /// Show gamemode in Intro Cutscene
+ /// Show gamemode in Intro Cutscene.
///
- public virtual bool ShowCustomRoleScreen() => false;
+ /// True if the game mode should be shown in the intro cutscene.
+ public virtual bool ShowGameModeIntroCutscene() => false;
///
- /// Can Admin be used in this gamemode
+ /// Can Admin be used in this gamemode.
///
- /// Admin Console
+ /// Admin Console.
+ /// True if Admin console is enabled.
public virtual bool CanUseMapConsole(MapConsole console) => true;
///
- /// Can a body be reported in this gamemode
+ /// Can a body be reported in this gamemode.
///
- /// Target body for reporting
+ /// Target body for reporting.
+ /// True if dead bodies can be reported.
public virtual bool CanReport(DeadBody body) => true;
///
- /// Can system consoles be used in this gamemode
+ /// Can system consoles be used in this gamemode.
///
- /// System Console
+ /// System Console.
+ /// True if system consoles are enabled in this mode.
public virtual bool CanUseSystemConsole(SystemConsole console) => true;
///
- /// Can tasks be interacted with in this gamemode
+ /// Can tasks be interacted with in this gamemode.
///
- /// Task console
- public virtual bool CanUseConsole(Console console) => true;
+ /// Task console.
+ /// True if tasks are enabled in this mode.
+ public virtual bool CanUseTasks(Console console) => true;
///
- /// Should the sabotage map be used when attempting to open sabotage overlay
+ /// Should the sabotage map be used when attempting to open sabotage overlay.
///
- /// MapBehaviour object
+ /// MapBehaviour object.
+ /// True if the sabotage map should be shown.
public virtual bool ShouldShowSabotageMap(MapBehaviour map) => true;
///
- /// Can a player vent in this gamemode
+ /// Can a player vent in this gamemode.
///
- /// Target vent
- /// Player attempting to vent
+ /// Target vent.
+ /// Player attempting to vent.
+ /// True if venting is enabled in this mode.
public virtual bool CanVent(Vent vent, NetworkedPlayerInfo playerInfo) => true;
-
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/GameModes/CustomGameModeManager.cs b/MiraAPI/GameModes/CustomGameModeManager.cs
index e80b05c..028eb94 100644
--- a/MiraAPI/GameModes/CustomGameModeManager.cs
+++ b/MiraAPI/GameModes/CustomGameModeManager.cs
@@ -1,5 +1,4 @@
-#nullable enable
-using Reactor.Utilities;
+using Reactor.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
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/ModdedEnumOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs
index cc861dd..2944520 100644
--- a/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs
+++ b/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs
@@ -4,24 +4,33 @@ using System.Reflection;
namespace MiraAPI.GameOptions.Attributes;
-public class ModdedEnumOptionAttribute(string title, Type enumType, string[] values = null, Type roleType = null)
+///
+/// Attribute for creating an enum option.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? values = null, Type? roleType = null)
: ModdedOptionAttribute(title, roleType)
{
- internal override IModdedOption CreateOption(object value, PropertyInfo property)
+ internal override IModdedOption CreateOption(object? value, PropertyInfo property)
{
- var opt = new ModdedEnumOption(Title, (int)value, enumType, values, RoleType);
+ var opt = new ModdedEnumOption(Title, (int)(value ?? 0), enumType, values, RoleType);
return opt;
}
+ ///
public override void SetValue(object value)
{
var opt = HolderOption as ModdedEnumOption;
- opt.SetValue((int)value);
+ opt?.SetValue((int)value);
}
+ ///
public override object GetValue()
{
- var opt = HolderOption as ModdedEnumOption;
- return Enum.ToObject(enumType, opt.Value);
+ if (HolderOption is ModdedEnumOption opt)
+ {
+ return Enum.ToObject(enumType, opt.Value);
+ }
+ throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType ${enumType.FullName} is not a ModdedEnumOption");
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs
index ac451ad..2bd4438 100644
--- a/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs
+++ b/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs
@@ -5,6 +5,10 @@ using System.Reflection;
namespace MiraAPI.GameOptions.Attributes;
+///
+/// A number option attribute for the modded options system.
+///
+[AttributeUsage(AttributeTargets.Property)]
public class ModdedNumberOptionAttribute(
string title,
float min,
@@ -12,24 +16,28 @@ public class ModdedNumberOptionAttribute(
float increment = 1,
MiraNumberSuffixes suffixType = MiraNumberSuffixes.None,
bool zeroInfinity = false,
- Type roleType = null)
+ Type? roleType = null)
: ModdedOptionAttribute(title, roleType)
{
- internal override IModdedOption CreateOption(object value, PropertyInfo property)
+ internal override IModdedOption CreateOption(object? value, PropertyInfo property)
{
- var toggleOpt = new ModdedNumberOption(Title, (float)value, min, max, increment, suffixType, zeroInfinity, RoleType);
- return toggleOpt;
+ return new ModdedNumberOption(Title, (float)(value ?? min+increment), min, max, increment, suffixType, zeroInfinity, RoleType);
}
+ ///
public override void SetValue(object value)
{
- var toggleOpt = HolderOption as ModdedNumberOption;
- toggleOpt.SetValue((float)value);
+ var opt = HolderOption as ModdedNumberOption;
+ opt?.SetValue((float)value);
}
+ ///
public override object GetValue()
{
- var toggleOpt = HolderOption as ModdedNumberOption;
- return toggleOpt.Value;
+ if (HolderOption is ModdedNumberOption opt)
+ {
+ return opt.Value;
+ }
+ throw new InvalidOperationException($"HolderOption for option \"{Title}\" is not a ModdedNumberOption");
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs
index 63bf017..702d75e 100644
--- a/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs
+++ b/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs
@@ -3,18 +3,37 @@ using System.Reflection;
namespace MiraAPI.GameOptions.Attributes;
+///
+/// Represents an attribute that is used to define a modded option.
+///
+/// The option title.
+/// Optional parameter to specify a role Type.
[AttributeUsage(AttributeTargets.Property)]
-public abstract class ModdedOptionAttribute(string title, Type roleType = null) : Attribute
+public abstract class ModdedOptionAttribute(string title, Type? roleType = null) : Attribute
{
- public IModdedOption HolderOption { get; internal set; }
-
- public string Title { get; private set; } = title;
-
- protected Type RoleType { get; private set; } = roleType;
+ internal IModdedOption? HolderOption { get; set; }
- public abstract void SetValue(object val);
-
+ ///
+ /// Gets the title of the option.
+ ///
+ public string Title { get; private set; } = title;
+
+ ///
+ /// Gets the role type of the option.
+ ///
+ protected Type? RoleType { get; private set; } = roleType;
+
+ ///
+ /// Sets the value of the option.
+ ///
+ /// The new value as an object.
+ public abstract void SetValue(object value);
+
+ ///
+ /// Gets the value of the option.
+ ///
+ /// The value of the option as an object.
public abstract object GetValue();
- internal abstract IModdedOption CreateOption(object value, PropertyInfo property);
-}
\ No newline at end of file
+ internal abstract IModdedOption? CreateOption(object? value, PropertyInfo property);
+}
diff --git a/MiraAPI/GameOptions/Attributes/ModdedStringOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedStringOptionAttribute.cs
deleted file mode 100644
index cb8d6b8..0000000
--- a/MiraAPI/GameOptions/Attributes/ModdedStringOptionAttribute.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using MiraAPI.GameOptions.OptionTypes;
-using System;
-using System.Reflection;
-
-namespace MiraAPI.GameOptions.Attributes;
-
-public class ModdedStringOptionAttribute(string title, string[] values, Type roleType = null)
- : ModdedOptionAttribute(title, roleType)
-{
- internal override IModdedOption CreateOption(object value, PropertyInfo property)
- {
- var opt = new ModdedStringOption(Title, (int)value, values, RoleType);
- return opt;
- }
-
- public override void SetValue(object value)
- {
- var opt = HolderOption as ModdedStringOption;
- opt.SetValue((int)value);
- }
-
- public override object GetValue()
- {
- var opt = HolderOption as ModdedStringOption;
- return opt.Value;
- }
-}
\ No newline at end of file
diff --git a/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs
index 2ab75fc..c389292 100644
--- a/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs
+++ b/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs
@@ -4,23 +4,34 @@ using System.Reflection;
namespace MiraAPI.GameOptions.Attributes;
-public class ModdedToggleOptionAttribute(string title, Type roleType = null) : ModdedOptionAttribute(title, roleType)
+///
+/// Attribute for a toggle option.
+///
+/// The option title.
+/// An optional role type.
+[AttributeUsage(AttributeTargets.Property)]
+public class ModdedToggleOptionAttribute(string title, Type? roleType = null) : ModdedOptionAttribute(title, roleType)
{
- internal override IModdedOption CreateOption(object value, PropertyInfo property)
+ internal override IModdedOption CreateOption(object? value, PropertyInfo property)
{
- var toggleOpt = new ModdedToggleOption(Title, (bool)value, RoleType);
+ var toggleOpt = new ModdedToggleOption(Title, (bool)(value ?? false), RoleType);
return toggleOpt;
}
+ ///
public override void SetValue(object value)
{
- var toggleOpt = HolderOption as ModdedToggleOption;
- toggleOpt.SetValue((bool)value);
+ var opt = HolderOption as ModdedToggleOption;
+ opt?.SetValue((bool)value);
}
+ ///
public override object GetValue()
{
- var toggleOpt = HolderOption as ModdedToggleOption;
- return toggleOpt.Value;
+ if (HolderOption is ModdedToggleOption opt)
+ {
+ return opt.Value;
+ }
+ throw new InvalidOperationException($"Holder option for {Title} is not a ModdedToggleOption.");
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs
index c4b88c1..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; protected set; }
- public string Title { get; }
- public StringNames StringName { get; }
- public Func Visible { get; set; }
- public ConfigDefinition ConfigDefinition { get; }
- 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 76410e4..b57528e 100644
--- a/MiraAPI/GameOptions/ModdedOptionsManager.cs
+++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs
@@ -1,17 +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();
@@ -19,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)
@@ -40,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))
@@ -57,11 +61,15 @@ public static class ModdedOptionsManager
Logger.Error($"Failed to get option for {property.Name}");
return;
}
-
- RegisterOption(option, group, pluginInfo);
+
+ 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))
{
@@ -76,13 +84,13 @@ public static class ModdedOptionsManager
}
var option = attribute.CreateOption(property.GetValue(group), property);
-
+
if (option == null)
{
Logger.Error($"Failed to get option for {property.Name}");
return;
}
-
+
var setterOriginal = property.GetSetMethod();
var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch));
PluginSingleton.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch));
@@ -93,25 +101,33 @@ public static class ModdedOptionsManager
OptionAttributes.Add(property, attribute);
attribute.HolderOption = option;
-
- RegisterOption(option, group, pluginInfo);
+
+ RegisterOption(option, group, property.Name, pluginInfo);
}
- private static void RegisterOption(IModdedOption option, AbstractOptionGroup group, MiraPluginInfo pluginInfo)
+ private static void RegisterOption(
+ IModdedOption option,
+ AbstractOptionGroup group,
+ string propertyName,
+ MiraPluginInfo pluginInfo)
{
+ var groupName = group.GetType().FullName;
+
+ option.ConfigDefinition = new ConfigDefinition(groupName, propertyName);
+
option.ParentMod = pluginInfo.MiraPlugin;
option.AdvancedRole = group.AdvancedRole;
-
+
pluginInfo.Options.Add(option);
-
+
ModdedOptions.Add(option.Id, option);
group.Options.Add(option);
}
-
- internal static void SyncAllOptions(int targetId=-1)
+
+ internal static void SyncAllOptions(int targetId = -1)
{
var chunks = ModdedOptions.Values.Select(option => option.GetNetData()).ChunkNetData(1000);
-
+
while (chunks.Count > 0)
{
Rpc.Instance.SendTo(PlayerControl.LocalPlayer, targetId, chunks.Dequeue());
@@ -123,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/OptionGroupSingleton.cs b/MiraAPI/GameOptions/OptionGroupSingleton.cs
index d7678c6..03a11fa 100644
--- a/MiraAPI/GameOptions/OptionGroupSingleton.cs
+++ b/MiraAPI/GameOptions/OptionGroupSingleton.cs
@@ -3,21 +3,17 @@ using System.Linq;
namespace MiraAPI.GameOptions;
+///
+/// Singleton for option groups.
+///
+/// The option group type.
public class OptionGroupSingleton where T : AbstractOptionGroup
{
- private static T _instance;
+ private static T? _instance;
- public static T Instance
- {
- get => _instance ??= ModdedOptionsManager.Groups.OfType().Single();
- set
- {
- if (_instance != null)
- {
- throw new InvalidOperationException($"Instance for {typeof(T)} is already set");
- }
-
- _instance = value;
- }
- }
-}
\ No newline at end of file
+ ///
+ /// Gets the instance of the option group.
+ ///
+ /// Can't
+ public static T Instance => _instance ??= ModdedOptionsManager.Groups.OfType().Single();
+}
diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
index 0be085f..c3f0f5e 100644
--- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
+++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
@@ -2,18 +2,33 @@
using Reactor.Localization.Utilities;
using System;
using System.Linq;
+using Il2CppInterop.Runtime.InteropTypes.Arrays;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.GameOptions.OptionTypes;
+///
+/// An option for selecting an enum value.
+///
public class ModdedEnumOption : ModdedOption
{
- public string[] Values { get; }
+ ///
+ /// Gets the string values of the enum.
+ ///
+ public string[]? Values { get; }
- public ModdedEnumOption(string title, int defaultValue, Type enumType, string[] values = null, Type roleType = null) : base(title, defaultValue, roleType)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The title of the option.
+ /// The default value as an int.
+ /// The Enum type.
+ /// An option list of string values to use in place of the enum name.
+ /// An optional role type to specify for a specific role.
+ public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null, Type? roleType = null) : base(title, defaultValue, roleType)
{
- Values = values is null ? Enum.GetNames(enumType) : values;
+ Values = values ?? Enum.GetNames(enumType);
Data = ScriptableObject.CreateInstance();
var data = (StringGameSetting)Data;
@@ -26,6 +41,7 @@ public class ModdedEnumOption : ModdedOption
data.Index = Value;
}
+ ///
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
{
var stringOption = Object.Instantiate(stringOpt, container);
@@ -35,7 +51,7 @@ public class ModdedEnumOption : ModdedOption
// SetUpFromData method doesnt work correctly so we must set the values manually
stringOption.Title = StringName;
- stringOption.Values = ((StringGameSetting)Data).Values;
+ stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray(0);
stringOption.Value = Value;
OptionBehaviour = stringOption;
@@ -43,43 +59,42 @@ public class ModdedEnumOption : ModdedOption
return stringOption;
}
+ ///
public override float GetFloatData()
{
return Value;
}
+ ///
public override NetData GetNetData()
{
return new NetData(Id, BitConverter.GetBytes(Value));
}
+ ///
public override void HandleNetData(byte[] data)
{
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);
+ DestroyableSingleton.Instance.Notifier.AddSettingsChangeMessage(StringName, Data?.GetValueString(newValue), false);
if (!OptionBehaviour)
{
return;
}
- var opt = OptionBehaviour as StringOption;
- if (opt)
+ if (OptionBehaviour is StringOption opt)
{
opt.Value = newValue;
}
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs
index ac04d1b..65aa846 100644
--- a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs
+++ b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs
@@ -7,19 +7,56 @@ using Object = UnityEngine.Object;
namespace MiraAPI.GameOptions.OptionTypes;
+///
+/// Represents a modded number option.
+///
public class ModdedNumberOption : ModdedOption
{
+ ///
+ /// Gets the minimum value of the option.
+ ///
public float Min { get; }
+ ///
+ /// Gets the maximum value of the option.
+ ///
public float Max { get; }
+ ///
+ /// Gets the increment value of the option.
+ ///
public float Increment { get; }
+ ///
+ /// Gets the suffix type of the option.
+ ///
public MiraNumberSuffixes SuffixType { get; }
+ ///
+ /// Gets a value indicating whether zero is infinity.
+ ///
public bool ZeroInfinity { get; }
- public ModdedNumberOption(string title, float defaultValue, float min, float max, float increment, MiraNumberSuffixes suffixType, bool zeroInfinity = false, Type roleType = null) : base(title, defaultValue, roleType)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The title of the option.
+ /// The default value as a float.
+ /// The minimum value.
+ /// The maximum value.
+ /// The increment.
+ /// The suffix type.
+ /// Whether zero is infinity or not.
+ /// An optional role type.
+ public ModdedNumberOption(
+ string title,
+ float defaultValue,
+ float min,
+ float max,
+ float increment,
+ MiraNumberSuffixes suffixType,
+ bool zeroInfinity = false,
+ Type? roleType = null) : base(title, defaultValue, roleType)
{
Min = min;
Max = max;
@@ -37,13 +74,18 @@ public class ModdedNumberOption : ModdedOption
data.Value = Value;
data.Increment = Increment;
data.ValidRange = new FloatRange(Min, Max);
- data.FormatString = Increment % 1 == 0 && Value % 1 == 0 && Min % 1 == 0 && Max % 1 == 0 ? "0" : "0.0";
+ data.FormatString = Increment % 1 == 0 && Value % 1 == 0 && Min % 1 == 0 && Max % 1 == 0 ? "0" : "0.0"; // not sure if the == checks will work since this is floats but we'll find out
data.ZeroIsInfinity = ZeroInfinity;
data.SuffixType = (NumberSuffixes)SuffixType;
data.OptionName = FloatOptionNames.Invalid;
}
- public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
+ ///
+ public override OptionBehaviour CreateOption(
+ ToggleOption toggleOpt,
+ NumberOption numberOpt,
+ StringOption stringOpt,
+ Transform container)
{
var numberOption = Object.Instantiate(numberOpt, Vector3.zero, Quaternion.identity, container);
@@ -54,56 +96,52 @@ public class ModdedNumberOption : ModdedOption
numberOption.Value = Value;
numberOption.Increment = Increment;
numberOption.ValidRange = new FloatRange(Min, Max);
- numberOption.FormatString = ((FloatGameSetting)Data).FormatString;
+ numberOption.FormatString = (Data as FloatGameSetting)?.FormatString ?? "0.0";
numberOption.ZeroIsInfinity = ZeroInfinity;
numberOption.SuffixType = (NumberSuffixes)SuffixType;
numberOption.floatOptionName = FloatOptionNames.Invalid;
-
OptionBehaviour = numberOption;
return numberOption;
}
+ ///
public override float GetFloatData()
{
return Value;
}
+ ///
public override NetData GetNetData()
{
return new NetData(Id, BitConverter.GetBytes(Value));
}
+ ///
public override void HandleNetData(byte[] data)
{
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);
+ DestroyableSingleton.Instance.Notifier.AddSettingsChangeMessage(
+ StringName,
+ Data?.GetValueString(Value),
+ false);
- if (!OptionBehaviour)
+ if (OptionBehaviour is NumberOption opt)
{
- return;
- }
-
- var opt = OptionBehaviour as NumberOption;
- if (opt)
- {
- opt.Value = newValue;
+ opt.Value = Value;
}
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs
index d93522d..e1c816e 100644
--- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs
+++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs
@@ -9,33 +9,101 @@ using UnityEngine;
namespace MiraAPI.GameOptions.OptionTypes;
+///
+/// Represents a modded option.
+///
+/// The value type.
public abstract class ModdedOption : IModdedOption
{
- private IMiraPlugin _parentMod;
+ private IMiraPlugin? _parentMod;
+ ///
+ /// Gets the unique identifier of the option.
+ ///
public uint Id { get; }
- public BaseGameSetting Data { get; protected init; }
- public IMiraPlugin ParentMod
+
+ ///
+ /// 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; }
+
+ ///
+ /// Gets or sets the parent mod of the option.
+ ///
+ public IMiraPlugin? ParentMod
{
get => _parentMod;
set
{
+ if (_parentMod != null || value == null) return;
_parentMod = value;
var entry = _parentMod.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
Value = entry.Value;
}
}
- public T Value { get; protected set; }
- public T DefaultValue { get; }
- public Action ChangedEvent { get; set; }
- public string Title { get; }
- public StringNames StringName { get; }
- public Func Visible { get; set; }
- public Type AdvancedRole { get; set; }
- public OptionBehaviour OptionBehaviour { get; set; }
- public ConfigDefinition ConfigDefinition => new("Options", Title);
- protected ModdedOption(string title, T defaultValue, Type roleType)
+ ///
+ /// Gets or sets the value of the option.
+ ///
+ public T Value { get; protected set; }
+
+ ///
+ /// Gets the default value of the option.
+ ///
+ public T DefaultValue { get; }
+
+ ///
+ /// Gets or sets the event that is invoked when the value of the option changes.
+ ///
+ public Action? ChangedEvent { get; set; }
+
+ ///
+ /// Gets or sets the visibility of the option.
+ ///
+ public Func Visible { get; set; }
+
+ ///
+ /// Gets or sets the advanced role of the option.
+ ///
+ public Type? AdvancedRole { 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.
+ ///
+ public ConfigDefinition? ConfigDefinition
+ {
+ get => _configDefinition;
+ set
+ {
+ if (_configDefinition is not null) return;
+ _configDefinition = value;
+ }
+ }
+
+ 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;
Title = title;
@@ -50,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)
@@ -70,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
deleted file mode 100644
index 6b878b9..0000000
--- a/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using Reactor.Localization.Utilities;
-using System;
-using System.Linq;
-using MiraAPI.Networking;
-using UnityEngine;
-using Object = UnityEngine.Object;
-
-namespace MiraAPI.GameOptions.OptionTypes;
-
-public class ModdedStringOption : ModdedOption
-{
- public string[] Values { get; }
-
- public ModdedStringOption(string title, int defaultValue, string[] values, Type roleType=null) : base(title, defaultValue, roleType)
- {
- Values = values;
- Data = ScriptableObject.CreateInstance();
- var data = (StringGameSetting)Data;
-
- data.Title = StringName;
- data.Type = global::OptionTypes.String;
- data.Values = Values.Select(CustomStringName.CreateAndRegister).ToArray();
- data.Index = Value;
- }
-
- public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
- {
- var stringOption = Object.Instantiate(stringOpt, container);
-
- stringOption.SetUpFromData(Data, 20);
- stringOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged;
-
- // SetUpFromData method doesnt work correctly so we must set the values manually
- stringOption.Title = StringName;
- stringOption.Values = ((StringGameSetting)Data).Values;
- stringOption.Value = Value;
-
- OptionBehaviour = stringOption;
-
- return stringOption;
- }
-
- public override float GetFloatData()
- {
- return Value;
- }
-
- public override NetData GetNetData()
- {
- return new NetData(Id, BitConverter.GetBytes(Value));
- }
-
- public override void HandleNetData(byte[] data)
- {
- 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)
- {
- DestroyableSingleton.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false);
- if (!OptionBehaviour)
- {
- return;
- }
-
- var opt = OptionBehaviour as StringOption;
- if (opt)
- {
- opt.Value = newValue;
- }
- }
-}
\ No newline at end of file
diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs
index f90f83f..ad00c33 100644
--- a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs
+++ b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs
@@ -5,9 +5,18 @@ using Object = UnityEngine.Object;
namespace MiraAPI.GameOptions.OptionTypes;
+///
+/// A modded toggle option.
+///
public class ModdedToggleOption : ModdedOption
{
- public ModdedToggleOption(string title, bool defaultValue, Type roleType = null) : base(title, defaultValue, roleType)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The option title.
+ /// The default value.
+ /// An optional role type.
+ public ModdedToggleOption(string title, bool defaultValue, Type? roleType = null) : base(title, defaultValue, roleType)
{
Data = ScriptableObject.CreateInstance();
@@ -16,6 +25,7 @@ public class ModdedToggleOption : ModdedOption
data.Type = global::OptionTypes.Checkbox;
}
+ ///
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
{
var toggleOption = Object.Instantiate(toggleOpt, Vector3.zero, Quaternion.identity, container);
@@ -32,32 +42,32 @@ public class ModdedToggleOption : ModdedOption
return toggleOption;
}
+ ///
public override float GetFloatData()
{
return Value ? 1 : 0;
}
+ ///
public override NetData GetNetData()
{
return new NetData(Id, BitConverter.GetBytes(Value));
}
+ ///
public override void HandleNetData(byte[] data)
{
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)
@@ -65,10 +75,9 @@ public class ModdedToggleOption : ModdedOption
return;
}
- var toggleOpt = OptionBehaviour as ToggleOption;
- if (toggleOpt)
+ if (OptionBehaviour is ToggleOption toggleOpt)
{
toggleOpt.CheckMark.enabled = newValue;
}
}
-}
\ No newline at end of file
+}
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/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs
index 6cae97d..361e434 100644
--- a/MiraAPI/Hud/CustomActionButton.cs
+++ b/MiraAPI/Hud/CustomActionButton.cs
@@ -1,5 +1,4 @@
-#nullable enable
-using MiraAPI.Utilities.Assets;
+using MiraAPI.Utilities.Assets;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
@@ -7,67 +6,67 @@ using UnityEngine.UI;
namespace MiraAPI.Hud;
///
-/// Class for making custom action buttons. More customizable than the default Action/Ability buttons in the base game
+/// Class for making custom action buttons. More customizable than the default Action/Ability buttons in the base game.
///
public abstract class CustomActionButton
{
///
- /// The name and text of the button.
+ /// Gets the name and text of the button.
///
public abstract string Name { get; }
///
- /// How long the button takes to cooldown.
+ /// Gets the button's cooldown duration in seconds.
///
public abstract float Cooldown { get; }
///
- /// If the button has an effect ability, how long the effect should last. To disable effects, set to 0.
+ /// Gets the button's effect duration in seconds. If the button has no effect, set to 0.
///
public abstract float EffectDuration { get; }
///
- /// If the button has limited uses, how many uses it has. To make the button infinite, set to 0.
+ /// Gets the maximum amount of uses the button has. If the button has infinite uses, set to 0.
///
public abstract int MaxUses { get; }
///
- /// The sprite of the button. Use to load a sprite from a resource path. Use to load a sprite from an asset bundle.
+ /// Gets the sprite of the button. Use to load a sprite from a resource path. Use to load a sprite from an asset bundle.
///
public abstract LoadableAsset Sprite { get; }
///
- /// The location of the button on the screen
+ /// Gets the location of the button on the screen.
///
public virtual ButtonLocation Location => ButtonLocation.BottomLeft;
///
- /// Returns true if the button has an effect ability.
+ /// Gets a value indicating whether the button has an effect ability.
///
public bool HasEffect => EffectDuration > 0;
///
- /// Returns true if the button has limited uses.
+ /// Gets a value indicating whether the button has limited uses.
///
public bool LimitedUses => MaxUses > 0;
///
- /// Returns true if the effect is currently active.
+ /// Gets or sets a value indicating whether the effect is currently active, if there is one.
///
public bool EffectActive { get; protected set; }
///
- /// Returns the amount of uses left.
+ /// Gets or sets the amount of uses left.
///
public int UsesLeft { get; protected set; }
///
- /// A timer variable to measure cooldowns and effects.
+ /// Gets or sets the timer variable to measure cooldowns and effects.
///
public float Timer { get; protected set; }
///
- /// The button object in game. This is created by Mira API automatically.
+ /// Gets the button object in game. This is created by Mira API automatically.
///
protected ActionButton? Button { get; private set; }
@@ -84,7 +83,7 @@ public abstract class CustomActionButton
Button = Object.Instantiate(HudManager.Instance.AbilityButton, parent);
Button.name = Name + "Button";
- Button.OverrideText(Name.ToUpper());
+ Button.OverrideText(Name.ToUpperInvariant());
Button.graphic.sprite = Sprite.LoadAsset();
@@ -116,7 +115,7 @@ public abstract class CustomActionButton
///
/// A utility function to override the sprite of the button.
///
- /// The new sprite to override with
+ /// The new sprite to override with.
public void OverrideSprite(Sprite sprite)
{
if (Button != null)
@@ -128,7 +127,7 @@ public abstract class CustomActionButton
///
/// A utility function to override the name of the button.
///
- /// The new name to override with
+ /// The new name to override with.
public void OverrideName(string name)
{
Button?.OverrideText(name);
@@ -137,7 +136,7 @@ public abstract class CustomActionButton
///
/// Set the button's timer.
///
- /// The amount you want to set to.
+ /// The time you want to set to.
public void SetTimer(float time)
{
Timer = Mathf.Clamp(time, -1, float.MaxValue);
@@ -161,7 +160,6 @@ public abstract class CustomActionButton
SetTimer(Timer - amount);
}
-
///
/// Set the amount of uses this button has left.
///
@@ -174,7 +172,7 @@ public abstract class CustomActionButton
///
/// Increase the amount of uses this button has left.
///
- /// The amount you want to increase by. Default: 1
+ /// The amount you want to increase by. Default: 1.
public void IncreaseUses(int amount = 1)
{
SetUses(UsesLeft + amount);
@@ -183,7 +181,7 @@ public abstract class CustomActionButton
///
/// Decrease the amount of uses this button has left.
///
- /// The amount you want to decrease by. Default: 1
+ /// The amount you want to decrease by. Default: 1.
public void DecreaseUses(int amount = 1)
{
SetUses(UsesLeft - amount);
@@ -192,8 +190,10 @@ public abstract class CustomActionButton
///
/// A utility function that runs with the local PlayerControl's FixedUpdate if the button is enabled.
///
- /// the local PlayerControl
- protected virtual void FixedUpdate(PlayerControl playerControl) { }
+ /// the local PlayerControl.
+ protected virtual void FixedUpdate(PlayerControl playerControl)
+ {
+ }
///
/// Callback method for the button click event.
@@ -205,13 +205,16 @@ public abstract class CustomActionButton
/// True means the button is active, false means the button is disabled.
///
/// The role of the local player.
+ /// True if the button is enabled, false otherwise.
public abstract bool Enabled(RoleBehaviour? role);
///
/// Given that there is an effect, this method runs when the effect ends.
///
THIS IS A CALLBACK METHOD! Use if you want to end the effect.
///
- public virtual void OnEffectEnd() { }
+ public virtual void OnEffectEnd()
+ {
+ }
///
/// When the button is enabled, this method is called to determine if the button can be used.
@@ -229,7 +232,7 @@ public abstract class CustomActionButton
/// The default behavior is to show the button if the parameter and the method return true.
/// It can be overridden for custom behavior.
///
- /// Passed in from HudManager.SetHudActive, should hud be active?
+ /// Passed in from HudManager.SetHudActive, should hud be active.
/// Passed in from HudManager.SetHudActive, the current role of the player.
public virtual void SetActive(bool visible, RoleBehaviour role)
{
@@ -272,7 +275,7 @@ public abstract class CustomActionButton
/// By default, it handles the cooldown and effect timers, and sets the button to enabled or disabled.
/// It can be overridden for custom behavior.
///
- /// The local PlayerControl
+ /// The local PlayerControl.
public virtual void FixedUpdateHandler(PlayerControl playerControl)
{
if (Timer >= 0)
@@ -308,23 +311,20 @@ public abstract class CustomActionButton
public abstract class CustomActionButton : CustomActionButton where T : MonoBehaviour
{
///
- /// The target object of the button.
+ /// Gets or sets the target object of the button.
///
- public T? Target { get; private set; }
+ public T? Target { get; protected set; }
///
- /// The distance the player must be from the target object to use the button.
+ /// Gets the distance the player must be from the target object to use the button.
///
public virtual float Distance => PlayerControl.LocalPlayer.Data.Role.GetAbilityDistance();
- ///
- /// An optional collider tag to filter the target object by.
- ///
- public virtual string? ColliderTag => null;
-
///
/// Determines if the target object is valid.
///
+ /// The target object being checked.
+ /// True if the target object is valid, false otherwise.
public virtual bool IsTargetValid(T? target)
{
return target;
@@ -333,15 +333,16 @@ public abstract class CustomActionButton : CustomActionButton where T : MonoB
///
/// The method used to get the target object.
///
+ /// The target object or null if it isn't found.
public abstract T? GetTarget();
///
/// Sets the outline of the target object.
///
- /// Should the outline be active
+ /// Should the outline be active.
public abstract void SetOutline(bool active);
-
+ ///
public override bool CanUse()
{
var newTarget = GetTarget();
@@ -355,4 +356,4 @@ public abstract class CustomActionButton : CustomActionButton where T : MonoB
return base.CanUse() && Target;
}
-}
\ No newline at end of file
+}
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 9c609e3..3e732be 100644
--- a/MiraAPI/Hud/CustomButtonSingleton.cs
+++ b/MiraAPI/Hud/CustomButtonSingleton.cs
@@ -1,6 +1,4 @@
-#nullable enable
-using System;
-using System.Linq;
+using System.Linq;
namespace MiraAPI.Hud;
@@ -8,26 +6,12 @@ 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;
- public static T Instance
- {
- get => _instance ??= CustomButtonManager.CustomButtons.OfType().Single();
- set
- {
- if (_instance == value)
- {
- return;
- }
-
- if (_instance != null)
- {
- throw new InvalidOperationException($"Instance for {typeof(T)} is already set");
- }
-
- _instance = value;
- }
- }
-}
\ No newline at end of file
+ ///
+ /// Gets the instance of the button.
+ ///
+ public static T Instance => _instance ??= CustomButtonManager.CustomButtons.OfType().Single();
+}
diff --git a/MiraAPI/MiraApiPlugin.cs b/MiraAPI/MiraApiPlugin.cs
index 918b53b..b928565 100644
--- a/MiraAPI/MiraApiPlugin.cs
+++ b/MiraAPI/MiraApiPlugin.cs
@@ -2,7 +2,6 @@
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using MiraAPI.PluginLoading;
-using MiraAPI.Utilities;
using Reactor;
using Reactor.Networking;
using Reactor.Networking.Attributes;
diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs
index 2e2f107..af9b491 100644
--- a/MiraAPI/Modifiers/BaseModifier.cs
+++ b/MiraAPI/Modifiers/BaseModifier.cs
@@ -1,14 +1,74 @@
namespace MiraAPI.Modifiers;
+
+///
+/// Base class for all modifiers.
+///
public abstract class BaseModifier
{
- public abstract string ModifierName { get; }
- public virtual bool HideOnUi => false;
- public PlayerControl Player { get; internal set; }
+ ///
+ /// Gets the player that the modifier is attached to.
+ ///
+ public PlayerControl? Player { get; internal set; }
+
+ ///
+ /// Gets the modifier id.
+ ///
public uint ModifierId { get; internal set; }
- public virtual void OnActivate() { }
- public virtual void OnDeactivate() { }
- public virtual void Update() { }
- public virtual void FixedUpdate() { }
- public virtual void OnDeath() { }
- public virtual bool CanVent() => Player.Data.Role.CanVent;
-}
\ No newline at end of file
+
+ ///
+ /// Gets the modifier name.
+ ///
+ public abstract string ModifierName { get; }
+
+ ///
+ /// Gets a value indicating whether the modifier is hidden on the UI.
+ ///
+ public virtual bool HideOnUi => false;
+
+ ///
+ /// Gets the HUD information for this modifier. Defaults to the modifier name. Does nothing if is true.
+ ///
+ /// The information string for the HUD.
+ public virtual string GetHudString() => ModifierName;
+
+ ///
+ /// Called when the modifier is activated.
+ ///
+ public virtual void OnActivate()
+ {
+ }
+
+ ///
+ /// Called when the modifier is deactivated.
+ ///
+ public virtual void OnDeactivate()
+ {
+ }
+
+ ///
+ /// Called when the modifier is updated. Attached to the ModifierComponent's Update method.
+ ///
+ public virtual void Update()
+ {
+ }
+
+ ///
+ /// Called when the modifier is updated. Attached to the ModifierComponent's FixedUpdate method.
+ ///
+ public virtual void FixedUpdate()
+ {
+ }
+
+ ///
+ /// Called when the player dies.
+ ///
+ public virtual void OnDeath()
+ {
+ }
+
+ ///
+ /// Determines whether the player can vent.
+ ///
+ /// True if the player can vent, false otherwise.
+ public virtual bool CanVent() => Player?.Data.Role.CanVent == true;
+}
diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs
index ba6f14a..eecda1a 100644
--- a/MiraAPI/Modifiers/ModifierComponent.cs
+++ b/MiraAPI/Modifiers/ModifierComponent.cs
@@ -1,31 +1,58 @@
-using MiraAPI.Modifiers.Types;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using Il2CppInterop.Runtime.Attributes;
+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.
+ ///
+ [HideFromIl2Cpp]
+ public ImmutableList ActiveModifiers => Modifiers.ToImmutableList();
- public PlayerControl player;
+ [HideFromIl2Cpp]
+ private List Modifiers { get; set; } = [];
- private TextMeshPro _modifierText;
+ private PlayerControl? _player;
- public void Start()
+ private TextMeshPro? _modifierText;
+
+ private readonly List _toRemove = [];
+
+ private readonly List _toAdd = [];
+
+ internal void ClearModifiers()
{
- player = GetComponent();
- ActiveModifiers = [];
+ foreach (var modifier in Modifiers)
+ {
+ modifier.OnDeactivate();
+ }
- if (!player.AmOwner)
+ _toRemove.AddRange(Modifiers);
+ }
+
+ private void Start()
+ {
+ _player = GetComponent();
+ Modifiers = [];
+
+ if (!_player.AmOwner)
{
return;
}
@@ -34,55 +61,80 @@ 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 _toRemove)
{
- if (modifier is null) continue;
+ modifier.OnDeactivate();
+ Modifiers.Remove(modifier);
+ }
+ foreach (var modifier in _toAdd)
+ {
+ Modifiers.Add(modifier);
+ modifier.OnActivate();
+
+ if (_player?.AmOwner == true && modifier is TimedModifier { AutoStart: true } timer)
+ {
+ timer.StartTimer();
+ }
+ }
+
+ if (_toAdd.Count > 0 || _toRemove.Count > 0)
+ {
+ if (_player?.AmOwner == true)
+ {
+ HudManager.Instance?.SetHudActive(true);
+ }
+ _toAdd.Clear();
+ _toRemove.Clear();
+ }
+
+ foreach (var modifier in Modifiers)
+ {
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 == false)
+ {
+ 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}");
- if (mod is TimedModifier timer)
- {
- stringBuild.Append($" ({Math.Round(timer.Duration - timer.TimeRemaining, 0)}s/{timer.Duration}s)");
- }
+ stringBuild.Append(CultureInfo.InvariantCulture, $"\n{mod.GetHudString()}");
}
- _modifierText.text = $"Modifiers:{stringBuild}";
+ _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.
+ [HideFromIl2Cpp]
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 +145,24 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
RemoveModifier(modifier);
}
+ ///
+ /// Removes a modifier from the player.
+ ///
+ /// The modifier type.
+ [HideFromIl2Cpp]
public void RemoveModifier() where T : BaseModifier
{
RemoveModifier(typeof(T));
}
+ ///
+ /// Removes a modifier from the player.
+ ///
+ /// The modifier ID.
+ [HideFromIl2Cpp]
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,68 +173,72 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
RemoveModifier(modifier);
}
+ ///
+ /// Removes a modifier from the player.
+ ///
+ /// The modifier object.
+ [HideFromIl2Cpp]
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);
-
- if (player.AmOwner)
- {
- HudManager.Instance.SetHudActive(true);
- }
+ _toRemove.Add(modifier);
}
- public BaseModifier AddModifier(BaseModifier modifier)
+ ///
+ /// Adds a modifier to the player.
+ ///
+ /// The modifier to add.
+ /// The modifier that was added.
+ [HideFromIl2Cpp]
+ 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()];
- modifier.OnActivate();
+ var modifierId = ModifierManager.GetModifierId(modifier.GetType());
- if (!player.AmOwner)
+ if (modifierId == null)
{
- return modifier;
+ Logger.Error($"Cannot add modifier {modifier.GetType().Name} because it has no ID!");
+ return null;
}
- if (modifier is TimedModifier { AutoStart: true } timer)
- {
- timer.StartTimer();
- }
-
- HudManager.Instance.SetHudActive(true);
-
+ _toAdd.Add(modifier);
+ modifier.Player = _player;
+ modifier.ModifierId = modifierId.Value;
return modifier;
}
- public BaseModifier AddModifier(Type type)
+ ///
+ /// Adds a modifier to the player.
+ ///
+ /// The modifier type.
+ /// The modifier that was added.
+ [HideFromIl2Cpp]
+ 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 +249,14 @@ 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.
+ [HideFromIl2Cpp]
+ 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 ad0d71d..773da63 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.Any(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]);
}
}
}
@@ -79,28 +100,26 @@ public static class ModifierManager
{
shuffledModifiers = shuffledModifiers.GetRange(0, plrs.Count);
}
-
+
while (shuffledModifiers.Count > 0)
{
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.Any(x=>IsGameModifierValid(x, mod, id)))
+
+ if (!plrs.Exists(x => IsGameModifierValid(x, mod, id)))
{
shuffledModifiers.RemoveAt(0);
continue;
}
-
+
var plr = plrs.Random();
- if (!IsGameModifierValid(plr, mod, id))
+ if (plr == null || !IsGameModifierValid(plr, mod, id))
{
continue;
}
@@ -116,17 +135,17 @@ public static class ModifierManager
modifier.IsModifierValidOn(player.Data.Role) &&
!player.HasModifier(modifierId);
}
-
+
internal static void SyncAllModifiers(int targetId = -1)
{
var data = new List();
- foreach (var player in GameData.Instance.AllPlayers)
+ foreach (var player in PlayerControl.AllPlayerControls)
{
- data.Add(GetPlayerModifiers(player.Object));
+ data.Add(GetPlayerModifiers(player));
}
- Rpc.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
+ Rpc.Instance.SendTo(PlayerControl.LocalPlayer, targetId, [.. data]);
}
internal static void HandleSyncModifiers(NetData[] data)
@@ -144,39 +163,40 @@ 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);
}
}
}
- private static NetData GetPlayerModifiers(PlayerControl player)
+ private static NetData GetPlayerModifiers(PlayerControl? player)
{
- var bytes = new List();
+ if (player == null || !player)
+ {
+ return new NetData(0, []);
+ }
+
+ List bytes = [];
var modifierComponent = player.GetComponent();
- if (!modifierComponent)
+ if (modifierComponent == null || !modifierComponent)
{
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/Modifiers/Types/GameModifier.cs b/MiraAPI/Modifiers/Types/GameModifier.cs
index cda0844..9ec6f28 100644
--- a/MiraAPI/Modifiers/Types/GameModifier.cs
+++ b/MiraAPI/Modifiers/Types/GameModifier.cs
@@ -1,7 +1,26 @@
namespace MiraAPI.Modifiers.Types;
+
+///
+/// The base class for a game modifier. Game modifiers are applied at the start of the game on top of the player's role.
+///
public abstract class GameModifier : BaseModifier
{
+ ///
+ /// Gets the chance of the modifier being assigned to a player.
+ ///
+ /// An int value between 0 and 100 representing percent.
public abstract int GetAssignmentChance();
+
+ ///
+ /// Gets the amount of players that can have this modifier in a game.
+ ///
+ /// An int value greater than or equal to zero.
public abstract int GetAmountPerGame();
+
+ ///
+ /// Determines whether the modifier is valid on a role.
+ ///
+ /// The role to be checked.
+ /// True if the modifier is valid on the role, otherwise false.
public virtual bool IsModifierValidOn(RoleBehaviour role) => true;
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/Modifiers/Types/TimedModifier.cs b/MiraAPI/Modifiers/Types/TimedModifier.cs
index 44e73f7..f18b136 100644
--- a/MiraAPI/Modifiers/Types/TimedModifier.cs
+++ b/MiraAPI/Modifiers/Types/TimedModifier.cs
@@ -1,22 +1,60 @@
-using MiraAPI.Utilities;
+using System;
+using MiraAPI.Utilities;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.Modifiers.Types;
+
+///
+/// The base class for all timed modifiers. Timed modifiers have a duration and can be started and stopped.
+///
public abstract class TimedModifier : BaseModifier
{
+ ///
+ /// Gets the duration of the modifier.
+ ///
public abstract float Duration { get; }
+
+ ///
+ /// Gets a value indicating whether the timer should start automatically when added.
+ ///
public virtual bool AutoStart => false;
+
+ ///
+ /// Gets a value indicating whether the modifier should be removed when the timer completes.
+ ///
public virtual bool RemoveOnComplete => true;
+
+ ///
+ /// Called when the timer completes.
+ ///
public abstract void OnTimerComplete();
+ ///
+ /// Gets or sets a value indicating whether the timer is active.
+ ///
public bool TimerActive { get; protected set; }
+ ///
+ /// Gets or sets the time remaining on the timer.
+ ///
public float TimeRemaining { get; protected set; }
+ ///
+ /// Gets the HUD information for Timed Modifier, including the time remaining.
+ ///
+ /// A string with the hud information + timer.
+ public override string GetHudString()
+ {
+ return base.GetHudString() + $" ({Math.Round(Duration - TimeRemaining, 0)}s/{Duration}s)";
+ }
+
+ ///
+ /// The FixedUpdate method for timed modifiers. Automatically handles the timer logic.
+ ///
public override void FixedUpdate()
{
- if (!Player.AmOwner)
+ if (Player?.AmOwner == false)
{
return;
}
@@ -33,11 +71,14 @@ public abstract class TimedModifier : BaseModifier
if (RemoveOnComplete)
{
- Player.RpcRemoveModifier(ModifierId);
+ Player?.RpcRemoveModifier(ModifierId);
}
}
}
+ ///
+ /// Starts the timer.
+ ///
public void StartTimer()
{
if (TimerActive)
@@ -49,4 +90,4 @@ public abstract class TimedModifier : BaseModifier
TimerActive = true;
TimeRemaining = Duration;
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/Networking/CustomMurderRpcs.cs b/MiraAPI/Networking/CustomMurderRpcs.cs
index 9f216ab..17fb4ae 100644
--- a/MiraAPI/Networking/CustomMurderRpcs.cs
+++ b/MiraAPI/Networking/CustomMurderRpcs.cs
@@ -14,8 +14,8 @@ public static class CustomMurderRpcs
public static void RpcCustomMurder(this PlayerControl source, PlayerControl target, bool didSucceed = true,
bool resetKillTimer = true, bool createDeadBody = true, bool teleportMurderer = true, bool showKillAnim = true, bool playKillSound = true)
{
- MurderResultFlags murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError;
- MurderResultFlags murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags;
+ var murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError;
+ var murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags;
source.CustomMurder(target, murderResultFlags2, resetKillTimer, createDeadBody, teleportMurderer, showKillAnim, playKillSound);
}
@@ -25,7 +25,7 @@ public static class CustomMurderRpcs
{
source.isKilling = false;
source.logger.Debug(string.Format("{0} trying to murder {1}", source.PlayerId, target.PlayerId), null);
- NetworkedPlayerInfo data = target.Data;
+ var data = target.Data;
if (resultFlags.HasFlag(MurderResultFlags.FailedError))
{
return;
@@ -33,7 +33,7 @@ public static class CustomMurderRpcs
if (resultFlags.HasFlag(MurderResultFlags.FailedProtected) || (resultFlags.HasFlag(MurderResultFlags.DecisionByHost) && target.protectedByGuardianId > -1))
{
target.protectedByGuardianThisRound = true;
- bool flag = PlayerControl.LocalPlayer.Data.Role.Role == RoleTypes.GuardianAngel;
+ var flag = PlayerControl.LocalPlayer.Data.Role.Role == RoleTypes.GuardianAngel;
if (flag && PlayerControl.LocalPlayer.Data.PlayerId == target.protectedByGuardianId)
{
StatsManager.Instance.IncrementStat(StringNames.StatsGuardianAngelCrewmatesProtected);
@@ -116,9 +116,9 @@ public static class CustomMurderRpcs
public static IEnumerator CoPerformCustomKill(this KillAnimation anim, PlayerControl source, PlayerControl target,
bool createDeadBody = true, bool teleportMurderer = true)
{
- FollowerCamera cam = Camera.main.GetComponent();
- bool isParticipant = PlayerControl.LocalPlayer == source || PlayerControl.LocalPlayer == target;
- PlayerPhysics sourcePhys = source.MyPhysics;
+ var cam = Camera.main.GetComponent();
+ var isParticipant = PlayerControl.LocalPlayer == source || PlayerControl.LocalPlayer == target;
+ var sourcePhys = source.MyPhysics;
if (teleportMurderer)
{
@@ -143,7 +143,7 @@ public static class CustomMurderRpcs
deadBody.bodyRenderers.ToArray().ToList().ForEach(target.SetPlayerMaterialColors);
target.SetPlayerMaterialColors(deadBody.bloodSplatter);
- Vector3 vector = target.transform.position + anim.BodyOffset;
+ var vector = target.transform.position + anim.BodyOffset;
vector.z = vector.y / 1000f;
deadBody.transform.position = vector;
}
diff --git a/MiraAPI/Patches/HudManagerPatches.cs b/MiraAPI/Patches/HudManagerPatches.cs
index 25e2d48..c3ee9e8 100644
--- a/MiraAPI/Patches/HudManagerPatches.cs
+++ b/MiraAPI/Patches/HudManagerPatches.cs
@@ -1,5 +1,4 @@
-using System;
-using HarmonyLib;
+using HarmonyLib;
using InnerNet;
using MiraAPI.Hud;
using MiraAPI.Roles;
@@ -9,15 +8,22 @@ using Object = UnityEngine.Object;
namespace MiraAPI.Patches;
+///
+/// General patches for the HudManager class.
+///
[HarmonyPatch(typeof(HudManager))]
public static class HudManagerPatches
{
- /// Custom buttons parent
- private static GameObject _bottomLeft;
+ // Custom buttons parent.
+ private static GameObject? _bottomLeft;
- /// Custom role tab
- private static TaskPanelBehaviour _roleTab;
+ // Custom role tab.
+ private static TaskPanelBehaviour? _roleTab;
+ ///
+ /// Update custom role tab and custom role hud elements.
+ ///
+ /// The HudManager instance.
[HarmonyPostfix]
[HarmonyPatch(nameof(HudManager.Update))]
public static void UpdatePostfix(HudManager __instance)
@@ -28,21 +34,25 @@ public static class HudManagerPatches
{
return;
}
-
- //CustomGameModeManager.ActiveMode?.HudUpdate(__instance);
- if (local?.Data?.Role is null)
+ // CustomGameModeManager.ActiveMode?.HudUpdate(__instance);
+
+ switch (local?.Data?.Role)
{
- return;
- }
+ case null:
+ return;
- if (local.Data.Role is ICustomRole customRole)
- {
- customRole.HudUpdate(__instance);
-
- if (customRole.SetTabText() != null)
+ case ICustomRole customRole:
{
- if (!_roleTab)
+ customRole.HudUpdate(__instance);
+
+ if (customRole.RoleHintType != RoleHintType.RoleTab)
+ {
+ _roleTab?.gameObject.Destroy();
+ return;
+ }
+
+ if (_roleTab == null)
{
_roleTab = CustomRoleManager.CreateRoleTab(customRole);
}
@@ -50,15 +60,16 @@ public static class HudManagerPatches
{
CustomRoleManager.UpdateRoleTab(_roleTab, customRole);
}
+
+ break;
}
- else if (customRole.SetTabText() == null && _roleTab)
- {
- _roleTab.gameObject.Destroy();
- }
- }
- else if (_roleTab)
- {
- _roleTab.gameObject.Destroy();
+
+ default:
+ if (_roleTab != null)
+ {
+ _roleTab.gameObject.Destroy();
+ }
+ break;
}
}
@@ -74,16 +85,17 @@ public static class HudManagerPatches
}*/
///
- /// Create custom buttons parent
+ /// Create custom buttons and arrange them on the hud.
///
+ /// The HudManager instance.
[HarmonyPostfix]
[HarmonyPatch(nameof(HudManager.Start))]
public static void StartPostfix(HudManager __instance)
{
var buttons = __instance.transform.Find("Buttons");
var bottomRight = buttons.Find("BottomRight");
-
- if (!_bottomLeft)
+
+ if (_bottomLeft == null)
{
_bottomLeft = Object.Instantiate(bottomRight.gameObject, buttons);
}
@@ -106,14 +118,14 @@ public static class HudManagerPatches
{
ButtonLocation.BottomLeft => _bottomLeft.transform,
ButtonLocation.BottomRight => bottomRight,
- _ => null
+ _ => null,
};
if (location is null)
{
continue;
}
-
+
button.CreateButton(location);
}
@@ -124,30 +136,34 @@ public static class HudManagerPatches
}
///
- /// Make sure all launchpad hud elements are inactive/active when appropriate
+ /// Set the custom role tab and custom buttons active when the hud is active.
///
+ /// HudManager instance.
+ /// The local PlayerControl.
+ /// The player's RoleBehaviour.
+ /// Whether the Hud should be set active or not.
[HarmonyPostfix]
[HarmonyPatch(nameof(HudManager.SetHudActive), typeof(PlayerControl), typeof(RoleBehaviour), typeof(bool))]
- public static void SetHudActivePostfix(HudManager __instance, [HarmonyArgument(0)] PlayerControl player, [HarmonyArgument(1)] RoleBehaviour roleBehaviour, [HarmonyArgument(2)] bool isActive)
+ public static void SetHudActivePostfix(HudManager __instance, PlayerControl localPlayer, RoleBehaviour role, bool isActive)
{
- if (!player.Data)
+ if (!localPlayer.Data)
{
return;
}
if (_roleTab)
{
- _roleTab.gameObject.SetActive(isActive);
+ _roleTab?.gameObject.SetActive(isActive);
}
foreach (var button in CustomButtonManager.CustomButtons)
{
- button.SetActive(isActive, roleBehaviour);
+ button.SetActive(isActive, role);
}
- if (roleBehaviour is ICustomRole role)
+ if (role is ICustomRole customRole)
{
- __instance.ImpostorVentButton.gameObject.SetActive(isActive && role.CanUseVent);
+ __instance.ImpostorVentButton.gameObject.SetActive(isActive && customRole.CanUseVent);
}
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/Patches/IntroCutscenePatches.cs b/MiraAPI/Patches/IntroCutscenePatches.cs
index 9d95896..01066c3 100644
--- a/MiraAPI/Patches/IntroCutscenePatches.cs
+++ b/MiraAPI/Patches/IntroCutscenePatches.cs
@@ -26,7 +26,7 @@ public static class IntroCutscenePatches
{
if (PlayerControl.LocalPlayer.Data.Role is ICustomRole customRole)
{
- if (!customRole.IsNeutral)
+ if (customRole.Team is not ModdedRoleTeams.Neutral)
{
return true;
}
diff --git a/MiraAPI/Patches/MeetingHudPatches.cs b/MiraAPI/Patches/MeetingHudPatches.cs
index e813730..6abe4c0 100644
--- a/MiraAPI/Patches/MeetingHudPatches.cs
+++ b/MiraAPI/Patches/MeetingHudPatches.cs
@@ -3,9 +3,28 @@ using MiraAPI.Hud;
namespace MiraAPI.Patches;
+///
+/// Harmony patches for the MeetingHud class.
+///
[HarmonyPatch(typeof(MeetingHud))]
public static class MeetingHudPatches
{
+ ///
+ /// Resets the cooldown and effect of all custom buttons when the meeting starts.
+ ///
+ [HarmonyPostfix]
+ [HarmonyPatch(nameof(MeetingHud.Start))]
+ public static void StartPostfix()
+ {
+ foreach (var customActionButton in CustomButtonManager.CustomButtons)
+ {
+ customActionButton.ResetCooldownAndOrEffect();
+ }
+ }
+
+ ///
+ /// Resets the cooldown and effect of all custom buttons when the voting is complete.
+ ///
[HarmonyPostfix]
[HarmonyPatch(nameof(MeetingHud.VotingComplete))]
public static void VotingCompletePostfix()
@@ -15,4 +34,4 @@ public static class MeetingHudPatches
customActionButton.ResetCooldownAndOrEffect();
}
}
-}
\ 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 b74581b..13c9036 100644
--- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs
+++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs
@@ -1,5 +1,4 @@
-#nullable enable
-using HarmonyLib;
+using HarmonyLib;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities.Extensions;
@@ -13,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)
@@ -57,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;
@@ -71,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;
@@ -89,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)
{
@@ -122,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 a0b79ee..a74588e 100644
--- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs
+++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs
@@ -1,5 +1,4 @@
-#nullable enable
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
@@ -17,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]
@@ -49,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;
}
@@ -72,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();
@@ -186,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/NotificationPopperPatch.cs b/MiraAPI/Patches/Options/NotificationPopperPatch.cs
index a45bfe6..0a8fc57 100644
--- a/MiraAPI/Patches/Options/NotificationPopperPatch.cs
+++ b/MiraAPI/Patches/Options/NotificationPopperPatch.cs
@@ -16,8 +16,8 @@ public class NotificationPopperPatch
{
if (CustomRoleManager.CustomRoles.Values.Any(role => role.StringName == key))
{
- string item = string.Empty;
- string text = (teamType == RoleTeamTypes.Crewmate) ? Palette.CrewmateSettingChangeText.ToTextColor() : Palette.ImpostorRed.ToTextColor();
+ var item = string.Empty;
+ var text = (teamType == RoleTeamTypes.Crewmate) ? Palette.CrewmateSettingChangeText.ToTextColor() : Palette.ImpostorRed.ToTextColor();
item = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotificationRole, new Object[]
{
string.Concat(
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 2591269..1b228a3 100644
--- a/MiraAPI/Patches/Options/SettingPatches.cs
+++ b/MiraAPI/Patches/Options/SettingPatches.cs
@@ -1,17 +1,32 @@
-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)
{
- string result = string.Empty;
- MiraNumberSuffixes suffix = (MiraNumberSuffixes)__instance.SuffixType;
+ var result = string.Empty;
+ var suffix = (MiraNumberSuffixes)__instance.SuffixType;
if (__instance.ZeroIsInfinity && Mathf.Abs(value) < 0.0001f)
{
@@ -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/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs
index f4f79ce..7d4d97a 100644
--- a/MiraAPI/Patches/PlayerControlPatches.cs
+++ b/MiraAPI/Patches/PlayerControlPatches.cs
@@ -1,14 +1,20 @@
using HarmonyLib;
using MiraAPI.Hud;
-using MiraAPI.Roles;
using MiraAPI.Modifiers;
+using MiraAPI.Roles;
using Reactor.Utilities.Extensions;
namespace MiraAPI.Patches;
+///
+/// General patches for the PlayerControl class.
+///
[HarmonyPatch(typeof(PlayerControl))]
public static class PlayerControlPatches
{
+ ///
+ /// Adds the modifier component to the player on start.
+ ///
[HarmonyPrefix]
[HarmonyPatch(nameof(PlayerControl.Start))]
public static void PlayerControlStartPostfix(PlayerControl __instance)
@@ -21,6 +27,24 @@ public static class PlayerControlPatches
__instance.gameObject.AddComponent();
}
+ ///
+ /// Calls the OnDeath method for all active modifiers.
+ ///
+ [HarmonyPostfix]
+ [HarmonyPatch(nameof(PlayerControl.Die))]
+ public static void PlayerControlDiePostfix(PlayerControl __instance)
+ {
+ var modifiersComponent = __instance.GetComponent();
+
+ if (modifiersComponent)
+ {
+ modifiersComponent.ActiveModifiers.ForEach(x=>x.OnDeath());
+ }
+ }
+
+ ///
+ /// FixedUpdate handler for custom roles and custom buttons.
+ ///
[HarmonyPostfix]
[HarmonyPatch(nameof(PlayerControl.FixedUpdate))]
public static void PlayerControlFixedUpdatePostfix(PlayerControl __instance)
@@ -29,26 +53,25 @@ public static class PlayerControlPatches
{
customRole.PlayerControlFixedUpdate(__instance);
}
-
+
if (!__instance.AmOwner)
{
return;
}
-
+
foreach (var button in CustomButtonManager.CustomButtons)
{
if (__instance.Data?.Role == null)
{
continue;
}
-
+
if (!button.Enabled(__instance.Data?.Role))
{
continue;
}
-
+
button.FixedUpdateHandler(__instance);
}
}
-
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/Patches/Roles/GameStartupPatch.cs b/MiraAPI/Patches/Roles/GameStartupPatch.cs
index 8455208..b452791 100644
--- a/MiraAPI/Patches/Roles/GameStartupPatch.cs
+++ b/MiraAPI/Patches/Roles/GameStartupPatch.cs
@@ -1,5 +1,4 @@
using HarmonyLib;
-using MiraAPI.Colors;
using MiraAPI.Roles;
namespace MiraAPI.Patches.Roles;
@@ -23,6 +22,5 @@ public static class GameStartupPatch
_runOnce = true;
CustomRoleManager.RegisterInRoleManager();
- PaletteManager.RegisterAllColors();
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/Patches/Roles/RoleManagerPatches.cs b/MiraAPI/Patches/Roles/RoleManagerPatches.cs
index f7d0401..350767f 100644
--- a/MiraAPI/Patches/Roles/RoleManagerPatches.cs
+++ b/MiraAPI/Patches/Roles/RoleManagerPatches.cs
@@ -3,7 +3,6 @@ using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.Roles;
using System.Linq;
-using UnityEngine;
namespace MiraAPI.Patches.Roles;
diff --git a/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs b/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs
index dd938b8..c146b8a 100644
--- a/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs
+++ b/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs
@@ -18,7 +18,7 @@ public static class RoleOptionsCollectionPatch
{
return true;
}
-
+
if (customRole.HideSettings)
{
return false;
@@ -27,7 +27,6 @@ public static class RoleOptionsCollectionPatch
customRole.ParentMod.PluginConfig.TryGetEntry(customRole.ChanceConfigDefinition, out var entry);
__result = entry.Value;
return false;
-
}
///
diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
index d36efb7..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;
@@ -34,17 +33,17 @@ public static class TaskAdderPatch
GameObject hitbox = new("Hitbox")
{
- layer = 5
+ layer = 5,
};
hitbox.transform.SetParent(__instance.TaskParent.transform, false);
hitbox.transform.localScale = new Vector3(7.5f, 6.5f, 1);
hitbox.transform.localPosition = new Vector3(2.8f, -2.2f, 0);
-
- SpriteMask mask = hitbox.AddComponent();
+
+ var mask = hitbox.AddComponent();
mask.sprite = MiraAssets.NextButton.LoadAsset();
mask.alphaCutoff = 0.0f;
-
- BoxCollider2D collider = hitbox.AddComponent();
+
+ var collider = hitbox.AddComponent();
collider.size = new Vector2(1f, 1f);
collider.enabled = true;
@@ -54,7 +53,7 @@ public static class TaskAdderPatch
__instance.RoleButton.GetComponent().ClickMask = collider;
__instance.RootFolderPrefab.GetComponent().ClickMask = collider;
__instance.RootFolderPrefab.gameObject.SetActive(false);
-
+
__instance.TaskParent = inner.transform;
_rolesFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
@@ -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;
@@ -85,14 +84,14 @@ public static class TaskAdderPatch
[HarmonyPatch(typeof(TaskAddButton), "Role", MethodType.Setter)]
public static void RoleGetterPatch(TaskAddButton __instance)
{
- if (__instance.role is ICustomRole { IsNeutral: true })
+ if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Neutral })
{
__instance.FileImage.color = Color.gray;
}
__instance.RolloverHandler.OutColor = __instance.FileImage.color;
}
-
+
private static void AddFileAsChildCustom(this TaskAdderGame instance, TaskFolder taskFolder, TaskAddButton item, ref float xCursor, ref float yCursor, ref float maxHeight)
{
item.transform.SetParent(instance.TaskParent);
@@ -115,7 +114,7 @@ public static class TaskAdderPatch
[HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.ShowFolder))]
public static bool ShowPatch(TaskAdderGame __instance, [HarmonyArgument(0)] TaskFolder taskFolder)
{
- var stringBuilder = new StringBuilder(64);
+ var stringBuilder = new Il2CppSystem.Text.StringBuilder(64);
__instance.Hierarchy.Add(taskFolder);
for (var i = 0; i < __instance.Hierarchy.Count; i++)
{
@@ -286,4 +285,4 @@ public static class TaskAdderPatch
ControllerManager.Instance.SetBackButton(__instance.FolderBackButton);
return false;
}
-}
\ No newline at end of file
+}
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..794d74e 100644
--- a/MiraAPI/PluginLoading/MiraPluginManager.cs
+++ b/MiraAPI/PluginLoading/MiraPluginManager.cs
@@ -1,61 +1,58 @@
-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.");
};
+ IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors;
}
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 +65,7 @@ internal class MiraPluginManager
{
continue;
}
-
+
foreach (var property in type.GetProperties())
{
if (property.PropertyType.IsAssignableTo(typeof(IModdedOption)))
@@ -76,7 +73,7 @@ internal class MiraPluginManager
ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo);
continue;
}
-
+
var attribute = property.GetCustomAttribute();
if (attribute == null)
{
@@ -98,22 +95,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 +135,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 +159,6 @@ internal class MiraPluginManager
}
}
-
private static void RegisterButtonAttribute(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
@@ -175,4 +170,4 @@ internal class MiraPluginManager
}
}
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs
index 798c662..ca34213 100644
--- a/MiraAPI/Roles/CustomRoleManager.cs
+++ b/MiraAPI/Roles/CustomRoleManager.cs
@@ -17,11 +17,13 @@ using Object = UnityEngine.Object;
namespace MiraAPI.Roles;
+///
+/// Custom role manager for handling custom roles.
+///
public static class CustomRoleManager
{
- public static readonly Dictionary CustomRoles = new();
-
- public static readonly Dictionary RoleIds = new();
+ internal static readonly Dictionary CustomRoles = [];
+ internal static readonly Dictionary RoleIds = [];
private static ushort _roleId = 100;
@@ -35,7 +37,7 @@ public static class CustomRoleManager
RoleManager.Instance.AllRoles = RoleManager.Instance.AllRoles.Concat(CustomRoles.Values).ToArray();
}
- public static void RegisterRoleTypes(List roles, MiraPluginInfo pluginInfo)
+ internal static void RegisterRoleTypes(List roles, MiraPluginInfo pluginInfo)
{
roles.ForEach(x => RoleIds.Add(x, GetNextRoleId()));
@@ -53,11 +55,11 @@ public static class CustomRoleManager
}
}
- private static RoleBehaviour RegisterRole(Type roleType, MiraPluginInfo parentMod)
+ private static RoleBehaviour? RegisterRole(Type roleType, MiraPluginInfo parentMod)
{
if (!(typeof(RoleBehaviour).IsAssignableFrom(roleType) && typeof(ICustomRole).IsAssignableFrom(roleType)))
{
- Logger.Error($"{roleType?.Name} does not inherit from RoleBehaviour or ICustomRole.");
+ Logger.Error($"{roleType.Name} does not inherit from RoleBehaviour or ICustomRole.");
return null;
}
@@ -91,6 +93,14 @@ public static class CustomRoleManager
RoleManager.GhostRoles.Add(roleBehaviour.Role);
}
+ var useTaskHint = customRole.RoleHintType is RoleHintType.TaskHint;
+ var overridesTaskText = customRole.GetType().GetMethod("SpawnTaskHeader")?.DeclaringType == customRole.GetType();
+
+ if (useTaskHint && !overridesTaskText)
+ {
+ Logger.Error($"Role {customRole.RoleName} is using RoleHintType.TaskHint but does not override SpawnTaskHeader!");
+ }
+
CustomRoles.Add(roleId, roleBehaviour);
if (customRole.HideSettings)
@@ -105,12 +115,23 @@ public static class CustomRoleManager
return roleBehaviour;
}
+ ///
+ /// Finds the parent mod of a custom role.
+ ///
+ /// The ICustomRole object.
+ /// 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)).Value;
+ return MiraPluginManager.Instance.RegisteredPlugins().First(plugin => plugin.CustomRoles.ContainsValue(role as RoleBehaviour ?? throw new InvalidOperationException()));
}
- public static bool GetCustomRoleBehaviour(RoleTypes roleType, out ICustomRole result)
+ ///
+ /// Gets a custom role behaviour by role type.
+ ///
+ /// The role type enum.
+ /// The ICustomRole result.
+ /// True if the role was found.
+ public static bool GetCustomRoleBehaviour(RoleTypes roleType, out ICustomRole? result)
{
CustomRoles.TryGetValue((ushort)roleType, out var temp);
if (temp)
@@ -176,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;
@@ -202,9 +223,11 @@ public static class CustomRoleManager
try
{
- customRole.ParentMod.PluginConfig.TryGetEntry(customRole.NumConfigDefinition,
+ customRole.ParentMod.PluginConfig.TryGetEntry(
+ customRole.NumConfigDefinition,
out var numEntry);
- customRole.ParentMod.PluginConfig.TryGetEntry(customRole.ChanceConfigDefinition,
+ customRole.ParentMod.PluginConfig.TryGetEntry(
+ customRole.ChanceConfigDefinition,
out var chanceEntry);
numEntry.Value = num;
@@ -216,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];
@@ -227,4 +250,4 @@ public static class CustomRoleManager
LobbyInfoPane.Instance.RefreshPane();
}
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs
index f8c3e8d..ffe2327 100644
--- a/MiraAPI/Roles/ICustomRole.cs
+++ b/MiraAPI/Roles/ICustomRole.cs
@@ -1,94 +1,161 @@
-using AmongUs.GameOptions;
+using System.Text;
+using AmongUs.GameOptions;
using BepInEx.Configuration;
-using HarmonyLib;
+using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Modifiers;
-using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
-using System;
-using System.Text;
using UnityEngine;
namespace MiraAPI.Roles;
+///
+/// Interface for custom roles.
+///
public interface ICustomRole
{
+ ///
+ /// Gets the name of the role.
+ ///
string RoleName { get; }
+ ///
+ /// Gets the description of the role. Used in the Intro Cutscene.
+ ///
string RoleDescription { get; }
+ ///
+ /// Gets the long description of the role. Used in the Role Tab and Role Options.
+ ///
string RoleLongDescription { get; }
+ ///
+ /// Gets the color of the role.
+ ///
Color RoleColor { get; }
+ ///
+ /// Gets the team of the role.
+ ///
ModdedRoleTeams Team { get; }
+ ///
+ /// Gets the **maximum amount** of players that can have this role at a time. This is not the same as the **number** of players that can have this role in a game.
+ ///
int MaxPlayers => 15;
+ ///
+ /// Gets the Sprite used for the Role Options menu screenshot.
+ ///
+ [HideFromIl2Cpp]
LoadableAsset OptionsScreenshot => MiraAssets.Empty;
+ ///
+ /// Gets the Sprite used for the Role Icon.
+ ///
+ [HideFromIl2Cpp]
LoadableAsset Icon => MiraAssets.Empty;
- ConfigDefinition NumConfigDefinition => new("Roles", $"Num{RoleName}");
+ ///
+ /// Gets the BepInEx ConfigDefinition for the amount of players that can have this role.
+ ///
+ ConfigDefinition NumConfigDefinition => new("Roles", $"Num {GetType().FullName}");
- ConfigDefinition ChanceConfigDefinition => new("Roles", $"Chance{RoleName}");
+ ///
+ /// Gets the BepInEx ConfigDefinition for the chance of this role being selected.
+ ///
+ ConfigDefinition ChanceConfigDefinition => new("Roles", $"Chance {GetType().FullName}");
+ ///
+ /// Gets a value indicating whether the role is affected by light sabotages.
+ ///
bool AffectedByLight => Team == ModdedRoleTeams.Crewmate;
+ ///
+ /// Gets a value indicating whether the role can be killed by others.
+ ///
bool CanGetKilled => Team == ModdedRoleTeams.Crewmate;
- bool IsNeutral => Team == ModdedRoleTeams.Neutral;
-
+ ///
+ /// Gets a value indicating whether the role can kill others.
+ ///
bool CanKill => Team == ModdedRoleTeams.Impostor;
+ ///
+ /// Gets a value indicating whether the role can use vents.
+ ///
bool CanUseVent => Team == ModdedRoleTeams.Impostor;
+ ///
+ /// Gets a value indicating whether the role's tasks count towards task progress.
+ ///
bool TasksCount => Team == ModdedRoleTeams.Crewmate;
+ ///
+ /// Gets a value indicating whether the role is a Ghost.
+ ///
bool IsGhostRole => false;
- bool CreateCustomTab => true;
-
+ ///
+ /// Gets a value indicating whether the role should show up in the Role Options menu.
+ ///
bool HideSettings => IsGhostRole;
- RoleTypes GhostRole => Team == ModdedRoleTeams.Crewmate ? RoleTypes.CrewmateGhost : RoleTypes.ImpostorGhost;
+ ///
+ /// Gets the role hint style. See enum for all options.
+ ///
+ RoleHintType RoleHintType => RoleHintType.RoleTab;
+ ///
+ /// Gets the Ghost role that is applied when the player is killed.
+ ///
+ RoleTypes GhostRole => Team == ModdedRoleTeams.Impostor ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost;
+
+ ///
+ /// Gets the parent mod of this role.
+ ///
MiraPluginInfo ParentMod => CustomRoleManager.FindParentMod(this);
///
- /// Runs on the PlayerControl FixedUpdate method for any player with this role
+ /// This method runs on the PlayerControl.FixedUpdate method for ALL players with this role.
///
- /// The PlayerControl that has this role
- void PlayerControlFixedUpdate(PlayerControl playerControl) { }
+ /// The PlayerControl that has this role.
+ void PlayerControlFixedUpdate(PlayerControl playerControl)
+ {
+ }
///
- /// Only runs for local player on HudManager Update method
+ /// This method runs on the HudManager.Update method ONLY when the LOCAL player has this role.
///
- /// Reference to HudManager instance
- void HudUpdate(HudManager hudManager) { }
+ /// Reference to HudManager instance.
+ void HudUpdate(HudManager hudManager)
+ {
+ }
- string GetCustomEjectionMessage(NetworkedPlayerInfo player)
+ ///
+ /// Gets a custom ejection message for the role. Return null to use the default message.
+ ///
+ /// The NetworkedPlayerInfo object for this player.
+ /// A string with a custom ejection message or null.
+ string? GetCustomEjectionMessage(NetworkedPlayerInfo player)
{
return Team == ModdedRoleTeams.Impostor ? $"{player.PlayerName} was The {RoleName}" : null;
}
- StringBuilder SetTabText()
- {
- var taskStringBuilder = Helpers.CreateForRole(this);
- return taskStringBuilder;
- }
+ ///
+ /// Get the custom Role Tab text for this role.
+ ///
+ /// A StringBuilder with the role tab text.
+ StringBuilder SetTabText() => Helpers.CreateForRole(this);
+ ///
+ /// Determine whether a given modifier can be applied to this role.
+ ///
+ /// The modifier to be tested.
+ /// True if the modifier is valid on this role, false otherwise.
bool IsModifierApplicable(BaseModifier modifier)
{
return true;
}
-
- NetData GetNetData()
- {
- ParentMod.PluginConfig.TryGetEntry(NumConfigDefinition, out var numEntry);
- ParentMod.PluginConfig.TryGetEntry(ChanceConfigDefinition, out var chanceEntry);
-
- return new NetData(RoleId.Get(GetType()), BitConverter.GetBytes(numEntry.Value).AddRangeToArray(BitConverter.GetBytes(chanceEntry.Value)));
- }
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/Roles/RoleHintType.cs b/MiraAPI/Roles/RoleHintType.cs
new file mode 100644
index 0000000..8994016
--- /dev/null
+++ b/MiraAPI/Roles/RoleHintType.cs
@@ -0,0 +1,24 @@
+// Copyright (c) PlaceholderCompany. All rights reserved.
+
+namespace MiraAPI.Roles;
+
+///
+/// The type of hint style for a role to use.
+///
+public enum RoleHintType
+{
+ ///
+ /// No hint.
+ ///
+ None,
+
+ ///
+ /// Original game style (hint above tasks).
+ ///
+ TaskHint,
+
+ ///
+ /// Use custom role tab.
+ ///
+ RoleTab,
+}
diff --git a/MiraAPI/Utilities/Assets/SpriteTools.cs b/MiraAPI/Utilities/Assets/SpriteTools.cs
index 3ed203c..d086a9e 100644
--- a/MiraAPI/Utilities/Assets/SpriteTools.cs
+++ b/MiraAPI/Utilities/Assets/SpriteTools.cs
@@ -13,10 +13,23 @@ namespace MiraAPI.Utilities.Assets;
///
public static class SpriteTools
{
+ ///
+ /// Delegate for LoadImage.
+ ///
public delegate bool DLoadImage(IntPtr tex, IntPtr data, bool markNonReadable);
- public static DLoadImage ICallLoadImage;
+ ///
+ /// The ICall for LoadImage.
+ ///
+ public static DLoadImage? ICallLoadImage;
+ ///
+ /// Load an image into a texture.
+ ///
+ /// The texture.
+ /// Byte data of image.
+ /// Mark nonreadable.
+ /// True if succeeded.
public static bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
{
ICallLoadImage ??= IL2CPP.ResolveICall("UnityEngine.ImageConversion::LoadImage");
@@ -48,4 +61,4 @@ public static class SpriteTools
return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f));
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index ffe0c23..8374649 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -1,29 +1,63 @@
-#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using HarmonyLib;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
using MiraAPI.Networking;
+using MiraAPI.Roles;
using Reactor.Networking.Attributes;
using Reactor.Utilities;
-using System.Collections.Generic;
-using System.Linq;
using UnityEngine;
namespace MiraAPI.Utilities;
+///
+/// Extension methods for various classes.
+///
public static class Extensions
{
- public static bool IsStatic(this System.Type type)
+ internal static NetData GetNetData(this ICustomRole role)
+ {
+ role.ParentMod.PluginConfig.TryGetEntry(role.NumConfigDefinition, out var numEntry);
+ role.ParentMod.PluginConfig.TryGetEntry(role.ChanceConfigDefinition, out var chanceEntry);
+
+ return new NetData(RoleId.Get(role.GetType()), BitConverter.GetBytes(numEntry.Value).AddRangeToArray(BitConverter.GetBytes(chanceEntry.Value)));
+ }
+
+ ///
+ /// Checks if a type is static.
+ ///
+ /// The type being checked.
+ /// True if the type is static, false otherwise.
+ public static bool IsStatic(this Type type)
{
return type is { IsClass: true, IsAbstract: true, IsSealed: true };
}
+ ///
+ /// Gets a darkened version of a color.
+ ///
+ /// The original color.
+ /// A darkness amount between 0 and 255.
+ /// The darkened color.
public static Color32 GetShadowColor(this Color32 color, byte darknessAmount)
{
return
- new Color32((byte)Mathf.Clamp(color.r - darknessAmount, 0, 255), (byte)Mathf.Clamp(color.g - darknessAmount, 0, 255),
- (byte)Mathf.Clamp(color.b - darknessAmount, 0, 255), byte.MaxValue);
+ new Color32(
+ (byte)Mathf.Clamp(color.r - darknessAmount, 0, 255),
+ (byte)Mathf.Clamp(color.g - darknessAmount, 0, 255),
+ (byte)Mathf.Clamp(color.b - darknessAmount, 0, 255),
+ byte.MaxValue);
}
+ ///
+ /// Truncates a string to a specified length.
+ ///
+ /// The original string.
+ /// The maximum length.
+ /// An option suffix to attach at the end of the truncated string.
+ /// A truncated string of maxLength with the attached suffix.
public static string? Truncate(this string? value, int maxLength, string truncationSuffix = "…")
{
return value?.Length > maxLength
@@ -31,6 +65,12 @@ public static class Extensions
: value;
}
+ ///
+ /// Chunks a collection of NetData into smaller arrays.
+ ///
+ /// A collection of NetData objects.
+ /// The max chunk size in bytes.
+ /// A Queue of NetData arrays.
public static Queue ChunkNetData(this IEnumerable dataCollection, int chunkSize)
{
Queue chunks = [];
@@ -59,20 +99,29 @@ public static class Extensions
if (current.Count > 0)
{
- chunks.Enqueue(current.ToArray());
+ chunks.Enqueue([.. current]);
}
return chunks;
}
-
+ ///
+ /// Determines if a given OptionBehaviour is for a custom option.
+ ///
+ /// The OptionBehaviour to be tested.
+ /// 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);
}
- public static readonly Dictionary ModifierComponents = new();
+ private static readonly Dictionary ModifierComponents = [];
+ ///
+ /// Gets the ModifierComponent for a player.
+ ///
+ /// The PlayerControl object.
+ /// A ModifierComponent if there is one, null otherwise.
public static ModifierComponent? GetModifierComponent(this PlayerControl player)
{
if (ModifierComponents.TryGetValue(player, out var component))
@@ -89,6 +138,12 @@ public static class Extensions
return component;
}
+ ///
+ /// Randomizes a list.
+ ///
+ /// The list object.
+ /// The type of object the list contains.
+ /// A randomized list made from the original list.
public static List Randomize(this List list)
{
List randomizedList = [];
@@ -102,101 +157,184 @@ public static class Extensions
return randomizedList;
}
+ ///
+ /// Gets a modifier by its type, or null if the player doesn't have it.
+ ///
+ /// The PlayerControl object.
+ /// The Type of the Modifier.
+ /// The Modifier if it is found, null otherwise.
public static T? GetModifier(this PlayerControl? player) where T : BaseModifier
{
return player?.GetModifierComponent()?.ActiveModifiers.Find(x => x is T) as T;
}
+ ///
+ /// Checks if a player has a modifier.
+ ///
+ /// The PlayerControl object.
+ /// The Type of the Modifier.
+ /// True if the Modifier is present, false otherwise.
public static bool HasModifier(this PlayerControl? player) where T : BaseModifier
{
return player?.GetModifierComponent() != null && player.GetModifierComponent()!.ActiveModifiers.Exists(x => x is T);
}
+ ///
+ /// Checks if a player has a modifier by its ID.
+ ///
+ /// The PlayerControl object.
+ /// The Modifier ID.
+ /// True if the Modifier is present, false otherwise.
public static bool HasModifier(this PlayerControl? player, uint id)
{
return player?.GetModifierComponent() != null && player.GetModifierComponent()!.ActiveModifiers.Exists(x => x.ModifierId == id);
}
+ ///
+ /// Remote Procedure Call to remove a modifier from a player.
+ ///
+ /// The player to remove the modifier from.
+ /// The ID of the modifier.
[MethodRpc((uint)MiraRpc.RemoveModifier)]
public static void RpcRemoveModifier(this PlayerControl target, uint modifierId)
{
target.GetModifierComponent()?.RemoveModifier(modifierId);
}
- [MethodRpc((uint)MiraRpc.AddModifier)]
- public static BaseModifier? RpcAddModifier(this PlayerControl target, uint modifierId)
- {
- if (ModifierManager.IdToTypeModifiers.TryGetValue(modifierId, out var type))
- {
- return target.GetModifierComponent()?.AddModifier(type);
- }
-
- Logger.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
- return null;
- }
-
- public static void RpcAddModifier(this PlayerControl player) where T : BaseModifier
- {
- if (!ModifierManager.TypeToIdModifiers.TryGetValue(typeof(T), out var id))
- {
- Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
- return;
- }
-
- player.RpcAddModifier(id);
- }
-
+ ///
+ /// Remote Procedure Call to remove a modifier from a player.
+ ///
+ /// The player to remove the modifier from.
+ /// 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.RpcAddModifier(id);
+ player.RpcRemoveModifier(id.Value);
}
+ ///
+ /// Remote Procedure Call to add a modifier to a player.
+ ///
+ /// The player to add the modifier to.
+ /// The modifier ID.
+ [MethodRpc((uint)MiraRpc.AddModifier)]
+ public static void RpcAddModifier(this PlayerControl target, uint modifierId)
+ {
+ var type = ModifierManager.GetModifierType(modifierId);
+ if (type == null)
+ {
+ Logger.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
+ return;
+ }
+ target.GetModifierComponent()?.AddModifier(type);
+ }
+
+ ///
+ /// Remote Procedure Call to add a modifier to a player.
+ ///
+ /// The player to add the modifier to.
+ /// The modifier Type.
+ public static void RpcAddModifier(this PlayerControl player) where T : BaseModifier
+ {
+ 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.Value);
+ }
+
+ ///
+ /// Darkens a color by a specified amount.
+ ///
+ /// The original color.
+ /// A float amount between 0 and 1.
+ /// The darkened color.
public static Color DarkenColor(this Color color, float amount = 0.45f)
{
return new Color(color.r - amount, color.g - amount, color.b - amount);
}
+
+ ///
+ /// Gets an alternate color based on the original color.
+ ///
+ /// The original color.
+ /// The amount to darken or lighten the original color by between 0.0 and 1.0.
+ /// An alternate color that has been darkened or lightened.
public static Color GetAlternateColor(this Color color, float amount = 0.45f)
{
return color.IsColorDark() ? LightenColor(color, amount) : DarkenColor(color, amount);
}
+ ///
+ /// Lightens a color by a specified amount.
+ ///
+ /// The original color.
+ /// A float amount between 0.0 and 1.0.
+ /// The lightened color.
public static Color LightenColor(this Color color, float amount = 0.45f)
{
return new Color(color.r + amount, color.g + amount, color.b + amount);
}
+ ///
+ /// Checks if a color is dark.
+ ///
+ /// The color to check.
+ /// True if the color is dark, false otherwise.
public static bool IsColorDark(this Color color)
{
return color.r < 0.5f && color is { g: < 0.5f, b: < 0.5f };
}
+ ///
+ /// Gets the nearest dead body to a player.
+ ///
+ /// The player object.
+ /// The radius to search within.
+ /// The dead body if it is found, or null there is none within the radius.
public static DeadBody? GetNearestDeadBody(this PlayerControl playerControl, float radius)
{
- return Helpers.GetNearestDeadBodies(playerControl.GetTruePosition(), radius).FirstOrDefault(component => component && !component.Reported);
+ return Helpers.GetNearestDeadBodies(playerControl.GetTruePosition(), radius).Find(component => component && !component.Reported);
}
- public static T? GetNearestObjectOfType(this PlayerControl playerControl, float radius, string? colliderTag = null, System.Func? predicate = null) where T : Component
+ ///
+ /// Finds the nearest object of a specified type to a player. It will only work if the object has a collider.
+ ///
+ /// The player object.
+ /// The radius to search within.
+ /// An optional collider tag.
+ /// Optional predicate to test if the object is valid.
+ /// The type of the object.
+ /// The object if it was found, or null if there is none within the radius.
+ public static T? GetNearestObjectOfType(this PlayerControl playerControl, float radius, string? colliderTag = null, Predicate? predicate = null) where T : Component
{
- return Helpers.GetNearestObjectsOfType(playerControl.GetTruePosition(), radius, colliderTag).FirstOrDefault(predicate ?? (component => component));
+ return Helpers.GetNearestObjectsOfType(playerControl.GetTruePosition(), radius, colliderTag).Find(predicate ?? (component => component));
}
+ ///
+ /// Gets the nearest player to a player.
+ ///
+ /// The player object.
+ /// Whether impostors should be included in the search.
+ /// The radius to search within.
+ /// Whether colliders should be ignored when searching.
+ /// The closest player if there is one, false otherwise.
public static PlayerControl? GetClosestPlayer(this PlayerControl playerControl, bool includeImpostors, float distance, bool ignoreColliders = false)
{
- if (!ShipStatus.Instance)
- {
- return null;
- }
-
var filteredPlayers = Helpers.GetClosestPlayers(playerControl, distance, ignoreColliders)
.Where(playerInfo => !playerInfo.Data.Disconnected && playerInfo.PlayerId != playerControl.PlayerId && !playerInfo.Data.IsDead &&
(includeImpostors || !playerInfo.Data.Role.IsImpostor));
- return filteredPlayers.First();
+ return filteredPlayers.FirstOrDefault();
}
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs
index 10085f4..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,95 +65,139 @@ 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)
{
- List newList = GetNearestObjectsOfType(source, radius);
+ var newList = GetNearestObjectsOfType(source, radius);
- if (ignoreColliders)
+ if (!ignoreColliders)
{
- List filteredList = new List();
- foreach (var player in newList)
- {
- Vector2 vector = player.GetTruePosition() - source;
- float 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)
{
- return null;
+ return [];
}
- Vector2 myPos = source.GetTruePosition();
- List players = GetClosestPlayers(myPos, distance, ignoreColliders);
+ var myPos = source.GetTruePosition();
+ var players = GetClosestPlayers(myPos, distance, ignoreColliders);
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)
{
- return null;
+ return [];
}
- List outputList = new List();
+ List outputList = [];
outputList.Clear();
- List allPlayers = GameData.Instance.AllPlayers.ToArray().ToList();
+ var allPlayers = GameData.Instance.AllPlayers.ToArray().Select(x => x.Object);
- for (int i = 0; i < allPlayers.Count; i++)
- {
- NetworkedPlayerInfo networkedPlayerInfo = allPlayers[i];
+ 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);
- PlayerControl @object = networkedPlayerInfo.Object;
- if (@object && @object.Collider.enabled)
+ outputList.Sort(
+ delegate(PlayerControl a, PlayerControl b)
{
- Vector2 vector = @object.GetTruePosition() - source;
- float magnitude = vector.magnitude;
- if (magnitude <= distance && (ignoreColliders || !PhysicsHelpers.AnyNonTriggersBetween(source, vector.normalized, magnitude, Constants.ShipAndObjectsMask)))
+ var magnitude2 = (a.GetTruePosition() - source).magnitude;
+ var magnitude3 = (b.GetTruePosition() - source).magnitude;
+ if (magnitude2 > magnitude3)
{
- outputList.Add(@object);
+ return 1;
}
- }
- }
- outputList.Sort(delegate (PlayerControl a, PlayerControl b)
- {
- float magnitude2 = (a.GetTruePosition() - source).magnitude;
- float 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
@@ -152,16 +237,21 @@ TextAlignmentOptions textAlignment = TextAlignmentOptions.Center)
MiraNumberSuffixes.Multiplier => "x",
MiraNumberSuffixes.Seconds => "s",
MiraNumberSuffixes.Percent => "%",
- _ => string.Empty
+ _ => string.Empty,
};
}
+ ///
+ /// Creates a string builder for the Role Tab.
+ ///
+ /// The ICustomRole object.
+ /// A StringBuilder.
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;
}
-}
\ No newline at end of file
+}
diff --git a/README.md b/README.md
index ee83945..383a41d 100644
--- a/README.md
+++ b/README.md
@@ -118,7 +118,7 @@ public float SussyLevel { get; set; } = 4f; // You can set a default value here.
Here are the available Option Attributes and their signatures:
```csharp
-ModdedEnumOption(string name, Type enumType, Type roleType = null)
+ModdedEnumOption(string name, Type enumType, string[]? values = null, Type? roleType = null)
ModdedNumberOption(
string name,
@@ -127,11 +127,9 @@ ModdedNumberOption(
float increment=1
NumberSuffixes suffixType = NumberSuffixes.None,
bool zeroInfinity = false,
- Type roleType = null)
-
-ModdedStringOption(string title, string[] values, Type roleType = null)
+ Type? roleType = null)
-ModdedToggleOption(string name, Type roleType = null)
+ModdedToggleOption(string name, Type? roleType = null)
```
### ModdedOption Properties
@@ -144,7 +142,6 @@ public ModdedToggleOption YeezusAbility { get; } = new ModdedToggleOption("Yeezu
Here is a full list of ModdedOption classes you can use:
- `ModdedEnumOption`
- `ModdedNumberOption`
-- `ModdedStringOption`
- `ModdedToggleOption`
To see a full example of an options class, see [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Options/ExampleOptions.cs).