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
25 files changed
+699 -429

No files matched your search

@@ -31,14 +31,13 @@ public class FreezeButton : CustomActionButton<PlayerControl>
Target?.cosmetics.SetOutline(active, new Il2CppSystem.Nullable<Color>(Palette.Blue));
}
public override bool IsTargetValid(PlayerControl target)
public override bool IsTargetValid(PlayerControl? target)
{
return true;
}
public override bool Enabled(RoleBehaviour role)
public override bool Enabled(RoleBehaviour? role)
{
return role is FreezerRole;
}
}
+10 -7
View File
@@ -4,31 +4,34 @@ using UnityEngine;
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 List<IModdedOption> Options { get; } = [];
internal List<IModdedOption> Options { get; } = [];
/// <summary>
/// The name of the group. Visible in options menu.
/// Gets the name of the group. Visible in options menu.
/// </summary>
public abstract string GroupName { get; }
/// <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>
public virtual Func<bool> GroupVisible => () => true;
/// <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>
public virtual Color GroupColor => Color.clear;
/// <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>
public virtual Type AdvancedRole => null;
public virtual Type? AdvancedRole => null;
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)]
public abstract class ModdedOptionAttribute(string title, Type? roleType = null) : Attribute
{
/// <summary>
/// Gets the IModdedOption object that this attribute is attached to.
/// </summary>
public IModdedOption? HolderOption { get; internal set; }
internal IModdedOption? HolderOption { get; set; }
/// <summary>
/// Gets the title of the option.
+16 -17
View File
@@ -1,25 +1,24 @@
using MiraAPI.PluginLoading;
using System;
using System;
using BepInEx.Configuration;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using UnityEngine;
namespace MiraAPI.GameOptions;
public interface IModdedOption
internal interface IModdedOption
{
public uint Id { get; }
public BaseGameSetting Data { get; }
public IMiraPlugin? ParentMod { get; set; }
public Type AdvancedRole { get; set; }
public OptionBehaviour OptionBehaviour { get; }
public string Title { get; }
public StringNames StringName { get; }
public Func<bool> Visible { get; set; }
public ConfigDefinition? ConfigDefinition { get; set; }
public void ValueChanged(OptionBehaviour optionBehaviour);
public OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container);
public abstract float GetFloatData();
public NetData GetNetData();
public void HandleNetData(byte[] data);
uint Id { get; }
string Title { get; }
StringNames StringName { get; }
IMiraPlugin? ParentMod { get; set; }
BaseGameSetting? Data { get; }
Type? AdvancedRole { get; set; }
OptionBehaviour? OptionBehaviour { get; }
Func<bool> Visible { get; set; }
ConfigDefinition? ConfigDefinition { get; set; }
OptionBehaviour? CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container);
float GetFloatData();
NetData GetNetData();
void HandleNetData(byte[] data);
}
+36 -20
View File
@@ -1,18 +1,21 @@
using HarmonyLib;
using MiraAPI.GameOptions.Attributes;
using Reactor.Utilities;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using BepInEx.Configuration;
using HarmonyLib;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities;
using Reactor.Networking.Rpc;
using Reactor.Utilities;
namespace MiraAPI.GameOptions;
/// <summary>
/// Handles modded options.
/// </summary>
public static class ModdedOptionsManager
{
private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = new();
@@ -62,7 +65,11 @@ public static class ModdedOptionsManager
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))
{
@@ -98,7 +105,11 @@ public static class ModdedOptionsManager
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;
@@ -128,8 +139,10 @@ public static class ModdedOptionsManager
// necessary to disable then re-enable this setting
// we dont know how other plugins handle their configs
// 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;
}
@@ -143,10 +156,10 @@ public static class ModdedOptionsManager
option.HandleNetData(netData.Data);
}
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values)
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
{
plugin.PluginConfig.Save();
plugin.PluginConfig.SaveOnConfigSet = true;
plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin];
}
if (LobbyInfoPane.Instance)
@@ -155,23 +168,26 @@ public static class ModdedOptionsManager
}
}
/// <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)
{
var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod().Equals(__originalMethod)).Value;
attribute?.SetValue(value);
var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod() == __originalMethod).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)
{
var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod().Equals(__originalMethod)).Value;
if (attribute == null)
{
return true;
}
var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod() == __originalMethod).Value;
__result = attribute.GetValue();
return false;
}
@@ -58,17 +58,12 @@ public class ModdedEnumOption : ModdedOption<int>
SetValue(BitConverter.ToInt32(data));
}
public override string GetHudStringText()
{
return Title + ": " + Values[Value];
}
public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{
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);
if (!OptionBehaviour)
@@ -80,17 +80,12 @@ public class ModdedNumberOption : ModdedOption<float>
SetValue(BitConverter.ToSingle(data));
}
public override string GetHudStringText()
{
return Title + ": " + Value + Helpers.GetSuffix(SuffixType);
}
public override float GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{
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);
DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(Value), false);
+73 -21
View File
@@ -9,6 +9,10 @@ using UnityEngine;
namespace MiraAPI.GameOptions.OptionTypes;
/// <summary>
/// Represents a modded option.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
public abstract class ModdedOption<T> : IModdedOption
{
private IMiraPlugin? _parentMod;
@@ -18,10 +22,20 @@ public abstract class ModdedOption<T> : IModdedOption
/// </summary>
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>
/// Gets the BaseGameSetting data of the option.
/// </summary>
public BaseGameSetting Data { get; protected init; }
public BaseGameSetting? Data { get; protected init; }
/// <summary>
/// Gets or sets the parent mod of the option.
@@ -51,26 +65,22 @@ public abstract class ModdedOption<T> : IModdedOption
/// <summary>
/// Gets or sets the event that is invoked when the value of the option changes.
/// </summary>
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; }
public Action<T>? ChangedEvent { get; set; }
/// <summary>
/// Gets or sets the visibility of the option.
/// </summary>
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>
/// Gets or sets the config definition of the option.
@@ -87,6 +97,12 @@ public abstract class ModdedOption<T> : IModdedOption
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)
{
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));
}
/// <summary>
/// Sets the value of the option.
/// </summary>
/// <param name="newValue">The new value.</param>
public void SetValue(T newValue)
{
var oldVal = Value;
Value = newValue;
if (!Value.Equals(oldVal) && ChangedEvent != null)
if (Value?.Equals(oldVal) == false)
{
ChangedEvent.Invoke(Value);
ChangedEvent?.Invoke(Value);
}
if (AmongUsClient.Instance.AmHost)
@@ -129,17 +150,48 @@ public abstract class ModdedOption<T> : IModdedOption
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();
/// <summary>
/// Gets the net data of the option.
/// </summary>
/// <returns>A NetData object representing this option's data.</returns>
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 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 OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container);
public abstract string GetHudStringText();
/// <summary>
/// Creates the option behaviour.
/// </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));
}
public override string GetHudStringText()
{
return Title + ": " + Values[Value];
}
public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{
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);
if (!OptionBehaviour)
@@ -47,17 +47,12 @@ public class ModdedToggleOption : ModdedOption<bool>
SetValue(BitConverter.ToBoolean(data));
}
public override string GetHudStringText()
{
return Title + ": " + (Value ? "On" : "Off");
}
public override bool GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{
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);
if (!OptionBehaviour)
+11 -1
View File
@@ -1,7 +1,17 @@
namespace MiraAPI.Hud;
/// <summary>
/// The location of the custom button on the screen.
/// </summary>
public enum ButtonLocation
{
/// <summary>
/// Placed in the bottom left.
/// </summary>
BottomLeft,
BottomRight
/// <summary>
/// Placed in the bottom right.
/// </summary>
BottomRight,
}
+4 -1
View File
@@ -7,10 +7,13 @@ namespace MiraAPI.Hud;
/// A utility class to get the instance of a custom action button.
/// </summary>
/// <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;
/// <summary>
/// Gets or sets instance of the button.
/// </summary>
public static T Instance
{
get => _instance ??= CustomButtonManager.CustomButtons.OfType<T>().Single();
+102 -47
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 Reactor.Utilities;
using Reactor.Utilities.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TMPro;
using UnityEngine;
namespace MiraAPI.Modifiers;
/// <summary>
/// The component for handling modifiers.
/// </summary>
[RegisterInIl2Cpp]
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>();
ActiveModifiers = [];
foreach (var modifier in Modifiers)
{
modifier.OnDeactivate();
}
if (!player.AmOwner)
Modifiers.Clear();
}
private void Start()
{
_player = GetComponent<PlayerControl>();
Modifiers = [];
if (!_player.AmOwner)
{
return;
}
@@ -34,55 +54,56 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
_modifierText.verticalAlignment = VerticalAlignmentOptions.Top;
}
public void FixedUpdate()
private void FixedUpdate()
{
if (ActiveModifiers is null) return;
foreach (var modifier in ActiveModifiers)
foreach (var modifier in Modifiers)
{
if (modifier is null) continue;
modifier.FixedUpdate();
}
}
public void Update()
private void Update()
{
if (ActiveModifiers is null) return;
foreach (var modifier in ActiveModifiers)
foreach (var modifier in Modifiers)
{
if (modifier is null) continue;
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();
if (player.AmOwner && baseModifiers.Any())
if (baseModifiers.Length != 0)
{
var stringBuild = new StringBuilder();
foreach (var mod in baseModifiers)
{
stringBuild.Append($"\n{mod.ModifierName}");
stringBuild.Append(CultureInfo.InvariantCulture, $"\n{mod.ModifierName}");
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}";
}
else if (player.AmOwner && !baseModifiers.Any() && _modifierText.text != string.Empty)
else if (_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)
{
var modifier = ActiveModifiers.Find(x => x.GetType() == type);
var modifier = Modifiers.Find(x => x.GetType() == type);
if (modifier is null)
{
@@ -93,14 +114,22 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
RemoveModifier(modifier);
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <typeparam name="T">The modifier type.</typeparam>
public void RemoveModifier<T>() where T : BaseModifier
{
RemoveModifier(typeof(T));
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="modifierId">The modifier ID.</param>
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)
{
@@ -111,37 +140,54 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
RemoveModifier(modifier);
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="modifier">The modifier object.</param>
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.");
return;
}
modifier.OnDeactivate();
ActiveModifiers.Remove(modifier);
Modifiers.Remove(modifier);
if (player.AmOwner)
if (_player.AmOwner)
{
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}!");
return null;
}
ActiveModifiers.Add(modifier);
modifier.Player = player;
modifier.ModifierId = ModifierManager.TypeToIdModifiers[modifier.GetType()];
var modifierId = ModifierManager.GetModifierId(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();
if (!player.AmOwner)
if (!_player.AmOwner)
{
return modifier;
}
@@ -156,23 +202,27 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
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.");
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}!");
return null;
}
var modifier = (BaseModifier)Activator.CreateInstance(type);
if (modifier is null)
if (Activator.CreateInstance(type) is not BaseModifier modifier)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {type.Name} because it is null.");
return null;
@@ -183,7 +233,12 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
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;
}
+47 -32
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.Roles;
using MiraAPI.Utilities;
using Reactor.Networking.Rpc;
using Reactor.Utilities.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using Random = System.Random;
namespace MiraAPI.Modifiers;
/// <summary>
/// The manager for handling modifiers.
/// </summary>
public static class ModifierManager
{
public static readonly Dictionary<uint, Type> IdToTypeModifiers = [];
public static readonly Dictionary<Type, uint> TypeToIdModifiers = [];
private static readonly Dictionary<uint, Type> IdToTypeModifierMap = [];
private static readonly Dictionary<Type, uint> TypeToIdModifierMap = [];
private static uint _nextId;
@@ -25,6 +28,26 @@ public static class ModifierManager
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)
{
if (!typeof(BaseModifier).IsAssignableFrom(modifierType))
@@ -32,8 +55,8 @@ public static class ModifierManager
return;
}
IdToTypeModifiers.Add(GetNextId(), modifierType);
TypeToIdModifiers.Add(modifierType, _nextId);
IdToTypeModifierMap.Add(GetNextId(), modifierType);
TypeToIdModifierMap.Add(modifierType, _nextId);
}
internal static void AssignModifiers(List<PlayerControl> plrs)
@@ -42,23 +65,21 @@ public static class ModifierManager
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 (mod is null)
if (Activator.CreateInstance(modifier.Value) is not GameModifier mod)
{
Logger<MiraApiPlugin>.Error($"Failed to create instance of {modifier.Value.Name}");
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);
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 chance = mod.GetAssignmentChance();
@@ -69,7 +90,7 @@ public static class ModifierManager
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 mod = (GameModifier)Activator.CreateInstance(IdToTypeModifiers[id]);
if (mod is null)
if (Activator.CreateInstance(IdToTypeModifierMap[id]) is not GameModifier mod)
{
Logger<MiraApiPlugin>.Error($"Failed to create instance of {IdToTypeModifiers[id].Name}");
Logger<MiraApiPlugin>.Error($"Failed to create instance of {IdToTypeModifierMap[id].Name}");
continue;
}
if (!plrs.Exists(x=>IsGameModifierValid(x, mod, id)))
if (!plrs.Exists(x => IsGameModifierValid(x, mod, id)))
{
shuffledModifiers.RemoveAt(0);
continue;
@@ -100,7 +119,7 @@ public static class ModifierManager
var plr = plrs.Random();
if (!IsGameModifierValid(plr, mod, id))
if (plr == null || !IsGameModifierValid(plr, mod, id))
{
continue;
}
@@ -126,7 +145,7 @@ public static class ModifierManager
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)
@@ -144,15 +163,11 @@ public static class ModifierManager
continue;
}
foreach (var modifier in modifierComponent.ActiveModifiers)
{
modifier.OnDeactivate();
}
modifierComponent.ActiveModifiers.Clear();
modifierComponent.ClearModifiers();
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.");
continue;
@@ -167,16 +182,16 @@ public static class ModifierManager
{
var bytes = new List<byte>();
var modifierComponent = player.GetComponent<ModifierComponent>();
if (!modifierComponent)
if (modifierComponent == null)
{
return new NetData(player.PlayerId, []);
}
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;
/// <summary>
/// Patches the GameOptionsMenu to add custom options.
/// </summary>
[HarmonyPatch(typeof(GameOptionsMenu))]
public static class GameOptionsMenuPatch
{
/// <summary>
/// Update patch for the GameOptionsMenu.
/// </summary>
/// <param name="__instance">The GameOptionsMenu instance.</param>
[HarmonyPostfix]
[HarmonyPatch(nameof(GameOptionsMenu.Update))]
public static void UpdatePatch(GameOptionsMenu __instance)
{
if (GameSettingMenuPatches.CurrentSelectedMod == 0)
if (GameSettingMenuPatches.SelectedModIdx == 0)
{
return;
}
@@ -71,7 +77,7 @@ public static class GameOptionsMenuPatch
[HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))]
public static bool SettingsPatch(GameOptionsMenu __instance)
{
if (GameSettingMenuPatches.CurrentSelectedMod == 0)
if (GameSettingMenuPatches.SelectedModIdx == 0)
{
return true;
}
@@ -12,17 +12,30 @@ using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Options;
/// <summary>
/// Patches for the <see cref="GameSettingMenu"/> to add support for custom options.
/// </summary>
[HarmonyPatch(typeof(GameSettingMenu))]
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; }
private static TextMeshPro? _text;
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]
[HarmonyPatch(nameof(GameSettingMenu.Start))]
public static void StartPrefix(GameSettingMenu __instance)
@@ -56,11 +69,12 @@ public static class GameSettingMenuPatches
passiveButton.OnClick = new ButtonClickedEvent();
passiveButton.OnClick.AddListener((UnityAction)(() =>
{
if (CurrentSelectedMod != MiraPluginManager.Instance.RegisteredPlugins.Count)
SelectedModIdx += 1;
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length)
{
CurrentSelectedMod += 1;
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
SelectedModIdx = 0;
}
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
}));
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.gameObject.GetComponent<PassiveButton>().OnClick.AddListener((UnityAction)(() =>
{
if (CurrentSelectedMod != 0)
SelectedModIdx -= 1;
if (SelectedModIdx < 0)
{
CurrentSelectedMod -= 1;
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length;
}
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
}));
}
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.fontSizeMax = 3.2f;
@@ -88,22 +103,17 @@ public static class GameSettingMenuPatches
else if (_text is not null)
{
_text.fontSizeMax = 2.3f;
SelectedMod = MiraPluginManager.Instance.RegisteredPlugins.ElementAt(CurrentSelectedMod - 1).Value;
if (SelectedMod == null)
{
CurrentSelectedMod = 0;
UpdateText(menu, settings, roles);
}
SelectedMod = MiraPluginManager.Instance.RegisteredPlugins()[SelectedModIdx - 1];
var name = SelectedMod?.MiraPlugin.OptionsTitleText;
_text.text = name?[..Math.Min(name.Length, 25)];
var name = SelectedMod.MiraPlugin.OptionsTitleText;
_text.text = name[..Math.Min(name.Length, 25)];
}
menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos;
menu.GameSettingsButton.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)
{
@@ -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)
{
+120 -122
View File
@@ -16,10 +16,9 @@ namespace MiraAPI.Patches.Options;
[HarmonyPatch(typeof(LobbyViewSettingsPane))]
public static class LobbyViewPanePatches
{
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]
@@ -48,7 +47,7 @@ public static class LobbyViewPanePatches
passiveButton.OnClick.AddListener((UnityAction)(() =>
{
CurrentSelectedMod += 1;
if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins.Count)
if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins().Length)
{
CurrentSelectedMod = 0;
}
@@ -71,7 +70,7 @@ public static class LobbyViewPanePatches
CurrentSelectedMod -= 1;
if (CurrentSelectedMod < 0)
{
CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins.Count;
CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins().Length;
}
__instance.RefreshTab();
__instance.scrollBar.ScrollToTop();
@@ -185,146 +184,145 @@ public static class LobbyViewPanePatches
private static void DrawRolesTab(LobbyViewSettingsPane instance)
{
if (SelectedMod == null)
{
return;
}
if (SelectedMod == null)
{
return;
}
var num = 0.95f;
var num2 = -6.53f;
var categoryHeaderMasked =
Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
categoryHeaderMasked.SetHeader(StringNames.RoleQuotaLabel, 61);
categoryHeaderMasked.transform.localScale = Vector3.one;
categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, 1.26f, -2f);
instance.settingsInfo.Add(categoryHeaderMasked.gameObject);
var num = 0.95f;
var num2 = -6.53f;
var categoryHeaderMasked =
Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
categoryHeaderMasked.SetHeader(StringNames.RoleQuotaLabel, 61);
categoryHeaderMasked.transform.localScale = Vector3.one;
categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, 1.26f, -2f);
instance.settingsInfo.Add(categoryHeaderMasked.gameObject);
var list = new List<Type>();
var list = new List<Type>();
foreach (var team in Enum.GetValues<ModdedRoleTeams>())
{
var filteredRoles = SelectedMod.CustomRoles.Values
.OfType<ICustomRole>()
.Where(x => !x.HideSettings && x.Team == team).ToList();
foreach (var team in Enum.GetValues<ModdedRoleTeams>())
{
var filteredRoles = SelectedMod.CustomRoles.Values
.OfType<ICustomRole>()
.Where(x => !x.HideSettings && x.Team == team).ToList();
if (filteredRoles.Count == 0)
{
continue;
}
if (filteredRoles.Count == 0)
{
continue;
}
var categoryHeaderRoleVariant =
Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true);
var categoryHeaderRoleVariant =
Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true);
switch (team)
{
case ModdedRoleTeams.Crewmate:
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
break;
case ModdedRoleTeams.Impostor:
categoryHeaderRoleVariant.SetHeader(StringNames.ImpostorRolesHeader, 61);
break;
case ModdedRoleTeams.Neutral:
default:
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
categoryHeaderRoleVariant.Title.text = team + " Roles";
categoryHeaderRoleVariant.Background.color = Color.gray;
categoryHeaderRoleVariant.Title.color = Color.white;
break;
}
switch (team)
{
case ModdedRoleTeams.Crewmate:
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
break;
case ModdedRoleTeams.Impostor:
categoryHeaderRoleVariant.SetHeader(StringNames.ImpostorRolesHeader, 61);
break;
default:
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
categoryHeaderRoleVariant.Title.text = team + " Roles";
categoryHeaderRoleVariant.Background.color = Color.gray;
categoryHeaderRoleVariant.Title.color = Color.white;
break;
}
categoryHeaderRoleVariant.transform.localScale = Vector3.one;
categoryHeaderRoleVariant.transform.localPosition = new Vector3(0.09f, num, -2f);
instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject);
num -= 0.696f;
categoryHeaderRoleVariant.transform.localScale = Vector3.one;
categoryHeaderRoleVariant.transform.localPosition = new Vector3(0.09f, num, -2f);
instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject);
num -= 0.696f;
foreach (var customRole in filteredRoles)
{
var roleBehaviour = customRole as RoleBehaviour;
if (roleBehaviour == null)
{
continue;
}
foreach (var customRole in filteredRoles)
{
var roleBehaviour = customRole as RoleBehaviour;
if (roleBehaviour == null)
{
continue;
}
var chancePerGame =
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleBehaviour.Role);
var numPerGame =
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role);
var chancePerGame =
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleBehaviour.Role);
var numPerGame =
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role);
var flag = numPerGame == 0;
var flag = numPerGame == 0;
var viewSettingsInfoPanelRoleVariant =
Object.Instantiate(
instance.infoPanelRoleOrigin, instance.settingsContainer, true);
viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one;
viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f);
var viewSettingsInfoPanelRoleVariant =
Object.Instantiate(
instance.infoPanelRoleOrigin, instance.settingsContainer, true);
viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one;
viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f);
var advancedRoleOptions = SelectedMod.Options
.Where(x => x.AdvancedRole == customRole.GetType())
.ToList();
var advancedRoleOptions = SelectedMod.Options
.Where(x => x.AdvancedRole == customRole.GetType())
.ToList();
if (!flag && advancedRoleOptions.Count > 0)
{
list.Add(customRole.GetType());
}
if (!flag && advancedRoleOptions.Count > 0)
{
list.Add(customRole.GetType());
}
viewSettingsInfoPanelRoleVariant.SetInfo(roleBehaviour.NiceName, numPerGame, chancePerGame,
61, customRole.RoleColor, customRole.Icon.LoadAsset(), true);
viewSettingsInfoPanelRoleVariant.SetInfo(roleBehaviour.NiceName, numPerGame, chancePerGame,
61, customRole.RoleColor, customRole.Icon.LoadAsset(), true);
viewSettingsInfoPanelRoleVariant.titleText.color =
viewSettingsInfoPanelRoleVariant.chanceTitle.color =
viewSettingsInfoPanelRoleVariant.chanceBackground.color =
viewSettingsInfoPanelRoleVariant.background.color =
customRole.RoleColor.GetAlternateColor();
instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject);
num -= 0.664f;
}
viewSettingsInfoPanelRoleVariant.titleText.color =
viewSettingsInfoPanelRoleVariant.chanceTitle.color =
viewSettingsInfoPanelRoleVariant.chanceBackground.color =
viewSettingsInfoPanelRoleVariant.background.color =
customRole.RoleColor.GetAlternateColor();
instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject);
num -= 0.664f;
}
}
}
if (list.Count > 0)
{
var categoryHeaderMasked2 =
Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
categoryHeaderMasked2.SetHeader(StringNames.RoleSettingsLabel, 61);
categoryHeaderMasked2.transform.localScale = Vector3.one;
categoryHeaderMasked2.transform.localPosition = new Vector3(-9.77f, num, -2f);
instance.settingsInfo.Add(categoryHeaderMasked2.gameObject);
num -= 1.7f;
var num3 = 0f;
for (var k = 0; k < list.Count; k++)
{
float num4;
if (k % 2 == 0)
{
num4 = -5.8f;
if (k > 0)
{
num -= num3 + 0.59f;
num3 = 0f;
}
}
else
{
num4 = 0.14999962f;
}
if (list.Count > 0)
{
var categoryHeaderMasked2 =
Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
categoryHeaderMasked2.SetHeader(StringNames.RoleSettingsLabel, 61);
categoryHeaderMasked2.transform.localScale = Vector3.one;
categoryHeaderMasked2.transform.localPosition = new Vector3(-9.77f, num, -2f);
instance.settingsInfo.Add(categoryHeaderMasked2.gameObject);
num -= 1.7f;
var num3 = 0f;
for (var k = 0; k < list.Count; k++)
{
float num4;
if (k % 2 == 0)
{
num4 = -5.8f;
if (k > 0)
{
num -= num3 + 0.59f;
num3 = 0f;
}
}
else
{
num4 = 0.14999962f;
}
var advancedRoleViewPanel =
Object.Instantiate(instance.advancedRolePanelOrigin, instance.settingsContainer, true);
advancedRoleViewPanel.transform.localScale = Vector3.one;
advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f);
var num5 = SetUpAdvancedRoleViewPanel(advancedRoleViewPanel, list[k], 0.59f, 61);
var advancedRoleViewPanel =
Object.Instantiate(instance.advancedRolePanelOrigin, instance.settingsContainer, true);
advancedRoleViewPanel.transform.localScale = Vector3.one;
advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f);
var num5 = SetUpAdvancedRoleViewPanel(advancedRoleViewPanel, list[k], 0.59f, 61);
if (num5 > num3)
{
num3 = num5;
}
if (num5 > num3)
{
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)
@@ -30,7 +30,7 @@ public static class RoleSettingMenuPatches
var maskBg = __instance.scrollBar.transform.FindChild("MaskBg");
var hitbox = __instance.scrollBar.transform.FindChild("Hitbox");
if (GameSettingMenuPatches.CurrentSelectedMod == 0)
if (GameSettingMenuPatches.SelectedModIdx == 0)
{
__instance.AllButton.transform.parent.gameObject.SetActive(true);
__instance.AllButton.gameObject.SetActive(true);
@@ -127,7 +127,7 @@ public static class RoleSettingMenuPatches
[HarmonyPatch(nameof(RolesSettingsMenu.Update))]
public static bool UpdatePatch()
{
return GameSettingMenuPatches.CurrentSelectedMod == 0;
return GameSettingMenuPatches.SelectedModIdx == 0;
}
[HarmonyPostfix]
+24 -6
View File
@@ -1,14 +1,29 @@
using HarmonyLib;
using System.Globalization;
using HarmonyLib;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Patches.Options;
/// <summary>
/// Patches for the various game settings.
/// </summary>
[HarmonyPatch]
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]
[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 suffix = (MiraNumberSuffixes)__instance.SuffixType;
@@ -31,11 +46,14 @@ public class SettingPatches
}
else
{
result = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameSecondsAbbrev, (Il2CppSystem.Object[])(
[
value.ToString(__instance.FormatString)
]));
result = DestroyableSingleton<TranslationController>.Instance.GetString(
StringNames.GameSecondsAbbrev,
(Il2CppSystem.Object[])
[
value.ToString(__instance.FormatString, CultureInfo.InvariantCulture)
]);
}
__result = result;
return false;
}
+1 -2
View File
@@ -2,7 +2,6 @@
using HarmonyLib;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppSystem.Text;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
@@ -63,7 +62,7 @@ public static class TaskAdderPatch
_rolesFolder.FolderName = "Roles";
_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);
newFolder.FolderName = newFolder.name = plugin.PluginInfo.Metadata.Name;
+37 -9
View File
@@ -6,16 +6,44 @@ using System.Collections.Generic;
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;
public IMiraPlugin MiraPlugin { get; set; } = miraPlugin;
public PluginInfo PluginInfo { get; set; } = info;
public ConfigFile PluginConfig { get; set; } = miraPlugin.GetConfigFile();
internal MiraPluginInfo(IMiraPlugin miraPlugin, PluginInfo info)
{
MiraPlugin = miraPlugin;
PluginConfig = miraPlugin.GetConfigFile();
PluginInfo = info;
PluginId = info.Metadata.GUID;
}
public readonly List<AbstractOptionGroup> OptionGroups = [];
public readonly List<IModdedOption> Options = [];
internal List<AbstractOptionGroup> OptionGroups { get; } = [];
public readonly Dictionary<ushort, RoleBehaviour> CustomRoles = [];
internal readonly Dictionary<int, CustomGameMode> GameModes = [];
internal List<IModdedOption> Options { get; } = [];
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; }
}
+16 -22
View File
@@ -1,43 +1,39 @@
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.Attributes;
using MiraAPI.Hud;
using MiraAPI.Modifiers;
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 Reactor.Networking;
using Reactor.Utilities;
namespace MiraAPI.PluginLoading;
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
{
get => _instance ??= new MiraPluginManager();
private set => _instance = value;
}
public static MiraPluginManager Instance { get; private set; } = new();
internal void Initialize()
{
Instance = this;
IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, assembly, plugin) =>
{
if (!plugin.GetType().GetInterfaces().Contains(typeof(IMiraPlugin)))
if (plugin is not IMiraPlugin miraPlugin)
{
return;
}
var info = new MiraPluginInfo(plugin as IMiraPlugin, pluginInfo);
var info = new MiraPluginInfo(miraPlugin, pluginInfo);
RegisterModifierAttribute(assembly);
RegisterAllOptions(assembly, info);
@@ -47,7 +43,7 @@ internal class MiraPluginManager
RegisterColorClasses(assembly);
RegisteredPlugins.Add(assembly, info);
_registeredPlugins.Add(assembly, info);
Logger<MiraApiPlugin>.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API.");
};
@@ -55,7 +51,7 @@ internal class MiraPluginManager
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)
@@ -139,10 +135,9 @@ internal class MiraPluginManager
continue;
}
var color = (CustomColor)property.GetValue(null);
if (color == null)
if (property.GetValue(null) is not CustomColor color)
{
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;
}
@@ -163,7 +158,6 @@ internal class MiraPluginManager
}
}
private static void RegisterButtonAttribute(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
+3 -3
View File
@@ -122,7 +122,7 @@ public static class CustomRoleManager
/// <returns>A MiraPluginInfo object representing the parent mod of the role.</returns>
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>
@@ -197,7 +197,7 @@ public static class CustomRoleManager
// we dont know how other plugins handle their configs
// this way, all the options are saved at once, instead of one by one
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);
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.SaveOnConfigSet = oldConfigSetting[plugin];
+13 -10
View File
@@ -99,7 +99,7 @@ public static class Extensions
if (current.Count > 0)
{
chunks.Enqueue(current.ToArray());
chunks.Enqueue([.. current]);
}
return chunks;
@@ -112,7 +112,7 @@ public static class Extensions
/// <returns>True if the OptionBehaviour is for a custom options, false otherwise.</returns>
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 = [];
@@ -208,13 +208,15 @@ public static class Extensions
/// <typeparam name="T">The Type of the Modifier.</typeparam>
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.");
return;
}
player.RpcRemoveModifier(id);
player.RpcRemoveModifier(id.Value);
}
/// <summary>
@@ -225,13 +227,13 @@ public static class Extensions
[MethodRpc((uint)MiraRpc.AddModifier)]
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;
}
Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
target.GetModifierComponent()?.AddModifier(type);
}
/// <summary>
@@ -241,13 +243,14 @@ public static class Extensions
/// <typeparam name="T">The modifier Type.</typeparam>
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.");
return;
}
player.RpcAddModifier(id);
player.RpcAddModifier(id.Value);
}
/// <summary>
+140 -55
View File
@@ -1,5 +1,6 @@
using MiraAPI.Roles;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using TMPro;
@@ -8,14 +9,35 @@ using Object = UnityEngine.Object;
namespace MiraAPI.Utilities;
/// <summary>
/// A class that contains helper methods.
/// </summary>
public static class Helpers
{
public static readonly ContactFilter2D Filter = ContactFilter2D.CreateLegacyFilter(Constants.NotShipMask, float.MinValue, float.MaxValue);
public static PlainShipRoom GetRoom(Vector3 pos)
/// <summary>
/// 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));
}
/// <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)
{
var results = new Il2CppSystem.Collections.Generic.List<Collider2D>();
@@ -25,7 +47,16 @@ public static class Helpers
.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>();
Physics2D.OverlapCircle(source, radius, Filter, results);
@@ -34,29 +65,49 @@ public static class Helpers
.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);
if (ignoreColliders)
if (!ignoreColliders)
{
var filteredList = new List<PlayerControl>();
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 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)
{
@@ -69,7 +120,17 @@ public static class Helpers
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)
{
@@ -78,51 +139,65 @@ public static class Helpers
List<PlayerControl> outputList = [];
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)
{
if (!playerControl || !playerControl.Collider.enabled)
{
continue;
}
outputList.AddRange(
from playerControl in allPlayers
where playerControl && playerControl.Collider.enabled
let vector = playerControl.GetTruePosition() - source
let magnitude = vector.magnitude
where magnitude <= distance && (ignoreColliders || !PhysicsHelpers.AnyNonTriggersBetween(
source,
vector.normalized,
magnitude,
Constants.ShipAndObjectsMask))
select playerControl);
var vector = playerControl.GetTruePosition() - source;
var magnitude = vector.magnitude;
if (magnitude <= distance && (ignoreColliders || !PhysicsHelpers.AnyNonTriggersBetween(source, vector.normalized, magnitude, Constants.ShipAndObjectsMask)))
outputList.Sort(
delegate(PlayerControl a, PlayerControl b)
{
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)
{
var magnitude2 = (a.GetTruePosition() - source).magnitude;
var magnitude3 = (b.GetTruePosition() - source).magnitude;
if (magnitude2 > magnitude3)
{
return 1;
}
if (magnitude2 < magnitude3)
{
return -1;
}
return 0;
});
if (magnitude2 < magnitude3)
{
return -1;
}
return 0;
});
return outputList;
}
public static TextMeshPro CreateTextLabel(string name, Transform parent,
AspectPosition.EdgeAlignments alignment, Vector3 distance, float fontSize = 2f,
TextAlignmentOptions textAlignment = TextAlignmentOptions.Center)
/// <summary>
/// Creates a TextMeshPro object with the specified parameters.
/// </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)
{
transform =
{
parent = parent
parent = parent,
},
layer = LayerMask.NameToLayer("UI")
layer = LayerMask.NameToLayer("UI"),
};
var textMeshPro = textObj.AddComponent<TextMeshPro>();
@@ -139,11 +214,21 @@ TextAlignmentOptions textAlignment = TextAlignmentOptions.Center)
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);
}
/// <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)
{
return suffix switch
@@ -164,9 +249,9 @@ TextAlignmentOptions textAlignment = TextAlignmentOptions.Center)
public static StringBuilder CreateForRole(ICustomRole role)
{
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.AppendLine($"{role.RoleLongDescription}");
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription}");
return taskStringBuilder;
}
}