Merge pull request #26 from All-Of-Us-Mods/freeplay-modifiers

freeplay modifiers
This commit is contained in:
XtraCube authored and GitHub committed 2025-02-23 19:05:01 -05:00
commit 047312a197
13 files changed
+220 -87

No files matched your search

+1 -2
View File
@@ -1,5 +1,4 @@
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Modifiers;
@@ -1,6 +1,5 @@
using Il2CppSystem;
using MiraAPI.Example.Roles;
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
using UnityEngine;
@@ -1,5 +1,4 @@
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Modifiers;
@@ -1,5 +1,4 @@
using System;
using System.Globalization;
using System.Linq;
using System.Text;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
+5
View File
@@ -30,6 +30,11 @@ public abstract class BaseModifier
/// </summary>
public virtual bool HideOnUi => false;
/// <summary>
/// Gets a value indicating whether the modifier is shown in the freeplay menu.
/// </summary>
public virtual bool ShowInFreeplay => false;
/// <summary>
/// Gets the HUD information for this modifier. Defaults to the modifier name. Does nothing if <see cref="HideOnUi"/> is true.
/// </summary>
+6 -1
View File
@@ -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))
{
+3
View File
@@ -5,6 +5,9 @@
/// </summary>
public abstract class GameModifier : BaseModifier
{
/// <inheritdoc />
public override bool ShowInFreeplay => true;
/// <summary>
/// Gets the chance of the modifier being assigned to a player.
/// </summary>
@@ -0,0 +1,36 @@
using HarmonyLib;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Patches.Freeplay;
[HarmonyPatch(typeof(TaskAddButton))]
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.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;
}
__instance.RolloverHandler.OutColor = __instance.FileImage.color;
}
}
@@ -1,32 +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;
using MiraAPI.Utilities.Assets;
using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Roles;
namespace MiraAPI.Patches.Freeplay;
/// <summary>
/// Patch TaskAdder for organizing Roles and adding scroller.
/// </summary>
[HarmonyPatch]
public static class TaskAdderPatch
[HarmonyPatch(typeof(TaskAdderGame))]
internal static class TaskAdderPatches
{
private static readonly Dictionary<string, string> ModsFolders = [];
private static Scroller? _scroller;
/// <summary>
/// Adds custom roles to the TaskAdder.
/// </summary>
/// <param name="__instance">TaskAdder instance.</param>
[HarmonyPostfix]
[HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.Begin))]
[HarmonyPatch(nameof(TaskAdderGame.Begin))]
public static void AddRolesFolder(TaskAdderGame __instance)
{
GameObject inner = new("Inner");
@@ -67,45 +62,38 @@ 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();
}
/// <summary>
/// Sets the color of the role button based on the role.
/// </summary>
/// <param name="__instance">TaskAddButton instance.</param>
[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,
@@ -130,8 +118,8 @@ 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)
[HarmonyPatch(nameof(TaskAdderGame.ShowFolder))]
public static bool ShowPatch(TaskAdderGame __instance, TaskFolder taskFolder)
{
var stringBuilder = new Il2CppSystem.Text.StringBuilder(64);
__instance.Hierarchy.Add(taskFolder);
@@ -277,46 +265,31 @@ public static class TaskAdderPatch
}
}
if (ModsFolders.TryGetValue(taskFolder.FolderName, out var guid))
if (taskFolder)
{
var plugin = MiraPluginManager.GetPluginByGuid(guid);
for (var m = 0; m < plugin.CustomRoles.Count; m++)
var split = taskFolder.name.Split(':');
if (split.Length == 2)
{
var roleBehaviour = plugin.CustomRoles.ElementAt(m).Value;
if (roleBehaviour.Role == RoleTypes.ImpostorGhost || roleBehaviour.Role == RoleTypes.CrewmateGhost ||
(roleBehaviour is ICustomRole customRole && !customRole.Configuration.ShowInFreeplay))
{
continue;
}
var pluginId = split[0];
var folderName = split[1];
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)
if (MiraPluginManager.GetPluginByGuid(pluginId) is { } plugin)
{
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;
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);
}
}
}
}
foreach (var chip in __instance.ActiveItems)
{
chip.GetComponentInChildren<TextMeshPro>().EnableMasking();
chip.GetComponentInChildren<TextMeshPro>().EnableStencilMasking();
chip.GetComponent<SpriteRenderer>().maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
}
@@ -336,4 +309,97 @@ 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(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);
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";
taskAddButton2.Text.EnableMasking();
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;
}
}
}
}
+12
View File
@@ -5,6 +5,7 @@ using MiraAPI.GameOptions;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MiraAPI.Hud;
using MiraAPI.Modifiers;
namespace MiraAPI.PluginLoading;
@@ -21,6 +22,15 @@ public class MiraPluginInfo
PluginId = info.Metadata.GUID;
}
/// <summary>
/// Get a read only collection of this plugin's modifiers. This is probably not safe because they are uninitialized.
/// </summary>
/// <returns>Readonly collection of modifiers.</returns>
public IReadOnlyCollection<BaseModifier> GetModifiers()
{
return Modifiers.AsReadOnly();
}
/// <summary>
/// Get a read only collection of this plugin's Option Groups.
/// </summary>
@@ -61,6 +71,8 @@ public class MiraPluginInfo
internal List<IModdedOption> Options { get; } = [];
internal List<BaseModifier> Modifiers { get; } = [];
internal Dictionary<ushort, RoleBehaviour> CustomRoles { get; } = [];
internal Dictionary<int, CustomGameMode> GameModes { get; } = [];
+5 -5
View File
@@ -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
/// </summary>
/// <param name="pluginId">The plugin GUID.</param>
/// <returns>A MiraPluginInfo.</returns>
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)
{
+1 -2
View File
@@ -1,5 +1,4 @@
using System;
using BepInEx.Configuration;
using BepInEx.Configuration;
using MiraAPI.Modifiers;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities;
+11
View File
@@ -8,6 +8,7 @@ using Reactor.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
namespace MiraAPI.Utilities;
@@ -39,6 +40,16 @@ public static class Extensions
BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value)));
}
/// <summary>
/// Enables Among Us style masking on a TMP text object.
/// </summary>
/// <param name="text">The TMP text.</param>
public static void EnableStencilMasking(this TMP_Text text)
{
text.fontMaterial.SetFloat(ShaderID.Get("_Stencil"), 1);
text.fontMaterial.SetFloat(ShaderID.Get("_StencilComp"), 4);
}
/// <summary>
/// Checks if a type is static.
/// </summary>