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;