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 Reactor.Utilities;
using UnityEngine; using UnityEngine;
namespace MiraAPI.Example; namespace MiraAPI.Example.Buttons;
[RegisterButton] [RegisterButton]
public class ExampleButton : CustomActionButton 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 MiraAPI.GameOptions.Attributes;
using UnityEngine; using UnityEngine;
namespace MiraAPI.Example; namespace MiraAPI.Example.Options;
public class ExampleOptions : IModdedOptionGroup public class ExampleOptions : IModdedOptionGroup
{ {
@@ -1,7 +1,7 @@
using MiraAPI.GameOptions; using MiraAPI.GameOptions;
using MiraAPI.GameOptions.OptionTypes; using MiraAPI.GameOptions.OptionTypes;
namespace MiraAPI.Example; namespace MiraAPI.Example.Options;
public class ExampleOptions2 : IModdedOptionGroup 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 MiraAPI.Utilities.Assets;
using UnityEngine; using UnityEngine;
namespace MiraAPI.Example; namespace MiraAPI.Example.Roles;
[RegisterCustomRole] [RegisterCustomRole]
public class CustomRole : ImpostorRole, ICustomRole public class CustomRole : ImpostorRole, ICustomRole
@@ -14,7 +13,5 @@ public class CustomRole : ImpostorRole, ICustomRole
public Color RoleColor => Palette.Orange; public Color RoleColor => Palette.Orange;
public ModdedRoleTeams Team => ModdedRoleTeams.Impostor; public ModdedRoleTeams Team => ModdedRoleTeams.Impostor;
public LoadableAsset<Sprite> OptionsScreenshot => MiraAssets.Banner; 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 MiraAPI.Utilities.Assets;
using UnityEngine; using UnityEngine;
namespace MiraAPI.Example; namespace MiraAPI.Example.Roles;
[RegisterCustomRole] [RegisterCustomRole]
public class CustomRole2 : CrewmateRole, ICustomRole public class CustomRole2 : CrewmateRole, ICustomRole
@@ -14,7 +13,5 @@ public class CustomRole2 : CrewmateRole, ICustomRole
public Color RoleColor => Palette.AcceptedGreen; public Color RoleColor => Palette.AcceptedGreen;
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
public LoadableAsset<Sprite> OptionsScreenshot => MiraAssets.Banner; 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 MiraAPI.Utilities.Assets;
using UnityEngine; using UnityEngine;
namespace MiraAPI.Example; namespace MiraAPI.Example.Roles;
[RegisterCustomRole] [RegisterCustomRole]
public class CustomRole3 : CrewmateRole, ICustomRole 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 uint Id { get; }
public BaseGameSetting Data { get; } public BaseGameSetting Data { get; }
public bool HasGroup { get; set; }
public IMiraPlugin ParentMod { get; set; } public IMiraPlugin ParentMod { get; set; }
public Type AdvancedRole { get; set; } public Type AdvancedRole { get; set; }
public OptionBehaviour OptionBehaviour { get; protected 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 public static T Instance
{ {
get => _instance ??= ModdedOptionsManager.Groups.OfType<T>().Single(); get => _instance ??= ModdedOptionsManager.Groups.Keys.OfType<T>().Single();
set set
{ {
if (_instance != null) if (_instance != null)
+98 -56
View File
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using MiraAPI.Networking; using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using Reactor.Networking.Rpc; using Reactor.Networking.Rpc;
namespace MiraAPI.GameOptions; namespace MiraAPI.GameOptions;
@@ -13,80 +14,121 @@ namespace MiraAPI.GameOptions;
public class ModdedOptionsManager public class ModdedOptionsManager
{ {
private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = new(); 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<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++; public static uint NextId => _nextId++;
private static uint _nextId = 1; 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)) if (OptionAttributes.ContainsKey(property))
{ {
return null; Logger<MiraApiPlugin>.Error($"Property {property.Name} already has an attribute registered.");
return;
} }
object newObj = Activator.CreateInstance(type); if (!TypeToGroup.TryGetValue(type, out var group))
IModdedOption result;
try
{
result = attribute.CreateOption(property.GetValue(newObj), property);
}
catch (Exception e)
{ {
Logger<MiraApiPlugin>.Error(e); Logger<MiraApiPlugin>.Error($"Failed to get group for {type.Name}");
return null; return;
} }
if (result != null) var option = attribute.CreateOption(property.GetValue(group), property);
if (option == null)
{ {
var setterOriginal = property.GetSetMethod(); Logger<MiraApiPlugin>.Error($"Failed to get option for {property.Name}");
var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch)); return;
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;
}
} }
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) internal static void SyncAllOptions(int targetId=-1)
{ {
List<NetData> data = []; 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); data.Add(netData);
count += netData.GetLength(); count += netData.GetLength();
if (count > 1000) if (count <= 1000)
{ {
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray()); continue;
data.Clear();
count = 0;
} }
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
data.Clear();
count = 0;
} }
if (data.Count > 0) if (data.Count > 0)
{ {
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray()); Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
@@ -98,11 +140,13 @@ public class ModdedOptionsManager
{ {
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))
{ {
Logger<MiraApiPlugin>.Error("Handling option " + option.Title); continue;
option.HandleNetData(netData.Data);
} }
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; 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) public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result)
{ {
ModdedOptionAttribute attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod().Equals(__originalMethod)).Value; ModdedOptionAttribute attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod().Equals(__originalMethod)).Value;
if (attribute != null) if (attribute == null)
{ {
__result = attribute.GetValue(); return true;
return false;
} }
return true;
__result = attribute.GetValue();
return false;
} }
} }
@@ -12,7 +12,6 @@ public abstract class ModdedOption<T> : IModdedOption
{ {
public uint Id { get; } public uint Id { get; }
public BaseGameSetting Data { get; protected init; } public BaseGameSetting Data { get; protected init; }
public bool HasGroup { get; set; } = false;
public IMiraPlugin ParentMod { get; set; } = null; public IMiraPlugin ParentMod { get; set; } = null;
public T Value { get; protected set; } public T Value { get; protected set; }
public T DefaultValue { get; init; } public T DefaultValue { get; init; }
@@ -41,7 +41,7 @@ public static class GameOptionsMenuPatch
categoryHeaderMasked.transform.localPosition = new Vector3(-0.903f, num, -2f); categoryHeaderMasked.transform.localPosition = new Vector3(-0.903f, num, -2f);
num -= 0.63f; 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) 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); __instance.scrollBar.SetYBoundsMax(-num - 1.65f);
return false; return false;
+19 -44
View File
@@ -37,8 +37,8 @@ internal class MiraPluginManager
var id = MetadataHelper.GetMetadata(plugin.GetType()).GUID; var id = MetadataHelper.GetMetadata(plugin.GetType()).GUID;
var info = new MiraPluginInfo(id, plugin as IMiraPlugin, IL2CPPChainloader.Instance.Plugins[id]); var info = new MiraPluginInfo(id, plugin as IMiraPlugin, IL2CPPChainloader.Instance.Plugins[id]);
RegisterOptionsGroups(assembly, info); RegisterAllOptions(assembly, info);
RegisterOptionsAttributes(assembly, info);
RegisterRoleAttribute(assembly, info); RegisterRoleAttribute(assembly, info);
RegisterButtonAttribute(assembly); RegisterButtonAttribute(assembly);
@@ -53,57 +53,32 @@ internal class MiraPluginManager
return RegisteredPlugins.Values.First(plugin => plugin.PluginId == guid); 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()) continue;
{
if (attribute.GetType().BaseType == typeof(ModdedOptionAttribute))
{
pluginInfo.Options.Add(ModdedOptionsManager.RegisterOption(type, (ModdedOptionAttribute)attribute, property));
}
}
} }
}
} foreach (var property in type.GetProperties())
private static void RegisterOptionsGroups(Assembly assembly, MiraPluginInfo pluginInfo)
{
foreach (var type in assembly.GetTypes())
{
if (typeof(IModdedOptionGroup).IsAssignableFrom(type))
{ {
IModdedOptionGroup group = (IModdedOptionGroup)Activator.CreateInstance(type); if (property.PropertyType.IsAssignableTo(typeof(IModdedOption)))
if (group == null) {
ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo);
continue;
}
var attribute = property.GetCustomAttribute<ModdedOptionAttribute>();
if (attribute == null)
{ {
continue; continue;
} }
ModdedOptionsManager.Groups.Add(group); ModdedOptionsManager.RegisterAttributeOption(type, attribute, property, pluginInfo);
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);
}
}
} }
} }
} }