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;