mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-20 01:46:15 +00:00
Multi select options (WIP)
This commit is contained in:
1 parent
5eba1c9d7f
commit
28636e961d
8 files changed
+292
-9
No files matched your search
@@ -6,18 +6,76 @@ using UnityEngine;
|
||||
|
||||
namespace MiraAPI.GameOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a modded game option interface that can be used to create custom game settings.
|
||||
/// </summary>
|
||||
public interface IModdedOption
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the option.
|
||||
/// </summary>
|
||||
uint Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the title of the option.
|
||||
/// </summary>
|
||||
string Title { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the StringName object of the option.
|
||||
/// </summary>
|
||||
StringNames StringName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parent mod of the option.
|
||||
/// </summary>
|
||||
IMiraPlugin? ParentMod { get; set; }
|
||||
BaseGameSetting Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the BaseGameSetting data of the option.
|
||||
/// </summary>
|
||||
BaseGameSetting? Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the option behaviour of the option.
|
||||
/// </summary>
|
||||
OptionBehaviour? OptionBehaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the visibility of the option.
|
||||
/// </summary>
|
||||
Func<bool> Visible { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the config definition of the option.
|
||||
/// </summary>
|
||||
ConfigDefinition? ConfigDefinition { get; set; }
|
||||
|
||||
/// <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>
|
||||
OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the float data of the option.
|
||||
/// </summary>
|
||||
/// <returns>A float object representing the option's value.</returns>
|
||||
float GetFloatData();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the net data of the option.
|
||||
/// </summary>
|
||||
/// <returns>A NetData object representing this option's data.</returns>
|
||||
NetData GetNetData();
|
||||
|
||||
/// <summary>
|
||||
/// Handles incoming net data.
|
||||
/// </summary>
|
||||
/// <param name="data">The NetData's byte array.</param>
|
||||
void HandleNetData(byte[] data);
|
||||
}
|
||||
@@ -44,9 +44,11 @@ public static class ModdedOptionsManager
|
||||
TypeToGroup.Add(type, group);
|
||||
pluginInfo.OptionGroups.Add(group);
|
||||
|
||||
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
||||
typeof(OptionGroupSingleton<>).MakeGenericType(type)
|
||||
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
.SetValue(null, group);
|
||||
#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : struct, Enum
|
||||
/// Initializes a new instance of the <see cref="ModdedEnumOption{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the option.</param>
|
||||
/// <param name="defaultValue">The default value as an int.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <param name="values">An option list of string values to use in place of the enum name.</param>
|
||||
public ModdedEnumOption(string title, T defaultValue, string[]? values = null) : base(title, defaultValue)
|
||||
{
|
||||
@@ -167,19 +167,19 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : struct, Enum
|
||||
/// <inheritdoc />
|
||||
public override void HandleNetData(byte[] data)
|
||||
{
|
||||
SetValue((T)Enum.Parse(typeof(T), Encoding.Unicode.GetString(data)));
|
||||
SetValue(Enum.Parse<T>(Encoding.Unicode.GetString(data)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
||||
{
|
||||
return (T)Enum.Parse(typeof(T), optionBehaviour.GetInt().ToString(NumberFormatInfo.InvariantInfo));
|
||||
return Enum.Parse<T>(optionBehaviour.GetInt().ToString(NumberFormatInfo.InvariantInfo));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(T newValue)
|
||||
{
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)), false);
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data!.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)), false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using MiraAPI.Modules;
|
||||
using MiraAPI.Networking;
|
||||
using Reactor.Localization.Utilities;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.GameOptions.OptionTypes;
|
||||
|
||||
/// <summary>
|
||||
/// An option for selecting an enum value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The enum type.</typeparam>
|
||||
public class ModdedMultiSelectOption<T> : ModdedOption<MultiSelectValue<T>> where T : struct, Enum
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the string values of the enum.
|
||||
/// </summary>
|
||||
public string[]? Values { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating "all of the above".
|
||||
/// </summary>
|
||||
public T All { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating "none of the above".
|
||||
/// </summary>
|
||||
public T None { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedMultiSelectOption{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the option.</param>
|
||||
/// <param name="defaultValue">The default value as an int.</param>
|
||||
/// <param name="all">The all value.</param>
|
||||
/// <param name="none">The none value.</param>
|
||||
/// <param name="values">An option list of string values to use in place of the enum name.</param>
|
||||
public ModdedMultiSelectOption(string title, T defaultValue, T all, T none, string[]? values = null) : base(title, defaultValue)
|
||||
{
|
||||
Values = values ?? Enum.GetNames<T>();
|
||||
All = all;
|
||||
None = none;
|
||||
Data = ScriptableObject.CreateInstance<StringGameSetting>();
|
||||
var data = (StringGameSetting)Data;
|
||||
|
||||
data.Title = StringName;
|
||||
data.Type = global::OptionTypes.String;
|
||||
data.Values = (values is null
|
||||
? Enum.GetNames<T>()
|
||||
: values)
|
||||
.Select(CustomStringName.CreateAndRegister).ToArray();
|
||||
|
||||
data.Index = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
|
||||
{
|
||||
var stringOption = Object.Instantiate(stringOpt, container);
|
||||
|
||||
stringOption.SetUpFromData(Data, 20);
|
||||
stringOption.OnValueChanged = (Action<OptionBehaviour>)ValueChanged;
|
||||
|
||||
// SetUpFromData method doesn't work correctly so we must set the values manually
|
||||
stringOption.Title = StringName;
|
||||
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
|
||||
stringOption.Value = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
|
||||
|
||||
OptionBehaviour = stringOption;
|
||||
|
||||
return stringOption;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float GetFloatData()
|
||||
{
|
||||
return Convert.ToSingle(Value, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override NetData GetNetData()
|
||||
{
|
||||
return new NetData(Id, Value.ToBytes());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void HandleNetData(byte[] data)
|
||||
{
|
||||
SetValue(MultiSelectValue<T>.FromBytes(data));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override MultiSelectValue<T> GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
||||
{
|
||||
return []; // TODO: Add implementation
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(MultiSelectValue<T> newValue)
|
||||
{
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data!.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)), false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OptionBehaviour is StringOption opt)
|
||||
{
|
||||
opt.Value = Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace MiraAPI.GameOptions.OptionTypes;
|
||||
/// Represents a modded option.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The value type.</typeparam>
|
||||
public abstract class ModdedOption<T> : IModdedOption
|
||||
public abstract class ModdedOption<T> : IModdedOption where T : struct
|
||||
{
|
||||
private IMiraPlugin? _parentMod;
|
||||
|
||||
@@ -34,7 +34,7 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
/// <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.
|
||||
@@ -120,7 +120,7 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
var oldVal = Value;
|
||||
Value = newValue;
|
||||
|
||||
if (Value?.Equals(oldVal) == false)
|
||||
if (!Value.Equals(oldVal))
|
||||
{
|
||||
ChangedEvent?.Invoke(Value);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma warning disable // TODO: Remove later before completing the pr
|
||||
using System;
|
||||
using AmongUs.GameOptions;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using Reactor.Utilities.Attributes;
|
||||
|
||||
namespace MiraAPI.Modules;
|
||||
|
||||
[RegisterInIl2Cpp, Serializable]
|
||||
public class MultiSelectGameSetting : BaseGameSetting
|
||||
{
|
||||
public MultiSelectGameSetting() => Type = OptionTypes.MultipleChoice; // How nice of the game to provide an enum value themselves
|
||||
|
||||
public override string GetValueString(float value)
|
||||
{
|
||||
return TranslationController.Instance.GetString(default(StringNames), null, null);
|
||||
}
|
||||
|
||||
public Int32OptionNames OptionName;
|
||||
|
||||
public Il2CppStructArray<StringNames> Values;
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Utilities;
|
||||
using MonoMod.Utils;
|
||||
|
||||
namespace MiraAPI.Modules;
|
||||
|
||||
@@ -12,7 +15,7 @@ namespace MiraAPI.Modules;
|
||||
/// <typeparam name="T">The enum type that this collection will store.</typeparam>
|
||||
/// <param name="values">One or more enum values to initialize the collection with.</param>
|
||||
[Serializable]
|
||||
public struct MultiSelectValue<T>(params T[] values) : IEnumerable<T>, IEquatable<MultiSelectValue<T>> where T : struct, Enum
|
||||
public struct MultiSelectValue<T>(params T[] values) : IEnumerable<T>, IEquatable<MultiSelectValue<T>>, IDisposable where T : struct, Enum
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the underlying comma-separated string representation of enum values.
|
||||
@@ -105,6 +108,12 @@ public struct MultiSelectValue<T>(params T[] values) : IEnumerable<T>, IEquatabl
|
||||
/// <returns>Returns the first value.</returns>
|
||||
public readonly T First() => values.First();
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the current instance to an array of bytes.
|
||||
/// </summary>
|
||||
/// <returns>An array of bytes representing the current instance.</returns>
|
||||
public readonly byte[] ToBytes() => [.. values.Select(Helpers.EnumToBytes).SelectMany(x => x)];
|
||||
|
||||
/// <summary>
|
||||
/// Converts the collection to its string representation.
|
||||
/// </summary>
|
||||
@@ -126,6 +135,16 @@ public struct MultiSelectValue<T>(params T[] values) : IEnumerable<T>, IEquatabl
|
||||
/// <inheritdoc/>
|
||||
readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public readonly void Dispose() => values.Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of <see cref="MultiSelectValue{T}"/> from the provided byte array.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes to deserialize from.</param>
|
||||
/// <returns>An instance of <see cref="MultiSelectValue{T}"/>.</returns>
|
||||
public static MultiSelectValue<T> FromBytes(byte[] bytes) => new([.. Helpers.EnumsFromBytes<T>(bytes)]);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the current instance to its string representation.
|
||||
/// </summary>
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MonoMod.Utils;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
@@ -430,4 +431,69 @@ public static class Helpers
|
||||
items.Do(x => result += $"{separator}{x}");
|
||||
return result[separator.Length..];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes an enum to an array of bytes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The enum type.</typeparam>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <returns>An array of bytes representing the enum value.</returns>
|
||||
/// <exception cref="ArgumentException">The enum type had an unknown underlying type.</exception>
|
||||
public static byte[] EnumToBytes<T>(T value) where T : struct, Enum
|
||||
{
|
||||
var enumType = typeof(T);
|
||||
var underlyingType = enumType.GetEnumUnderlyingType();
|
||||
|
||||
if (underlyingType == typeof(byte))
|
||||
return [Convert.ToByte(value, NumberFormatInfo.InvariantInfo)];
|
||||
else if (underlyingType == typeof(sbyte))
|
||||
return [(byte)Convert.ToSByte(value, NumberFormatInfo.InvariantInfo)];
|
||||
else if (underlyingType == typeof(short))
|
||||
return BitConverter.GetBytes(Convert.ToInt16(value, NumberFormatInfo.InvariantInfo));
|
||||
else if (underlyingType == typeof(ushort))
|
||||
return BitConverter.GetBytes(Convert.ToUInt16(value, NumberFormatInfo.InvariantInfo));
|
||||
else if (underlyingType == typeof(int))
|
||||
return BitConverter.GetBytes(Convert.ToInt32(value, NumberFormatInfo.InvariantInfo));
|
||||
else if (underlyingType == typeof(uint))
|
||||
return BitConverter.GetBytes(Convert.ToUInt32(value, NumberFormatInfo.InvariantInfo));
|
||||
else if (underlyingType == typeof(long))
|
||||
return BitConverter.GetBytes(Convert.ToInt64(value, NumberFormatInfo.InvariantInfo));
|
||||
else if (underlyingType == typeof(ulong))
|
||||
return BitConverter.GetBytes(Convert.ToUInt64(value, NumberFormatInfo.InvariantInfo));
|
||||
else
|
||||
throw new ArgumentException("Unknown underlying type for " + enumType.Name);
|
||||
}
|
||||
|
||||
public static T BytesToEnum<T>(byte[] bytes, int offset) where T : struct, Enum => BytesToEnum<T>(bytes, offset, typeof(T).GetEnumUnderlyingType());
|
||||
|
||||
public static T BytesToEnum<T>(byte[] bytes, int offset, Type underlyingType) where T : struct, Enum // This version exists purely for performance and optimisation
|
||||
{
|
||||
var enumType = typeof(T);
|
||||
|
||||
if (underlyingType == typeof(byte) || underlyingType == typeof(sbyte))
|
||||
return (T)(object)bytes[offset];
|
||||
else if (underlyingType == typeof(short))
|
||||
return (T)(object)BitConverter.ToInt16(bytes, offset);
|
||||
else if (underlyingType == typeof(ushort))
|
||||
return (T)(object)BitConverter.ToUInt16(bytes, offset);
|
||||
else if (underlyingType == typeof(int))
|
||||
return (T)(object)BitConverter.ToInt32(bytes, offset);
|
||||
else if (underlyingType == typeof(uint))
|
||||
return (T)(object)BitConverter.ToUInt32(bytes, offset);
|
||||
else if (underlyingType == typeof(long))
|
||||
return (T)(object)BitConverter.ToInt64(bytes, offset);
|
||||
else if (underlyingType == typeof(ulong))
|
||||
return (T)(object)BitConverter.ToUInt64(bytes, offset);
|
||||
else
|
||||
throw new ArgumentException("Unknown underlying type for " + enumType.Name);
|
||||
}
|
||||
|
||||
public static IEnumerable<T> EnumsFromBytes<T>(byte[] bytes) where T : struct, Enum
|
||||
{
|
||||
var underlyingType = typeof(T).GetEnumUnderlyingType();
|
||||
var size = underlyingType.GetManagedSize();
|
||||
|
||||
for (var i = 0; i < bytes.Length; i += size)
|
||||
yield return BytesToEnum<T>(bytes, i, underlyingType);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user