From 9485099ab94510e66b6051a7cb6537d874a964f1 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 2 Feb 2025 16:09:06 -0500
Subject: [PATCH 01/71] RPC add modifier with arguments
---
.../Buttons/Freezer/FreezeButton.cs | 1 +
MiraAPI.Example/Buttons/MeetingButton.cs | 2 +-
.../OptionTypes/ModdedEnumOption.cs | 1 -
MiraAPI/Modifiers/ModifierExtensions.cs | 139 ++++++++++++++++++
MiraAPI/Modifiers/ModifierFactory.cs | 83 +++++++++++
.../Patches/Modifiers/EndGameDidWinPatch.cs | 2 +-
MiraAPI/Patches/PlayerControlPatches.cs | 2 +-
MiraAPI/Patches/VentPatches.cs | 2 +-
MiraAPI/Utilities/Extensions.cs | 125 ----------------
9 files changed, 227 insertions(+), 130 deletions(-)
create mode 100644 MiraAPI/Modifiers/ModifierExtensions.cs
create mode 100644 MiraAPI/Modifiers/ModifierFactory.cs
diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs
index 8db98c7..86feece 100644
--- a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs
+++ b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs
@@ -3,6 +3,7 @@ using MiraAPI.Example.Options.Roles;
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.Hud;
+using MiraAPI.Modifiers;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
diff --git a/MiraAPI.Example/Buttons/MeetingButton.cs b/MiraAPI.Example/Buttons/MeetingButton.cs
index 85b8ff1..742c7e0 100644
--- a/MiraAPI.Example/Buttons/MeetingButton.cs
+++ b/MiraAPI.Example/Buttons/MeetingButton.cs
@@ -1,6 +1,6 @@
using MiraAPI.Example.Modifiers;
using MiraAPI.Hud;
-using MiraAPI.Utilities;
+using MiraAPI.Modifiers;
using MiraAPI.Utilities.Assets;
using UnityEngine;
diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
index e917cdc..80bd497 100644
--- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
+++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
@@ -1,5 +1,4 @@
using System;
-using System.Globalization;
using System.Linq;
using System.Text;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs
new file mode 100644
index 0000000..83dfd4c
--- /dev/null
+++ b/MiraAPI/Modifiers/ModifierExtensions.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using MiraAPI.Networking;
+using Reactor.Networking.Attributes;
+using Reactor.Utilities;
+
+namespace MiraAPI.Modifiers;
+
+///
+/// Extension methods for modifiers.
+///
+public static class ModifierExtensions
+{
+ internal static Dictionary ModifierComponents { get; } = [];
+
+ ///
+ /// 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))
+ {
+ return component;
+ }
+
+ component = player.GetComponent();
+ if (component == null)
+ {
+ return null;
+ }
+
+ ModifierComponents[player] = component;
+ return component;
+ }
+
+ ///
+ /// 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()!.HasModifier();
+ }
+
+ ///
+ /// 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()!.HasModifier(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);
+ }
+
+ ///
+ /// 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
+ {
+ var id = ModifierManager.GetModifierId(typeof(T));
+
+ if (id == null)
+ {
+ Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
+ return;
+ }
+
+ player.RpcRemoveModifier(id.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, params object[] args)
+ {
+ var type = ModifierManager.GetModifierType(modifierId);
+ if (type == null)
+ {
+ Logger.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
+ return;
+ }
+
+ var modifier = ModifierFactory.CreateInstance(type, args);
+ target.GetModifierComponent()?.AddModifier(modifier);
+ }
+
+ ///
+ /// Remote Procedure Call to add a modifier to a player.
+ ///
+ /// The player to add the modifier to.
+ /// The arguments to initialize the modifier constructor with.
+ /// The modifier Type.
+ public static void RpcAddModifier(this PlayerControl player, params object[] args) 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;
+ }
+
+ var modifier = ModifierFactory.CreateInstance(args);
+ player.GetModifierComponent()!.AddModifier(modifier);
+ }
+}
diff --git a/MiraAPI/Modifiers/ModifierFactory.cs b/MiraAPI/Modifiers/ModifierFactory.cs
new file mode 100644
index 0000000..e1b018c
--- /dev/null
+++ b/MiraAPI/Modifiers/ModifierFactory.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace MiraAPI.Modifiers;
+
+public static class ModifierFactory
+{
+ private static Func? _constructor;
+
+ private static Func CreateConstructor(Type type)
+ {
+ var constructorInfo = type.GetConstructors()
+ .OrderByDescending(c => c.GetParameters().Length)
+ .FirstOrDefault() ?? throw new InvalidOperationException($"Type {type} does not have a public constructor.");
+ var parameters = constructorInfo.GetParameters();
+
+ var argsParam = Expression.Parameter(typeof(object[]), "args");
+ var constructorParams = new Expression[parameters.Length];
+
+ for (var i = 0; i < parameters.Length; i++)
+ {
+ var paramType = parameters[i].ParameterType;
+ var paramAccess = Expression.ArrayIndex(argsParam, Expression.Constant(i));
+ var convertedParam = Expression.Convert(paramAccess, paramType);
+ constructorParams[i] = convertedParam;
+ }
+
+ var newExpr = Expression.New(constructorInfo, constructorParams);
+ var lambda = Expression.Lambda>(newExpr, argsParam);
+
+ return lambda.Compile();
+ }
+
+ public static BaseModifier CreateInstance(Type type, params object[] args)
+ {
+ _constructor ??= CreateConstructor(type);
+ return _constructor(args);
+ }
+}
+
+///
+/// Factory for creating instances of a modifier. More efficient than using reflection.
+///
+/// The modifier type.
+public static class ModifierFactory where T : BaseModifier
+{
+ private static readonly Func Constructor = CreateConstructor();
+
+ private static Func CreateConstructor()
+ {
+ var constructorInfo = typeof(T).GetConstructors()
+ .OrderByDescending(c => c.GetParameters().Length)
+ .FirstOrDefault() ?? throw new InvalidOperationException($"Type {typeof(T)} does not have a public constructor.");
+ var parameters = constructorInfo.GetParameters();
+
+ var argsParam = Expression.Parameter(typeof(object[]), "args");
+ var constructorParams = new Expression[parameters.Length];
+
+ for (var i = 0; i < parameters.Length; i++)
+ {
+ var paramType = parameters[i].ParameterType;
+ var paramAccess = Expression.ArrayIndex(argsParam, Expression.Constant(i));
+ var convertedParam = Expression.Convert(paramAccess, paramType);
+ constructorParams[i] = convertedParam;
+ }
+
+ var newExpr = Expression.New(constructorInfo, constructorParams);
+ var lambda = Expression.Lambda>(newExpr, argsParam);
+
+ return lambda.Compile();
+ }
+
+ ///
+ /// Creates an instance of the modifier.
+ ///
+ /// Parameters for the constructor.
+ /// An instance of the modifier.
+ public static T CreateInstance(params object[] args)
+ {
+ return Constructor(args);
+ }
+}
diff --git a/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs b/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs
index d7a99d5..17356db 100644
--- a/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs
+++ b/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs
@@ -1,7 +1,7 @@
using System.Linq;
using HarmonyLib;
+using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
-using MiraAPI.Utilities;
namespace MiraAPI.Patches.Modifiers;
diff --git a/MiraAPI/Patches/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs
index bc98a51..e0d647d 100644
--- a/MiraAPI/Patches/PlayerControlPatches.cs
+++ b/MiraAPI/Patches/PlayerControlPatches.cs
@@ -86,6 +86,6 @@ public static class PlayerControlPatches
[HarmonyPatch(nameof(PlayerControl.OnDestroy))]
public static void PlayerControlOnDestroyPrefix(PlayerControl __instance)
{
- Utilities.Extensions.ModifierComponents.Remove(__instance);
+ ModifierExtensions.ModifierComponents.Remove(__instance);
}
}
diff --git a/MiraAPI/Patches/VentPatches.cs b/MiraAPI/Patches/VentPatches.cs
index 66c9aae..a41c690 100644
--- a/MiraAPI/Patches/VentPatches.cs
+++ b/MiraAPI/Patches/VentPatches.cs
@@ -1,6 +1,6 @@
using HarmonyLib;
+using MiraAPI.Modifiers;
using MiraAPI.Roles;
-using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Patches;
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index 032cff3..d177d60 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -3,10 +3,8 @@ 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 UnityEngine;
@@ -130,30 +128,6 @@ public static class Extensions
opt => opt.OptionBehaviour && opt.OptionBehaviour == optionBehaviour);
}
- internal static Dictionary ModifierComponents { get; } = [];
-
- ///
- /// 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))
- {
- return component;
- }
-
- component = player.GetComponent();
- if (component == null)
- {
- return null;
- }
-
- ModifierComponents[player] = component;
- return component;
- }
-
///
/// Randomizes a list.
///
@@ -174,105 +148,6 @@ 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()!.HasModifier();
- }
-
- ///
- /// 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()!.HasModifier(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);
- }
-
- ///
- /// 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
- {
- var id = ModifierManager.GetModifierId(typeof(T));
-
- if (id == null)
- {
- Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
- return;
- }
-
- player.RpcRemoveModifier(id.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.
///
From ce81d8c1ff8be72e41ce26415f0667364cd63e03 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 2 Feb 2025 20:36:14 -0500
Subject: [PATCH 02/71] it works?
---
MiraAPI/Modifiers/ModifierData.cs | 21 +++++++++
MiraAPI/Modifiers/ModifierExtensions.cs | 9 ++--
MiraAPI/Networking/AddModifierRpc.cs | 61 +++++++++++++++++++++++++
3 files changed, 86 insertions(+), 5 deletions(-)
create mode 100644 MiraAPI/Modifiers/ModifierData.cs
create mode 100644 MiraAPI/Networking/AddModifierRpc.cs
diff --git a/MiraAPI/Modifiers/ModifierData.cs b/MiraAPI/Modifiers/ModifierData.cs
new file mode 100644
index 0000000..425e063
--- /dev/null
+++ b/MiraAPI/Modifiers/ModifierData.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace MiraAPI.Modifiers;
+
+///
+/// Modifier data for networking.
+///
+/// The Type of the modifier.
+/// Parameters for constructor.
+public readonly struct ModifierData(Type type, object[] args)
+{
+ ///
+ /// Gets the type of the modifier.
+ ///
+ public Type Type { get; } = type;
+
+ ///
+ /// Gets the parameters for the constructor.
+ ///
+ public object[] Args { get; } = args;
+}
diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs
index 83dfd4c..d6ce5ff 100644
--- a/MiraAPI/Modifiers/ModifierExtensions.cs
+++ b/MiraAPI/Modifiers/ModifierExtensions.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using MiraAPI.Networking;
using Reactor.Networking.Attributes;
+using Reactor.Networking.Rpc;
using Reactor.Utilities;
namespace MiraAPI.Modifiers;
@@ -104,7 +105,7 @@ public static class ModifierExtensions
///
/// The player to add the modifier to.
/// The modifier ID.
- [MethodRpc((uint)MiraRpc.AddModifier)]
+ /// The arguments to initialize the modifier constructor with.
public static void RpcAddModifier(this PlayerControl target, uint modifierId, params object[] args)
{
var type = ModifierManager.GetModifierType(modifierId);
@@ -114,8 +115,7 @@ public static class ModifierExtensions
return;
}
- var modifier = ModifierFactory.CreateInstance(type, args);
- target.GetModifierComponent()?.AddModifier(modifier);
+ Rpc.Instance.Send(target, new ModifierData(type, args));
}
///
@@ -133,7 +133,6 @@ public static class ModifierExtensions
return;
}
- var modifier = ModifierFactory.CreateInstance(args);
- player.GetModifierComponent()!.AddModifier(modifier);
+ player.RpcAddModifier(id.Value, args);
}
}
diff --git a/MiraAPI/Networking/AddModifierRpc.cs b/MiraAPI/Networking/AddModifierRpc.cs
new file mode 100644
index 0000000..3546d4f
--- /dev/null
+++ b/MiraAPI/Networking/AddModifierRpc.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Hazel;
+using MiraAPI.Modifiers;
+using Reactor.Networking.Attributes;
+using Reactor.Networking.Rpc;
+using Reactor.Networking.Serialization;
+
+namespace MiraAPI.Networking;
+
+///
+/// Remote procedure call for adding a modifier.
+///
+/// Mira plugin.
+/// RPC ID.
+[RegisterCustomRpc((uint)MiraRpc.AddModifier)]
+public class AddModifierRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc(plugin, id)
+{
+ private static readonly Dictionary ParameterCache = [];
+
+ ///
+ public override RpcLocalHandling LocalHandling => RpcLocalHandling.Before;
+
+ ///
+ public override void Write(MessageWriter writer, ModifierData data)
+ {
+ var modId = ModifierManager.GetModifierId(data.Type);
+ writer.WritePacked((uint)modId!);
+ MessageSerializer.Serialize(writer, data.Args);
+ }
+
+ ///
+ public override ModifierData Read(MessageReader reader)
+ {
+ var modId = reader.ReadPackedUInt32();
+ var modifier = ModifierManager.GetModifierType(modId)!;
+
+ if (!ParameterCache.TryGetValue(modifier, out var paramTypes))
+ {
+ paramTypes = modifier.GetConstructors().OrderBy(x => x.GetParameters().Length).First().GetParameters();
+ ParameterCache[modifier] = paramTypes;
+ }
+
+ var objects = new object[paramTypes.Length];
+ foreach (var paramType in paramTypes)
+ {
+ objects[paramType.Position] = reader.Deserialize(paramType.ParameterType);
+ }
+
+ return new ModifierData(modifier, objects);
+ }
+
+ ///
+ public override void Handle(PlayerControl innerNetObject, ModifierData data)
+ {
+ var modifier = ModifierFactory.CreateInstance(data.Type, data.Args);
+ innerNetObject.GetModifierComponent()?.AddModifier(modifier);
+ }
+}
From 8e2535ada25339845470623541afe08386bfc326 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 2 Feb 2025 20:42:23 -0500
Subject: [PATCH 03/71] yes it works
---
MiraAPI.Example/Buttons/TestButton.cs | 27 +++++++++++++++++++++
MiraAPI.Example/Modifiers/ModifierParams.cs | 16 ++++++++++++
2 files changed, 43 insertions(+)
create mode 100644 MiraAPI.Example/Buttons/TestButton.cs
create mode 100644 MiraAPI.Example/Modifiers/ModifierParams.cs
diff --git a/MiraAPI.Example/Buttons/TestButton.cs b/MiraAPI.Example/Buttons/TestButton.cs
new file mode 100644
index 0000000..8062c81
--- /dev/null
+++ b/MiraAPI.Example/Buttons/TestButton.cs
@@ -0,0 +1,27 @@
+using MiraAPI.Example.Modifiers;
+using MiraAPI.Hud;
+using MiraAPI.Modifiers;
+using MiraAPI.Utilities.Assets;
+using Reactor.Utilities.Extensions;
+using UnityEngine;
+
+namespace MiraAPI.Example.Buttons;
+
+[RegisterButton]
+public class TestButton : CustomActionButton
+{
+ public override string Name => "Test Button";
+ public override float Cooldown => 0f;
+ public override LoadableAsset Sprite => ExampleAssets.ExampleButton;
+
+ protected override void OnClick()
+ {
+ var randomPlayer = PlayerControl.AllPlayerControls.ToArray().Random();
+ PlayerControl.LocalPlayer.RpcAddModifier("test", 1, randomPlayer);
+ }
+
+ public override bool Enabled(RoleBehaviour? role)
+ {
+ return true;
+ }
+}
diff --git a/MiraAPI.Example/Modifiers/ModifierParams.cs b/MiraAPI.Example/Modifiers/ModifierParams.cs
new file mode 100644
index 0000000..cde7d3b
--- /dev/null
+++ b/MiraAPI.Example/Modifiers/ModifierParams.cs
@@ -0,0 +1,16 @@
+using MiraAPI.Modifiers;
+using Reactor.Utilities;
+
+namespace MiraAPI.Example.Modifiers;
+
+[RegisterModifier]
+public class ModifierParams : BaseModifier
+{
+ public override string ModifierName => "ModifierParams";
+
+ public ModifierParams(string param1, int param2, PlayerControl playerControl)
+ {
+ Logger.Error($"Param1: {param1}, Param2: {param2}");
+ Logger.Error($"Player: {playerControl.PlayerId}");
+ }
+}
From 5e16429acf88a187eafc052f342239c24a59c82d Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Tue, 18 Feb 2025 11:15:00 -0500
Subject: [PATCH 04/71] fix more scrolling issues
---
MiraAPI/Patches/Options/GameSettingMenuPatches.cs | 2 --
MiraAPI/Patches/Options/RoleSettingMenuPatches.cs | 4 ++++
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs
index a44b35c..0cdca29 100644
--- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs
+++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs
@@ -157,8 +157,6 @@ public static class GameSettingMenuPatches
roles.AdvancedRolesSettings.gameObject.SetActive(false);
roles.RoleChancesSettings.gameObject.SetActive(true);
roles.SetQuotaTab();
-
- roles.scrollBar.CalculateAndSetYBounds(roles.roleChances.Count + 5, 1f, 6f, 0.43f);
roles.scrollBar.ScrollToTop();
}
diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs
index 518268c..915f2bc 100644
--- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs
+++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs
@@ -32,6 +32,10 @@ public static class RoleSettingMenuPatches
[HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
public static bool PatchStart(RolesSettingsMenu __instance)
{
+ CategoryHeaderEditRoles.ForEach(Object.Destroy);
+ CategoryHeaderEditRoles.Clear();
+ RoleOptionSettings.ForEach(Object.Destroy);
+ RoleOptionSettings.Clear();
__instance.roleChances = new Il2CppSystem.Collections.Generic.List();
__instance.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List();
From ccde63068ba18da058f31cbdb12089c8b9ff0dd8 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Tue, 18 Feb 2025 21:49:54 -0500
Subject: [PATCH 05/71] fix lobby view pane issues
---
.../Patches/Options/LobbyViewPanePatches.cs | 61 +++++++++++--------
1 file changed, 36 insertions(+), 25 deletions(-)
diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs
index ee26822..e9d3c42 100644
--- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs
+++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs
@@ -6,6 +6,7 @@ using MiraAPI.Utilities.Assets;
using System;
using System.Collections.Generic;
using System.Linq;
+using Reactor.Localization.Utilities;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
@@ -211,34 +212,43 @@ public static class LobbyViewPanePatches
var list = new List();
- foreach (var team in Enum.GetValues())
- {
- var filteredRoles = SelectedMod.CustomRoles.Values
- .OfType()
- .Where(x => !x.Configuration.HideSettings && x.Team == team).ToList();
+ var roleGroups = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values.OfType()
+ .ToLookup(x => x.RoleOptionsGroup);
- if (filteredRoles.Count == 0)
+ if (roleGroups is null)
+ {
+ return;
+ }
+
+ // sort the groups by priority
+ var sortedRoleGroups = roleGroups
+ .OrderBy(x => x.Key.Priority)
+ .ThenBy(x => x.Key.Name);
+
+ foreach (var grouping in sortedRoleGroups)
+ {
+ if (!grouping.Any())
{
continue;
}
- var categoryHeaderRoleVariant =
- Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true);
+ var group = grouping.Key;
- switch (team)
+ var name = group.Name switch
{
- 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;
+ "Crewmate" => StringNames.CrewmateRolesHeader,
+ "Impostor" => StringNames.ImpostorRolesHeader,
+ _ => CustomStringName.CreateAndRegister(group.Name),
+ };
+
+ var categoryHeaderRoleVariant = Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true);
+ categoryHeaderRoleVariant.SetHeader(name, 61);
+
+ if (name is not (StringNames.CrewmateRolesHeader or StringNames.ImpostorRolesHeader))
+ {
+ var veryDarkColor = group.Color.DarkenColor(.35f);
+ categoryHeaderRoleVariant.Title.color = veryDarkColor;
+ categoryHeaderRoleVariant.Background.color = group.Color;
}
categoryHeaderRoleVariant.transform.localScale = Vector3.one;
@@ -246,7 +256,7 @@ public static class LobbyViewPanePatches
instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject);
num -= 0.696f;
- foreach (var customRole in filteredRoles)
+ foreach (var customRole in grouping)
{
var roleBehaviour = customRole as RoleBehaviour;
if (roleBehaviour == null)
@@ -259,8 +269,6 @@ public static class LobbyViewPanePatches
var numPerGame =
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role);
- var flag = numPerGame == 0;
-
var viewSettingsInfoPanelRoleVariant =
Object.Instantiate(
instance.infoPanelRoleOrigin,
@@ -273,7 +281,7 @@ public static class LobbyViewPanePatches
.Where(x => x.AdvancedRole == customRole.GetType())
.ToList();
- if (!flag && advancedRoleOptions.Count > 0)
+ if (numPerGame > 0 && advancedRoleOptions.Count > 0)
{
list.Add(customRole.GetType());
}
@@ -286,6 +294,8 @@ public static class LobbyViewPanePatches
customRole.RoleColor,
customRole.Configuration.Icon.LoadAsset(),
true);
+ viewSettingsInfoPanelRoleVariant.iconSprite.transform.localScale = new Vector3(0.365f, 0.365f, 1f);
+ viewSettingsInfoPanelRoleVariant.iconSprite.transform.localPosition = new Vector3(0.7144f, -0.028f, -2);
viewSettingsInfoPanelRoleVariant.titleText.color =
viewSettingsInfoPanelRoleVariant.chanceTitle.color =
@@ -370,6 +380,7 @@ public static class LobbyViewPanePatches
maskLayer,
role.TeamType == RoleTeamTypes.Crewmate,
customRole.Configuration.Icon.LoadAsset());
+ viewPanel.header.icon.transform.localScale = new Vector3(0.465f, 0.465f, 1f);
viewPanel.divider.material.SetInt(PlayerMaterial.MaskLayer, maskLayer);
var num = viewPanel.yPosStart;
From 4b6ffc65df4da8376e631bdf33079e5f6bed5cd4 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Fri, 21 Feb 2025 09:26:53 -0500
Subject: [PATCH 06/71] fix after murder event
---
MiraAPI/Networking/CustomMurderRpc.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/MiraAPI/Networking/CustomMurderRpc.cs b/MiraAPI/Networking/CustomMurderRpc.cs
index 7f1e60f..1a46865 100644
--- a/MiraAPI/Networking/CustomMurderRpc.cs
+++ b/MiraAPI/Networking/CustomMurderRpc.cs
@@ -263,6 +263,9 @@ public static class CustomMurderRpc
deadBody.enabled = true;
}
+ var afterMurderEvent = new AfterMurderEvent(source, target);
+ MiraEventManager.InvokeEvent(afterMurderEvent);
+
if (!isParticipant)
{
yield break;
@@ -275,8 +278,5 @@ public static class CustomMurderRpc
PlayerControl.LocalPlayer.isKilling = false;
source.isKilling = false;
-
- var afterMurderEvent = new AfterMurderEvent(source, target);
- MiraEventManager.InvokeEvent(afterMurderEvent);
}
}
From 9504862bea025c0a7b54fa593e5127cff60c974b Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Fri, 21 Feb 2025 12:01:28 -0500
Subject: [PATCH 07/71] add format string option to number options
---
MiraAPI.Example/Options/ExampleOptions.cs | 2 +-
.../Attributes/ModdedNumberOptionAttribute.cs | 3 ++-
MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs | 10 +++++++++-
3 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/MiraAPI.Example/Options/ExampleOptions.cs b/MiraAPI.Example/Options/ExampleOptions.cs
index 9e9fa59..53bc9eb 100644
--- a/MiraAPI.Example/Options/ExampleOptions.cs
+++ b/MiraAPI.Example/Options/ExampleOptions.cs
@@ -16,7 +16,7 @@ public class ExampleOptions : AbstractOptionGroup
[ModdedToggleOption("Toggle Opt 2")]
public bool ToggleOpt2 { get; set; } = true;
- [ModdedNumberOption("Number Opt", min: 0, max: 10, suffixType: MiraNumberSuffixes.Percent)]
+ [ModdedNumberOption("Number Opt", min: 0, max: 10, increment: .25f, formatString: "0.00", suffixType: MiraNumberSuffixes.Percent)]
public float NumberOpt { get; set; } = 4f;
[ModdedEnumOption("Best API", typeof(BestApi), ["Mira API", "Mitochondria", "Reactor"])]
diff --git a/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs
index 2bd4438..b7b85c2 100644
--- a/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs
+++ b/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs
@@ -15,13 +15,14 @@ public class ModdedNumberOptionAttribute(
float max,
float increment = 1,
MiraNumberSuffixes suffixType = MiraNumberSuffixes.None,
+ string? formatString = null,
bool zeroInfinity = false,
Type? roleType = null)
: ModdedOptionAttribute(title, roleType)
{
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
{
- return new ModdedNumberOption(Title, (float)(value ?? min+increment), min, max, increment, suffixType, zeroInfinity, RoleType);
+ return new ModdedNumberOption(Title, (float)(value ?? min+increment), min, max, increment, suffixType, formatString, zeroInfinity, RoleType);
}
///
diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs
index 24cd346..deb8052 100644
--- a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs
+++ b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs
@@ -46,6 +46,7 @@ public class ModdedNumberOption : ModdedOption
/// The maximum value.
/// The increment.
/// The suffix type.
+ /// Optional format string for the option screen.
/// Whether zero is infinity or not.
/// An optional role type.
public ModdedNumberOption(
@@ -55,6 +56,7 @@ public class ModdedNumberOption : ModdedOption
float max,
float increment,
MiraNumberSuffixes suffixType,
+ string? formatString = null,
bool zeroInfinity = false,
Type? roleType = null) : base(title, defaultValue, roleType)
{
@@ -74,7 +76,13 @@ 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"; // not sure if the == checks will work since this is floats but we'll find out
+ data.FormatString = formatString ??
+ (!Mathf.Approximately(defaultValue, 0) ||
+ !Mathf.Approximately(Increment % 1, 0) ||
+ !Mathf.Approximately(Value % 1, 0) ||
+ !Mathf.Approximately(Min % 1, 0) ||
+ !Mathf.Approximately(Max % 1, 0) ? "0.0" : "0");
+
data.ZeroIsInfinity = ZeroInfinity;
data.SuffixType = (NumberSuffixes)SuffixType;
data.OptionName = FloatOptionNames.Invalid;
From b6dda5f9704d8e3b6a8ce6daf0a10515f74484d9 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sat, 22 Feb 2025 18:54:20 -0500
Subject: [PATCH 08/71] freeplay modifiers
---
MiraAPI.Example/Modifiers/CaptainModifier.cs | 3 +-
.../Modifiers/Freezer/FreezeModifier.cs | 1 -
.../Modifiers/HighPriorityModifier.cs | 3 +-
.../OptionTypes/ModdedEnumOption.cs | 1 -
MiraAPI/Modifiers/BaseModifier.cs | 5 +
MiraAPI/Modifiers/ModifierManager.cs | 7 +-
MiraAPI/Modifiers/Types/GameModifier.cs | 3 +
MiraAPI/Patches/Roles/TaskAddButtonPatches.cs | 43 ++++++
MiraAPI/Patches/Roles/TaskAdderPatch.cs | 141 +++++++++++++-----
MiraAPI/PluginLoading/MiraPluginInfo.cs | 3 +
MiraAPI/PluginLoading/MiraPluginManager.cs | 10 +-
MiraAPI/Roles/ICustomRole.cs | 3 +-
12 files changed, 170 insertions(+), 53 deletions(-)
create mode 100644 MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
diff --git a/MiraAPI.Example/Modifiers/CaptainModifier.cs b/MiraAPI.Example/Modifiers/CaptainModifier.cs
index 60691a8..63b7c41 100644
--- a/MiraAPI.Example/Modifiers/CaptainModifier.cs
+++ b/MiraAPI.Example/Modifiers/CaptainModifier.cs
@@ -1,5 +1,4 @@
-using MiraAPI.Modifiers;
-using MiraAPI.Modifiers.Types;
+using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Modifiers;
diff --git a/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs b/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs
index 642e941..12a9587 100644
--- a/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs
+++ b/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs
@@ -1,6 +1,5 @@
using Il2CppSystem;
using MiraAPI.Example.Roles;
-using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
using UnityEngine;
diff --git a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs
index b94c4d0..c640ed8 100644
--- a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs
+++ b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs
@@ -1,5 +1,4 @@
-using MiraAPI.Modifiers;
-using MiraAPI.Modifiers.Types;
+using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Modifiers;
diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
index e917cdc..80bd497 100644
--- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
+++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
@@ -1,5 +1,4 @@
using System;
-using System.Globalization;
using System.Linq;
using System.Text;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs
index 47cd491..78a5cd0 100644
--- a/MiraAPI/Modifiers/BaseModifier.cs
+++ b/MiraAPI/Modifiers/BaseModifier.cs
@@ -30,6 +30,11 @@ public abstract class BaseModifier
///
public virtual bool HideOnUi => false;
+ ///
+ /// Gets a value indicating whether the modifier is shown in the freeplay menu.
+ ///
+ public virtual bool ShowInFreeplay => false;
+
///
/// Gets the HUD information for this modifier. Defaults to the modifier name. Does nothing if is true.
///
diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs
index ddb3c87..d4d4004 100644
--- a/MiraAPI/Modifiers/ModifierManager.cs
+++ b/MiraAPI/Modifiers/ModifierManager.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.Serialization;
using MiraAPI.Modifiers.Types;
using MiraAPI.Networking;
+using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using Reactor.Networking.Rpc;
@@ -49,7 +51,7 @@ public static class ModifierManager
return TypeToIdModifierMap.GetValueOrDefault(type);
}
- internal static bool RegisterModifier(Type modifierType)
+ internal static bool RegisterModifier(Type modifierType, MiraPluginInfo info)
{
if (!typeof(BaseModifier).IsAssignableFrom(modifierType))
{
@@ -58,6 +60,9 @@ public static class ModifierManager
IdToTypeModifierMap.Add(GetNextId(), modifierType);
TypeToIdModifierMap.Add(modifierType, _nextId);
+ var bm = (BaseModifier)FormatterServices.GetUninitializedObject(modifierType); // this probably isn't a great idea
+ bm.ModifierId = _nextId;
+ info.Modifiers.Add(bm);
if (!typeof(GameModifier).IsAssignableFrom(modifierType))
{
diff --git a/MiraAPI/Modifiers/Types/GameModifier.cs b/MiraAPI/Modifiers/Types/GameModifier.cs
index a7b1fe1..abcd1f7 100644
--- a/MiraAPI/Modifiers/Types/GameModifier.cs
+++ b/MiraAPI/Modifiers/Types/GameModifier.cs
@@ -5,6 +5,9 @@
///
public abstract class GameModifier : BaseModifier
{
+ ///
+ public override bool ShowInFreeplay => true;
+
///
/// Gets the chance of the modifier being assigned to a player.
///
diff --git a/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs b/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
new file mode 100644
index 0000000..c9e3e8b
--- /dev/null
+++ b/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
@@ -0,0 +1,43 @@
+using HarmonyLib;
+using MiraAPI.Modifiers;
+using MiraAPI.Utilities;
+using UnityEngine.UI;
+
+namespace MiraAPI.Patches.Roles;
+
+[HarmonyPatch(typeof(TaskAddButton))]
+public static class TaskAddButtonPatches
+{
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(TaskAddButton.Start))]
+ public static bool StartPrefix(TaskAddButton __instance)
+ {
+ if (uint.TryParse(__instance.name, out var result))
+ {
+ __instance.Overlay.enabled = false;
+ if (PlayerControl.LocalPlayer.HasModifier(result))
+ {
+ __instance.Overlay.enabled = true;
+ __instance.Overlay.sprite = __instance.CheckImage;
+ }
+
+ __instance.Button.OnClick = new Button.ButtonClickedEvent();
+ __instance.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() =>
+ {
+ if (PlayerControl.LocalPlayer.HasModifier(result))
+ {
+ PlayerControl.LocalPlayer.GetModifierComponent()!.RemoveModifier(result);
+ __instance.Overlay.enabled = false;
+ }
+ else
+ {
+ PlayerControl.LocalPlayer.GetModifierComponent()!.AddModifier(ModifierManager.GetModifierType(result)!);
+ __instance.Overlay.enabled = true;
+ }
+ }));
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
index 98e93bc..b6516bd 100644
--- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs
+++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
@@ -4,7 +4,6 @@ using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using System;
-using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
@@ -18,7 +17,6 @@ namespace MiraAPI.Patches.Roles;
[HarmonyPatch]
public static class TaskAdderPatch
{
- private static readonly Dictionary ModsFolders = [];
private static Scroller? _scroller;
///
@@ -67,25 +65,34 @@ public static class TaskAdderPatch
rolesFolder.FolderName = "Roles";
rolesFolder.name = "RolesFolder";
+ var modifiersFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
+ modifiersFolder.gameObject.SetActive(false);
+ modifiersFolder.FolderName = "Modifiers";
+ modifiersFolder.name = "ModifiersFolder";
+
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
{
- if (plugin.CustomRoles.Count == 0)
+ if (plugin.CustomRoles.Any(x => x.Value is ICustomRole { Configuration.ShowInFreeplay: true }))
{
- continue;
+ var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
+ newFolder.name = plugin.PluginId+":Roles";
+ newFolder.FolderName = plugin.PluginInfo.Metadata.Name;
+ newFolder.gameObject.SetActive(false);
+ rolesFolder.SubFolders.Add(newFolder);
}
- var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
- newFolder.FolderName = newFolder.name = plugin.PluginInfo.Metadata.Name;
- newFolder.gameObject.SetActive(false);
- rolesFolder.SubFolders.Add(newFolder);
-
- if (!ModsFolders.ContainsKey(plugin.PluginInfo.Metadata.Name))
+ if (plugin.Modifiers.Exists(x => x.ShowInFreeplay))
{
- ModsFolders.Add(plugin.PluginInfo.Metadata.Name, plugin.PluginId);
+ var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
+ newFolder.name = plugin.PluginId+":Modifiers";
+ newFolder.FolderName = plugin.PluginInfo.Metadata.Name;
+ newFolder.gameObject.SetActive(false);
+ modifiersFolder.SubFolders.Add(newFolder);
}
}
__instance.Root.SubFolders.Add(rolesFolder);
+ __instance.Root.SubFolders.Add(modifiersFolder);
__instance.GoToRoot();
}
@@ -277,39 +284,21 @@ public static class TaskAdderPatch
}
}
- if (ModsFolders.TryGetValue(taskFolder.FolderName, out var guid))
+ var split = taskFolder.name.Split(':');
+ if (split.Length == 2)
{
- var plugin = MiraPluginManager.GetPluginByGuid(guid);
- for (var m = 0; m < plugin.CustomRoles.Count; m++)
+ var pluginId = split[0];
+ var folderName = split[1];
+
+ if (MiraPluginManager.GetPluginByGuid(pluginId) is { } plugin)
{
- var roleBehaviour = plugin.CustomRoles.ElementAt(m).Value;
- if (roleBehaviour.Role == RoleTypes.ImpostorGhost || roleBehaviour.Role == RoleTypes.CrewmateGhost ||
- (roleBehaviour is ICustomRole customRole && !customRole.Configuration.ShowInFreeplay))
+ if (folderName == "Roles(Clone)")
{
- continue;
+ PluginRoles(__instance, plugin, ref num, ref num2, ref num3);
}
-
- var taskAddButton2 = Object.Instantiate(__instance.RoleButton);
- taskAddButton2.SafePositionWorld = __instance.SafePositionWorld;
- taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
- __instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
- taskAddButton2.Role = roleBehaviour;
- if (taskAddButton2.Button == null)
+ else if (folderName == "Modifiers(Clone)")
{
- continue;
- }
-
- ControllerManager.Instance.AddSelectableUiElement(taskAddButton2.Button);
- switch (m)
- {
- case 0 when __instance.restorePreviousSelectionFound != null:
- ControllerManager.Instance.SetDefaultSelection(__instance.restorePreviousSelectionFound);
- __instance.restorePreviousSelectionByFolderName = string.Empty;
- __instance.restorePreviousSelectionFound = null;
- break;
- case 0:
- ControllerManager.Instance.SetDefaultSelection(taskAddButton2.Button);
- break;
+ PluginModifiers(__instance, plugin, ref num, ref num2, ref num3);
}
}
}
@@ -336,4 +325,78 @@ public static class TaskAdderPatch
ControllerManager.Instance.SetBackButton(__instance.FolderBackButton);
return false;
}
+
+ private static void PluginModifiers(
+ TaskAdderGame instance,
+ MiraPluginInfo plugin,
+ ref float num,
+ ref float num2,
+ ref float num3)
+ {
+ for (var m = 0; m < plugin.Modifiers.Count; m++)
+ {
+ var modifier = plugin.Modifiers[m];
+ if (!modifier.ShowInFreeplay)
+ {
+ continue;
+ }
+
+ var taskAddButton = Object.Instantiate(instance.RoleButton);
+ taskAddButton.name = modifier.ModifierId.ToString();
+ taskAddButton.role = null;
+ taskAddButton.MyTask = null;
+ taskAddButton.SafePositionWorld = instance.SafePositionWorld;
+ taskAddButton.Text.text = modifier.ModifierName;
+ instance.AddFileAsChildCustom(taskAddButton, ref num, ref num2, ref num3);
+
+ ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button);
+ switch (m)
+ {
+ case 0 when instance.restorePreviousSelectionFound != null:
+ ControllerManager.Instance.SetDefaultSelection(instance.restorePreviousSelectionFound);
+ instance.restorePreviousSelectionByFolderName = string.Empty;
+ instance.restorePreviousSelectionFound = null;
+ break;
+ case 0:
+ ControllerManager.Instance.SetDefaultSelection(taskAddButton.Button);
+ break;
+ }
+ }
+ }
+
+ private static void PluginRoles(TaskAdderGame instance, MiraPluginInfo plugin, ref float num, ref float num2, ref float num3)
+ {
+ for (var m = 0; m < plugin.CustomRoles.Count; m++)
+ {
+ var roleBehaviour = plugin.CustomRoles.ElementAt(m).Value;
+ if (roleBehaviour.Role == RoleTypes.ImpostorGhost || roleBehaviour.Role == RoleTypes.CrewmateGhost ||
+ roleBehaviour is ICustomRole { Configuration.ShowInFreeplay: false })
+ {
+ continue;
+ }
+
+ var taskAddButton2 = Object.Instantiate(instance.RoleButton);
+ taskAddButton2.SafePositionWorld = instance.SafePositionWorld;
+ taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
+ instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
+ taskAddButton2.Role = roleBehaviour;
+ if (taskAddButton2.Button == null)
+ {
+ continue;
+ }
+
+ ControllerManager.Instance.AddSelectableUiElement(taskAddButton2.Button);
+ switch (m)
+ {
+ case 0 when instance.restorePreviousSelectionFound != null:
+ ControllerManager.Instance.SetDefaultSelection(instance.restorePreviousSelectionFound);
+ instance.restorePreviousSelectionByFolderName = string.Empty;
+ instance.restorePreviousSelectionFound = null;
+ break;
+ case 0:
+ ControllerManager.Instance.SetDefaultSelection(taskAddButton2.Button);
+ break;
+ }
+ }
+ }
}
diff --git a/MiraAPI/PluginLoading/MiraPluginInfo.cs b/MiraAPI/PluginLoading/MiraPluginInfo.cs
index 25a4dca..160980e 100644
--- a/MiraAPI/PluginLoading/MiraPluginInfo.cs
+++ b/MiraAPI/PluginLoading/MiraPluginInfo.cs
@@ -5,6 +5,7 @@ using MiraAPI.GameOptions;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MiraAPI.Hud;
+using MiraAPI.Modifiers;
namespace MiraAPI.PluginLoading;
@@ -61,6 +62,8 @@ public class MiraPluginInfo
internal List Options { get; } = [];
+ internal List Modifiers { get; } = [];
+
internal Dictionary CustomRoles { get; } = [];
internal Dictionary GameModes { get; } = [];
diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs
index 678dee4..a7ed480 100644
--- a/MiraAPI/PluginLoading/MiraPluginManager.cs
+++ b/MiraAPI/PluginLoading/MiraPluginManager.cs
@@ -48,7 +48,7 @@ public sealed class MiraPluginManager
continue;
}
- if (RegisterModifier(type))
+ if (RegisterModifier(type, info))
{
continue;
}
@@ -91,9 +91,9 @@ public sealed class MiraPluginManager
///
/// The plugin GUID.
/// A MiraPluginInfo.
- public static MiraPluginInfo GetPluginByGuid(string pluginId)
+ public static MiraPluginInfo? GetPluginByGuid(string pluginId)
{
- return Instance._registeredPlugins.Values.First(plugin => plugin.PluginId == pluginId);
+ return Instance._registeredPlugins.Values.FirstOrDefault(plugin => plugin.PluginId == pluginId);
}
private static bool RegisterOptions(Type type, MiraPluginInfo pluginInfo)
@@ -199,11 +199,11 @@ public sealed class MiraPluginManager
}
}
- private static bool RegisterModifier(Type type)
+ private static bool RegisterModifier(Type type, MiraPluginInfo info)
{
try
{
- return ModifierManager.RegisterModifier(type);
+ return ModifierManager.RegisterModifier(type, info);
}
catch (Exception e)
{
diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs
index 9afc407..a452d1e 100644
--- a/MiraAPI/Roles/ICustomRole.cs
+++ b/MiraAPI/Roles/ICustomRole.cs
@@ -1,5 +1,4 @@
-using System;
-using BepInEx.Configuration;
+using BepInEx.Configuration;
using MiraAPI.Modifiers;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities;
From 52ff4bfbfc3b99f649f44eb3db8137aab8a71804 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sat, 22 Feb 2025 18:59:19 -0500
Subject: [PATCH 09/71] .
---
MiraAPI/PluginLoading/MiraPluginInfo.cs | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/MiraAPI/PluginLoading/MiraPluginInfo.cs b/MiraAPI/PluginLoading/MiraPluginInfo.cs
index 160980e..81412e8 100644
--- a/MiraAPI/PluginLoading/MiraPluginInfo.cs
+++ b/MiraAPI/PluginLoading/MiraPluginInfo.cs
@@ -22,6 +22,15 @@ public class MiraPluginInfo
PluginId = info.Metadata.GUID;
}
+ ///
+ /// Get a read only collection of this plugin's modifiers. This is probably not safe because they are uninitialized.
+ ///
+ /// Readonly collection of modifiers.
+ public IReadOnlyCollection GetModifiers()
+ {
+ return Modifiers.AsReadOnly();
+ }
+
///
/// Get a read only collection of this plugin's Option Groups.
///
From ba92d9608cdef40b70b7257d3466a235068497dd Mon Sep 17 00:00:00 2001
From: ang-xd
Date: Sat, 22 Feb 2025 21:12:54 -0500
Subject: [PATCH 10/71] Updated role settings appearance + role options
---
.../Patches/Options/RoleSettingMenuPatches.cs | 184 +++++++++++++-----
MiraAPI/Roles/CustomRoleConfiguration.cs | 3 +-
MiraAPI/Roles/CustomRoleManager.cs | 2 +-
3 files changed, 139 insertions(+), 50 deletions(-)
diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs
index 915f2bc..f8dd48d 100644
--- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs
+++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs
@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using HarmonyLib;
+using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using MiraAPI.Networking;
using MiraAPI.Roles;
@@ -11,6 +8,9 @@ using Reactor.Localization.Utilities;
using Reactor.Networking.Rpc;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
@@ -22,9 +22,9 @@ namespace MiraAPI.Patches.Options;
[HarmonyPatch(typeof(RolesSettingsMenu))]
public static class RoleSettingMenuPatches
{
- private static Dictionary RoleGroupHidden { get; } = [];
- private static List CategoryHeaderEditRoles { get; } = [];
- private static List RoleOptionSettings { get; } = [];
+ private static Dictionary RoleGroupHidden { get; set; } = new();
+ private static List Headers { get; set; } = new();
+ private static List RoleOptionSettings { get; set; } = new();
private static float ScrollerNum { get; set; } = 0.522f;
@@ -32,10 +32,11 @@ public static class RoleSettingMenuPatches
[HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
public static bool PatchStart(RolesSettingsMenu __instance)
{
- CategoryHeaderEditRoles.ForEach(Object.Destroy);
- CategoryHeaderEditRoles.Clear();
+ Headers.ForEach(Object.Destroy);
RoleOptionSettings.ForEach(Object.Destroy);
+ Headers.Clear();
RoleOptionSettings.Clear();
+
__instance.roleChances = new Il2CppSystem.Collections.Generic.List();
__instance.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List();
@@ -79,6 +80,10 @@ public static class RoleSettingMenuPatches
.OrderBy(x => x.Key.Priority)
.ThenBy(x => x.Key.Name);
+ var quotaThing = __instance.categoryHeaderEditRoleOrigin.transform.FindChild("QuotaHeader");
+ var template = __instance.transform.parent.parent.GetComponent().GameSettingsTab.categoryHeaderOrigin;
+
+
foreach (var grouping in sortedRoleGroups)
{
if (!grouping.Any())
@@ -97,39 +102,73 @@ public static class RoleSettingMenuPatches
_ => CustomStringName.CreateAndRegister(group.Name),
};
- var categoryHeaderEditRole = Object.Instantiate(
- __instance.categoryHeaderEditRoleOrigin,
+ var categoryHeaderMasked = Object.Instantiate(
+ template,
Vector3.zero,
Quaternion.identity,
__instance.RoleChancesSettings.transform);
- categoryHeaderEditRole.SetHeader(name, 20);
- if (name is not (StringNames.CrewmateRolesHeader or StringNames.ImpostorRolesHeader))
+
+ categoryHeaderMasked.SetHeader(name, 20);
+
+ var quotaInst = Object.Instantiate(quotaThing, categoryHeaderMasked.transform);
+ quotaInst.transform.localScale = new Vector3(1.3f, 1.3f, 1.3f);
+ quotaInst.transform.localPosition = new Vector3(0.7f, -0.82f, 0);
+
+ var chanceText = quotaInst.transform.FindChild("Chance Text");
+ chanceText.transform.localPosition = new Vector3(4.3f, 0.0993f, 0);
+
+ var countText = quotaInst.transform.FindChild("# Text");
+ countText.transform.localPosition = new Vector3(1.9f, 0.0993f, 0f);
+
+ var blankLabel = quotaInst.transform.FindChild("BlankLabel").gameObject;
+ var chanceLabel = quotaInst.transform.FindChild("Chance Label").gameObject;
+ var countLabel = quotaInst.transform.FindChild("# Label").gameObject;
+ blankLabel.Destroy();
+ chanceLabel.Destroy();
+ countLabel.Destroy();
+
+ if (name is StringNames.CrewmateRolesHeader)
{
- var darkColor = group.Color.DarkenColor(.25f);
- var veryDarkColor = group.Color.DarkenColor(.35f);
- categoryHeaderEditRole.Title.color = veryDarkColor;
- categoryHeaderEditRole.blankLabel.color = veryDarkColor;
- categoryHeaderEditRole.Background.color = group.Color;
- categoryHeaderEditRole.chanceLabel.color = darkColor;
- categoryHeaderEditRole.countLabel.color = darkColor;
+ categoryHeaderMasked.Title.color = Palette.CrewmateRoleHeaderTextBlue;
+ categoryHeaderMasked.Background.color = Palette.CrewmateRoleHeaderBlue;
+ }
+ else if (name is StringNames.ImpostorRolesHeader)
+ {
+ categoryHeaderMasked.Title.color = Palette.ImpostorRoleHeaderTextRed;
+ categoryHeaderMasked.Background.color = Palette.ImpostorRoleHeaderRed;
+ }
+ else
+ {
+ categoryHeaderMasked.Title.color = group.Color;
+ categoryHeaderMasked.Divider.color = group.Color;
+ categoryHeaderMasked.Background.color = group.Color;
}
- categoryHeaderEditRole.transform.localPosition = new Vector3(4.986f, ScrollerNum, -2f);
- categoryHeaderEditRole.transform.Find("LabelSprite").transform.localScale = new Vector3(1.3f, 1, 0.5529f);
- categoryHeaderEditRole.transform.Find("QuotaHeader").gameObject.SetActive(!RoleGroupHidden[group]);
+ categoryHeaderMasked.Title.color = categoryHeaderMasked.Title.color.IsColorDark() ?
+ categoryHeaderMasked.Title.color.LightenColor(0.3f) : categoryHeaderMasked.Title.color.LightenColor(0.1f);
- CategoryHeaderEditRoles.Add(categoryHeaderEditRole);
+ categoryHeaderMasked.Title.fontStyle = __instance.categoryHeaderEditRoleOrigin.Title.fontStyle;
+ categoryHeaderMasked.Title.font = __instance.categoryHeaderEditRoleOrigin.Title.font;
+ categoryHeaderMasked.Title.fontMaterial = __instance.categoryHeaderEditRoleOrigin.Title.fontMaterial;
- ScrollerNum -= 0.522f;
+ categoryHeaderMasked.Divider.color = categoryHeaderMasked.Background.color;
+ categoryHeaderMasked.Background.size = new Vector2(categoryHeaderMasked.Background.size.x + 1.5f, categoryHeaderMasked.Background.size.y);
+ categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f;
+ categoryHeaderMasked.transform.localPosition = new Vector3(-0.44f, ScrollerNum, -2f);
+ categoryHeaderMasked.gameObject.SetActive(true);
+ quotaInst.gameObject.SetActive(!RoleGroupHidden[group]);
var label = RoleGroupHidden[group]
? "(Click to open)"
: "(Click to close)";
- var newText = Object.Instantiate(categoryHeaderEditRole.Title, categoryHeaderEditRole.transform);
- newText.text = $"{label} ";
- newText.transform.localPosition = new Vector3(-3.3425f, -0.1706f, -1);
+ var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform);
+ newText.text = $"{label} ";
+ newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f);
newText.gameObject.GetComponent().Destroy();
+ Headers.Add(categoryHeaderMasked.gameObject);
+ ScrollerNum -= 0.422f;
+
if (!RoleGroupHidden[group])
{
foreach (var role in grouping)
@@ -148,11 +187,11 @@ public static class RoleSettingMenuPatches
}
}
- var boxCol = categoryHeaderEditRole.gameObject.AddComponent();
- boxCol.size = new Vector2(3, 0.25f);
- boxCol.offset = new Vector2(-2.05f, -.175f);
+ var boxCol = categoryHeaderMasked.gameObject.AddComponent();
+ boxCol.size = new Vector2(7, 0.7f);
+ boxCol.offset = new Vector2(1.5f, -0.3f);
- var headerBtn = categoryHeaderEditRole.gameObject.AddComponent();
+ var headerBtn = categoryHeaderMasked.gameObject.AddComponent();
headerBtn.ClickSound = __instance.BackButton.GetComponent().ClickSound;
headerBtn.OnMouseOver = new UnityEvent();
headerBtn.OnMouseOut = new UnityEvent();
@@ -163,11 +202,11 @@ public static class RoleSettingMenuPatches
{
RoleGroupHidden[group] = !value;
}
- foreach (var header in CategoryHeaderEditRoles)
+ foreach (var header in Headers)
{
- header.gameObject.Destroy();
+ header.Destroy();
}
- CategoryHeaderEditRoles.Clear();
+ Headers.Clear();
foreach (var option in RoleOptionSettings)
{
option.gameObject.Destroy();
@@ -177,7 +216,7 @@ public static class RoleSettingMenuPatches
}));
headerBtn.SetButtonEnableState(true);
- if (!RoleGroupHidden[group])
+ if (RoleGroupHidden.TryGetValue(group, out var value) && !value)
{
ScrollerNum -= 0.4f;
}
@@ -188,7 +227,7 @@ public static class RoleSettingMenuPatches
private static void SetScrollBounds(this Scroller scroller)
{
- scroller.CalculateAndSetYBounds(1 + 1.5f * CategoryHeaderEditRoles.Count + RoleOptionSettings.Count, 1f, 6f, 0.43f);
+ scroller.CalculateAndSetYBounds(1 + 1.5f * Headers.Count + RoleOptionSettings.Count, 1f, 6f, 0.43f);
}
[HarmonyPrefix]
@@ -255,14 +294,15 @@ public static class RoleSettingMenuPatches
private static void CreateAdvancedSettings(RolesSettingsMenu __instance, RoleBehaviour role)
{
- foreach (var option in __instance.advancedSettingChildren)
+ foreach (var optBehaviour in __instance.AdvancedRolesSettings.GetComponentsInChildren())
{
- Object.Destroy(option.gameObject);
+ optBehaviour.gameObject.DestroyImmediate();
}
__instance.advancedSettingChildren.Clear();
- var num = -0.872f;
+ var hasImage = role.RoleScreenshot != null;
+ var num = hasImage ? -0.872f : -1;
var filteredOptions = GameSettingMenuPatches.SelectedMod?.Options.Where(x => x.AdvancedRole == role.GetType()) ?? [];
@@ -279,7 +319,7 @@ public static class RoleSettingMenuPatches
__instance.stringOptionOrigin,
__instance.AdvancedRolesSettings.transform);
- newOpt.transform.localPosition = new Vector3(2.17f, num, -2f);
+ newOpt.transform.localPosition = new Vector3(hasImage ? 2.17f : 1.1f, num, -2f);
newOpt.SetClickMask(__instance.ButtonClickMask);
SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren(true);
@@ -317,15 +357,63 @@ public static class RoleSettingMenuPatches
__instance.roleTitleText.text = TranslationController.Instance.GetString(
role.StringName,
new Il2CppReferenceArray(0));
- __instance.roleScreenshot.sprite = Sprite.Create(
- customRole.Configuration.OptionsScreenshot.LoadAsset().texture,
- new Rect(0, 0, 370, 230),
- Vector2.one / 2,
- 100);
- __instance.roleScreenshot.drawMode = SpriteDrawMode.Sliced;
+
+ var imgBg = __instance.AdvancedRolesSettings.transform.FindChild("Imagebackground");
+ var labelBg = __instance.AdvancedRolesSettings.transform.FindChild("InfoLabelBackground");
+ if (role.RoleScreenshot == null)
+ {
+ imgBg.gameObject.SetActive(false);
+ __instance.roleScreenshot.gameObject.SetActive(false);
+ __instance.roleDescriptionText.transform.parent.localPosition = new Vector3(1.5f, -0.2731f, -1);
+ __instance.roleDescriptionText.transform.parent.localScale = new Vector3(0.09f, 0.2f, 0.5687f);
+ labelBg.localPosition = new Vector3(-0.7f, 0.1054f, -2.5f);
+ }
+ else
+ {
+ imgBg.gameObject.SetActive(true);
+ __instance.roleScreenshot.gameObject.SetActive(true);
+ __instance.roleDescriptionText.transform.parent.localPosition = new Vector3(2.5176f, -0.2731f, -1f);
+ __instance.roleDescriptionText.transform.parent.localScale = new Vector3(0.0675f, 0.1494f, 0.5687f);
+ labelBg.transform.localPosition = new Vector3(1.082f, 0.1054f, -2.5f);
+
+ __instance.roleScreenshot.sprite = Sprite.Create(
+ role.RoleScreenshot.texture,
+ new Rect(0, 0, 370, 230),
+ Vector2.one / 2,
+ 100);
+ __instance.roleScreenshot.drawMode = SpriteDrawMode.Sliced;
+ }
+
__instance.roleHeaderSprite.color = customRole.RoleColor;
__instance.roleHeaderText.color = customRole.RoleColor.GetAlternateColor();
+ var categoryHeaderMasked = __instance.AdvancedRolesSettings.transform.Find("CategoryHeaderMasked").GetComponent();
+
+ if (categoryHeaderMasked.Title.gameObject.TryGetComponent(out var comp))
+ {
+ comp.Destroy();
+ }
+
+ categoryHeaderMasked.Title.text = "RETURN TO ROLE SETTINGS";
+
+ if (!categoryHeaderMasked.gameObject.TryGetComponent(out _))
+ {
+ var boxCol = categoryHeaderMasked.gameObject.AddComponent();
+ boxCol.size = new Vector2(7, 0.7f);
+ boxCol.offset = new Vector2(1.5f, -0.3f);
+
+ var headerBtn = categoryHeaderMasked.gameObject.AddComponent();
+ headerBtn.ClickSound = __instance.BackButton.GetComponent().ClickSound;
+ headerBtn.OnMouseOver = new UnityEvent();
+ headerBtn.OnMouseOut = new UnityEvent();
+ headerBtn.OnClick.AddListener(
+ (UnityAction)(() =>
+ {
+ __instance.OpenChancesTab();
+ }));
+ headerBtn.SetButtonEnableState(true);
+ }
+
var bg = __instance.AdvancedRolesSettings.transform.Find("Background");
bg.localPosition = new Vector3(1.4041f, -7.08f, 0);
bg.GetComponent().size = new Vector2(89.4628f, 100);
@@ -369,7 +457,7 @@ public static class RoleSettingMenuPatches
Vector3.zero,
Quaternion.identity,
__instance.RoleChancesSettings.transform);
- roleOptionSetting.transform.localPosition = new Vector3(-0.15f, ScrollerNum, -2f);
+ roleOptionSetting.transform.localPosition = new Vector3(-0.1f, ScrollerNum, -2f);
roleOptionSetting.SetRole(GameOptionsManager.Instance.CurrentGameOptions.RoleOptions, role, 20);
roleOptionSetting.labelSprite.color = customRole.RoleColor;
diff --git a/MiraAPI/Roles/CustomRoleConfiguration.cs b/MiraAPI/Roles/CustomRoleConfiguration.cs
index 4500331..efaf444 100644
--- a/MiraAPI/Roles/CustomRoleConfiguration.cs
+++ b/MiraAPI/Roles/CustomRoleConfiguration.cs
@@ -32,6 +32,7 @@ public record struct CustomRoleConfiguration
TasksCountForProgress = role.Team is ModdedRoleTeams.Crewmate;
HideSettings = roleBehaviour?.IsDead == true;
ShowInFreeplay = roleBehaviour?.IsDead == false;
+ OptionsScreenshot = null;
}
///
@@ -57,7 +58,7 @@ public record struct CustomRoleConfiguration
///
/// Gets or sets the Sprite used for the Role Options menu screenshot.
///
- public LoadableAsset OptionsScreenshot { get; set; } = MiraAssets.Empty;
+ public LoadableAsset? OptionsScreenshot { get; set; }
///
/// Gets or sets the Sprite used for the Role Icon.
diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs
index cc79c02..c31e922 100644
--- a/MiraAPI/Roles/CustomRoleManager.cs
+++ b/MiraAPI/Roles/CustomRoleManager.cs
@@ -91,7 +91,7 @@ public static class CustomRoleManager
roleBehaviour.CanVent = customRole.Configuration.CanUseVent;
roleBehaviour.DefaultGhostRole = customRole.Configuration.GhostRole;
roleBehaviour.MaxCount = customRole.Configuration.MaxRoleCount;
- roleBehaviour.RoleScreenshot = customRole.Configuration.OptionsScreenshot.LoadAsset();
+ roleBehaviour.RoleScreenshot = customRole.Configuration.OptionsScreenshot?.LoadAsset();
if (roleBehaviour.IsDead)
{
From dda46e590320a2d489ff9b8155b55e9327354446 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 15:45:31 -0500
Subject: [PATCH 11/71] i dont know why this is even a bug
---
MiraAPI/Patches/Roles/TaskAdderPatch.cs | 29 ++++++++++++++-----------
1 file changed, 16 insertions(+), 13 deletions(-)
diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
index b6516bd..6de6649 100644
--- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs
+++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
@@ -138,7 +138,7 @@ public static class TaskAdderPatch
// yes it might be crazy patching the entire method, but i tried so many other methods and only this works :cry:
[HarmonyPrefix]
[HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.ShowFolder))]
- public static bool ShowPatch(TaskAdderGame __instance, [HarmonyArgument(0)] TaskFolder taskFolder)
+ public static bool ShowPatch(TaskAdderGame __instance, TaskFolder taskFolder)
{
var stringBuilder = new Il2CppSystem.Text.StringBuilder(64);
__instance.Hierarchy.Add(taskFolder);
@@ -284,21 +284,24 @@ public static class TaskAdderPatch
}
}
- var split = taskFolder.name.Split(':');
- if (split.Length == 2)
+ if (taskFolder)
{
- var pluginId = split[0];
- var folderName = split[1];
-
- if (MiraPluginManager.GetPluginByGuid(pluginId) is { } plugin)
+ var split = taskFolder.name.Split(':');
+ if (split.Length == 2)
{
- if (folderName == "Roles(Clone)")
+ var pluginId = split[0];
+ var folderName = split[1];
+
+ if (MiraPluginManager.GetPluginByGuid(pluginId) is { } plugin)
{
- PluginRoles(__instance, plugin, ref num, ref num2, ref num3);
- }
- else if (folderName == "Modifiers(Clone)")
- {
- PluginModifiers(__instance, plugin, ref num, ref num2, ref num3);
+ if (folderName == "Roles(Clone)")
+ {
+ PluginRoles(__instance, plugin, ref num, ref num2, ref num3);
+ }
+ else if (folderName == "Modifiers(Clone)")
+ {
+ PluginModifiers(__instance, plugin, ref num, ref num2, ref num3);
+ }
}
}
}
From 2af89a678ff89f9206bc85c51fc6364d355d5508 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 16:34:59 -0500
Subject: [PATCH 12/71] add masking to task adder roles
---
MiraAPI/Patches/Roles/TaskAdderPatch.cs | 5 +++++
MiraAPI/Utilities/Extensions.cs | 10 ++++++++++
2 files changed, 15 insertions(+)
diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
index 6de6649..b15e9cf 100644
--- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs
+++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
@@ -5,6 +5,7 @@ using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using System;
using System.Linq;
+using MiraAPI.Utilities;
using TMPro;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -261,6 +262,7 @@ public static class TaskAdderPatch
var taskAddButton2 = Object.Instantiate(__instance.RoleButton);
taskAddButton2.SafePositionWorld = __instance.SafePositionWorld;
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
+ taskAddButton2.Text.fontMaterial.EnableMasking();
__instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
taskAddButton2.Role = roleBehaviour;
@@ -350,6 +352,7 @@ public static class TaskAdderPatch
taskAddButton.MyTask = null;
taskAddButton.SafePositionWorld = instance.SafePositionWorld;
taskAddButton.Text.text = modifier.ModifierName;
+ taskAddButton.Text.fontMaterial.EnableMasking();
instance.AddFileAsChildCustom(taskAddButton, ref num, ref num2, ref num3);
ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button);
@@ -381,6 +384,8 @@ public static class TaskAdderPatch
var taskAddButton2 = Object.Instantiate(instance.RoleButton);
taskAddButton2.SafePositionWorld = instance.SafePositionWorld;
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
+ taskAddButton2.Text.fontMaterial.EnableMasking();
+
instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
taskAddButton2.Role = roleBehaviour;
if (taskAddButton2.Button == null)
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index dc3bba1..97bd8c9 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -39,6 +39,16 @@ public static class Extensions
BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value)));
}
+ ///
+ /// Sets masking on a TMP MATERIAL! Do not use this on other materials.
+ ///
+ /// The TMP material.
+ public static void EnableMasking(this Material material)
+ {
+ material.SetFloat(ShaderID.Get("_Stencil"), 1);
+ material.SetFloat(ShaderID.Get("_StencilComp"), 4);
+ }
+
///
/// Checks if a type is static.
///
From 6775294a1995ff85a311fa305b037f16ea30b273 Mon Sep 17 00:00:00 2001
From: Pietrodjaowjao
Date: Sun, 23 Feb 2025 20:22:11 -0300
Subject: [PATCH 13/71] Change util to TMP_Text (all textmeshpro render
components)
---
MiraAPI/Patches/Roles/TaskAdderPatch.cs | 6 +++---
MiraAPI/Utilities/Extensions.cs | 9 +++++----
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
index b15e9cf..2135dc5 100644
--- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs
+++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
@@ -262,7 +262,7 @@ public static class TaskAdderPatch
var taskAddButton2 = Object.Instantiate(__instance.RoleButton);
taskAddButton2.SafePositionWorld = __instance.SafePositionWorld;
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
- taskAddButton2.Text.fontMaterial.EnableMasking();
+ taskAddButton2.Text.EnableMasking();
__instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
taskAddButton2.Role = roleBehaviour;
@@ -352,7 +352,7 @@ public static class TaskAdderPatch
taskAddButton.MyTask = null;
taskAddButton.SafePositionWorld = instance.SafePositionWorld;
taskAddButton.Text.text = modifier.ModifierName;
- taskAddButton.Text.fontMaterial.EnableMasking();
+ taskAddButton.Text.EnableMasking();
instance.AddFileAsChildCustom(taskAddButton, ref num, ref num2, ref num3);
ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button);
@@ -384,7 +384,7 @@ public static class TaskAdderPatch
var taskAddButton2 = Object.Instantiate(instance.RoleButton);
taskAddButton2.SafePositionWorld = instance.SafePositionWorld;
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
- taskAddButton2.Text.fontMaterial.EnableMasking();
+ taskAddButton2.Text.EnableMasking();
instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
taskAddButton2.Role = roleBehaviour;
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index 97bd8c9..a89bfd3 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -8,6 +8,7 @@ using Reactor.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
+using TMPro;
using UnityEngine;
namespace MiraAPI.Utilities;
@@ -42,11 +43,11 @@ public static class Extensions
///
/// Sets masking on a TMP MATERIAL! Do not use this on other materials.
///
- /// The TMP material.
- public static void EnableMasking(this Material material)
+ /// The TMP text.
+ public static void EnableMasking(this TMP_Text text)
{
- material.SetFloat(ShaderID.Get("_Stencil"), 1);
- material.SetFloat(ShaderID.Get("_StencilComp"), 4);
+ text.fontMaterial.SetFloat(ShaderID.Get("_Stencil"), 1);
+ text.fontMaterial.SetFloat(ShaderID.Get("_StencilComp"), 4);
}
///
From 975e8cb9b77500578765f0216df733a262babe35 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 18:24:56 -0500
Subject: [PATCH 14/71] allow stackable modifiers
---
MiraAPI/Modifiers/BaseModifier.cs | 5 +++++
MiraAPI/Modifiers/ModifierComponent.cs | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs
index 47cd491..dcea493 100644
--- a/MiraAPI/Modifiers/BaseModifier.cs
+++ b/MiraAPI/Modifiers/BaseModifier.cs
@@ -30,6 +30,11 @@ public abstract class BaseModifier
///
public virtual bool HideOnUi => false;
+ ///
+ /// Gets a value indicating whether the modifier can be added multiple times.
+ ///
+ public virtual bool Stackable => false;
+
///
/// Gets the HUD information for this modifier. Defaults to the modifier name. Does nothing if is true.
///
diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs
index 7db81f5..faffcc0 100644
--- a/MiraAPI/Modifiers/ModifierComponent.cs
+++ b/MiraAPI/Modifiers/ModifierComponent.cs
@@ -228,7 +228,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
return null;
}
- if (Modifiers.Find(x => x.ModifierId == modifierId) != null)
+ if (Modifiers.Find(x => x.ModifierId == modifierId && !x.Stackable) != null)
{
Logger.Error($"Player already has modifier with id {modifierId}!");
return null;
From 87187fe205f981312f3791fdd5d1ebbe539d9a63 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 18:25:25 -0500
Subject: [PATCH 15/71] improve modifier extensions and add more method for
dealing with modifiers
---
MiraAPI/Modifiers/ModifierComponent.cs | 44 ++++-
MiraAPI/Patches/PlayerControlPatches.cs | 2 +-
MiraAPI/Utilities/Extensions.cs | 134 +------------
MiraAPI/Utilities/ModifierExtensions.cs | 252 ++++++++++++++++++++++++
4 files changed, 297 insertions(+), 135 deletions(-)
create mode 100644 MiraAPI/Utilities/ModifierExtensions.cs
diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs
index faffcc0..9918eab 100644
--- a/MiraAPI/Modifiers/ModifierComponent.cs
+++ b/MiraAPI/Modifiers/ModifierComponent.cs
@@ -122,6 +122,38 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
}
}
+ ///
+ /// Gets a collection of modifiers by their type, or null if the player doesn't have one.
+ ///
+ /// The Type of the Modifier.
+ /// The Modifier if it is found, null otherwise.
+ public IEnumerable GetModifiersByType() where T : BaseModifier
+ {
+ return ActiveModifiers.OfType();
+ }
+
+ ///
+ /// Gets a modifier by its type, or null if the player doesn't have it.
+ ///
+ /// The Type of the Modifier.
+ /// The Modifier if it is found, null otherwise.
+ public T? GetModifier() where T : BaseModifier
+ {
+ return GetModifiersByType().FirstOrDefault();
+ }
+
+ ///
+ /// Tries to get a modifier by its type.
+ ///
+ /// The modifier or null.
+ /// The Type of the Modifier.
+ /// True if the modifier was found, false otherwise.
+ public bool TryGetModifier(out T? modifier) where T : BaseModifier
+ {
+ modifier = GetModifier();
+ return modifier != null;
+ }
+
///
/// Removes a modifier from the player.
///
@@ -260,19 +292,23 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
/// Checks if a player has an active or queued modifier by its ID.
///
/// The Modifier ID.
+ /// The predicate to check the modifier.
/// True if the Modifier is present, false otherwise.
- public bool HasModifier(uint id)
+ public bool HasModifier(uint id, Func? predicate=null)
{
- return ActiveModifiers.Exists(x => x.ModifierId == id) || _toAdd.Exists(x => x.ModifierId == id);
+ return ActiveModifiers.Exists(x => x.ModifierId == id && (predicate == null || predicate(x))) ||
+ _toAdd.Exists(x => x.ModifierId == id && (predicate == null || predicate(x)));
}
///
/// Checks if a player has an active or queued modifier by its type.
///
+ /// The predicate to check the modifier.
/// The Type of the Modifier.
/// True if the Modifier is present, false otherwise.
- public bool HasModifier()
+ public bool HasModifier(Func? predicate=null) where T : BaseModifier
{
- return ActiveModifiers.Exists(x => x is T) || _toAdd.Exists(x => x is T);
+ return ModifierManager.GetModifierId(typeof(T)) is { } id &&
+ HasModifier(id, (Func?)predicate);
}
}
diff --git a/MiraAPI/Patches/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs
index c7918fe..fbabe1a 100644
--- a/MiraAPI/Patches/PlayerControlPatches.cs
+++ b/MiraAPI/Patches/PlayerControlPatches.cs
@@ -126,6 +126,6 @@ public static class PlayerControlPatches
[HarmonyPatch(nameof(PlayerControl.OnDestroy))]
public static void PlayerControlOnDestroyPrefix(PlayerControl __instance)
{
- Utilities.Extensions.ModifierComponents.Remove(__instance);
+ Utilities.ModifierExtensions.ModifierComponents.Remove(__instance);
}
}
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index dc3bba1..f738558 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -130,30 +130,6 @@ public static class Extensions
opt => opt.OptionBehaviour && opt.OptionBehaviour == optionBehaviour);
}
- internal static Dictionary ModifierComponents { get; } = [];
-
- ///
- /// 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))
- {
- return component;
- }
-
- component = player.GetComponent();
- if (component == null)
- {
- return null;
- }
-
- ModifierComponents[player] = component;
- return component;
- }
-
///
/// Randomizes a list.
///
@@ -174,105 +150,6 @@ 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()!.HasModifier();
- }
-
- ///
- /// 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()!.HasModifier(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);
- }
-
- ///
- /// 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
- {
- var id = ModifierManager.GetModifierId(typeof(T));
-
- if (id == null)
- {
- Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
- return;
- }
-
- player.RpcRemoveModifier(id.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.
///
@@ -328,6 +205,7 @@ public static class Extensions
.GetNearestDeadBodies(playerControl.GetTruePosition(), radius, Helpers.CreateFilter(Constants.NotShipMask))
.Find(component => component && !component.Reported);
}
+
///
/// Finds the nearest object of a specified type to a player. It will only work if the object has a collider.
///
@@ -376,10 +254,6 @@ public static class Extensions
return predicate != null ? filteredPlayers.Find(predicate) : filteredPlayers.FirstOrDefault();
}
- private static readonly int _outline = Shader.PropertyToID("_Outline");
- private static readonly int _outlineColor = Shader.PropertyToID("_OutlineColor");
- private static readonly int _addColor = Shader.PropertyToID("_AddColor");
-
///
/// Fixed version of Reactor's SetOutline.
///
@@ -387,8 +261,8 @@ public static class Extensions
/// The outline color.
public static void UpdateOutline(this Renderer renderer, Color? color)
{
- renderer.material.SetFloat(_outline, color.HasValue ? 1 : 0);
- renderer.material.SetColor(_outlineColor, color.HasValue ? color.Value : Color.clear);
- renderer.material.SetColor(_addColor, color.HasValue ? color.Value : Color.clear);
+ renderer.material.SetFloat(ShaderID.Outline, color.HasValue ? 1 : 0);
+ renderer.material.SetColor(ShaderID.OutlineColor, color ?? Color.clear);
+ renderer.material.SetColor(ShaderID.AddColor, color ?? Color.clear);
}
}
diff --git a/MiraAPI/Utilities/ModifierExtensions.cs b/MiraAPI/Utilities/ModifierExtensions.cs
new file mode 100644
index 0000000..dadb62c
--- /dev/null
+++ b/MiraAPI/Utilities/ModifierExtensions.cs
@@ -0,0 +1,252 @@
+using System;
+using System.Collections.Generic;
+using MiraAPI.Modifiers;
+using MiraAPI.Networking;
+using Reactor.Networking.Attributes;
+using Reactor.Utilities;
+
+namespace MiraAPI.Utilities;
+
+///
+/// Extensions for modifiers.
+///
+public static class ModifierExtensions
+{
+ ///
+ /// Gets the dictionary to cache ModifierComponents.
+ ///
+ public static Dictionary ModifierComponents { get; } = [];
+
+ ///
+ /// 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.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);
+ }
+
+ ///
+ /// 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.RemoveModifier(modifierId);
+ }
+
+ ///
+ /// 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
+ {
+ var id = ModifierManager.GetModifierId(typeof(T));
+ if (id == null)
+ {
+ Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
+ return;
+ }
+
+ player.RpcRemoveModifier(id.Value);
+ }
+
+ ///
+ /// 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))
+ {
+ return component;
+ }
+
+ component = player.GetComponent();
+ if (!component)
+ {
+ return null;
+ }
+
+ ModifierComponents[player] = component;
+ return component;
+ }
+
+ ///
+ /// Gets the ModifierComponent for a player.
+ ///
+ /// The PlayerControl object.
+ /// The ModifierComponent if it is found.
+ /// A ModifierComponent if there is one, null otherwise.
+ public static bool TryGetModifierComponent(this PlayerControl player, out ModifierComponent? component)
+ {
+ if (ModifierComponents.TryGetValue(player, out component))
+ {
+ return component;
+ }
+
+ component = player.GetComponent();
+ return component;
+ }
+
+ ///
+ /// Gets a collection of modifiers by their type, or null if the player doesn't have one.
+ ///
+ /// The PlayerControl object.
+ /// The Type of the Modifier.
+ /// The Modifier if it is found, null otherwise.
+ public static IEnumerable GetModifiersByType(this PlayerControl player) where T : BaseModifier
+ {
+ return player.GetModifierComponent()!.GetModifiersByType();
+ }
+
+ ///
+ /// 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()!.GetModifier();
+ }
+
+ ///
+ /// Tries to get a modifier by its type.
+ ///
+ /// The PlayerControl object.
+ /// The modifier or null.
+ /// The Type of the Modifier.
+ /// True if the modifier was found, false otherwise.
+ public static bool TryGetModifier(this PlayerControl player, out T? modifier) where T : BaseModifier
+ {
+ return player.GetModifierComponent()!.TryGetModifier(out modifier);
+ }
+
+ ///
+ /// Removes a modifier from the player.
+ ///
+ /// The PlayerControl object.
+ /// The modifier type.
+ public static void RemoveModifier(this PlayerControl player, Type type)
+ {
+ player.GetModifierComponent()!.RemoveModifier(type);
+ }
+
+ ///
+ /// Removes a modifier from the player.
+ ///
+ /// The PlayerControl object.
+ /// The modifier type.
+ public static void RemoveModifier(this PlayerControl player) where T : BaseModifier
+ {
+ player.GetModifierComponent()!.RemoveModifier();
+ }
+
+ ///
+ /// Removes a modifier from the player.
+ ///
+ /// The PlayerControl object.
+ /// The modifier ID.
+ public static void RemoveModifier(this PlayerControl player, uint modifierId)
+ {
+ player.GetModifierComponent()!.RemoveModifier(modifierId);
+ }
+
+ ///
+ /// Removes a modifier from the player.
+ ///
+ /// The PlayerControl object.
+ /// The modifier object.
+ public static void RemoveModifier(this PlayerControl player, BaseModifier modifier)
+ {
+ player.GetModifierComponent()!.RemoveModifier(modifier);
+ }
+
+ ///
+ /// Adds a modifier to the player.
+ ///
+ /// The PlayerControl object.
+ /// The modifier to add.
+ /// The modifier that was added.
+ public static BaseModifier? AddModifier(this PlayerControl player, BaseModifier modifier)
+ {
+ return player.GetModifierComponent()!.AddModifier(modifier);
+ }
+
+ ///
+ /// Adds a modifier to the player.
+ ///
+ /// The PlayerControl object.
+ /// The modifier type.
+ /// The modifier that was added.
+ public static BaseModifier? AddModifier(this PlayerControl player, Type type)
+ {
+ return player.GetModifierComponent()!.AddModifier(type);
+ }
+
+ ///
+ /// Adds a modifier to the player.
+ ///
+ /// The PlayerControl object.
+ /// The Type of the modifier.
+ /// The new modifier.
+ public static T? AddModifier(this PlayerControl player) where T : BaseModifier
+ {
+ return player.GetModifierComponent()!.AddModifier();
+ }
+
+ ///
+ /// Checks if a player has an active or queued modifier by its ID.
+ ///
+ /// The PlayerControl object.
+ /// The Modifier ID.
+ /// The predicate to check the modifier.
+ /// True if the Modifier is present, false otherwise.
+ public static bool HasModifier(this PlayerControl player, uint id, Func? predicate=null)
+ {
+ return player.GetModifierComponent()!.HasModifier(id, predicate);
+ }
+
+ ///
+ /// Checks if a player has an active or queued modifier by its type.
+ ///
+ /// The PlayerControl object.
+ /// The predicate to check the modifier.
+ /// The Type of the Modifier.
+ /// True if the Modifier is present, false otherwise.
+ public static bool HasModifier(this PlayerControl player, Func? predicate=null) where T : BaseModifier
+ {
+ return player.GetModifierComponent()!.HasModifier(predicate);
+ }
+}
From 055b097c2165ad1dac9b1de77cfb1590e196eece Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 18:32:23 -0500
Subject: [PATCH 16/71] better summary comment
---
MiraAPI/Utilities/Extensions.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index a89bfd3..9d8367c 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -41,7 +41,7 @@ public static class Extensions
}
///
- /// Sets masking on a TMP MATERIAL! Do not use this on other materials.
+ /// Enables Among Us style masking on a TMP text object.
///
/// The TMP text.
public static void EnableMasking(this TMP_Text text)
From 9cc9a137cccdfe55364eb94fe5be46b35ea3623c Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 18:54:51 -0500
Subject: [PATCH 17/71] fix pietros mistake and cleanup things
---
MiraAPI/Patches/Roles/TaskAddButtonPatches.cs | 40 +++++-------
...{TaskAdderPatch.cs => TaskAdderPatches.cs} | 63 +++++++++----------
MiraAPI/Utilities/Extensions.cs | 2 +-
3 files changed, 46 insertions(+), 59 deletions(-)
rename MiraAPI/Patches/Roles/{TaskAdderPatch.cs => TaskAdderPatches.cs} (91%)
diff --git a/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs b/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
index c9e3e8b..510d5e9 100644
--- a/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
+++ b/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
@@ -1,43 +1,35 @@
using HarmonyLib;
-using MiraAPI.Modifiers;
+using MiraAPI.Roles;
using MiraAPI.Utilities;
-using UnityEngine.UI;
+using UnityEngine;
namespace MiraAPI.Patches.Roles;
[HarmonyPatch(typeof(TaskAddButton))]
-public static class TaskAddButtonPatches
+internal static class TaskAddButtonPatches
{
[HarmonyPrefix]
[HarmonyPatch(nameof(TaskAddButton.Start))]
public static bool StartPrefix(TaskAddButton __instance)
{
+ // if this becomes problematic in the future, find a new method.
if (uint.TryParse(__instance.name, out var result))
{
- __instance.Overlay.enabled = false;
- if (PlayerControl.LocalPlayer.HasModifier(result))
- {
- __instance.Overlay.enabled = true;
- __instance.Overlay.sprite = __instance.CheckImage;
- }
-
- __instance.Button.OnClick = new Button.ButtonClickedEvent();
- __instance.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() =>
- {
- if (PlayerControl.LocalPlayer.HasModifier(result))
- {
- PlayerControl.LocalPlayer.GetModifierComponent()!.RemoveModifier(result);
- __instance.Overlay.enabled = false;
- }
- else
- {
- PlayerControl.LocalPlayer.GetModifierComponent()!.AddModifier(ModifierManager.GetModifierType(result)!);
- __instance.Overlay.enabled = true;
- }
- }));
+ __instance.Overlay.sprite = __instance.CheckImage;
+ __instance.Overlay.enabled = PlayerControl.LocalPlayer.HasModifier(result);
return false;
}
return true;
}
+
+ [HarmonyPostfix]
+ [HarmonyPatch(nameof(TaskAddButton.Role), MethodType.Setter)]
+ public static void RoleGetterPatch(TaskAddButton __instance)
+ {
+ if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Custom } customRole)
+ {
+ __instance.FileImage.color = customRole.IntroConfiguration?.IntroTeamColor ?? Color.gray;
+ }
+ }
}
diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatches.cs
similarity index 91%
rename from MiraAPI/Patches/Roles/TaskAdderPatch.cs
rename to MiraAPI/Patches/Roles/TaskAdderPatches.cs
index 2135dc5..37f9744 100644
--- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs
+++ b/MiraAPI/Patches/Roles/TaskAdderPatches.cs
@@ -1,31 +1,27 @@
-using AmongUs.GameOptions;
+using System;
+using System.Globalization;
+using System.Linq;
+using AmongUs.GameOptions;
using HarmonyLib;
+using MiraAPI.Modifiers;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
-using MiraAPI.Utilities.Assets;
-using System;
-using System.Linq;
using MiraAPI.Utilities;
+using MiraAPI.Utilities.Assets;
using TMPro;
using UnityEngine;
+using UnityEngine.UI;
using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Roles;
-///
-/// Patch TaskAdder for organizing Roles and adding scroller.
-///
-[HarmonyPatch]
-public static class TaskAdderPatch
+[HarmonyPatch(typeof(TaskAdderGame))]
+internal static class TaskAdderPatches
{
private static Scroller? _scroller;
- ///
- /// Adds custom roles to the TaskAdder.
- ///
- /// TaskAdder instance.
[HarmonyPostfix]
- [HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.Begin))]
+ [HarmonyPatch(nameof(TaskAdderGame.Begin))]
public static void AddRolesFolder(TaskAdderGame __instance)
{
GameObject inner = new("Inner");
@@ -98,22 +94,6 @@ public static class TaskAdderPatch
__instance.GoToRoot();
}
- ///
- /// Sets the color of the role button based on the role.
- ///
- /// TaskAddButton instance.
- [HarmonyPostfix]
- [HarmonyPatch(typeof(TaskAddButton), nameof(TaskAddButton.Role), MethodType.Setter)]
- public static void RoleGetterPatch(TaskAddButton __instance)
- {
- if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Custom } customRole)
- {
- __instance.FileImage.color = customRole.IntroConfiguration?.IntroTeamColor ?? Color.gray;
- }
-
- __instance.RolloverHandler.OutColor = __instance.FileImage.color;
- }
-
private static void AddFileAsChildCustom(
this TaskAdderGame instance,
TaskAddButton item,
@@ -138,7 +118,7 @@ public static class TaskAdderPatch
// yes it might be crazy patching the entire method, but i tried so many other methods and only this works :cry:
[HarmonyPrefix]
- [HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.ShowFolder))]
+ [HarmonyPatch(nameof(TaskAdderGame.ShowFolder))]
public static bool ShowPatch(TaskAdderGame __instance, TaskFolder taskFolder)
{
var stringBuilder = new Il2CppSystem.Text.StringBuilder(64);
@@ -262,7 +242,6 @@ public static class TaskAdderPatch
var taskAddButton2 = Object.Instantiate(__instance.RoleButton);
taskAddButton2.SafePositionWorld = __instance.SafePositionWorld;
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
- taskAddButton2.Text.EnableMasking();
__instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
taskAddButton2.Role = roleBehaviour;
@@ -310,7 +289,7 @@ public static class TaskAdderPatch
foreach (var chip in __instance.ActiveItems)
{
- chip.GetComponentInChildren().EnableMasking();
+ chip.GetComponentInChildren().EnableStencilMasking();
chip.GetComponent().maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
}
@@ -347,12 +326,28 @@ public static class TaskAdderPatch
}
var taskAddButton = Object.Instantiate(instance.RoleButton);
- taskAddButton.name = modifier.ModifierId.ToString();
+ taskAddButton.name = modifier.ModifierId.ToString(CultureInfo.InvariantCulture);
taskAddButton.role = null;
taskAddButton.MyTask = null;
taskAddButton.SafePositionWorld = instance.SafePositionWorld;
taskAddButton.Text.text = modifier.ModifierName;
taskAddButton.Text.EnableMasking();
+
+ taskAddButton.Button.OnClick = new Button.ButtonClickedEvent();
+ taskAddButton.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() =>
+ {
+ if (PlayerControl.LocalPlayer.HasModifier(modifier.ModifierId))
+ {
+ PlayerControl.LocalPlayer.GetModifierComponent()!.RemoveModifier(modifier.ModifierId);
+ taskAddButton.Overlay.enabled = false;
+ }
+ else
+ {
+ PlayerControl.LocalPlayer.GetModifierComponent()!.AddModifier(ModifierManager.GetModifierType(modifier.ModifierId)!);
+ taskAddButton.Overlay.enabled = true;
+ }
+ }));
+
instance.AddFileAsChildCustom(taskAddButton, ref num, ref num2, ref num3);
ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button);
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index 9d8367c..89fc17b 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -44,7 +44,7 @@ public static class Extensions
/// Enables Among Us style masking on a TMP text object.
///
/// The TMP text.
- public static void EnableMasking(this TMP_Text text)
+ public static void EnableStencilMasking(this TMP_Text text)
{
text.fontMaterial.SetFloat(ShaderID.Get("_Stencil"), 1);
text.fontMaterial.SetFloat(ShaderID.Get("_StencilComp"), 4);
From 4849b52b47e34b60e5e363824746294dd45735e7 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 18:59:14 -0500
Subject: [PATCH 18/71] organize task add patches
---
MiraAPI/Patches/{Roles => Freeplay}/TaskAddButtonPatches.cs | 2 +-
MiraAPI/Patches/{Roles => Freeplay}/TaskAdderPatches.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
rename MiraAPI/Patches/{Roles => Freeplay}/TaskAddButtonPatches.cs (96%)
rename MiraAPI/Patches/{Roles => Freeplay}/TaskAdderPatches.cs (99%)
diff --git a/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
similarity index 96%
rename from MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
rename to MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
index 510d5e9..07b7f75 100644
--- a/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
+++ b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
@@ -3,7 +3,7 @@ using MiraAPI.Roles;
using MiraAPI.Utilities;
using UnityEngine;
-namespace MiraAPI.Patches.Roles;
+namespace MiraAPI.Patches.Freeplay;
[HarmonyPatch(typeof(TaskAddButton))]
internal static class TaskAddButtonPatches
diff --git a/MiraAPI/Patches/Roles/TaskAdderPatches.cs b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs
similarity index 99%
rename from MiraAPI/Patches/Roles/TaskAdderPatches.cs
rename to MiraAPI/Patches/Freeplay/TaskAdderPatches.cs
index 37f9744..e36ac61 100644
--- a/MiraAPI/Patches/Roles/TaskAdderPatches.cs
+++ b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs
@@ -13,7 +13,7 @@ using UnityEngine;
using UnityEngine.UI;
using Object = UnityEngine.Object;
-namespace MiraAPI.Patches.Roles;
+namespace MiraAPI.Patches.Freeplay;
[HarmonyPatch(typeof(TaskAdderGame))]
internal static class TaskAdderPatches
From 8e0a8f0585a0f3fc29f26c853341cc742c97ba0c Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 19:02:22 -0500
Subject: [PATCH 19/71] fix something i almost broke
---
MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
index 07b7f75..fb57d70 100644
--- a/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
+++ b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
@@ -31,5 +31,6 @@ internal static class TaskAddButtonPatches
{
__instance.FileImage.color = customRole.IntroConfiguration?.IntroTeamColor ?? Color.gray;
}
+ __instance.RolloverHandler.OutColor = __instance.FileImage.color;
}
}
From fb5ff4b53e997fd1f89bcdb10b96f0c48fd00ee4 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sat, 22 Feb 2025 18:54:20 -0500
Subject: [PATCH 20/71] freeplay modifiers
---
MiraAPI.Example/Modifiers/CaptainModifier.cs | 3 +-
.../Modifiers/Freezer/FreezeModifier.cs | 1 -
.../Modifiers/HighPriorityModifier.cs | 3 +-
.../OptionTypes/ModdedEnumOption.cs | 1 -
MiraAPI/Modifiers/BaseModifier.cs | 5 +
MiraAPI/Modifiers/ModifierManager.cs | 7 +-
MiraAPI/Modifiers/Types/GameModifier.cs | 3 +
MiraAPI/Patches/Roles/TaskAddButtonPatches.cs | 43 ++++++
MiraAPI/Patches/Roles/TaskAdderPatch.cs | 141 +++++++++++++-----
MiraAPI/PluginLoading/MiraPluginInfo.cs | 3 +
MiraAPI/PluginLoading/MiraPluginManager.cs | 10 +-
MiraAPI/Roles/ICustomRole.cs | 3 +-
12 files changed, 170 insertions(+), 53 deletions(-)
create mode 100644 MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
diff --git a/MiraAPI.Example/Modifiers/CaptainModifier.cs b/MiraAPI.Example/Modifiers/CaptainModifier.cs
index 60691a8..63b7c41 100644
--- a/MiraAPI.Example/Modifiers/CaptainModifier.cs
+++ b/MiraAPI.Example/Modifiers/CaptainModifier.cs
@@ -1,5 +1,4 @@
-using MiraAPI.Modifiers;
-using MiraAPI.Modifiers.Types;
+using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Modifiers;
diff --git a/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs b/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs
index 642e941..12a9587 100644
--- a/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs
+++ b/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs
@@ -1,6 +1,5 @@
using Il2CppSystem;
using MiraAPI.Example.Roles;
-using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
using UnityEngine;
diff --git a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs
index b94c4d0..c640ed8 100644
--- a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs
+++ b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs
@@ -1,5 +1,4 @@
-using MiraAPI.Modifiers;
-using MiraAPI.Modifiers.Types;
+using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Modifiers;
diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
index e917cdc..80bd497 100644
--- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
+++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
@@ -1,5 +1,4 @@
using System;
-using System.Globalization;
using System.Linq;
using System.Text;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs
index dcea493..67f7410 100644
--- a/MiraAPI/Modifiers/BaseModifier.cs
+++ b/MiraAPI/Modifiers/BaseModifier.cs
@@ -30,6 +30,11 @@ public abstract class BaseModifier
///
public virtual bool HideOnUi => false;
+ ///
+ /// Gets a value indicating whether the modifier is shown in the freeplay menu.
+ ///
+ public virtual bool ShowInFreeplay => false;
+
///
/// Gets a value indicating whether the modifier can be added multiple times.
///
diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs
index ddb3c87..d4d4004 100644
--- a/MiraAPI/Modifiers/ModifierManager.cs
+++ b/MiraAPI/Modifiers/ModifierManager.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.Serialization;
using MiraAPI.Modifiers.Types;
using MiraAPI.Networking;
+using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using Reactor.Networking.Rpc;
@@ -49,7 +51,7 @@ public static class ModifierManager
return TypeToIdModifierMap.GetValueOrDefault(type);
}
- internal static bool RegisterModifier(Type modifierType)
+ internal static bool RegisterModifier(Type modifierType, MiraPluginInfo info)
{
if (!typeof(BaseModifier).IsAssignableFrom(modifierType))
{
@@ -58,6 +60,9 @@ public static class ModifierManager
IdToTypeModifierMap.Add(GetNextId(), modifierType);
TypeToIdModifierMap.Add(modifierType, _nextId);
+ var bm = (BaseModifier)FormatterServices.GetUninitializedObject(modifierType); // this probably isn't a great idea
+ bm.ModifierId = _nextId;
+ info.Modifiers.Add(bm);
if (!typeof(GameModifier).IsAssignableFrom(modifierType))
{
diff --git a/MiraAPI/Modifiers/Types/GameModifier.cs b/MiraAPI/Modifiers/Types/GameModifier.cs
index a7b1fe1..abcd1f7 100644
--- a/MiraAPI/Modifiers/Types/GameModifier.cs
+++ b/MiraAPI/Modifiers/Types/GameModifier.cs
@@ -5,6 +5,9 @@
///
public abstract class GameModifier : BaseModifier
{
+ ///
+ public override bool ShowInFreeplay => true;
+
///
/// Gets the chance of the modifier being assigned to a player.
///
diff --git a/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs b/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
new file mode 100644
index 0000000..c9e3e8b
--- /dev/null
+++ b/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
@@ -0,0 +1,43 @@
+using HarmonyLib;
+using MiraAPI.Modifiers;
+using MiraAPI.Utilities;
+using UnityEngine.UI;
+
+namespace MiraAPI.Patches.Roles;
+
+[HarmonyPatch(typeof(TaskAddButton))]
+public static class TaskAddButtonPatches
+{
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(TaskAddButton.Start))]
+ public static bool StartPrefix(TaskAddButton __instance)
+ {
+ if (uint.TryParse(__instance.name, out var result))
+ {
+ __instance.Overlay.enabled = false;
+ if (PlayerControl.LocalPlayer.HasModifier(result))
+ {
+ __instance.Overlay.enabled = true;
+ __instance.Overlay.sprite = __instance.CheckImage;
+ }
+
+ __instance.Button.OnClick = new Button.ButtonClickedEvent();
+ __instance.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() =>
+ {
+ if (PlayerControl.LocalPlayer.HasModifier(result))
+ {
+ PlayerControl.LocalPlayer.GetModifierComponent()!.RemoveModifier(result);
+ __instance.Overlay.enabled = false;
+ }
+ else
+ {
+ PlayerControl.LocalPlayer.GetModifierComponent()!.AddModifier(ModifierManager.GetModifierType(result)!);
+ __instance.Overlay.enabled = true;
+ }
+ }));
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
index 98e93bc..b6516bd 100644
--- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs
+++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
@@ -4,7 +4,6 @@ using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using System;
-using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
@@ -18,7 +17,6 @@ namespace MiraAPI.Patches.Roles;
[HarmonyPatch]
public static class TaskAdderPatch
{
- private static readonly Dictionary ModsFolders = [];
private static Scroller? _scroller;
///
@@ -67,25 +65,34 @@ public static class TaskAdderPatch
rolesFolder.FolderName = "Roles";
rolesFolder.name = "RolesFolder";
+ var modifiersFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
+ modifiersFolder.gameObject.SetActive(false);
+ modifiersFolder.FolderName = "Modifiers";
+ modifiersFolder.name = "ModifiersFolder";
+
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
{
- if (plugin.CustomRoles.Count == 0)
+ if (plugin.CustomRoles.Any(x => x.Value is ICustomRole { Configuration.ShowInFreeplay: true }))
{
- continue;
+ var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
+ newFolder.name = plugin.PluginId+":Roles";
+ newFolder.FolderName = plugin.PluginInfo.Metadata.Name;
+ newFolder.gameObject.SetActive(false);
+ rolesFolder.SubFolders.Add(newFolder);
}
- var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
- newFolder.FolderName = newFolder.name = plugin.PluginInfo.Metadata.Name;
- newFolder.gameObject.SetActive(false);
- rolesFolder.SubFolders.Add(newFolder);
-
- if (!ModsFolders.ContainsKey(plugin.PluginInfo.Metadata.Name))
+ if (plugin.Modifiers.Exists(x => x.ShowInFreeplay))
{
- ModsFolders.Add(plugin.PluginInfo.Metadata.Name, plugin.PluginId);
+ var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
+ newFolder.name = plugin.PluginId+":Modifiers";
+ newFolder.FolderName = plugin.PluginInfo.Metadata.Name;
+ newFolder.gameObject.SetActive(false);
+ modifiersFolder.SubFolders.Add(newFolder);
}
}
__instance.Root.SubFolders.Add(rolesFolder);
+ __instance.Root.SubFolders.Add(modifiersFolder);
__instance.GoToRoot();
}
@@ -277,39 +284,21 @@ public static class TaskAdderPatch
}
}
- if (ModsFolders.TryGetValue(taskFolder.FolderName, out var guid))
+ var split = taskFolder.name.Split(':');
+ if (split.Length == 2)
{
- var plugin = MiraPluginManager.GetPluginByGuid(guid);
- for (var m = 0; m < plugin.CustomRoles.Count; m++)
+ var pluginId = split[0];
+ var folderName = split[1];
+
+ if (MiraPluginManager.GetPluginByGuid(pluginId) is { } plugin)
{
- var roleBehaviour = plugin.CustomRoles.ElementAt(m).Value;
- if (roleBehaviour.Role == RoleTypes.ImpostorGhost || roleBehaviour.Role == RoleTypes.CrewmateGhost ||
- (roleBehaviour is ICustomRole customRole && !customRole.Configuration.ShowInFreeplay))
+ if (folderName == "Roles(Clone)")
{
- continue;
+ PluginRoles(__instance, plugin, ref num, ref num2, ref num3);
}
-
- var taskAddButton2 = Object.Instantiate(__instance.RoleButton);
- taskAddButton2.SafePositionWorld = __instance.SafePositionWorld;
- taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
- __instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
- taskAddButton2.Role = roleBehaviour;
- if (taskAddButton2.Button == null)
+ else if (folderName == "Modifiers(Clone)")
{
- continue;
- }
-
- ControllerManager.Instance.AddSelectableUiElement(taskAddButton2.Button);
- switch (m)
- {
- case 0 when __instance.restorePreviousSelectionFound != null:
- ControllerManager.Instance.SetDefaultSelection(__instance.restorePreviousSelectionFound);
- __instance.restorePreviousSelectionByFolderName = string.Empty;
- __instance.restorePreviousSelectionFound = null;
- break;
- case 0:
- ControllerManager.Instance.SetDefaultSelection(taskAddButton2.Button);
- break;
+ PluginModifiers(__instance, plugin, ref num, ref num2, ref num3);
}
}
}
@@ -336,4 +325,78 @@ public static class TaskAdderPatch
ControllerManager.Instance.SetBackButton(__instance.FolderBackButton);
return false;
}
+
+ private static void PluginModifiers(
+ TaskAdderGame instance,
+ MiraPluginInfo plugin,
+ ref float num,
+ ref float num2,
+ ref float num3)
+ {
+ for (var m = 0; m < plugin.Modifiers.Count; m++)
+ {
+ var modifier = plugin.Modifiers[m];
+ if (!modifier.ShowInFreeplay)
+ {
+ continue;
+ }
+
+ var taskAddButton = Object.Instantiate(instance.RoleButton);
+ taskAddButton.name = modifier.ModifierId.ToString();
+ taskAddButton.role = null;
+ taskAddButton.MyTask = null;
+ taskAddButton.SafePositionWorld = instance.SafePositionWorld;
+ taskAddButton.Text.text = modifier.ModifierName;
+ instance.AddFileAsChildCustom(taskAddButton, ref num, ref num2, ref num3);
+
+ ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button);
+ switch (m)
+ {
+ case 0 when instance.restorePreviousSelectionFound != null:
+ ControllerManager.Instance.SetDefaultSelection(instance.restorePreviousSelectionFound);
+ instance.restorePreviousSelectionByFolderName = string.Empty;
+ instance.restorePreviousSelectionFound = null;
+ break;
+ case 0:
+ ControllerManager.Instance.SetDefaultSelection(taskAddButton.Button);
+ break;
+ }
+ }
+ }
+
+ private static void PluginRoles(TaskAdderGame instance, MiraPluginInfo plugin, ref float num, ref float num2, ref float num3)
+ {
+ for (var m = 0; m < plugin.CustomRoles.Count; m++)
+ {
+ var roleBehaviour = plugin.CustomRoles.ElementAt(m).Value;
+ if (roleBehaviour.Role == RoleTypes.ImpostorGhost || roleBehaviour.Role == RoleTypes.CrewmateGhost ||
+ roleBehaviour is ICustomRole { Configuration.ShowInFreeplay: false })
+ {
+ continue;
+ }
+
+ var taskAddButton2 = Object.Instantiate(instance.RoleButton);
+ taskAddButton2.SafePositionWorld = instance.SafePositionWorld;
+ taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
+ instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
+ taskAddButton2.Role = roleBehaviour;
+ if (taskAddButton2.Button == null)
+ {
+ continue;
+ }
+
+ ControllerManager.Instance.AddSelectableUiElement(taskAddButton2.Button);
+ switch (m)
+ {
+ case 0 when instance.restorePreviousSelectionFound != null:
+ ControllerManager.Instance.SetDefaultSelection(instance.restorePreviousSelectionFound);
+ instance.restorePreviousSelectionByFolderName = string.Empty;
+ instance.restorePreviousSelectionFound = null;
+ break;
+ case 0:
+ ControllerManager.Instance.SetDefaultSelection(taskAddButton2.Button);
+ break;
+ }
+ }
+ }
}
diff --git a/MiraAPI/PluginLoading/MiraPluginInfo.cs b/MiraAPI/PluginLoading/MiraPluginInfo.cs
index 25a4dca..160980e 100644
--- a/MiraAPI/PluginLoading/MiraPluginInfo.cs
+++ b/MiraAPI/PluginLoading/MiraPluginInfo.cs
@@ -5,6 +5,7 @@ using MiraAPI.GameOptions;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MiraAPI.Hud;
+using MiraAPI.Modifiers;
namespace MiraAPI.PluginLoading;
@@ -61,6 +62,8 @@ public class MiraPluginInfo
internal List Options { get; } = [];
+ internal List Modifiers { get; } = [];
+
internal Dictionary CustomRoles { get; } = [];
internal Dictionary GameModes { get; } = [];
diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs
index 678dee4..a7ed480 100644
--- a/MiraAPI/PluginLoading/MiraPluginManager.cs
+++ b/MiraAPI/PluginLoading/MiraPluginManager.cs
@@ -48,7 +48,7 @@ public sealed class MiraPluginManager
continue;
}
- if (RegisterModifier(type))
+ if (RegisterModifier(type, info))
{
continue;
}
@@ -91,9 +91,9 @@ public sealed class MiraPluginManager
///
/// The plugin GUID.
/// A MiraPluginInfo.
- public static MiraPluginInfo GetPluginByGuid(string pluginId)
+ public static MiraPluginInfo? GetPluginByGuid(string pluginId)
{
- return Instance._registeredPlugins.Values.First(plugin => plugin.PluginId == pluginId);
+ return Instance._registeredPlugins.Values.FirstOrDefault(plugin => plugin.PluginId == pluginId);
}
private static bool RegisterOptions(Type type, MiraPluginInfo pluginInfo)
@@ -199,11 +199,11 @@ public sealed class MiraPluginManager
}
}
- private static bool RegisterModifier(Type type)
+ private static bool RegisterModifier(Type type, MiraPluginInfo info)
{
try
{
- return ModifierManager.RegisterModifier(type);
+ return ModifierManager.RegisterModifier(type, info);
}
catch (Exception e)
{
diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs
index 9afc407..a452d1e 100644
--- a/MiraAPI/Roles/ICustomRole.cs
+++ b/MiraAPI/Roles/ICustomRole.cs
@@ -1,5 +1,4 @@
-using System;
-using BepInEx.Configuration;
+using BepInEx.Configuration;
using MiraAPI.Modifiers;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities;
From 3905a3b39464c1561e8178f9f0ba33b07107a185 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sat, 22 Feb 2025 18:59:19 -0500
Subject: [PATCH 21/71] .
---
MiraAPI/PluginLoading/MiraPluginInfo.cs | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/MiraAPI/PluginLoading/MiraPluginInfo.cs b/MiraAPI/PluginLoading/MiraPluginInfo.cs
index 160980e..81412e8 100644
--- a/MiraAPI/PluginLoading/MiraPluginInfo.cs
+++ b/MiraAPI/PluginLoading/MiraPluginInfo.cs
@@ -22,6 +22,15 @@ public class MiraPluginInfo
PluginId = info.Metadata.GUID;
}
+ ///
+ /// Get a read only collection of this plugin's modifiers. This is probably not safe because they are uninitialized.
+ ///
+ /// Readonly collection of modifiers.
+ public IReadOnlyCollection GetModifiers()
+ {
+ return Modifiers.AsReadOnly();
+ }
+
///
/// Get a read only collection of this plugin's Option Groups.
///
From 84c8f743f8066f6165bcb6de71cc07007708a527 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 15:45:31 -0500
Subject: [PATCH 22/71] i dont know why this is even a bug
---
MiraAPI/Patches/Roles/TaskAdderPatch.cs | 29 ++++++++++++++-----------
1 file changed, 16 insertions(+), 13 deletions(-)
diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
index b6516bd..6de6649 100644
--- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs
+++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
@@ -138,7 +138,7 @@ public static class TaskAdderPatch
// yes it might be crazy patching the entire method, but i tried so many other methods and only this works :cry:
[HarmonyPrefix]
[HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.ShowFolder))]
- public static bool ShowPatch(TaskAdderGame __instance, [HarmonyArgument(0)] TaskFolder taskFolder)
+ public static bool ShowPatch(TaskAdderGame __instance, TaskFolder taskFolder)
{
var stringBuilder = new Il2CppSystem.Text.StringBuilder(64);
__instance.Hierarchy.Add(taskFolder);
@@ -284,21 +284,24 @@ public static class TaskAdderPatch
}
}
- var split = taskFolder.name.Split(':');
- if (split.Length == 2)
+ if (taskFolder)
{
- var pluginId = split[0];
- var folderName = split[1];
-
- if (MiraPluginManager.GetPluginByGuid(pluginId) is { } plugin)
+ var split = taskFolder.name.Split(':');
+ if (split.Length == 2)
{
- if (folderName == "Roles(Clone)")
+ var pluginId = split[0];
+ var folderName = split[1];
+
+ if (MiraPluginManager.GetPluginByGuid(pluginId) is { } plugin)
{
- PluginRoles(__instance, plugin, ref num, ref num2, ref num3);
- }
- else if (folderName == "Modifiers(Clone)")
- {
- PluginModifiers(__instance, plugin, ref num, ref num2, ref num3);
+ if (folderName == "Roles(Clone)")
+ {
+ PluginRoles(__instance, plugin, ref num, ref num2, ref num3);
+ }
+ else if (folderName == "Modifiers(Clone)")
+ {
+ PluginModifiers(__instance, plugin, ref num, ref num2, ref num3);
+ }
}
}
}
From 5dfbd5dc8f64a17d327e8a103ff8cee1796ff668 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 16:34:59 -0500
Subject: [PATCH 23/71] add masking to task adder roles
---
MiraAPI/Patches/Roles/TaskAdderPatch.cs | 5 +++++
MiraAPI/Utilities/Extensions.cs | 10 ++++++++++
2 files changed, 15 insertions(+)
diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
index 6de6649..b15e9cf 100644
--- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs
+++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
@@ -5,6 +5,7 @@ using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using System;
using System.Linq;
+using MiraAPI.Utilities;
using TMPro;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -261,6 +262,7 @@ public static class TaskAdderPatch
var taskAddButton2 = Object.Instantiate(__instance.RoleButton);
taskAddButton2.SafePositionWorld = __instance.SafePositionWorld;
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
+ taskAddButton2.Text.fontMaterial.EnableMasking();
__instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
taskAddButton2.Role = roleBehaviour;
@@ -350,6 +352,7 @@ public static class TaskAdderPatch
taskAddButton.MyTask = null;
taskAddButton.SafePositionWorld = instance.SafePositionWorld;
taskAddButton.Text.text = modifier.ModifierName;
+ taskAddButton.Text.fontMaterial.EnableMasking();
instance.AddFileAsChildCustom(taskAddButton, ref num, ref num2, ref num3);
ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button);
@@ -381,6 +384,8 @@ public static class TaskAdderPatch
var taskAddButton2 = Object.Instantiate(instance.RoleButton);
taskAddButton2.SafePositionWorld = instance.SafePositionWorld;
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
+ taskAddButton2.Text.fontMaterial.EnableMasking();
+
instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
taskAddButton2.Role = roleBehaviour;
if (taskAddButton2.Button == null)
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index f738558..4461ba6 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -39,6 +39,16 @@ public static class Extensions
BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value)));
}
+ ///
+ /// Sets masking on a TMP MATERIAL! Do not use this on other materials.
+ ///
+ /// The TMP material.
+ public static void EnableMasking(this Material material)
+ {
+ material.SetFloat(ShaderID.Get("_Stencil"), 1);
+ material.SetFloat(ShaderID.Get("_StencilComp"), 4);
+ }
+
///
/// Checks if a type is static.
///
From bc12c5a5f78aee91f836f2671b019b22a6655b78 Mon Sep 17 00:00:00 2001
From: Pietrodjaowjao
Date: Sun, 23 Feb 2025 20:22:11 -0300
Subject: [PATCH 24/71] Change util to TMP_Text (all textmeshpro render
components)
---
MiraAPI/Patches/Roles/TaskAdderPatch.cs | 6 +++---
MiraAPI/Utilities/Extensions.cs | 9 +++++----
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
index b15e9cf..2135dc5 100644
--- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs
+++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs
@@ -262,7 +262,7 @@ public static class TaskAdderPatch
var taskAddButton2 = Object.Instantiate(__instance.RoleButton);
taskAddButton2.SafePositionWorld = __instance.SafePositionWorld;
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
- taskAddButton2.Text.fontMaterial.EnableMasking();
+ taskAddButton2.Text.EnableMasking();
__instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
taskAddButton2.Role = roleBehaviour;
@@ -352,7 +352,7 @@ public static class TaskAdderPatch
taskAddButton.MyTask = null;
taskAddButton.SafePositionWorld = instance.SafePositionWorld;
taskAddButton.Text.text = modifier.ModifierName;
- taskAddButton.Text.fontMaterial.EnableMasking();
+ taskAddButton.Text.EnableMasking();
instance.AddFileAsChildCustom(taskAddButton, ref num, ref num2, ref num3);
ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button);
@@ -384,7 +384,7 @@ public static class TaskAdderPatch
var taskAddButton2 = Object.Instantiate(instance.RoleButton);
taskAddButton2.SafePositionWorld = instance.SafePositionWorld;
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
- taskAddButton2.Text.fontMaterial.EnableMasking();
+ taskAddButton2.Text.EnableMasking();
instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
taskAddButton2.Role = roleBehaviour;
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index 4461ba6..f9a7974 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -8,6 +8,7 @@ using Reactor.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
+using TMPro;
using UnityEngine;
namespace MiraAPI.Utilities;
@@ -42,11 +43,11 @@ public static class Extensions
///
/// Sets masking on a TMP MATERIAL! Do not use this on other materials.
///
- /// The TMP material.
- public static void EnableMasking(this Material material)
+ /// The TMP text.
+ public static void EnableMasking(this TMP_Text text)
{
- material.SetFloat(ShaderID.Get("_Stencil"), 1);
- material.SetFloat(ShaderID.Get("_StencilComp"), 4);
+ text.fontMaterial.SetFloat(ShaderID.Get("_Stencil"), 1);
+ text.fontMaterial.SetFloat(ShaderID.Get("_StencilComp"), 4);
}
///
From 9ccf7104cbbde1763fa0ebdacc3289b6afe547c7 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 18:32:23 -0500
Subject: [PATCH 25/71] better summary comment
---
MiraAPI/Utilities/Extensions.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index f9a7974..3879029 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -41,7 +41,7 @@ public static class Extensions
}
///
- /// Sets masking on a TMP MATERIAL! Do not use this on other materials.
+ /// Enables Among Us style masking on a TMP text object.
///
/// The TMP text.
public static void EnableMasking(this TMP_Text text)
From d8131d1f5d49b6f998d4472869f0db1228ea4ecd Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 18:54:51 -0500
Subject: [PATCH 26/71] fix pietros mistake and cleanup things
---
MiraAPI/Patches/Roles/TaskAddButtonPatches.cs | 40 +++++-------
...{TaskAdderPatch.cs => TaskAdderPatches.cs} | 63 +++++++++----------
MiraAPI/Utilities/Extensions.cs | 2 +-
3 files changed, 46 insertions(+), 59 deletions(-)
rename MiraAPI/Patches/Roles/{TaskAdderPatch.cs => TaskAdderPatches.cs} (91%)
diff --git a/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs b/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
index c9e3e8b..510d5e9 100644
--- a/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
+++ b/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
@@ -1,43 +1,35 @@
using HarmonyLib;
-using MiraAPI.Modifiers;
+using MiraAPI.Roles;
using MiraAPI.Utilities;
-using UnityEngine.UI;
+using UnityEngine;
namespace MiraAPI.Patches.Roles;
[HarmonyPatch(typeof(TaskAddButton))]
-public static class TaskAddButtonPatches
+internal static class TaskAddButtonPatches
{
[HarmonyPrefix]
[HarmonyPatch(nameof(TaskAddButton.Start))]
public static bool StartPrefix(TaskAddButton __instance)
{
+ // if this becomes problematic in the future, find a new method.
if (uint.TryParse(__instance.name, out var result))
{
- __instance.Overlay.enabled = false;
- if (PlayerControl.LocalPlayer.HasModifier(result))
- {
- __instance.Overlay.enabled = true;
- __instance.Overlay.sprite = __instance.CheckImage;
- }
-
- __instance.Button.OnClick = new Button.ButtonClickedEvent();
- __instance.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() =>
- {
- if (PlayerControl.LocalPlayer.HasModifier(result))
- {
- PlayerControl.LocalPlayer.GetModifierComponent()!.RemoveModifier(result);
- __instance.Overlay.enabled = false;
- }
- else
- {
- PlayerControl.LocalPlayer.GetModifierComponent()!.AddModifier(ModifierManager.GetModifierType(result)!);
- __instance.Overlay.enabled = true;
- }
- }));
+ __instance.Overlay.sprite = __instance.CheckImage;
+ __instance.Overlay.enabled = PlayerControl.LocalPlayer.HasModifier(result);
return false;
}
return true;
}
+
+ [HarmonyPostfix]
+ [HarmonyPatch(nameof(TaskAddButton.Role), MethodType.Setter)]
+ public static void RoleGetterPatch(TaskAddButton __instance)
+ {
+ if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Custom } customRole)
+ {
+ __instance.FileImage.color = customRole.IntroConfiguration?.IntroTeamColor ?? Color.gray;
+ }
+ }
}
diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatches.cs
similarity index 91%
rename from MiraAPI/Patches/Roles/TaskAdderPatch.cs
rename to MiraAPI/Patches/Roles/TaskAdderPatches.cs
index 2135dc5..37f9744 100644
--- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs
+++ b/MiraAPI/Patches/Roles/TaskAdderPatches.cs
@@ -1,31 +1,27 @@
-using AmongUs.GameOptions;
+using System;
+using System.Globalization;
+using System.Linq;
+using AmongUs.GameOptions;
using HarmonyLib;
+using MiraAPI.Modifiers;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
-using MiraAPI.Utilities.Assets;
-using System;
-using System.Linq;
using MiraAPI.Utilities;
+using MiraAPI.Utilities.Assets;
using TMPro;
using UnityEngine;
+using UnityEngine.UI;
using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Roles;
-///
-/// Patch TaskAdder for organizing Roles and adding scroller.
-///
-[HarmonyPatch]
-public static class TaskAdderPatch
+[HarmonyPatch(typeof(TaskAdderGame))]
+internal static class TaskAdderPatches
{
private static Scroller? _scroller;
- ///
- /// Adds custom roles to the TaskAdder.
- ///
- /// TaskAdder instance.
[HarmonyPostfix]
- [HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.Begin))]
+ [HarmonyPatch(nameof(TaskAdderGame.Begin))]
public static void AddRolesFolder(TaskAdderGame __instance)
{
GameObject inner = new("Inner");
@@ -98,22 +94,6 @@ public static class TaskAdderPatch
__instance.GoToRoot();
}
- ///
- /// Sets the color of the role button based on the role.
- ///
- /// TaskAddButton instance.
- [HarmonyPostfix]
- [HarmonyPatch(typeof(TaskAddButton), nameof(TaskAddButton.Role), MethodType.Setter)]
- public static void RoleGetterPatch(TaskAddButton __instance)
- {
- if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Custom } customRole)
- {
- __instance.FileImage.color = customRole.IntroConfiguration?.IntroTeamColor ?? Color.gray;
- }
-
- __instance.RolloverHandler.OutColor = __instance.FileImage.color;
- }
-
private static void AddFileAsChildCustom(
this TaskAdderGame instance,
TaskAddButton item,
@@ -138,7 +118,7 @@ public static class TaskAdderPatch
// yes it might be crazy patching the entire method, but i tried so many other methods and only this works :cry:
[HarmonyPrefix]
- [HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.ShowFolder))]
+ [HarmonyPatch(nameof(TaskAdderGame.ShowFolder))]
public static bool ShowPatch(TaskAdderGame __instance, TaskFolder taskFolder)
{
var stringBuilder = new Il2CppSystem.Text.StringBuilder(64);
@@ -262,7 +242,6 @@ public static class TaskAdderPatch
var taskAddButton2 = Object.Instantiate(__instance.RoleButton);
taskAddButton2.SafePositionWorld = __instance.SafePositionWorld;
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
- taskAddButton2.Text.EnableMasking();
__instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
taskAddButton2.Role = roleBehaviour;
@@ -310,7 +289,7 @@ public static class TaskAdderPatch
foreach (var chip in __instance.ActiveItems)
{
- chip.GetComponentInChildren().EnableMasking();
+ chip.GetComponentInChildren().EnableStencilMasking();
chip.GetComponent().maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
}
@@ -347,12 +326,28 @@ public static class TaskAdderPatch
}
var taskAddButton = Object.Instantiate(instance.RoleButton);
- taskAddButton.name = modifier.ModifierId.ToString();
+ taskAddButton.name = modifier.ModifierId.ToString(CultureInfo.InvariantCulture);
taskAddButton.role = null;
taskAddButton.MyTask = null;
taskAddButton.SafePositionWorld = instance.SafePositionWorld;
taskAddButton.Text.text = modifier.ModifierName;
taskAddButton.Text.EnableMasking();
+
+ taskAddButton.Button.OnClick = new Button.ButtonClickedEvent();
+ taskAddButton.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() =>
+ {
+ if (PlayerControl.LocalPlayer.HasModifier(modifier.ModifierId))
+ {
+ PlayerControl.LocalPlayer.GetModifierComponent()!.RemoveModifier(modifier.ModifierId);
+ taskAddButton.Overlay.enabled = false;
+ }
+ else
+ {
+ PlayerControl.LocalPlayer.GetModifierComponent()!.AddModifier(ModifierManager.GetModifierType(modifier.ModifierId)!);
+ taskAddButton.Overlay.enabled = true;
+ }
+ }));
+
instance.AddFileAsChildCustom(taskAddButton, ref num, ref num2, ref num3);
ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button);
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index 3879029..6d8cbf6 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -44,7 +44,7 @@ public static class Extensions
/// Enables Among Us style masking on a TMP text object.
///
/// The TMP text.
- public static void EnableMasking(this TMP_Text text)
+ public static void EnableStencilMasking(this TMP_Text text)
{
text.fontMaterial.SetFloat(ShaderID.Get("_Stencil"), 1);
text.fontMaterial.SetFloat(ShaderID.Get("_StencilComp"), 4);
From 758b1d3183f067abaacc7da5752b6c23d652e617 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 18:59:14 -0500
Subject: [PATCH 27/71] organize task add patches
---
MiraAPI/Patches/{Roles => Freeplay}/TaskAddButtonPatches.cs | 2 +-
MiraAPI/Patches/{Roles => Freeplay}/TaskAdderPatches.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
rename MiraAPI/Patches/{Roles => Freeplay}/TaskAddButtonPatches.cs (96%)
rename MiraAPI/Patches/{Roles => Freeplay}/TaskAdderPatches.cs (99%)
diff --git a/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
similarity index 96%
rename from MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
rename to MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
index 510d5e9..07b7f75 100644
--- a/MiraAPI/Patches/Roles/TaskAddButtonPatches.cs
+++ b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
@@ -3,7 +3,7 @@ using MiraAPI.Roles;
using MiraAPI.Utilities;
using UnityEngine;
-namespace MiraAPI.Patches.Roles;
+namespace MiraAPI.Patches.Freeplay;
[HarmonyPatch(typeof(TaskAddButton))]
internal static class TaskAddButtonPatches
diff --git a/MiraAPI/Patches/Roles/TaskAdderPatches.cs b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs
similarity index 99%
rename from MiraAPI/Patches/Roles/TaskAdderPatches.cs
rename to MiraAPI/Patches/Freeplay/TaskAdderPatches.cs
index 37f9744..e36ac61 100644
--- a/MiraAPI/Patches/Roles/TaskAdderPatches.cs
+++ b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs
@@ -13,7 +13,7 @@ using UnityEngine;
using UnityEngine.UI;
using Object = UnityEngine.Object;
-namespace MiraAPI.Patches.Roles;
+namespace MiraAPI.Patches.Freeplay;
[HarmonyPatch(typeof(TaskAdderGame))]
internal static class TaskAdderPatches
From bbec3c1958643f5dbf4534d55bf0cc3515ccbfaa Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 19:02:22 -0500
Subject: [PATCH 28/71] fix something i almost broke
---
MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
index 07b7f75..fb57d70 100644
--- a/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
+++ b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
@@ -31,5 +31,6 @@ internal static class TaskAddButtonPatches
{
__instance.FileImage.color = customRole.IntroConfiguration?.IntroTeamColor ?? Color.gray;
}
+ __instance.RolloverHandler.OutColor = __instance.FileImage.color;
}
}
From 8b8f9ea5db8d433bf31b6ab1e4512d28fdd5f453 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 19:06:59 -0500
Subject: [PATCH 29/71] utilize new modifier extension stuff
---
MiraAPI/Patches/Freeplay/TaskAdderPatches.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs
index e36ac61..d1c6cb9 100644
--- a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs
+++ b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs
@@ -338,12 +338,12 @@ internal static class TaskAdderPatches
{
if (PlayerControl.LocalPlayer.HasModifier(modifier.ModifierId))
{
- PlayerControl.LocalPlayer.GetModifierComponent()!.RemoveModifier(modifier.ModifierId);
+ PlayerControl.LocalPlayer.RemoveModifier(modifier.ModifierId);
taskAddButton.Overlay.enabled = false;
}
else
{
- PlayerControl.LocalPlayer.GetModifierComponent()!.AddModifier(ModifierManager.GetModifierType(modifier.ModifierId)!);
+ PlayerControl.LocalPlayer.AddModifier(ModifierManager.GetModifierType(modifier.ModifierId)!);
taskAddButton.Overlay.enabled = true;
}
}));
From 8fb8d18be9a20dbd7fc1cea3e65d9c896f1b90ca Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 19:10:57 -0500
Subject: [PATCH 30/71] fix name
---
MiraAPI/Utilities/Extensions.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index 6d8cbf6..6912ac8 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -41,7 +41,7 @@ public static class Extensions
}
///
- /// Enables Among Us style masking on a TMP text object.
+ /// Enables stencil masking on a TMP text object.
///
/// The TMP text.
public static void EnableStencilMasking(this TMP_Text text)
From fb19b4bc041fdd481aece090f7d6b30c11da4ee2 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 20:13:12 -0500
Subject: [PATCH 31/71] better naming for "stackable" modifier
---
MiraAPI/Modifiers/BaseModifier.cs | 4 ++--
MiraAPI/Modifiers/ModifierComponent.cs | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs
index 67f7410..9f6aa93 100644
--- a/MiraAPI/Modifiers/BaseModifier.cs
+++ b/MiraAPI/Modifiers/BaseModifier.cs
@@ -36,9 +36,9 @@ public abstract class BaseModifier
public virtual bool ShowInFreeplay => false;
///
- /// Gets a value indicating whether the modifier can be added multiple times.
+ /// Gets a value indicating whether the modifier is unique. If true, the player can only have one instance of this modifier.
///
- public virtual bool Stackable => false;
+ public virtual bool Unique => true;
///
/// Gets the HUD information for this modifier. Defaults to the modifier name. Does nothing if is true.
diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs
index 9918eab..2b00652 100644
--- a/MiraAPI/Modifiers/ModifierComponent.cs
+++ b/MiraAPI/Modifiers/ModifierComponent.cs
@@ -260,7 +260,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
return null;
}
- if (Modifiers.Find(x => x.ModifierId == modifierId && !x.Stackable) != null)
+ if (Modifiers.Find(x => x.ModifierId == modifierId && x.Unique) != null)
{
Logger.Error($"Player already has modifier with id {modifierId}!");
return null;
From 0ebec6d0f1ba220950db3932ee93b3d7a1df42d0 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Sun, 23 Feb 2025 20:28:24 -0500
Subject: [PATCH 32/71] fix testing thing
---
MiraAPI.Example/Buttons/TestButton.cs | 1 -
MiraAPI.Example/Modifiers/ModifierParams.cs | 1 -
2 files changed, 2 deletions(-)
diff --git a/MiraAPI.Example/Buttons/TestButton.cs b/MiraAPI.Example/Buttons/TestButton.cs
index 8062c81..feb7992 100644
--- a/MiraAPI.Example/Buttons/TestButton.cs
+++ b/MiraAPI.Example/Buttons/TestButton.cs
@@ -7,7 +7,6 @@ using UnityEngine;
namespace MiraAPI.Example.Buttons;
-[RegisterButton]
public class TestButton : CustomActionButton
{
public override string Name => "Test Button";
diff --git a/MiraAPI.Example/Modifiers/ModifierParams.cs b/MiraAPI.Example/Modifiers/ModifierParams.cs
index cde7d3b..e92bbda 100644
--- a/MiraAPI.Example/Modifiers/ModifierParams.cs
+++ b/MiraAPI.Example/Modifiers/ModifierParams.cs
@@ -3,7 +3,6 @@ using Reactor.Utilities;
namespace MiraAPI.Example.Modifiers;
-[RegisterModifier]
public class ModifierParams : BaseModifier
{
public override string ModifierName => "ModifierParams";
From 1c6fa9a84dae0b057b32fcbeca83d7b1bdd9aff8 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Mon, 24 Feb 2025 21:51:02 -0500
Subject: [PATCH 33/71] major modifier overhaul
---
MiraAPI/Modifiers/BaseModifier.cs | 23 +-
MiraAPI/Modifiers/ModifierComponent.cs | 270 ++++++++++-----
MiraAPI/Modifiers/ModifierData.cs | 10 +-
MiraAPI/Modifiers/ModifierExtensions.cs | 313 ++++++++++++++----
MiraAPI/Modifiers/ModifierFactory.cs | 43 ++-
MiraAPI/Modifiers/ModifierManager.cs | 8 +-
MiraAPI/Modifiers/Types/TimedModifier.cs | 2 +-
MiraAPI/Networking/AddModifierRpc.cs | 42 ++-
.../Patches/Freeplay/TaskAddButtonPatches.cs | 2 +-
MiraAPI/Patches/Freeplay/TaskAdderPatches.cs | 9 +-
MiraAPI/Patches/PlayerControlPatches.cs | 2 +-
MiraAPI/Patches/Roles/TaskPanelPatch.cs | 2 +-
MiraAPI/Utilities/ModifierExtensions.cs | 252 --------------
13 files changed, 531 insertions(+), 447 deletions(-)
delete mode 100644 MiraAPI/Utilities/ModifierExtensions.cs
diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs
index 9f6aa93..8834454 100644
--- a/MiraAPI/Modifiers/BaseModifier.cs
+++ b/MiraAPI/Modifiers/BaseModifier.cs
@@ -1,4 +1,6 @@
-namespace MiraAPI.Modifiers;
+using System;
+
+namespace MiraAPI.Modifiers;
///
/// Base class for all modifiers.
@@ -15,11 +17,6 @@ public abstract class BaseModifier
///
public ModifierComponent? ModifierComponent { get; internal set; }
- ///
- /// Gets the modifier id.
- ///
- public uint ModifierId { get; internal set; }
-
///
/// Gets the modifier name.
///
@@ -40,6 +37,20 @@ public abstract class BaseModifier
///
public virtual bool Unique => true;
+ ///
+ /// Gets a value indicating whether the modifier has been initialized.
+ ///
+ public bool Initialized { get; internal set; }
+
+ ///
+ /// Get the ID of the modifier.
+ ///
+ /// The ID of the modifier.
+ public uint GetId()
+ {
+ return ModifierManager.GetModifierId(GetType()) ?? throw new InvalidOperationException("Modifier is not registered.");
+ }
+
///
/// Gets the HUD information for this modifier. Defaults to the modifier name. Does nothing if is true.
///
diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs
index 2b00652..6cd3d5e 100644
--- a/MiraAPI/Modifiers/ModifierComponent.cs
+++ b/MiraAPI/Modifiers/ModifierComponent.cs
@@ -67,6 +67,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
foreach (var modifier in _toAdd)
{
Modifiers.Add(modifier);
+ modifier.Initialized = true;
modifier.OnActivate();
if (modifier is TimedModifier { AutoStart: true } timer)
@@ -123,46 +124,153 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
}
///
- /// Gets a collection of modifiers by their type, or null if the player doesn't have one.
+ /// Gets a collection of modifiers by type.
///
+ /// The predicate to check the modifier by.
/// The Type of the Modifier.
/// The Modifier if it is found, null otherwise.
- public IEnumerable GetModifiersByType() where T : BaseModifier
+ [HideFromIl2Cpp]
+ public IEnumerable GetModifiersByType(Func? predicate=null) where T : BaseModifier
{
- return ActiveModifiers.OfType();
+ return ActiveModifiers.OfType().Where(x => predicate == null || predicate(x));
}
///
- /// Gets a modifier by its type, or null if the player doesn't have it.
+ /// Gets a collection of modifiers by type.
///
- /// The Type of the Modifier.
+ /// The modifier type.
+ /// The predicate to check the modifier by.
/// The Modifier if it is found, null otherwise.
- public T? GetModifier() where T : BaseModifier
+ [HideFromIl2Cpp]
+ public IEnumerable GetModifiersByType(Type type, Func? predicate=null)
{
- return GetModifiersByType().FirstOrDefault();
+ return ActiveModifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x)));
+ }
+
+ ///
+ /// Gets a collection of modifiers by ID.
+ ///
+ /// The Modifier ID.
+ /// The predicate to check the modifier by.
+ /// The Modifier if it is found, null otherwise.
+ [HideFromIl2Cpp]
+ public IEnumerable GetModifiersByType(uint id, Func? predicate=null)
+ {
+ var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
+ $"Cannot get modifier with id {id} because it is not registered.");
+
+ return GetModifiersByType(type, predicate);
}
///
/// Tries to get a modifier by its type.
///
/// The modifier or null.
+ /// The predicate to check the modifier by.
/// The Type of the Modifier.
/// True if the modifier was found, false otherwise.
- public bool TryGetModifier(out T? modifier) where T : BaseModifier
+ [HideFromIl2Cpp]
+ public bool TryGetModifier(out T? modifier, Func? predicate = null) where T : BaseModifier
{
- modifier = GetModifier();
+ modifier = GetModifier(predicate);
return modifier != null;
}
+ ///
+ /// Tries to get a modifier by its type.
+ ///
+ /// The modifier type.
+ /// The modifier or null.
+ /// The predicate to check the modifier by.
+ /// True if the modifier was found, false otherwise.
+ [HideFromIl2Cpp]
+ public bool TryGetModifier(Type type, out BaseModifier? modifier, Func? predicate = null)
+ {
+ modifier = GetModifier(type, predicate);
+ return modifier != null;
+ }
+
+ ///
+ /// Tries to get a modifier by its ID.
+ ///
+ /// The modifier ID.
+ /// The modifier or null.
+ /// The predicate to check the modifier by.
+ /// True if the modifier was found, false otherwise.
+ [HideFromIl2Cpp]
+ public bool TryGetModifier(uint id, out BaseModifier? modifier, Func? predicate = null)
+ {
+ var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
+ $"Cannot get modifier with id {id} because it is not registered.");
+
+ return TryGetModifier(type, out modifier, predicate);
+ }
+
+ ///
+ /// Gets a modifier by its type, or null if the player doesn't have it.
+ ///
+ /// The predicate to check the modifier by.
+ /// The Type of the Modifier.
+ /// The Modifier if it is found, null otherwise.
+ [HideFromIl2Cpp]
+ public T? GetModifier(Func? predicate = null) where T : BaseModifier
+ {
+ return GetModifiersByType(predicate).FirstOrDefault();
+ }
+
+ ///
+ /// Gets a modifier by its type, or null if the player doesn't have it.
+ ///
+ /// The modifier type.
+ /// The predicate to check the modifier by.
+ /// The Modifier if it is found, null otherwise.
+ [HideFromIl2Cpp]
+ public BaseModifier? GetModifier(Type type, Func? predicate = null)
+ {
+ return GetModifiersByType(type).FirstOrDefault(predicate ?? (_ => true));
+ }
+
+ ///
+ /// Gets a modifier by its type, or null if the player doesn't have it.
+ ///
+ /// The modifier ID.
+ /// The predicate to check the modifier by.
+ /// The Modifier if it is found, null otherwise.
+ [HideFromIl2Cpp]
+ public BaseModifier? GetModifier(uint id, Func? predicate = null)
+ {
+ var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
+ $"Cannot get modifier with id {id} because it is not registered.");
+
+ return GetModifier(type, predicate);
+ }
+
+ ///
+ /// Removes a modifier from the player.
+ ///
+ /// The modifier type.
+ /// The predicate to check the modifier by.
+ [HideFromIl2Cpp]
+ public void RemoveModifier(Func? predicate = null) where T : BaseModifier
+ {
+ RemoveModifier(typeof(T), x => predicate == null || predicate((T)x));
+ }
+
///
/// Removes a modifier from the player.
///
/// The modifier type.
+ /// The predicate to check the modifier by.
[HideFromIl2Cpp]
- public void RemoveModifier(Type type)
+ public void RemoveModifier(Type type, Func? predicate = null)
{
- var modifier = Modifiers.Find(x => x.GetType() == type);
+ var modifiers = Modifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x))).ToList();
+ if (modifiers.Count > 1)
+ {
+ throw new InvalidOperationException($"Cannot remove modifier {type.Name} because there are multiple instances of that modifier.");
+ }
+ var modifier = modifiers.FirstOrDefault();
if (modifier is null)
{
Logger.Error($"Cannot remove modifier {type.Name} because it is not active.");
@@ -172,32 +280,17 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
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.
+ /// The predicate to check the modifier by.
[HideFromIl2Cpp]
- public void RemoveModifier(uint modifierId)
+ public void RemoveModifier(uint modifierId, Func? predicate = null)
{
- var modifier = Modifiers.Find(x => x.ModifierId == modifierId);
-
- if (modifier is null)
- {
- Logger.Error($"Cannot remove modifier with id {modifierId} because it is not active.");
- return;
- }
-
- RemoveModifier(modifier);
+ var type = ModifierManager.GetModifierType(modifierId) ?? throw new InvalidOperationException(
+ $"Cannot remove modifier with id {modifierId} because it is not registered.");
+ RemoveModifier(type, predicate);
}
///
@@ -216,6 +309,18 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
_toRemove.Add(modifier);
}
+ ///
+ /// Adds a modifier to the player.
+ ///
+ /// The arguments to initialize the modifier constructor with.
+ /// The Type of the modifier.
+ /// The new modifier.
+ [HideFromIl2Cpp]
+ public T? AddModifier(params object[] args) where T : BaseModifier
+ {
+ return AddModifier(typeof(T), args) as T;
+ }
+
///
/// Adds a modifier to the player.
///
@@ -224,24 +329,22 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
[HideFromIl2Cpp]
public BaseModifier? AddModifier(BaseModifier modifier)
{
- if (Modifiers.Contains(modifier))
+ var id = modifier.GetId();
+ if (modifier.Unique && Modifiers.Find(x => x.GetId() == id) != null)
{
- Logger.Error($"Player already has modifier with id {modifier.ModifierId}!");
+ Logger.Error($"Player already has modifier with id {id}!");
return null;
}
- var modifierId = ModifierManager.GetModifierId(modifier.GetType());
-
- if (modifierId == null)
+ if (Modifiers.Contains(modifier))
{
- Logger.Error($"Cannot add modifier {modifier.GetType().Name} because it has no ID!");
+ Logger.Error($"Player already has this modifier!");
return null;
}
_toAdd.Add(modifier);
modifier.Player = _player;
modifier.ModifierComponent = this;
- modifier.ModifierId = modifierId.Value;
return modifier;
}
@@ -249,66 +352,79 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
/// Adds a modifier to the player.
///
/// The modifier type.
+ /// The arguments to initialize the modifier constructor with.
/// The modifier that was added.
[HideFromIl2Cpp]
- public BaseModifier? AddModifier(Type type)
+ public BaseModifier? AddModifier(Type type, params object[] args)
{
- var modifierId = ModifierManager.GetModifierId(type);
- if (modifierId == null)
+ BaseModifier? modifier;
+ if (args.Length > 0)
{
- Logger.Error($"Cannot add modifier {type.Name} because it is not registered.");
- return null;
+ modifier = ModifierFactory.CreateInstance(type, args);
+ }
+ else
+ {
+ modifier = Activator.CreateInstance(type) as BaseModifier;
+ if (modifier == null)
+ {
+ throw new InvalidOperationException($"Cannot add modifier {type.Name} because it is not a valid modifier.");
+ }
}
- if (Modifiers.Find(x => x.ModifierId == modifierId && x.Unique) != null)
- {
- Logger.Error($"Player already has modifier with id {modifierId}!");
- return null;
- }
-
- if (Activator.CreateInstance(type) is not BaseModifier modifier)
- {
- Logger.Error($"Cannot add modifier {type.Name} because it is null.");
- return null;
- }
-
- AddModifier(modifier);
-
- return modifier;
+ return AddModifier(modifier);
}
///
/// Adds a modifier to the player.
///
- /// The Type of the modifier.
- /// The new modifier.
+ /// The ID of the modifier.
+ /// The arguments to initialize the modifier constructor with.
+ /// The modifier if it was created, or null if it failed.
[HideFromIl2Cpp]
- public T? AddModifier() where T : BaseModifier
+ public BaseModifier? AddModifier(uint id, params object[] args)
{
- return AddModifier(typeof(T)) as T;
+ var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
+ $"Cannot add modifier with id {id} because it is not registered.");
+
+ return AddModifier(type, args);
}
///
- /// Checks if a player has an active or queued modifier by its ID.
- ///
- /// The Modifier ID.
- /// The predicate to check the modifier.
- /// True if the Modifier is present, false otherwise.
- public bool HasModifier(uint id, Func? predicate=null)
- {
- return ActiveModifiers.Exists(x => x.ModifierId == id && (predicate == null || predicate(x))) ||
- _toAdd.Exists(x => x.ModifierId == id && (predicate == null || predicate(x)));
- }
-
- ///
- /// Checks if a player has an active or queued modifier by its type.
+ /// Checks if a player has an active modifier by its type.
///
/// The predicate to check the modifier.
/// The Type of the Modifier.
/// True if the Modifier is present, false otherwise.
+ [HideFromIl2Cpp]
public bool HasModifier(Func? predicate=null) where T : BaseModifier
{
- return ModifierManager.GetModifierId(typeof(T)) is { } id &&
- HasModifier(id, (Func?)predicate);
+ return ActiveModifiers.Exists(x => x is T modifier && (predicate == null || predicate(modifier)));
+ }
+
+ ///
+ /// Checks if a player has an active modifier by its type.
+ ///
+ /// The modifier type.
+ /// The predicate to check the modifier.
+ /// True if the Modifier is present, false otherwise.
+ [HideFromIl2Cpp]
+ public bool HasModifier(Type type, Func? predicate=null)
+ {
+ return ActiveModifiers.Exists(x => x.GetType() == type && (predicate == null || predicate(x)));
+ }
+
+ ///
+ /// Checks if a player has an active modifier by its ID.
+ ///
+ /// The Modifier ID.
+ /// The predicate to check the modifier.
+ /// True if the Modifier is present, false otherwise.
+ [HideFromIl2Cpp]
+ public bool HasModifier(uint id, Func? predicate=null)
+ {
+ var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
+ $"Cannot get modifier with id {id} because it is not registered.");
+
+ return HasModifier(type, predicate);
}
}
diff --git a/MiraAPI/Modifiers/ModifierData.cs b/MiraAPI/Modifiers/ModifierData.cs
index 425e063..7260834 100644
--- a/MiraAPI/Modifiers/ModifierData.cs
+++ b/MiraAPI/Modifiers/ModifierData.cs
@@ -1,18 +1,16 @@
-using System;
-
-namespace MiraAPI.Modifiers;
+namespace MiraAPI.Modifiers;
///
/// Modifier data for networking.
///
-/// The Type of the modifier.
+/// The ID of the modifier.
/// Parameters for constructor.
-public readonly struct ModifierData(Type type, object[] args)
+public readonly struct ModifierData(uint id, object[] args)
{
///
/// Gets the type of the modifier.
///
- public Type Type { get; } = type;
+ public uint Id { get; } = id;
///
/// Gets the parameters for the constructor.
diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs
index d6ce5ff..226b16b 100644
--- a/MiraAPI/Modifiers/ModifierExtensions.cs
+++ b/MiraAPI/Modifiers/ModifierExtensions.cs
@@ -8,11 +8,81 @@ using Reactor.Utilities;
namespace MiraAPI.Modifiers;
///
-/// Extension methods for modifiers.
+/// Extensions for modifiers.
///
public static class ModifierExtensions
{
- internal static Dictionary ModifierComponents { get; } = [];
+ ///
+ /// Gets the dictionary to cache ModifierComponents.
+ ///
+ public static Dictionary ModifierComponents { get; } = [];
+
+ ///
+ /// Remote Procedure Call to add a modifier to a player.
+ ///
+ /// The player to add the modifier to.
+ /// The modifier ID.
+ /// The arguments to initialize the modifier constructor with.
+ public static void RpcAddModifier(this PlayerControl target, uint modifierId, params object[] args)
+ {
+ _ = ModifierManager.GetModifierType(modifierId) ?? throw new InvalidOperationException(
+ $"Modifier with ID {modifierId} is not registered.");
+
+ Rpc.Instance.Send(target, new ModifierData(modifierId, args));
+ }
+
+ ///
+ /// Remote Procedure Call to add a modifier to a player.
+ ///
+ /// The player to add the modifier to.
+ /// The type of the modifier.
+ /// The arguments to initialize the modifier constructor with.
+ public static void RpcAddModifier(this PlayerControl player, Type type, params object[] args)
+ {
+ var id = ModifierManager.GetModifierId(type) ?? throw new InvalidOperationException(
+ $"Modifier {type.Name} is not registered.");
+
+ player.RpcAddModifier(id, args);
+ }
+
+ ///
+ /// Remote Procedure Call to add a modifier to a player.
+ ///
+ /// The player to add the modifier to.
+ /// The arguments to initialize the modifier constructor with.
+ /// The modifier Type.
+ public static void RpcAddModifier(this PlayerControl player, params object[] args) where T : BaseModifier
+ {
+ player.RpcAddModifier(typeof(T), args);
+ }
+
+ ///
+ /// 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.RemoveModifier(modifierId);
+ }
+
+ ///
+ /// 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
+ {
+ var id = ModifierManager.GetModifierId(typeof(T));
+ if (id == null)
+ {
+ Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
+ return;
+ }
+
+ player.RpcRemoveModifier(id.Value);
+ }
///
/// Gets the ModifierComponent for a player.
@@ -37,102 +107,217 @@ public static class ModifierExtensions
}
///
- /// Gets a modifier by its type, or null if the player doesn't have it.
+ /// Checks if the player has a specific modifier.
///
- /// 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
+ /// The type of the modifier.
+ /// The PlayerControl instance.
+ /// Optional predicate to filter the modifiers.
+ /// True if the player has the modifier, false otherwise.
+ public static bool HasModifier(this PlayerControl player, Func? predicate = null) where T : BaseModifier
{
- return player?.GetModifierComponent()?.ActiveModifiers.Find(x => x is T) as T;
+ return player.GetModifierComponent()!.HasModifier(predicate);
}
///
- /// Checks if a player has a modifier.
+ /// Checks if the player has a specific modifier by type.
///
- /// 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
+ /// The PlayerControl instance.
+ /// The type of the modifier.
+ /// Optional predicate to filter the modifiers.
+ /// True if the player has the modifier, false otherwise.
+ public static bool HasModifier(this PlayerControl player, Type type, Func? predicate = null)
{
- return player?.GetModifierComponent() != null &&
- player.GetModifierComponent()!.HasModifier();
+ return player.GetModifierComponent()!.HasModifier(type, predicate);
}
///
- /// Checks if a player has a modifier by its ID.
+ /// Checks if the player has a specific modifier by ID.
///
- /// The PlayerControl object.
- /// The Modifier ID.
- /// True if the Modifier is present, false otherwise.
- public static bool HasModifier(this PlayerControl? player, uint id)
+ /// The PlayerControl instance.
+ /// The ID of the modifier.
+ /// Optional predicate to filter the modifiers.
+ /// True if the player has the modifier, false otherwise.
+ public static bool HasModifier(this PlayerControl player, uint id, Func? predicate = null)
{
- return player?.GetModifierComponent() != null &&
- player.GetModifierComponent()!.HasModifier(id);
+ return player.GetModifierComponent()!.HasModifier(id, predicate);
}
///
- /// Remote Procedure Call to remove a modifier from a player.
+ /// Gets a specific modifier from the player.
///
- /// The player to remove the modifier from.
+ /// The type of the modifier.
+ /// The PlayerControl instance.
+ /// Optional predicate to filter the modifiers.
+ /// The modifier if found, null otherwise.
+ public static T? GetModifier(this PlayerControl player, Func? predicate = null) where T : BaseModifier
+ {
+ return player.GetModifierComponent()!.GetModifier(predicate);
+ }
+
+ ///
+ /// Gets a specific modifier from the player by type.
+ ///
+ /// The PlayerControl instance.
+ /// The type of the modifier.
+ /// Optional predicate to filter the modifiers.
+ /// The modifier if found, null otherwise.
+ public static BaseModifier? GetModifier(
+ this PlayerControl player,
+ Type type,
+ Func? predicate = null)
+ {
+ return player.GetModifierComponent()!.GetModifier(type, predicate);
+ }
+
+ ///
+ /// Gets a specific modifier from the player by ID.
+ ///
+ /// The PlayerControl instance.
+ /// The ID of the modifier.
+ /// Optional predicate to filter the modifiers.
+ /// The modifier if found, null otherwise.
+ public static BaseModifier? GetModifier(
+ this PlayerControl player,
+ uint id,
+ Func? predicate = null)
+ {
+ return player.GetModifierComponent()!.GetModifier(id, predicate);
+ }
+
+ ///
+ /// Gets all modifiers of a specific type from the player.
+ ///
+ /// The type of the modifiers.
+ /// The PlayerControl instance.
+ /// Optional predicate to filter the modifiers.
+ /// An enumerable of modifiers.
+ public static IEnumerable GetModifiersByType(this PlayerControl player, Func? predicate = null)
+ where T : BaseModifier
+ {
+ return player.GetModifierComponent()!.GetModifiersByType(predicate);
+ }
+
+ ///
+ /// Gets all modifiers of a specific type from the player.
+ ///
+ /// The PlayerControl instance.
+ /// The type of the modifiers.
+ /// Optional predicate to filter the modifiers.
+ /// An enumerable of modifiers.
+ public static IEnumerable GetModifiersByType(
+ this PlayerControl player,
+ Type type,
+ Func? predicate = null)
+ {
+ return player.GetModifierComponent()!.GetModifiersByType(type, predicate);
+ }
+
+ ///
+ /// Gets all modifiers of a specific type from the player by ID.
+ ///
+ /// The PlayerControl instance.
+ /// The ID of the modifiers.
+ /// Optional predicate to filter the modifiers.
+ /// An enumerable of modifiers.
+ public static IEnumerable GetModifiersByType(
+ this PlayerControl player,
+ uint id,
+ Func? predicate = null)
+ {
+ return player.GetModifierComponent()!.GetModifiersByType(id, predicate);
+ }
+
+ ///
+ /// Removes a specific modifier from the player.
+ ///
+ /// The type of the modifier.
+ /// The PlayerControl instance.
+ /// Optional predicate to filter the modifiers.
+ public static void RemoveModifier(this PlayerControl player, Func? predicate = null)
+ where T : BaseModifier
+ {
+ player.GetModifierComponent()!.RemoveModifier(predicate);
+ }
+
+ ///
+ /// Removes a specific modifier from the player by type.
+ ///
+ /// The PlayerControl instance.
+ /// The type of the modifier.
+ /// Optional predicate to filter the modifiers.
+ public static void RemoveModifier(this PlayerControl player, Type type, Func? predicate = null)
+ {
+ player.GetModifierComponent()!.RemoveModifier(type, predicate);
+ }
+
+ ///
+ /// Removes a specific modifier from the player by ID.
+ ///
+ /// The PlayerControl instance.
/// The ID of the modifier.
- [MethodRpc((uint)MiraRpc.RemoveModifier)]
- public static void RpcRemoveModifier(this PlayerControl target, uint modifierId)
+ /// Optional predicate to filter the modifiers.
+ public static void RemoveModifier(
+ this PlayerControl player,
+ uint modifierId,
+ Func? predicate = null)
{
- target.GetModifierComponent()?.RemoveModifier(modifierId);
+ player.GetModifierComponent()!.RemoveModifier(modifierId, predicate);
}
///
- /// Remote Procedure Call to remove a modifier from a player.
+ /// Removes a specific modifier from the player.
///
- /// The player to remove the modifier from.
- /// The Type of the Modifier.
- public static void RpcRemoveModifier(this PlayerControl player) where T : BaseModifier
+ /// The PlayerControl instance.
+ /// The modifier to remove.
+ public static void RemoveModifier(this PlayerControl player, BaseModifier modifier)
{
- var id = ModifierManager.GetModifierId(typeof(T));
-
- if (id == null)
- {
- Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
- return;
- }
-
- player.RpcRemoveModifier(id.Value);
+ player.GetModifierComponent()!.RemoveModifier(modifier);
}
///
- /// Remote Procedure Call to add a modifier to a player.
+ /// Adds a specific modifier to the player.
///
- /// The player to add the modifier to.
- /// The modifier ID.
+ /// The type of the modifier.
+ /// The PlayerControl instance.
/// The arguments to initialize the modifier constructor with.
- public static void RpcAddModifier(this PlayerControl target, uint modifierId, params object[] args)
+ /// The added modifier.
+ public static T? AddModifier(this PlayerControl player, params object[] args) where T : BaseModifier
{
- var type = ModifierManager.GetModifierType(modifierId);
- if (type == null)
- {
- Logger.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
- return;
- }
-
- Rpc.Instance.Send(target, new ModifierData(type, args));
+ return player.GetModifierComponent()!.AddModifier(args);
}
///
- /// Remote Procedure Call to add a modifier to a player.
+ /// Adds a specific modifier to the player.
///
- /// The player to add the modifier to.
- /// The arguments to initialize the modifier constructor with.
- /// The modifier Type.
- public static void RpcAddModifier(this PlayerControl player, params object[] args) where T : BaseModifier
+ /// The PlayerControl instance.
+ /// The modifier to add.
+ /// The added modifier.
+ public static BaseModifier? AddModifier(this PlayerControl player, BaseModifier modifier)
{
- var id = ModifierManager.GetModifierId(typeof(T));
- if (id == null)
- {
- Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
- return;
- }
+ return player.GetModifierComponent()!.AddModifier(modifier);
+ }
- player.RpcAddModifier(id.Value, args);
+ ///
+ /// Adds a specific modifier to the player by type.
+ ///
+ /// The PlayerControl instance.
+ /// The type of the modifier.
+ /// The arguments to initialize the modifier constructor with.
+ /// The added modifier.
+ public static BaseModifier? AddModifier(this PlayerControl player, Type type, params object[] args)
+ {
+ return player.GetModifierComponent()!.AddModifier(type, args);
+ }
+
+ ///
+ /// Adds a specific modifier to the player by ID.
+ ///
+ /// The PlayerControl instance.
+ /// The ID of the modifier.
+ /// The arguments to initialize the modifier constructor with.
+ /// The added modifier.
+ public static BaseModifier? AddModifier(this PlayerControl player, uint id, params object[] args)
+ {
+ return player.GetModifierComponent()!.AddModifier(id, args);
}
}
diff --git a/MiraAPI/Modifiers/ModifierFactory.cs b/MiraAPI/Modifiers/ModifierFactory.cs
index e1b018c..d145b82 100644
--- a/MiraAPI/Modifiers/ModifierFactory.cs
+++ b/MiraAPI/Modifiers/ModifierFactory.cs
@@ -1,18 +1,28 @@
using System;
-using System.Linq;
using System.Linq.Expressions;
namespace MiraAPI.Modifiers;
+///
+/// Factory for creating instances of a modifier. More efficient than using reflection.
+///
public static class ModifierFactory
{
private static Func? _constructor;
- private static Func CreateConstructor(Type type)
+ private static Func CreateConstructor(Type type, params object[] args)
{
- var constructorInfo = type.GetConstructors()
- .OrderByDescending(c => c.GetParameters().Length)
- .FirstOrDefault() ?? throw new InvalidOperationException($"Type {type} does not have a public constructor.");
+ var constructorInfo = Array.Find(
+ type.GetConstructors(),
+ x =>
+ {
+ var parameters = x.GetParameters();
+ return parameters.Length == args.Length && Array.TrueForAll(
+ parameters,
+ t => t.ParameterType.IsInstanceOfType(args[t.Position]));
+ }) ?? throw new InvalidOperationException(
+ $"Could not find a constructor for type {type} with the specified arguments.");
+
var parameters = constructorInfo.GetParameters();
var argsParam = Expression.Parameter(typeof(object[]), "args");
@@ -32,9 +42,15 @@ public static class ModifierFactory
return lambda.Compile();
}
+ ///
+ /// Creates a modifier with the specified type and arguments.
+ ///
+ /// Modifier type.
+ /// Arguments.
+ /// An instance of the modifier.
public static BaseModifier CreateInstance(Type type, params object[] args)
{
- _constructor ??= CreateConstructor(type);
+ _constructor ??= CreateConstructor(type, args);
return _constructor(args);
}
}
@@ -47,11 +63,18 @@ public static class ModifierFactory where T : BaseModifier
{
private static readonly Func Constructor = CreateConstructor();
- private static Func CreateConstructor()
+ private static Func CreateConstructor(params object[] args)
{
- var constructorInfo = typeof(T).GetConstructors()
- .OrderByDescending(c => c.GetParameters().Length)
- .FirstOrDefault() ?? throw new InvalidOperationException($"Type {typeof(T)} does not have a public constructor.");
+ var constructorInfo = Array.Find(
+ typeof(T).GetConstructors(),
+ x =>
+ {
+ var parameters = x.GetParameters();
+ return parameters.Length == args.Length && Array.TrueForAll(
+ parameters,
+ t => t.ParameterType.IsInstanceOfType(args[t.Position]));
+ }) ?? throw new InvalidOperationException($"Could not find a constructor for type {typeof(T)} with the specified arguments.");
+
var parameters = constructorInfo.GetParameters();
var argsParam = Expression.Parameter(typeof(object[]), "args");
diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs
index d4d4004..65341a0 100644
--- a/MiraAPI/Modifiers/ModifierManager.cs
+++ b/MiraAPI/Modifiers/ModifierManager.cs
@@ -48,7 +48,12 @@ public static class ModifierManager
/// The ID of the modifier.
public static uint? GetModifierId(Type type)
{
- return TypeToIdModifierMap.GetValueOrDefault(type);
+ if (!TypeToIdModifierMap.TryGetValue(type, out var id))
+ {
+ return null;
+ }
+
+ return id;
}
internal static bool RegisterModifier(Type modifierType, MiraPluginInfo info)
@@ -61,7 +66,6 @@ public static class ModifierManager
IdToTypeModifierMap.Add(GetNextId(), modifierType);
TypeToIdModifierMap.Add(modifierType, _nextId);
var bm = (BaseModifier)FormatterServices.GetUninitializedObject(modifierType); // this probably isn't a great idea
- bm.ModifierId = _nextId;
info.Modifiers.Add(bm);
if (!typeof(GameModifier).IsAssignableFrom(modifierType))
diff --git a/MiraAPI/Modifiers/Types/TimedModifier.cs b/MiraAPI/Modifiers/Types/TimedModifier.cs
index 286b80a..038fa6d 100644
--- a/MiraAPI/Modifiers/Types/TimedModifier.cs
+++ b/MiraAPI/Modifiers/Types/TimedModifier.cs
@@ -102,7 +102,7 @@ public abstract class TimedModifier : BaseModifier
if (RemoveOnComplete)
{
- ModifierComponent?.RemoveModifier(ModifierId);
+ ModifierComponent?.RemoveModifier(this);
}
}
diff --git a/MiraAPI/Networking/AddModifierRpc.cs b/MiraAPI/Networking/AddModifierRpc.cs
index 3546d4f..fdc37a9 100644
--- a/MiraAPI/Networking/AddModifierRpc.cs
+++ b/MiraAPI/Networking/AddModifierRpc.cs
@@ -1,7 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
using Hazel;
using MiraAPI.Modifiers;
using Reactor.Networking.Attributes;
@@ -18,44 +15,45 @@ namespace MiraAPI.Networking;
[RegisterCustomRpc((uint)MiraRpc.AddModifier)]
public class AddModifierRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc(plugin, id)
{
- private static readonly Dictionary ParameterCache = [];
-
///
public override RpcLocalHandling LocalHandling => RpcLocalHandling.Before;
///
public override void Write(MessageWriter writer, ModifierData data)
{
- var modId = ModifierManager.GetModifierId(data.Type);
- writer.WritePacked((uint)modId!);
- MessageSerializer.Serialize(writer, data.Args);
+ writer.WritePacked(data.Id);
+ writer.WritePacked(data.Args.Length);
+ foreach (var arg in data.Args)
+ {
+ writer.Write(arg.GetType().AssemblyQualifiedName);
+ writer.Serialize(arg);
+ }
}
///
public override ModifierData Read(MessageReader reader)
{
var modId = reader.ReadPackedUInt32();
- var modifier = ModifierManager.GetModifierType(modId)!;
+ var argCount = reader.ReadPackedUInt32();
+ var objects = new object[argCount];
- if (!ParameterCache.TryGetValue(modifier, out var paramTypes))
+ if (argCount > 0)
{
- paramTypes = modifier.GetConstructors().OrderBy(x => x.GetParameters().Length).First().GetParameters();
- ParameterCache[modifier] = paramTypes;
+ var types = new Type[argCount];
+ for (var i = 0; i < argCount; i++)
+ {
+ var name = reader.ReadString();
+ types[i] = Type.GetType(name) ?? throw new InvalidOperationException($"Type not found: {name}");
+ objects[i] = reader.Deserialize(types[i]);
+ }
}
- var objects = new object[paramTypes.Length];
- foreach (var paramType in paramTypes)
- {
- objects[paramType.Position] = reader.Deserialize(paramType.ParameterType);
- }
-
- return new ModifierData(modifier, objects);
+ return new ModifierData(modId, objects);
}
///
- public override void Handle(PlayerControl innerNetObject, ModifierData data)
+ public override void Handle(PlayerControl player, ModifierData data)
{
- var modifier = ModifierFactory.CreateInstance(data.Type, data.Args);
- innerNetObject.GetModifierComponent()?.AddModifier(modifier);
+ player.AddModifier(data.Id, data.Args);
}
}
diff --git a/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
index fb57d70..184bafb 100644
--- a/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
+++ b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs
@@ -1,6 +1,6 @@
using HarmonyLib;
+using MiraAPI.Modifiers;
using MiraAPI.Roles;
-using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Patches.Freeplay;
diff --git a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs
index d1c6cb9..b634c91 100644
--- a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs
+++ b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs
@@ -326,7 +326,7 @@ internal static class TaskAdderPatches
}
var taskAddButton = Object.Instantiate(instance.RoleButton);
- taskAddButton.name = modifier.ModifierId.ToString(CultureInfo.InvariantCulture);
+ taskAddButton.name = modifier.GetId().ToString(NumberFormatInfo.InvariantInfo);
taskAddButton.role = null;
taskAddButton.MyTask = null;
taskAddButton.SafePositionWorld = instance.SafePositionWorld;
@@ -336,14 +336,15 @@ internal static class TaskAdderPatches
taskAddButton.Button.OnClick = new Button.ButtonClickedEvent();
taskAddButton.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() =>
{
- if (PlayerControl.LocalPlayer.HasModifier(modifier.ModifierId))
+ var id = modifier.GetId();
+ if (PlayerControl.LocalPlayer.HasModifier(id))
{
- PlayerControl.LocalPlayer.RemoveModifier(modifier.ModifierId);
+ PlayerControl.LocalPlayer.RemoveModifier(id);
taskAddButton.Overlay.enabled = false;
}
else
{
- PlayerControl.LocalPlayer.AddModifier(ModifierManager.GetModifierType(modifier.ModifierId)!);
+ PlayerControl.LocalPlayer.AddModifier(id);
taskAddButton.Overlay.enabled = true;
}
}));
diff --git a/MiraAPI/Patches/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs
index fbabe1a..31fdbb8 100644
--- a/MiraAPI/Patches/PlayerControlPatches.cs
+++ b/MiraAPI/Patches/PlayerControlPatches.cs
@@ -126,6 +126,6 @@ public static class PlayerControlPatches
[HarmonyPatch(nameof(PlayerControl.OnDestroy))]
public static void PlayerControlOnDestroyPrefix(PlayerControl __instance)
{
- Utilities.ModifierExtensions.ModifierComponents.Remove(__instance);
+ ModifierExtensions.ModifierComponents.Remove(__instance);
}
}
diff --git a/MiraAPI/Patches/Roles/TaskPanelPatch.cs b/MiraAPI/Patches/Roles/TaskPanelPatch.cs
index 431d47b..a08445a 100644
--- a/MiraAPI/Patches/Roles/TaskPanelPatch.cs
+++ b/MiraAPI/Patches/Roles/TaskPanelPatch.cs
@@ -4,7 +4,7 @@ using UnityEngine;
namespace MiraAPI.Patches.Roles;
[HarmonyPatch(typeof(TaskPanelBehaviour))]
-public static class TaskPanelPatch
+internal static class TaskPanelPatch
{
///
/// This patch is to override the automatic updating of the y position on the tab (which is in base game)
diff --git a/MiraAPI/Utilities/ModifierExtensions.cs b/MiraAPI/Utilities/ModifierExtensions.cs
deleted file mode 100644
index dadb62c..0000000
--- a/MiraAPI/Utilities/ModifierExtensions.cs
+++ /dev/null
@@ -1,252 +0,0 @@
-using System;
-using System.Collections.Generic;
-using MiraAPI.Modifiers;
-using MiraAPI.Networking;
-using Reactor.Networking.Attributes;
-using Reactor.Utilities;
-
-namespace MiraAPI.Utilities;
-
-///
-/// Extensions for modifiers.
-///
-public static class ModifierExtensions
-{
- ///
- /// Gets the dictionary to cache ModifierComponents.
- ///
- public static Dictionary ModifierComponents { get; } = [];
-
- ///
- /// 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.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);
- }
-
- ///
- /// 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.RemoveModifier(modifierId);
- }
-
- ///
- /// 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
- {
- var id = ModifierManager.GetModifierId(typeof(T));
- if (id == null)
- {
- Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
- return;
- }
-
- player.RpcRemoveModifier(id.Value);
- }
-
- ///
- /// 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))
- {
- return component;
- }
-
- component = player.GetComponent();
- if (!component)
- {
- return null;
- }
-
- ModifierComponents[player] = component;
- return component;
- }
-
- ///
- /// Gets the ModifierComponent for a player.
- ///
- /// The PlayerControl object.
- /// The ModifierComponent if it is found.
- /// A ModifierComponent if there is one, null otherwise.
- public static bool TryGetModifierComponent(this PlayerControl player, out ModifierComponent? component)
- {
- if (ModifierComponents.TryGetValue(player, out component))
- {
- return component;
- }
-
- component = player.GetComponent();
- return component;
- }
-
- ///
- /// Gets a collection of modifiers by their type, or null if the player doesn't have one.
- ///
- /// The PlayerControl object.
- /// The Type of the Modifier.
- /// The Modifier if it is found, null otherwise.
- public static IEnumerable GetModifiersByType(this PlayerControl player) where T : BaseModifier
- {
- return player.GetModifierComponent()!.GetModifiersByType();
- }
-
- ///
- /// 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()!.GetModifier();
- }
-
- ///
- /// Tries to get a modifier by its type.
- ///
- /// The PlayerControl object.
- /// The modifier or null.
- /// The Type of the Modifier.
- /// True if the modifier was found, false otherwise.
- public static bool TryGetModifier(this PlayerControl player, out T? modifier) where T : BaseModifier
- {
- return player.GetModifierComponent()!.TryGetModifier(out modifier);
- }
-
- ///
- /// Removes a modifier from the player.
- ///
- /// The PlayerControl object.
- /// The modifier type.
- public static void RemoveModifier(this PlayerControl player, Type type)
- {
- player.GetModifierComponent()!.RemoveModifier(type);
- }
-
- ///
- /// Removes a modifier from the player.
- ///
- /// The PlayerControl object.
- /// The modifier type.
- public static void RemoveModifier(this PlayerControl player) where T : BaseModifier
- {
- player.GetModifierComponent()!.RemoveModifier();
- }
-
- ///
- /// Removes a modifier from the player.
- ///
- /// The PlayerControl object.
- /// The modifier ID.
- public static void RemoveModifier(this PlayerControl player, uint modifierId)
- {
- player.GetModifierComponent()!.RemoveModifier(modifierId);
- }
-
- ///
- /// Removes a modifier from the player.
- ///
- /// The PlayerControl object.
- /// The modifier object.
- public static void RemoveModifier(this PlayerControl player, BaseModifier modifier)
- {
- player.GetModifierComponent()!.RemoveModifier(modifier);
- }
-
- ///
- /// Adds a modifier to the player.
- ///
- /// The PlayerControl object.
- /// The modifier to add.
- /// The modifier that was added.
- public static BaseModifier? AddModifier(this PlayerControl player, BaseModifier modifier)
- {
- return player.GetModifierComponent()!.AddModifier(modifier);
- }
-
- ///
- /// Adds a modifier to the player.
- ///
- /// The PlayerControl object.
- /// The modifier type.
- /// The modifier that was added.
- public static BaseModifier? AddModifier(this PlayerControl player, Type type)
- {
- return player.GetModifierComponent()!.AddModifier(type);
- }
-
- ///
- /// Adds a modifier to the player.
- ///
- /// The PlayerControl object.
- /// The Type of the modifier.
- /// The new modifier.
- public static T? AddModifier(this PlayerControl player) where T : BaseModifier
- {
- return player.GetModifierComponent()!.AddModifier();
- }
-
- ///
- /// Checks if a player has an active or queued modifier by its ID.
- ///
- /// The PlayerControl object.
- /// The Modifier ID.
- /// The predicate to check the modifier.
- /// True if the Modifier is present, false otherwise.
- public static bool HasModifier(this PlayerControl player, uint id, Func? predicate=null)
- {
- return player.GetModifierComponent()!.HasModifier(id, predicate);
- }
-
- ///
- /// Checks if a player has an active or queued modifier by its type.
- ///
- /// The PlayerControl object.
- /// The predicate to check the modifier.
- /// The Type of the Modifier.
- /// True if the Modifier is present, false otherwise.
- public static bool HasModifier(this PlayerControl player, Func? predicate=null) where T : BaseModifier
- {
- return player.GetModifierComponent()!.HasModifier(predicate);
- }
-}
From 9ae9094a85e2443e98725e8cff0603b2f114f88a Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Mon, 24 Feb 2025 21:52:06 -0500
Subject: [PATCH 34/71] oops fix that
---
MiraAPI/Patches/VentPatches.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/MiraAPI/Patches/VentPatches.cs b/MiraAPI/Patches/VentPatches.cs
index 621abbc..8aa174b 100644
--- a/MiraAPI/Patches/VentPatches.cs
+++ b/MiraAPI/Patches/VentPatches.cs
@@ -1,8 +1,8 @@
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Usables;
+using MiraAPI.Modifiers;
using MiraAPI.Roles;
-using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Patches;
From 66375e884f70d2f0a439df8c2536d374eccf3561 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Mon, 24 Feb 2025 21:52:53 -0500
Subject: [PATCH 35/71] use invariant info to shut up warnings
---
MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs | 13 +++++++------
MiraAPI/Hud/CustomActionButton.cs | 3 ++-
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
index 80bd497..f25fda6 100644
--- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
+++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.Linq;
using System.Text;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
@@ -146,7 +147,7 @@ public class ModdedEnumOption : ModdedOption where T : Enum
// SetUpFromData method doesnt work correctly so we must set the values manually
stringOption.Title = StringName;
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray(0);
- stringOption.Value = Convert.ToInt32(Value);
+ stringOption.Value = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
OptionBehaviour = stringOption;
@@ -156,13 +157,13 @@ public class ModdedEnumOption : ModdedOption where T : Enum
///
public override float GetFloatData()
{
- return Convert.ToSingle(Value);
+ return Convert.ToSingle(Value, NumberFormatInfo.InvariantInfo);
}
///
public override NetData GetNetData()
{
- return new NetData(Id, Encoding.Unicode.GetBytes(Convert.ToString(Value)));
+ return new NetData(Id, Encoding.Unicode.GetBytes(Convert.ToString(Value, NumberFormatInfo.InvariantInfo)!));
}
///
@@ -174,13 +175,13 @@ public class ModdedEnumOption : ModdedOption where T : Enum
///
public override T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{
- return (T)Enum.Parse(typeof(T), optionBehaviour.GetInt().ToString());
+ return (T)Enum.Parse(typeof(T), optionBehaviour.GetInt().ToString(NumberFormatInfo.InvariantInfo));
}
///
protected override void OnValueChanged(T newValue)
{
- HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data?.GetValueString(Convert.ToInt32(newValue)), false);
+ HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)), false);
if (!OptionBehaviour)
{
return;
@@ -188,7 +189,7 @@ public class ModdedEnumOption : ModdedOption where T : Enum
if (OptionBehaviour is StringOption opt)
{
- opt.Value = Convert.ToInt32(newValue);
+ opt.Value = Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo);
}
}
}
diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs
index 7049a8e..6b8b8cc 100644
--- a/MiraAPI/Hud/CustomActionButton.cs
+++ b/MiraAPI/Hud/CustomActionButton.cs
@@ -3,6 +3,7 @@ using MiraAPI.Events.Mira;
using MiraAPI.Patches;
using MiraAPI.Utilities.Assets;
using System;
+using System.Globalization;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
@@ -421,7 +422,7 @@ public abstract class CustomActionButton
{
Button?.SetFillUp(Timer, EffectDuration);
- Button!.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString();
+ Button!.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString(NumberFormatInfo.InvariantInfo);
Button!.cooldownTimerText.gameObject.SetActive(true);
}
else
From 4a8fb38380402e2745c080103bed37155de1aa15 Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Tue, 25 Feb 2025 17:17:40 -0500
Subject: [PATCH 36/71] improvements to timed modifier
---
MiraAPI/Modifiers/Types/TimedModifier.cs | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/MiraAPI/Modifiers/Types/TimedModifier.cs b/MiraAPI/Modifiers/Types/TimedModifier.cs
index 038fa6d..ebb0457 100644
--- a/MiraAPI/Modifiers/Types/TimedModifier.cs
+++ b/MiraAPI/Modifiers/Types/TimedModifier.cs
@@ -17,7 +17,7 @@ public abstract class TimedModifier : BaseModifier
///
/// Gets a value indicating whether the timer should start automatically when added.
///
- public virtual bool AutoStart => false;
+ public virtual bool AutoStart => true;
///
/// Gets a value indicating whether the modifier should be removed when the timer completes.
@@ -27,7 +27,9 @@ public abstract class TimedModifier : BaseModifier
///
/// Called when the timer completes.
///
- public abstract void OnTimerComplete();
+ public virtual void OnTimerComplete()
+ {
+ }
///
/// Gets or sets a value indicating whether the timer is active.
From caa27642db35486f326ac6e81a8f2e4ab539faed Mon Sep 17 00:00:00 2001
From: XtraCube <72575280+XtraCube@users.noreply.github.com>
Date: Tue, 25 Feb 2025 17:22:00 -0500
Subject: [PATCH 37/71] further modifier improvements (massive changes)
---
MiraAPI.Example/Buttons/TestButton.cs | 6 +
.../Events/Vanilla/Usables/PlayerUseEvent.cs | 2 +-
MiraAPI/Modifiers/BaseModifier.cs | 13 +-
MiraAPI/Modifiers/ModifierComponent.cs | 155 +++++++++-----
MiraAPI/Modifiers/ModifierExtensions.cs | 191 ++++++++++++------
MiraAPI/Modifiers/ModifierManager.cs | 24 +--
.../{ => Modifiers}/AddModifierRpc.cs | 2 +-
.../{ => Modifiers}/SyncModifiersRpc.cs | 2 +-
.../Patches/Events/KillAnimationPatches.cs | 2 +-
MiraAPI/Patches/Events/UsePatches.cs | 2 +-
.../Patches/Freeplay/TaskAddButtonPatches.cs | 2 +-
MiraAPI/Patches/Freeplay/TaskAdderPatches.cs | 8 +-
.../Patches/Modifiers/EndGameDidWinPatch.cs | 2 +-
MiraAPI/Patches/VentPatches.cs | 4 +-
14 files changed, 276 insertions(+), 139 deletions(-)
rename MiraAPI/Networking/{ => Modifiers}/AddModifierRpc.cs (97%)
rename MiraAPI/Networking/{ => Modifiers}/SyncModifiersRpc.cs (97%)
diff --git a/MiraAPI.Example/Buttons/TestButton.cs b/MiraAPI.Example/Buttons/TestButton.cs
index feb7992..776dc40 100644
--- a/MiraAPI.Example/Buttons/TestButton.cs
+++ b/MiraAPI.Example/Buttons/TestButton.cs
@@ -15,6 +15,12 @@ public class TestButton : CustomActionButton
protected override void OnClick()
{
+ if (PlayerControl.LocalPlayer.HasModifier())
+ {
+ PlayerControl.LocalPlayer.RpcRemoveModifier();
+ return;
+ }
+
var randomPlayer = PlayerControl.AllPlayerControls.ToArray().Random();
PlayerControl.LocalPlayer.RpcAddModifier("test", 1, randomPlayer);
}
diff --git a/MiraAPI/Events/Vanilla/Usables/PlayerUseEvent.cs b/MiraAPI/Events/Vanilla/Usables/PlayerUseEvent.cs
index 655a46c..5f05676 100644
--- a/MiraAPI/Events/Vanilla/Usables/PlayerUseEvent.cs
+++ b/MiraAPI/Events/Vanilla/Usables/PlayerUseEvent.cs
@@ -1,4 +1,4 @@
-namespace MiraAPI.Events.Vanilla;
+namespace MiraAPI.Events.Vanilla.Usables;
///
/// Event for using a from Vanilla Among Us. Will always be ran locally.
diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs
index 8834454..21ce064 100644
--- a/MiraAPI/Modifiers/BaseModifier.cs
+++ b/MiraAPI/Modifiers/BaseModifier.cs
@@ -43,13 +43,14 @@ public abstract class BaseModifier
public bool Initialized { get; internal set; }
///
- /// Get the ID of the modifier.
+ /// Gets the unique ID of the modifier.
///
- /// The ID of the modifier.
- public uint GetId()
- {
- return ModifierManager.GetModifierId(GetType()) ?? throw new InvalidOperationException("Modifier is not registered.");
- }
+ public uint UniqueId { get; internal set; }
+
+ ///
+ /// Gets the type ID of the modifier.
+ ///
+ public uint TypeId => ModifierManager.GetModifierTypeId(GetType()) ?? throw new InvalidOperationException("Modifier is not registered.");
///
/// Gets the HUD information for this modifier. Defaults to the modifier name. Does nothing if is true.
diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs
index 6cd3d5e..94ab3db 100644
--- a/MiraAPI/Modifiers/ModifierComponent.cs
+++ b/MiraAPI/Modifiers/ModifierComponent.cs
@@ -37,6 +37,14 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
private readonly List _toAdd = [];
+ private uint _nextUniqueId;
+
+ private uint GetNextUniqueId()
+ {
+ _nextUniqueId++;
+ return _nextUniqueId;
+ }
+
internal void ClearModifiers()
{
_toRemove.AddRange(Modifiers);
@@ -104,14 +112,12 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
return;
}
- var filteredModifiers = Modifiers.Where(mod => !mod.HideOnUi);
+ var filteredModifiers = Modifiers.Where(mod => !mod.HideOnUi).ToArray();
- var baseModifiers = filteredModifiers as BaseModifier[] ?? filteredModifiers.ToArray();
-
- if (baseModifiers.Length != 0 && !MeetingHud.Instance)
+ if (filteredModifiers.Length != 0 && !MeetingHud.Instance)
{
var stringBuild = new StringBuilder();
- foreach (var mod in baseModifiers)
+ foreach (var mod in filteredModifiers)
{
stringBuild.Append(CultureInfo.InvariantCulture, $"\n{mod.GetHudString()}");
}
@@ -128,11 +134,11 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
///
/// The predicate to check the modifier by.
/// The Type of the Modifier.
- /// The Modifier if it is found, null otherwise.
+ /// A collection of modifiers.
[HideFromIl2Cpp]
- public IEnumerable GetModifiersByType(Func? predicate=null) where T : BaseModifier
+ public IEnumerable GetModifiers(Func? predicate=null) where T : BaseModifier
{
- return ActiveModifiers.OfType().Where(x => predicate == null || predicate(x));
+ return Modifiers.OfType().Where(x => predicate == null || predicate(x));
}
///
@@ -140,26 +146,26 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
///
/// The modifier type.
/// The predicate to check the modifier by.
- /// The Modifier if it is found, null otherwise.
+ /// A collection of modifiers.
[HideFromIl2Cpp]
- public IEnumerable GetModifiersByType(Type type, Func? predicate=null)
+ public IEnumerable GetModifiers(Type type, Func? predicate=null)
{
- return ActiveModifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x)));
+ return Modifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x)));
}
///
- /// Gets a collection of modifiers by ID.
+ /// Gets a collection of modifiers by type ID.
///
- /// The Modifier ID.
+ /// The modifier's type ID.
/// The predicate to check the modifier by.
- /// The Modifier if it is found, null otherwise.
+ /// A collection of modifiers.
[HideFromIl2Cpp]
- public IEnumerable GetModifiersByType(uint id, Func? predicate=null)
+ public IEnumerable GetModifiers(uint id, Func? predicate=null)
{
var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
$"Cannot get modifier with id {id} because it is not registered.");
- return GetModifiersByType(type, predicate);
+ return GetModifiers(type, predicate);
}
///
@@ -191,23 +197,34 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
}
///
- /// Tries to get a modifier by its ID.
+ /// Tries to get a modifier by its type ID.
///
- /// The modifier ID.
+ /// The modifier type ID.
/// The modifier or null.
/// The predicate to check the modifier by.
/// True if the modifier was found, false otherwise.
[HideFromIl2Cpp]
- public bool TryGetModifier(uint id, out BaseModifier? modifier, Func? predicate = null)
+ public bool TryGetModifierByTypeId(uint id, out BaseModifier? modifier, Func? predicate = null)
{
- var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
- $"Cannot get modifier with id {id} because it is not registered.");
-
- return TryGetModifier(type, out modifier, predicate);
+ modifier = GetModifierByTypeId(id, predicate);
+ return modifier != null;
}
///
- /// Gets a modifier by its type, or null if the player doesn't have it.
+ /// Tries to get a modifier by its unique ID.
+ ///
+ /// The modifier unique ID.
+ /// The modifier or null.
+ /// True if the modifier was found, false otherwise.
+ [HideFromIl2Cpp]
+ public bool TryGetModifierByUniqueId(uint id, out BaseModifier? modifier)
+ {
+ modifier = GetModifierByUniqueId(id);
+ return modifier != null;
+ }
+
+ ///
+ /// Gets a modifier by its type.
///
/// The predicate to check the modifier by.
/// The Type of the Modifier.
@@ -215,11 +232,11 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
[HideFromIl2Cpp]
public T? GetModifier(Func? predicate = null) where T : BaseModifier
{
- return GetModifiersByType(predicate).FirstOrDefault();
+ return GetModifiers(predicate).FirstOrDefault();
}
///
- /// Gets a modifier by its type, or null if the player doesn't have it.
+ /// Gets a modifier by its type.
///
/// The modifier type.
/// The predicate to check the modifier by.
@@ -227,17 +244,17 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
[HideFromIl2Cpp]
public BaseModifier? GetModifier(Type type, Func? predicate = null)
{
- return GetModifiersByType(type).FirstOrDefault(predicate ?? (_ => true));
+ return GetModifiers(type).FirstOrDefault(predicate ?? (_ => true));
}
///
- /// Gets a modifier by its type, or null if the player doesn't have it.
+ /// Gets a modifier by its type ID.
///
/// The modifier ID.
/// The predicate to check the modifier by.
/// The Modifier if it is found, null otherwise.
[HideFromIl2Cpp]
- public BaseModifier? GetModifier(uint id, Func? predicate = null)
+ public BaseModifier? GetModifierByTypeId(uint id, Func? predicate = null)
{
var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
$"Cannot get modifier with id {id} because it is not registered.");
@@ -245,6 +262,17 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
return GetModifier(type, predicate);
}
+ ///
+ /// Gets a modifier by unique ID.
+ ///
+ /// The modifier's unique ID.
+ /// The modifier if it is found, or null.
+ [HideFromIl2Cpp]
+ public BaseModifier? GetModifierByUniqueId(uint id)
+ {
+ return Modifiers.Find(x => x.UniqueId == id);
+ }
+
///
/// Removes a modifier from the player.
///
@@ -280,19 +308,6 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
RemoveModifier(modifier);
}
- ///
- /// Removes a modifier from the player.
- ///
- /// The modifier ID.
- /// The predicate to check the modifier by.
- [HideFromIl2Cpp]
- public void RemoveModifier(uint modifierId, Func? predicate = null)
- {
- var type = ModifierManager.GetModifierType(modifierId) ?? throw new InvalidOperationException(
- $"Cannot remove modifier with id {modifierId} because it is not registered.");
- RemoveModifier(type, predicate);
- }
-
///
/// Removes a modifier from the player.
///
@@ -309,6 +324,36 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
_toRemove.Add(modifier);
}
+ ///
+ /// Removes a modifier from the player.
+ ///
+ /// The modifier's type ID.
+ /// The predicate to check the modifier by.
+ [HideFromIl2Cpp]
+ public void RemoveModifierByTypeId(uint typeId, Func? predicate = null)
+ {
+ var type = ModifierManager.GetModifierType(typeId) ?? throw new InvalidOperationException(
+ $"Cannot remove modifier with id {typeId} because it is not registered.");
+ RemoveModifier(type, predicate);
+ }
+
+ ///
+ /// Removes a modifier from the player.
+ ///
+ /// The modifier's unique ID.
+ [HideFromIl2Cpp]
+ public void RemoveModifierByUniqueId(uint uniqueId)
+ {
+ var modifier = GetModifierByUniqueId(uniqueId);
+ if (modifier == null)
+ {
+ Logger.Error($"Cannot remove modifier with unique id {uniqueId} because it is not active.");
+ return;
+ }
+
+ RemoveModifier(modifier);
+ }
+
///
/// Adds a modifier to the player.
///
@@ -329,8 +374,8 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
[HideFromIl2Cpp]
public BaseModifier? AddModifier(BaseModifier modifier)
{
- var id = modifier.GetId();
- if (modifier.Unique && Modifiers.Find(x => x.GetId() == id) != null)
+ var id = modifier.TypeId;
+ if (modifier.Unique && Modifiers.Find(x => x.TypeId == id) != null)
{
Logger.Error($"Player already has modifier with id {id}!");
return null;
@@ -345,6 +390,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
_toAdd.Add(modifier);
modifier.Player = _player;
modifier.ModifierComponent = this;
+ modifier.UniqueId = GetNextUniqueId();
return modifier;
}
@@ -398,7 +444,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
[HideFromIl2Cpp]
public bool HasModifier(Func? predicate=null) where T : BaseModifier
{
- return ActiveModifiers.Exists(x => x is T modifier && (predicate == null || predicate(modifier)));
+ return Modifiers.Exists(x => x is T modifier && (predicate == null || predicate(modifier)));
}
///
@@ -410,21 +456,32 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
[HideFromIl2Cpp]
public bool HasModifier(Type type, Func? predicate=null)
{
- return ActiveModifiers.Exists(x => x.GetType() == type && (predicate == null || predicate(x)));
+ return Modifiers.Exists(x => x.GetType() == type && (predicate == null || predicate(x)));
}
///
- /// Checks if a player has an active modifier by its ID.
+ /// Checks if a player has an active modifier by its type ID
///
- /// The Modifier ID.
+ /// The Modifier type ID.
/// The predicate to check the modifier.
/// True if the Modifier is present, false otherwise.
[HideFromIl2Cpp]
- public bool HasModifier(uint id, Func? predicate=null)
+ public bool HasModifierByTypeId(uint id, Func? predicate=null)
{
var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
$"Cannot get modifier with id {id} because it is not registered.");
return HasModifier(type, predicate);
}
+
+ ///
+ /// Checks if a player has an active modifier by its unique ID
+ ///
+ /// The Modifier type ID.
+ /// True if the Modifier is present, false otherwise.
+ [HideFromIl2Cpp]
+ public bool HasModifierByUniqueId(uint id)
+ {
+ return Modifiers.Exists(x => x.UniqueId == id);
+ }
}
diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs
index 226b16b..5b42c6b 100644
--- a/MiraAPI/Modifiers/ModifierExtensions.cs
+++ b/MiraAPI/Modifiers/ModifierExtensions.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using MiraAPI.Networking;
+using MiraAPI.Networking.Modifiers;
using Reactor.Networking.Attributes;
using Reactor.Networking.Rpc;
using Reactor.Utilities;
@@ -21,14 +22,14 @@ public static class ModifierExtensions
/// Remote Procedure Call to add a modifier to a player.
///
/// The player to add the modifier to.
- /// The modifier ID.
+ /// The modifier type ID.
/// The arguments to initialize the modifier constructor with.
- public static void RpcAddModifier(this PlayerControl target, uint modifierId, params object[] args)
+ public static void RpcAddModifier(this PlayerControl target, uint typeId, params object[] args)
{
- _ = ModifierManager.GetModifierType(modifierId) ?? throw new InvalidOperationException(
- $"Modifier with ID {modifierId} is not registered.");
+ _ = ModifierManager.GetModifierType(typeId) ?? throw new InvalidOperationException(
+ $"Modifier with ID {typeId} is not registered.");
- Rpc.Instance.Send(target, new ModifierData(modifierId, args));
+ Rpc.Instance.Send(target, new ModifierData(typeId, args));
}
///
@@ -39,7 +40,7 @@ public static class ModifierExtensions
/// The arguments to initialize the modifier constructor with.
public static void RpcAddModifier(this PlayerControl player, Type type, params object[] args)
{
- var id = ModifierManager.GetModifierId(type) ?? throw new InvalidOperationException(
+ var id = ModifierManager.GetModifierTypeId(type) ?? throw new InvalidOperationException(
$"Modifier {type.Name} is not registered.");
player.RpcAddModifier(id, args);
@@ -60,28 +61,61 @@ public static class ModifierExtensions
/// Remote Procedure Call to remove a modifier from a player.
///
/// The player to remove the modifier from.
- /// The ID of the modifier.
+ /// The unique ID of the modifier.
[MethodRpc((uint)MiraRpc.RemoveModifier)]
- public static void RpcRemoveModifier(this PlayerControl target, uint modifierId)
+ public static void RpcRemoveModifierByUniqueId(this PlayerControl target, uint uniqueId)
{
- target.RemoveModifier(modifierId);
+ target.RemoveModifierByUniqueId(uniqueId);
+ }
+
+ ///
+ /// Remote Procedure Call to remove a modifier from a player by type ID.
+ ///
+ /// The player to remove the modifier from.
+ /// The type ID of the modifier.
+ /// Optional predicate to filter the modifiers.
+ public static void RpcRemoveModifierByTypeId(
+ this PlayerControl target,
+ uint typeId,
+ Func? predicate = null)
+ {
+ var modifier = target.GetModifierByTypeId(typeId, predicate);
+ if (modifier is null)
+ {
+ Logger.Error($"Player {target.PlayerId} does not have modifier with type ID {typeId}.");
+ return;
+ }
+
+ target.RpcRemoveModifierByUniqueId(modifier.UniqueId);
}
///
/// 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
+ /// The type of the modifier.
+ /// Optional predicate to filter the modifiers.
+ public static void RpcRemoveModifier(
+ this PlayerControl player,
+ Type type,
+ Func? predicate = null)
{
- var id = ModifierManager.GetModifierId(typeof(T));
- if (id == null)
- {
- Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
- return;
- }
+ var id = ModifierManager.GetModifierTypeId(type) ?? throw new InvalidOperationException(
+ $"Modifier {type.Name} is not registered.");
- player.RpcRemoveModifier(id.Value);
+ player.RpcRemoveModifierByTypeId(id, predicate);
+ }
+
+ ///
+ /// Remote Procedure Call to remove a modifier from a player.
+ ///
+ /// The player to remove the modifier from.
+ /// Optional predicate to filter the modifiers.
+ /// The Type of the Modifier.
+ public static void RpcRemoveModifier(this PlayerControl player, Func? predicate = null)
+ where T : BaseModifier
+ {
+ player.RpcRemoveModifier(typeof(T), predicate);
}
///
@@ -89,7 +123,7 @@ public static class ModifierExtensions
///
/// The PlayerControl object.
/// A ModifierComponent if there is one, null otherwise.
- public static ModifierComponent? GetModifierComponent(this PlayerControl player)
+ public static ModifierComponent GetModifierComponent(this PlayerControl player)
{
if (ModifierComponents.TryGetValue(player, out var component))
{
@@ -97,9 +131,9 @@ public static class ModifierExtensions
}
component = player.GetComponent();
- if (component == null)
+ if (!component)
{
- return null;
+ throw new InvalidOperationException("ModifierComponent is not attached to the player.");
}
ModifierComponents[player] = component;
@@ -115,7 +149,7 @@ public static class ModifierExtensions
/// True if the player has the modifier, false otherwise.
public static bool HasModifier(this PlayerControl player, Func? predicate = null) where T : BaseModifier
{
- return player.GetModifierComponent()!.HasModifier(predicate);
+ return player.GetModifierComponent().HasModifier(predicate);
}
///
@@ -127,19 +161,33 @@ public static class ModifierExtensions
/// True if the player has the modifier, false otherwise.
public static bool HasModifier(this PlayerControl player, Type type, Func? predicate = null)
{
- return player.GetModifierComponent()!.HasModifier(type, predicate);
+ return player.GetModifierComponent().HasModifier(type, predicate);
}
///
- /// Checks if the player has a specific modifier by ID.
+ /// Checks if the player has a specific modifier by type ID.
///
/// The PlayerControl instance.
- /// The ID of the modifier.
+ /// The type ID of the modifier.
/// Optional predicate to filter the modifiers.
/// True if the player has the modifier, false otherwise.
- public static bool HasModifier(this PlayerControl player, uint id, Func? predicate = null)
+ public static bool HasModifierByTypeId(
+ this PlayerControl player,
+ uint id,
+ Func? predicate = null)
{
- return player.GetModifierComponent()!.HasModifier(id, predicate);
+ return player.GetModifierComponent().HasModifierByTypeId(id, predicate);
+ }
+
+ ///
+ /// Checks if the player has a specific modifier by unique ID.
+ ///
+ /// The PlayerControl instance.
+ /// The unique ID of the modifier.
+ /// True if the player has the modifier, false otherwise.
+ public static bool HasModifierByUniqueId(this PlayerControl player, uint id)
+ {
+ return player.GetModifierComponent().HasModifierByUniqueId(id);
}
///
@@ -151,7 +199,7 @@ public static class ModifierExtensions
/// The modifier if found, null otherwise.
public static T? GetModifier(this PlayerControl player, Func? predicate = null) where T : BaseModifier
{
- return player.GetModifierComponent()!.GetModifier(predicate);
+ return player.GetModifierComponent().GetModifier(predicate);
}
///
@@ -166,7 +214,22 @@ public static class ModifierExtensions
Type type,
Func? predicate = null)
{
- return player.GetModifierComponent()!.GetModifier(type, predicate);
+ return player.GetModifierComponent().GetModifier(type, predicate);
+ }
+
+ ///
+ /// Gets a specific modifier from the player by type ID.
+ ///
+ /// The PlayerControl instance.
+ /// The type ID of the modifier.
+ /// Optional predicate to filter the modifiers.
+ /// The modifier if found, null otherwise.
+ public static BaseModifier? GetModifierByTypeId(
+ this PlayerControl player,
+ uint id,
+ Func? predicate = null)
+ {
+ return player.GetModifierComponent().GetModifierByTypeId(id, predicate);
}
///
@@ -174,14 +237,12 @@ public static class ModifierExtensions
///
/// The PlayerControl instance.
/// The ID of the modifier.
- /// Optional predicate to filter the modifiers.
/// The modifier if found, null otherwise.
- public static BaseModifier? GetModifier(
+ public static BaseModifier? GetModifierByUniqueId(
this PlayerControl player,
- uint id,
- Func? predicate = null)
+ uint id)
{
- return player.GetModifierComponent()!.GetModifier(id, predicate);
+ return player.GetModifierComponent().GetModifierByUniqueId(id);
}
///
@@ -191,10 +252,10 @@ public static class ModifierExtensions
/// The PlayerControl instance.
/// Optional predicate to filter the modifiers.
/// An enumerable of modifiers.
- public static IEnumerable GetModifiersByType(this PlayerControl player, Func? predicate = null)
+ public static IEnumerable GetModifiers(this PlayerControl player, Func? predicate = null)
where T : BaseModifier
{
- return player.GetModifierComponent()!.GetModifiersByType(predicate);
+ return player.GetModifierComponent().GetModifiers(predicate);
}
///
@@ -204,27 +265,37 @@ public static class ModifierExtensions
/// The type of the modifiers.
/// Optional predicate to filter the modifiers.
/// An enumerable of modifiers.
- public static IEnumerable GetModifiersByType(
+ public static IEnumerable GetModifiers(
this PlayerControl player,
Type type,
Func? predicate = null)
{
- return player.GetModifierComponent()!.GetModifiersByType(type, predicate);
+ return player.GetModifierComponent().GetModifiers(type, predicate);
}
///
- /// Gets all modifiers of a specific type from the player by ID.
+ /// Gets all modifiers of a specific type from the player by type ID.
///
/// The PlayerControl instance.
- /// The ID of the modifiers.
+ /// The type ID of the modifiers.
/// Optional predicate to filter the modifiers.
/// An enumerable of modifiers.
- public static IEnumerable GetModifiersByType(
+ public static IEnumerable GetModifiers(
this PlayerControl player,
uint id,
Func? predicate = null)
{
- return player.GetModifierComponent()!.GetModifiersByType(id, predicate);
+ return player.GetModifierComponent().GetModifiers(id, predicate);
+ }
+
+ ///
+ /// Removes a specific modifier from the player.
+ ///
+ /// The PlayerControl instance.
+ /// The modifier to remove.
+ public static void RemoveModifier(this PlayerControl player, BaseModifier modifier)
+ {
+ player.GetModifierComponent().RemoveModifier(modifier);
}
///
@@ -236,7 +307,7 @@ public static class ModifierExtensions
public static void RemoveModifier(this PlayerControl player, Func? predicate = null)
where T : BaseModifier
{
- player.GetModifierComponent()!.RemoveModifier(predicate);
+ player.GetModifierComponent().RemoveModifier(predicate);
}
///