Change options system to force all options into groups

This commit is contained in:
XtraCube committed 2024-08-22 13:08:05 -04:00
1 parent f9a587257b
commit 382fabb4bb
16 files changed
+163 -190

No files matched your search

@@ -3,7 +3,7 @@ using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.Example;
namespace MiraAPI.Example.Buttons;
[RegisterButton]
public class ExampleButton : CustomActionButton
-19
View File
@@ -1,19 +0,0 @@
using System;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.GameOptions.OptionTypes;
namespace MiraAPI.Example;
public class CustomRoleSettings : IModdedOptionGroup
{
public string GroupName => "Custom Role";
public Type AdvancedRole => typeof(CustomRole);
[ModdedNumberOption("amogus", min: 3, max: 9)] public float Amogus { get; set; } = 1;
[ModdedEnumOption("dwayne the rock johnson", typeof(TestEnum))] public TestEnum DwayneTheRockJohnson { get; set; } = TestEnum.Hello;
public ModdedToggleOption TestToggle { get; } = new ("test toggle", false);
}
@@ -2,7 +2,7 @@
using MiraAPI.GameOptions.Attributes;
using UnityEngine;
namespace MiraAPI.Example;
namespace MiraAPI.Example.Options;
public class ExampleOptions : IModdedOptionGroup
{
@@ -1,7 +1,7 @@
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.OptionTypes;
namespace MiraAPI.Example;
namespace MiraAPI.Example.Options;
public class ExampleOptions2 : IModdedOptionGroup
{
@@ -0,0 +1,17 @@
using System;
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.GameOptions.OptionTypes;
namespace MiraAPI.Example.Options.Roles;
public class CustomRole2Settings : IModdedOptionGroup
{
public string GroupName => "Custom Role";
public Type AdvancedRole => typeof(CustomRole2);
[ModdedToggleOption("Teleportation ability")] public bool Teleport { get; set; } = true;
}
@@ -0,0 +1,17 @@
using System;
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.GameOptions.OptionTypes;
namespace MiraAPI.Example.Options.Roles;
public class CustomRoleSettings : IModdedOptionGroup
{
public string GroupName => "Custom Role";
public Type AdvancedRole => typeof(CustomRole);
[ModdedNumberOption("Testing level", min: 3, max: 9)] public float TestingLevel { get; set; } = 4;
}
@@ -1,9 +1,8 @@
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Roles;
using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example;
namespace MiraAPI.Example.Roles;
[RegisterCustomRole]
public class CustomRole : ImpostorRole, ICustomRole
@@ -14,7 +13,5 @@ public class CustomRole : ImpostorRole, ICustomRole
public Color RoleColor => Palette.Orange;
public ModdedRoleTeams Team => ModdedRoleTeams.Impostor;
public LoadableAsset<Sprite> OptionsScreenshot => MiraAssets.Banner;
[ModdedNumberOption("Testing level", min: 3, max: 9, roleType: typeof(CustomRole))] public float TestingLevel { get; set; } = 4;
}
@@ -1,9 +1,8 @@
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Roles;
using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example;
namespace MiraAPI.Example.Roles;
[RegisterCustomRole]
public class CustomRole2 : CrewmateRole, ICustomRole
@@ -14,7 +13,5 @@ public class CustomRole2 : CrewmateRole, ICustomRole
public Color RoleColor => Palette.AcceptedGreen;
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
public LoadableAsset<Sprite> OptionsScreenshot => MiraAssets.Banner;
[ModdedToggleOption("Teleportation ability", roleType: typeof(CustomRole2))] public bool Teleport { get; set; } = true;
}
@@ -2,7 +2,7 @@
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example;
namespace MiraAPI.Example.Roles;
[RegisterCustomRole]
public class CustomRole3 : CrewmateRole, ICustomRole
@@ -1,9 +0,0 @@
using MiraAPI.GameOptions.Attributes;
namespace MiraAPI.Example;
public class SomeRandomNonGroupClass
{
[ModdedStringOption("Yeah, idk", ["Idk 1", "idk 2", "idk 3", "idk 4"])] public static int YeaIdk { get; set; } = 3;
[ModdedNumberOption("Aw man", min: 45, max: 95)] public float SussyLevel { get; set; } = 63;
}
-1
View File
@@ -9,7 +9,6 @@ public interface IModdedOption
{
public uint Id { get; }
public BaseGameSetting Data { get; }
public bool HasGroup { get; set; }
public IMiraPlugin ParentMod { get; set; }
public Type AdvancedRole { get; set; }
public OptionBehaviour OptionBehaviour { get; protected set; }
+1 -1
View File
@@ -9,7 +9,7 @@ public class ModdedGroupSingleton<T> where T : IModdedOptionGroup
public static T Instance
{
get => _instance ??= ModdedOptionsManager.Groups.OfType<T>().Single();
get => _instance ??= ModdedOptionsManager.Groups.Keys.OfType<T>().Single();
set
{
if (_instance != null)
+98 -56
View File
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using Reactor.Networking.Rpc;
namespace MiraAPI.GameOptions;
@@ -13,80 +14,121 @@ namespace MiraAPI.GameOptions;
public class ModdedOptionsManager
{
private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = new();
private static readonly Dictionary<Type, IModdedOptionGroup> TypeToGroup = new();
public static readonly Dictionary<uint, IModdedOption> ModdedOptions = new();
public static readonly Dictionary<IModdedOptionGroup, List<IModdedOption>> GroupedOptions = new();
public static readonly Dictionary<IModdedOptionGroup, List<IModdedOption>> Groups = new();
public static readonly List<IModdedOptionGroup> Groups = [];
public static readonly Dictionary<Type, IModdedOptionGroup> TypeToGroup = new();
public static uint NextId => _nextId++;
private static uint _nextId = 1;
internal static bool RegisterGroup(Type type, MiraPluginInfo pluginInfo)
{
var group = (IModdedOptionGroup)Activator.CreateInstance(type);
if (group == null)
{
Logger<MiraApiPlugin>.Error($"Failed to create group from {type.Name}");
return false;
}
internal static IModdedOption RegisterOption(Type type, ModdedOptionAttribute attribute, PropertyInfo property)
if (TypeToGroup.ContainsKey(type))
{
Logger<MiraApiPlugin>.Error($"Group {type.Name} already exists.");
return false;
}
Groups.Add(group, []);
TypeToGroup.Add(type, group);
pluginInfo.OptionGroups.Add(group);
return true;
}
internal static void RegisterPropertyOption(Type type, PropertyInfo property, MiraPluginInfo pluginInfo)
{
if (!TypeToGroup.TryGetValue(type, out var group))
{
Logger<MiraApiPlugin>.Error($"Failed to get group for {type.Name}");
return;
}
var option = (IModdedOption) property.GetValue(group);
if (option == null)
{
Logger<MiraApiPlugin>.Error($"Failed to get option for {property.Name}");
return;
}
option.ParentMod = pluginInfo.MiraPlugin;
option.AdvancedRole = group.AdvancedRole;
pluginInfo.Options.Add(option);
Groups[group].Add(option);
}
internal static void RegisterAttributeOption(Type type, ModdedOptionAttribute attribute, PropertyInfo property, MiraPluginInfo pluginInfo)
{
if (OptionAttributes.ContainsKey(property))
{
return null;
Logger<MiraApiPlugin>.Error($"Property {property.Name} already has an attribute registered.");
return;
}
object newObj = Activator.CreateInstance(type);
IModdedOption result;
try
{
result = attribute.CreateOption(property.GetValue(newObj), property);
}
catch (Exception e)
if (!TypeToGroup.TryGetValue(type, out var group))
{
Logger<MiraApiPlugin>.Error(e);
return null;
Logger<MiraApiPlugin>.Error($"Failed to get group for {type.Name}");
return;
}
if (result != null)
var option = attribute.CreateOption(property.GetValue(group), property);
if (option == null)
{
var setterOriginal = property.GetSetMethod();
var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch));
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch));
var getterOriginal = property.GetGetMethod();
var getterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyGetterPatch));
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(getterOriginal, prefix: new HarmonyMethod(getterPatch));
attribute.HolderOption = result;
OptionAttributes.Add(property, attribute);
if (TypeToGroup.ContainsKey(type))
{
Logger<MiraApiPlugin>.Error($"Grouping {attribute.Title} with {TypeToGroup[type].GroupName}");
GroupedOptions[TypeToGroup[type]].Add(result);
result.HasGroup = true;
result.AdvancedRole = TypeToGroup[type].AdvancedRole;
}
Logger<MiraApiPlugin>.Error($"Failed to get option for {property.Name}");
return;
}
var setterOriginal = property.GetSetMethod();
var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch));
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch));
return result;
var getterOriginal = property.GetGetMethod();
var getterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyGetterPatch));
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(getterOriginal, prefix: new HarmonyMethod(getterPatch));
OptionAttributes.Add(property, attribute);
attribute.HolderOption = option;
option.ParentMod = pluginInfo.MiraPlugin;
option.AdvancedRole = group.AdvancedRole;
pluginInfo.Options.Add(option);
Groups[group].Add(option);
}
internal static void SyncAllOptions(int targetId=-1)
{
List<NetData> data = [];
int count = 0;
foreach (var option in ModdedOptions.Values)
var count = 0;
foreach (var netData in ModdedOptions.Values.Select(option => option.GetNetData()))
{
var netData = option.GetNetData();
data.Add(netData);
count += netData.GetLength();
if (count > 1000)
if (count <= 1000)
{
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
data.Clear();
count = 0;
continue;
}
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
data.Clear();
count = 0;
}
if (data.Count > 0)
{
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
@@ -98,11 +140,13 @@ public class ModdedOptionsManager
{
foreach (var netData in data)
{
if (ModdedOptions.TryGetValue(netData.Id, out var option))
if (!ModdedOptions.TryGetValue(netData.Id, out var option))
{
Logger<MiraApiPlugin>.Error("Handling option " + option.Title);
option.HandleNetData(netData.Data);
continue;
}
Logger<MiraApiPlugin>.Error("Handling option " + option.Title);
option.HandleNetData(netData.Data);
}
}
@@ -111,21 +155,19 @@ public class ModdedOptionsManager
{
ModdedOptionAttribute attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod().Equals(__originalMethod)).Value;
if (attribute != null)
{
attribute.SetValue(value);
}
attribute?.SetValue(value);
}
public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result)
{
ModdedOptionAttribute attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod().Equals(__originalMethod)).Value;
if (attribute != null)
if (attribute == null)
{
__result = attribute.GetValue();
return false;
return true;
}
return true;
__result = attribute.GetValue();
return false;
}
}
@@ -12,7 +12,6 @@ public abstract class ModdedOption<T> : IModdedOption
{
public uint Id { get; }
public BaseGameSetting Data { get; protected init; }
public bool HasGroup { get; set; } = false;
public IMiraPlugin ParentMod { get; set; } = null;
public T Value { get; protected set; }
public T DefaultValue { get; init; }
@@ -41,7 +41,7 @@ public static class GameOptionsMenuPatch
categoryHeaderMasked.transform.localPosition = new Vector3(-0.903f, num, -2f);
num -= 0.63f;
var filteredOpts = ModdedOptionsManager.GroupedOptions[group].Where(x=>x.Visible.Invoke()).ToList();
var filteredOpts = ModdedOptionsManager.Groups[group].Where(x=>x.Visible.Invoke()).ToList();
foreach (var opt in filteredOpts)
{
@@ -78,48 +78,6 @@ public static class GameOptionsMenuPatch
}
}
var filteredOptions = GameSettingMenuPatches.SelectedMod.Options.Where(x => x.AdvancedRole is null && !x.HasGroup).ToList();
if (filteredOptions.Count > 0)
{
CategoryHeaderMasked ungroupedHeader = Object.Instantiate(__instance.categoryHeaderOrigin, Vector3.zero,
Quaternion.identity, __instance.settingsContainer);
ungroupedHeader.SetHeader(CustomStringName.CreateAndRegister("Ungrouped Options"), 20);
ungroupedHeader.transform.localScale = Vector3.one * 0.63f;
ungroupedHeader.transform.localPosition = new Vector3(-0.903f, num, -2f);
num -= 0.63f;
foreach (var opt in filteredOptions)
{
OptionBehaviour newOpt = opt.CreateOption(__instance.checkboxOrigin, __instance.numberOptionOrigin,
__instance.stringOptionOrigin, __instance.settingsContainer);
newOpt.transform.localPosition = new Vector3(0.952f, num, -2f);
newOpt.SetClickMask(__instance.ButtonClickMask);
newOpt.SetUpFromData(newOpt.data, 20);
SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren<SpriteRenderer>(true);
for (int i = 0; i < componentsInChildren.Length; i++)
{
componentsInChildren[i].material.SetInt(PlayerMaterial.MaskLayer, 20);
}
foreach (TextMeshPro textMeshPro in newOpt.GetComponentsInChildren<TextMeshPro>(true))
{
textMeshPro.fontMaterial.SetFloat(ShaderID.StencilComp, 3f);
textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20);
}
__instance.Children.Add(newOpt);
num -= 0.45f;
newOpt.Initialize();
}
}
__instance.scrollBar.SetYBoundsMax(-num - 1.65f);
return false;
+19 -44
View File
@@ -37,8 +37,8 @@ internal class MiraPluginManager
var id = MetadataHelper.GetMetadata(plugin.GetType()).GUID;
var info = new MiraPluginInfo(id, plugin as IMiraPlugin, IL2CPPChainloader.Instance.Plugins[id]);
RegisterOptionsGroups(assembly, info);
RegisterOptionsAttributes(assembly, info);
RegisterAllOptions(assembly, info);
RegisterRoleAttribute(assembly, info);
RegisterButtonAttribute(assembly);
@@ -53,57 +53,32 @@ internal class MiraPluginManager
return RegisteredPlugins.Values.First(plugin => plugin.PluginId == guid);
}
private static void RegisterOptionsAttributes(Assembly assembly, MiraPluginInfo pluginInfo)
private static void RegisterAllOptions(Assembly assembly, MiraPluginInfo pluginInfo)
{
foreach (var type in assembly.GetTypes())
var filteredTypes = assembly.GetTypes().Where(type => type.IsAssignableTo(typeof(IModdedOptionGroup)));
foreach (var type in filteredTypes)
{
foreach (PropertyInfo property in type.GetProperties())
if (!ModdedOptionsManager.RegisterGroup(type, pluginInfo))
{
foreach (var attribute in property.GetCustomAttributes())
{
if (attribute.GetType().BaseType == typeof(ModdedOptionAttribute))
{
pluginInfo.Options.Add(ModdedOptionsManager.RegisterOption(type, (ModdedOptionAttribute)attribute, property));
}
}
continue;
}
}
}
private static void RegisterOptionsGroups(Assembly assembly, MiraPluginInfo pluginInfo)
{
foreach (var type in assembly.GetTypes())
{
if (typeof(IModdedOptionGroup).IsAssignableFrom(type))
foreach (var property in type.GetProperties())
{
IModdedOptionGroup group = (IModdedOptionGroup)Activator.CreateInstance(type);
if (group == null)
if (property.PropertyType.IsAssignableTo(typeof(IModdedOption)))
{
ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo);
continue;
}
var attribute = property.GetCustomAttribute<ModdedOptionAttribute>();
if (attribute == null)
{
continue;
}
ModdedOptionsManager.Groups.Add(group);
ModdedOptionsManager.TypeToGroup.Add(type, group);
ModdedOptionsManager.GroupedOptions.Add(group, []);
pluginInfo.OptionGroups.Add(group);
foreach (var property in type.GetProperties())
{
if (typeof(IModdedOption).IsAssignableFrom(property.PropertyType))
{
IModdedOption option = (IModdedOption)property.GetValue(group);
if (option == null)
{
continue;
}
option.HasGroup = true;
option.AdvancedRole = group.AdvancedRole;
ModdedOptionsManager.GroupedOptions[group].Add(option);
pluginInfo.Options.Add(option);
}
}
ModdedOptionsManager.RegisterAttributeOption(type, attribute, property, pluginInfo);
}
}
}