From 35689487aede2e701f57555632dc56537de4d831 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Sun, 2 Feb 2025 16:08:29 -0500 Subject: [PATCH 01/65] Updated events --- MiraAPI.Example/ExampleEventHandlers.cs | 11 ++++++ .../Events/Vanilla/AdminButtonClickEvent.cs | 5 ++- .../Vanilla/Map/PlayerOpenSabotageEvent.cs | 21 +++++++++++ .../Vanilla/SabotageButtonClickEvent.cs | 5 ++- .../Vanilla/Usables/PlayerCanUseEvent.cs | 15 ++++++++ .../Events/Vanilla/Usables/PlayerUseEvent.cs | 34 ++++++++++++++++++ MiraAPI/Events/Vanilla/UseButtonClickEvent.cs | 5 ++- MiraAPI/Patches/Events/CanUsePatches.cs | 36 +++++++++++++++++++ MiraAPI/Patches/Events/MapBehaviourPatches.cs | 21 +++++++++++ MiraAPI/Patches/Events/UsePatches.cs | 35 ++++++++++++++++++ MiraAPI/Patches/Events/VentEventPatches.cs | 4 +-- MiraAPI/Patches/VentPatches.cs | 16 +++++++-- MiraAPI/Utilities/Extensions.cs | 23 ++++++++++-- 13 files changed, 221 insertions(+), 10 deletions(-) create mode 100644 MiraAPI/Events/Vanilla/Map/PlayerOpenSabotageEvent.cs create mode 100644 MiraAPI/Events/Vanilla/Usables/PlayerCanUseEvent.cs create mode 100644 MiraAPI/Events/Vanilla/Usables/PlayerUseEvent.cs create mode 100644 MiraAPI/Patches/Events/CanUsePatches.cs create mode 100644 MiraAPI/Patches/Events/MapBehaviourPatches.cs create mode 100644 MiraAPI/Patches/Events/UsePatches.cs diff --git a/MiraAPI.Example/ExampleEventHandlers.cs b/MiraAPI.Example/ExampleEventHandlers.cs index 21904e4..9d18bc1 100644 --- a/MiraAPI.Example/ExampleEventHandlers.cs +++ b/MiraAPI.Example/ExampleEventHandlers.cs @@ -1,6 +1,7 @@ using MiraAPI.Events; using MiraAPI.Events.Mira; using MiraAPI.Events.Vanilla; +using MiraAPI.Events.Vanilla.Usables; using MiraAPI.Example.Buttons.Freezer; using Reactor.Utilities; @@ -14,6 +15,16 @@ public static class ExampleEventHandlers MiraEventManager.RegisterEventHandler>(FreezeButtonClickHandler, 1); MiraEventManager.RegisterEventHandler>(FreezeButtonCancelledHandler); MiraEventManager.RegisterEventHandler(UpdateSystemEventHandler); + MiraEventManager.RegisterEventHandler(PlayerControlCanUse); + MiraEventManager.RegisterEventHandler(PlayerControlUse); + } + + public static void PlayerControlCanUse(PlayerCanUseEvent @event) + { + } + + public static void PlayerControlUse(PlayerUseEvent @event) + { } public static void UpdateSystemEventHandler(UpdateSystemEvent @event) diff --git a/MiraAPI/Events/Vanilla/AdminButtonClickEvent.cs b/MiraAPI/Events/Vanilla/AdminButtonClickEvent.cs index c37b933..de02bb9 100644 --- a/MiraAPI/Events/Vanilla/AdminButtonClickEvent.cs +++ b/MiraAPI/Events/Vanilla/AdminButtonClickEvent.cs @@ -1,8 +1,11 @@ -namespace MiraAPI.Events.Vanilla; +using System; + +namespace MiraAPI.Events.Vanilla; /// /// Button click event for from Vanilla Among Us. /// +[Obsolete("This class is deprecated. Use PlayerUseEvent instead.")] public class AdminButtonClickEvent : MiraCancelableEvent { /// diff --git a/MiraAPI/Events/Vanilla/Map/PlayerOpenSabotageEvent.cs b/MiraAPI/Events/Vanilla/Map/PlayerOpenSabotageEvent.cs new file mode 100644 index 0000000..8b2b6cb --- /dev/null +++ b/MiraAPI/Events/Vanilla/Map/PlayerOpenSabotageEvent.cs @@ -0,0 +1,21 @@ +namespace MiraAPI.Events.Vanilla.Map; + +/// +/// Sabotage from Vanilla Among Us. +/// +public class PlayerOpenSabotageEvent : MiraCancelableEvent +{ + /// + /// Gets the MapBehaviour. + /// + public MapBehaviour MapBehaviour { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The MapBehaviour. + public PlayerOpenSabotageEvent(MapBehaviour mapBehaviour) + { + MapBehaviour = mapBehaviour; + } +} diff --git a/MiraAPI/Events/Vanilla/SabotageButtonClickEvent.cs b/MiraAPI/Events/Vanilla/SabotageButtonClickEvent.cs index b657486..9d3d2ba 100644 --- a/MiraAPI/Events/Vanilla/SabotageButtonClickEvent.cs +++ b/MiraAPI/Events/Vanilla/SabotageButtonClickEvent.cs @@ -1,8 +1,11 @@ -namespace MiraAPI.Events.Vanilla; +using System; + +namespace MiraAPI.Events.Vanilla; /// /// Button click event for from Vanilla Among Us. /// +[Obsolete("This class is deprecated. Use PlayerOpenSabotageEvent instead.")] public class SabotageButtonClickEvent : MiraCancelableEvent { /// diff --git a/MiraAPI/Events/Vanilla/Usables/PlayerCanUseEvent.cs b/MiraAPI/Events/Vanilla/Usables/PlayerCanUseEvent.cs new file mode 100644 index 0000000..f9ba115 --- /dev/null +++ b/MiraAPI/Events/Vanilla/Usables/PlayerCanUseEvent.cs @@ -0,0 +1,15 @@ +namespace MiraAPI.Events.Vanilla.Usables; + +/// +/// Event for if a can use an from Vanilla Among Us. Will always be ran locally. +/// +public class PlayerCanUseEvent : PlayerUseEvent +{ + /// + /// Initializes a new instance of the class. + /// + /// The IUsable. + public PlayerCanUseEvent(IUsable usable) : base(usable) + { + } +} diff --git a/MiraAPI/Events/Vanilla/Usables/PlayerUseEvent.cs b/MiraAPI/Events/Vanilla/Usables/PlayerUseEvent.cs new file mode 100644 index 0000000..655a46c --- /dev/null +++ b/MiraAPI/Events/Vanilla/Usables/PlayerUseEvent.cs @@ -0,0 +1,34 @@ +namespace MiraAPI.Events.Vanilla; + +/// +/// Event for using a from Vanilla Among Us. Will always be ran locally. +/// +public class PlayerUseEvent : MiraCancelableEvent +{ + /// + /// Gets the instance of that was used. + /// + public IUsable Usable { get; } + + /// + /// Gets a value indicating whether the IUsable is a , , or . + /// + public bool IsPrimaryConsole { get; } + + /// + /// Gets a value indicating whether the IUsable is a . + /// + public bool IsVent { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The IUsable. + public PlayerUseEvent(IUsable usable) + { + Usable = usable; + + IsPrimaryConsole = usable.TryCast() || usable.TryCast() || usable.TryCast(); + IsVent = usable.TryCast(); + } +} diff --git a/MiraAPI/Events/Vanilla/UseButtonClickEvent.cs b/MiraAPI/Events/Vanilla/UseButtonClickEvent.cs index 043b166..8b859fb 100644 --- a/MiraAPI/Events/Vanilla/UseButtonClickEvent.cs +++ b/MiraAPI/Events/Vanilla/UseButtonClickEvent.cs @@ -1,8 +1,11 @@ -namespace MiraAPI.Events.Vanilla; +using System; + +namespace MiraAPI.Events.Vanilla; /// /// Button click event for from Vanilla Among Us. /// +[Obsolete("This class is deprecated. Use PlayerUseEvent instead.")] public class UseButtonClickEvent : MiraCancelableEvent { /// diff --git a/MiraAPI/Patches/Events/CanUsePatches.cs b/MiraAPI/Patches/Events/CanUsePatches.cs new file mode 100644 index 0000000..56a88e6 --- /dev/null +++ b/MiraAPI/Patches/Events/CanUsePatches.cs @@ -0,0 +1,36 @@ +using HarmonyLib; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla.Usables; +using System.Collections.Generic; +using System.Reflection; + +namespace MiraAPI.Patches.Events; + +[HarmonyPatch] +public static class CanUsePatches +{ + public static IEnumerable TargetMethods() + { + yield return AccessTools.Method(typeof(Console), nameof(Console.CanUse)); + yield return AccessTools.Method(typeof(MapConsole), nameof(MapConsole.CanUse)); + yield return AccessTools.Method(typeof(SystemConsole), nameof(SystemConsole.CanUse)); + yield return AccessTools.Method(typeof(Ladder), nameof(Ladder.CanUse)); + yield return AccessTools.Method(typeof(PlatformConsole), nameof(PlatformConsole.CanUse)); + yield return AccessTools.Method(typeof(OpenDoorConsole), nameof(OpenDoorConsole.CanUse)); + yield return AccessTools.Method(typeof(DoorConsole), nameof(DoorConsole.CanUse)); + yield return AccessTools.Method(typeof(OptionsConsole), nameof(OptionsConsole.CanUse)); + yield return AccessTools.Method(typeof(ZiplineConsole), nameof(ZiplineConsole.CanUse)); + } + + [HarmonyPriority(Priority.Last)] + [HarmonyPrefix] + public static bool CanUsePatch(Il2CppSystem.Object __instance, [HarmonyArgument(0)] NetworkedPlayerInfo pc, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse) + { + var @event = new PlayerCanUseEvent(__instance.Cast()); + MiraEventManager.InvokeEvent(@event); + + canUse = couldUse = false; + + return !@event.IsCancelled; + } +} diff --git a/MiraAPI/Patches/Events/MapBehaviourPatches.cs b/MiraAPI/Patches/Events/MapBehaviourPatches.cs new file mode 100644 index 0000000..a409226 --- /dev/null +++ b/MiraAPI/Patches/Events/MapBehaviourPatches.cs @@ -0,0 +1,21 @@ +using HarmonyLib; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla.Map; + +namespace MiraAPI.Patches.Events; + +/// +/// Patch for map related MiraEvents. +/// +[HarmonyPatch] +public static class MapBehaviourPatches +{ + [HarmonyPrefix] + [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.ShowSabotageMap))] + public static bool MapShowSabotagePatch(MapBehaviour __instance) + { + var @event = new PlayerOpenSabotageEvent(__instance); + MiraEventManager.InvokeEvent(@event); + return !@event.IsCancelled; + } +} diff --git a/MiraAPI/Patches/Events/UsePatches.cs b/MiraAPI/Patches/Events/UsePatches.cs new file mode 100644 index 0000000..beb1f69 --- /dev/null +++ b/MiraAPI/Patches/Events/UsePatches.cs @@ -0,0 +1,35 @@ +using HarmonyLib; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla; +using System.Collections.Generic; +using System.Reflection; + +namespace MiraAPI.Patches.Events; + +[HarmonyPatch] +public static class UsePatches +{ + public static IEnumerable TargetMethods() + { + yield return AccessTools.Method(typeof(Console), nameof(Console.Use)); + yield return AccessTools.Method(typeof(MapConsole), nameof(MapConsole.Use)); + yield return AccessTools.Method(typeof(SystemConsole), nameof(SystemConsole.Use)); + yield return AccessTools.Method(typeof(Vent), nameof(Vent.Use)); + yield return AccessTools.Method(typeof(Ladder), nameof(Ladder.Use)); + yield return AccessTools.Method(typeof(PlatformConsole), nameof(PlatformConsole.Use)); + yield return AccessTools.Method(typeof(OpenDoorConsole), nameof(OpenDoorConsole.Use)); + yield return AccessTools.Method(typeof(DoorConsole), nameof(DoorConsole.Use)); + yield return AccessTools.Method(typeof(OptionsConsole), nameof(OptionsConsole.Use)); + yield return AccessTools.Method(typeof(ZiplineConsole), nameof(ZiplineConsole.Use)); + } + + [HarmonyPriority(Priority.Last)] + [HarmonyPrefix] + public static bool UsePatch(Il2CppSystem.Object __instance) + { + var @event = new PlayerUseEvent(__instance.Cast()); + MiraEventManager.InvokeEvent(@event); + + return !@event.IsCancelled; + } +} diff --git a/MiraAPI/Patches/Events/VentEventPatches.cs b/MiraAPI/Patches/Events/VentEventPatches.cs index 2617ff8..e8e5661 100644 --- a/MiraAPI/Patches/Events/VentEventPatches.cs +++ b/MiraAPI/Patches/Events/VentEventPatches.cs @@ -1,7 +1,7 @@ -using System.Linq; -using HarmonyLib; +using HarmonyLib; using MiraAPI.Events; using MiraAPI.Events.Vanilla; +using System.Linq; namespace MiraAPI.Patches.Events; diff --git a/MiraAPI/Patches/VentPatches.cs b/MiraAPI/Patches/VentPatches.cs index 66c9aae..621abbc 100644 --- a/MiraAPI/Patches/VentPatches.cs +++ b/MiraAPI/Patches/VentPatches.cs @@ -1,4 +1,6 @@ using HarmonyLib; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla.Usables; using MiraAPI.Roles; using MiraAPI.Utilities; using UnityEngine; @@ -18,6 +20,15 @@ public static class VentPatches [HarmonyPatch(nameof(Vent.CanUse))] public static void VentCanUsePostfix(Vent __instance, ref float __result, [HarmonyArgument(0)] NetworkedPlayerInfo pc, [HarmonyArgument(1)] ref bool canUse, [HarmonyArgument(2)] ref bool couldUse) { + var @event = new PlayerCanUseEvent(__instance.Cast()); + MiraEventManager.InvokeEvent(@event); + + if (@event.IsCancelled) + { + canUse = couldUse = false; + return; + } + var @object = pc.Object; var role = @object.Data.Role; @@ -29,10 +40,10 @@ public static class VentPatches { switch (canVent) { - case true when modifiers.Exists(x => x.CanVent().HasValue && x.CanVent()==false): + case true when modifiers.Exists(x => x.CanVent().HasValue && x.CanVent() == false): couldUse = canUse = false; return; - case false when modifiers.Exists(x => x.CanVent().HasValue && x.CanVent()==true): + case false when modifiers.Exists(x => x.CanVent().HasValue && x.CanVent() == true): couldUse = true; break; } @@ -48,6 +59,7 @@ public static class VentPatches num = Vector2.Distance(center, position); canUse &= num <= __instance.UsableDistance && !PhysicsHelpers.AnythingBetween(@object.Collider, center, position, Constants.ShipOnlyMask, false); } + __result = num; } } diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 032cff3..636601b 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using HarmonyLib; using MiraAPI.GameOptions; using MiraAPI.Modifiers; @@ -8,6 +5,10 @@ using MiraAPI.Networking; using MiraAPI.Roles; using Reactor.Networking.Attributes; using Reactor.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using UnityEngine; namespace MiraAPI.Utilities; @@ -17,6 +18,22 @@ namespace MiraAPI.Utilities; /// public static class Extensions { + public static IEnumerable GetTypesSafe(this Assembly assembly) + { + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + return ex.Types.Where(t => t != null); + } + catch + { + return Enumerable.Empty(); + } + } + internal static NetData GetNetData(this ICustomRole role) { var count = role.GetCount(); From a65de5a938296636dda27f58c42b613ea725c101 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:46:56 -0500 Subject: [PATCH 02/65] Epic CI Win! --- .github/workflows/main.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 278c205..ad0f754 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -6,11 +6,11 @@ jobs: build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: | ~/.nuget/packages @@ -20,7 +20,7 @@ jobs: ${{ runner.os }}-nuget- - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x @@ -29,17 +29,17 @@ jobs: with: verbosity: Diagnostic - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: MiraAPI.dll path: MiraAPI/bin/Release/net6.0/MiraAPI.dll - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: AllOfUs.MiraAPI.nupkg path: MiraAPI/bin/Release/AllOfUs.MiraAPI.*.nupkg - - uses: softprops/action-gh-release@v1 + - uses: softprops/action-gh-release@v2 if: github.ref_type == 'tag' with: draft: true From dcf557ca46411351d4de9d81dbfd28487e9a8218 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:57:10 -0500 Subject: [PATCH 03/65] optimize plugin loading --- MiraAPI/PluginLoading/MiraPluginManager.cs | 169 ++++++++++----------- 1 file changed, 82 insertions(+), 87 deletions(-) diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index acb6ebe..8dc0f17 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -38,17 +38,26 @@ public sealed class MiraPluginManager } var info = new MiraPluginInfo(miraPlugin, pluginInfo); + var roles = new List(); - RegisterModifierAttribute(assembly); - RegisterAllOptions(assembly, info); + foreach (var type in assembly.GetTypes()) + { + RegisterModifierAttribute(type); + RegisterOptions(type, info); - RegisterRoleAttribute(assembly, info); - RegisterButtonAttribute(assembly, info); + if (RegisterRoleAttribute(type, info, out var role)) + { + roles.Add(role!); + } - RegisterColorClasses(assembly); + RegisterButtonAttribute(type, info); + RegisterColorClasses(type); + } + + info.OptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority)); + CustomRoleManager.RegisterRoleTypes(roles, info); _registeredPlugins.Add(assembly, info); - Logger.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API."); }; IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors; @@ -65,121 +74,107 @@ public sealed class MiraPluginManager return Instance._registeredPlugins.Values.First(plugin => plugin.PluginId == guid); } - private static void RegisterAllOptions(Assembly assembly, MiraPluginInfo pluginInfo) + private static void RegisterOptions(Type type, MiraPluginInfo pluginInfo) { - var filteredTypes = assembly.GetTypes().Where(type => type.IsAssignableTo(typeof(AbstractOptionGroup))); - - foreach (var type in filteredTypes) + if (!type.IsAssignableTo(typeof(AbstractOptionGroup))) { - if (!ModdedOptionsManager.RegisterGroup(type, pluginInfo)) + return; + } + + if (!ModdedOptionsManager.RegisterGroup(type, pluginInfo)) + { + return; + } + + foreach (var property in type.GetProperties()) + { + if (property.PropertyType.IsAssignableTo(typeof(IModdedOption))) { + ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo); continue; } - foreach (var property in type.GetProperties()) - { - if (property.PropertyType.IsAssignableTo(typeof(IModdedOption))) - { - ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo); - continue; - } - - var attribute = property.GetCustomAttribute(); - if (attribute == null) - { - continue; - } - - ModdedOptionsManager.RegisterAttributeOption(type, attribute, property, pluginInfo); - } - } - - pluginInfo.OptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority)); - } - - private static void RegisterRoleAttribute(Assembly assembly, MiraPluginInfo pluginInfo) - { - List roles = []; - foreach (var type in assembly.GetTypes()) - { - var attribute = type.GetCustomAttribute(); + var attribute = property.GetCustomAttribute(); if (attribute == null) { continue; } - if (!(typeof(RoleBehaviour).IsAssignableFrom(type) && typeof(ICustomRole).IsAssignableFrom(type))) - { - Logger.Error($"{type.Name} does not inherit from RoleBehaviour or ICustomRole."); - continue; - } - - if (!ModList.GetById(pluginInfo.PluginId).IsRequiredOnAllClients) - { - Logger.Error("Custom roles are only supported on all clients."); - return; - } - - roles.Add(type); + ModdedOptionsManager.RegisterAttributeOption(type, attribute, property, pluginInfo); } - - CustomRoleManager.RegisterRoleTypes(roles, pluginInfo); } - private static void RegisterColorClasses(Assembly assembly) + private static bool RegisterRoleAttribute(Type type, MiraPluginInfo pluginInfo, out Type? role) { - foreach (var type in assembly.GetTypes()) + role = null; + + var attribute = type.GetCustomAttribute(); + if (attribute == null) { - if (type.GetCustomAttribute() == null) + return false; + } + + if (!(typeof(RoleBehaviour).IsAssignableFrom(type) && typeof(ICustomRole).IsAssignableFrom(type))) + { + Logger.Error($"{type.Name} does not inherit from RoleBehaviour or ICustomRole."); + return false; + } + + if (!ModList.GetById(pluginInfo.PluginId).IsRequiredOnAllClients) + { + Logger.Error("Custom roles are only supported on all clients."); + return false; + } + + role = type; + return true; + } + + private static void RegisterColorClasses(Type type) + { + if (type.GetCustomAttribute() == null) + { + return; + } + + if (!type.IsStatic()) + { + Logger.Error($"Color class {type.Name} must be static."); + return; + } + + foreach (var property in type.GetProperties()) + { + if (property.PropertyType != typeof(CustomColor)) { continue; } - if (!type.IsStatic()) + if (property.GetValue(null) is not CustomColor color) { - Logger.Error($"Color class {type.Name} must be static."); + Logger.Error($"Color property {property.Name} in {type.Name} is not a CustomColor."); continue; } - foreach (var property in type.GetProperties()) - { - if (property.PropertyType != typeof(CustomColor)) - { - continue; - } - - if (property.GetValue(null) is not CustomColor color) - { - Logger.Error($"Color property {property.Name} in {type.Name} is not a CustomColor."); - continue; - } - - PaletteManager.CustomColors.Add(color); - } + PaletteManager.CustomColors.Add(color); } } - private static void RegisterModifierAttribute(Assembly assembly) + private static void RegisterModifierAttribute(Type type) { - foreach (var type in assembly.GetTypes()) + var attribute = type.GetCustomAttribute(); + if (attribute != null) { - var attribute = type.GetCustomAttribute(); - if (attribute != null) - { - ModifierManager.RegisterModifier(type); - } + ModifierManager.RegisterModifier(type); } } - private static void RegisterButtonAttribute(Assembly assembly, MiraPluginInfo pluginInfo) + private static void RegisterButtonAttribute(Type type, MiraPluginInfo pluginInfo) { - foreach (var type in assembly.GetTypes()) + var attribute = type.GetCustomAttribute(); + if (attribute != null) { - var attribute = type.GetCustomAttribute(); - if (attribute != null) - { - CustomButtonManager.RegisterButton(type, pluginInfo); - } + CustomButtonManager.RegisterButton(type, pluginInfo); } } } From 145ae304ebb890f456b73d0a2317b656088ffdb7 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:59:44 -0500 Subject: [PATCH 04/65] remove extra thingy --- MiraAPI/Utilities/Extensions.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 636601b..da51eec 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -8,7 +8,6 @@ using Reactor.Utilities; using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using UnityEngine; namespace MiraAPI.Utilities; @@ -18,22 +17,6 @@ namespace MiraAPI.Utilities; /// public static class Extensions { - public static IEnumerable GetTypesSafe(this Assembly assembly) - { - try - { - return assembly.GetTypes(); - } - catch (ReflectionTypeLoadException ex) - { - return ex.Types.Where(t => t != null); - } - catch - { - return Enumerable.Empty(); - } - } - internal static NetData GetNetData(this ICustomRole role) { var count = role.GetCount(); From e569c270103bfcf1c64af0df4fd1362dd1f97bbf Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 3 Feb 2025 21:58:57 -0500 Subject: [PATCH 05/65] replace most attributes and add MiraDisableAttribute.cs --- .../Buttons/Freezer/FreezeButton.cs | 1 - MiraAPI.Example/Buttons/MeetingButton.cs | 1 - .../Buttons/Teleporter/TeleportButton.cs | 1 - MiraAPI.Example/Modifiers/CaptainModifier.cs | 1 - .../Modifiers/Freezer/FreezeModifier.cs | 1 - .../Modifiers/HighPriorityModifier.cs | 1 - MiraAPI.Example/Roles/ChameleonRole.cs | 1 - MiraAPI.Example/Roles/FreezerRole.cs | 1 - MiraAPI.Example/Roles/NeutralKillerRole.cs | 1 - MiraAPI.Example/Roles/TeleporterRole.cs | 1 - MiraAPI/GameModes/DefaultMode.cs | 1 - .../GameModes/RegisterGameModeAttribute.cs | 28 ------------------- MiraAPI/Hud/RegisterButtonAttribute.cs | 9 ------ .../Modifiers/RegisterModifierAttribute.cs | 9 ------ MiraAPI/PluginLoading/MiraDisableAttribute.cs | 9 ++++++ MiraAPI/PluginLoading/MiraPluginManager.cs | 21 ++++++-------- MiraAPI/Roles/RegisterCustomRoleAttribute.cs | 6 ---- 17 files changed, 18 insertions(+), 75 deletions(-) delete mode 100644 MiraAPI/GameModes/RegisterGameModeAttribute.cs delete mode 100644 MiraAPI/Hud/RegisterButtonAttribute.cs delete mode 100644 MiraAPI/Modifiers/RegisterModifierAttribute.cs create mode 100644 MiraAPI/PluginLoading/MiraDisableAttribute.cs delete mode 100644 MiraAPI/Roles/RegisterCustomRoleAttribute.cs diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs index 8db98c7..e26405e 100644 --- a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs +++ b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs @@ -9,7 +9,6 @@ using UnityEngine; namespace MiraAPI.Example.Buttons.Freezer; -[RegisterButton] public class FreezeButton : CustomActionButton { public override string Name => "Freeze"; diff --git a/MiraAPI.Example/Buttons/MeetingButton.cs b/MiraAPI.Example/Buttons/MeetingButton.cs index 85b8ff1..fed4e1f 100644 --- a/MiraAPI.Example/Buttons/MeetingButton.cs +++ b/MiraAPI.Example/Buttons/MeetingButton.cs @@ -6,7 +6,6 @@ using UnityEngine; namespace MiraAPI.Example.Buttons; -[RegisterButton] public class MeetingButton : CustomActionButton { public override string Name => "Call Meeting"; diff --git a/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs b/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs index 564b738..9341596 100644 --- a/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs +++ b/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs @@ -9,7 +9,6 @@ using UnityEngine; namespace MiraAPI.Example.Buttons.Teleporter; -[RegisterButton] public class TeleportButton : CustomActionButton { public override string Name => "Teleport"; diff --git a/MiraAPI.Example/Modifiers/CaptainModifier.cs b/MiraAPI.Example/Modifiers/CaptainModifier.cs index 09c733c..60691a8 100644 --- a/MiraAPI.Example/Modifiers/CaptainModifier.cs +++ b/MiraAPI.Example/Modifiers/CaptainModifier.cs @@ -3,7 +3,6 @@ using MiraAPI.Modifiers.Types; namespace MiraAPI.Example.Modifiers; -[RegisterModifier] public class CaptainModifier : GameModifier { public override string ModifierName => "Captain"; diff --git a/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs b/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs index 12a87ec..642e941 100644 --- a/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs +++ b/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs @@ -6,7 +6,6 @@ using UnityEngine; namespace MiraAPI.Example.Modifiers.Freezer; -[RegisterModifier] public class FreezeModifier : TimedModifier { public override string ModifierName => "Freezed"; diff --git a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs index c96507a..b94c4d0 100644 --- a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs +++ b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs @@ -3,7 +3,6 @@ using MiraAPI.Modifiers.Types; namespace MiraAPI.Example.Modifiers; -[RegisterModifier] public class HighPriorityModifier : GameModifier { public override string ModifierName => "High Priority"; diff --git a/MiraAPI.Example/Roles/ChameleonRole.cs b/MiraAPI.Example/Roles/ChameleonRole.cs index e3eb6c4..1f2f61f 100644 --- a/MiraAPI.Example/Roles/ChameleonRole.cs +++ b/MiraAPI.Example/Roles/ChameleonRole.cs @@ -4,7 +4,6 @@ using UnityEngine; namespace MiraAPI.Example.Roles; -[RegisterCustomRole] public class ChameloenRole : CrewmateRole, ICustomRole { public string RoleName => "Chamelon"; diff --git a/MiraAPI.Example/Roles/FreezerRole.cs b/MiraAPI.Example/Roles/FreezerRole.cs index 4f07124..bef24d2 100644 --- a/MiraAPI.Example/Roles/FreezerRole.cs +++ b/MiraAPI.Example/Roles/FreezerRole.cs @@ -3,7 +3,6 @@ using UnityEngine; namespace MiraAPI.Example.Roles; -[RegisterCustomRole] public class FreezerRole : ImpostorRole, ICustomRole { public string RoleName => "Freezer"; diff --git a/MiraAPI.Example/Roles/NeutralKillerRole.cs b/MiraAPI.Example/Roles/NeutralKillerRole.cs index 0eb60af..50a8810 100644 --- a/MiraAPI.Example/Roles/NeutralKillerRole.cs +++ b/MiraAPI.Example/Roles/NeutralKillerRole.cs @@ -3,7 +3,6 @@ using UnityEngine; namespace MiraAPI.Example.Roles; -[RegisterCustomRole] public class NeutralKillerRole : ImpostorRole, ICustomRole { public string RoleName => "Neutral Killer"; diff --git a/MiraAPI.Example/Roles/TeleporterRole.cs b/MiraAPI.Example/Roles/TeleporterRole.cs index 447e185..9877f84 100644 --- a/MiraAPI.Example/Roles/TeleporterRole.cs +++ b/MiraAPI.Example/Roles/TeleporterRole.cs @@ -3,7 +3,6 @@ using UnityEngine; namespace MiraAPI.Example.Roles; -[RegisterCustomRole] public class TeleporterRole : CrewmateRole, ICustomRole { public string RoleName => "Teleporter"; diff --git a/MiraAPI/GameModes/DefaultMode.cs b/MiraAPI/GameModes/DefaultMode.cs index 73c4ca8..83c11ba 100644 --- a/MiraAPI/GameModes/DefaultMode.cs +++ b/MiraAPI/GameModes/DefaultMode.cs @@ -1,6 +1,5 @@ namespace MiraAPI.GameModes; -[RegisterGameMode] public class DefaultMode : CustomGameMode { public override string Name => "Default"; diff --git a/MiraAPI/GameModes/RegisterGameModeAttribute.cs b/MiraAPI/GameModes/RegisterGameModeAttribute.cs deleted file mode 100644 index 4c66c35..0000000 --- a/MiraAPI/GameModes/RegisterGameModeAttribute.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace MiraAPI.GameModes; - -[AttributeUsage(AttributeTargets.Class)] -public class RegisterGameModeAttribute : Attribute -{ - private static readonly HashSet RegisteredAssemblies = []; - - internal static void Register(Assembly assembly) - { - if (!RegisteredAssemblies.Add(assembly)) - { - return; - } - - foreach (var type in assembly.GetTypes()) - { - var attribute = type.GetCustomAttribute(); - if (attribute != null) - { - CustomGameModeManager.RegisterGameMode(type); - } - } - } -} diff --git a/MiraAPI/Hud/RegisterButtonAttribute.cs b/MiraAPI/Hud/RegisterButtonAttribute.cs deleted file mode 100644 index 82e0ca5..0000000 --- a/MiraAPI/Hud/RegisterButtonAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace MiraAPI.Hud; - -/// -/// Attribute to register a button in the HUD. Necessary for the button to be recognized by Mira. -/// -[AttributeUsage(AttributeTargets.Class)] -public class RegisterButtonAttribute : Attribute; diff --git a/MiraAPI/Modifiers/RegisterModifierAttribute.cs b/MiraAPI/Modifiers/RegisterModifierAttribute.cs deleted file mode 100644 index 4858b96..0000000 --- a/MiraAPI/Modifiers/RegisterModifierAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace MiraAPI.Modifiers; - -/// -/// Marks a class as a modifier that can be registered with the . -/// -[AttributeUsage(AttributeTargets.Class)] -public class RegisterModifierAttribute : Attribute; diff --git a/MiraAPI/PluginLoading/MiraDisableAttribute.cs b/MiraAPI/PluginLoading/MiraDisableAttribute.cs new file mode 100644 index 0000000..23c1e28 --- /dev/null +++ b/MiraAPI/PluginLoading/MiraDisableAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace MiraAPI.PluginLoading; + +/// +/// Skip an element during plugin loading. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] +public class MiraDisableAttribute : Attribute; diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 8dc0f17..5c8e573 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -42,7 +42,12 @@ public sealed class MiraPluginManager foreach (var type in assembly.GetTypes()) { - RegisterModifierAttribute(type); + if (type.GetCustomAttribute() != null) + { + continue; + } + + RegisterModifier(type); RegisterOptions(type, info); if (RegisterRoleAttribute(type, info, out var role)) @@ -108,12 +113,6 @@ public sealed class MiraPluginManager { role = null; - var attribute = type.GetCustomAttribute(); - if (attribute == null) - { - return false; - } - if (!(typeof(RoleBehaviour).IsAssignableFrom(type) && typeof(ICustomRole).IsAssignableFrom(type))) { Logger.Error($"{type.Name} does not inherit from RoleBehaviour or ICustomRole."); @@ -160,10 +159,9 @@ public sealed class MiraPluginManager } } - private static void RegisterModifierAttribute(Type type) + private static void RegisterModifier(Type type) { - var attribute = type.GetCustomAttribute(); - if (attribute != null) + if (type.IsAssignableTo(typeof(BaseModifier))) { ModifierManager.RegisterModifier(type); } @@ -171,8 +169,7 @@ public sealed class MiraPluginManager private static void RegisterButtonAttribute(Type type, MiraPluginInfo pluginInfo) { - var attribute = type.GetCustomAttribute(); - if (attribute != null) + if (type.IsAssignableTo(typeof(CustomActionButton)) || type.IsAssignableTo(typeof(CustomActionButton<>))) { CustomButtonManager.RegisterButton(type, pluginInfo); } diff --git a/MiraAPI/Roles/RegisterCustomRoleAttribute.cs b/MiraAPI/Roles/RegisterCustomRoleAttribute.cs deleted file mode 100644 index a6352f1..0000000 --- a/MiraAPI/Roles/RegisterCustomRoleAttribute.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System; - -namespace MiraAPI.Roles; - -[AttributeUsage(AttributeTargets.Class)] -public class RegisterCustomRoleAttribute : Attribute; From 22718a708cc28f47384e0dd98b80eb243d7f74b5 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Thu, 6 Feb 2025 20:24:53 -0500 Subject: [PATCH 06/65] New events and more flexibility --- .../Events/Vanilla/Meeting/EjectionEvent.cs | 28 ++++++++ .../{ => Meeting}/StartMeetingEvent.cs | 4 +- .../Events/Vanilla/Player/IntroBeginEvent.cs | 21 ++++++ MiraAPI/Hud/CustomActionButton.cs | 70 +++++++++++++++---- MiraAPI/Hud/CustomButtonManager.cs | 8 +-- .../Patches/Events/StartMeetingEventPatch.cs | 2 +- MiraAPI/Patches/HudManagerPatches.cs | 19 +++-- MiraAPI/Patches/IntroCutscenePatches.cs | 10 +++ MiraAPI/Patches/Roles/EjectionPatches.cs | 9 +++ MiraAPI/Roles/ICustomRole.cs | 10 +-- 10 files changed, 151 insertions(+), 30 deletions(-) create mode 100644 MiraAPI/Events/Vanilla/Meeting/EjectionEvent.cs rename MiraAPI/Events/Vanilla/{ => Meeting}/StartMeetingEvent.cs (91%) create mode 100644 MiraAPI/Events/Vanilla/Player/IntroBeginEvent.cs diff --git a/MiraAPI/Events/Vanilla/Meeting/EjectionEvent.cs b/MiraAPI/Events/Vanilla/Meeting/EjectionEvent.cs new file mode 100644 index 0000000..31b08c1 --- /dev/null +++ b/MiraAPI/Events/Vanilla/Meeting/EjectionEvent.cs @@ -0,0 +1,28 @@ +namespace MiraAPI.Events.Vanilla.Meeting; + +/// +/// The event that is invoked when a player is ejected. +/// +public class EjectionEvent : MiraCancelableEvent +{ + /// + /// Gets the player that is being ejected. + /// + public PlayerControl? Player { get; } + + /// + /// Gets the instance of the ExileController. + /// + public ExileController ExileController { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The player who is being ejected. + /// The exile controller. + public EjectionEvent(PlayerControl player, ExileController controller) + { + Player = player; + ExileController = controller; + } +} diff --git a/MiraAPI/Events/Vanilla/StartMeetingEvent.cs b/MiraAPI/Events/Vanilla/Meeting/StartMeetingEvent.cs similarity index 91% rename from MiraAPI/Events/Vanilla/StartMeetingEvent.cs rename to MiraAPI/Events/Vanilla/Meeting/StartMeetingEvent.cs index 2e3d8c2..b988b2f 100644 --- a/MiraAPI/Events/Vanilla/StartMeetingEvent.cs +++ b/MiraAPI/Events/Vanilla/Meeting/StartMeetingEvent.cs @@ -1,7 +1,7 @@ -namespace MiraAPI.Events.Vanilla; +namespace MiraAPI.Events.Vanilla.Meeting; /// -/// The event that is invoked when a meeting is called +/// The event that is invoked when a meeting is called. /// public class StartMeetingEvent : MiraCancelableEvent { diff --git a/MiraAPI/Events/Vanilla/Player/IntroBeginEvent.cs b/MiraAPI/Events/Vanilla/Player/IntroBeginEvent.cs new file mode 100644 index 0000000..817f6e7 --- /dev/null +++ b/MiraAPI/Events/Vanilla/Player/IntroBeginEvent.cs @@ -0,0 +1,21 @@ +namespace MiraAPI.Events.Vanilla.Meeting; + +/// +/// The event that is invoked when the intro cutscene is shown. Non cancellable. +/// +public class IntroBeginEvent : MiraEvent +{ + /// + /// Gets the instance of the IntroCutscene. + /// + public IntroCutscene IntroCutscene { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The exile controller. + public IntroBeginEvent(IntroCutscene introCutscene) + { + IntroCutscene = introCutscene; + } +} diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index 3e47d59..fcefc95 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -1,7 +1,8 @@ -using System; -using MiraAPI.Events; +using MiraAPI.Events; using MiraAPI.Events.Mira; +using MiraAPI.Patches; using MiraAPI.Utilities.Assets; +using System; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; @@ -47,7 +48,7 @@ public abstract class CustomActionButton /// /// Gets the location of the button on the screen. /// - public virtual ButtonLocation Location => ButtonLocation.BottomLeft; + public virtual ButtonLocation Location { get; private set; } = ButtonLocation.BottomLeft; /// /// Gets a value indicating whether the button has an effect ability. @@ -79,7 +80,11 @@ public abstract class CustomActionButton /// protected ActionButton? Button { get; private set; } - internal void CreateButton(Transform parent) + /// + /// The method used to create the button. + /// + /// The parent of the button. + public virtual void CreateButton(Transform parent) { if (Button) { @@ -142,10 +147,49 @@ public abstract class CustomActionButton })); } + /// + /// Allows you to change the button's location. + /// + /// The new location. + /// Whether the button's position should change ingame. + public virtual void SetButtonLocation(ButtonLocation location, bool moveButton = true) + { + if (HudManager.Instance == null || Button == null) + { + return; + } + + this.Location = location; + + if (!moveButton) return; + + if (HudManagerPatches._bottomLeft == null || HudManagerPatches._bottomRight == null) + { + return; + } + + switch (location) + { + case ButtonLocation.BottomLeft: + var gridArrange = HudManagerPatches._bottomLeft.GetComponent(); + var aspectPosition = HudManagerPatches._bottomLeft.GetComponent(); + + Button.transform.SetParent(HudManagerPatches._bottomLeft.transform); + + gridArrange.Start(); + gridArrange.ArrangeChilds(); + aspectPosition.AdjustPosition(); + break; + case ButtonLocation.BottomRight: + Button.transform.SetParent(HudManagerPatches._bottomRight); + break; + } + } + /// /// A utility function to reset the cooldown and/or effect of the button. /// - public void ResetCooldownAndOrEffect() + public virtual void ResetCooldownAndOrEffect() { Timer = Cooldown; if (EffectActive) @@ -160,7 +204,7 @@ public abstract class CustomActionButton /// A utility function to override the sprite of the button. /// /// The new sprite to override with. - public void OverrideSprite(Sprite sprite) + public virtual void OverrideSprite(Sprite sprite) { if (Button != null) { @@ -172,7 +216,7 @@ public abstract class CustomActionButton /// A utility function to override the name of the button. /// /// The new name to override with. - public void OverrideName(string name) + public virtual void OverrideName(string name) { Button?.OverrideText(name); } @@ -181,7 +225,7 @@ public abstract class CustomActionButton /// Set the button's timer. /// /// The time you want to set to. - public void SetTimer(float time) + public virtual void SetTimer(float time) { Timer = Mathf.Clamp(time, -1, float.MaxValue); } @@ -190,7 +234,7 @@ public abstract class CustomActionButton /// Increase the button's timer. /// /// The amount you want to increase by. - public void IncreaseTimer(float amount) + public virtual void IncreaseTimer(float amount) { SetTimer(Timer + amount); } @@ -199,7 +243,7 @@ public abstract class CustomActionButton /// Decrease the button's timer. /// /// The amount you want to decrease by. - public void DecreaseTimer(float amount) + public virtual void DecreaseTimer(float amount) { SetTimer(Timer - amount); } @@ -208,7 +252,7 @@ public abstract class CustomActionButton /// Set the amount of uses this button has left. /// /// The amount you want to set to. - public void SetUses(int amount) + public virtual void SetUses(int amount) { UsesLeft = Mathf.Clamp(amount, 0, int.MaxValue); Button?.SetUsesRemaining(UsesLeft); @@ -218,7 +262,7 @@ public abstract class CustomActionButton /// Increase the amount of uses this button has left. /// /// The amount you want to increase by. Default: 1. - public void IncreaseUses(int amount = 1) + public virtual void IncreaseUses(int amount = 1) { SetUses(UsesLeft + amount); } @@ -227,7 +271,7 @@ public abstract class CustomActionButton /// Decrease the amount of uses this button has left. /// /// The amount you want to decrease by. Default: 1. - public void DecreaseUses(int amount = 1) + public virtual void DecreaseUses(int amount = 1) { SetUses(UsesLeft - amount); } diff --git a/MiraAPI/Hud/CustomButtonManager.cs b/MiraAPI/Hud/CustomButtonManager.cs index 398aa58..c66ae4d 100644 --- a/MiraAPI/Hud/CustomButtonManager.cs +++ b/MiraAPI/Hud/CustomButtonManager.cs @@ -1,9 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using MiraAPI.Events.Mira; +using MiraAPI.Events.Mira; using MiraAPI.PluginLoading; using Reactor.Utilities; +using System; +using System.Collections.Generic; +using System.Reflection; namespace MiraAPI.Hud; diff --git a/MiraAPI/Patches/Events/StartMeetingEventPatch.cs b/MiraAPI/Patches/Events/StartMeetingEventPatch.cs index 00f8ba4..2012b5d 100644 --- a/MiraAPI/Patches/Events/StartMeetingEventPatch.cs +++ b/MiraAPI/Patches/Events/StartMeetingEventPatch.cs @@ -1,6 +1,6 @@ using HarmonyLib; using MiraAPI.Events; -using MiraAPI.Events.Vanilla; +using MiraAPI.Events.Vanilla.Meeting; using MiraAPI.Utilities; namespace MiraAPI.Patches.Events; diff --git a/MiraAPI/Patches/HudManagerPatches.cs b/MiraAPI/Patches/HudManagerPatches.cs index 85d7750..fa057d4 100644 --- a/MiraAPI/Patches/HudManagerPatches.cs +++ b/MiraAPI/Patches/HudManagerPatches.cs @@ -13,7 +13,9 @@ namespace MiraAPI.Patches; public static class HudManagerPatches { // Custom buttons parent. - private static GameObject? _bottomLeft; + internal static GameObject? _bottomLeft; + internal static Transform? _bottomRight; + internal static Transform? _buttons; /* /// @@ -34,12 +36,19 @@ public static class HudManagerPatches [HarmonyPatch(nameof(HudManager.Start))] public static void StartPostfix(HudManager __instance) { - var buttons = __instance.transform.Find("Buttons"); - var bottomRight = buttons.Find("BottomRight"); + if (_buttons == null) + { + _buttons = __instance.transform.Find("Buttons"); + } + + if (_bottomRight == null) + { + _bottomRight = _buttons.Find("BottomRight"); + } if (_bottomLeft == null) { - _bottomLeft = Object.Instantiate(bottomRight.gameObject, buttons); + _bottomLeft = Object.Instantiate(_bottomRight.gameObject, _buttons); } foreach (var t in _bottomLeft.GetComponentsInChildren(true)) @@ -59,7 +68,7 @@ public static class HudManagerPatches var location = button.Location switch { ButtonLocation.BottomLeft => _bottomLeft.transform, - ButtonLocation.BottomRight => bottomRight, + ButtonLocation.BottomRight => _bottomRight, _ => null, }; diff --git a/MiraAPI/Patches/IntroCutscenePatches.cs b/MiraAPI/Patches/IntroCutscenePatches.cs index 2250d3d..03cc403 100644 --- a/MiraAPI/Patches/IntroCutscenePatches.cs +++ b/MiraAPI/Patches/IntroCutscenePatches.cs @@ -1,4 +1,6 @@ using HarmonyLib; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla.Meeting; using MiraAPI.Roles; using MiraAPI.Utilities; using UnityEngine; @@ -20,6 +22,14 @@ public static class IntroCutscenePatches } }*/ + [HarmonyPostfix] + [HarmonyPatch(nameof(IntroCutscene.CoBegin))] + public static void IntroBeginPatch(IntroCutscene __instance) + { + var @event = new IntroBeginEvent(__instance); + MiraEventManager.InvokeEvent(@event); + } + [HarmonyPrefix] [HarmonyPatch(nameof(IntroCutscene.BeginCrewmate))] public static bool BeginCrewmatePatch(IntroCutscene __instance) diff --git a/MiraAPI/Patches/Roles/EjectionPatches.cs b/MiraAPI/Patches/Roles/EjectionPatches.cs index 9f85872..3acf228 100644 --- a/MiraAPI/Patches/Roles/EjectionPatches.cs +++ b/MiraAPI/Patches/Roles/EjectionPatches.cs @@ -1,4 +1,6 @@ using HarmonyLib; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla.Meeting; using MiraAPI.Roles; namespace MiraAPI.Patches.Roles; @@ -13,6 +15,13 @@ public static class EjectionPatches [HarmonyPatch(nameof(ExileController.Begin))] public static void BeginPostfix(ExileController __instance) { + var @event = new EjectionEvent(__instance.initData.networkedPlayer.Object, __instance); + MiraEventManager.InvokeEvent(@event); + + if (@event.IsCancelled) + { + return; + } if (!__instance.initData.networkedPlayer || !__instance.initData.networkedPlayer.Role || __instance.initData.networkedPlayer.Role is not ICustomRole role) { diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index ba318b2..a28bed9 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -2,8 +2,8 @@ using MiraAPI.Modifiers; using MiraAPI.PluginLoading; using MiraAPI.Utilities; -using System.Text; using Reactor.Utilities; +using System.Text; using UnityEngine; namespace MiraAPI.Roles; @@ -63,7 +63,7 @@ public interface ICustomRole /// Gets the role chance option. /// /// The role chance option. - public int? GetChance() + public virtual int? GetChance() { if (!Configuration.CanModifyChance) { @@ -82,7 +82,7 @@ public interface ICustomRole /// Gets the role count option. /// /// The role count option. - public int? GetCount() + public virtual int? GetCount() { if (ParentMod.PluginConfig.TryGetEntry(NumConfigDefinition, out ConfigEntry entry)) { @@ -96,7 +96,7 @@ public interface ICustomRole /// Sets the role chance option. /// /// The chance between 0 and 100. - public void SetChance(int chance) + public virtual void SetChance(int chance) { if (!Configuration.CanModifyChance) { @@ -117,7 +117,7 @@ public interface ICustomRole /// Sets the role count option. /// /// The amount of this role between zero and its MaxRoleCount in the Configuration. - public void SetCount(int count) + public virtual void SetCount(int count) { if (ParentMod.PluginConfig.TryGetEntry(NumConfigDefinition, out ConfigEntry entry)) { From 4fe301511daf71d89798ae259cb361129997df3a Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 9 Feb 2025 14:47:02 -0500 Subject: [PATCH 07/65] remove annoying logs --- MiraAPI/Events/MiraEventManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/MiraAPI/Events/MiraEventManager.cs b/MiraAPI/Events/MiraEventManager.cs index 719d69c..f613cd5 100644 --- a/MiraAPI/Events/MiraEventManager.cs +++ b/MiraAPI/Events/MiraEventManager.cs @@ -22,7 +22,6 @@ public static class MiraEventManager EventWrappers.TryGetValue(typeof(T), out var handlers); if (handlers == null) { - Logger.Warning("No handlers for event " + typeof(T).Name); return false; } @@ -45,7 +44,6 @@ public static class MiraEventManager EventWrappers.TryGetValue(type, out var handlers); if (handlers == null) { - Logger.Warning("No handlers for event " + type.Name); return false; } From 7042c6e64607cb8da32693adc924a57e7d10bc2c Mon Sep 17 00:00:00 2001 From: ang-xd Date: Sun, 9 Feb 2025 16:42:09 -0500 Subject: [PATCH 08/65] New events & extensions --- MiraAPI.Example/ExampleEventHandlers.cs | 26 ++++++---- .../Events/Vanilla/AdminButtonClickEvent.cs | 24 ---------- .../{ => Gameplay}/AfterMurderEvent.cs | 2 +- .../{ => Gameplay}/BeforeMurderEvent.cs | 2 +- .../Events/Vanilla/Gameplay/GameEndEvent.cs | 21 ++++++++ .../{Player => Gameplay}/IntroBeginEvent.cs | 6 +-- .../Vanilla/{ => Map}/CloseDoorsEvent.cs | 2 +- .../Vanilla/{ => Map}/UpdateSystemEvent.cs | 2 +- .../Vanilla/Player/CompleteTaskEvent.cs | 28 +++++++++++ .../Events/Vanilla/Player/PlayerDeathEvent.cs | 28 +++++++++++ .../Events/Vanilla/Player/PlayerJoinEvent.cs | 23 +++++++++ .../Events/Vanilla/Player/PlayerLeaveEvent.cs | 30 ++++++++++++ .../Vanilla/SabotageButtonClickEvent.cs | 24 ---------- .../Vanilla/{ => Usables}/EnterVentEvent.cs | 2 +- .../Vanilla/{ => Usables}/ExitVentEvent.cs | 2 +- MiraAPI/Events/Vanilla/UseButtonClickEvent.cs | 24 ---------- .../Events/Vanilla/VentButtonClickEvent.cs | 21 -------- MiraAPI/Hud/CustomActionButton.cs | 9 ++++ MiraAPI/Networking/CustomMurderRpc.cs | 9 ++-- MiraAPI/Patches/AmongUsClientSyncPatch.cs | 19 +++++++- MiraAPI/Patches/Events/EndGamePatches.cs | 20 ++++++++ .../Patches/Events/HudButtonEventPatches.cs | 48 ------------------- .../Patches/Events/KillAnimationPatches.cs | 25 ++++++++++ .../Patches/Events/SabotageEventPatches.cs | 2 +- MiraAPI/Patches/Events/VentEventPatches.cs | 2 +- MiraAPI/Patches/IntroCutscenePatches.cs | 2 +- MiraAPI/Patches/PlayerControlPatches.cs | 42 +++++++++++++++- MiraAPI/PluginLoading/MiraPluginManager.cs | 10 ++-- MiraAPI/Utilities/Extensions.cs | 26 ++++++++++ 29 files changed, 305 insertions(+), 176 deletions(-) delete mode 100644 MiraAPI/Events/Vanilla/AdminButtonClickEvent.cs rename MiraAPI/Events/Vanilla/{ => Gameplay}/AfterMurderEvent.cs (94%) rename MiraAPI/Events/Vanilla/{ => Gameplay}/BeforeMurderEvent.cs (94%) create mode 100644 MiraAPI/Events/Vanilla/Gameplay/GameEndEvent.cs rename MiraAPI/Events/Vanilla/{Player => Gameplay}/IntroBeginEvent.cs (79%) rename MiraAPI/Events/Vanilla/{ => Map}/CloseDoorsEvent.cs (92%) rename MiraAPI/Events/Vanilla/{ => Map}/UpdateSystemEvent.cs (96%) create mode 100644 MiraAPI/Events/Vanilla/Player/CompleteTaskEvent.cs create mode 100644 MiraAPI/Events/Vanilla/Player/PlayerDeathEvent.cs create mode 100644 MiraAPI/Events/Vanilla/Player/PlayerJoinEvent.cs create mode 100644 MiraAPI/Events/Vanilla/Player/PlayerLeaveEvent.cs delete mode 100644 MiraAPI/Events/Vanilla/SabotageButtonClickEvent.cs rename MiraAPI/Events/Vanilla/{ => Usables}/EnterVentEvent.cs (94%) rename MiraAPI/Events/Vanilla/{ => Usables}/ExitVentEvent.cs (94%) delete mode 100644 MiraAPI/Events/Vanilla/UseButtonClickEvent.cs delete mode 100644 MiraAPI/Events/Vanilla/VentButtonClickEvent.cs create mode 100644 MiraAPI/Patches/Events/EndGamePatches.cs delete mode 100644 MiraAPI/Patches/Events/HudButtonEventPatches.cs create mode 100644 MiraAPI/Patches/Events/KillAnimationPatches.cs diff --git a/MiraAPI.Example/ExampleEventHandlers.cs b/MiraAPI.Example/ExampleEventHandlers.cs index 9d18bc1..e196b28 100644 --- a/MiraAPI.Example/ExampleEventHandlers.cs +++ b/MiraAPI.Example/ExampleEventHandlers.cs @@ -1,7 +1,8 @@ using MiraAPI.Events; using MiraAPI.Events.Mira; -using MiraAPI.Events.Vanilla; -using MiraAPI.Events.Vanilla.Usables; +using MiraAPI.Events.Vanilla.Gameplay; +using MiraAPI.Events.Vanilla.Map; +using MiraAPI.Events.Vanilla.Player; using MiraAPI.Example.Buttons.Freezer; using Reactor.Utilities; @@ -15,16 +16,21 @@ public static class ExampleEventHandlers MiraEventManager.RegisterEventHandler>(FreezeButtonClickHandler, 1); MiraEventManager.RegisterEventHandler>(FreezeButtonCancelledHandler); MiraEventManager.RegisterEventHandler(UpdateSystemEventHandler); - MiraEventManager.RegisterEventHandler(PlayerControlCanUse); - MiraEventManager.RegisterEventHandler(PlayerControlUse); - } - public static void PlayerControlCanUse(PlayerCanUseEvent @event) - { - } + MiraEventManager.RegisterEventHandler(@event => + { + Logger.Info($"{@event.Source.Data.PlayerName} is about to kill {@event.Target.Data.PlayerName}"); + }); - public static void PlayerControlUse(PlayerUseEvent @event) - { + MiraEventManager.RegisterEventHandler(@event => + { + Logger.Info($"{@event.Source.Data.PlayerName} has killed {@event.Target.Data.PlayerName}"); + }); + + MiraEventManager.RegisterEventHandler(@event => + { + Logger.Info($"{@event.Player.Data.PlayerName} completed {@event.Task.TaskType.ToString()}"); + }); } public static void UpdateSystemEventHandler(UpdateSystemEvent @event) diff --git a/MiraAPI/Events/Vanilla/AdminButtonClickEvent.cs b/MiraAPI/Events/Vanilla/AdminButtonClickEvent.cs deleted file mode 100644 index de02bb9..0000000 --- a/MiraAPI/Events/Vanilla/AdminButtonClickEvent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace MiraAPI.Events.Vanilla; - -/// -/// Button click event for from Vanilla Among Us. -/// -[Obsolete("This class is deprecated. Use PlayerUseEvent instead.")] -public class AdminButtonClickEvent : MiraCancelableEvent -{ - /// - /// Gets the instance of that was clicked. - /// - public AdminButton Button { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The AdminButton. - public AdminButtonClickEvent(AdminButton button) - { - Button = button; - } -} diff --git a/MiraAPI/Events/Vanilla/AfterMurderEvent.cs b/MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs similarity index 94% rename from MiraAPI/Events/Vanilla/AfterMurderEvent.cs rename to MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs index a425cd2..fcf9df4 100644 --- a/MiraAPI/Events/Vanilla/AfterMurderEvent.cs +++ b/MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs @@ -1,4 +1,4 @@ -namespace MiraAPI.Events.Vanilla; +namespace MiraAPI.Events.Vanilla.Gameplay; /// /// Event that is invoked after a player is murdered. Only called after a successful murder. This event is not cancelable. diff --git a/MiraAPI/Events/Vanilla/BeforeMurderEvent.cs b/MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs similarity index 94% rename from MiraAPI/Events/Vanilla/BeforeMurderEvent.cs rename to MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs index f4f2be3..82694f7 100644 --- a/MiraAPI/Events/Vanilla/BeforeMurderEvent.cs +++ b/MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs @@ -1,4 +1,4 @@ -namespace MiraAPI.Events.Vanilla; +namespace MiraAPI.Events.Vanilla.Gameplay; /// /// Event that is invoked before a player is murdered. This event is cancelable. diff --git a/MiraAPI/Events/Vanilla/Gameplay/GameEndEvent.cs b/MiraAPI/Events/Vanilla/Gameplay/GameEndEvent.cs new file mode 100644 index 0000000..1f06533 --- /dev/null +++ b/MiraAPI/Events/Vanilla/Gameplay/GameEndEvent.cs @@ -0,0 +1,21 @@ +namespace MiraAPI.Events.Vanilla.Gameplay; + +/// +/// The event that is invoked when the end game screen is shown. Non cancelable. +/// +public class GameEndEvent : MiraEvent +{ + /// + /// Gets the instance of the EndGameManager. + /// + public EndGameManager EndGameManager { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The instance of the EndGameManager. + public GameEndEvent(EndGameManager manager) + { + EndGameManager = manager; + } +} diff --git a/MiraAPI/Events/Vanilla/Player/IntroBeginEvent.cs b/MiraAPI/Events/Vanilla/Gameplay/IntroBeginEvent.cs similarity index 79% rename from MiraAPI/Events/Vanilla/Player/IntroBeginEvent.cs rename to MiraAPI/Events/Vanilla/Gameplay/IntroBeginEvent.cs index 817f6e7..5d5e8d8 100644 --- a/MiraAPI/Events/Vanilla/Player/IntroBeginEvent.cs +++ b/MiraAPI/Events/Vanilla/Gameplay/IntroBeginEvent.cs @@ -1,7 +1,7 @@ -namespace MiraAPI.Events.Vanilla.Meeting; +namespace MiraAPI.Events.Vanilla.Gameplay; /// -/// The event that is invoked when the intro cutscene is shown. Non cancellable. +/// The event that is invoked when the intro cutscene is shown. Non cancelable. /// public class IntroBeginEvent : MiraEvent { @@ -13,7 +13,7 @@ public class IntroBeginEvent : MiraEvent /// /// Initializes a new instance of the class. /// - /// The exile controller. + /// The intro cutscene. public IntroBeginEvent(IntroCutscene introCutscene) { IntroCutscene = introCutscene; diff --git a/MiraAPI/Events/Vanilla/CloseDoorsEvent.cs b/MiraAPI/Events/Vanilla/Map/CloseDoorsEvent.cs similarity index 92% rename from MiraAPI/Events/Vanilla/CloseDoorsEvent.cs rename to MiraAPI/Events/Vanilla/Map/CloseDoorsEvent.cs index 8d0b9bf..86f2bfd 100644 --- a/MiraAPI/Events/Vanilla/CloseDoorsEvent.cs +++ b/MiraAPI/Events/Vanilla/Map/CloseDoorsEvent.cs @@ -1,4 +1,4 @@ -namespace MiraAPI.Events.Vanilla; +namespace MiraAPI.Events.Vanilla.Map; /// /// Event fired when a player closes the doors in a room. diff --git a/MiraAPI/Events/Vanilla/UpdateSystemEvent.cs b/MiraAPI/Events/Vanilla/Map/UpdateSystemEvent.cs similarity index 96% rename from MiraAPI/Events/Vanilla/UpdateSystemEvent.cs rename to MiraAPI/Events/Vanilla/Map/UpdateSystemEvent.cs index 1ac8bda..6b6d907 100644 --- a/MiraAPI/Events/Vanilla/UpdateSystemEvent.cs +++ b/MiraAPI/Events/Vanilla/Map/UpdateSystemEvent.cs @@ -1,4 +1,4 @@ -namespace MiraAPI.Events.Vanilla; +namespace MiraAPI.Events.Vanilla.Map; /// /// Cancelable event that is invoked before a system is updated. Usually used for sabotage events. diff --git a/MiraAPI/Events/Vanilla/Player/CompleteTaskEvent.cs b/MiraAPI/Events/Vanilla/Player/CompleteTaskEvent.cs new file mode 100644 index 0000000..31fd9d4 --- /dev/null +++ b/MiraAPI/Events/Vanilla/Player/CompleteTaskEvent.cs @@ -0,0 +1,28 @@ +namespace MiraAPI.Events.Vanilla.Player; + +/// +/// The event that is invoked when a player completes a task. Non cancelable. +/// +public class CompleteTaskEvent : MiraEvent +{ + /// + /// Gets the instance of the . + /// + public PlayerControl Player { get; } + + /// + /// Gets the instance of the that the player completed. + /// + public PlayerTask Task { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The player who completed the task. + /// The task that the player completed. + public CompleteTaskEvent(PlayerControl player, PlayerTask task) + { + Player = player; + Task = task; + } +} diff --git a/MiraAPI/Events/Vanilla/Player/PlayerDeathEvent.cs b/MiraAPI/Events/Vanilla/Player/PlayerDeathEvent.cs new file mode 100644 index 0000000..e787b13 --- /dev/null +++ b/MiraAPI/Events/Vanilla/Player/PlayerDeathEvent.cs @@ -0,0 +1,28 @@ +namespace MiraAPI.Events.Vanilla.Player; + +/// +/// Event that is invoked when a player dies. Non cancelable. +/// +public class PlayerDeathEvent : MiraEvent +{ + /// + /// Gets the player who died. + /// + public PlayerControl Player { get; } + + /// + /// Gets the reason the player died. + /// + public DeathReason DeathReason { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The player who died. + /// The reason the player died. + public PlayerDeathEvent(PlayerControl player, DeathReason reason) + { + Player = player; + DeathReason = reason; + } +} diff --git a/MiraAPI/Events/Vanilla/Player/PlayerJoinEvent.cs b/MiraAPI/Events/Vanilla/Player/PlayerJoinEvent.cs new file mode 100644 index 0000000..ba9bfb5 --- /dev/null +++ b/MiraAPI/Events/Vanilla/Player/PlayerJoinEvent.cs @@ -0,0 +1,23 @@ +using InnerNet; + +namespace MiraAPI.Events.Vanilla.Player; + +/// +/// Event that is invoked when a player joins the lobby. Non cancelable. +/// +public class PlayerJoinEvent : MiraEvent +{ + /// + /// Gets the player who joined. + /// + public ClientData ClientData { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The data of the player who joined. + public PlayerJoinEvent(ClientData data) + { + ClientData = data; + } +} diff --git a/MiraAPI/Events/Vanilla/Player/PlayerLeaveEvent.cs b/MiraAPI/Events/Vanilla/Player/PlayerLeaveEvent.cs new file mode 100644 index 0000000..4607305 --- /dev/null +++ b/MiraAPI/Events/Vanilla/Player/PlayerLeaveEvent.cs @@ -0,0 +1,30 @@ +using InnerNet; + +namespace MiraAPI.Events.Vanilla.Player; + +/// +/// Event that is invoked when a player leaves the game. Non cancelable. +/// +public class PlayerLeaveEvent : MiraEvent +{ + /// + /// Gets the player who left. + /// + public ClientData ClientData { get; } + + /// + /// Gets the reason why the player left. + /// + public DisconnectReasons Reason { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The data of the player who left. + /// The reason why the player left. + public PlayerLeaveEvent(ClientData data, DisconnectReasons reason) + { + ClientData = data; + Reason = reason; + } +} diff --git a/MiraAPI/Events/Vanilla/SabotageButtonClickEvent.cs b/MiraAPI/Events/Vanilla/SabotageButtonClickEvent.cs deleted file mode 100644 index 9d3d2ba..0000000 --- a/MiraAPI/Events/Vanilla/SabotageButtonClickEvent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace MiraAPI.Events.Vanilla; - -/// -/// Button click event for from Vanilla Among Us. -/// -[Obsolete("This class is deprecated. Use PlayerOpenSabotageEvent instead.")] -public class SabotageButtonClickEvent : MiraCancelableEvent -{ - /// - /// Gets the instance of that was clicked. - /// - public SabotageButton Button { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The SabotageButton. - public SabotageButtonClickEvent(SabotageButton button) - { - Button = button; - } -} diff --git a/MiraAPI/Events/Vanilla/EnterVentEvent.cs b/MiraAPI/Events/Vanilla/Usables/EnterVentEvent.cs similarity index 94% rename from MiraAPI/Events/Vanilla/EnterVentEvent.cs rename to MiraAPI/Events/Vanilla/Usables/EnterVentEvent.cs index 63e872c..ce3b3ca 100644 --- a/MiraAPI/Events/Vanilla/EnterVentEvent.cs +++ b/MiraAPI/Events/Vanilla/Usables/EnterVentEvent.cs @@ -1,4 +1,4 @@ -namespace MiraAPI.Events.Vanilla; +namespace MiraAPI.Events.Vanilla.Usables; /// /// Event that is invoked when a player enters a vent. This event is cancelable. diff --git a/MiraAPI/Events/Vanilla/ExitVentEvent.cs b/MiraAPI/Events/Vanilla/Usables/ExitVentEvent.cs similarity index 94% rename from MiraAPI/Events/Vanilla/ExitVentEvent.cs rename to MiraAPI/Events/Vanilla/Usables/ExitVentEvent.cs index 1269b8a..351411f 100644 --- a/MiraAPI/Events/Vanilla/ExitVentEvent.cs +++ b/MiraAPI/Events/Vanilla/Usables/ExitVentEvent.cs @@ -1,4 +1,4 @@ -namespace MiraAPI.Events.Vanilla; +namespace MiraAPI.Events.Vanilla.Usables; /// /// Event that is invoked when a player exits a vent. This event is cancelable. diff --git a/MiraAPI/Events/Vanilla/UseButtonClickEvent.cs b/MiraAPI/Events/Vanilla/UseButtonClickEvent.cs deleted file mode 100644 index 8b859fb..0000000 --- a/MiraAPI/Events/Vanilla/UseButtonClickEvent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace MiraAPI.Events.Vanilla; - -/// -/// Button click event for from Vanilla Among Us. -/// -[Obsolete("This class is deprecated. Use PlayerUseEvent instead.")] -public class UseButtonClickEvent : MiraCancelableEvent -{ - /// - /// Gets the instance of that was clicked. - /// - public UseButton Button { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The UseButton. - public UseButtonClickEvent(UseButton button) - { - Button = button; - } -} diff --git a/MiraAPI/Events/Vanilla/VentButtonClickEvent.cs b/MiraAPI/Events/Vanilla/VentButtonClickEvent.cs deleted file mode 100644 index a46601f..0000000 --- a/MiraAPI/Events/Vanilla/VentButtonClickEvent.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace MiraAPI.Events.Vanilla; - -/// -/// Button click event for from Vanilla Among Us. -/// -public class VentButtonClickEvent : MiraCancelableEvent -{ - /// - /// Gets the instance of that was clicked. - /// - public VentButton Button { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The VentButton. - public VentButtonClickEvent(VentButton button) - { - Button = button; - } -} diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index fcefc95..f37f2eb 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -445,4 +445,13 @@ public abstract class CustomActionButton : CustomActionButton where T : MonoB return base.CanUse() && Target != null; } + + /// + /// Use this to reset the button's target after used. + /// + public virtual void ResetTarget() + { + Target = null; + SetOutline(false); + } } diff --git a/MiraAPI/Networking/CustomMurderRpc.cs b/MiraAPI/Networking/CustomMurderRpc.cs index e28ddf2..252ac91 100644 --- a/MiraAPI/Networking/CustomMurderRpc.cs +++ b/MiraAPI/Networking/CustomMurderRpc.cs @@ -1,14 +1,14 @@ -using System.Collections; -using System.Linq; -using AmongUs.GameOptions; +using AmongUs.GameOptions; using Assets.CoreScripts; using BepInEx.Unity.IL2CPP.Utils; using MiraAPI.Events; -using MiraAPI.Events.Vanilla; +using MiraAPI.Events.Vanilla.Gameplay; using Reactor.Networking.Attributes; using Reactor.Networking.Rpc; using Reactor.Utilities; using Reactor.Utilities.Extensions; +using System.Collections; +using System.Linq; using UnityEngine; namespace MiraAPI.Networking; @@ -84,7 +84,6 @@ public static class CustomMurderRpc bool playKillSound = true) { source.isKilling = false; - Logger.Error($"{source.PlayerId} trying to murder {target.PlayerId}"); var data = target.Data; if (resultFlags.HasFlag(MurderResultFlags.FailedError)) { diff --git a/MiraAPI/Patches/AmongUsClientSyncPatch.cs b/MiraAPI/Patches/AmongUsClientSyncPatch.cs index 3034b42..4a76283 100644 --- a/MiraAPI/Patches/AmongUsClientSyncPatch.cs +++ b/MiraAPI/Patches/AmongUsClientSyncPatch.cs @@ -1,5 +1,7 @@ using HarmonyLib; using InnerNet; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla.Player; using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.Roles; @@ -9,11 +11,16 @@ namespace MiraAPI.Patches; /// /// Sync all options, role settings, and modifiers to the player when they join the game. /// -[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CreatePlayer))] +[HarmonyPatch(typeof(AmongUsClient))] public static class AmongUsClientSyncPatch { - public static void Postfix(ClientData clientData) + [HarmonyPostfix] + [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CreatePlayer))] + public static void CreatePlayerPatch(ClientData clientData) { + var joinEvent = new PlayerJoinEvent(clientData); + MiraEventManager.InvokeEvent(joinEvent); + if (!AmongUsClient.Instance.AmHost) { return; @@ -28,4 +35,12 @@ public static class AmongUsClientSyncPatch CustomRoleManager.SyncAllRoleSettings(clientData.Id); ModifierManager.SyncAllModifiers(clientData.Id); } + + [HarmonyPostfix] + [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerLeft))] + public static void PlayerLeftPatch(ClientData data, DisconnectReasons reason) + { + var leftEvent = new PlayerLeaveEvent(data, reason); + MiraEventManager.InvokeEvent(leftEvent); + } } diff --git a/MiraAPI/Patches/Events/EndGamePatches.cs b/MiraAPI/Patches/Events/EndGamePatches.cs new file mode 100644 index 0000000..4f7118b --- /dev/null +++ b/MiraAPI/Patches/Events/EndGamePatches.cs @@ -0,0 +1,20 @@ +using HarmonyLib; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla.Gameplay; + +namespace MiraAPI.Patches.Events; + +/// +/// Patch for trigging the GameEndEvent. +/// +[HarmonyPatch] +public static class EndGamePatches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))] + public static void SetEverythingUpPatch(EndGameManager __instance) + { + var @event = new GameEndEvent(__instance); + MiraEventManager.InvokeEvent(@event); + } +} diff --git a/MiraAPI/Patches/Events/HudButtonEventPatches.cs b/MiraAPI/Patches/Events/HudButtonEventPatches.cs deleted file mode 100644 index 42dde11..0000000 --- a/MiraAPI/Patches/Events/HudButtonEventPatches.cs +++ /dev/null @@ -1,48 +0,0 @@ -using HarmonyLib; -using MiraAPI.Events; -using MiraAPI.Events.Vanilla; - -namespace MiraAPI.Patches.Events; - -/// -/// Patch for button related MiraEvents. -/// -[HarmonyPatch] -public static class HudButtonEventPatches -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(AdminButton), nameof(AdminButton.DoClick))] - public static bool AdminButtonDoClickPrefix(AdminButton __instance) - { - var @event = new AdminButtonClickEvent(__instance); - MiraEventManager.InvokeEvent(@event); - return !@event.IsCancelled; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(SabotageButton), nameof(SabotageButton.DoClick))] - public static bool SabotageButtonDoClickPrefix(SabotageButton __instance) - { - var @event = new SabotageButtonClickEvent(__instance); - MiraEventManager.InvokeEvent(@event); - return !@event.IsCancelled; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(UseButton), nameof(UseButton.DoClick))] - public static bool UseButtonDoClickPrefix(UseButton __instance) - { - var @event = new UseButtonClickEvent(__instance); - MiraEventManager.InvokeEvent(@event); - return !@event.IsCancelled; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(VentButton), nameof(VentButton.DoClick))] - public static bool VentButtonDoClickPrefix(VentButton __instance) - { - var @event = new VentButtonClickEvent(__instance); - MiraEventManager.InvokeEvent(@event); - return !@event.IsCancelled; - } -} diff --git a/MiraAPI/Patches/Events/KillAnimationPatches.cs b/MiraAPI/Patches/Events/KillAnimationPatches.cs new file mode 100644 index 0000000..13b4cff --- /dev/null +++ b/MiraAPI/Patches/Events/KillAnimationPatches.cs @@ -0,0 +1,25 @@ +using HarmonyLib; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla.Gameplay; + +namespace MiraAPI.Patches; + +/// +/// General patches for the KillAnimation class. +/// +[HarmonyPatch] +public static class KillAnimationPatches +{ + /// + /// Used to trigger the . + /// + /// The killer. + /// The target. + [HarmonyPostfix] + [HarmonyPatch(typeof(KillAnimation), nameof(KillAnimation.CoPerformKill))] + public static void KillAnimPostfix(PlayerControl source, PlayerControl target) + { + var afterMurderEvent = new AfterMurderEvent(source, target); + MiraEventManager.InvokeEvent(afterMurderEvent); + } +} diff --git a/MiraAPI/Patches/Events/SabotageEventPatches.cs b/MiraAPI/Patches/Events/SabotageEventPatches.cs index b8a1fd2..4f04d7e 100644 --- a/MiraAPI/Patches/Events/SabotageEventPatches.cs +++ b/MiraAPI/Patches/Events/SabotageEventPatches.cs @@ -1,6 +1,6 @@ using HarmonyLib; using MiraAPI.Events; -using MiraAPI.Events.Vanilla; +using MiraAPI.Events.Vanilla.Map; namespace MiraAPI.Patches.Events; diff --git a/MiraAPI/Patches/Events/VentEventPatches.cs b/MiraAPI/Patches/Events/VentEventPatches.cs index e8e5661..0b1b1cd 100644 --- a/MiraAPI/Patches/Events/VentEventPatches.cs +++ b/MiraAPI/Patches/Events/VentEventPatches.cs @@ -1,6 +1,6 @@ using HarmonyLib; using MiraAPI.Events; -using MiraAPI.Events.Vanilla; +using MiraAPI.Events.Vanilla.Usables; using System.Linq; namespace MiraAPI.Patches.Events; diff --git a/MiraAPI/Patches/IntroCutscenePatches.cs b/MiraAPI/Patches/IntroCutscenePatches.cs index 03cc403..297ce94 100644 --- a/MiraAPI/Patches/IntroCutscenePatches.cs +++ b/MiraAPI/Patches/IntroCutscenePatches.cs @@ -1,6 +1,6 @@ using HarmonyLib; using MiraAPI.Events; -using MiraAPI.Events.Vanilla.Meeting; +using MiraAPI.Events.Vanilla.Gameplay; using MiraAPI.Roles; using MiraAPI.Utilities; using UnityEngine; diff --git a/MiraAPI/Patches/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs index bc98a51..c7918fe 100644 --- a/MiraAPI/Patches/PlayerControlPatches.cs +++ b/MiraAPI/Patches/PlayerControlPatches.cs @@ -1,8 +1,12 @@ using HarmonyLib; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla.Gameplay; +using MiraAPI.Events.Vanilla.Player; using MiraAPI.Hud; using MiraAPI.Modifiers; using MiraAPI.Roles; using Reactor.Utilities.Extensions; +using System.Linq; namespace MiraAPI.Patches; @@ -36,14 +40,50 @@ public static class PlayerControlPatches [HarmonyPatch(nameof(PlayerControl.Die))] public static void PlayerControlDiePostfix(PlayerControl __instance, DeathReason reason) { + var deathEvent = new PlayerDeathEvent(__instance, reason); + MiraEventManager.InvokeEvent(deathEvent); + var modifiersComponent = __instance.GetComponent(); if (modifiersComponent) { - modifiersComponent.ActiveModifiers.ForEach(x=>x.OnDeath(reason)); + modifiersComponent.ActiveModifiers.ForEach(x => x.OnDeath(reason)); } } + /// + /// Used to trigger the . + /// + /// PlayerControl instance. + /// The task id. + [HarmonyPostfix] + [HarmonyPatch(nameof(PlayerControl.CompleteTask))] + public static void PlayerCompleteTaskPostfix(PlayerControl __instance, uint idx) + { + var playerTask = __instance.myTasks.ToArray().First(playerTask => playerTask.Id == idx); + if (playerTask != null) + { + var completeTaskEvent = new CompleteTaskEvent(__instance, playerTask); + MiraEventManager.InvokeEvent(completeTaskEvent); + } + } + + /// + /// Used to trigger the . + /// + /// The source player. + /// The target. + /// Whether the kill succeeded. + [HarmonyPrefix] + [HarmonyPatch(nameof(PlayerControl.RpcMurderPlayer))] + public static void PlayerControlMurderPrefix(PlayerControl __instance, PlayerControl target, ref bool didSucceed) + { + var beforeMurderEvent = new BeforeMurderEvent(__instance, target); + MiraEventManager.InvokeEvent(beforeMurderEvent); + + didSucceed = beforeMurderEvent.IsCancelled; + } + /// /// FixedUpdate handler for custom roles and custom buttons. /// diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index acb6ebe..25d3065 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using BepInEx.Unity.IL2CPP; +using BepInEx.Unity.IL2CPP; using MiraAPI.Colors; using MiraAPI.Events; using MiraAPI.GameOptions; @@ -13,6 +9,10 @@ using MiraAPI.Roles; using MiraAPI.Utilities; using Reactor.Networking; using Reactor.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; namespace MiraAPI.PluginLoading; diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 636601b..d694c85 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -346,6 +346,16 @@ public static class Extensions .Find(component => component && !component.Reported); } + /// + /// Get all living players. + /// + /// A list of alive players. + public static List GetAlivePlayers() + { + return PlayerControl.AllPlayerControls.ToArray().Where(x => !x.Data.IsDead).ToList(); + } + + /// /// Finds the nearest object of a specified type to a player. It will only work if the object has a collider. /// @@ -393,4 +403,20 @@ 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. + /// + /// The renderer you want to update the outline for. + /// 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); + } } From 5bf39593c5e8495e147a6957d71e8be7bd6b859a Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 10 Feb 2025 16:41:00 -0500 Subject: [PATCH 09/65] get rid of annoying log --- MiraAPI/PluginLoading/MiraPluginManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 88fd088..5542381 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -115,7 +115,6 @@ public sealed class MiraPluginManager if (!(typeof(RoleBehaviour).IsAssignableFrom(type) && typeof(ICustomRole).IsAssignableFrom(type))) { - Logger.Error($"{type.Name} does not inherit from RoleBehaviour or ICustomRole."); return false; } From 7fe2341be0af6289ceb8a9cce7486a2f729cfb1c Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 10 Feb 2025 16:43:45 -0500 Subject: [PATCH 10/65] fix miradisable bug --- MiraAPI/PluginLoading/MiraDisableAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MiraAPI/PluginLoading/MiraDisableAttribute.cs b/MiraAPI/PluginLoading/MiraDisableAttribute.cs index 23c1e28..410f56b 100644 --- a/MiraAPI/PluginLoading/MiraDisableAttribute.cs +++ b/MiraAPI/PluginLoading/MiraDisableAttribute.cs @@ -5,5 +5,5 @@ namespace MiraAPI.PluginLoading; /// /// Skip an element during plugin loading. /// -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, Inherited = false)] public class MiraDisableAttribute : Attribute; From bb27f83e8b011e961dfb85eea94cd0154f081441 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Thu, 13 Feb 2025 16:18:53 -0500 Subject: [PATCH 11/65] miradisable -> miraignore --- .../{MiraDisableAttribute.cs => MiraIgnoreAttribute.cs} | 2 +- MiraAPI/PluginLoading/MiraPluginManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename MiraAPI/PluginLoading/{MiraDisableAttribute.cs => MiraIgnoreAttribute.cs} (81%) diff --git a/MiraAPI/PluginLoading/MiraDisableAttribute.cs b/MiraAPI/PluginLoading/MiraIgnoreAttribute.cs similarity index 81% rename from MiraAPI/PluginLoading/MiraDisableAttribute.cs rename to MiraAPI/PluginLoading/MiraIgnoreAttribute.cs index 410f56b..b8ee939 100644 --- a/MiraAPI/PluginLoading/MiraDisableAttribute.cs +++ b/MiraAPI/PluginLoading/MiraIgnoreAttribute.cs @@ -6,4 +6,4 @@ namespace MiraAPI.PluginLoading; /// Skip an element during plugin loading. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, Inherited = false)] -public class MiraDisableAttribute : Attribute; +public class MiraIgnoreAttribute : Attribute; diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 5542381..15c9dcf 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -42,7 +42,7 @@ public sealed class MiraPluginManager foreach (var type in assembly.GetTypes()) { - if (type.GetCustomAttribute() != null) + if (type.GetCustomAttribute() != null) { continue; } From 59ab2736abd4b96edcd8bfe2215d1f841bbdffce Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Thu, 13 Feb 2025 16:19:12 -0500 Subject: [PATCH 12/65] further optimization? --- MiraAPI/GameOptions/ModdedOptionsManager.cs | 1 - MiraAPI/Hud/CustomButtonManager.cs | 15 +- MiraAPI/Modifiers/ModifierManager.cs | 7 +- MiraAPI/PluginLoading/MiraPluginManager.cs | 165 +++++++++++++------- 4 files changed, 115 insertions(+), 73 deletions(-) diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index b6c42bb..a6dd1c9 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -31,7 +31,6 @@ public static class ModdedOptionsManager { if (Activator.CreateInstance(type) is not AbstractOptionGroup group) { - Logger.Error($"Skipping option group {type.Name}. Does not inherit AbstractOptionGroup."); return false; } diff --git a/MiraAPI/Hud/CustomButtonManager.cs b/MiraAPI/Hud/CustomButtonManager.cs index c66ae4d..60f2fc6 100644 --- a/MiraAPI/Hud/CustomButtonManager.cs +++ b/MiraAPI/Hud/CustomButtonManager.cs @@ -16,18 +16,11 @@ public static class CustomButtonManager internal static readonly Dictionary ButtonEventTypes = []; internal static readonly Dictionary ButtonCancelledEventTypes = []; - internal static void RegisterButton(Type buttonType, MiraPluginInfo pluginInfo) + internal static bool RegisterButton(Type buttonType, MiraPluginInfo pluginInfo) { - if (!typeof(CustomActionButton).IsAssignableFrom(buttonType)) + if (!buttonType.IsAssignableTo(typeof(CustomActionButton)) || Activator.CreateInstance(buttonType) is not CustomActionButton button) { - Logger.Error($"Skipping button {buttonType.Name}. Does not inherit CustomActionButton!"); - return; - } - - if (Activator.CreateInstance(buttonType) is not CustomActionButton button) - { - Logger.Error($"Failed to create button from {buttonType.Name}"); - return; + return false; } CustomButtons.Add(button); @@ -38,5 +31,7 @@ public static class CustomButtonManager ButtonEventTypes.Add(buttonType, typeof(MiraButtonClickEvent<>).MakeGenericType(buttonType)); ButtonCancelledEventTypes.Add(buttonType, typeof(MiraButtonCancelledEvent<>).MakeGenericType(buttonType)); + + return true; } } diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index fab840c..ddb3c87 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -49,11 +49,11 @@ public static class ModifierManager return TypeToIdModifierMap.GetValueOrDefault(type); } - internal static void RegisterModifier(Type modifierType) + internal static bool RegisterModifier(Type modifierType) { if (!typeof(BaseModifier).IsAssignableFrom(modifierType)) { - return; + return false; } IdToTypeModifierMap.Add(GetNextId(), modifierType); @@ -61,7 +61,7 @@ public static class ModifierManager if (!typeof(GameModifier).IsAssignableFrom(modifierType)) { - return; + return true; } var mod = Activator.CreateInstance(modifierType) as GameModifier; @@ -73,6 +73,7 @@ public static class ModifierManager } list.Add(_nextId); + return true; } internal static void AssignModifiers(List plrs) diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 15c9dcf..82362ec 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -47,15 +47,27 @@ public sealed class MiraPluginManager continue; } - RegisterModifier(type); - RegisterOptions(type, info); + if (RegisterModifier(type)) + { + continue; + } + + if (RegisterOptions(type, info)) + { + continue; + } if (RegisterRoleAttribute(type, info, out var role)) { roles.Add(role!); + continue; + } + + if (RegisterButtonAttribute(type, info)) + { + continue; } - RegisterButtonAttribute(type, info); RegisterColorClasses(type); } @@ -72,105 +84,140 @@ public sealed class MiraPluginManager /// /// Get a mira plugin by its GUID. /// - /// The plugin GUID. + /// The plugin GUID. /// A MiraPluginInfo. - public static MiraPluginInfo GetPluginByGuid(string guid) + public static MiraPluginInfo GetPluginByGuid(string pluginId) { - return Instance._registeredPlugins.Values.First(plugin => plugin.PluginId == guid); + return Instance._registeredPlugins.Values.First(plugin => plugin.PluginId == pluginId); } - private static void RegisterOptions(Type type, MiraPluginInfo pluginInfo) + private static bool RegisterOptions(Type type, MiraPluginInfo pluginInfo) { - if (!type.IsAssignableTo(typeof(AbstractOptionGroup))) + try { - return; - } - - if (!ModdedOptionsManager.RegisterGroup(type, pluginInfo)) - { - return; - } - - foreach (var property in type.GetProperties()) - { - if (property.PropertyType.IsAssignableTo(typeof(IModdedOption))) + if (!type.IsAssignableTo(typeof(AbstractOptionGroup))) { - ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo); - continue; + return false; } - var attribute = property.GetCustomAttribute(); - if (attribute == null) + if (!ModdedOptionsManager.RegisterGroup(type, pluginInfo)) { - continue; + return false; } - ModdedOptionsManager.RegisterAttributeOption(type, attribute, property, pluginInfo); + foreach (var property in type.GetProperties()) + { + if (property.PropertyType.IsAssignableTo(typeof(IModdedOption))) + { + ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo); + continue; + } + + var attribute = property.GetCustomAttribute(); + if (attribute == null) + { + continue; + } + + ModdedOptionsManager.RegisterAttributeOption(type, attribute, property, pluginInfo); + } + + return true; } + catch (Exception e) + { + Logger.Error($"Failed to register options for {type.Name}: {e}"); + } + return false; } private static bool RegisterRoleAttribute(Type type, MiraPluginInfo pluginInfo, out Type? role) { role = null; - - if (!(typeof(RoleBehaviour).IsAssignableFrom(type) && typeof(ICustomRole).IsAssignableFrom(type))) + try { - return false; - } + if (!(typeof(RoleBehaviour).IsAssignableFrom(type) && typeof(ICustomRole).IsAssignableFrom(type))) + { + return false; + } - if (!ModList.GetById(pluginInfo.PluginId).IsRequiredOnAllClients) + if (!ModList.GetById(pluginInfo.PluginId).IsRequiredOnAllClients) + { + Logger.Error("Custom roles are only supported on all clients."); + return false; + } + + role = type; + return true; + } + catch (Exception e) { - Logger.Error("Custom roles are only supported on all clients."); - return false; + Logger.Error($"Failed to register role for {type.Name}: {e}"); } - - role = type; - return true; + return false; } private static void RegisterColorClasses(Type type) { - if (type.GetCustomAttribute() == null) + try { - return; - } - - if (!type.IsStatic()) - { - Logger.Error($"Color class {type.Name} must be static."); - return; - } - - foreach (var property in type.GetProperties()) - { - if (property.PropertyType != typeof(CustomColor)) + if (type.GetCustomAttribute() == null) { - continue; + return; } - if (property.GetValue(null) is not CustomColor color) + if (!type.IsStatic()) { - Logger.Error($"Color property {property.Name} in {type.Name} is not a CustomColor."); - continue; + Logger.Error($"Color class {type.Name} must be static."); + return; } - PaletteManager.CustomColors.Add(color); + foreach (var property in type.GetProperties()) + { + if (property.PropertyType != typeof(CustomColor)) + { + continue; + } + + if (property.GetValue(null) is not CustomColor color) + { + Logger.Error($"Color property {property.Name} in {type.Name} is not a CustomColor."); + continue; + } + + PaletteManager.CustomColors.Add(color); + } + } + catch (Exception e) + { + Logger.Error($"Failed to register color class {type.Name}: {e}"); } } - private static void RegisterModifier(Type type) + private static bool RegisterModifier(Type type) { - if (type.IsAssignableTo(typeof(BaseModifier))) + try { - ModifierManager.RegisterModifier(type); + return ModifierManager.RegisterModifier(type); + } + catch (Exception e) + { + Logger.Error($"Failed to register modifier {type.Name}: {e}"); + return false; } } - private static void RegisterButtonAttribute(Type type, MiraPluginInfo pluginInfo) + private static bool RegisterButtonAttribute(Type type, MiraPluginInfo pluginInfo) { - if (type.IsAssignableTo(typeof(CustomActionButton)) || type.IsAssignableTo(typeof(CustomActionButton<>))) + try { - CustomButtonManager.RegisterButton(type, pluginInfo); + return CustomButtonManager.RegisterButton(type, pluginInfo); } + catch (Exception e) + { + Logger.Error($"Failed to register button {type.Name}: {e}"); + } + + return false; } } From e412e5ead58f6157f341e1900d556717ca47b998 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Thu, 13 Feb 2025 16:51:00 -0500 Subject: [PATCH 13/65] Some fixes --- .../Buttons/Teleporter/TeleportButton.cs | 1 + .../Events/Vanilla/Meeting/EjectionEvent.cs | 13 ++------ MiraAPI/Hud/CustomActionButton.cs | 5 +++ MiraAPI/Hud/CustomButtonManager.cs | 1 - MiraAPI/Modifiers/ModifierComponent.cs | 14 ++++----- MiraAPI/Patches/Roles/EjectionPatches.cs | 6 +--- MiraAPI/Utilities/Extensions.cs | 11 ------- MiraAPI/Utilities/Helpers.cs | 31 ++++++++++++------- 8 files changed, 37 insertions(+), 45 deletions(-) diff --git a/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs b/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs index 9341596..ce20ddb 100644 --- a/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs +++ b/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs @@ -16,6 +16,7 @@ public class TeleportButton : CustomActionButton public override float Cooldown => OptionGroupSingleton.Instance.TeleportCooldown.Value; public override float EffectDuration => OptionGroupSingleton.Instance.TeleportDuration; + public override int MaxUses => 2; public override LoadableAsset Sprite => ExampleAssets.TeleportButton; diff --git a/MiraAPI/Events/Vanilla/Meeting/EjectionEvent.cs b/MiraAPI/Events/Vanilla/Meeting/EjectionEvent.cs index 31b08c1..1d25098 100644 --- a/MiraAPI/Events/Vanilla/Meeting/EjectionEvent.cs +++ b/MiraAPI/Events/Vanilla/Meeting/EjectionEvent.cs @@ -1,15 +1,10 @@ namespace MiraAPI.Events.Vanilla.Meeting; /// -/// The event that is invoked when a player is ejected. +/// The event that is invoked when a player is ejected. Non-cancelable. /// -public class EjectionEvent : MiraCancelableEvent +public class EjectionEvent : MiraEvent { - /// - /// Gets the player that is being ejected. - /// - public PlayerControl? Player { get; } - /// /// Gets the instance of the ExileController. /// @@ -18,11 +13,9 @@ public class EjectionEvent : MiraCancelableEvent /// /// Initializes a new instance of the class. /// - /// The player who is being ejected. /// The exile controller. - public EjectionEvent(PlayerControl player, ExileController controller) + public EjectionEvent(ExileController controller) { - Player = player; ExileController = controller; } } diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index f37f2eb..a1934b3 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -344,6 +344,11 @@ public abstract class CustomActionButton { UsesLeft--; Button?.SetUsesRemaining(UsesLeft); + + if (Button != null) + { + Button.usesRemainingSprite.color = UsesLeft == 0 ? Color.red : Color.white; + } } OnClick(); diff --git a/MiraAPI/Hud/CustomButtonManager.cs b/MiraAPI/Hud/CustomButtonManager.cs index 60f2fc6..0133353 100644 --- a/MiraAPI/Hud/CustomButtonManager.cs +++ b/MiraAPI/Hud/CustomButtonManager.cs @@ -1,6 +1,5 @@ using MiraAPI.Events.Mira; using MiraAPI.PluginLoading; -using Reactor.Utilities; using System; using System.Collections.Generic; using System.Reflection; diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 8b91d21..7db81f5 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -1,14 +1,14 @@ -using System; +using Il2CppInterop.Runtime.Attributes; +using MiraAPI.Modifiers.Types; +using MiraAPI.Utilities; +using Reactor.Utilities; +using Reactor.Utilities.Attributes; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; using System.Linq; using System.Text; -using Il2CppInterop.Runtime.Attributes; -using MiraAPI.Modifiers.Types; -using MiraAPI.Utilities; -using Reactor.Utilities; -using Reactor.Utilities.Attributes; using TMPro; using UnityEngine; @@ -107,7 +107,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) var baseModifiers = filteredModifiers as BaseModifier[] ?? filteredModifiers.ToArray(); - if (baseModifiers.Length != 0) + if (baseModifiers.Length != 0 && !MeetingHud.Instance) { var stringBuild = new StringBuilder(); foreach (var mod in baseModifiers) diff --git a/MiraAPI/Patches/Roles/EjectionPatches.cs b/MiraAPI/Patches/Roles/EjectionPatches.cs index 3acf228..48d6abb 100644 --- a/MiraAPI/Patches/Roles/EjectionPatches.cs +++ b/MiraAPI/Patches/Roles/EjectionPatches.cs @@ -15,13 +15,9 @@ public static class EjectionPatches [HarmonyPatch(nameof(ExileController.Begin))] public static void BeginPostfix(ExileController __instance) { - var @event = new EjectionEvent(__instance.initData.networkedPlayer.Object, __instance); + var @event = new EjectionEvent(__instance); MiraEventManager.InvokeEvent(@event); - if (@event.IsCancelled) - { - return; - } if (!__instance.initData.networkedPlayer || !__instance.initData.networkedPlayer.Role || __instance.initData.networkedPlayer.Role is not ICustomRole role) { diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index afa835b..dc3bba1 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -328,17 +328,6 @@ public static class Extensions .GetNearestDeadBodies(playerControl.GetTruePosition(), radius, Helpers.CreateFilter(Constants.NotShipMask)) .Find(component => component && !component.Reported); } - - /// - /// Get all living players. - /// - /// A list of alive players. - public static List GetAlivePlayers() - { - return PlayerControl.AllPlayerControls.ToArray().Where(x => !x.Data.IsDead).ToList(); - } - - /// /// Finds the nearest object of a specified type to a player. It will only work if the object has a collider. /// diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index cea3bef..29ae10e 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -1,5 +1,5 @@ -using System.Collections; -using MiraAPI.Roles; +using MiraAPI.Roles; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -15,6 +15,15 @@ namespace MiraAPI.Utilities; /// public static class Helpers { + /// + /// Get all living players. + /// + /// A list of alive players. + public static List GetAlivePlayers() + { + return PlayerControl.AllPlayerControls.ToArray().Where(x => !x.Data.IsDead).ToList(); + } + /// /// Returns an empty coroutine. /// @@ -99,14 +108,14 @@ public static class Helpers } return (from player in newList - let vector = player.GetTruePosition() - source - let magnitude = vector.magnitude - where !PhysicsHelpers.AnyNonTriggersBetween( - source, - vector.normalized, - magnitude, - Constants.ShipAndObjectsMask) - select player).ToList(); + let vector = player.GetTruePosition() - source + let magnitude = vector.magnitude + where !PhysicsHelpers.AnyNonTriggersBetween( + source, + vector.normalized, + magnitude, + Constants.ShipAndObjectsMask) + select player).ToList(); } /// @@ -168,7 +177,7 @@ public static class Helpers select playerControl); outputList.Sort( - delegate(PlayerControl a, PlayerControl b) + delegate (PlayerControl a, PlayerControl b) { var magnitude2 = (a.GetTruePosition() - source).magnitude; var magnitude3 = (b.GetTruePosition() - source).magnitude; From 328ee5254903c36e967e28067314cfc46862c1b7 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Thu, 13 Feb 2025 17:08:21 -0500 Subject: [PATCH 14/65] SetRole event --- .../Events/Vanilla/Gameplay/SetRoleEvent.cs | 30 +++++++++++++++++++ MiraAPI/Patches/Roles/RoleManagerPatches.cs | 10 +++++++ 2 files changed, 40 insertions(+) create mode 100644 MiraAPI/Events/Vanilla/Gameplay/SetRoleEvent.cs diff --git a/MiraAPI/Events/Vanilla/Gameplay/SetRoleEvent.cs b/MiraAPI/Events/Vanilla/Gameplay/SetRoleEvent.cs new file mode 100644 index 0000000..4c00cf2 --- /dev/null +++ b/MiraAPI/Events/Vanilla/Gameplay/SetRoleEvent.cs @@ -0,0 +1,30 @@ +using AmongUs.GameOptions; + +namespace MiraAPI.Events.Vanilla.Gameplay; + +/// +/// Event that is invoked after a player's role is set. This event is not cancelable. +/// +public class SetRoleEvent : MiraEvent +{ + /// + /// Gets the player whos role was changed. + /// + public PlayerControl Player { get; } + + /// + /// Gets the role that the player was set to. + /// + public RoleTypes Role { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The player. + /// The new role. + public SetRoleEvent(PlayerControl player, RoleTypes role) + { + Player = player; + Role = role; + } +} diff --git a/MiraAPI/Patches/Roles/RoleManagerPatches.cs b/MiraAPI/Patches/Roles/RoleManagerPatches.cs index 357b2eb..ed1c5e2 100644 --- a/MiraAPI/Patches/Roles/RoleManagerPatches.cs +++ b/MiraAPI/Patches/Roles/RoleManagerPatches.cs @@ -1,5 +1,7 @@ using AmongUs.GameOptions; using HarmonyLib; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla.Gameplay; using MiraAPI.Modifiers; using MiraAPI.Roles; using System.Linq; @@ -9,6 +11,14 @@ namespace MiraAPI.Patches.Roles; [HarmonyPatch(typeof(RoleManager))] public static class RoleManagerPatches { + [HarmonyPostfix] + [HarmonyPatch(nameof(RoleManager.SetRole))] + public static void SetRolePatch(RoleManager __instance, PlayerControl targetPlayer, RoleTypes roleType) + { + var @event = new SetRoleEvent(targetPlayer, roleType); + MiraEventManager.InvokeEvent(@event); + } + [HarmonyPostfix] [HarmonyPatch(nameof(RoleManager.SelectRoles))] public static void ModifierSelectionPatches(RoleManager __instance) From 8ed38c02cedc0fdc1f4b32f93a6398d1c7cc10b5 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Thu, 13 Feb 2025 17:37:44 -0500 Subject: [PATCH 15/65] This ones for Xtra --- MiraAPI/Hud/CustomActionButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index a1934b3..6b1c21e 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -154,7 +154,7 @@ public abstract class CustomActionButton /// Whether the button's position should change ingame. public virtual void SetButtonLocation(ButtonLocation location, bool moveButton = true) { - if (HudManager.Instance == null || Button == null) + if (!HudManager.InstanceExists || Button == null) { return; } From c7d16f7fc93a124032f48054e1118702c4641bd3 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Thu, 13 Feb 2025 17:43:24 -0500 Subject: [PATCH 16/65] Try fix console spam bug --- MiraAPI/Events/MiraEventManager.cs | 1 - MiraAPI/Patches/Events/CanUsePatches.cs | 14 ++++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/MiraAPI/Events/MiraEventManager.cs b/MiraAPI/Events/MiraEventManager.cs index f613cd5..eff933d 100644 --- a/MiraAPI/Events/MiraEventManager.cs +++ b/MiraAPI/Events/MiraEventManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Reactor.Utilities; namespace MiraAPI.Events; diff --git a/MiraAPI/Patches/Events/CanUsePatches.cs b/MiraAPI/Patches/Events/CanUsePatches.cs index 56a88e6..aa80fd7 100644 --- a/MiraAPI/Patches/Events/CanUsePatches.cs +++ b/MiraAPI/Patches/Events/CanUsePatches.cs @@ -26,11 +26,17 @@ public static class CanUsePatches [HarmonyPrefix] public static bool CanUsePatch(Il2CppSystem.Object __instance, [HarmonyArgument(0)] NetworkedPlayerInfo pc, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse) { - var @event = new PlayerCanUseEvent(__instance.Cast()); - MiraEventManager.InvokeEvent(@event); - canUse = couldUse = false; - return !@event.IsCancelled; + IUsable usable = __instance.Cast(); + if (usable != null) + { + var @event = new PlayerCanUseEvent(__instance.Cast()); + MiraEventManager.InvokeEvent(@event); + + return !@event.IsCancelled; + } + + return true; } } From 4db7605d27bde1be10ded96dc8a282a51789a5da Mon Sep 17 00:00:00 2001 From: ang-xd Date: Thu, 13 Feb 2025 18:10:42 -0500 Subject: [PATCH 17/65] Some changes to role visibility name text --- MiraAPI.Example/Roles/TeleporterRole.cs | 5 ++++ MiraAPI/Patches/Roles/HauntMenuPatch.cs | 27 +++++++++++++++++++ MiraAPI/Patches/Roles/NameTagPatch.cs | 9 ++++++- MiraAPI/Patches/Roles/RoleBehaviourPatches.cs | 25 ----------------- MiraAPI/Roles/ICustomRole.cs | 10 +++++++ 5 files changed, 50 insertions(+), 26 deletions(-) create mode 100644 MiraAPI/Patches/Roles/HauntMenuPatch.cs delete mode 100644 MiraAPI/Patches/Roles/RoleBehaviourPatches.cs diff --git a/MiraAPI.Example/Roles/TeleporterRole.cs b/MiraAPI.Example/Roles/TeleporterRole.cs index 9877f84..5b00f63 100644 --- a/MiraAPI.Example/Roles/TeleporterRole.cs +++ b/MiraAPI.Example/Roles/TeleporterRole.cs @@ -18,4 +18,9 @@ public class TeleporterRole : CrewmateRole, ICustomRole DefaultChance = 73, DefaultRoleCount = 4, }; + + public bool CanLocalPlayerSeeRole(PlayerControl player) + { + return true; + } } diff --git a/MiraAPI/Patches/Roles/HauntMenuPatch.cs b/MiraAPI/Patches/Roles/HauntMenuPatch.cs new file mode 100644 index 0000000..b174165 --- /dev/null +++ b/MiraAPI/Patches/Roles/HauntMenuPatch.cs @@ -0,0 +1,27 @@ +using HarmonyLib; +using MiraAPI.Roles; +using UnityEngine; + +namespace MiraAPI.Patches.Roles; + +/// +/// Patches the haunt menu to show the actual role name rather then team. +/// +[HarmonyPatch(typeof(HauntMenuMinigame), nameof(HauntMenuMinigame.SetFilterText))] +public static class HauntMenuMinigamePatch +{ + public static void Postfix(HauntMenuMinigame __instance) + { + if (__instance.HauntTarget.Data.IsDead) + { + __instance.FilterText.color = Color.white; + return; + } + + var role = __instance.HauntTarget.Data.Role; + var color = role is ICustomRole custom ? custom.RoleColor : role.TeamColor; + + __instance.FilterText.text = role.NiceName; + __instance.FilterText.color = color; + } +} \ No newline at end of file diff --git a/MiraAPI/Patches/Roles/NameTagPatch.cs b/MiraAPI/Patches/Roles/NameTagPatch.cs index 821db1c..7974351 100644 --- a/MiraAPI/Patches/Roles/NameTagPatch.cs +++ b/MiraAPI/Patches/Roles/NameTagPatch.cs @@ -1,10 +1,11 @@ using HarmonyLib; +using MiraAPI.Roles; using UnityEngine; namespace MiraAPI.Patches.Roles; /// -/// Set nametag color depending on option +/// Set nametag color depending on visibility. /// [HarmonyPatch(typeof(PlayerNameColor))] public static class NameTagPatch @@ -13,6 +14,12 @@ public static class NameTagPatch [HarmonyPatch(nameof(PlayerNameColor.Get), typeof(RoleBehaviour))] public static bool GetPatch([HarmonyArgument(0)] RoleBehaviour otherPlayerRole, ref Color __result) { + if (otherPlayerRole is ICustomRole customRole && customRole.CanLocalPlayerSeeRole(otherPlayerRole.Player)) + { + __result = customRole.RoleColor; + return false; + } + if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && otherPlayerRole.IsImpostor) { return true; diff --git a/MiraAPI/Patches/Roles/RoleBehaviourPatches.cs b/MiraAPI/Patches/Roles/RoleBehaviourPatches.cs deleted file mode 100644 index 98cb5e6..0000000 --- a/MiraAPI/Patches/Roles/RoleBehaviourPatches.cs +++ /dev/null @@ -1,25 +0,0 @@ -using HarmonyLib; -using MiraAPI.Roles; -using UnityEngine; - -namespace MiraAPI.Patches.Roles; - -[HarmonyPatch(typeof(RoleBehaviour))] -public static class RoleBehaviourPatches -{ - /// - /// Update TeamColor text for Launchpad roles - /// - [HarmonyPrefix] - [HarmonyPatch(nameof(RoleBehaviour.TeamColor), MethodType.Getter)] - public static bool PrefixTeamColorGetter(RoleBehaviour __instance, ref Color __result) - { - if (__instance is not ICustomRole behaviour) - { - return true; - } - - __result = behaviour.RoleColor; - return false; - } -} diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index a28bed9..63642cb 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -128,6 +128,16 @@ public interface ICustomRole Logger.Error($"Error getting count configuration for role: {RoleName}"); } + /// + /// Whether the local player can see this role. + /// + /// The player with the role. + /// Whether they can see the role (name color) or not. + public virtual bool CanLocalPlayerSeeRole(PlayerControl player) + { + return (Team == ModdedRoleTeams.Impostor && player.Data.Role.IsImpostor) || PlayerControl.LocalPlayer.Data.IsDead; + } + /// /// This method runs on the HudManager.Update method ONLY when the LOCAL player has this role. /// From 719ee0400d82158cbd94f21b9a5dc8193c5f67c7 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Thu, 13 Feb 2025 18:21:36 -0500 Subject: [PATCH 18/65] for MDB fr this time :pray: --- MiraAPI/Hud/CustomButtonManager.cs | 6 ++++++ MiraAPI/PluginLoading/MiraPluginManager.cs | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/MiraAPI/Hud/CustomButtonManager.cs b/MiraAPI/Hud/CustomButtonManager.cs index 0133353..2bdb9bf 100644 --- a/MiraAPI/Hud/CustomButtonManager.cs +++ b/MiraAPI/Hud/CustomButtonManager.cs @@ -2,6 +2,7 @@ using MiraAPI.PluginLoading; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Reflection; namespace MiraAPI.Hud; @@ -11,6 +12,11 @@ namespace MiraAPI.Hud; /// public static class CustomButtonManager { + /// + /// Gets a list of all registered custom buttons. + /// + public static ReadOnlyCollection Buttons { get; internal set; } = new([]); + internal static readonly List CustomButtons = []; internal static readonly Dictionary ButtonEventTypes = []; internal static readonly Dictionary ButtonCancelledEventTypes = []; diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 82362ec..678dee4 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -11,6 +11,7 @@ using Reactor.Networking; using Reactor.Utilities; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Reflection; @@ -79,6 +80,10 @@ public sealed class MiraPluginManager }; IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors; IL2CPPChainloader.Instance.Finished += MiraEventManager.SortAllHandlers; + IL2CPPChainloader.Instance.Finished += () => + { + CustomButtonManager.Buttons = new ReadOnlyCollection(CustomButtonManager.CustomButtons); + }; } /// From 7bf35fc8ae7a184e6d351c0c5df34169862a0a0d Mon Sep 17 00:00:00 2001 From: Pietrodjaowjao Date: Sat, 15 Feb 2025 15:49:09 -0300 Subject: [PATCH 19/65] Fix player skin resetting on no-teleport murder --- MiraAPI/Networking/CustomMurderRpc.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MiraAPI/Networking/CustomMurderRpc.cs b/MiraAPI/Networking/CustomMurderRpc.cs index 252ac91..134db97 100644 --- a/MiraAPI/Networking/CustomMurderRpc.cs +++ b/MiraAPI/Networking/CustomMurderRpc.cs @@ -250,11 +250,11 @@ public static class CustomMurderRpc } target.Die(DeathReason.Kill, true); - yield return source.MyPhysics.Animations.CoPlayCustomAnimation(anim.BlurAnim); - sourcePhys.Animations.PlayIdleAnimation(); if (teleportMurderer) { + yield return source.MyPhysics.Animations.CoPlayCustomAnimation(anim.BlurAnim); + sourcePhys.Animations.PlayIdleAnimation(); source.NetTransform.SnapTo(target.transform.position); KillAnimation.SetMovement(source, true); } From 2bb1d41e6575db190ae390fd46e10d2f56467ab4 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Sat, 15 Feb 2025 13:49:38 -0500 Subject: [PATCH 20/65] Fix issue #24 --- MiraAPI/Patches/Roles/RoleBehaviourPatch.cs | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 MiraAPI/Patches/Roles/RoleBehaviourPatch.cs diff --git a/MiraAPI/Patches/Roles/RoleBehaviourPatch.cs b/MiraAPI/Patches/Roles/RoleBehaviourPatch.cs new file mode 100644 index 0000000..55cc5a5 --- /dev/null +++ b/MiraAPI/Patches/Roles/RoleBehaviourPatch.cs @@ -0,0 +1,25 @@ +using HarmonyLib; +using MiraAPI.Roles; +using UnityEngine; + +namespace MiraAPI.Patches.Roles; + +[HarmonyPatch(typeof(RoleBehaviour))] +public static class RoleBehaviourPatch +{ + /// + /// Update TeamColor text for Mira roles. + /// + [HarmonyPrefix] + [HarmonyPatch(nameof(RoleBehaviour.TeamColor), MethodType.Getter)] + public static bool PrefixTeamColorGetter(RoleBehaviour __instance, ref Color __result) + { + if (__instance is not ICustomRole behaviour) + { + return true; + } + + __result = behaviour.RoleColor; + return false; + } +} \ No newline at end of file From 2b1db0ee24096130afcfb960a44ce3c2af994e25 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Sat, 15 Feb 2025 17:59:40 -0500 Subject: [PATCH 21/65] Whoops --- MiraAPI/Roles/ICustomRole.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index 63642cb..b42c9e3 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -135,7 +135,7 @@ public interface ICustomRole /// Whether they can see the role (name color) or not. public virtual bool CanLocalPlayerSeeRole(PlayerControl player) { - return (Team == ModdedRoleTeams.Impostor && player.Data.Role.IsImpostor) || PlayerControl.LocalPlayer.Data.IsDead; + return (PlayerControl.LocalPlayer.Data.Role.IsImpostor && player.Data.Role.IsImpostor) || PlayerControl.LocalPlayer.Data.IsDead; } /// From e910805e5003b9ab8243f772b9ac7db93345cdbb Mon Sep 17 00:00:00 2001 From: ang-xd Date: Sat, 15 Feb 2025 23:54:47 -0500 Subject: [PATCH 22/65] Delete log --- MiraAPI/Networking/CustomMurderRpc.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/MiraAPI/Networking/CustomMurderRpc.cs b/MiraAPI/Networking/CustomMurderRpc.cs index 134db97..7f1e60f 100644 --- a/MiraAPI/Networking/CustomMurderRpc.cs +++ b/MiraAPI/Networking/CustomMurderRpc.cs @@ -5,7 +5,6 @@ using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; using Reactor.Networking.Attributes; using Reactor.Networking.Rpc; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using System.Collections; using System.Linq; @@ -116,7 +115,6 @@ public static class CustomMurderRpc target.RemoveProtection(); } - Logger.Error($"{source.PlayerId} failed to murder {target.PlayerId} due to guardian angel protection"); return; } @@ -184,7 +182,6 @@ public static class CustomMurderRpc source.shapeshiftTargetPlayerId, target.PlayerId); source.MyPhysics.StartCoroutine(source.KillAnimations.Random()?.CoPerformCustomKill(source, target, createDeadBody, teleportMurderer)); - Logger.Error($"{source.PlayerId} succeeded in murdering {target.PlayerId}"); } /// From 0af392d60ed2115ca28e1adf20121cdf9adec135 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Sun, 16 Feb 2025 01:04:15 -0500 Subject: [PATCH 23/65] Make buttons act more like vanilla --- MiraAPI/Hud/CustomActionButton.cs | 65 +++++++++++++++++++++++++------ MiraAPI/Hud/CustomPlayerMenu.cs | 16 +++++--- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index 6b1c21e..7049a8e 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -65,6 +65,11 @@ public abstract class CustomActionButton /// public bool EffectActive { get; protected set; } + /// + /// Gets or sets a value indicating whether the timer is currently active. + /// + public bool TimerPaused { get; protected set; } + /// /// Gets or sets the amount of uses left. /// @@ -94,6 +99,7 @@ public abstract class CustomActionButton UsesLeft = MaxUses; Timer = AmongUsClient.Instance?.NetworkMode == NetworkModes.FreePlay ? 0 : InitialCooldown; EffectActive = false; + TimerPaused = false; Button = Object.Instantiate(HudManager.Instance.AbilityButton, parent); Button.name = Name + "Button"; @@ -248,6 +254,15 @@ public abstract class CustomActionButton SetTimer(Timer - amount); } + /// + /// Sets whether the timer is paused or not. + /// + /// Whether you want to pause/resume the timer. + public virtual void SetTimerPaused(bool val) + { + TimerPaused = val; + } + /// /// Set the amount of uses this button has left. /// @@ -256,6 +271,11 @@ public abstract class CustomActionButton { UsesLeft = Mathf.Clamp(amount, 0, int.MaxValue); Button?.SetUsesRemaining(UsesLeft); + + if (Button != null) + { + Button.usesRemainingSprite.color = UsesLeft == 0 ? Color.red : Color.white; + } } /// @@ -306,14 +326,24 @@ public abstract class CustomActionButton } /// - /// When the button is enabled, this method is called to determine if the button can be used. + /// When the button is usable, this method is called to determine if the button can be clicked. /// By default, it takes into account the timer, effect, and uses. /// You can override it to change the behavior. /// - /// A value that represents whether the button can be used or not. + /// A value that represents whether the button can be clicked or not. + public virtual bool CanClick() + { + return Timer <= 0 && !EffectActive && CanUse(); + } + + /// + /// Whether the button should light up or not. This is also the base for CanClick. + /// You can override it to change the behaviour. Do not include timer in here, that is for CanClick. + /// + /// A value that represents whether the button should light up or not. public virtual bool CanUse() { - return Timer <= 0 && !EffectActive && (!LimitedUses || UsesLeft > 0); + return !LimitedUses || UsesLeft > 0; } /// @@ -335,7 +365,7 @@ public abstract class CustomActionButton /// public virtual void ClickHandler() { - if (!CanUse()) + if (!CanClick()) { return; } @@ -344,15 +374,10 @@ public abstract class CustomActionButton { UsesLeft--; Button?.SetUsesRemaining(UsesLeft); - - if (Button != null) - { - Button.usesRemainingSprite.color = UsesLeft == 0 ? Color.red : Color.white; - } } OnClick(); - Button?.SetDisabled(); + if (HasEffect) { EffectActive = true; @@ -372,7 +397,7 @@ public abstract class CustomActionButton /// The local PlayerControl. public virtual void FixedUpdateHandler(PlayerControl playerControl) { - if (Timer >= 0) + if (Timer >= 0 && !TimerPaused) { Timer -= Time.deltaTime; } @@ -392,7 +417,17 @@ public abstract class CustomActionButton Button?.SetDisabled(); } - Button?.SetCoolDown(Timer, EffectActive ? EffectDuration : Cooldown); + if (EffectActive) + { + Button?.SetFillUp(Timer, EffectDuration); + + Button!.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString(); + Button!.cooldownTimerText.gameObject.SetActive(true); + } + else + { + Button?.SetCoolDown(Timer, Cooldown); + } FixedUpdate(playerControl); } @@ -451,6 +486,12 @@ public abstract class CustomActionButton : CustomActionButton where T : MonoB return base.CanUse() && Target != null; } + /// + public override bool CanClick() + { + return base.CanClick() && Target != null; + } + /// /// Use this to reset the button's target after used. /// diff --git a/MiraAPI/Hud/CustomPlayerMenu.cs b/MiraAPI/Hud/CustomPlayerMenu.cs index 7c861ec..a188596 100644 --- a/MiraAPI/Hud/CustomPlayerMenu.cs +++ b/MiraAPI/Hud/CustomPlayerMenu.cs @@ -1,11 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AmongUs.GameOptions; +using AmongUs.GameOptions; using Il2CppInterop.Runtime.Attributes; using MiraAPI.Patches.Stubs; using Reactor.Utilities.Attributes; using Reactor.Utilities.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; using UnityEngine; using UnityEngine.Events; @@ -86,10 +86,16 @@ public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr) /// Function to determine if player should show in the custom menu. /// Onclick action for player. [HideFromIl2Cpp] - public void Begin(Func playerMatch, Action onClick) + public void Begin(Func playerMatch, Action onClick) { MinigameStubs.Begin(this, null); + var back = BackButton.GetComponent(); + back.OnClick.AddListener((UnityAction)(() => + { + onClick(null); + })); + DestroyableSingleton.Instance.Analytics.MinigameOpened(PlayerControl.LocalPlayer.Data, TaskType); var list = PlayerControl.AllPlayerControls.ToArray().Where(playerMatch).ToList(); potentialVictims = []; From 37446964c5426b6447e0dcbfebdf59adb072791a Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 16 Feb 2025 22:33:07 -0500 Subject: [PATCH 24/65] change neutral team to Custom and add more customization --- MiraAPI.Example/Roles/NeutralKillerRole.cs | 9 +- MiraAPI/Patches/IntroCutscenePatches.cs | 48 ++++++++- .../Patches/Options/RoleSettingMenuPatches.cs | 98 ++++++------------- MiraAPI/Patches/Roles/TaskAdderPatch.cs | 4 +- MiraAPI/Roles/CustomRoleConfiguration.cs | 40 +++++++- MiraAPI/Roles/CustomRoleManager.cs | 2 +- MiraAPI/Roles/ModdedRoleTeams.cs | 2 +- MiraAPI/Roles/RoleGroup.cs | 16 +++ 8 files changed, 138 insertions(+), 81 deletions(-) create mode 100644 MiraAPI/Roles/RoleGroup.cs diff --git a/MiraAPI.Example/Roles/NeutralKillerRole.cs b/MiraAPI.Example/Roles/NeutralKillerRole.cs index 50a8810..d9e4798 100644 --- a/MiraAPI.Example/Roles/NeutralKillerRole.cs +++ b/MiraAPI.Example/Roles/NeutralKillerRole.cs @@ -5,17 +5,18 @@ namespace MiraAPI.Example.Roles; public class NeutralKillerRole : ImpostorRole, ICustomRole { - public string RoleName => "Neutral Killer"; - public string RoleDescription => "Neutral who can kill."; + public string RoleName => "Outcast Killer"; + public string RoleDescription => "Outcast who can kill."; public string RoleLongDescription => RoleDescription; public Color RoleColor => Color.magenta; - public ModdedRoleTeams Team => ModdedRoleTeams.Neutral; + public ModdedRoleTeams Team => ModdedRoleTeams.Custom; - public CustomRoleConfiguration Configuration => new CustomRoleConfiguration(this) + public CustomRoleConfiguration Configuration => new(this) { UseVanillaKillButton = true, CanGetKilled = true, CanUseVent = true, + RoleGroup = new RoleGroup("Outcast", Color.gray), }; public override void SpawnTaskHeader(PlayerControl playerControl) diff --git a/MiraAPI/Patches/IntroCutscenePatches.cs b/MiraAPI/Patches/IntroCutscenePatches.cs index 297ce94..571fd9b 100644 --- a/MiraAPI/Patches/IntroCutscenePatches.cs +++ b/MiraAPI/Patches/IntroCutscenePatches.cs @@ -30,6 +30,32 @@ public static class IntroCutscenePatches MiraEventManager.InvokeEvent(@event); } + [HarmonyPrefix] + [HarmonyPatch(nameof(IntroCutscene.BeginImpostor))] + public static void BeginImpostorPatch(IntroCutscene __instance) + { + if (PlayerControl.LocalPlayer.Data.Role is not ICustomRole customRole) + { + return; + } + + if (customRole.Configuration.IntroTeamColor is { } color) + { + __instance.BackgroundBar.material.SetColor(ShaderID.Color, color); + __instance.TeamTitle.color = color; + } + + if (customRole.Configuration.IntroTeamTitle is { } title) + { + __instance.TeamTitle.text = title; + } + + if (customRole.Configuration.IntroTeamDescription is { } description) + { + __instance.ImpostorText.text = description; + } + } + [HarmonyPrefix] [HarmonyPatch(nameof(IntroCutscene.BeginCrewmate))] public static bool BeginCrewmatePatch(IntroCutscene __instance) @@ -39,7 +65,23 @@ public static class IntroCutscenePatches return true; } - if (customRole.Team is not ModdedRoleTeams.Neutral) + if (customRole.Configuration.IntroTeamColor is { } color) + { + __instance.BackgroundBar.material.SetColor(ShaderID.Color, color); + __instance.TeamTitle.color = color; + } + + if (customRole.Configuration.IntroTeamTitle is { } title) + { + __instance.TeamTitle.text = title; + } + + if (customRole.Configuration.IntroTeamDescription is { } description) + { + __instance.ImpostorText.text = description; + } + + if (customRole.Team is not ModdedRoleTeams.Custom) { return true; } @@ -49,11 +91,7 @@ public static class IntroCutscenePatches position.y -= 0.25f; barTransform.position = position; - __instance.BackgroundBar.material.SetColor(ShaderID.Color, Color.gray); - __instance.TeamTitle.text = "NEUTRAL"; __instance.impostorScale = 1f; - __instance.ImpostorText.text = "You are Neutral. You do not have a team."; - __instance.TeamTitle.color = Color.gray; __instance.ourCrewmate = __instance.CreatePlayer( 0, diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 4461423..ec6a68b 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -1,14 +1,15 @@ -using HarmonyLib; +using System; +using System.Linq; +using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.Networking; using MiraAPI.Roles; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; +using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; using Reactor.Utilities; using Reactor.Utilities.Extensions; -using System; -using System.Linq; using TMPro; using UnityEngine; using UnityEngine.Events; @@ -54,23 +55,43 @@ public static class RoleSettingMenuPatches var num3 = 0; - var crewRoles = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values - .OfType() - .Where(role => role is { Team: ModdedRoleTeams.Crewmate, Configuration.HideSettings: false }) - .ToList() ?? []; + var roleGroups = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values.OfType() + .ToLookup(x => x.Configuration.RoleGroup); - if (crewRoles is { Count: > 0 }) + if (roleGroups is null) { + return true; + } + + // 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 name = grouping.Key.Name switch + { + "Crewmate" => StringNames.CrewmateRolesHeader, + "Impostor" => StringNames.ImpostorRolesHeader, + _ => CustomStringName.CreateAndRegister(grouping.Key.Name), + }; + var categoryHeaderEditRole = Object.Instantiate( __instance.categoryHeaderEditRoleOrigin, Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform); - categoryHeaderEditRole.SetHeader(StringNames.CrewmateRolesHeader, 20); + categoryHeaderEditRole.SetHeader(name, 20); categoryHeaderEditRole.transform.localPosition = new Vector3(4.986f, num, -2f); num -= 0.522f; - foreach (var role in crewRoles) + foreach (var role in grouping) { if (role is RoleBehaviour roleBehaviour) { @@ -78,65 +99,8 @@ public static class RoleSettingMenuPatches } num3++; } - } - - var impRoles = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values - .OfType() - .Where(role => role is { Team: ModdedRoleTeams.Impostor, Configuration.HideSettings: false }) - .ToList() ?? []; - - if (impRoles is { Count: > 0 }) - { num -= 0.4f; - var categoryHeaderEditRole2 = Object.Instantiate( - __instance.categoryHeaderEditRoleOrigin, - Vector3.zero, - Quaternion.identity, - __instance.RoleChancesSettings.transform); - categoryHeaderEditRole2.SetHeader(StringNames.ImpostorRolesHeader, 20); - categoryHeaderEditRole2.transform.localPosition = new Vector3(4.986f, num, -2f); - num -= 0.522f; - - foreach (var role in impRoles) - { - if (role is RoleBehaviour roleBehaviour) - { - CreateQuotaOption(__instance, roleBehaviour, ref num, num3); - } - num3++; - } } - - var neutRoles = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values - .OfType() - .Where(role => role is { Team: ModdedRoleTeams.Neutral, Configuration.HideSettings: false }) - .ToList() ?? []; - - if (neutRoles is { Count: > 0 }) - { - num -= 0.4f; - var categoryHeaderEditRole3 = Object.Instantiate( - __instance.categoryHeaderEditRoleOrigin, - Vector3.zero, - Quaternion.identity, - __instance.RoleChancesSettings.transform); - categoryHeaderEditRole3.SetHeader(StringNames.None, 20); - categoryHeaderEditRole3.Title.text = "Neutral Roles"; - categoryHeaderEditRole3.Background.color = Color.gray; - categoryHeaderEditRole3.Title.color = Color.white; - categoryHeaderEditRole3.transform.localPosition = new Vector3(4.986f, num, -2f); - num -= 0.522f; - - foreach (var role in neutRoles) - { - if (role is RoleBehaviour roleBehaviour) - { - CreateQuotaOption(__instance, roleBehaviour, ref num, num3); - } - num3++; - } - } - return false; } diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs index ee3c34d..e371b7d 100644 --- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs +++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs @@ -90,9 +90,9 @@ public static class TaskAdderPatch [HarmonyPatch(typeof(TaskAddButton), "Role", MethodType.Setter)] public static void RoleGetterPatch(TaskAddButton __instance) { - if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Neutral }) + if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Custom } customRole) { - __instance.FileImage.color = Color.gray; + __instance.FileImage.color = customRole.Configuration.IntroTeamColor ?? Color.gray; } __instance.RolloverHandler.OutColor = __instance.FileImage.color; diff --git a/MiraAPI/Roles/CustomRoleConfiguration.cs b/MiraAPI/Roles/CustomRoleConfiguration.cs index 5bd29bb..cd2220c 100644 --- a/MiraAPI/Roles/CustomRoleConfiguration.cs +++ b/MiraAPI/Roles/CustomRoleConfiguration.cs @@ -38,8 +38,31 @@ public struct CustomRoleConfiguration TasksCountForProgress = role.Team is ModdedRoleTeams.Crewmate; HideSettings = roleBehaviour?.IsDead == true; CanModifyChance = true; + IntroTeamColor = role.Team switch + { + ModdedRoleTeams.Custom => Color.gray, + _ => null, + }; + IntroTeamTitle = role.Team switch + { + ModdedRoleTeams.Custom => "NEUTRAL", + _ => null, + }; + IntroTeamDescription = role.Team switch + { + ModdedRoleTeams.Custom => "You are Neutral. You do not have a team.", + _ => null, + }; + RoleGroup = role.Team switch + { + ModdedRoleTeams.Crewmate => RoleGroup.Crewmate, + ModdedRoleTeams.Impostor => RoleGroup.Impostor, + ModdedRoleTeams.Custom => RoleGroup.Neutral, + }; } + public RoleGroup RoleGroup; + /// /// Gets the hard limit of players that can have this role. This property is used to set a limit in the Role Options menu. If set to 0, the role will not be assigned at start. /// @@ -105,6 +128,21 @@ public struct CustomRoleConfiguration /// public bool HideSettings; + /// + /// Gets the color to show during the "Team" part of the intro cutscene. + /// + public Color? IntroTeamColor; + + /// + /// Gets the title text to show during the "Team" part of the intro cutscene. + /// + public string? IntroTeamTitle; + + /// + /// Gets the description text to show during the "Team" part of the intro cutscene. + /// + public string? IntroTeamDescription; + /// /// Gets the outline color for the KillButton if is true. /// @@ -119,4 +157,4 @@ public struct CustomRoleConfiguration /// Gets the Ghost role that is applied when the player is killed. /// public RoleTypes GhostRole; -} \ No newline at end of file +} diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index eb291d8..cc79c02 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -79,7 +79,7 @@ public static class CustomRoleManager var roleId = RoleIds[roleType]; roleBehaviour.Role = (RoleTypes)roleId; - roleBehaviour.TeamType = customRole.Team == ModdedRoleTeams.Neutral ? RoleTeamTypes.Crewmate : (RoleTeamTypes)customRole.Team; + roleBehaviour.TeamType = customRole.Team == ModdedRoleTeams.Custom ? RoleTeamTypes.Crewmate : (RoleTeamTypes)customRole.Team; roleBehaviour.NameColor = customRole.RoleColor; roleBehaviour.StringName = CustomStringName.CreateAndRegister(customRole.RoleName); roleBehaviour.BlurbName = CustomStringName.CreateAndRegister(customRole.RoleDescription); diff --git a/MiraAPI/Roles/ModdedRoleTeams.cs b/MiraAPI/Roles/ModdedRoleTeams.cs index e599cd7..0d8c6a1 100644 --- a/MiraAPI/Roles/ModdedRoleTeams.cs +++ b/MiraAPI/Roles/ModdedRoleTeams.cs @@ -4,5 +4,5 @@ public enum ModdedRoleTeams { Crewmate, Impostor, - Neutral, + Custom, } diff --git a/MiraAPI/Roles/RoleGroup.cs b/MiraAPI/Roles/RoleGroup.cs new file mode 100644 index 0000000..4de874d --- /dev/null +++ b/MiraAPI/Roles/RoleGroup.cs @@ -0,0 +1,16 @@ +using UnityEngine; + +namespace MiraAPI.Roles; + +/// +/// Represents a group of roles. +/// +/// The group's name. +/// The group's color. +/// The priority to sort by on role setting screen. Negative means first. +public record struct RoleGroup(string Name, Color Color, int Priority = 0) +{ + public static RoleGroup Crewmate { get; } = new("Crewmate", Palette.CrewmateBlue, -2); + public static RoleGroup Impostor { get; } = new("Impostor", Palette.ImpostorRed, -1); + public static RoleGroup Neutral { get; } = new("Neutral", Color.gray); +} From f7c34fb5cd480d69b3fb08762fc7576a95f9c036 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Sun, 16 Feb 2025 23:13:04 -0500 Subject: [PATCH 25/65] Add ShowInFreeplay configuration --- MiraAPI/Patches/Roles/TaskAdderPatch.cs | 38 ++++++++++++------------ MiraAPI/Roles/CustomRoleConfiguration.cs | 6 ++++ 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs index e371b7d..6b549a2 100644 --- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs +++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs @@ -1,10 +1,10 @@ using AmongUs.GameOptions; using HarmonyLib; -using System; -using System.Collections.Generic; using MiraAPI.PluginLoading; using MiraAPI.Roles; using MiraAPI.Utilities.Assets; +using System; +using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine; @@ -186,23 +186,23 @@ public static class TaskAdderPatch switch (taskAddButton.MyTask.TaskType) { case TaskTypes.DivertPower: - { - var targetSystem = taskAddButton.MyTask.Cast().TargetSystem; - taskAddButton.Text.text = TranslationController.Instance.GetString( - StringNames.DivertPowerTo, - TranslationController.Instance.GetString(targetSystem)); - break; - } + { + var targetSystem = taskAddButton.MyTask.Cast().TargetSystem; + taskAddButton.Text.text = TranslationController.Instance.GetString( + StringNames.DivertPowerTo, + TranslationController.Instance.GetString(targetSystem)); + break; + } case TaskTypes.FixWeatherNode: - { - var nodeId = taskAddButton.MyTask.Cast().NodeId; - taskAddButton.Text.text = - TranslationController.Instance.GetString( - StringNames.FixWeatherNode) + " " + - TranslationController.Instance.GetString( - WeatherSwitchGame.ControlNames[nodeId]); - break; - } + { + var nodeId = taskAddButton.MyTask.Cast().NodeId; + taskAddButton.Text.text = + TranslationController.Instance.GetString( + StringNames.FixWeatherNode) + " " + + TranslationController.Instance.GetString( + WeatherSwitchGame.ControlNames[nodeId]); + break; + } default: taskAddButton.Text.text = TranslationController.Instance.GetString(taskAddButton.MyTask.TaskType); @@ -276,7 +276,7 @@ public static class TaskAdderPatch { var roleBehaviour = plugin.CustomRoles.ElementAt(m).Value; if (roleBehaviour.Role == RoleTypes.ImpostorGhost || roleBehaviour.Role == RoleTypes.CrewmateGhost || - roleBehaviour.IsDead) + (roleBehaviour is ICustomRole customRole && !customRole.Configuration.ShowInFreeplay)) { continue; } diff --git a/MiraAPI/Roles/CustomRoleConfiguration.cs b/MiraAPI/Roles/CustomRoleConfiguration.cs index cd2220c..a6b1b21 100644 --- a/MiraAPI/Roles/CustomRoleConfiguration.cs +++ b/MiraAPI/Roles/CustomRoleConfiguration.cs @@ -37,6 +37,7 @@ public struct CustomRoleConfiguration CanUseSabotage = role.Team is ModdedRoleTeams.Impostor; TasksCountForProgress = role.Team is ModdedRoleTeams.Crewmate; HideSettings = roleBehaviour?.IsDead == true; + ShowInFreeplay = roleBehaviour?.IsDead == false; CanModifyChance = true; IntroTeamColor = role.Team switch { @@ -128,6 +129,11 @@ public struct CustomRoleConfiguration /// public bool HideSettings; + /// + /// Gets a value indicating whether the role should show up in the Freeplay Role Selection menu. + /// + public bool ShowInFreeplay; + /// /// Gets the color to show during the "Team" part of the intro cutscene. /// From 113c716fd3c64832c5f6896f899269d4c2d70fdf Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 17 Feb 2025 00:00:37 -0500 Subject: [PATCH 26/65] hide role groups in settings menu --- .../Patches/Options/RoleSettingMenuPatches.cs | 76 +++++++++++++++++-- 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index ec6a68b..790024a 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; @@ -21,6 +22,10 @@ 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; } = []; + [HarmonyPrefix] [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))] public static bool PatchStart(RolesSettingsMenu __instance) @@ -75,6 +80,8 @@ public static class RoleSettingMenuPatches continue; } + RoleGroupHidden.TryAdd(grouping.Key, false); + var name = grouping.Key.Name switch { "Crewmate" => StringNames.CrewmateRolesHeader, @@ -89,18 +96,71 @@ public static class RoleSettingMenuPatches __instance.RoleChancesSettings.transform); categoryHeaderEditRole.SetHeader(name, 20); categoryHeaderEditRole.transform.localPosition = new Vector3(4.986f, num, -2f); + categoryHeaderEditRole.transform.Find("LabelSprite").transform.localScale = new Vector3(1.3f, 1, 0.5529f); + categoryHeaderEditRole.transform.Find("QuotaHeader").gameObject.SetActive(!RoleGroupHidden[grouping.Key]); + + CategoryHeaderEditRoles.Add(categoryHeaderEditRole); + num -= 0.522f; - foreach (var role in grouping) + var label = RoleGroupHidden[grouping.Key] + ? "(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); + newText.gameObject.GetComponent().Destroy(); + + if (!RoleGroupHidden[grouping.Key]) { - if (role is RoleBehaviour roleBehaviour) + foreach (var role in grouping) { - CreateQuotaOption(__instance, roleBehaviour, ref num, num3); + if (role is not RoleBehaviour roleBehaviour) + { + continue; + } + + var option = CreateQuotaOption(__instance, roleBehaviour, ref num, num3); + if (option is not null) + { + RoleOptionSettings.Add(option); + num3++; + } } - num3++; } - num -= 0.4f; + + var boxCol = categoryHeaderEditRole.gameObject.AddComponent(); + boxCol.size = new Vector2(3, 0.25f); + boxCol.offset = new Vector2(-2.05f, -.175f); + + var headerBtn = categoryHeaderEditRole.gameObject.AddComponent(); + headerBtn.ClickSound = __instance.BackButton.GetComponent().ClickSound; + headerBtn.OnMouseOver = new UnityEvent(); + headerBtn.OnMouseOut = new UnityEvent(); + headerBtn.OnClick.AddListener( + (UnityAction)(() => + { + RoleGroupHidden[grouping.Key] = !RoleGroupHidden[grouping.Key]; + foreach (var header in CategoryHeaderEditRoles) + { + header.gameObject.Destroy(); + } + CategoryHeaderEditRoles.Clear(); + foreach (var option in RoleOptionSettings) + { + option.gameObject.Destroy(); + } + RoleOptionSettings.Clear(); + __instance.SetQuotaTab(); + })); + headerBtn.SetButtonEnableState(true); + + if (!RoleGroupHidden[grouping.Key]) + { + num -= 0.4f; + } } + __instance.scrollBar.CalculateAndSetYBounds(__instance.roleChances.Count + 5, 1f, 6f, 0.43f); return false; } @@ -259,12 +319,12 @@ public static class RoleSettingMenuPatches __instance.RefreshChildren(); } - private static void CreateQuotaOption(RolesSettingsMenu __instance, RoleBehaviour role, ref float yPos, int index) + private static RoleOptionSetting? CreateQuotaOption(RolesSettingsMenu __instance, RoleBehaviour role, ref float yPos, int index) { if (role is not ICustomRole customRole) { Logger.Error($"Role {role.NiceName} is not a custom role."); - return; + return null; } var roleOptionSetting = Object.Instantiate( @@ -321,5 +381,7 @@ public static class RoleSettingMenuPatches { yPos += -0.43f; } + + return roleOptionSetting; } } From ee15a1acbe148fbb5380b0e162c86aa0dd730a3b Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 17 Feb 2025 09:46:09 -0500 Subject: [PATCH 27/65] fix neutral default role group title --- MiraAPI/Roles/RoleGroup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MiraAPI/Roles/RoleGroup.cs b/MiraAPI/Roles/RoleGroup.cs index 4de874d..4d9a0c5 100644 --- a/MiraAPI/Roles/RoleGroup.cs +++ b/MiraAPI/Roles/RoleGroup.cs @@ -12,5 +12,5 @@ public record struct RoleGroup(string Name, Color Color, int Priority = 0) { public static RoleGroup Crewmate { get; } = new("Crewmate", Palette.CrewmateBlue, -2); public static RoleGroup Impostor { get; } = new("Impostor", Palette.ImpostorRed, -1); - public static RoleGroup Neutral { get; } = new("Neutral", Color.gray); + public static RoleGroup Neutral { get; } = new("Neutral Roles", Color.gray); } From 4508aaeb55d3243ec24d3c1a30668b49c8ffb6f4 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 17 Feb 2025 10:32:18 -0500 Subject: [PATCH 28/65] add role stubs --- MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs | 228 ++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs diff --git a/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs b/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs new file mode 100644 index 0000000..7f132fd --- /dev/null +++ b/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs @@ -0,0 +1,228 @@ +using System.Runtime.CompilerServices; +using HarmonyLib; +using Il2CppSystem.Text; + +namespace MiraAPI.Patches.Stubs; + +[HarmonyPatch] +public static class RoleBehaviourStubs +{ + /// + /// Stub method for RoleBehaviour.Initialize. + /// + /// The RoleBehaviour object. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.Initialize))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Initialize(RoleBehaviour instance) + { + // nothing needed + } + + /// + /// Stub method for RoleBehaviour.AdjustTasks. + /// + /// The RoleBehaviour object. + /// The PlayerControl to adjust tasks for. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.AdjustTasks))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void AdjustTasks(RoleBehaviour instance, PlayerControl player) + { + // nothing needed + } + + /// + /// Stub method for RoleBehaviour.AppendTaskHint. + /// + /// The RoleBehaviour object. + /// The StringBuilder to append the task hint to. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.AppendTaskHint))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void AppendTaskHint(RoleBehaviour instance, StringBuilder taskStringBuilder) + { + // nothing needed + } + + /// + /// Stub method for RoleBehaviour.CanUse. + /// + /// The RoleBehaviour object. + /// The IUsable console to check. + /// Whether the console can be used. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.CanUse))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool CanUse(RoleBehaviour instance, IUsable console) + { + return default; + } + + /// + /// Stub method for RoleBehaviour.Deinitialize. + /// + /// The RoleBehaviour object. + /// The PlayerControl to deinitialize for. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.Deinitialize))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Deinitialize(RoleBehaviour instance, PlayerControl targetPlayer) + { + // nothing needed + } + + /// + /// Stub method for RoleBehaviour.DidWin. + /// + /// The RoleBehaviour object. + /// The reason for game over. + /// Whether the role won. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.DidWin))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool DidWin(RoleBehaviour instance, GameOverReason gameOverReason) + { + return default; + } + + /// + /// Stub method for RoleBehaviour.FindClosestTarget. + /// + /// The RoleBehaviour object. + /// The closest target PlayerControl. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.FindClosestTarget))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static PlayerControl FindClosestTarget(RoleBehaviour instance) + { + return null; + } + + /// + /// Stub method for RoleBehaviour.GetAbilityDistance. + /// + /// The RoleBehaviour object. + /// The ability distance. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.GetAbilityDistance))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static float GetAbilityDistance(RoleBehaviour instance) + { + return default; + } + + /// + /// Stub method for RoleBehaviour.IsValidTarget. + /// + /// The RoleBehaviour object. + /// The NetworkedPlayerInfo to validate. + /// Whether the target is valid. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.IsValidTarget))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool IsValidTarget(RoleBehaviour instance, NetworkedPlayerInfo target) + { + return default; + } + + /// + /// Stub method for RoleBehaviour.OnDeath. + /// + /// The RoleBehaviour object. + /// The reason for death. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.OnDeath))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void OnDeath(RoleBehaviour instance, DeathReason reason) + { + // nothing needed + } + + /// + /// Stub method for RoleBehaviour.OnMeetingStart. + /// + /// The RoleBehaviour object. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.OnMeetingStart))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void OnMeetingStart(RoleBehaviour instance) + { + // nothing needed + } + + /// + /// Stub method for RoleBehaviour.OnVotingComplete. + /// + /// The RoleBehaviour object. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.OnVotingComplete))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void OnVotingComplete(RoleBehaviour instance) + { + // nothing needed + } + + /// + /// Stub method for RoleBehaviour.SetCooldown. + /// + /// The RoleBehaviour object. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.SetCooldown))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void SetCooldown(RoleBehaviour instance) + { + // nothing needed + } + + /// + /// Stub method for RoleBehaviour.SetPlayerTarget. + /// + /// The RoleBehaviour object. + /// The PlayerControl target to set. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.SetPlayerTarget))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void SetPlayerTarget(RoleBehaviour instance, PlayerControl target) + { + // nothing needed + } + + /// + /// Stub method for RoleBehaviour.SetUsableTarget. + /// + /// The RoleBehaviour object. + /// The IUsable target to set. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.SetUsableTarget))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void SetUsableTarget(RoleBehaviour instance, IUsable target) + { + // nothing needed + } + + /// + /// Stub method for RoleBehaviour.SpawnTaskHeader. + /// + /// The RoleBehaviour object. + /// The PlayerControl to spawn the task header for. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.SpawnTaskHeader))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void SpawnTaskHeader(RoleBehaviour instance, PlayerControl playerControl) + { + // nothing needed + } + + /// + /// Stub method for RoleBehaviour.UseAbility. + /// + /// The RoleBehaviour object. + [HarmonyReversePatch] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.UseAbility))] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void UseAbility(RoleBehaviour instance) + { + // nothing needed + } +} From 3de9482233580c5afce3af2d3590dc514767a238 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 17 Feb 2025 10:32:28 -0500 Subject: [PATCH 29/65] add missing minigame stubs --- MiraAPI/Patches/Stubs/MinigameStubs.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/MiraAPI/Patches/Stubs/MinigameStubs.cs b/MiraAPI/Patches/Stubs/MinigameStubs.cs index aa8d05f..3818d09 100644 --- a/MiraAPI/Patches/Stubs/MinigameStubs.cs +++ b/MiraAPI/Patches/Stubs/MinigameStubs.cs @@ -21,4 +21,16 @@ public static class MinigameStubs { // nothing needed } + + /// + /// Reverse patch for Minigame.Close. + /// + /// The Minigame instance. + [HarmonyReversePatch] + [HarmonyPatch(typeof(Minigame), nameof(Minigame.Close), [])] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Close(Minigame instance) + { + // nothing needed + } } From e950278bf986d63fd9c9c2ba1d8d55193d4e1461 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 17 Feb 2025 10:58:25 -0500 Subject: [PATCH 30/65] seperate some role configs --- MiraAPI.Example/Roles/NeutralKillerRole.cs | 3 +- MiraAPI/Patches/IntroCutscenePatches.cs | 36 +++++------------- .../Patches/Options/RoleSettingMenuPatches.cs | 4 +- MiraAPI/Patches/Roles/TaskAdderPatch.cs | 12 +++++- MiraAPI/Roles/CustomRoleConfiguration.cs | 38 ------------------- MiraAPI/Roles/ICustomRole.cs | 23 ++++++++++- MiraAPI/Roles/RoleGroup.cs | 16 -------- MiraAPI/Roles/RoleIntroConfiguration.cs | 20 ++++++++++ MiraAPI/Roles/RoleOptionsGroup.cs | 27 +++++++++++++ 9 files changed, 93 insertions(+), 86 deletions(-) delete mode 100644 MiraAPI/Roles/RoleGroup.cs create mode 100644 MiraAPI/Roles/RoleIntroConfiguration.cs create mode 100644 MiraAPI/Roles/RoleOptionsGroup.cs diff --git a/MiraAPI.Example/Roles/NeutralKillerRole.cs b/MiraAPI.Example/Roles/NeutralKillerRole.cs index d9e4798..eb937e1 100644 --- a/MiraAPI.Example/Roles/NeutralKillerRole.cs +++ b/MiraAPI.Example/Roles/NeutralKillerRole.cs @@ -16,9 +16,10 @@ public class NeutralKillerRole : ImpostorRole, ICustomRole UseVanillaKillButton = true, CanGetKilled = true, CanUseVent = true, - RoleGroup = new RoleGroup("Outcast", Color.gray), }; + public RoleOptionsGroup RoleOptionsGroup { get; } = new("Outcast", Color.gray); + public override void SpawnTaskHeader(PlayerControl playerControl) { // remove existing task header. diff --git a/MiraAPI/Patches/IntroCutscenePatches.cs b/MiraAPI/Patches/IntroCutscenePatches.cs index 571fd9b..b98c58e 100644 --- a/MiraAPI/Patches/IntroCutscenePatches.cs +++ b/MiraAPI/Patches/IntroCutscenePatches.cs @@ -39,20 +39,12 @@ public static class IntroCutscenePatches return; } - if (customRole.Configuration.IntroTeamColor is { } color) + if (customRole.IntroConfiguration is { } introConfig) { - __instance.BackgroundBar.material.SetColor(ShaderID.Color, color); - __instance.TeamTitle.color = color; - } - - if (customRole.Configuration.IntroTeamTitle is { } title) - { - __instance.TeamTitle.text = title; - } - - if (customRole.Configuration.IntroTeamDescription is { } description) - { - __instance.ImpostorText.text = description; + __instance.BackgroundBar.material.SetColor(ShaderID.Color, introConfig.IntroTeamColor); + __instance.TeamTitle.color = introConfig.IntroTeamColor; + __instance.TeamTitle.text = introConfig.IntroTeamTitle; + __instance.ImpostorText.text = introConfig.IntroTeamDescription; } } @@ -65,20 +57,12 @@ public static class IntroCutscenePatches return true; } - if (customRole.Configuration.IntroTeamColor is { } color) + if (customRole.IntroConfiguration is { } introConfig) { - __instance.BackgroundBar.material.SetColor(ShaderID.Color, color); - __instance.TeamTitle.color = color; - } - - if (customRole.Configuration.IntroTeamTitle is { } title) - { - __instance.TeamTitle.text = title; - } - - if (customRole.Configuration.IntroTeamDescription is { } description) - { - __instance.ImpostorText.text = description; + __instance.BackgroundBar.material.SetColor(ShaderID.Color, introConfig.IntroTeamColor); + __instance.TeamTitle.color = introConfig.IntroTeamColor; + __instance.TeamTitle.text = introConfig.IntroTeamTitle; + __instance.ImpostorText.text = introConfig.IntroTeamDescription; } if (customRole.Team is not ModdedRoleTeams.Custom) diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 790024a..9212e86 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -22,7 +22,7 @@ namespace MiraAPI.Patches.Options; [HarmonyPatch(typeof(RolesSettingsMenu))] public static class RoleSettingMenuPatches { - private static Dictionary RoleGroupHidden { get; } = []; + private static Dictionary RoleGroupHidden { get; } = []; private static List CategoryHeaderEditRoles { get; } = []; private static List RoleOptionSettings { get; } = []; @@ -61,7 +61,7 @@ public static class RoleSettingMenuPatches var num3 = 0; var roleGroups = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values.OfType() - .ToLookup(x => x.Configuration.RoleGroup); + .ToLookup(x => x.RoleOptionsGroup); if (roleGroups is null) { diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs index 6b549a2..98e93bc 100644 --- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs +++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs @@ -21,6 +21,10 @@ public static class TaskAdderPatch private static readonly Dictionary ModsFolders = []; private static Scroller? _scroller; + /// + /// Adds custom roles to the TaskAdder. + /// + /// TaskAdder instance. [HarmonyPostfix] [HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.Begin))] public static void AddRolesFolder(TaskAdderGame __instance) @@ -86,13 +90,17 @@ public static class TaskAdderPatch __instance.GoToRoot(); } + /// + /// Sets the color of the role button based on the role. + /// + /// TaskAddButton instance. [HarmonyPostfix] - [HarmonyPatch(typeof(TaskAddButton), "Role", MethodType.Setter)] + [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.Configuration.IntroTeamColor ?? Color.gray; + __instance.FileImage.color = customRole.IntroConfiguration?.IntroTeamColor ?? Color.gray; } __instance.RolloverHandler.OutColor = __instance.FileImage.color; diff --git a/MiraAPI/Roles/CustomRoleConfiguration.cs b/MiraAPI/Roles/CustomRoleConfiguration.cs index a6b1b21..2f69d09 100644 --- a/MiraAPI/Roles/CustomRoleConfiguration.cs +++ b/MiraAPI/Roles/CustomRoleConfiguration.cs @@ -39,31 +39,8 @@ public struct CustomRoleConfiguration HideSettings = roleBehaviour?.IsDead == true; ShowInFreeplay = roleBehaviour?.IsDead == false; CanModifyChance = true; - IntroTeamColor = role.Team switch - { - ModdedRoleTeams.Custom => Color.gray, - _ => null, - }; - IntroTeamTitle = role.Team switch - { - ModdedRoleTeams.Custom => "NEUTRAL", - _ => null, - }; - IntroTeamDescription = role.Team switch - { - ModdedRoleTeams.Custom => "You are Neutral. You do not have a team.", - _ => null, - }; - RoleGroup = role.Team switch - { - ModdedRoleTeams.Crewmate => RoleGroup.Crewmate, - ModdedRoleTeams.Impostor => RoleGroup.Impostor, - ModdedRoleTeams.Custom => RoleGroup.Neutral, - }; } - public RoleGroup RoleGroup; - /// /// Gets the hard limit of players that can have this role. This property is used to set a limit in the Role Options menu. If set to 0, the role will not be assigned at start. /// @@ -134,21 +111,6 @@ public struct CustomRoleConfiguration /// public bool ShowInFreeplay; - /// - /// Gets the color to show during the "Team" part of the intro cutscene. - /// - public Color? IntroTeamColor; - - /// - /// Gets the title text to show during the "Team" part of the intro cutscene. - /// - public string? IntroTeamTitle; - - /// - /// Gets the description text to show during the "Team" part of the intro cutscene. - /// - public string? IntroTeamDescription; - /// /// Gets the outline color for the KillButton if is true. /// diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index b42c9e3..0c77019 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -1,4 +1,5 @@ -using BepInEx.Configuration; +using System; +using BepInEx.Configuration; using MiraAPI.Modifiers; using MiraAPI.PluginLoading; using MiraAPI.Utilities; @@ -43,6 +44,26 @@ public interface ICustomRole /// CustomRoleConfiguration Configuration { get; } + /// + /// Gets the role options group. + /// + public RoleOptionsGroup RoleOptionsGroup => Team switch + { + ModdedRoleTeams.Crewmate => RoleOptionsGroup.Crewmate, + ModdedRoleTeams.Impostor => RoleOptionsGroup.Impostor, + ModdedRoleTeams.Custom => RoleOptionsGroup.Neutral, + _ => new RoleOptionsGroup(RoleName, RoleColor), + }; + + /// + /// Gets the role's IntroCutscene configuration. + /// + public RoleIntroConfiguration? IntroConfiguration => Team switch + { + ModdedRoleTeams.Custom => RoleIntroConfiguration.Neutral, + _ => null, + }; + /// /// Gets the parent mod of this role. /// diff --git a/MiraAPI/Roles/RoleGroup.cs b/MiraAPI/Roles/RoleGroup.cs deleted file mode 100644 index 4d9a0c5..0000000 --- a/MiraAPI/Roles/RoleGroup.cs +++ /dev/null @@ -1,16 +0,0 @@ -using UnityEngine; - -namespace MiraAPI.Roles; - -/// -/// Represents a group of roles. -/// -/// The group's name. -/// The group's color. -/// The priority to sort by on role setting screen. Negative means first. -public record struct RoleGroup(string Name, Color Color, int Priority = 0) -{ - public static RoleGroup Crewmate { get; } = new("Crewmate", Palette.CrewmateBlue, -2); - public static RoleGroup Impostor { get; } = new("Impostor", Palette.ImpostorRed, -1); - public static RoleGroup Neutral { get; } = new("Neutral Roles", Color.gray); -} diff --git a/MiraAPI/Roles/RoleIntroConfiguration.cs b/MiraAPI/Roles/RoleIntroConfiguration.cs new file mode 100644 index 0000000..53ca749 --- /dev/null +++ b/MiraAPI/Roles/RoleIntroConfiguration.cs @@ -0,0 +1,20 @@ +using UnityEngine; + +namespace MiraAPI.Roles; + +/// +/// Used to configure the intro cutscene. +/// +/// The color to show during the "Team" part of the intro cutscene. +/// The title text to show during the "Team" part of the intro cutscene. +/// The description text to show during the "Team" part of the intro cutscene. +public record struct RoleIntroConfiguration(Color IntroTeamColor, string IntroTeamTitle, string IntroTeamDescription) +{ + /// + /// Gets the default configuration for the "Neutral" team. + /// + public static RoleIntroConfiguration Neutral { get; } = new( + Color.gray, + "NEUTRAL", + "You are Neutral. You do not have a team."); +} diff --git a/MiraAPI/Roles/RoleOptionsGroup.cs b/MiraAPI/Roles/RoleOptionsGroup.cs new file mode 100644 index 0000000..a1785b0 --- /dev/null +++ b/MiraAPI/Roles/RoleOptionsGroup.cs @@ -0,0 +1,27 @@ +using UnityEngine; + +namespace MiraAPI.Roles; + +/// +/// Represents a group of roles. +/// +/// The group's name. +/// The group's color. +/// The priority to sort by on role setting screen. Negative means first. +public record struct RoleOptionsGroup(string Name, Color Color, int Priority = 0) +{ + /// + /// Gets the default group for crewmates. + /// + public static RoleOptionsGroup Crewmate { get; } = new("Crewmate", Palette.CrewmateBlue, -2); + + /// + /// Gets the default group for impostors. + /// + public static RoleOptionsGroup Impostor { get; } = new("Impostor", Palette.ImpostorRed, -1); + + /// + /// Gets the default group for neutral roles. + /// + public static RoleOptionsGroup Neutral { get; } = new("Neutral Roles", Color.gray); +} From cb1a818377b21119b4588933dadbe34b1a6fcc0d Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:03:39 -0500 Subject: [PATCH 31/65] role configuration improvements --- MiraAPI/Roles/CustomRoleConfiguration.cs | 77 +++++++++++------------- 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/MiraAPI/Roles/CustomRoleConfiguration.cs b/MiraAPI/Roles/CustomRoleConfiguration.cs index 2f69d09..4500331 100644 --- a/MiraAPI/Roles/CustomRoleConfiguration.cs +++ b/MiraAPI/Roles/CustomRoleConfiguration.cs @@ -7,7 +7,7 @@ namespace MiraAPI.Roles; /// /// Used to configure the specific settings of a role. /// -public struct CustomRoleConfiguration +public record struct CustomRoleConfiguration { /// /// Initializes a new instance of the struct. @@ -17,11 +17,6 @@ public struct CustomRoleConfiguration { var roleBehaviour = role as RoleBehaviour; - MaxRoleCount = 15; - DefaultRoleCount = 0; - DefaultChance = 0; - - OptionsScreenshot = Icon = MiraAssets.Empty; AffectedByLightOnAirship = role.Team == ModdedRoleTeams.Crewmate; KillButtonOutlineColor = role.Team switch { @@ -29,7 +24,6 @@ public struct CustomRoleConfiguration ModdedRoleTeams.Crewmate => Palette.CrewmateBlue, _ => role.RoleColor, }; - RoleHintType = RoleHintType.RoleTab; GhostRole = role.Team is ModdedRoleTeams.Impostor ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost; CanGetKilled = roleBehaviour?.IsDead == false && role.Team is not ModdedRoleTeams.Impostor; UseVanillaKillButton = role.Team is ModdedRoleTeams.Impostor; @@ -38,91 +32,90 @@ public struct CustomRoleConfiguration TasksCountForProgress = role.Team is ModdedRoleTeams.Crewmate; HideSettings = roleBehaviour?.IsDead == true; ShowInFreeplay = roleBehaviour?.IsDead == false; - CanModifyChance = true; } /// - /// Gets the hard limit of players that can have this role. This property is used to set a limit in the Role Options menu. If set to 0, the role will not be assigned at start. + /// Gets or sets the hard limit of players that can have this role. This property is used to set a limit in the Role Options menu. If set to 0, the role will not be assigned at start. /// - public int MaxRoleCount; + public int MaxRoleCount { get; set; } = 15; /// - /// Gets the default role count. + /// Gets or sets the default role count. /// - public int DefaultRoleCount; + public int DefaultRoleCount { get; set; } = 0; /// - /// Gets the default role chance. + /// Gets or sets the default role chance. /// - public int DefaultChance; + public int DefaultChance { get; set; } = 0; /// - /// Whether the chance option can be changed or not. + /// Gets or sets a value indicating whether the chance option can be changed or not. /// - public bool CanModifyChance; + public bool CanModifyChance { get; set; } = true; /// - /// Gets the Sprite used for the Role Options menu screenshot. + /// Gets or sets the Sprite used for the Role Options menu screenshot. /// - public LoadableAsset OptionsScreenshot; + public LoadableAsset OptionsScreenshot { get; set; } = MiraAssets.Empty; /// - /// Gets the Sprite used for the Role Icon. + /// Gets or sets the Sprite used for the Role Icon. /// - public LoadableAsset Icon; + public LoadableAsset Icon { get; set; } = MiraAssets.Empty; /// - /// Gets a value indicating whether the role is affected by light affectors on Airship. + /// Gets or sets a value indicating whether the role is affected by light affectors on Airship. /// - public bool AffectedByLightOnAirship; + public bool AffectedByLightOnAirship { get; set; } /// - /// Gets a value indicating whether the role can be killed by vanilla murder system. + /// Gets or sets a value indicating whether the role can be killed by vanilla murder system. /// - public bool CanGetKilled; + public bool CanGetKilled { get; set; } /// - /// Gets a value indicating whether the role should use the vanilla kill button. + /// Gets or sets a value indicating whether the role should use the vanilla kill button. /// - public bool UseVanillaKillButton; + public bool UseVanillaKillButton { get; set; } /// - /// Gets a value indicating whether the role can use vents. + /// Gets or sets a value indicating whether the role can use vents. /// - public bool CanUseVent; + public bool CanUseVent { get; set; } /// - /// Gets a value indicating whether the role can use the sabotage button. + /// Gets or sets a value indicating whether the role can use the sabotage button. /// - public bool CanUseSabotage; + public bool CanUseSabotage { get; set; } /// - /// Gets a value indicating whether the role's tasks count towards task progress. + /// Gets or sets a value indicating whether the role's tasks count towards task progress. /// - public bool TasksCountForProgress; + public bool TasksCountForProgress { get; set; } /// - /// Gets a value indicating whether the role should show up in the Role Options menu. + /// Gets or sets a value indicating whether the role should show up in the Role Options menu. /// - public bool HideSettings; + public bool HideSettings { get; set; } /// - /// Gets a value indicating whether the role should show up in the Freeplay Role Selection menu. + /// Gets or sets a value indicating whether the role should show up in the Freeplay Role Selection menu. /// - public bool ShowInFreeplay; + public bool ShowInFreeplay { get; set; } /// - /// Gets the outline color for the KillButton if is true. + /// Gets or sets the outline color for the KillButton if is true. /// - public Color KillButtonOutlineColor; + public Color KillButtonOutlineColor { get; set; } /// - /// Gets the role hint style. See enum for all options. + /// Gets or sets the role hint style. See enum for all options. /// - public RoleHintType RoleHintType; + public RoleHintType RoleHintType { get; set; } = RoleHintType.RoleTab; /// - /// Gets the Ghost role that is applied when the player is killed. + /// Gets or sets the Ghost role that is applied when the player is killed. /// - public RoleTypes GhostRole; + public RoleTypes GhostRole { get; set; } } From 8382b9adf153e7d36a51db60a0361d86378e60e2 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:08:12 -0500 Subject: [PATCH 32/65] clearer naming --- MiraAPI.Example/Roles/NeutralKillerRole.cs | 5 +++++ MiraAPI/Roles/ICustomRole.cs | 4 ++-- ...{RoleIntroConfiguration.cs => TeamIntroConfiguration.cs} | 6 +++--- 3 files changed, 10 insertions(+), 5 deletions(-) rename MiraAPI/Roles/{RoleIntroConfiguration.cs => TeamIntroConfiguration.cs} (78%) diff --git a/MiraAPI.Example/Roles/NeutralKillerRole.cs b/MiraAPI.Example/Roles/NeutralKillerRole.cs index eb937e1..e9b4344 100644 --- a/MiraAPI.Example/Roles/NeutralKillerRole.cs +++ b/MiraAPI.Example/Roles/NeutralKillerRole.cs @@ -20,6 +20,11 @@ public class NeutralKillerRole : ImpostorRole, ICustomRole public RoleOptionsGroup RoleOptionsGroup { get; } = new("Outcast", Color.gray); + public TeamIntroConfiguration? IntroConfiguration { get; } = new( + Color.gray, + "NEUTRAL", + "You are an Outcast. You do not have a team."); + public override void SpawnTaskHeader(PlayerControl playerControl) { // remove existing task header. diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index 0c77019..d02453c 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -58,9 +58,9 @@ public interface ICustomRole /// /// Gets the role's IntroCutscene configuration. /// - public RoleIntroConfiguration? IntroConfiguration => Team switch + public TeamIntroConfiguration? IntroConfiguration => Team switch { - ModdedRoleTeams.Custom => RoleIntroConfiguration.Neutral, + ModdedRoleTeams.Custom => TeamIntroConfiguration.Neutral, _ => null, }; diff --git a/MiraAPI/Roles/RoleIntroConfiguration.cs b/MiraAPI/Roles/TeamIntroConfiguration.cs similarity index 78% rename from MiraAPI/Roles/RoleIntroConfiguration.cs rename to MiraAPI/Roles/TeamIntroConfiguration.cs index 53ca749..9177722 100644 --- a/MiraAPI/Roles/RoleIntroConfiguration.cs +++ b/MiraAPI/Roles/TeamIntroConfiguration.cs @@ -3,17 +3,17 @@ namespace MiraAPI.Roles; /// -/// Used to configure the intro cutscene. +/// Used to configure the TEAM part of the intro cutscene. /// /// The color to show during the "Team" part of the intro cutscene. /// The title text to show during the "Team" part of the intro cutscene. /// The description text to show during the "Team" part of the intro cutscene. -public record struct RoleIntroConfiguration(Color IntroTeamColor, string IntroTeamTitle, string IntroTeamDescription) +public record struct TeamIntroConfiguration(Color IntroTeamColor, string IntroTeamTitle, string IntroTeamDescription) { /// /// Gets the default configuration for the "Neutral" team. /// - public static RoleIntroConfiguration Neutral { get; } = new( + public static TeamIntroConfiguration Neutral { get; } = new( Color.gray, "NEUTRAL", "You are Neutral. You do not have a team."); From fb99f0254930795249095f2d34250fdcf8068149 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:10:26 -0500 Subject: [PATCH 33/65] i forgor --- MiraAPI/Roles/ICustomRole.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index d02453c..9afc407 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -56,7 +56,7 @@ public interface ICustomRole }; /// - /// Gets the role's IntroCutscene configuration. + /// Gets the role's TeamIntroCutscene configuration. /// public TeamIntroConfiguration? IntroConfiguration => Team switch { From ae79cd87d90b0e207f51a8c174190a551a2465cc Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:37:02 -0500 Subject: [PATCH 34/65] :shush: --- MiraAPI/Patches/Colors/ScrollingColorsPatch.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/MiraAPI/Patches/Colors/ScrollingColorsPatch.cs b/MiraAPI/Patches/Colors/ScrollingColorsPatch.cs index e89de23..f5ef949 100644 --- a/MiraAPI/Patches/Colors/ScrollingColorsPatch.cs +++ b/MiraAPI/Patches/Colors/ScrollingColorsPatch.cs @@ -4,17 +4,19 @@ using UnityEngine; namespace MiraAPI.Patches.Colors; -[HarmonyPatch(typeof(PlayerTab))] +/// +/// This patch adds scrolling to the colors tab in the player customization menu. +/// +[HarmonyPatch(typeof(PlayerTab), nameof(PlayerTab.OnEnable))] public static class ScrollingColorsPatch { // Collider private static BoxCollider2D? _collider; /// - /// Add scrolling to the colors tab + /// Add scrolling to the colors tab. /// - [HarmonyPostfix, HarmonyPatch("OnEnable")] - public static void AddScrollingToColorsTabPatch(PlayerTab __instance) + public static void Postfix(PlayerTab __instance) { if (!PlayerCustomizationMenu.Instance) { From 14a36690311240e88e10a684a60956a937b2f17f Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:52:57 -0500 Subject: [PATCH 35/65] fix rolebehaviour stubs cuz im an idiot --- MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs b/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs index 7f132fd..0248aaa 100644 --- a/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs +++ b/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs @@ -11,10 +11,11 @@ public static class RoleBehaviourStubs /// Stub method for RoleBehaviour.Initialize. /// /// The RoleBehaviour object. + /// The PlayerControl to initialize. [HarmonyReversePatch] [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.Initialize))] [MethodImpl(MethodImplOptions.NoInlining)] - public static void Initialize(RoleBehaviour instance) + public static void Initialize(RoleBehaviour instance, PlayerControl player) { // nothing needed } @@ -38,7 +39,7 @@ public static class RoleBehaviourStubs /// The RoleBehaviour object. /// The StringBuilder to append the task hint to. [HarmonyReversePatch] - [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.AppendTaskHint))] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.AppendTaskHint), typeof(StringBuilder))] [MethodImpl(MethodImplOptions.NoInlining)] public static void AppendTaskHint(RoleBehaviour instance, StringBuilder taskStringBuilder) { From 0b1cf998f848b73f42ce12c646c3c03dfbac0701 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 17 Feb 2025 21:59:58 -0500 Subject: [PATCH 36/65] fix coloring issues and hidden roles in rolesettingsmenu --- .../Patches/Options/RoleSettingMenuPatches.cs | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 9212e86..4e3f803 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -80,13 +80,15 @@ public static class RoleSettingMenuPatches continue; } - RoleGroupHidden.TryAdd(grouping.Key, false); + var group = grouping.Key; - var name = grouping.Key.Name switch + RoleGroupHidden.TryAdd(group, false); + + var name = group.Name switch { "Crewmate" => StringNames.CrewmateRolesHeader, "Impostor" => StringNames.ImpostorRolesHeader, - _ => CustomStringName.CreateAndRegister(grouping.Key.Name), + _ => CustomStringName.CreateAndRegister(group.Name), }; var categoryHeaderEditRole = Object.Instantiate( @@ -95,15 +97,26 @@ public static class RoleSettingMenuPatches Quaternion.identity, __instance.RoleChancesSettings.transform); categoryHeaderEditRole.SetHeader(name, 20); + if (name is not (StringNames.CrewmateRolesHeader or StringNames.ImpostorRolesHeader)) + { + 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; + } + categoryHeaderEditRole.transform.localPosition = new Vector3(4.986f, num, -2f); categoryHeaderEditRole.transform.Find("LabelSprite").transform.localScale = new Vector3(1.3f, 1, 0.5529f); - categoryHeaderEditRole.transform.Find("QuotaHeader").gameObject.SetActive(!RoleGroupHidden[grouping.Key]); + categoryHeaderEditRole.transform.Find("QuotaHeader").gameObject.SetActive(!RoleGroupHidden[group]); CategoryHeaderEditRoles.Add(categoryHeaderEditRole); num -= 0.522f; - var label = RoleGroupHidden[grouping.Key] + var label = RoleGroupHidden[group] ? "(Click to open)" : "(Click to close)"; var newText = Object.Instantiate(categoryHeaderEditRole.Title, categoryHeaderEditRole.transform); @@ -111,7 +124,7 @@ public static class RoleSettingMenuPatches newText.transform.localPosition = new Vector3(-3.3425f, -0.1706f, -1); newText.gameObject.GetComponent().Destroy(); - if (!RoleGroupHidden[grouping.Key]) + if (!RoleGroupHidden[group]) { foreach (var role in grouping) { @@ -140,7 +153,10 @@ public static class RoleSettingMenuPatches headerBtn.OnClick.AddListener( (UnityAction)(() => { - RoleGroupHidden[grouping.Key] = !RoleGroupHidden[grouping.Key]; + if (RoleGroupHidden.TryGetValue(group, out var value)) + { + RoleGroupHidden[group] = !value; + } foreach (var header in CategoryHeaderEditRoles) { header.gameObject.Destroy(); @@ -155,7 +171,7 @@ public static class RoleSettingMenuPatches })); headerBtn.SetButtonEnableState(true); - if (!RoleGroupHidden[grouping.Key]) + if (!RoleGroupHidden[group]) { num -= 0.4f; } @@ -327,6 +343,11 @@ public static class RoleSettingMenuPatches return null; } + if (customRole.Configuration.HideSettings) + { + return null; + } + var roleOptionSetting = Object.Instantiate( __instance.roleOptionSettingOrigin, Vector3.zero, From 1e475006864508461615ba618ce63fa45426d5a6 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:03:50 -0500 Subject: [PATCH 37/65] fix scrolling issues --- .../Patches/Options/RoleSettingMenuPatches.cs | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 4e3f803..518268c 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -26,6 +26,8 @@ public static class RoleSettingMenuPatches private static List CategoryHeaderEditRoles { get; } = []; private static List RoleOptionSettings { get; } = []; + private static float ScrollerNum { get; set; } = 0.522f; + [HarmonyPrefix] [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))] public static bool PatchStart(RolesSettingsMenu __instance) @@ -48,7 +50,7 @@ public static class RoleSettingMenuPatches return true; } - var num = 0.522f; + ScrollerNum = 0.522f; __instance.AllButton.transform.parent.gameObject.SetActive(false); __instance.AllButton.gameObject.SetActive(false); @@ -108,13 +110,13 @@ public static class RoleSettingMenuPatches categoryHeaderEditRole.countLabel.color = darkColor; } - categoryHeaderEditRole.transform.localPosition = new Vector3(4.986f, num, -2f); + 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]); CategoryHeaderEditRoles.Add(categoryHeaderEditRole); - num -= 0.522f; + ScrollerNum -= 0.522f; var label = RoleGroupHidden[group] ? "(Click to open)" @@ -133,7 +135,7 @@ public static class RoleSettingMenuPatches continue; } - var option = CreateQuotaOption(__instance, roleBehaviour, ref num, num3); + var option = CreateQuotaOption(__instance, roleBehaviour, num3); if (option is not null) { RoleOptionSettings.Add(option); @@ -173,13 +175,18 @@ public static class RoleSettingMenuPatches if (!RoleGroupHidden[group]) { - num -= 0.4f; + ScrollerNum -= 0.4f; } } - __instance.scrollBar.CalculateAndSetYBounds(__instance.roleChances.Count + 5, 1f, 6f, 0.43f); + __instance.scrollBar.SetScrollBounds(); return false; } + private static void SetScrollBounds(this Scroller scroller) + { + scroller.CalculateAndSetYBounds(1 + 1.5f * CategoryHeaderEditRoles.Count + RoleOptionSettings.Count, 1f, 6f, 0.43f); + } + [HarmonyPrefix] [HarmonyPatch(nameof(RolesSettingsMenu.Update))] public static bool UpdatePatch() @@ -191,7 +198,12 @@ public static class RoleSettingMenuPatches [HarmonyPatch(nameof(RolesSettingsMenu.OpenChancesTab))] public static void OpenChancesTabPostfix(RolesSettingsMenu __instance) { - __instance.scrollBar.CalculateAndSetYBounds(__instance.roleChances.Count + 5, 1f, 6f, 0.43f); + if (GameSettingMenuPatches.SelectedModIdx == 0) + { + return; + } + + __instance.scrollBar.SetScrollBounds(); __instance.scrollBar.ScrollToTop(); } @@ -335,7 +347,7 @@ public static class RoleSettingMenuPatches __instance.RefreshChildren(); } - private static RoleOptionSetting? CreateQuotaOption(RolesSettingsMenu __instance, RoleBehaviour role, ref float yPos, int index) + private static RoleOptionSetting? CreateQuotaOption(RolesSettingsMenu __instance, RoleBehaviour role, int index) { if (role is not ICustomRole customRole) { @@ -353,7 +365,7 @@ public static class RoleSettingMenuPatches Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform); - roleOptionSetting.transform.localPosition = new Vector3(-0.15f, yPos, -2f); + roleOptionSetting.transform.localPosition = new Vector3(-0.15f, ScrollerNum, -2f); roleOptionSetting.SetRole(GameOptionsManager.Instance.CurrentGameOptions.RoleOptions, role, 20); roleOptionSetting.labelSprite.color = customRole.RoleColor; @@ -400,7 +412,7 @@ public static class RoleSettingMenuPatches if (index < GameSettingMenuPatches.SelectedMod?.CustomRoles.Count - 1) { - yPos += -0.43f; + ScrollerNum += -0.43f; } return roleOptionSetting; 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 38/65] 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 39/65] 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 40/65] 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 41/65] 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 42/65] 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 43/65] . --- 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 44/65] 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 45/65] 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 46/65] 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 47/65] 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 48/65] 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 49/65] 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 50/65] 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 51/65] 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 52/65] 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 53/65] 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 54/65] 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 55/65] . --- 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 56/65] 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 57/65] 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 58/65] 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 59/65] 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 60/65] 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 61/65] 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 62/65] 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 63/65] 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 64/65] 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 65/65] 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;