WIP code documentation and various fixes

This commit is contained in:
XtraCube committed 2024-08-30 23:52:43 -04:00
1 parent a52f46972e
commit 409113e37a
26 files changed
+737 -467

No files matched your search

@@ -31,14 +31,13 @@ public class FreezeButton : CustomActionButton<PlayerControl>
Target?.cosmetics.SetOutline(active, new Il2CppSystem.Nullable<Color>(Palette.Blue)); Target?.cosmetics.SetOutline(active, new Il2CppSystem.Nullable<Color>(Palette.Blue));
} }
public override bool IsTargetValid(PlayerControl target) public override bool IsTargetValid(PlayerControl? target)
{ {
return true; return true;
} }
public override bool Enabled(RoleBehaviour role) public override bool Enabled(RoleBehaviour? role)
{ {
return role is FreezerRole; return role is FreezerRole;
} }
}
}
+11 -8
View File
@@ -4,31 +4,34 @@ using UnityEngine;
namespace MiraAPI.GameOptions; namespace MiraAPI.GameOptions;
/// <summary>
/// Base class for option groups. An option group is a collection of options that are displayed together in the options menu.
/// </summary>
public abstract class AbstractOptionGroup public abstract class AbstractOptionGroup
{ {
public List<IModdedOption> Options { get; } = []; internal List<IModdedOption> Options { get; } = [];
/// <summary> /// <summary>
/// The name of the group. Visible in options menu. /// Gets the name of the group. Visible in options menu.
/// </summary> /// </summary>
public abstract string GroupName { get; } public abstract string GroupName { get; }
/// <summary> /// <summary>
/// A function that returns whether the group should be visible or not. /// Gets the function that determines whether the group should be visible or not.
/// </summary> /// </summary>
public virtual Func<bool> GroupVisible => () => true; public virtual Func<bool> GroupVisible => () => true;
/// <summary> /// <summary>
/// The group color. This is used to color the group in the options menu. /// Gets the group color. This is used to color the group in the options menu.
/// </summary> /// </summary>
public virtual Color GroupColor => Color.clear; public virtual Color GroupColor => Color.clear;
/// <summary> /// <summary>
/// The role the group is associated with. This is used for the advanced role options menu. /// Gets the role the group is associated with. This is used for the advanced role options menu.
/// </summary> /// </summary>
public virtual Type AdvancedRole => null; public virtual Type? AdvancedRole => null;
internal bool AllOptionsHidden { get; set; } internal bool AllOptionsHidden { get; set; }
internal CategoryHeaderMasked Header { get; set; } internal CategoryHeaderMasked? Header { get; set; }
} }
@@ -11,10 +11,7 @@ namespace MiraAPI.GameOptions.Attributes;
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public abstract class ModdedOptionAttribute(string title, Type? roleType = null) : Attribute public abstract class ModdedOptionAttribute(string title, Type? roleType = null) : Attribute
{ {
/// <summary> internal IModdedOption? HolderOption { get; set; }
/// Gets the IModdedOption object that this attribute is attached to.
/// </summary>
public IModdedOption? HolderOption { get; internal set; }
/// <summary> /// <summary>
/// Gets the title of the option. /// Gets the title of the option.
+17 -18
View File
@@ -1,25 +1,24 @@
using MiraAPI.PluginLoading; using System;
using System;
using BepInEx.Configuration; using BepInEx.Configuration;
using MiraAPI.Networking; using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using UnityEngine; using UnityEngine;
namespace MiraAPI.GameOptions; namespace MiraAPI.GameOptions;
public interface IModdedOption internal interface IModdedOption
{ {
public uint Id { get; } uint Id { get; }
public BaseGameSetting Data { get; } string Title { get; }
public IMiraPlugin? ParentMod { get; set; } StringNames StringName { get; }
public Type AdvancedRole { get; set; } IMiraPlugin? ParentMod { get; set; }
public OptionBehaviour OptionBehaviour { get; } BaseGameSetting? Data { get; }
public string Title { get; } Type? AdvancedRole { get; set; }
public StringNames StringName { get; } OptionBehaviour? OptionBehaviour { get; }
public Func<bool> Visible { get; set; } Func<bool> Visible { get; set; }
public ConfigDefinition? ConfigDefinition { get; set; } ConfigDefinition? ConfigDefinition { get; set; }
public void ValueChanged(OptionBehaviour optionBehaviour); OptionBehaviour? CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container);
public OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container); float GetFloatData();
public abstract float GetFloatData(); NetData GetNetData();
public NetData GetNetData(); void HandleNetData(byte[] data);
public void HandleNetData(byte[] data); }
}
+46 -30
View File
@@ -1,18 +1,21 @@
using HarmonyLib; using System;
using MiraAPI.GameOptions.Attributes;
using Reactor.Utilities;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using BepInEx.Configuration; using BepInEx.Configuration;
using HarmonyLib;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Networking; using MiraAPI.Networking;
using MiraAPI.PluginLoading; using MiraAPI.PluginLoading;
using MiraAPI.Utilities; using MiraAPI.Utilities;
using Reactor.Networking.Rpc; using Reactor.Networking.Rpc;
using Reactor.Utilities;
namespace MiraAPI.GameOptions; namespace MiraAPI.GameOptions;
/// <summary>
/// Handles modded options.
/// </summary>
public static class ModdedOptionsManager public static class ModdedOptionsManager
{ {
private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = new(); private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = new();
@@ -20,10 +23,10 @@ public static class ModdedOptionsManager
internal static readonly Dictionary<uint, IModdedOption> ModdedOptions = new(); internal static readonly Dictionary<uint, IModdedOption> ModdedOptions = new();
internal static readonly List<AbstractOptionGroup> Groups = []; internal static readonly List<AbstractOptionGroup> Groups = [];
internal static uint NextId => _nextId++; internal static uint NextId => _nextId++;
private static uint _nextId = 1; private static uint _nextId = 1;
internal static bool RegisterGroup(Type type, MiraPluginInfo pluginInfo) internal static bool RegisterGroup(Type type, MiraPluginInfo pluginInfo)
{ {
if (Activator.CreateInstance(type) is not AbstractOptionGroup group) if (Activator.CreateInstance(type) is not AbstractOptionGroup group)
@@ -41,10 +44,10 @@ public static class ModdedOptionsManager
Groups.Add(group); Groups.Add(group);
TypeToGroup.Add(type, group); TypeToGroup.Add(type, group);
pluginInfo.OptionGroups.Add(group); pluginInfo.OptionGroups.Add(group);
return true; return true;
} }
internal static void RegisterPropertyOption(Type type, PropertyInfo property, MiraPluginInfo pluginInfo) internal static void RegisterPropertyOption(Type type, PropertyInfo property, MiraPluginInfo pluginInfo)
{ {
if (!TypeToGroup.TryGetValue(type, out var group)) if (!TypeToGroup.TryGetValue(type, out var group))
@@ -62,7 +65,11 @@ public static class ModdedOptionsManager
RegisterOption(option, group, property.Name, pluginInfo); RegisterOption(option, group, property.Name, pluginInfo);
} }
internal static void RegisterAttributeOption(Type type, ModdedOptionAttribute attribute, PropertyInfo property, MiraPluginInfo pluginInfo) internal static void RegisterAttributeOption(
Type type,
ModdedOptionAttribute attribute,
PropertyInfo property,
MiraPluginInfo pluginInfo)
{ {
if (OptionAttributes.ContainsKey(property)) if (OptionAttributes.ContainsKey(property))
{ {
@@ -98,7 +105,11 @@ public static class ModdedOptionsManager
RegisterOption(option, group, property.Name, pluginInfo); RegisterOption(option, group, property.Name, pluginInfo);
} }
private static void RegisterOption(IModdedOption option, AbstractOptionGroup group, string propertyName, MiraPluginInfo pluginInfo) private static void RegisterOption(
IModdedOption option,
AbstractOptionGroup group,
string propertyName,
MiraPluginInfo pluginInfo)
{ {
var groupName = group.GetType().FullName; var groupName = group.GetType().FullName;
@@ -128,51 +139,56 @@ public static class ModdedOptionsManager
// necessary to disable then re-enable this setting // necessary to disable then re-enable this setting
// we dont know how other plugins handle their configs // we dont know how other plugins handle their configs
// this way, all the options are saved at once, instead of one by one // this way, all the options are saved at once, instead of one by one
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) var oldConfigSetting = new Dictionary<MiraPluginInfo, bool>();
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
{ {
oldConfigSetting.Add(plugin, plugin.PluginConfig.SaveOnConfigSet);
plugin.PluginConfig.SaveOnConfigSet = false; plugin.PluginConfig.SaveOnConfigSet = false;
} }
foreach (var netData in data) foreach (var netData in data)
{ {
if (!ModdedOptions.TryGetValue(netData.Id, out var option)) if (!ModdedOptions.TryGetValue(netData.Id, out var option))
{ {
continue; continue;
} }
option.HandleNetData(netData.Data); option.HandleNetData(netData.Data);
} }
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
{ {
plugin.PluginConfig.Save(); plugin.PluginConfig.Save();
plugin.PluginConfig.SaveOnConfigSet = true; plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin];
} }
if (LobbyInfoPane.Instance) if (LobbyInfoPane.Instance)
{ {
LobbyInfoPane.Instance.RefreshPane(); LobbyInfoPane.Instance.RefreshPane();
} }
} }
/// <summary>
/// Patches the setter of a property to update the value of the option.
/// </summary>
/// <param name="__originalMethod">The original setter method.</param>
/// <param name="value">The new object value.</param>
public static void PropertySetterPatch(MethodBase __originalMethod, object value) public static void PropertySetterPatch(MethodBase __originalMethod, object value)
{ {
var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod().Equals(__originalMethod)).Value; var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod() == __originalMethod).Value;
attribute.SetValue(value);
attribute?.SetValue(value);
} }
/// <summary>
/// Patches the getter of a property to return the value of the option.
/// </summary>
/// <param name="__originalMethod">The original getter method.</param>
/// <param name="__result">The result of the property getter.</param>
/// <returns>False so the original getter gets skipped.</returns>
public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result) public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result)
{ {
var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod().Equals(__originalMethod)).Value; var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod() == __originalMethod).Value;
if (attribute == null)
{
return true;
}
__result = attribute.GetValue(); __result = attribute.GetValue();
return false; return false;
} }
} }
@@ -58,17 +58,12 @@ public class ModdedEnumOption : ModdedOption<int>
SetValue(BitConverter.ToInt32(data)); SetValue(BitConverter.ToInt32(data));
} }
public override string GetHudStringText()
{
return Title + ": " + Values[Value];
}
public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour) public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{ {
return optionBehaviour.GetInt(); return optionBehaviour.GetInt();
} }
public override void OnValueChanged(int newValue) protected override void OnValueChanged(int newValue)
{ {
DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false); DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false);
if (!OptionBehaviour) if (!OptionBehaviour)
@@ -80,17 +80,12 @@ public class ModdedNumberOption : ModdedOption<float>
SetValue(BitConverter.ToSingle(data)); SetValue(BitConverter.ToSingle(data));
} }
public override string GetHudStringText()
{
return Title + ": " + Value + Helpers.GetSuffix(SuffixType);
}
public override float GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour) public override float GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{ {
return Mathf.Clamp(optionBehaviour.GetFloat(), Min, Max); return Mathf.Clamp(optionBehaviour.GetFloat(), Min, Max);
} }
public override void OnValueChanged(float newValue) protected override void OnValueChanged(float newValue)
{ {
Value = Mathf.Clamp(newValue, Min, Max); Value = Mathf.Clamp(newValue, Min, Max);
DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(Value), false); DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(Value), false);
+76 -24
View File
@@ -9,6 +9,10 @@ using UnityEngine;
namespace MiraAPI.GameOptions.OptionTypes; namespace MiraAPI.GameOptions.OptionTypes;
/// <summary>
/// Represents a modded option.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
public abstract class ModdedOption<T> : IModdedOption public abstract class ModdedOption<T> : IModdedOption
{ {
private IMiraPlugin? _parentMod; private IMiraPlugin? _parentMod;
@@ -18,10 +22,20 @@ public abstract class ModdedOption<T> : IModdedOption
/// </summary> /// </summary>
public uint Id { get; } public uint Id { get; }
/// <summary>
/// Gets the title of the option.
/// </summary>
public string Title { get; }
/// <summary>
/// Gets the StringName object of the option.
/// </summary>
public StringNames StringName { get; }
/// <summary> /// <summary>
/// Gets the BaseGameSetting data of the option. /// Gets the BaseGameSetting data of the option.
/// </summary> /// </summary>
public BaseGameSetting Data { get; protected init; } public BaseGameSetting? Data { get; protected init; }
/// <summary> /// <summary>
/// Gets or sets the parent mod of the option. /// Gets or sets the parent mod of the option.
@@ -51,26 +65,22 @@ public abstract class ModdedOption<T> : IModdedOption
/// <summary> /// <summary>
/// Gets or sets the event that is invoked when the value of the option changes. /// Gets or sets the event that is invoked when the value of the option changes.
/// </summary> /// </summary>
public Action<T> ChangedEvent { get; set; } public Action<T>? ChangedEvent { get; set; }
/// <summary>
/// Gets the title of the option.
/// </summary>
public string Title { get; }
/// <summary>
/// Gets the StringName object of the option.
/// </summary>
public StringNames StringName { get; }
/// <summary> /// <summary>
/// Gets or sets the visibility of the option. /// Gets or sets the visibility of the option.
/// </summary> /// </summary>
public Func<bool> Visible { get; set; } public Func<bool> Visible { get; set; }
public Type AdvancedRole { get; set; } /// <summary>
/// Gets or sets the advanced role of the option.
/// </summary>
public Type? AdvancedRole { get; set; }
public OptionBehaviour OptionBehaviour { get; set; } /// <summary>
/// Gets or sets the option behaviour of the option.
/// </summary>
public OptionBehaviour? OptionBehaviour { get; protected set; }
/// <summary> /// <summary>
/// Gets or sets the config definition of the option. /// Gets or sets the config definition of the option.
@@ -87,6 +97,12 @@ public abstract class ModdedOption<T> : IModdedOption
private ConfigDefinition? _configDefinition; private ConfigDefinition? _configDefinition;
/// <summary>
/// Initializes a new instance of the <see cref="ModdedOption{T}"/> class.
/// </summary>
/// <param name="title">The option title.</param>
/// <param name="defaultValue">The default value.</param>
/// <param name="roleType">The Role Type or null if it doesn't belong to a role.</param>
protected ModdedOption(string title, T defaultValue, Type? roleType) protected ModdedOption(string title, T defaultValue, Type? roleType)
{ {
Id = ModdedOptionsManager.NextId; Id = ModdedOptionsManager.NextId;
@@ -102,18 +118,23 @@ public abstract class ModdedOption<T> : IModdedOption
} }
} }
public void ValueChanged(OptionBehaviour optionBehaviour) internal void ValueChanged(OptionBehaviour optionBehaviour)
{ {
SetValue(GetValueFromOptionBehaviour(optionBehaviour)); SetValue(GetValueFromOptionBehaviour(optionBehaviour));
} }
/// <summary>
/// Sets the value of the option.
/// </summary>
/// <param name="newValue">The new value.</param>
public void SetValue(T newValue) public void SetValue(T newValue)
{ {
var oldVal = Value; var oldVal = Value;
Value = newValue; Value = newValue;
if (!Value.Equals(oldVal) && ChangedEvent != null)
if (Value?.Equals(oldVal) == false)
{ {
ChangedEvent.Invoke(Value); ChangedEvent?.Invoke(Value);
} }
if (AmongUsClient.Instance.AmHost) if (AmongUsClient.Instance.AmHost)
@@ -122,24 +143,55 @@ public abstract class ModdedOption<T> : IModdedOption
{ {
entry.Value = Value; entry.Value = Value;
} }
Rpc<SyncOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()], true); Rpc<SyncOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()], true);
} }
OnValueChanged(newValue); OnValueChanged(newValue);
} }
/// <summary>
/// Gets the float data of the option.
/// </summary>
/// <returns>A float object representing the option's value.</returns>
public abstract float GetFloatData(); public abstract float GetFloatData();
/// <summary>
/// Gets the net data of the option.
/// </summary>
/// <returns>A NetData object representing this option's data.</returns>
public abstract NetData GetNetData(); public abstract NetData GetNetData();
/// <summary>
/// Handles incoming net data.
/// </summary>
/// <param name="data">The NetData's byte array.</param>
public abstract void HandleNetData(byte[] data); public abstract void HandleNetData(byte[] data);
public abstract void OnValueChanged(T newValue); /// <summary>
/// Handles the value changed event.
/// </summary>
/// <param name="newValue">The new value.</param>
protected abstract void OnValueChanged(T newValue);
/// <summary>
/// Gets the value from the option behaviour.
/// </summary>
/// <param name="optionBehaviour">The OptionBehaviour.</param>
/// <returns>The value.</returns>
public abstract T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour); public abstract T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour);
public abstract OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container); /// <summary>
/// Creates the option behaviour.
public abstract string GetHudStringText(); /// </summary>
} /// <param name="toggleOpt">The ToggleOption prefab.</param>
/// <param name="numberOpt">The NumberOption prefab.</param>
/// <param name="stringOpt">The StringOption prefab.</param>
/// <param name="container">The options container.</param>
/// <returns>A new OptionBehaviour for this modded option.</returns>
public abstract OptionBehaviour CreateOption(
ToggleOption toggleOpt,
NumberOption numberOpt,
StringOption stringOpt,
Transform container);
}
@@ -55,17 +55,12 @@ public class ModdedStringOption : ModdedOption<int>
SetValue(BitConverter.ToInt32(data)); SetValue(BitConverter.ToInt32(data));
} }
public override string GetHudStringText()
{
return Title + ": " + Values[Value];
}
public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour) public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{ {
return optionBehaviour.GetInt(); return optionBehaviour.GetInt();
} }
public override void OnValueChanged(int newValue) protected override void OnValueChanged(int newValue)
{ {
DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false); DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false);
if (!OptionBehaviour) if (!OptionBehaviour)
@@ -47,17 +47,12 @@ public class ModdedToggleOption : ModdedOption<bool>
SetValue(BitConverter.ToBoolean(data)); SetValue(BitConverter.ToBoolean(data));
} }
public override string GetHudStringText()
{
return Title + ": " + (Value ? "On" : "Off");
}
public override bool GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour) public override bool GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{ {
return optionBehaviour.GetBool(); return optionBehaviour.GetBool();
} }
public override void OnValueChanged(bool newValue) protected override void OnValueChanged(bool newValue)
{ {
DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, newValue ? "On" : "Off", false); DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, newValue ? "On" : "Off", false);
if (!OptionBehaviour) if (!OptionBehaviour)
+12 -2
View File
@@ -1,7 +1,17 @@
namespace MiraAPI.Hud; namespace MiraAPI.Hud;
/// <summary>
/// The location of the custom button on the screen.
/// </summary>
public enum ButtonLocation public enum ButtonLocation
{ {
/// <summary>
/// Placed in the bottom left.
/// </summary>
BottomLeft, BottomLeft,
BottomRight
} /// <summary>
/// Placed in the bottom right.
/// </summary>
BottomRight,
}
+2 -2
View File
@@ -22,10 +22,10 @@ internal static class CustomButtonManager
Logger<MiraApiPlugin>.Error($"Failed to create button from {buttonType.Name}"); Logger<MiraApiPlugin>.Error($"Failed to create button from {buttonType.Name}");
return; return;
} }
CustomButtons.Add(button); CustomButtons.Add(button);
typeof(CustomButtonSingleton<>).MakeGenericType(buttonType) typeof(CustomButtonSingleton<>).MakeGenericType(buttonType)
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)! .GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
.SetValue(null, button); .SetValue(null, button);
} }
} }
+4 -1
View File
@@ -7,10 +7,13 @@ namespace MiraAPI.Hud;
/// A utility class to get the instance of a custom action button. /// A utility class to get the instance of a custom action button.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the button you are trying to access.</typeparam> /// <typeparam name="T">The type of the button you are trying to access.</typeparam>
public class CustomButtonSingleton<T> where T : CustomActionButton public static class CustomButtonSingleton<T> where T : CustomActionButton
{ {
private static T? _instance; private static T? _instance;
/// <summary>
/// Gets or sets instance of the button.
/// </summary>
public static T Instance public static T Instance
{ {
get => _instance ??= CustomButtonManager.CustomButtons.OfType<T>().Single(); get => _instance ??= CustomButtonManager.CustomButtons.OfType<T>().Single();
+103 -48
View File
@@ -1,31 +1,51 @@
using MiraAPI.Modifiers.Types; using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Text;
using MiraAPI.Modifiers.Types;
using MiraAPI.Utilities; using MiraAPI.Utilities;
using Reactor.Utilities; using Reactor.Utilities;
using Reactor.Utilities.Attributes; using Reactor.Utilities.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TMPro; using TMPro;
using UnityEngine; using UnityEngine;
namespace MiraAPI.Modifiers; namespace MiraAPI.Modifiers;
/// <summary>
/// The component for handling modifiers.
/// </summary>
[RegisterInIl2Cpp] [RegisterInIl2Cpp]
public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
{ {
public List<BaseModifier> ActiveModifiers { get; private set; } /// <summary>
/// Gets the active modifiers on the player.
/// </summary>
public ImmutableList<BaseModifier> ActiveModifiers => Modifiers.ToImmutableList();
public PlayerControl player; private List<BaseModifier> Modifiers { get; set; } = [];
private TextMeshPro _modifierText; private PlayerControl _player = null!;
public void Start() private TextMeshPro _modifierText = null!;
internal void ClearModifiers()
{ {
player = GetComponent<PlayerControl>(); foreach (var modifier in Modifiers)
ActiveModifiers = []; {
modifier.OnDeactivate();
}
if (!player.AmOwner) Modifiers.Clear();
}
private void Start()
{
_player = GetComponent<PlayerControl>();
Modifiers = [];
if (!_player.AmOwner)
{ {
return; return;
} }
@@ -34,55 +54,56 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
_modifierText.verticalAlignment = VerticalAlignmentOptions.Top; _modifierText.verticalAlignment = VerticalAlignmentOptions.Top;
} }
public void FixedUpdate() private void FixedUpdate()
{ {
if (ActiveModifiers is null) return; foreach (var modifier in Modifiers)
foreach (var modifier in ActiveModifiers)
{ {
if (modifier is null) continue;
modifier.FixedUpdate(); modifier.FixedUpdate();
} }
} }
public void Update() private void Update()
{ {
if (ActiveModifiers is null) return; foreach (var modifier in Modifiers)
foreach (var modifier in ActiveModifiers)
{ {
if (modifier is null) continue;
modifier.Update(); modifier.Update();
} }
var filteredModifiers = ActiveModifiers.Where(mod => !mod.HideOnUi); if (!_modifierText || !_player.AmOwner)
{
return;
}
var filteredModifiers = Modifiers.Where(mod => !mod.HideOnUi);
var baseModifiers = filteredModifiers as BaseModifier[] ?? filteredModifiers.ToArray(); var baseModifiers = filteredModifiers as BaseModifier[] ?? filteredModifiers.ToArray();
if (player.AmOwner && baseModifiers.Any()) if (baseModifiers.Length != 0)
{ {
var stringBuild = new StringBuilder(); var stringBuild = new StringBuilder();
foreach (var mod in baseModifiers) foreach (var mod in baseModifiers)
{ {
stringBuild.Append($"\n{mod.ModifierName}"); stringBuild.Append(CultureInfo.InvariantCulture, $"\n{mod.ModifierName}");
if (mod is TimedModifier timer) if (mod is TimedModifier timer)
{ {
stringBuild.Append($" <size=70%>({Math.Round(timer.Duration - timer.TimeRemaining, 0)}s/{timer.Duration}s)</size>"); stringBuild.Append(CultureInfo.InvariantCulture, $" <size=70%>({Math.Round(timer.Duration - timer.TimeRemaining, 0)}s/{timer.Duration}s)</size>");
} }
} }
_modifierText.text = $"<b><size=130%>Modifiers:</b></size>{stringBuild}"; _modifierText.text = $"<b><size=130%>Modifiers:</b></size>{stringBuild}";
} }
else if (player.AmOwner && !baseModifiers.Any() && _modifierText.text != string.Empty) else if (_modifierText.text != string.Empty)
{ {
_modifierText.text = string.Empty; _modifierText.text = string.Empty;
} }
} }
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="type">The modifier type.</param>
public void RemoveModifier(Type type) public void RemoveModifier(Type type)
{ {
var modifier = ActiveModifiers.Find(x => x.GetType() == type); var modifier = Modifiers.Find(x => x.GetType() == type);
if (modifier is null) if (modifier is null)
{ {
@@ -93,14 +114,22 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
RemoveModifier(modifier); RemoveModifier(modifier);
} }
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <typeparam name="T">The modifier type.</typeparam>
public void RemoveModifier<T>() where T : BaseModifier public void RemoveModifier<T>() where T : BaseModifier
{ {
RemoveModifier(typeof(T)); RemoveModifier(typeof(T));
} }
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="modifierId">The modifier ID.</param>
public void RemoveModifier(uint modifierId) public void RemoveModifier(uint modifierId)
{ {
var modifier = ActiveModifiers.Find(x => x.ModifierId == modifierId); var modifier = Modifiers.Find(x => x.ModifierId == modifierId);
if (modifier is null) if (modifier is null)
{ {
@@ -111,37 +140,54 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
RemoveModifier(modifier); RemoveModifier(modifier);
} }
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="modifier">The modifier object.</param>
public void RemoveModifier(BaseModifier modifier) public void RemoveModifier(BaseModifier modifier)
{ {
if (!ActiveModifiers.Contains(modifier)) if (!Modifiers.Contains(modifier))
{ {
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {modifier.ModifierName} because it is not active on this player."); Logger<MiraApiPlugin>.Error($"Cannot remove modifier {modifier.ModifierName} because it is not active on this player.");
return; return;
} }
modifier.OnDeactivate(); modifier.OnDeactivate();
ActiveModifiers.Remove(modifier); Modifiers.Remove(modifier);
if (player.AmOwner) if (_player.AmOwner)
{ {
HudManager.Instance.SetHudActive(true); HudManager.Instance.SetHudActive(true);
} }
} }
public BaseModifier AddModifier(BaseModifier modifier) /// <summary>
/// Adds a modifier to the player.
/// </summary>
/// <param name="modifier">The modifier to add.</param>
/// <returns>The modifier that was added.</returns>
public BaseModifier? AddModifier(BaseModifier modifier)
{ {
if (ActiveModifiers.Contains(modifier)) if (Modifiers.Contains(modifier))
{ {
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {modifier.ModifierId}!"); Logger<MiraApiPlugin>.Error($"Player already has modifier with id {modifier.ModifierId}!");
return null; return null;
} }
ActiveModifiers.Add(modifier); var modifierId = ModifierManager.GetModifierId(modifier.GetType());
modifier.Player = player;
modifier.ModifierId = ModifierManager.TypeToIdModifiers[modifier.GetType()]; if (modifierId == null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {modifier.GetType().Name} because it has no ID!");
return null;
}
Modifiers.Add(modifier);
modifier.Player = _player;
modifier.ModifierId = modifierId.Value;
modifier.OnActivate(); modifier.OnActivate();
if (!player.AmOwner) if (!_player.AmOwner)
{ {
return modifier; return modifier;
} }
@@ -156,23 +202,27 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
return modifier; return modifier;
} }
public BaseModifier AddModifier(Type type) /// <summary>
/// Adds a modifier to the player.
/// </summary>
/// <param name="type">The modifier type.</param>
/// <returns>The modifier that was added.</returns>
public BaseModifier? AddModifier(Type type)
{ {
if (!ModifierManager.TypeToIdModifiers.TryGetValue(type, out var modifierId)) var modifierId = ModifierManager.GetModifierId(type);
if (modifierId == null)
{ {
Logger<MiraApiPlugin>.Error($"Cannot add modifier {type.Name} because it is not registered."); Logger<MiraApiPlugin>.Error($"Cannot add modifier {type.Name} because it is not registered.");
return null; return null;
} }
if (ActiveModifiers.Find(x => x.ModifierId == modifierId) != null) if (Modifiers.Find(x => x.ModifierId == modifierId) != null)
{ {
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {modifierId}!"); Logger<MiraApiPlugin>.Error($"Player already has modifier with id {modifierId}!");
return null; return null;
} }
var modifier = (BaseModifier)Activator.CreateInstance(type); if (Activator.CreateInstance(type) is not BaseModifier modifier)
if (modifier is null)
{ {
Logger<MiraApiPlugin>.Error($"Cannot add modifier {type.Name} because it is null."); Logger<MiraApiPlugin>.Error($"Cannot add modifier {type.Name} because it is null.");
return null; return null;
@@ -183,8 +233,13 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
return modifier; return modifier;
} }
public T AddModifier<T>() where T : BaseModifier /// <summary>
/// Adds a modifier to the player.
/// </summary>
/// <typeparam name="T">The Type of the modifier.</typeparam>
/// <returns>The new modifier.</returns>
public T? AddModifier<T>() where T : BaseModifier
{ {
return AddModifier(typeof(T)) as T; return AddModifier(typeof(T)) as T;
} }
} }
+53 -38
View File
@@ -1,21 +1,24 @@
using MiraAPI.Modifiers.Types; using System;
using System.Collections.Generic;
using System.Linq;
using MiraAPI.Modifiers.Types;
using MiraAPI.Networking; using MiraAPI.Networking;
using MiraAPI.Roles; using MiraAPI.Roles;
using MiraAPI.Utilities; using MiraAPI.Utilities;
using Reactor.Networking.Rpc; using Reactor.Networking.Rpc;
using Reactor.Utilities.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using Reactor.Utilities; using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using Random = System.Random; using Random = System.Random;
namespace MiraAPI.Modifiers; namespace MiraAPI.Modifiers;
/// <summary>
/// The manager for handling modifiers.
/// </summary>
public static class ModifierManager public static class ModifierManager
{ {
public static readonly Dictionary<uint, Type> IdToTypeModifiers = []; private static readonly Dictionary<uint, Type> IdToTypeModifierMap = [];
public static readonly Dictionary<Type, uint> TypeToIdModifiers = []; private static readonly Dictionary<Type, uint> TypeToIdModifierMap = [];
private static uint _nextId; private static uint _nextId;
@@ -25,6 +28,26 @@ public static class ModifierManager
return _nextId; return _nextId;
} }
/// <summary>
/// Gets the modifier type from the id.
/// </summary>
/// <param name="id">The ID.</param>
/// <returns>The Type of the modifier.</returns>
public static Type? GetModifierType(uint id)
{
return IdToTypeModifierMap.GetValueOrDefault(id);
}
/// <summary>
/// Gets the modifier id from the type.
/// </summary>
/// <param name="type">The Type.</param>
/// <returns>The ID of the modifier.</returns>
public static uint? GetModifierId(Type type)
{
return TypeToIdModifierMap.GetValueOrDefault(type);
}
internal static void RegisterModifier(Type modifierType) internal static void RegisterModifier(Type modifierType)
{ {
if (!typeof(BaseModifier).IsAssignableFrom(modifierType)) if (!typeof(BaseModifier).IsAssignableFrom(modifierType))
@@ -32,8 +55,8 @@ public static class ModifierManager
return; return;
} }
IdToTypeModifiers.Add(GetNextId(), modifierType); IdToTypeModifierMap.Add(GetNextId(), modifierType);
TypeToIdModifiers.Add(modifierType, _nextId); TypeToIdModifierMap.Add(modifierType, _nextId);
} }
internal static void AssignModifiers(List<PlayerControl> plrs) internal static void AssignModifiers(List<PlayerControl> plrs)
@@ -42,34 +65,32 @@ public static class ModifierManager
List<uint> filteredModifiers = []; List<uint> filteredModifiers = [];
foreach (var modifier in IdToTypeModifiers.Where(pair => pair.Value.IsAssignableTo(typeof(GameModifier)))) foreach (var modifier in IdToTypeModifierMap.Where(pair => pair.Value.IsAssignableTo(typeof(GameModifier))))
{ {
var mod = (GameModifier)Activator.CreateInstance(modifier.Value); if (Activator.CreateInstance(modifier.Value) is not GameModifier mod)
if (mod is null)
{ {
Logger<MiraApiPlugin>.Error($"Failed to create instance of {modifier.Value.Name}"); Logger<MiraApiPlugin>.Error($"Failed to create instance of {modifier.Value.Name}");
continue; continue;
} }
if (!plrs.Exists(x=>IsGameModifierValid(x, mod, modifier.Key))) if (!plrs.Exists(x => IsGameModifierValid(x, mod, modifier.Key)))
{ {
Logger<MiraApiPlugin>.Warning("No players are valid for modifier: " + mod.ModifierName); Logger<MiraApiPlugin>.Warning("No players are valid for modifier: " + mod.ModifierName);
continue; continue;
} }
var maxCount = plrs.Count(x=>IsGameModifierValid(x, mod, modifier.Key)); var maxCount = plrs.Count(x => IsGameModifierValid(x, mod, modifier.Key));
var num = Math.Clamp(mod.GetAmountPerGame(), 0, maxCount); var num = Math.Clamp(mod.GetAmountPerGame(), 0, maxCount);
var chance = mod.GetAssignmentChance(); var chance = mod.GetAssignmentChance();
for (var i = 0; i < num; i++) for (var i = 0; i < num; i++)
{ {
var randomNum = rand.Next(100); var randomNum = rand.Next(100);
if (randomNum < Math.Clamp(chance, 0, 100)) if (randomNum < Math.Clamp(chance, 0, 100))
{ {
filteredModifiers.Add(TypeToIdModifiers[modifier.Value]); filteredModifiers.Add(TypeToIdModifierMap[modifier.Value]);
} }
} }
} }
@@ -84,15 +105,13 @@ public static class ModifierManager
{ {
var id = shuffledModifiers[0]; var id = shuffledModifiers[0];
var mod = (GameModifier)Activator.CreateInstance(IdToTypeModifiers[id]); if (Activator.CreateInstance(IdToTypeModifierMap[id]) is not GameModifier mod)
if (mod is null)
{ {
Logger<MiraApiPlugin>.Error($"Failed to create instance of {IdToTypeModifiers[id].Name}"); Logger<MiraApiPlugin>.Error($"Failed to create instance of {IdToTypeModifierMap[id].Name}");
continue; continue;
} }
if (!plrs.Exists(x=>IsGameModifierValid(x, mod, id))) if (!plrs.Exists(x => IsGameModifierValid(x, mod, id)))
{ {
shuffledModifiers.RemoveAt(0); shuffledModifiers.RemoveAt(0);
continue; continue;
@@ -100,7 +119,7 @@ public static class ModifierManager
var plr = plrs.Random(); var plr = plrs.Random();
if (!IsGameModifierValid(plr, mod, id)) if (plr == null || !IsGameModifierValid(plr, mod, id))
{ {
continue; continue;
} }
@@ -116,7 +135,7 @@ public static class ModifierManager
modifier.IsModifierValidOn(player.Data.Role) && modifier.IsModifierValidOn(player.Data.Role) &&
!player.HasModifier(modifierId); !player.HasModifier(modifierId);
} }
internal static void SyncAllModifiers(int targetId = -1) internal static void SyncAllModifiers(int targetId = -1)
{ {
var data = new List<NetData>(); var data = new List<NetData>();
@@ -126,7 +145,7 @@ public static class ModifierManager
data.Add(GetPlayerModifiers(player.Object)); data.Add(GetPlayerModifiers(player.Object));
} }
Rpc<SyncModifiersRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray()); Rpc<SyncModifiersRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, [.. data]);
} }
internal static void HandleSyncModifiers(NetData[] data) internal static void HandleSyncModifiers(NetData[] data)
@@ -144,20 +163,16 @@ public static class ModifierManager
continue; continue;
} }
foreach (var modifier in modifierComponent.ActiveModifiers) modifierComponent.ClearModifiers();
{
modifier.OnDeactivate();
}
modifierComponent.ActiveModifiers.Clear();
foreach (var id in ids) foreach (var id in ids)
{ {
if (!IdToTypeModifiers.TryGetValue(id, out var type)) if (!IdToTypeModifierMap.TryGetValue(id, out var type))
{ {
Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {id} because it is not registered."); Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {id} because it is not registered.");
continue; continue;
} }
modifierComponent.AddModifier(type); modifierComponent.AddModifier(type);
} }
} }
@@ -167,16 +182,16 @@ public static class ModifierManager
{ {
var bytes = new List<byte>(); var bytes = new List<byte>();
var modifierComponent = player.GetComponent<ModifierComponent>(); var modifierComponent = player.GetComponent<ModifierComponent>();
if (!modifierComponent) if (modifierComponent == null)
{ {
return new NetData(player.PlayerId, []); return new NetData(player.PlayerId, []);
} }
foreach (var modifier in modifierComponent.ActiveModifiers) foreach (var modifier in modifierComponent.ActiveModifiers)
{ {
bytes.AddRange(BitConverter.GetBytes(TypeToIdModifiers[modifier.GetType()])); bytes.AddRange(BitConverter.GetBytes(TypeToIdModifierMap[modifier.GetType()]));
} }
return new NetData(player.PlayerId, bytes.ToArray()); return new NetData(player.PlayerId, [.. bytes]);
} }
} }
@@ -10,15 +10,21 @@ using UnityEngine.Events;
namespace MiraAPI.Patches.Options; namespace MiraAPI.Patches.Options;
/// <summary>
/// Patches the GameOptionsMenu to add custom options.
/// </summary>
[HarmonyPatch(typeof(GameOptionsMenu))] [HarmonyPatch(typeof(GameOptionsMenu))]
public static class GameOptionsMenuPatch public static class GameOptionsMenuPatch
{ {
/// <summary>
/// Update patch for the GameOptionsMenu.
/// </summary>
/// <param name="__instance">The GameOptionsMenu instance.</param>
[HarmonyPostfix] [HarmonyPostfix]
[HarmonyPatch(nameof(GameOptionsMenu.Update))] [HarmonyPatch(nameof(GameOptionsMenu.Update))]
public static void UpdatePatch(GameOptionsMenu __instance) public static void UpdatePatch(GameOptionsMenu __instance)
{ {
if (GameSettingMenuPatches.CurrentSelectedMod == 0) if (GameSettingMenuPatches.SelectedModIdx == 0)
{ {
return; return;
} }
@@ -71,7 +77,7 @@ public static class GameOptionsMenuPatch
[HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))] [HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))]
public static bool SettingsPatch(GameOptionsMenu __instance) public static bool SettingsPatch(GameOptionsMenu __instance)
{ {
if (GameSettingMenuPatches.CurrentSelectedMod == 0) if (GameSettingMenuPatches.SelectedModIdx == 0)
{ {
return true; return true;
} }
@@ -12,17 +12,30 @@ using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Options; namespace MiraAPI.Patches.Options;
/// <summary>
/// Patches for the <see cref="GameSettingMenu"/> to add support for custom options.
/// </summary>
[HarmonyPatch(typeof(GameSettingMenu))] [HarmonyPatch(typeof(GameSettingMenu))]
public static class GameSettingMenuPatches public static class GameSettingMenuPatches
{ {
public static int CurrentSelectedMod { get; private set; } /// <summary>
/// Gets the currently selected mod index.
/// </summary>
public static int SelectedModIdx { get; private set; }
/// <summary>
/// Gets the currently selected mod.
/// </summary>
public static MiraPluginInfo? SelectedMod { get; private set; } public static MiraPluginInfo? SelectedMod { get; private set; }
private static TextMeshPro? _text; private static TextMeshPro? _text;
private static Vector3 _roleBtnOgPos; private static Vector3 _roleBtnOgPos;
/// <summary>
/// Prefix for the <see cref="GameSettingMenu.Start"/> method. Sets up the custom options.
/// </summary>
/// <param name="__instance">The GameSettingMenu instance.</param>
[HarmonyPrefix] [HarmonyPrefix]
[HarmonyPatch(nameof(GameSettingMenu.Start))] [HarmonyPatch(nameof(GameSettingMenu.Start))]
public static void StartPrefix(GameSettingMenu __instance) public static void StartPrefix(GameSettingMenu __instance)
@@ -56,11 +69,12 @@ public static class GameSettingMenuPatches
passiveButton.OnClick = new ButtonClickedEvent(); passiveButton.OnClick = new ButtonClickedEvent();
passiveButton.OnClick.AddListener((UnityAction)(() => passiveButton.OnClick.AddListener((UnityAction)(() =>
{ {
if (CurrentSelectedMod != MiraPluginManager.Instance.RegisteredPlugins.Count) SelectedModIdx += 1;
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length)
{ {
CurrentSelectedMod += 1; SelectedModIdx = 0;
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
} }
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
})); }));
var backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject; var backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject;
@@ -70,17 +84,18 @@ public static class GameSettingMenuPatches
backButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().flipX = backButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().flipX = true; backButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().flipX = backButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().flipX = true;
backButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener((UnityAction)(() => backButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener((UnityAction)(() =>
{ {
if (CurrentSelectedMod != 0) SelectedModIdx -= 1;
if (SelectedModIdx < 0)
{ {
CurrentSelectedMod -= 1; SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length;
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
} }
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
})); }));
} }
private static void UpdateText(GameSettingMenu menu, GameOptionsMenu settings, RolesSettingsMenu roles) private static void UpdateText(GameSettingMenu menu, GameOptionsMenu settings, RolesSettingsMenu roles)
{ {
if (_text is not null && CurrentSelectedMod == 0) if (_text is not null && SelectedModIdx == 0)
{ {
_text.text = "Default"; _text.text = "Default";
_text.fontSizeMax = 3.2f; _text.fontSizeMax = 3.2f;
@@ -88,22 +103,17 @@ public static class GameSettingMenuPatches
else if (_text is not null) else if (_text is not null)
{ {
_text.fontSizeMax = 2.3f; _text.fontSizeMax = 2.3f;
SelectedMod = MiraPluginManager.Instance.RegisteredPlugins.ElementAt(CurrentSelectedMod - 1).Value; SelectedMod = MiraPluginManager.Instance.RegisteredPlugins()[SelectedModIdx - 1];
if (SelectedMod == null)
{
CurrentSelectedMod = 0;
UpdateText(menu, settings, roles);
}
var name = SelectedMod?.MiraPlugin.OptionsTitleText; var name = SelectedMod.MiraPlugin.OptionsTitleText;
_text.text = name?[..Math.Min(name.Length, 25)]; _text.text = name[..Math.Min(name.Length, 25)];
} }
menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos; menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos;
menu.GameSettingsButton.gameObject.SetActive(true); menu.GameSettingsButton.gameObject.SetActive(true);
menu.RoleSettingsButton.gameObject.SetActive(true); menu.RoleSettingsButton.gameObject.SetActive(true);
if (CurrentSelectedMod != 0) if (SelectedModIdx != 0)
{ {
if (SelectedMod?.Options.Where(x=>x.AdvancedRole==null).ToList().Count == 0) if (SelectedMod?.Options.Where(x=>x.AdvancedRole==null).ToList().Count == 0)
{ {
@@ -121,7 +131,7 @@ public static class GameSettingMenuPatches
} }
} }
if (roles?.roleChances != null && SelectedMod != null && SelectedMod.CustomRoles.Count != 0) if (roles.roleChances != null && SelectedMod != null && SelectedMod.CustomRoles.Count != 0)
{ {
if (roles.advancedSettingChildren is not null) if (roles.advancedSettingChildren is not null)
{ {
+120 -122
View File
@@ -16,10 +16,9 @@ namespace MiraAPI.Patches.Options;
[HarmonyPatch(typeof(LobbyViewSettingsPane))] [HarmonyPatch(typeof(LobbyViewSettingsPane))]
public static class LobbyViewPanePatches public static class LobbyViewPanePatches
{ {
public static int CurrentSelectedMod { get; private set; } public static int CurrentSelectedMod { get; private set; }
public static MiraPluginInfo? SelectedMod => CurrentSelectedMod == 0 ? null : MiraPluginManager.Instance.RegisteredPlugins.ElementAt(CurrentSelectedMod-1).Value; public static MiraPluginInfo? SelectedMod => CurrentSelectedMod == 0 ? null : MiraPluginManager.Instance.RegisteredPlugins()[CurrentSelectedMod-1];
[HarmonyPostfix] [HarmonyPostfix]
@@ -48,7 +47,7 @@ public static class LobbyViewPanePatches
passiveButton.OnClick.AddListener((UnityAction)(() => passiveButton.OnClick.AddListener((UnityAction)(() =>
{ {
CurrentSelectedMod += 1; CurrentSelectedMod += 1;
if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins.Count) if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins().Length)
{ {
CurrentSelectedMod = 0; CurrentSelectedMod = 0;
} }
@@ -71,7 +70,7 @@ public static class LobbyViewPanePatches
CurrentSelectedMod -= 1; CurrentSelectedMod -= 1;
if (CurrentSelectedMod < 0) if (CurrentSelectedMod < 0)
{ {
CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins.Count; CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins().Length;
} }
__instance.RefreshTab(); __instance.RefreshTab();
__instance.scrollBar.ScrollToTop(); __instance.scrollBar.ScrollToTop();
@@ -185,146 +184,145 @@ public static class LobbyViewPanePatches
private static void DrawRolesTab(LobbyViewSettingsPane instance) private static void DrawRolesTab(LobbyViewSettingsPane instance)
{ {
if (SelectedMod == null) if (SelectedMod == null)
{ {
return; return;
} }
var num = 0.95f; var num = 0.95f;
var num2 = -6.53f; var num2 = -6.53f;
var categoryHeaderMasked = var categoryHeaderMasked =
Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true); Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
categoryHeaderMasked.SetHeader(StringNames.RoleQuotaLabel, 61); categoryHeaderMasked.SetHeader(StringNames.RoleQuotaLabel, 61);
categoryHeaderMasked.transform.localScale = Vector3.one; categoryHeaderMasked.transform.localScale = Vector3.one;
categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, 1.26f, -2f); categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, 1.26f, -2f);
instance.settingsInfo.Add(categoryHeaderMasked.gameObject); instance.settingsInfo.Add(categoryHeaderMasked.gameObject);
var list = new List<Type>(); var list = new List<Type>();
foreach (var team in Enum.GetValues<ModdedRoleTeams>()) foreach (var team in Enum.GetValues<ModdedRoleTeams>())
{ {
var filteredRoles = SelectedMod.CustomRoles.Values var filteredRoles = SelectedMod.CustomRoles.Values
.OfType<ICustomRole>() .OfType<ICustomRole>()
.Where(x => !x.HideSettings && x.Team == team).ToList(); .Where(x => !x.HideSettings && x.Team == team).ToList();
if (filteredRoles.Count == 0) if (filteredRoles.Count == 0)
{ {
continue; continue;
} }
var categoryHeaderRoleVariant = var categoryHeaderRoleVariant =
Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true); Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true);
switch (team) switch (team)
{ {
case ModdedRoleTeams.Crewmate: case ModdedRoleTeams.Crewmate:
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61); categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
break; break;
case ModdedRoleTeams.Impostor: case ModdedRoleTeams.Impostor:
categoryHeaderRoleVariant.SetHeader(StringNames.ImpostorRolesHeader, 61); categoryHeaderRoleVariant.SetHeader(StringNames.ImpostorRolesHeader, 61);
break; break;
case ModdedRoleTeams.Neutral: default:
default: categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61); categoryHeaderRoleVariant.Title.text = team + " Roles";
categoryHeaderRoleVariant.Title.text = team + " Roles"; categoryHeaderRoleVariant.Background.color = Color.gray;
categoryHeaderRoleVariant.Background.color = Color.gray; categoryHeaderRoleVariant.Title.color = Color.white;
categoryHeaderRoleVariant.Title.color = Color.white; break;
break; }
}
categoryHeaderRoleVariant.transform.localScale = Vector3.one; categoryHeaderRoleVariant.transform.localScale = Vector3.one;
categoryHeaderRoleVariant.transform.localPosition = new Vector3(0.09f, num, -2f); categoryHeaderRoleVariant.transform.localPosition = new Vector3(0.09f, num, -2f);
instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject); instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject);
num -= 0.696f; num -= 0.696f;
foreach (var customRole in filteredRoles) foreach (var customRole in filteredRoles)
{ {
var roleBehaviour = customRole as RoleBehaviour; var roleBehaviour = customRole as RoleBehaviour;
if (roleBehaviour == null) if (roleBehaviour == null)
{ {
continue; continue;
} }
var chancePerGame = var chancePerGame =
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleBehaviour.Role); GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleBehaviour.Role);
var numPerGame = var numPerGame =
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role); GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role);
var flag = numPerGame == 0; var flag = numPerGame == 0;
var viewSettingsInfoPanelRoleVariant = var viewSettingsInfoPanelRoleVariant =
Object.Instantiate( Object.Instantiate(
instance.infoPanelRoleOrigin, instance.settingsContainer, true); instance.infoPanelRoleOrigin, instance.settingsContainer, true);
viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one; viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one;
viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f); viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f);
var advancedRoleOptions = SelectedMod.Options var advancedRoleOptions = SelectedMod.Options
.Where(x => x.AdvancedRole == customRole.GetType()) .Where(x => x.AdvancedRole == customRole.GetType())
.ToList(); .ToList();
if (!flag && advancedRoleOptions.Count > 0) if (!flag && advancedRoleOptions.Count > 0)
{ {
list.Add(customRole.GetType()); list.Add(customRole.GetType());
} }
viewSettingsInfoPanelRoleVariant.SetInfo(roleBehaviour.NiceName, numPerGame, chancePerGame, viewSettingsInfoPanelRoleVariant.SetInfo(roleBehaviour.NiceName, numPerGame, chancePerGame,
61, customRole.RoleColor, customRole.Icon.LoadAsset(), true); 61, customRole.RoleColor, customRole.Icon.LoadAsset(), true);
viewSettingsInfoPanelRoleVariant.titleText.color = viewSettingsInfoPanelRoleVariant.titleText.color =
viewSettingsInfoPanelRoleVariant.chanceTitle.color = viewSettingsInfoPanelRoleVariant.chanceTitle.color =
viewSettingsInfoPanelRoleVariant.chanceBackground.color = viewSettingsInfoPanelRoleVariant.chanceBackground.color =
viewSettingsInfoPanelRoleVariant.background.color = viewSettingsInfoPanelRoleVariant.background.color =
customRole.RoleColor.GetAlternateColor(); customRole.RoleColor.GetAlternateColor();
instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject); instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject);
num -= 0.664f; num -= 0.664f;
} }
} }
if (list.Count > 0) if (list.Count > 0)
{ {
var categoryHeaderMasked2 = var categoryHeaderMasked2 =
Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true); Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
categoryHeaderMasked2.SetHeader(StringNames.RoleSettingsLabel, 61); categoryHeaderMasked2.SetHeader(StringNames.RoleSettingsLabel, 61);
categoryHeaderMasked2.transform.localScale = Vector3.one; categoryHeaderMasked2.transform.localScale = Vector3.one;
categoryHeaderMasked2.transform.localPosition = new Vector3(-9.77f, num, -2f); categoryHeaderMasked2.transform.localPosition = new Vector3(-9.77f, num, -2f);
instance.settingsInfo.Add(categoryHeaderMasked2.gameObject); instance.settingsInfo.Add(categoryHeaderMasked2.gameObject);
num -= 1.7f; num -= 1.7f;
var num3 = 0f; var num3 = 0f;
for (var k = 0; k < list.Count; k++) for (var k = 0; k < list.Count; k++)
{ {
float num4; float num4;
if (k % 2 == 0) if (k % 2 == 0)
{ {
num4 = -5.8f; num4 = -5.8f;
if (k > 0) if (k > 0)
{ {
num -= num3 + 0.59f; num -= num3 + 0.59f;
num3 = 0f; num3 = 0f;
} }
} }
else else
{ {
num4 = 0.14999962f; num4 = 0.14999962f;
} }
var advancedRoleViewPanel = var advancedRoleViewPanel =
Object.Instantiate(instance.advancedRolePanelOrigin, instance.settingsContainer, true); Object.Instantiate(instance.advancedRolePanelOrigin, instance.settingsContainer, true);
advancedRoleViewPanel.transform.localScale = Vector3.one; advancedRoleViewPanel.transform.localScale = Vector3.one;
advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f); advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f);
var num5 = SetUpAdvancedRoleViewPanel(advancedRoleViewPanel, list[k], 0.59f, 61); var num5 = SetUpAdvancedRoleViewPanel(advancedRoleViewPanel, list[k], 0.59f, 61);
if (num5 > num3) if (num5 > num3)
{ {
num3 = num5; num3 = num5;
} }
instance.settingsInfo.Add(advancedRoleViewPanel.gameObject); instance.settingsInfo.Add(advancedRoleViewPanel.gameObject);
} }
} }
instance.scrollBar.SetYBoundsMax(-num); instance.scrollBar.SetYBoundsMax(-num);
} }
private static float SetUpAdvancedRoleViewPanel(AdvancedRoleViewPanel viewPanel, Type roleType, float spacingY, int maskLayer) private static float SetUpAdvancedRoleViewPanel(AdvancedRoleViewPanel viewPanel, Type roleType, float spacingY, int maskLayer)
@@ -30,7 +30,7 @@ public static class RoleSettingMenuPatches
var maskBg = __instance.scrollBar.transform.FindChild("MaskBg"); var maskBg = __instance.scrollBar.transform.FindChild("MaskBg");
var hitbox = __instance.scrollBar.transform.FindChild("Hitbox"); var hitbox = __instance.scrollBar.transform.FindChild("Hitbox");
if (GameSettingMenuPatches.CurrentSelectedMod == 0) if (GameSettingMenuPatches.SelectedModIdx == 0)
{ {
__instance.AllButton.transform.parent.gameObject.SetActive(true); __instance.AllButton.transform.parent.gameObject.SetActive(true);
__instance.AllButton.gameObject.SetActive(true); __instance.AllButton.gameObject.SetActive(true);
@@ -127,7 +127,7 @@ public static class RoleSettingMenuPatches
[HarmonyPatch(nameof(RolesSettingsMenu.Update))] [HarmonyPatch(nameof(RolesSettingsMenu.Update))]
public static bool UpdatePatch() public static bool UpdatePatch()
{ {
return GameSettingMenuPatches.CurrentSelectedMod == 0; return GameSettingMenuPatches.SelectedModIdx == 0;
} }
[HarmonyPostfix] [HarmonyPostfix]
+25 -7
View File
@@ -1,14 +1,29 @@
using HarmonyLib; using System.Globalization;
using HarmonyLib;
using MiraAPI.Utilities; using MiraAPI.Utilities;
using UnityEngine; using UnityEngine;
namespace MiraAPI.Patches.Options; namespace MiraAPI.Patches.Options;
/// <summary>
/// Patches for the various game settings.
/// </summary>
[HarmonyPatch] [HarmonyPatch]
public class SettingPatches public class SettingPatches
{ {
/// <summary>
/// Prefix for the <see cref="FloatGameSetting.GetValueString"/> method. Adds support for custom number suffixes.
/// </summary>
/// <param name="__instance">The FloatGameSetting instance.</param>
/// <param name="__result">The result of the GetValueString method.</param>
/// <param name="value">The float value.</param>
/// <returns>False to skip original method.</returns>
[HarmonyPrefix] [HarmonyPrefix]
[HarmonyPatch(typeof(FloatGameSetting), nameof(FloatGameSetting.GetValueString))] [HarmonyPatch(typeof(FloatGameSetting), nameof(FloatGameSetting.GetValueString))]
public static bool ValueStringPatch(FloatGameSetting __instance, ref string __result, [HarmonyArgument(0)] float value) public static bool ValueStringPatch(
FloatGameSetting __instance,
ref string __result,
[HarmonyArgument(0)] float value)
{ {
var result = string.Empty; var result = string.Empty;
var suffix = (MiraNumberSuffixes)__instance.SuffixType; var suffix = (MiraNumberSuffixes)__instance.SuffixType;
@@ -31,12 +46,15 @@ public class SettingPatches
} }
else else
{ {
result = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameSecondsAbbrev, (Il2CppSystem.Object[])( result = DestroyableSingleton<TranslationController>.Instance.GetString(
[ StringNames.GameSecondsAbbrev,
value.ToString(__instance.FormatString) (Il2CppSystem.Object[])
])); [
value.ToString(__instance.FormatString, CultureInfo.InvariantCulture)
]);
} }
__result = result; __result = result;
return false; return false;
} }
} }
+1 -2
View File
@@ -2,7 +2,6 @@
using HarmonyLib; using HarmonyLib;
using Il2CppSystem; using Il2CppSystem;
using Il2CppSystem.Collections.Generic; using Il2CppSystem.Collections.Generic;
using Il2CppSystem.Text;
using MiraAPI.PluginLoading; using MiraAPI.PluginLoading;
using MiraAPI.Roles; using MiraAPI.Roles;
using MiraAPI.Utilities.Assets; using MiraAPI.Utilities.Assets;
@@ -63,7 +62,7 @@ public static class TaskAdderPatch
_rolesFolder.FolderName = "Roles"; _rolesFolder.FolderName = "Roles";
_rolesFolder.name = "RolesFolder"; _rolesFolder.name = "RolesFolder";
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
{ {
var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner); var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
newFolder.FolderName = newFolder.name = plugin.PluginInfo.Metadata.Name; newFolder.FolderName = newFolder.name = plugin.PluginInfo.Metadata.Name;
+38 -10
View File
@@ -6,16 +6,44 @@ using System.Collections.Generic;
namespace MiraAPI.PluginLoading; namespace MiraAPI.PluginLoading;
public class MiraPluginInfo(IMiraPlugin miraPlugin, PluginInfo info) /// <summary>
/// Represents a Mira plugin.
/// </summary>
public class MiraPluginInfo
{ {
public string PluginId { get; set; } = info.Metadata.GUID; internal MiraPluginInfo(IMiraPlugin miraPlugin, PluginInfo info)
public IMiraPlugin MiraPlugin { get; set; } = miraPlugin; {
public PluginInfo PluginInfo { get; set; } = info; MiraPlugin = miraPlugin;
public ConfigFile PluginConfig { get; set; } = miraPlugin.GetConfigFile(); PluginConfig = miraPlugin.GetConfigFile();
PluginInfo = info;
PluginId = info.Metadata.GUID;
}
public readonly List<AbstractOptionGroup> OptionGroups = []; internal List<AbstractOptionGroup> OptionGroups { get; } = [];
public readonly List<IModdedOption> Options = [];
public readonly Dictionary<ushort, RoleBehaviour> CustomRoles = []; internal List<IModdedOption> Options { get; } = [];
internal readonly Dictionary<int, CustomGameMode> GameModes = [];
} internal Dictionary<ushort, RoleBehaviour> CustomRoles { get; } = [];
internal Dictionary<int, CustomGameMode> GameModes { get; } = [];
/// <summary>
/// Gets the plugin's ID, as defined in the plugin's BepInEx metadata.
/// </summary>
public string PluginId { get; }
/// <summary>
/// Gets the plugin's instance as an <see cref="IMiraPlugin"/>.
/// </summary>
public IMiraPlugin MiraPlugin { get; }
/// <summary>
/// Gets the plugin's BepInEx metadata.
/// </summary>
public PluginInfo PluginInfo { get; }
/// <summary>
/// Gets the plugin's configuration file.
/// </summary>
public ConfigFile PluginConfig { get; }
}
+26 -32
View File
@@ -1,53 +1,49 @@
using BepInEx.Unity.IL2CPP; using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using BepInEx.Unity.IL2CPP;
using MiraAPI.Colors;
using MiraAPI.GameOptions; using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes; using MiraAPI.GameOptions.Attributes;
using MiraAPI.Hud; using MiraAPI.Hud;
using MiraAPI.Modifiers; using MiraAPI.Modifiers;
using MiraAPI.Roles; using MiraAPI.Roles;
using Reactor.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MiraAPI.Colors;
using MiraAPI.Utilities; using MiraAPI.Utilities;
using Reactor.Networking; using Reactor.Networking;
using Reactor.Utilities;
namespace MiraAPI.PluginLoading; namespace MiraAPI.PluginLoading;
internal class MiraPluginManager internal class MiraPluginManager
{ {
public readonly Dictionary<Assembly, MiraPluginInfo> RegisteredPlugins = []; private readonly Dictionary<Assembly, MiraPluginInfo> _registeredPlugins = [];
private static MiraPluginManager _instance; internal MiraPluginInfo[] RegisteredPlugins() => [.. _registeredPlugins.Values];
public static MiraPluginManager Instance public static MiraPluginManager Instance { get; private set; } = new();
{
get => _instance ??= new MiraPluginManager();
private set => _instance = value;
}
internal void Initialize() internal void Initialize()
{ {
Instance = this; Instance = this;
IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, assembly, plugin) => IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, assembly, plugin) =>
{ {
if (!plugin.GetType().GetInterfaces().Contains(typeof(IMiraPlugin))) if (plugin is not IMiraPlugin miraPlugin)
{ {
return; return;
} }
var info = new MiraPluginInfo(plugin as IMiraPlugin, pluginInfo); var info = new MiraPluginInfo(miraPlugin, pluginInfo);
RegisterModifierAttribute(assembly); RegisterModifierAttribute(assembly);
RegisterAllOptions(assembly, info); RegisterAllOptions(assembly, info);
RegisterRoleAttribute(assembly, info); RegisterRoleAttribute(assembly, info);
RegisterButtonAttribute(assembly); RegisterButtonAttribute(assembly);
RegisterColorClasses(assembly); RegisterColorClasses(assembly);
RegisteredPlugins.Add(assembly, info); _registeredPlugins.Add(assembly, info);
Logger<MiraApiPlugin>.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API."); Logger<MiraApiPlugin>.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API.");
}; };
@@ -55,7 +51,7 @@ internal class MiraPluginManager
public MiraPluginInfo GetPluginByGuid(string guid) public MiraPluginInfo GetPluginByGuid(string guid)
{ {
return RegisteredPlugins.Values.First(plugin => plugin.PluginId == guid); return _registeredPlugins.Values.First(plugin => plugin.PluginId == guid);
} }
private static void RegisterAllOptions(Assembly assembly, MiraPluginInfo pluginInfo) private static void RegisterAllOptions(Assembly assembly, MiraPluginInfo pluginInfo)
@@ -68,7 +64,7 @@ internal class MiraPluginManager
{ {
continue; continue;
} }
foreach (var property in type.GetProperties()) foreach (var property in type.GetProperties())
{ {
if (property.PropertyType.IsAssignableTo(typeof(IModdedOption))) if (property.PropertyType.IsAssignableTo(typeof(IModdedOption)))
@@ -76,7 +72,7 @@ internal class MiraPluginManager
ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo); ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo);
continue; continue;
} }
var attribute = property.GetCustomAttribute<ModdedOptionAttribute>(); var attribute = property.GetCustomAttribute<ModdedOptionAttribute>();
if (attribute == null) if (attribute == null)
{ {
@@ -98,22 +94,22 @@ internal class MiraPluginManager
{ {
continue; continue;
} }
if (!(typeof(RoleBehaviour).IsAssignableFrom(type) && typeof(ICustomRole).IsAssignableFrom(type))) if (!(typeof(RoleBehaviour).IsAssignableFrom(type) && typeof(ICustomRole).IsAssignableFrom(type)))
{ {
Logger<MiraApiPlugin>.Error($"{type.Name} does not inherit from RoleBehaviour or ICustomRole."); Logger<MiraApiPlugin>.Error($"{type.Name} does not inherit from RoleBehaviour or ICustomRole.");
continue; continue;
} }
if (!ModList.GetById(pluginInfo.PluginId).IsRequiredOnAllClients) if (!ModList.GetById(pluginInfo.PluginId).IsRequiredOnAllClients)
{ {
Logger<MiraApiPlugin>.Error("Custom roles are only supported on all clients."); Logger<MiraApiPlugin>.Error("Custom roles are only supported on all clients.");
return; return;
} }
roles.Add(type); roles.Add(type);
} }
CustomRoleManager.RegisterRoleTypes(roles, pluginInfo); CustomRoleManager.RegisterRoleTypes(roles, pluginInfo);
} }
@@ -138,14 +134,13 @@ internal class MiraPluginManager
{ {
continue; continue;
} }
var color = (CustomColor)property.GetValue(null); if (property.GetValue(null) is not CustomColor color)
if (color == null)
{ {
Logger<MiraApiPlugin>.Error($"Color property {property.Name} in {type.Name} is null."); Logger<MiraApiPlugin>.Error($"Color property {property.Name} in {type.Name} is not a CustomColor.");
continue; continue;
} }
PaletteManager.CustomColors.Add(color); PaletteManager.CustomColors.Add(color);
} }
} }
@@ -163,7 +158,6 @@ internal class MiraPluginManager
} }
} }
private static void RegisterButtonAttribute(Assembly assembly) private static void RegisterButtonAttribute(Assembly assembly)
{ {
foreach (var type in assembly.GetTypes()) foreach (var type in assembly.GetTypes())
@@ -175,4 +169,4 @@ internal class MiraPluginManager
} }
} }
} }
} }
+3 -3
View File
@@ -122,7 +122,7 @@ public static class CustomRoleManager
/// <returns>A MiraPluginInfo object representing the parent mod of the role.</returns> /// <returns>A MiraPluginInfo object representing the parent mod of the role.</returns>
public static MiraPluginInfo FindParentMod(ICustomRole role) public static MiraPluginInfo FindParentMod(ICustomRole role)
{ {
return MiraPluginManager.Instance.RegisteredPlugins.First(plugin => plugin.Value.CustomRoles.ContainsValue(role as RoleBehaviour ?? throw new InvalidOperationException())).Value; return MiraPluginManager.Instance.RegisteredPlugins().First(plugin => plugin.CustomRoles.ContainsValue(role as RoleBehaviour ?? throw new InvalidOperationException()));
} }
/// <summary> /// <summary>
@@ -197,7 +197,7 @@ public static class CustomRoleManager
// we dont know how other plugins handle their configs // we dont know how other plugins handle their configs
// this way, all the options are saved at once, instead of one by one // this way, all the options are saved at once, instead of one by one
var oldConfigSetting = new Dictionary<MiraPluginInfo, bool>(); var oldConfigSetting = new Dictionary<MiraPluginInfo, bool>();
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
{ {
oldConfigSetting.Add(plugin, plugin.PluginConfig.SaveOnConfigSet); oldConfigSetting.Add(plugin, plugin.PluginConfig.SaveOnConfigSet);
plugin.PluginConfig.SaveOnConfigSet = false; plugin.PluginConfig.SaveOnConfigSet = false;
@@ -239,7 +239,7 @@ public static class CustomRoleManager
} }
} }
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
{ {
plugin.PluginConfig.Save(); plugin.PluginConfig.Save();
plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin]; plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin];
+13 -10
View File
@@ -99,7 +99,7 @@ public static class Extensions
if (current.Count > 0) if (current.Count > 0)
{ {
chunks.Enqueue(current.ToArray()); chunks.Enqueue([.. current]);
} }
return chunks; return chunks;
@@ -112,7 +112,7 @@ public static class Extensions
/// <returns>True if the OptionBehaviour is for a custom options, false otherwise.</returns> /// <returns>True if the OptionBehaviour is for a custom options, false otherwise.</returns>
public static bool IsCustom(this OptionBehaviour optionBehaviour) public static bool IsCustom(this OptionBehaviour optionBehaviour)
{ {
return ModdedOptionsManager.ModdedOptions.Values.Any(opt => opt.OptionBehaviour && opt.OptionBehaviour.Equals(optionBehaviour)); return ModdedOptionsManager.ModdedOptions.Values.Any(opt => opt.OptionBehaviour && opt.OptionBehaviour == optionBehaviour);
} }
private static readonly Dictionary<PlayerControl, ModifierComponent> ModifierComponents = []; private static readonly Dictionary<PlayerControl, ModifierComponent> ModifierComponents = [];
@@ -208,13 +208,15 @@ public static class Extensions
/// <typeparam name="T">The Type of the Modifier.</typeparam> /// <typeparam name="T">The Type of the Modifier.</typeparam>
public static void RpcRemoveModifier<T>(this PlayerControl player) where T : BaseModifier public static void RpcRemoveModifier<T>(this PlayerControl player) where T : BaseModifier
{ {
if (!ModifierManager.TypeToIdModifiers.TryGetValue(typeof(T), out var id)) var id = ModifierManager.GetModifierId(typeof(T));
if (id == null)
{ {
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
return; return;
} }
player.RpcRemoveModifier(id); player.RpcRemoveModifier(id.Value);
} }
/// <summary> /// <summary>
@@ -225,13 +227,13 @@ public static class Extensions
[MethodRpc((uint)MiraRpc.AddModifier)] [MethodRpc((uint)MiraRpc.AddModifier)]
public static void RpcAddModifier(this PlayerControl target, uint modifierId) public static void RpcAddModifier(this PlayerControl target, uint modifierId)
{ {
if (ModifierManager.IdToTypeModifiers.TryGetValue(modifierId, out var type)) var type = ModifierManager.GetModifierType(modifierId);
if (type == null)
{ {
target.GetModifierComponent()?.AddModifier(type); Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
return; return;
} }
target.GetModifierComponent()?.AddModifier(type);
Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
} }
/// <summary> /// <summary>
@@ -241,13 +243,14 @@ public static class Extensions
/// <typeparam name="T">The modifier Type.</typeparam> /// <typeparam name="T">The modifier Type.</typeparam>
public static void RpcAddModifier<T>(this PlayerControl player) where T : BaseModifier public static void RpcAddModifier<T>(this PlayerControl player) where T : BaseModifier
{ {
if (!ModifierManager.TypeToIdModifiers.TryGetValue(typeof(T), out var id)) var id = ModifierManager.GetModifierId(typeof(T));
if (id == null)
{ {
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
return; return;
} }
player.RpcAddModifier(id); player.RpcAddModifier(id.Value);
} }
/// <summary> /// <summary>
+140 -55
View File
@@ -1,5 +1,6 @@
using MiraAPI.Roles; using MiraAPI.Roles;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using TMPro; using TMPro;
@@ -8,14 +9,35 @@ using Object = UnityEngine.Object;
namespace MiraAPI.Utilities; namespace MiraAPI.Utilities;
/// <summary>
/// A class that contains helper methods.
/// </summary>
public static class Helpers public static class Helpers
{ {
public static readonly ContactFilter2D Filter = ContactFilter2D.CreateLegacyFilter(Constants.NotShipMask, float.MinValue, float.MaxValue); /// <summary>
public static PlainShipRoom GetRoom(Vector3 pos) /// NotShipMask filter for collisions.
/// </summary>
public static readonly ContactFilter2D Filter = ContactFilter2D.CreateLegacyFilter(
Constants.NotShipMask,
float.MinValue,
float.MaxValue);
/// <summary>
/// Get the room at a specific position.
/// </summary>
/// <param name="pos">The position.</param>
/// <returns>The ship room if its found.</returns>
public static PlainShipRoom? GetRoom(Vector3 pos)
{ {
return ShipStatus.Instance.AllRooms.ToList().Find(room => room.roomArea.OverlapPoint(pos)); return ShipStatus.Instance.AllRooms.ToList().Find(room => room.roomArea.OverlapPoint(pos));
} }
/// <summary>
/// Gets a list of dead bodies within a radius.
/// </summary>
/// <param name="source">The source location.</param>
/// <param name="radius">The radius to search in.</param>
/// <returns>A list of dead bodies.</returns>
public static List<DeadBody> GetNearestDeadBodies(Vector2 source, float radius) public static List<DeadBody> GetNearestDeadBodies(Vector2 source, float radius)
{ {
var results = new Il2CppSystem.Collections.Generic.List<Collider2D>(); var results = new Il2CppSystem.Collections.Generic.List<Collider2D>();
@@ -25,7 +47,16 @@ public static class Helpers
.Select(collider2D => collider2D.GetComponent<DeadBody>()).ToList(); .Select(collider2D => collider2D.GetComponent<DeadBody>()).ToList();
} }
public static List<T> GetNearestObjectsOfType<T>(Vector2 source, float radius, string? colliderTag = null) where T : Component /// <summary>
/// Gets a list of objects within a radius.
/// </summary>
/// <param name="source">The source point.</param>
/// <param name="radius">The radius to search in.</param>
/// <param name="colliderTag">An optional collider tag.</param>
/// <typeparam name="T">The type of the object.</typeparam>
/// <returns>A list of objects of type T.</returns>
public static List<T> GetNearestObjectsOfType<T>(Vector2 source, float radius, string? colliderTag = null)
where T : Component
{ {
var results = new Il2CppSystem.Collections.Generic.List<Collider2D>(); var results = new Il2CppSystem.Collections.Generic.List<Collider2D>();
Physics2D.OverlapCircle(source, radius, Filter, results); Physics2D.OverlapCircle(source, radius, Filter, results);
@@ -34,29 +65,49 @@ public static class Helpers
.Select(collider2D => collider2D.GetComponent<T>()).ToList(); .Select(collider2D => collider2D.GetComponent<T>()).ToList();
} }
public static List<PlayerControl> GetClosestPlayersInCircle(Vector2 source, float radius, bool ignoreColliders = true) /// <summary>
/// Gets the closest players to a specific point.
/// </summary>
/// <param name="source">The source point.</param>
/// <param name="radius">The radius to search in.</param>
/// <param name="ignoreColliders">Whether colliders should be ignored.</param>
/// <returns>A list of Player Controls in the radius.</returns>
public static List<PlayerControl> GetClosestPlayersInCircle(
Vector2 source,
float radius,
bool ignoreColliders = true)
{ {
var newList = GetNearestObjectsOfType<PlayerControl>(source, radius); var newList = GetNearestObjectsOfType<PlayerControl>(source, radius);
if (ignoreColliders) if (!ignoreColliders)
{ {
var filteredList = new List<PlayerControl>(); return newList;
foreach (var player in newList)
{
var vector = player.GetTruePosition() - source;
var magnitude = vector.magnitude;
if (!PhysicsHelpers.AnyNonTriggersBetween(source, vector.normalized, magnitude, Constants.ShipAndObjectsMask))
{
filteredList.Add(player);
}
}
return filteredList;
} }
return newList; 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();
} }
public static List<PlayerControl> GetClosestPlayers(PlayerControl source, float distance = 2f, bool ignoreColliders = true, bool ignoreSource = true)
/// <summary>
/// Gets the closest players to a specific player.
/// </summary>
/// <param name="source">The source player.</param>
/// <param name="distance">Distance to search in.</param>
/// <param name="ignoreColliders">Whether to ignore colliders.</param>
/// <param name="ignoreSource">Whether to ignore the source player.</param>
/// <returns>A list of PlayerControls.</returns>
public static List<PlayerControl> GetClosestPlayers(
PlayerControl source,
float distance = 2f,
bool ignoreColliders = true,
bool ignoreSource = true)
{ {
if (!ShipStatus.Instance) if (!ShipStatus.Instance)
{ {
@@ -69,7 +120,17 @@ public static class Helpers
return ignoreSource ? players.Where(plr => plr.PlayerId != source.PlayerId).ToList() : players; return ignoreSource ? players.Where(plr => plr.PlayerId != source.PlayerId).ToList() : players;
} }
public static List<PlayerControl> GetClosestPlayers(Vector2 source, float distance = 2f, bool ignoreColliders = true) /// <summary>
/// Gets the closest players to a specific point.
/// </summary>
/// <param name="source">The source point.</param>
/// <param name="distance">The distance to search in.</param>
/// <param name="ignoreColliders">Whether to ignore colliders.</param>
/// <returns>A list of Player Controls.</returns>
public static List<PlayerControl> GetClosestPlayers(
Vector2 source,
float distance = 2f,
bool ignoreColliders = true)
{ {
if (!ShipStatus.Instance) if (!ShipStatus.Instance)
{ {
@@ -78,51 +139,65 @@ public static class Helpers
List<PlayerControl> outputList = []; List<PlayerControl> outputList = [];
outputList.Clear(); outputList.Clear();
var allPlayers = GameData.Instance.AllPlayers.ToArray().Select(x=>x.Object); var allPlayers = GameData.Instance.AllPlayers.ToArray().Select(x => x.Object);
foreach (var playerControl in allPlayers) outputList.AddRange(
{ from playerControl in allPlayers
if (!playerControl || !playerControl.Collider.enabled) where playerControl && playerControl.Collider.enabled
{ let vector = playerControl.GetTruePosition() - source
continue; let magnitude = vector.magnitude
} where magnitude <= distance && (ignoreColliders || !PhysicsHelpers.AnyNonTriggersBetween(
source,
vector.normalized,
magnitude,
Constants.ShipAndObjectsMask))
select playerControl);
var vector = playerControl.GetTruePosition() - source; outputList.Sort(
var magnitude = vector.magnitude; delegate(PlayerControl a, PlayerControl b)
if (magnitude <= distance && (ignoreColliders || !PhysicsHelpers.AnyNonTriggersBetween(source, vector.normalized, magnitude, Constants.ShipAndObjectsMask)))
{ {
outputList.Add(playerControl); var magnitude2 = (a.GetTruePosition() - source).magnitude;
} var magnitude3 = (b.GetTruePosition() - source).magnitude;
} if (magnitude2 > magnitude3)
{
return 1;
}
outputList.Sort(delegate (PlayerControl a, PlayerControl b) if (magnitude2 < magnitude3)
{ {
var magnitude2 = (a.GetTruePosition() - source).magnitude; return -1;
var magnitude3 = (b.GetTruePosition() - source).magnitude; }
if (magnitude2 > magnitude3)
{ return 0;
return 1; });
}
if (magnitude2 < magnitude3)
{
return -1;
}
return 0;
});
return outputList; return outputList;
} }
public static TextMeshPro CreateTextLabel(string name, Transform parent, /// <summary>
AspectPosition.EdgeAlignments alignment, Vector3 distance, float fontSize = 2f, /// Creates a TextMeshPro object with the specified parameters.
TextAlignmentOptions textAlignment = TextAlignmentOptions.Center) /// </summary>
/// <param name="name">The name of the object.</param>
/// <param name="parent">The object parent.</param>
/// <param name="alignment">The alignment of the TMP object.</param>
/// <param name="distance">The distance from the edge.</param>
/// <param name="fontSize">The font size.</param>
/// <param name="textAlignment">The text alignment.</param>
/// <returns>A new TMP object.</returns>
public static TextMeshPro CreateTextLabel(
string name,
Transform parent,
AspectPosition.EdgeAlignments alignment,
Vector3 distance,
float fontSize = 2f,
TextAlignmentOptions textAlignment = TextAlignmentOptions.Center)
{ {
var textObj = new GameObject(name) var textObj = new GameObject(name)
{ {
transform = transform =
{ {
parent = parent parent = parent,
}, },
layer = LayerMask.NameToLayer("UI") layer = LayerMask.NameToLayer("UI"),
}; };
var textMeshPro = textObj.AddComponent<TextMeshPro>(); var textMeshPro = textObj.AddComponent<TextMeshPro>();
@@ -139,11 +214,21 @@ TextAlignmentOptions textAlignment = TextAlignmentOptions.Center)
return textMeshPro; return textMeshPro;
} }
public static DeadBody GetBodyById(byte id) /// <summary>
/// Gets a DeadBody by its parent ID.
/// </summary>
/// <param name="id">The player ID.</param>
/// <returns>A dead body or null if its not found.</returns>
public static DeadBody? GetBodyById(byte id)
{ {
return Object.FindObjectsOfType<DeadBody>().FirstOrDefault(body => body.ParentId == id); return Object.FindObjectsOfType<DeadBody>().FirstOrDefault(body => body.ParentId == id);
} }
/// <summary>
/// Gets the suffix for a MiraNumberSuffixes enum.
/// </summary>
/// <param name="suffix">The MiraNumberSuffixes enum.</param>
/// <returns>A suffix based on the enum.</returns>
public static string GetSuffix(MiraNumberSuffixes suffix) public static string GetSuffix(MiraNumberSuffixes suffix)
{ {
return suffix switch return suffix switch
@@ -164,9 +249,9 @@ TextAlignmentOptions textAlignment = TextAlignmentOptions.Center)
public static StringBuilder CreateForRole(ICustomRole role) public static StringBuilder CreateForRole(ICustomRole role)
{ {
var taskStringBuilder = new StringBuilder(); var taskStringBuilder = new StringBuilder();
taskStringBuilder.AppendLine($"{role.RoleColor.ToTextColor()}You are a <b>{role.RoleName}.</b></color>"); taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}You are a <b>{role.RoleName}.</b></color>");
taskStringBuilder.Append("<size=70%>"); taskStringBuilder.Append("<size=70%>");
taskStringBuilder.AppendLine($"{role.RoleLongDescription}"); taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription}");
return taskStringBuilder; return taskStringBuilder;
} }
} }