mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-20 01:46:15 +00:00
Compare commits
26
Commits
0.3.3
...
options-changes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5049d16ac9 | ||
|
|
dccd06eac3 | ||
|
|
bf9b34e728 | ||
|
|
baf80618bb | ||
|
|
7d1976f772 | ||
|
|
4ce24c2a00 | ||
|
|
7fa75946fd | ||
|
|
43f92d4dc2 | ||
|
|
ee22b776eb | ||
|
|
f31349f6e8 | ||
|
|
448e2f9152 | ||
|
|
dde7e70406 | ||
|
|
713a7a2bcc | ||
|
|
2c2a2207c5 | ||
|
|
960573243c | ||
|
|
94bb5a7a37 | ||
|
|
5eba77dba9 | ||
|
|
ef329bfcbd | ||
|
|
a95cac055f | ||
|
|
7909b8f13d | ||
|
|
8ad2e220c9 | ||
|
|
40d88750da | ||
|
|
28636e961d | ||
|
|
5eba1c9d7f | ||
|
|
7cbf9ffe1b | ||
|
|
38ec50bcc7 |
No files matched your search
+2
-1
@@ -6,4 +6,5 @@ bin/
|
|||||||
.qodo/
|
.qodo/
|
||||||
|
|
||||||
*.user
|
*.user
|
||||||
*.lock.json
|
*.lock.json
|
||||||
|
.qodo
|
||||||
@@ -19,11 +19,14 @@ public class ExampleOptions : AbstractOptionGroup
|
|||||||
[ModdedNumberOption("Number Opt", min: 0, max: 10, increment: .25f, formatString: "0.00", suffixType: MiraNumberSuffixes.Percent)]
|
[ModdedNumberOption("Number Opt", min: 0, max: 10, increment: .25f, formatString: "0.00", suffixType: MiraNumberSuffixes.Percent)]
|
||||||
public float NumberOpt { get; set; } = 4f;
|
public float NumberOpt { get; set; } = 4f;
|
||||||
|
|
||||||
[ModdedEnumOption("Best API", typeof(BestApi), ["Mira API", "Mitochondria", "Reactor"])]
|
[ModdedEnumOption("Best API", typeof(AuApis), ["Mira API", "Mitochondria", "Reactor"])]
|
||||||
public BestApi Opt { get; set; } = BestApi.MiraAPI;
|
public AuApis BestApiOpt { get; set; } = AuApis.MiraAPI;
|
||||||
|
|
||||||
|
[ModdedEnumOption<AuApis>("Worst API", ["Mira API", "Mitochondria", "Reactor"])]
|
||||||
|
public AuApis WorstApiOpt { get; set; } = AuApis.Mitochondria;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum BestApi
|
public enum AuApis
|
||||||
{
|
{
|
||||||
MiraAPI,
|
MiraAPI,
|
||||||
Mitochondria,
|
Mitochondria,
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ public class ExampleOptions2 : AbstractOptionGroup
|
|||||||
{
|
{
|
||||||
ChangedEvent = x => Logger<ExamplePlugin>.Info($"changed Enum Opt to {x}"),
|
ChangedEvent = x => Logger<ExamplePlugin>.Info($"changed Enum Opt to {x}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public ModdedMultiSelectOption<TestingData> MultiSelectOption { get; } = new(
|
||||||
|
"Emotions",
|
||||||
|
TestingData.Happy,
|
||||||
|
TestingData.All,
|
||||||
|
TestingData.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TestingData : ulong
|
public enum TestingData : ulong
|
||||||
@@ -30,4 +36,6 @@ public enum TestingData : ulong
|
|||||||
Happy,
|
Happy,
|
||||||
Sad,
|
Sad,
|
||||||
Neutral,
|
Neutral,
|
||||||
|
All,
|
||||||
|
None,
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using MiraAPI.Modifiers;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace MiraAPI.GameOptions;
|
namespace MiraAPI.GameOptions;
|
||||||
@@ -28,11 +29,16 @@ public abstract class AbstractOptionGroup
|
|||||||
public virtual Type? OptionableType => null;
|
public virtual Type? OptionableType => null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the group should be shown in the modifiers menu.
|
/// Gets a value indicating whether the group should be shown in the modifiers menu. This is deprecated, please use ParentMenu!
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// TODO: make this not a boolean
|
// TODO: make this not a boolean
|
||||||
public virtual bool ShowInModifiersMenu => false;
|
public virtual bool ShowInModifiersMenu => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating which menu the group is in.
|
||||||
|
/// </summary>
|
||||||
|
public virtual MenuCategory ParentMenu => MenuCategory.Roles;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the function that determines whether the group should be visible or not.
|
/// Gets the function that determines whether the group should be visible or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -62,4 +68,33 @@ public abstract class AbstractOptionGroup<T> : AbstractOptionGroup where T : IOp
|
|||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override Type OptionableType => typeof(T);
|
public override Type OptionableType => typeof(T);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating which menu the group is in.
|
||||||
|
/// </summary>
|
||||||
|
public override MenuCategory ParentMenu
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var type = typeof(T);
|
||||||
|
if (type == typeof(BaseModifier))
|
||||||
|
{
|
||||||
|
return MenuCategory.Modifiers;
|
||||||
|
}
|
||||||
|
else if (type == typeof(RoleBehaviour))
|
||||||
|
{
|
||||||
|
return MenuCategory.Roles;
|
||||||
|
}
|
||||||
|
return MenuCategory.Game;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MenuCategory
|
||||||
|
{
|
||||||
|
Game,
|
||||||
|
Roles,
|
||||||
|
Modifiers,
|
||||||
|
CustomOne,
|
||||||
|
CustomTwo
|
||||||
}
|
}
|
||||||
@@ -32,3 +32,33 @@ public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? va
|
|||||||
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType ${enumType.FullName} is not a ModdedEnumOption");
|
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType ${enumType.FullName} is not a ModdedEnumOption");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generic attribute for creating an enum option.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">An enum type.</typeparam>
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class ModdedEnumOptionAttribute<T>(string title, string[]? values = null)
|
||||||
|
: ModdedOptionAttribute(title) where T : struct, Enum
|
||||||
|
{
|
||||||
|
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
|
||||||
|
{
|
||||||
|
var opt = new ModdedEnumOption<T>(Title, (T)(value ?? 0), values);
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void SetValue(object value)
|
||||||
|
{
|
||||||
|
var opt = HolderOption as ModdedEnumOption;
|
||||||
|
opt?.SetValue((int)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override object GetValue()
|
||||||
|
{
|
||||||
|
return HolderOption is ModdedEnumOption<T> opt
|
||||||
|
? Enum.ToObject(typeof(T), opt.Value)
|
||||||
|
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" is not a ModdedEnumOption<{typeof(T).Name}>");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,15 @@ public class ModdedNumberOptionAttribute(
|
|||||||
{
|
{
|
||||||
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
|
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
|
||||||
{
|
{
|
||||||
return new ModdedNumberOption(Title, (float)(value ?? min+increment), min, max, increment, suffixType, formatString, zeroInfinity);
|
return new ModdedNumberOption(
|
||||||
|
Title,
|
||||||
|
(float)(value ?? min + increment),
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
increment,
|
||||||
|
suffixType,
|
||||||
|
formatString,
|
||||||
|
zeroInfinity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -92,6 +92,11 @@ public interface IModdedOption
|
|||||||
/// <param name="saveDefault">Indicates whether to save the default value instead of the current value.</param>
|
/// <param name="saveDefault">Indicates whether to save the default value instead of the current value.</param>
|
||||||
void SaveToPreset(ConfigFile presetConfig, bool saveDefault = false);
|
void SaveToPreset(ConfigFile presetConfig, bool saveDefault = false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the scales and positions of the option if it's attached to the game options menu.
|
||||||
|
/// </summary>
|
||||||
|
void ChangeGameSetting();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Binds the option to a configuration file.
|
/// Binds the option to a configuration file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using MiraAPI.GameOptions.OptionTypes;
|
||||||
|
using Reactor.Utilities.Attributes;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace MiraAPI.GameOptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Component added onto options generated via MiraAPI. This allows developers to hook patches onto options in general, without using a list.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterInIl2Cpp]
|
||||||
|
public class MiraNumberOptionComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the modded option associated with the object.
|
||||||
|
/// </summary>
|
||||||
|
public ModdedNumberOption NumberOption { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the default increment.
|
||||||
|
/// </summary>
|
||||||
|
public float DefaultIncrement { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the value of whether or not holding shift will split the increment in half.
|
||||||
|
/// </summary>
|
||||||
|
public bool ShiftIncrementToggle { get; set; }
|
||||||
|
}
|
||||||
@@ -47,11 +47,13 @@ public static class ModdedOptionsManager
|
|||||||
TypeToGroup.Add(type, group);
|
TypeToGroup.Add(type, group);
|
||||||
pluginInfo.InternalOptionGroups.Add(group);
|
pluginInfo.InternalOptionGroups.Add(group);
|
||||||
|
|
||||||
|
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
||||||
typeof(OptionGroupSingleton<>).MakeGenericType(type)
|
typeof(OptionGroupSingleton<>).MakeGenericType(type)
|
||||||
#pragma warning disable S3011
|
#pragma warning disable S3011
|
||||||
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
|
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||||
#pragma warning restore S3011
|
#pragma warning restore S3011
|
||||||
.SetValue(null, group);
|
.SetValue(null, group);
|
||||||
|
#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,11 +47,17 @@ public class ModdedEnumOption : ModdedOption<int>
|
|||||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
||||||
{
|
{
|
||||||
var stringOption = Object.Instantiate(stringOpt, container);
|
var stringOption = Object.Instantiate(stringOpt, container);
|
||||||
|
stringOption.name =
|
||||||
|
$"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}";
|
||||||
|
|
||||||
stringOption.SetUpFromData(Data, 20);
|
stringOption.SetUpFromData(Data, 20);
|
||||||
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
||||||
|
|
||||||
// SetUpFromData method doesnt work correctly so we must set the values manually
|
// Changing the button text to make them more distinct from number options
|
||||||
|
stringOption.MinusBtn.ChangeButtonText("<");
|
||||||
|
stringOption.PlusBtn.ChangeButtonText(">");
|
||||||
|
|
||||||
|
// SetUpFromData method doesn't work correctly so we must set the values manually
|
||||||
stringOption.Title = StringName;
|
stringOption.Title = StringName;
|
||||||
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
|
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
|
||||||
stringOption.Value = Value;
|
stringOption.Value = Value;
|
||||||
@@ -61,6 +67,32 @@ public class ModdedEnumOption : ModdedOption<int>
|
|||||||
return stringOption;
|
return stringOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeGameSetting()
|
||||||
|
{
|
||||||
|
var stringOption = OptionBehaviour as StringOption;
|
||||||
|
|
||||||
|
stringOption!.MinusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f);
|
||||||
|
stringOption.PlusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f);
|
||||||
|
|
||||||
|
// The value box itself becomes the button
|
||||||
|
stringOption.PlusBtn.gameObject.SetActive(false);
|
||||||
|
stringOption.MinusBtn.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
var background = stringOption.transform.GetChild(0);
|
||||||
|
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||||
|
background.localScale += new Vector3(1f, 0f, 0f);
|
||||||
|
|
||||||
|
var title = stringOption.TitleText;
|
||||||
|
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||||
|
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||||
|
title.fontSize = 2.9f; // Why is it different for string options??
|
||||||
|
|
||||||
|
var valueBox = stringOption.transform.GetChild(5);
|
||||||
|
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||||
|
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override float GetFloatData()
|
public override float GetFloatData()
|
||||||
{
|
{
|
||||||
@@ -105,7 +137,7 @@ public class ModdedEnumOption : ModdedOption<int>
|
|||||||
/// An option for selecting an enum value.
|
/// An option for selecting an enum value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The enum type.</typeparam>
|
/// <typeparam name="T">The enum type.</typeparam>
|
||||||
public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
public class ModdedEnumOption<T> : ModdedOption<T> where T : struct, Enum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the string values of the enum.
|
/// Gets the string values of the enum.
|
||||||
@@ -116,12 +148,12 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
|||||||
/// Initializes a new instance of the <see cref="ModdedEnumOption{T}"/> class.
|
/// Initializes a new instance of the <see cref="ModdedEnumOption{T}"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="title">The title of the option.</param>
|
/// <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>
|
/// <param name="values">An option list of string values to use in place of the enum name.</param>
|
||||||
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
||||||
public ModdedEnumOption(string title, T defaultValue, string[]? values = null, bool includeInPreset=true) : base(title, defaultValue, includeInPreset)
|
public ModdedEnumOption(string title, T defaultValue, string[]? values = null, bool includeInPreset=true) : base(title, defaultValue, includeInPreset)
|
||||||
{
|
{
|
||||||
Values = values ?? Enum.GetNames(typeof(T));
|
Values = values ?? Enum.GetNames<T>();
|
||||||
Data = ScriptableObject.CreateInstance<StringGameSetting>();
|
Data = ScriptableObject.CreateInstance<StringGameSetting>();
|
||||||
var data = (StringGameSetting)Data;
|
var data = (StringGameSetting)Data;
|
||||||
|
|
||||||
@@ -138,11 +170,17 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
|||||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
||||||
{
|
{
|
||||||
var stringOption = Object.Instantiate(stringOpt, container);
|
var stringOption = Object.Instantiate(stringOpt, container);
|
||||||
|
stringOption.name =
|
||||||
|
$"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}";
|
||||||
|
|
||||||
stringOption.SetUpFromData(Data, 20);
|
stringOption.SetUpFromData(Data, 20);
|
||||||
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
stringOption.OnValueChanged = (Action<OptionBehaviour>)ValueChanged;
|
||||||
|
|
||||||
// SetUpFromData method doesnt work correctly so we must set the values manually
|
// Changing the button text to make them more distinct from number options
|
||||||
|
stringOption.MinusBtn.ChangeButtonText("<");
|
||||||
|
stringOption.PlusBtn.ChangeButtonText(">");
|
||||||
|
|
||||||
|
// SetUpFromData method doesn't work correctly so we must set the values manually
|
||||||
stringOption.Title = StringName;
|
stringOption.Title = StringName;
|
||||||
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
|
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
|
||||||
stringOption.Value = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
|
stringOption.Value = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
|
||||||
@@ -152,6 +190,32 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
|||||||
return stringOption;
|
return stringOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeGameSetting()
|
||||||
|
{
|
||||||
|
var stringOption = OptionBehaviour as StringOption;
|
||||||
|
|
||||||
|
stringOption!.MinusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f);
|
||||||
|
stringOption.PlusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f);
|
||||||
|
|
||||||
|
// The value box itself becomes the button
|
||||||
|
stringOption.PlusBtn.gameObject.SetActive(false);
|
||||||
|
stringOption.MinusBtn.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
var background = stringOption.transform.GetChild(0);
|
||||||
|
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||||
|
background.localScale += new Vector3(1f, 0f, 0f);
|
||||||
|
|
||||||
|
var title = stringOption.TitleText;
|
||||||
|
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||||
|
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||||
|
title.fontSize = 2.9f; // Why is it different for string options??
|
||||||
|
|
||||||
|
var valueBox = stringOption.transform.GetChild(5);
|
||||||
|
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||||
|
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override float GetFloatData()
|
public override float GetFloatData()
|
||||||
{
|
{
|
||||||
@@ -167,19 +231,19 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void HandleNetData(byte[] data)
|
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 />
|
/// <inheritdoc />
|
||||||
public override T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
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 />
|
/// <inheritdoc />
|
||||||
protected override void OnValueChanged(T newValue)
|
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)
|
if (!OptionBehaviour)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
using AmongUs.GameOptions;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||||
|
using MiraAPI.Modules;
|
||||||
|
using MiraAPI.Networking;
|
||||||
|
using MiraAPI.Utilities;
|
||||||
|
using MiraAPI.Utilities.Assets;
|
||||||
|
using Reactor.Utilities.Extensions;
|
||||||
|
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<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>
|
||||||
|
/// Gets the list of buttons that relate to the current instance.
|
||||||
|
/// </summary>
|
||||||
|
public List<MonoBehaviour> Buttons { 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>
|
||||||
|
/// <exception cref="ArgumentException">The enum type doesn't have the flags attribute.</exception>
|
||||||
|
public ModdedMultiSelectOption(string title, T defaultValue, T all, T none, string[]? values = null) : base(title, defaultValue)
|
||||||
|
{
|
||||||
|
var tType = typeof(T);
|
||||||
|
|
||||||
|
if (tType.GetCustomAttribute<FlagsAttribute>() == null)
|
||||||
|
throw new ArgumentException($"{tType.Name} is not a flag enum type");
|
||||||
|
|
||||||
|
Values = values ?? Enum.GetNames<T>();
|
||||||
|
All = all;
|
||||||
|
None = none;
|
||||||
|
Data = ScriptableObject.CreateInstance<MultiSelectGameSetting>();
|
||||||
|
Buttons = [];
|
||||||
|
|
||||||
|
var data = (MultiSelectGameSetting)Data;
|
||||||
|
data.Title = StringName;
|
||||||
|
data.OptionName = Int32OptionNames.Invalid;
|
||||||
|
data.Type = global::OptionTypes.MultipleChoice; // How nice of the game to provide an enum value themselves
|
||||||
|
data.EnumNames = []; // We don't initialise the translation here, we just add one as and when needed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override OptionBehaviour CreateOption(
|
||||||
|
ToggleOption toggleOpt,
|
||||||
|
NumberOption numberOpt,
|
||||||
|
StringOption stringOpt,
|
||||||
|
PlayerOption playerOpt,
|
||||||
|
Transform container)
|
||||||
|
{
|
||||||
|
var stringOption = Object.Instantiate(stringOpt, container);
|
||||||
|
|
||||||
|
stringOption.SetUpFromData(Data, 20);
|
||||||
|
stringOption.OnValueChanged = (Action<OptionBehaviour>)ValueChanged;
|
||||||
|
|
||||||
|
var toggle = Object.Instantiate(toggleOpt.GetComponentInChildren<PassiveButton>(), stringOption.transform);
|
||||||
|
toggle.name = "Button";
|
||||||
|
toggle.transform.DestroyChildren();
|
||||||
|
toggle.OverrideOnClickListeners(ToggleButtons);
|
||||||
|
|
||||||
|
var box = toggle.GetComponent<BoxCollider2D>();
|
||||||
|
var prevColliderSize = box.size;
|
||||||
|
prevColliderSize.x *= 4.97f;
|
||||||
|
box.size = prevColliderSize;
|
||||||
|
|
||||||
|
var rend = stringOption.transform.GetChild(5).GetComponent<SpriteRenderer>();
|
||||||
|
toggle.OverrideOnMouseOverListeners(() => rend.color = Buttons.Count > 0 ? Color.white : MiraAssets.AcceptedTeal);
|
||||||
|
toggle.OverrideOnMouseOutListeners(() => rend.color = Buttons.Count > 0 ? MiraAssets.AcceptedTeal : Color.white);
|
||||||
|
|
||||||
|
// SetUpFromData method doesn't work correctly so we must set the values manually
|
||||||
|
stringOption.Title = StringName;
|
||||||
|
stringOption.Values = (Data as MultiSelectGameSetting)?.EnumNames?.Values?.ToArray() ?? new Il2CppStructArray<StringNames>(0);
|
||||||
|
stringOption.Value = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
|
||||||
|
|
||||||
|
OptionBehaviour = stringOption;
|
||||||
|
|
||||||
|
return stringOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeGameSetting()
|
||||||
|
{
|
||||||
|
var stringOption = OptionBehaviour as StringOption;
|
||||||
|
|
||||||
|
// The value box itself becomes the button
|
||||||
|
stringOption!.PlusBtn.gameObject.SetActive(false);
|
||||||
|
stringOption.MinusBtn.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
var background = stringOption.transform.GetChild(0);
|
||||||
|
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||||
|
background.localScale += new Vector3(1f, 0f, 0f);
|
||||||
|
|
||||||
|
var title = stringOption.TitleText;
|
||||||
|
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||||
|
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||||
|
title.fontSize = 2.9f; // Why is it different for string options??
|
||||||
|
|
||||||
|
var valueBox = stringOption.transform.GetChild(5);
|
||||||
|
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||||
|
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleButtons()
|
||||||
|
{
|
||||||
|
if (Buttons.Count > 0)
|
||||||
|
{
|
||||||
|
Buttons.ForEach(x => x.gameObject.Destroy());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement this
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override float GetFloatData()
|
||||||
|
{
|
||||||
|
return Convert.ToSingle(Value, NumberFormatInfo.InvariantInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override NetData GetNetData()
|
||||||
|
{
|
||||||
|
return new NetData(Id, EnumSerializer.EnumToBytes(Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void HandleNetData(byte[] data)
|
||||||
|
{
|
||||||
|
SetValue(EnumSerializer.EnumFromBytes<T>(data, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
if (!OptionBehaviour)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OptionBehaviour is StringOption opt)
|
||||||
|
{
|
||||||
|
opt.Value = Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,11 @@ public class ModdedNumberOption : ModdedOption<float>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ZeroInfinity { get; }
|
public bool ZeroInfinity { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether holding shift will divide the increment.
|
||||||
|
/// </summary>
|
||||||
|
public bool ShiftIncrement { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ModdedNumberOption"/> class.
|
/// Initializes a new instance of the <see cref="ModdedNumberOption"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -49,6 +54,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
|||||||
/// <param name="formatString">Optional format string for the option screen.</param>
|
/// <param name="formatString">Optional format string for the option screen.</param>
|
||||||
/// <param name="zeroInfinity">Whether zero is infinity or not.</param>
|
/// <param name="zeroInfinity">Whether zero is infinity or not.</param>
|
||||||
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
||||||
|
/// <param name="shiftIncrement">Whether holding shift will divide the increment or not.</param>
|
||||||
public ModdedNumberOption(
|
public ModdedNumberOption(
|
||||||
string title,
|
string title,
|
||||||
float defaultValue,
|
float defaultValue,
|
||||||
@@ -65,6 +71,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
|||||||
Increment = increment;
|
Increment = increment;
|
||||||
SuffixType = suffixType;
|
SuffixType = suffixType;
|
||||||
ZeroInfinity = zeroInfinity;
|
ZeroInfinity = zeroInfinity;
|
||||||
|
ShiftIncrement = increment != 1f;
|
||||||
|
|
||||||
Value = Mathf.Clamp(defaultValue, min, max);
|
Value = Mathf.Clamp(defaultValue, min, max);
|
||||||
|
|
||||||
@@ -97,6 +104,12 @@ public class ModdedNumberOption : ModdedOption<float>
|
|||||||
Transform container)
|
Transform container)
|
||||||
{
|
{
|
||||||
var numberOption = Object.Instantiate(numberOpt, Vector3.zero, Quaternion.identity, container);
|
var numberOption = Object.Instantiate(numberOpt, Vector3.zero, Quaternion.identity, container);
|
||||||
|
numberOption.name =
|
||||||
|
$"{ParentMod!.OptionsTitleText}.NumberOption.{TranslationController.Instance.GetString(StringName)}";
|
||||||
|
var optionComponent = numberOption.gameObject.AddComponent<MiraNumberOptionComponent>();
|
||||||
|
optionComponent.NumberOption = this;
|
||||||
|
optionComponent.DefaultIncrement = Increment;
|
||||||
|
optionComponent.ShiftIncrementToggle = ShiftIncrement;
|
||||||
|
|
||||||
numberOption.SetUpFromData(Data, 20);
|
numberOption.SetUpFromData(Data, 20);
|
||||||
numberOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
numberOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
||||||
@@ -115,6 +128,27 @@ public class ModdedNumberOption : ModdedOption<float>
|
|||||||
return numberOption;
|
return numberOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeGameSetting()
|
||||||
|
{
|
||||||
|
var numberOption = OptionBehaviour as NumberOption;
|
||||||
|
|
||||||
|
numberOption!.PlusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f);
|
||||||
|
numberOption.MinusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f);
|
||||||
|
|
||||||
|
var background = numberOption.transform.GetChild(0);
|
||||||
|
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||||
|
background.localScale += new Vector3(1f, 0f, 0f);
|
||||||
|
|
||||||
|
var title = numberOption.TitleText;
|
||||||
|
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||||
|
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||||
|
|
||||||
|
var valueBox = numberOption.transform.GetChild(5);
|
||||||
|
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||||
|
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override float GetFloatData()
|
public override float GetFloatData()
|
||||||
{
|
{
|
||||||
@@ -145,7 +179,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
|||||||
Value = Mathf.Clamp(newValue, Min, Max);
|
Value = Mathf.Clamp(newValue, Min, Max);
|
||||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(
|
HudManager.Instance.Notifier.AddSettingsChangeMessage(
|
||||||
StringName,
|
StringName,
|
||||||
Data.GetValueString(Value),
|
Data!.GetValueString(Value),
|
||||||
false);
|
false);
|
||||||
|
|
||||||
if (OptionBehaviour is NumberOption opt)
|
if (OptionBehaviour is NumberOption opt)
|
||||||
|
|||||||
@@ -181,6 +181,23 @@ public abstract class ModdedOption<T> : IModdedOption
|
|||||||
PlayerOption playerOpt,
|
PlayerOption playerOpt,
|
||||||
Transform container);
|
Transform container);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Visually adjust the game option as needed.
|
||||||
|
/// </summary>
|
||||||
|
public abstract void ChangeGameSetting();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binds the option to the configuration file of the parent mod, if available.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void BindConfig()
|
||||||
|
{
|
||||||
|
var entry = ParentMod?.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
|
||||||
|
if (entry != null)
|
||||||
|
{
|
||||||
|
Value = entry.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implicitly converts the option to type of <typeparamref name="T"/>.
|
/// Implicitly converts the option to type of <typeparamref name="T"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -28,6 +28,27 @@ public class ModdedPlayerOption : ModdedOption<int>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AllowNone { get; set; }
|
public bool AllowNone { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeGameSetting()
|
||||||
|
{
|
||||||
|
var numberOption = OptionBehaviour as PlayerOption;
|
||||||
|
|
||||||
|
numberOption!.PlusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f);
|
||||||
|
numberOption.MinusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f);
|
||||||
|
|
||||||
|
var background = numberOption.transform.GetChild(0);
|
||||||
|
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||||
|
background.localScale += new Vector3(1f, 0f, 0f);
|
||||||
|
|
||||||
|
var title = numberOption.TitleText;
|
||||||
|
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||||
|
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||||
|
|
||||||
|
var valueBox = numberOption.transform.GetChild(5);
|
||||||
|
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||||
|
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ModdedPlayerOption"/> class.
|
/// Initializes a new instance of the <see cref="ModdedPlayerOption"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -55,6 +76,8 @@ public class ModdedPlayerOption : ModdedOption<int>
|
|||||||
Transform container)
|
Transform container)
|
||||||
{
|
{
|
||||||
var playerOption = Object.Instantiate(playerOpt, container);
|
var playerOption = Object.Instantiate(playerOpt, container);
|
||||||
|
playerOption.name =
|
||||||
|
$"{ParentMod!.OptionsTitleText}.PlayerOption.{TranslationController.Instance.GetString(StringName)}";
|
||||||
|
|
||||||
var filteredList = GetFilteredPlayers();
|
var filteredList = GetFilteredPlayers();
|
||||||
Values = filteredList.ToIl2CppList();
|
Values = filteredList.ToIl2CppList();
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ public class ModdedToggleOption : ModdedOption<bool>
|
|||||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
||||||
{
|
{
|
||||||
var toggleOption = Object.Instantiate(toggleOpt, Vector3.zero, Quaternion.identity, container);
|
var toggleOption = Object.Instantiate(toggleOpt, Vector3.zero, Quaternion.identity, container);
|
||||||
|
toggleOption.name =
|
||||||
|
$"{ParentMod!.OptionsTitleText}.ToggleOption.{TranslationController.Instance.GetString(StringName)}";
|
||||||
|
|
||||||
toggleOption.SetUpFromData(Data, 20);
|
toggleOption.SetUpFromData(Data, 20);
|
||||||
|
|
||||||
@@ -42,6 +44,21 @@ public class ModdedToggleOption : ModdedOption<bool>
|
|||||||
return toggleOption;
|
return toggleOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ChangeGameSetting()
|
||||||
|
{
|
||||||
|
var toggleOption = OptionBehaviour as ToggleOption;
|
||||||
|
toggleOption!.transform.GetChild(1).localPosition += new Vector3(2.2f, 0f, 0f); // Checkbox
|
||||||
|
|
||||||
|
var title = toggleOption.TitleText;
|
||||||
|
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||||
|
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||||
|
|
||||||
|
var background = toggleOption.transform.GetChild(2);
|
||||||
|
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||||
|
background.localScale += new Vector3(1f, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override float GetFloatData()
|
public override float GetFloatData()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ public abstract class CustomActionButton
|
|||||||
new Vector3(MaxUses <= 0 ? -0.4f : 0.4f, 0.45f, -9f)
|
new Vector3(MaxUses <= 0 ? -0.4f : 0.4f, 0.45f, -9f)
|
||||||
);
|
);
|
||||||
KeybindText = KeybindIcon.transform.GetChild(0).GetComponent<TextMeshPro>();
|
KeybindText = KeybindIcon.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||||
|
HudManagerPatches.moddedKeybindIcons.Add(KeybindText);
|
||||||
Button.usesRemainingSprite.transform.localPosition = new(-0.341f, 0.45f, -0.1f);
|
Button.usesRemainingSprite.transform.localPosition = new(-0.341f, 0.45f, -0.1f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -494,7 +495,7 @@ public abstract class CustomActionButton
|
|||||||
if (Keybind != null && KeybindText != null)
|
if (Keybind != null && KeybindText != null)
|
||||||
{
|
{
|
||||||
KeybindText.text = Keybind.CurrentKey.ToString();
|
KeybindText.text = Keybind.CurrentKey.ToString();
|
||||||
KeybindIcon?.SetActive(ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard &&
|
KeybindIcon?.SetActive(ActiveInputManager.currentControlType is ActiveInputManager.InputType.Keyboard &&
|
||||||
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value &&
|
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value &&
|
||||||
Keybind.CurrentKey != KeyboardKeyCode.None);
|
Keybind.CurrentKey != KeyboardKeyCode.None);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
global using static Reactor.Utilities.Logger<MiraAPI.MiraApiPlugin>;
|
global using static Reactor.Utilities.Logger<MiraAPI.MiraApiPlugin>;
|
||||||
using BepInEx;
|
using BepInEx;
|
||||||
|
using BepInEx.Configuration;
|
||||||
using BepInEx.Unity.IL2CPP;
|
using BepInEx.Unity.IL2CPP;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using MiraAPI.PluginLoading;
|
using MiraAPI.PluginLoading;
|
||||||
@@ -31,6 +32,7 @@ public partial class MiraApiPlugin : BasePlugin
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static Color DefaultHeaderColor { get; } = new Color32(77, 77, 77, 255);
|
public static Color DefaultHeaderColor { get; } = new Color32(77, 77, 77, 255);
|
||||||
|
|
||||||
|
internal static ConfigEntry<string>? DistanceSuffix { get; private set; }
|
||||||
private static MiraPluginManager? PluginManager { get; set; }
|
private static MiraPluginManager? PluginManager { get; set; }
|
||||||
internal Harmony Harmony { get; } = new(Id);
|
internal Harmony Harmony { get; } = new(Id);
|
||||||
|
|
||||||
@@ -41,6 +43,8 @@ public partial class MiraApiPlugin : BasePlugin
|
|||||||
|
|
||||||
ReactorCredits.Register("Mira API", Version, true, ReactorCredits.AlwaysShow);
|
ReactorCredits.Register("Mira API", Version, true, ReactorCredits.AlwaysShow);
|
||||||
|
|
||||||
|
DistanceSuffix = Config.Bind("Display", "Distance", "unit(s)", "Dictates how a distance is shown in numeric option displays");
|
||||||
|
|
||||||
PluginManager = new MiraPluginManager();
|
PluginManager = new MiraPluginManager();
|
||||||
PluginManager.Initialize();
|
PluginManager.Initialize();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,9 +28,11 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
|
|||||||
public ConfigEntry<bool> ModifiersHudLeftSide { get; private set; } = config.Bind("Displays", "Show Modifiers HUD on Left Side", false);
|
public ConfigEntry<bool> ModifiersHudLeftSide { get; private set; } = config.Bind("Displays", "Show Modifiers HUD on Left Side", false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether to show keybinds in the control mapper.
|
/// Gets whether to show keybinds on buttons.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ConfigEntry<bool> ShowKeybinds { get; private set; } = config.Bind("Keybinds", "Show Keybinds in Control Mapper", true);
|
[LocalToggleSetting]
|
||||||
|
public ConfigEntry<bool> ShowKeybinds { get; private set; } = config.Bind("Keybinds", "Show Keybinds on Buttons", true);
|
||||||
|
// This would be placed in the keybinds menu, but it crashes for Epic Games users. - Atony
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether to apply cosmetic changes to the TaskAdder.
|
/// Gets whether to apply cosmetic changes to the TaskAdder.
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MiraAPI.Modules;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Denotes a failure in conversion of values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to log.</param>
|
||||||
|
public class ConversionFailException(string message) : Exception(message);
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// TODO: Remove later before completing the pr
|
||||||
|
#pragma warning disable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AmongUs.GameOptions;
|
||||||
|
using Il2CppInterop.Runtime.Attributes;
|
||||||
|
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||||
|
using Reactor.Utilities.Attributes;
|
||||||
|
|
||||||
|
namespace MiraAPI.Modules;
|
||||||
|
|
||||||
|
[RegisterInIl2Cpp, Serializable]
|
||||||
|
public class MultiSelectGameSetting : BaseGameSetting
|
||||||
|
{
|
||||||
|
public Int32OptionNames OptionName;
|
||||||
|
public Dictionary<int, StringNames> EnumNames;
|
||||||
|
|
||||||
|
public override string GetValueString(float value) => TranslationController.Instance.GetString(EnumNames[(int)value]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using MiraAPI.Utilities;
|
||||||
|
|
||||||
|
namespace MiraAPI.Modules;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a collection of enum values that can be stored and manipulated as a comma-separated string.
|
||||||
|
/// </summary>
|
||||||
|
/// <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>>, IDisposable where T : struct, Enum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the underlying comma-separated string representation of enum values.
|
||||||
|
/// </summary>
|
||||||
|
public string Values
|
||||||
|
{
|
||||||
|
readonly get => Helpers.Join(',', _values);
|
||||||
|
set => _values = [.. value.TrueSplit(',').Select(Enum.Parse<T>)];
|
||||||
|
}
|
||||||
|
private HashSet<T> _values = [.. values];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of values contained within.
|
||||||
|
/// </summary>
|
||||||
|
public readonly int Count => _values.Count;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a single enum value to the end of the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The enum value to add.</param>
|
||||||
|
/// <returns><c>true</c> if successfully added; otherwise, <c>false</c>.</returns>
|
||||||
|
public readonly bool Add(T item) => _values.Add(item);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the first occurrence of the specified enum value.<br/>
|
||||||
|
/// Returns <c>true</c> if the value was found and removed, <c>false</c> otherwise.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The enum value to remove.</param>
|
||||||
|
/// <returns><c>true</c> if successfully removed; otherwise, <c>false</c>.</returns>
|
||||||
|
public readonly bool Remove(T item) => _values.Remove(item);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds multiple enum values to the end of the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="items">The collection of enum values to add.</param>
|
||||||
|
public readonly void AddRange(IEnumerable<T> items)
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
|
_values.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes multiple enum values from the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="items">The enum values to remove.</param>
|
||||||
|
/// <returns>The number of items successfully removed.</returns>
|
||||||
|
public readonly int RemoveRange(params T[] items) => RemoveRange((IEnumerable<T>)items);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes multiple enum values from the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="items">The collection of enum values to remove.</param>
|
||||||
|
/// <returns>The number of items successfully removed.</returns>
|
||||||
|
public readonly int RemoveRange(IEnumerable<T> items) => items.Count(Remove);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the collection contains the specified enum value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The enum value to locate.</param>
|
||||||
|
/// <returns>true if the item is found in the collection; otherwise, false.</returns>
|
||||||
|
public readonly bool Contains(T item) => _values.Contains(item);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all enum values that match the specified predicate condition.<br/>
|
||||||
|
/// Iterates through the collection and removes each matching value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="predicate">A function that defines the condition for removing items.</param>
|
||||||
|
/// <returns>The number of items removed from the collection.</returns>
|
||||||
|
public readonly int RemoveAll(Func<T, bool> predicate)
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
foreach (var item in this)
|
||||||
|
{
|
||||||
|
if (predicate(item) && Remove(item))
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all enum values from the collection.
|
||||||
|
/// </summary>
|
||||||
|
public readonly void Clear() => _values.Clear();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the first value in the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <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() => EnumSerializer.EnumsToBytes(this);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the collection to its string representation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A comma-separated string of enum values.</returns>
|
||||||
|
public readonly override string ToString() => Values;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public readonly override bool Equals(object? obj) => obj is MultiSelectValue<T> other && Equals(other);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public readonly bool Equals(MultiSelectValue<T> other) => _values.SetEquals(other._values);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public readonly override int GetHashCode() => Values.GetHashCode();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public readonly IEnumerator<T> GetEnumerator() => ((IEnumerable<T>)_values).GetEnumerator();
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
#pragma warning disable CA1000 // Do not declare static members on generic types
|
||||||
|
public static MultiSelectValue<T> FromBytes(byte[] bytes) => new([.. EnumSerializer.EnumsFromBytes<T>(bytes)]);
|
||||||
|
#pragma warning restore CA1000 // Do not declare static members on generic types
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the current instance to its string representation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to convert.</param>
|
||||||
|
public static implicit operator string(MultiSelectValue<T> value) => value.Values;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the current instance to its array representation of values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to convert.</param>
|
||||||
|
public static implicit operator T[](MultiSelectValue<T> value) => [.. value._values];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the current instance to one singular value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to convert.</param>
|
||||||
|
/// <exception cref="ConversionFailException">There were either no values, or more than one value.</exception>
|
||||||
|
#pragma warning disable S3877 // Exceptions should not be thrown from unexpected methods
|
||||||
|
public static implicit operator T(MultiSelectValue<T> value) =>
|
||||||
|
value.Count == 1
|
||||||
|
? value._values.First()
|
||||||
|
: throw new ConversionFailException($"Tried to convert multiple or no values ({value.Values}) to a singular one");
|
||||||
|
#pragma warning restore S3877 // Exceptions should not be thrown from unexpected methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the value to an instance of MultiSelectValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to convert.</param>
|
||||||
|
public static implicit operator MultiSelectValue<T>(T value) => new(value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the array of values to an instance of MultiSelectValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">The values to convert.</param>
|
||||||
|
public static implicit operator MultiSelectValue<T>(T[] values) => new(values);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the value string to an instance of MultiSelectValue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">The values to convert.</param>
|
||||||
|
public static implicit operator MultiSelectValue<T>(string values) => new([.. values.TrueSplit(',').Select(Enum.Parse<T>)]);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Equality comparison.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">Left.</param>
|
||||||
|
/// <param name="right">Right.</param>
|
||||||
|
public static bool operator ==(MultiSelectValue<T> left, MultiSelectValue<T> right) => left.Equals(right);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inequality comparison.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">Left.</param>
|
||||||
|
/// <param name="right">Right.</param>
|
||||||
|
public static bool operator !=(MultiSelectValue<T> left, MultiSelectValue<T> right) => !left.Equals(right);
|
||||||
|
}
|
||||||
@@ -9,6 +9,11 @@ namespace MiraAPI.Patches.Hud;
|
|||||||
public static class ButtonResetPatches
|
public static class ButtonResetPatches
|
||||||
{
|
{
|
||||||
public static void Postfix()
|
public static void Postfix()
|
||||||
|
{
|
||||||
|
ResetCooldowns();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ResetCooldowns()
|
||||||
{
|
{
|
||||||
foreach (var customActionButton in CustomButtonManager.CustomButtons)
|
foreach (var customActionButton in CustomButtonManager.CustomButtons)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ public static class HudManagerPatches
|
|||||||
|
|
||||||
private static Dictionary<TextMeshPro, int> vanillaKeybindIcons = new();
|
private static Dictionary<TextMeshPro, int> vanillaKeybindIcons = new();
|
||||||
|
|
||||||
|
internal static List<TextMeshPro> moddedKeybindIcons = new();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Trigger hudstart on current custom gamemode
|
/// Trigger hudstart on current custom gamemode
|
||||||
@@ -88,6 +90,7 @@ public static class HudManagerPatches
|
|||||||
fakeButton.ToggleVisible(true);
|
fakeButton.ToggleVisible(true);
|
||||||
fakeButton.Destroy();
|
fakeButton.Destroy();
|
||||||
}
|
}
|
||||||
|
moddedKeybindIcons = [];
|
||||||
|
|
||||||
foreach (var button in CustomButtonManager.CustomButtons)
|
foreach (var button in CustomButtonManager.CustomButtons)
|
||||||
{
|
{
|
||||||
@@ -112,6 +115,8 @@ public static class HudManagerPatches
|
|||||||
Error($"Failed to create custom button {button.GetType().Name}: {e}");
|
Error($"Failed to create custom button {button.GetType().Name}: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
__instance.ImpostorVentButton.transform.SetParent(null);
|
||||||
|
__instance.ImpostorVentButton.transform.SetParent(BottomRight.transform);
|
||||||
|
|
||||||
gridArrange.Start();
|
gridArrange.Start();
|
||||||
gridArrange.ArrangeChilds();
|
gridArrange.ArrangeChilds();
|
||||||
@@ -180,11 +185,16 @@ public static class HudManagerPatches
|
|||||||
[HarmonyPostfix]
|
[HarmonyPostfix]
|
||||||
public static void UpdatePostfix()
|
public static void UpdatePostfix()
|
||||||
{
|
{
|
||||||
|
var canSeeBinds = ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard &&
|
||||||
|
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value;
|
||||||
foreach (var btnIcon in vanillaKeybindIcons)
|
foreach (var btnIcon in vanillaKeybindIcons)
|
||||||
{
|
{
|
||||||
btnIcon.Key.text = KeybindUtils.GetKeycodeByActionId(btnIcon.Value).ToString();
|
btnIcon.Key.text = KeybindUtils.GetKeycodeByActionId(btnIcon.Value).ToString();
|
||||||
btnIcon.Key.transform.parent.gameObject.SetActive(ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard &&
|
btnIcon.Key.transform.parent.gameObject.SetActive(canSeeBinds);
|
||||||
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value);
|
}
|
||||||
|
foreach (var btnIcon in moddedKeybindIcons)
|
||||||
|
{
|
||||||
|
btnIcon.transform.parent.gameObject.SetActive(canSeeBinds);
|
||||||
}
|
}
|
||||||
|
|
||||||
var player = ReInput.players.GetPlayer(0);
|
var player = ReInput.players.GetPlayer(0);
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ namespace MiraAPI.Patches.Keybinds;
|
|||||||
[HarmonyPatch(typeof(ControlMapper))]
|
[HarmonyPatch(typeof(ControlMapper))]
|
||||||
public static class ControlMapperPatches
|
public static class ControlMapperPatches
|
||||||
{
|
{
|
||||||
[HarmonyPrefix]
|
// Patching this crashes for Epic Games users
|
||||||
|
/*[HarmonyPrefix]
|
||||||
[HarmonyPatch(nameof(ControlMapper.Start))]
|
[HarmonyPatch(nameof(ControlMapper.Start))]
|
||||||
private static void StartPrefix(ControlMapper __instance)
|
private static void StartPrefix(ControlMapper __instance)
|
||||||
{
|
{
|
||||||
@@ -37,7 +38,7 @@ public static class ControlMapperPatches
|
|||||||
entry.Value = !entry.Value;
|
entry.Value = !entry.Value;
|
||||||
text.text = $"Show Keybinds: {(entry.Value ? "On" : "Off")}";
|
text.text = $"Show Keybinds: {(entry.Value ? "On" : "Off")}";
|
||||||
}));
|
}));
|
||||||
}
|
}*/
|
||||||
|
|
||||||
[HarmonyPrefix]
|
[HarmonyPrefix]
|
||||||
[HarmonyPatch(nameof(ControlMapper.Update))]
|
[HarmonyPatch(nameof(ControlMapper.Update))]
|
||||||
|
|||||||
@@ -32,11 +32,20 @@ internal static class GameOptionsMenuPatch
|
|||||||
{
|
{
|
||||||
ModifiersUpdate(ref num);
|
ModifiersUpdate(ref num);
|
||||||
}
|
}
|
||||||
|
else if (__instance.name == "CUSTOM TAB 1")
|
||||||
|
{
|
||||||
|
CustomMenuOneUpdate(ref num);
|
||||||
|
}
|
||||||
|
else if (__instance.name == "CUSTOM TAB 2")
|
||||||
|
{
|
||||||
|
CustomMenuTwoUpdate(ref num);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var filteredGroups =
|
var filteredGroups =
|
||||||
GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||||
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
|
.Where(x => x.OptionableType == null &&
|
||||||
|
!x.ShowInModifiersMenu && x.ParentMenu == MenuCategory.Roles) ?? [];
|
||||||
|
|
||||||
foreach (var group in filteredGroups)
|
foreach (var group in filteredGroups)
|
||||||
{
|
{
|
||||||
@@ -99,6 +108,48 @@ internal static class GameOptionsMenuPatch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void CustomMenuOneUpdate(ref float num)
|
||||||
|
{
|
||||||
|
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||||
|
.Where(x => x.ParentMenu == MenuCategory.CustomOne) ?? [];
|
||||||
|
|
||||||
|
foreach (var modGroup in groups)
|
||||||
|
{
|
||||||
|
UpdateGroup(modGroup, ref num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CustomMenuOneCreate(GameOptionsMenu menu)
|
||||||
|
{
|
||||||
|
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||||
|
.Where(x => x.ParentMenu == MenuCategory.CustomOne) ?? [];
|
||||||
|
foreach (var group in groups)
|
||||||
|
{
|
||||||
|
CreateGroup(menu, group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CustomMenuTwoUpdate(ref float num)
|
||||||
|
{
|
||||||
|
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||||
|
.Where(x => x.ParentMenu == MenuCategory.CustomTwo) ?? [];
|
||||||
|
|
||||||
|
foreach (var modGroup in groups)
|
||||||
|
{
|
||||||
|
UpdateGroup(modGroup, ref num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CustomMenuTwoCreate(GameOptionsMenu menu)
|
||||||
|
{
|
||||||
|
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||||
|
.Where(x => x.ParentMenu == MenuCategory.CustomTwo) ?? [];
|
||||||
|
foreach (var group in groups)
|
||||||
|
{
|
||||||
|
CreateGroup(menu, group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void ModifiersUpdate(ref float num)
|
private static void ModifiersUpdate(ref float num)
|
||||||
{
|
{
|
||||||
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||||
@@ -137,9 +188,19 @@ internal static class GameOptionsMenuPatch
|
|||||||
ModifiersCreate(__instance);
|
ModifiersCreate(__instance);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
else if (__instance.name == "CUSTOM TAB 1")
|
||||||
|
{
|
||||||
|
CustomMenuOneCreate(__instance);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (__instance.name == "CUSTOM TAB 2")
|
||||||
|
{
|
||||||
|
CustomMenuTwoCreate(__instance);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var filteredGroups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
var filteredGroups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||||
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
|
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu && x.ParentMenu == MenuCategory.Roles) ?? [];
|
||||||
|
|
||||||
foreach (var group in filteredGroups)
|
foreach (var group in filteredGroups)
|
||||||
{
|
{
|
||||||
@@ -223,6 +284,9 @@ internal static class GameOptionsMenuPatch
|
|||||||
menu.playerOptionOrigin,
|
menu.playerOptionOrigin,
|
||||||
menu.settingsContainer));
|
menu.settingsContainer));
|
||||||
|
|
||||||
|
// TODO: Reimplement this without breaking the menu.
|
||||||
|
// group.Options.ForEach(x => x.ChangeGameSetting());
|
||||||
|
|
||||||
foreach (var newOpt in options)
|
foreach (var newOpt in options)
|
||||||
{
|
{
|
||||||
newOpt.SetClickMask(menu.ButtonClickMask);
|
newOpt.SetClickMask(menu.ButtonClickMask);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
using MiraAPI.GameOptions;
|
||||||
using MiraAPI.Modifiers;
|
using MiraAPI.Modifiers;
|
||||||
using MiraAPI.PluginLoading;
|
using MiraAPI.PluginLoading;
|
||||||
using MiraAPI.Presets;
|
using MiraAPI.Presets;
|
||||||
@@ -27,13 +28,23 @@ internal static class GameSettingMenuPatches
|
|||||||
private static Vector3 _roleBtnOgPos;
|
private static Vector3 _roleBtnOgPos;
|
||||||
private static Vector3 _smallRoleBtnOgPos;
|
private static Vector3 _smallRoleBtnOgPos;
|
||||||
private static Vector3 _modifierBtnOgPos;
|
private static Vector3 _modifierBtnOgPos;
|
||||||
|
private static Vector3 _customOneBtnOgPos;
|
||||||
|
private static Vector3 _customTwoBtnOgPos;
|
||||||
|
|
||||||
private static GameOptionsMenu? _modifiersTab;
|
private static GameOptionsMenu? _modifiersTab;
|
||||||
private static PassiveButton? _modifiersButton;
|
private static PassiveButton? _modifiersButton;
|
||||||
private static PassiveButton? _smallRolesButton;
|
private static PassiveButton? _smallRolesButton;
|
||||||
|
private static GameOptionsMenu? _customOneTab;
|
||||||
|
private static PassiveButton? _customOneButton;
|
||||||
|
private static GameOptionsMenu? _customTwoTab;
|
||||||
|
private static PassiveButton? _customTwoButton;
|
||||||
|
private static GameObject? _nextModButton;
|
||||||
|
private static GameObject? _previousModButton;
|
||||||
|
|
||||||
private static Dictionary<int, Vector3> OptionsPositions { get; } = [];
|
private static Dictionary<int, Vector3> OptionsPositions { get; } = [];
|
||||||
private static Dictionary<int, Vector3> ModifiersPositions { get; } = [];
|
private static Dictionary<int, Vector3> ModifiersPositions { get; } = [];
|
||||||
|
private static Dictionary<int, Vector3> CustomOnePositions { get; } = [];
|
||||||
|
private static Dictionary<int, Vector3> CustomTwoPositions { get; } = [];
|
||||||
|
|
||||||
private static void SaveScrollPositions(GameSettingMenu gameSettingMenu)
|
private static void SaveScrollPositions(GameSettingMenu gameSettingMenu)
|
||||||
{
|
{
|
||||||
@@ -41,6 +52,14 @@ internal static class GameSettingMenuPatches
|
|||||||
{
|
{
|
||||||
ModifiersPositions[SelectedModIdx] = _modifiersTab!.scrollBar.Inner.localPosition;
|
ModifiersPositions[SelectedModIdx] = _modifiersTab!.scrollBar.Inner.localPosition;
|
||||||
}
|
}
|
||||||
|
else if (_customOneTab)
|
||||||
|
{
|
||||||
|
CustomOnePositions[SelectedModIdx] = _customOneTab!.scrollBar.Inner.localPosition;
|
||||||
|
}
|
||||||
|
else if (_customTwoTab)
|
||||||
|
{
|
||||||
|
CustomTwoPositions[SelectedModIdx] = _customTwoTab!.scrollBar.Inner.localPosition;
|
||||||
|
}
|
||||||
|
|
||||||
RoleSettingMenuPatches.RolePositions[SelectedModIdx] = gameSettingMenu.RoleSettingsTab.scrollBar.Inner.localPosition;
|
RoleSettingMenuPatches.RolePositions[SelectedModIdx] = gameSettingMenu.RoleSettingsTab.scrollBar.Inner.localPosition;
|
||||||
OptionsPositions[SelectedModIdx] = gameSettingMenu.GameSettingsTab.scrollBar.Inner.localPosition;
|
OptionsPositions[SelectedModIdx] = gameSettingMenu.GameSettingsTab.scrollBar.Inner.localPosition;
|
||||||
@@ -65,9 +84,21 @@ internal static class GameSettingMenuPatches
|
|||||||
_modifiersTab!.gameObject.SetActive(tabNum == 3);
|
_modifiersTab!.gameObject.SetActive(tabNum == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_customOneTab)
|
||||||
|
{
|
||||||
|
_customOneTab!.gameObject.SetActive(tabNum == 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_customTwoTab)
|
||||||
|
{
|
||||||
|
_customTwoTab!.gameObject.SetActive(tabNum == 5);
|
||||||
|
}
|
||||||
|
|
||||||
_modifiersButton?.SelectButton(tabNum == 3);
|
_modifiersButton?.SelectButton(tabNum == 3);
|
||||||
_smallRolesButton?.SelectButton(tabNum == 2);
|
_smallRolesButton?.SelectButton(tabNum == 2);
|
||||||
if (tabNum == 3)
|
_customOneButton?.SelectButton(tabNum == 4);
|
||||||
|
_customTwoButton?.SelectButton(tabNum == 5);
|
||||||
|
if (tabNum >= 3 && tabNum <= 5)
|
||||||
{
|
{
|
||||||
if (__instance.RoleSettingsButton.gameObject.active)
|
if (__instance.RoleSettingsButton.gameObject.active)
|
||||||
{
|
{
|
||||||
@@ -75,7 +106,18 @@ internal static class GameSettingMenuPatches
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_modifiersButton?.SelectButton(true);
|
switch (tabNum)
|
||||||
|
{
|
||||||
|
case 3:
|
||||||
|
_modifiersButton?.SelectButton(true);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
_customOneButton?.SelectButton(true);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
_customTwoButton?.SelectButton(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,19 +129,30 @@ internal static class GameSettingMenuPatches
|
|||||||
|
|
||||||
_modifiersButton?.SelectButton(tabNum == 3);
|
_modifiersButton?.SelectButton(tabNum == 3);
|
||||||
_smallRolesButton?.SelectButton(tabNum == 2);
|
_smallRolesButton?.SelectButton(tabNum == 2);
|
||||||
|
_customOneButton?.SelectButton(tabNum == 4);
|
||||||
|
_customTwoButton?.SelectButton(tabNum == 5);
|
||||||
|
|
||||||
if (tabNum != 3)
|
if (tabNum >= 3 && tabNum <= 5)
|
||||||
{
|
{
|
||||||
return;
|
if (__instance.RoleSettingsButton.gameObject.active)
|
||||||
}
|
{
|
||||||
|
__instance.RoleSettingsButton.SelectButton(true);
|
||||||
if (__instance.RoleSettingsButton.gameObject.active)
|
}
|
||||||
{
|
else
|
||||||
__instance.RoleSettingsButton.SelectButton(true);
|
{
|
||||||
}
|
switch (tabNum)
|
||||||
else
|
{
|
||||||
{
|
case 3:
|
||||||
_modifiersButton?.SelectButton(true);
|
_modifiersButton?.SelectButton(true);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
_customOneButton?.SelectButton(true);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
_customTwoButton?.SelectButton(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,16 +180,16 @@ internal static class GameSettingMenuPatches
|
|||||||
|
|
||||||
_text.alignment = TextAlignmentOptions.Center;
|
_text.alignment = TextAlignmentOptions.Center;
|
||||||
|
|
||||||
var nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject;
|
_nextModButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject;
|
||||||
nextButton.transform.localPosition = new Vector3(-2.2663f, 1.5272f, -25f);
|
_nextModButton.transform.localPosition = new Vector3(-2.2663f, 1.5272f, -25f);
|
||||||
nextButton.name = "RightArrowButton";
|
_nextModButton.name = "RightArrowButton";
|
||||||
nextButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().sprite =
|
_nextModButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().sprite =
|
||||||
MiraAssets.NextButton.LoadAsset();
|
MiraAssets.NextButton.LoadAsset();
|
||||||
nextButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().sprite =
|
_nextModButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().sprite =
|
||||||
MiraAssets.NextButtonActive.LoadAsset();
|
MiraAssets.NextButtonActive.LoadAsset();
|
||||||
nextButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().DestroyImmediate();
|
_nextModButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().DestroyImmediate();
|
||||||
|
|
||||||
var passiveButton = nextButton.gameObject.GetComponent<PassiveButton>();
|
var passiveButton = _nextModButton.gameObject.GetComponent<PassiveButton>();
|
||||||
passiveButton.OnClick = new ButtonClickedEvent();
|
passiveButton.OnClick = new ButtonClickedEvent();
|
||||||
passiveButton.OnClick.AddListener(
|
passiveButton.OnClick.AddListener(
|
||||||
(UnityAction)(() =>
|
(UnityAction)(() =>
|
||||||
@@ -151,13 +204,13 @@ internal static class GameSettingMenuPatches
|
|||||||
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject;
|
_previousModButton = Object.Instantiate(_nextModButton, __instance.BackButton.transform.parent).gameObject;
|
||||||
backButton.transform.localPosition = new Vector3(-4.4209f, 1.5272f, -25f);
|
_previousModButton.transform.localPosition = new Vector3(-4.4209f, 1.5272f, -25f);
|
||||||
backButton.name = "LeftArrowButton";
|
_previousModButton.name = "LeftArrowButton";
|
||||||
backButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().Destroy();
|
_previousModButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().Destroy();
|
||||||
backButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().flipX =
|
_previousModButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().flipX =
|
||||||
backButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().flipX = true;
|
_previousModButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().flipX = true;
|
||||||
backButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener(
|
_previousModButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener(
|
||||||
(UnityAction)(() =>
|
(UnityAction)(() =>
|
||||||
{
|
{
|
||||||
SaveScrollPositions(__instance);
|
SaveScrollPositions(__instance);
|
||||||
@@ -174,6 +227,61 @@ internal static class GameSettingMenuPatches
|
|||||||
_modifiersTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent);
|
_modifiersTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent);
|
||||||
_modifiersTab.name = "MODIFIERS TAB";
|
_modifiersTab.name = "MODIFIERS TAB";
|
||||||
|
|
||||||
|
// clone game settings tab for both custom tabs
|
||||||
|
_customOneTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent);
|
||||||
|
_customOneTab.name = "CUSTOM TAB 1";
|
||||||
|
_customTwoTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent);
|
||||||
|
_customTwoTab.name = "CUSTOM TAB 2";
|
||||||
|
|
||||||
|
// create button for the first custom category
|
||||||
|
var customPos = new Vector3(
|
||||||
|
__instance.RoleSettingsButton.transform.localPosition.x,
|
||||||
|
__instance.RoleSettingsButton.transform.localPosition.y,
|
||||||
|
__instance.RoleSettingsButton.transform.localPosition.z);
|
||||||
|
_customOneButton = Object.Instantiate(__instance.RoleSettingsButton, __instance.RoleSettingsButton.transform.parent);
|
||||||
|
_customOneButton.buttonText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
|
||||||
|
_customOneButton.OnClick = new ButtonClickedEvent();
|
||||||
|
_customOneButton.OnClick.AddListener(
|
||||||
|
(UnityAction)(() =>
|
||||||
|
{
|
||||||
|
__instance.ChangeTab(4, false);
|
||||||
|
}));
|
||||||
|
_customOneButton.OnMouseOver = new UnityEvent();
|
||||||
|
_customOneButton.OnMouseOver.AddListener(
|
||||||
|
(UnityAction)(() =>
|
||||||
|
{
|
||||||
|
__instance.ChangeTab(4, true);
|
||||||
|
}));
|
||||||
|
|
||||||
|
_customOneButton.buttonText.text = SelectedMod != null ? SelectedMod.MiraPlugin.CustomOptionMenuNameOne : "Custom Category 1";
|
||||||
|
customPos.y -= 0.637f;
|
||||||
|
_customOneButton.transform.localPosition = customPos;
|
||||||
|
_customOneButton.name = "CustomOneButton";
|
||||||
|
|
||||||
|
_customOneBtnOgPos = _customOneButton.transform.localPosition;
|
||||||
|
|
||||||
|
// create button for the first custom category
|
||||||
|
_customTwoButton = Object.Instantiate(_customOneButton, _customOneButton.transform.parent);
|
||||||
|
_customTwoButton.OnClick = new ButtonClickedEvent();
|
||||||
|
_customTwoButton.OnClick.AddListener(
|
||||||
|
(UnityAction)(() =>
|
||||||
|
{
|
||||||
|
__instance.ChangeTab(5, false);
|
||||||
|
}));
|
||||||
|
_customTwoButton.OnMouseOver = new UnityEvent();
|
||||||
|
_customTwoButton.OnMouseOver.AddListener(
|
||||||
|
(UnityAction)(() =>
|
||||||
|
{
|
||||||
|
__instance.ChangeTab(5, true);
|
||||||
|
}));
|
||||||
|
|
||||||
|
_customTwoButton.buttonText.text = SelectedMod != null ? SelectedMod.MiraPlugin.CustomOptionMenuNameTwo : "Custom Category 2";
|
||||||
|
customPos.y -= 0.637f;
|
||||||
|
_customTwoButton.transform.localPosition = customPos;
|
||||||
|
_customTwoButton.name = "CustomTwoButton";
|
||||||
|
|
||||||
|
_customTwoBtnOgPos = _customTwoButton.transform.localPosition;
|
||||||
|
|
||||||
// create button for modifiers
|
// create button for modifiers
|
||||||
__instance.RoleSettingsButton.buttonText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
|
__instance.RoleSettingsButton.buttonText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
|
||||||
__instance.RoleSettingsButton.gameObject.SetActive(false);
|
__instance.RoleSettingsButton.gameObject.SetActive(false);
|
||||||
@@ -304,20 +412,32 @@ internal static class GameSettingMenuPatches
|
|||||||
bool replaceWithModifiers = true;
|
bool replaceWithModifiers = true;
|
||||||
_smallRolesButton!.transform.localPosition = _smallRoleBtnOgPos;
|
_smallRolesButton!.transform.localPosition = _smallRoleBtnOgPos;
|
||||||
_modifiersButton!.transform.localPosition = _modifierBtnOgPos;
|
_modifiersButton!.transform.localPosition = _modifierBtnOgPos;
|
||||||
|
_customOneButton!.transform.localPosition = _customOneBtnOgPos;
|
||||||
|
_customTwoButton!.transform.localPosition = _customTwoBtnOgPos;
|
||||||
menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos;
|
menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos;
|
||||||
|
|
||||||
if (SelectedModIdx != 0)
|
if (SelectedModIdx != 0)
|
||||||
{
|
{
|
||||||
|
_customOneButton.buttonText.text = SelectedMod!.MiraPlugin.CustomOptionMenuNameOne;
|
||||||
|
_customTwoButton.buttonText.text = SelectedMod!.MiraPlugin.CustomOptionMenuNameTwo;
|
||||||
var modHasRoles = SelectedMod!.InternalRoles.Count != 0;
|
var modHasRoles = SelectedMod!.InternalRoles.Count != 0;
|
||||||
|
var modHasCustomOne = SelectedMod.InternalOptionGroups.Exists(
|
||||||
|
x => x.ParentMenu == MenuCategory.CustomOne);
|
||||||
|
var modHasCustomTwo = SelectedMod.InternalOptionGroups.Exists(
|
||||||
|
x => x.ParentMenu == MenuCategory.CustomTwo);
|
||||||
var modHasModifiers = SelectedMod.InternalOptionGroups.Exists(
|
var modHasModifiers = SelectedMod.InternalOptionGroups.Exists(
|
||||||
x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true);
|
x => x.ShowInModifiersMenu || x.ParentMenu == MenuCategory.Modifiers || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true);
|
||||||
var modHasOptions =
|
var modHasOptions =
|
||||||
SelectedMod.InternalOptionGroups.Exists(x => x.OptionableType == null && !x.ShowInModifiersMenu);
|
SelectedMod.InternalOptionGroups.Exists(x => x.OptionableType == null && (!x.ShowInModifiersMenu ||
|
||||||
|
(x.ParentMenu != MenuCategory.Modifiers && x.ParentMenu != MenuCategory.Roles)));
|
||||||
|
|
||||||
_modifiersButton.gameObject.SetActive(true);
|
_modifiersButton.gameObject.SetActive(true);
|
||||||
_smallRolesButton.gameObject.SetActive(true);
|
_smallRolesButton.gameObject.SetActive(true);
|
||||||
|
_customOneButton.gameObject.SetActive(true);
|
||||||
|
_customTwoButton.gameObject.SetActive(true);
|
||||||
menu.GameSettingsButton.gameObject.SetActive(true);
|
menu.GameSettingsButton.gameObject.SetActive(true);
|
||||||
menu.RoleSettingsButton.gameObject.SetActive(false);
|
menu.RoleSettingsButton.gameObject.SetActive(false);
|
||||||
|
var defaultButton = menu.RoleSettingsButton;
|
||||||
|
|
||||||
// If there are no registered custom roles in the selected mod, hide the button.
|
// If there are no registered custom roles in the selected mod, hide the button.
|
||||||
if (!modHasRoles)
|
if (!modHasRoles)
|
||||||
@@ -334,6 +454,7 @@ internal static class GameSettingMenuPatches
|
|||||||
{
|
{
|
||||||
_modifiersButton.gameObject.SetActive(false);
|
_modifiersButton.gameObject.SetActive(false);
|
||||||
menu.RoleSettingsButton.gameObject.SetActive(true);
|
menu.RoleSettingsButton.gameObject.SetActive(true);
|
||||||
|
defaultButton = _modifiersButton;
|
||||||
|
|
||||||
if (_modifiersTab!.gameObject.active)
|
if (_modifiersTab!.gameObject.active)
|
||||||
{
|
{
|
||||||
@@ -356,6 +477,7 @@ internal static class GameSettingMenuPatches
|
|||||||
// If the mod has roles, we can enable the bigger role button and disable the small one.
|
// If the mod has roles, we can enable the bigger role button and disable the small one.
|
||||||
if (modHasRoles)
|
if (modHasRoles)
|
||||||
{
|
{
|
||||||
|
defaultButton = menu.RoleSettingsButton;
|
||||||
menu.RoleSettingsButton.gameObject.SetActive(true);
|
menu.RoleSettingsButton.gameObject.SetActive(true);
|
||||||
_smallRolesButton.gameObject.SetActive(false);
|
_smallRolesButton.gameObject.SetActive(false);
|
||||||
}
|
}
|
||||||
@@ -370,6 +492,7 @@ internal static class GameSettingMenuPatches
|
|||||||
{
|
{
|
||||||
menu.ChangeTab(0, false);
|
menu.ChangeTab(0, false);
|
||||||
}
|
}
|
||||||
|
defaultButton = menu.RoleSettingsButton;
|
||||||
|
|
||||||
// If the mod has roles and modifiers, we can move their buttons to the game settings button position, since nothing is there.
|
// If the mod has roles and modifiers, we can move their buttons to the game settings button position, since nothing is there.
|
||||||
if (menu.RoleSettingsButton.gameObject.active)
|
if (menu.RoleSettingsButton.gameObject.active)
|
||||||
@@ -389,16 +512,37 @@ internal static class GameSettingMenuPatches
|
|||||||
_smallRolesButton.transform.localPosition.z);
|
_smallRolesButton.transform.localPosition.z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the mod has no primary custom tab, we can go ahead and hide the button.
|
||||||
|
if (!modHasCustomOne)
|
||||||
|
{
|
||||||
|
_customOneButton.gameObject.SetActive(false);
|
||||||
|
if (_customOneTab!.gameObject.active)
|
||||||
|
{
|
||||||
|
defaultButton.SelectButton(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the mod has no secondary custom tab, we can go ahead and hide the button.
|
||||||
|
if (!modHasCustomTwo)
|
||||||
|
{
|
||||||
|
_customTwoButton.gameObject.SetActive(false);
|
||||||
|
if (_customTwoTab!.gameObject.active)
|
||||||
|
{
|
||||||
|
defaultButton.SelectButton(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_modifiersButton.gameObject.SetActive(false);
|
_modifiersButton.gameObject.SetActive(false);
|
||||||
_smallRolesButton.gameObject.SetActive(false);
|
_smallRolesButton.gameObject.SetActive(false);
|
||||||
|
_customOneButton.gameObject.SetActive(false);
|
||||||
|
_customTwoButton.gameObject.SetActive(false);
|
||||||
menu.RoleSettingsButton.gameObject.SetActive(true);
|
menu.RoleSettingsButton.gameObject.SetActive(true);
|
||||||
menu.GameSettingsButton.gameObject.SetActive(true);
|
menu.GameSettingsButton.gameObject.SetActive(true);
|
||||||
replaceWithModifiers = false;
|
replaceWithModifiers = false;
|
||||||
|
|
||||||
if (_modifiersTab!.gameObject.active)
|
if (_modifiersTab!.gameObject.active || _customOneTab!.gameObject.active || _customTwoTab!.gameObject.active)
|
||||||
{
|
{
|
||||||
menu.ChangeTab(0, false);
|
menu.ChangeTab(0, false);
|
||||||
}
|
}
|
||||||
@@ -446,7 +590,17 @@ internal static class GameSettingMenuPatches
|
|||||||
|
|
||||||
if (_modifiersTab?.Children?.Count > 0)
|
if (_modifiersTab?.Children?.Count > 0)
|
||||||
{
|
{
|
||||||
CleanupSettings(_modifiersTab, true);
|
CleanupSettings(_modifiersTab, MenuCategory.Modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_customOneTab?.Children?.Count > 0)
|
||||||
|
{
|
||||||
|
CleanupSettings(_customOneTab, MenuCategory.CustomOne);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_customTwoTab?.Children?.Count > 0)
|
||||||
|
{
|
||||||
|
CleanupSettings(_customTwoTab, MenuCategory.CustomTwo);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CleanupRoleSettings(RolesSettingsMenu rolesMenu)
|
void CleanupRoleSettings(RolesSettingsMenu rolesMenu)
|
||||||
@@ -484,7 +638,7 @@ internal static class GameSettingMenuPatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CleanupSettings(GameOptionsMenu gameOptMenu, bool modifiers = false)
|
void CleanupSettings(GameOptionsMenu gameOptMenu, MenuCategory menuCategory = MenuCategory.Roles)
|
||||||
{
|
{
|
||||||
ClearOptions(gameOptMenu.Children);
|
ClearOptions(gameOptMenu.Children);
|
||||||
gameOptMenu.Children = null;
|
gameOptMenu.Children = null;
|
||||||
@@ -495,7 +649,19 @@ internal static class GameSettingMenuPatches
|
|||||||
.ForEach(header => header.gameObject.DestroyImmediate());
|
.ForEach(header => header.gameObject.DestroyImmediate());
|
||||||
|
|
||||||
gameOptMenu.Initialize();
|
gameOptMenu.Initialize();
|
||||||
var positions = modifiers ? ModifiersPositions : OptionsPositions;
|
var positions = OptionsPositions;
|
||||||
|
switch (menuCategory)
|
||||||
|
{
|
||||||
|
case MenuCategory.Modifiers:
|
||||||
|
positions = ModifiersPositions;
|
||||||
|
break;
|
||||||
|
case MenuCategory.CustomOne:
|
||||||
|
positions = CustomOnePositions;
|
||||||
|
break;
|
||||||
|
case MenuCategory.CustomTwo:
|
||||||
|
positions = CustomTwoPositions;
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (positions.TryGetValue(SelectedModIdx, out var pos))
|
if (positions.TryGetValue(SelectedModIdx, out var pos))
|
||||||
{
|
{
|
||||||
gameOptMenu.scrollBar.Inner.localPosition = pos;
|
gameOptMenu.scrollBar.Inner.localPosition = pos;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using AmongUs.GameOptions;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using MiraAPI.GameOptions;
|
using MiraAPI.GameOptions;
|
||||||
using MiraAPI.Modifiers;
|
using MiraAPI.Modifiers;
|
||||||
@@ -163,7 +164,108 @@ public static class LobbyViewPanePatches
|
|||||||
{
|
{
|
||||||
if (SelectedModIdx == 0)
|
if (SelectedModIdx == 0)
|
||||||
{
|
{
|
||||||
return true;
|
float num = 0.95f;
|
||||||
|
float num2 = -6.53f;
|
||||||
|
CategoryHeaderMasked categoryHeaderMasked =
|
||||||
|
Object.Instantiate(__instance.categoryHeaderOrigin, __instance.settingsContainer);
|
||||||
|
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<RoleBehaviour>();
|
||||||
|
for (int i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
var categoryHeaderRoleVariant =
|
||||||
|
Object.Instantiate(__instance.categoryHeaderRoleOrigin, __instance.settingsContainer);
|
||||||
|
categoryHeaderRoleVariant.SetHeader(
|
||||||
|
(i == 0) ? StringNames.CrewmateRolesHeader : StringNames.ImpostorRolesHeader,
|
||||||
|
61);
|
||||||
|
categoryHeaderRoleVariant.transform.localScale = Vector3.one;
|
||||||
|
categoryHeaderRoleVariant.transform.localPosition = new Vector3(0.09f, num, -2f);
|
||||||
|
__instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject);
|
||||||
|
num -= 0.696f;
|
||||||
|
var roles = RoleManager.Instance.AllRoles.ToArray().Where(x =>
|
||||||
|
x is not ICustomRole && x.Role != RoleTypes.Crewmate && x.Role != RoleTypes.Impostor &&
|
||||||
|
x.Role != RoleTypes.CrewmateGhost && x.Role != RoleTypes.ImpostorGhost).ToList();
|
||||||
|
for (int j = 0; j < roles.Count; j++)
|
||||||
|
{
|
||||||
|
var roleBehaviour = roles[j];
|
||||||
|
int chancePerGame =
|
||||||
|
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(
|
||||||
|
roleBehaviour.Role);
|
||||||
|
int numPerGame =
|
||||||
|
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions
|
||||||
|
.GetNumPerGame(roleBehaviour.Role);
|
||||||
|
bool flag = numPerGame == 0;
|
||||||
|
var viewSettingsInfoPanelRoleVariant =
|
||||||
|
Object.Instantiate(
|
||||||
|
__instance.infoPanelRoleOrigin,
|
||||||
|
__instance.settingsContainer);
|
||||||
|
viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one;
|
||||||
|
viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f);
|
||||||
|
if (!flag)
|
||||||
|
{
|
||||||
|
list.Add(roleBehaviour);
|
||||||
|
}
|
||||||
|
|
||||||
|
viewSettingsInfoPanelRoleVariant.SetInfo(
|
||||||
|
roleBehaviour.NiceName,
|
||||||
|
numPerGame,
|
||||||
|
chancePerGame,
|
||||||
|
61,
|
||||||
|
(i == 0) ? Palette.CrewmateRoleBlue : Palette.ImpostorRoleRed,
|
||||||
|
roleBehaviour.RoleIconSolid,
|
||||||
|
i == 0,
|
||||||
|
flag);
|
||||||
|
__instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject);
|
||||||
|
num -= 0.664f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.Count > 0)
|
||||||
|
{
|
||||||
|
var categoryHeaderMasked2 =
|
||||||
|
Object.Instantiate(__instance.categoryHeaderOrigin, __instance.settingsContainer);
|
||||||
|
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 -= 2.1f;
|
||||||
|
float num3 = 0f;
|
||||||
|
for (int k = 0; k < list.Count; k++)
|
||||||
|
{
|
||||||
|
float num4;
|
||||||
|
if (k % 2 == 0)
|
||||||
|
{
|
||||||
|
num4 = -5.8f;
|
||||||
|
if (k > 0)
|
||||||
|
{
|
||||||
|
num -= num3 + 0.85f;
|
||||||
|
num3 = 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
num4 = 0.14999962f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var advancedRoleViewPanel =
|
||||||
|
Object.Instantiate(__instance.advancedRolePanelOrigin, __instance.settingsContainer);
|
||||||
|
advancedRoleViewPanel.transform.localScale = Vector3.one;
|
||||||
|
advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f);
|
||||||
|
float num5 = advancedRoleViewPanel.SetUp(list[k], 0.85f, 61);
|
||||||
|
if (num5 > num3)
|
||||||
|
{
|
||||||
|
num3 = num5;
|
||||||
|
}
|
||||||
|
|
||||||
|
__instance.settingsInfo.Add(advancedRoleViewPanel.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__instance.scrollBar.SetYBoundsMax(-num);
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__instance.currentTab == ModifiersTabName)
|
if (__instance.currentTab == ModifiersTabName)
|
||||||
|
|||||||
@@ -216,7 +216,16 @@ public static class OptionsPatches
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
__instance.Value += __instance.Increment;
|
var increment = __instance.Increment;
|
||||||
|
|
||||||
|
if (Input.GetKey(KeyCode.LeftShift) &&
|
||||||
|
__instance.TryGetComponent<MiraNumberOptionComponent>(out var miraNumber) &&
|
||||||
|
miraNumber.ShiftIncrementToggle)
|
||||||
|
{
|
||||||
|
increment /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
__instance.Value += increment;
|
||||||
if (__instance.Value > __instance.ValidRange.max)
|
if (__instance.Value > __instance.ValidRange.max)
|
||||||
{
|
{
|
||||||
__instance.Value = __instance.ValidRange.min;
|
__instance.Value = __instance.ValidRange.min;
|
||||||
@@ -235,7 +244,16 @@ public static class OptionsPatches
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
__instance.Value -= __instance.Increment;
|
var increment = __instance.Increment;
|
||||||
|
|
||||||
|
if (Input.GetKey(KeyCode.LeftShift) &&
|
||||||
|
__instance.TryGetComponent<MiraNumberOptionComponent>(out var miraNumber) &&
|
||||||
|
miraNumber.ShiftIncrementToggle)
|
||||||
|
{
|
||||||
|
increment /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
__instance.Value -= increment;
|
||||||
if (__instance.Value < __instance.ValidRange.min)
|
if (__instance.Value < __instance.ValidRange.min)
|
||||||
{
|
{
|
||||||
__instance.Value = __instance.ValidRange.max;
|
__instance.Value = __instance.ValidRange.max;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ public static class SettingPatches
|
|||||||
MiraNumberSuffixes.None => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo),
|
MiraNumberSuffixes.None => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo),
|
||||||
MiraNumberSuffixes.Multiplier => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "x",
|
MiraNumberSuffixes.Multiplier => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "x",
|
||||||
MiraNumberSuffixes.Percent => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "%",
|
MiraNumberSuffixes.Percent => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "%",
|
||||||
|
MiraNumberSuffixes.Distance => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + MiraApiPlugin.DistanceSuffix.Value,
|
||||||
_ => TranslationController.Instance.GetString(
|
_ => TranslationController.Instance.GetString(
|
||||||
StringNames.GameSecondsAbbrev,
|
StringNames.GameSecondsAbbrev,
|
||||||
(Il2CppSystem.Object[])[value.ToString(__instance.FormatString, CultureInfo.InvariantCulture)]),
|
(Il2CppSystem.Object[])[value.ToString(__instance.FormatString, CultureInfo.InvariantCulture)]),
|
||||||
|
|||||||
@@ -127,17 +127,19 @@ internal static class PlayerControlPatches
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var role = __instance.Data?.Role;
|
||||||
foreach (var button in CustomButtonManager.CustomButtons)
|
foreach (var button in CustomButtonManager.CustomButtons)
|
||||||
{
|
{
|
||||||
if (__instance.Data?.Role == null)
|
if (role == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!button.Enabled(__instance.Data?.Role))
|
if (!button.Enabled(role))
|
||||||
{
|
{
|
||||||
|
button.SetActive(false, role);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,16 @@ public interface IMiraPlugin
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
string OptionsTitleText { get; }
|
string OptionsTitleText { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name for the first custom category in the game options menu, if any.
|
||||||
|
/// </summary>
|
||||||
|
public virtual string CustomOptionMenuNameOne => "Custom Category 1";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name for the second custom category in the game options menu, if any.
|
||||||
|
/// </summary>
|
||||||
|
public virtual string CustomOptionMenuNameTwo => "Custom Category 2";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the BepInEx configuration file for the plugin.
|
/// Gets the BepInEx configuration file for the plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace MiraAPI.Utilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum serializer for converting enums to and from byte arrays.
|
||||||
|
/// </summary>
|
||||||
|
public static class EnumSerializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes an enum to an array of bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The enum value to serialize.</param>
|
||||||
|
/// <typeparam name="T">The enum type.</typeparam>
|
||||||
|
/// <returns>A byte array representing the enum value.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the underlying type is not recognized.</exception>
|
||||||
|
public static byte[] EnumToBytes<T>(T value) where T : struct, Enum
|
||||||
|
{
|
||||||
|
var underlyingType = typeof(T).GetEnumUnderlyingType();
|
||||||
|
|
||||||
|
return underlyingType switch
|
||||||
|
{
|
||||||
|
not null when underlyingType == typeof(byte) => [(byte)(object)value],
|
||||||
|
not null when underlyingType == typeof(sbyte) => [(byte)(sbyte)(object)value],
|
||||||
|
not null when underlyingType == typeof(short) => BitConverter.GetBytes((short)(object)value),
|
||||||
|
not null when underlyingType == typeof(ushort) => BitConverter.GetBytes((ushort)(object)value),
|
||||||
|
not null when underlyingType == typeof(int) => BitConverter.GetBytes((int)(object)value),
|
||||||
|
not null when underlyingType == typeof(uint) => BitConverter.GetBytes((uint)(object)value),
|
||||||
|
not null when underlyingType == typeof(long) => BitConverter.GetBytes((long)(object)value),
|
||||||
|
not null when underlyingType == typeof(ulong) => BitConverter.GetBytes((ulong)(object)value),
|
||||||
|
_ => throw new ArgumentException("Unknown underlying type for " + typeof(T).Name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts bytes to an enum using the underlying type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">The byte array to convert.</param>
|
||||||
|
/// <param name="offset">The offset in the byte array to start reading from.</param>
|
||||||
|
/// <param name="underlyingType">The underlying type of the enum.</param>
|
||||||
|
/// <typeparam name="T">The enum type.</typeparam>
|
||||||
|
/// <returns>The enum value represented by the bytes.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the underlying type is not recognized.</exception>
|
||||||
|
public static T EnumFromBytes<T>(byte[] bytes, int offset, Type underlyingType) where T : struct, Enum
|
||||||
|
{
|
||||||
|
return underlyingType switch
|
||||||
|
{
|
||||||
|
{ } t when t == typeof(byte) || t == typeof(sbyte) => (T)(object)bytes[offset],
|
||||||
|
{ } t when t == typeof(short) => (T)(object)BitConverter.ToInt16(bytes, offset),
|
||||||
|
{ } t when t == typeof(ushort) => (T)(object)BitConverter.ToUInt16(bytes, offset),
|
||||||
|
{ } t when t == typeof(int) => (T)(object)BitConverter.ToInt32(bytes, offset),
|
||||||
|
{ } t when t == typeof(uint) => (T)(object)BitConverter.ToUInt32(bytes, offset),
|
||||||
|
{ } t when t == typeof(long) => (T)(object)BitConverter.ToInt64(bytes, offset),
|
||||||
|
{ } t when t == typeof(ulong) => (T)(object)BitConverter.ToUInt64(bytes, offset),
|
||||||
|
_ => throw new ArgumentException("Unknown underlying type for " + typeof(T).Name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EnumFromBytes{T}(byte[], int, Type)"/>
|
||||||
|
public static T EnumFromBytes<T>(byte[] bytes, int offset) where T : struct, Enum => EnumFromBytes<T>(bytes, offset, typeof(T).GetEnumUnderlyingType());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a byte array into a collection of enum values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">The byte array to convert.</param>
|
||||||
|
/// <typeparam name="T">The enum type.</typeparam>
|
||||||
|
/// <returns>An enumerable collection of enum values.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the byte array length is not a multiple of the enum's underlying type size.</exception>
|
||||||
|
public static IEnumerable<T> EnumsFromBytes<T>(byte[] bytes) where T : struct, Enum
|
||||||
|
{
|
||||||
|
var underlyingType = typeof(T).GetEnumUnderlyingType();
|
||||||
|
var size = GetManagedSize(underlyingType);
|
||||||
|
|
||||||
|
if (bytes.Length % size != 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Byte array length must be a multiple of " + size + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
return EnumsFromBytesIterator<T>(bytes, underlyingType, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<T> EnumsFromBytesIterator<T>(byte[] bytes, Type underlyingType, int size) where T : struct, Enum
|
||||||
|
{
|
||||||
|
var count = bytes.Length / size;
|
||||||
|
var underlyingArray = Array.CreateInstance(underlyingType, count);
|
||||||
|
Buffer.BlockCopy(bytes, 0, underlyingArray, 0, bytes.Length);
|
||||||
|
|
||||||
|
foreach (var value in underlyingArray)
|
||||||
|
yield return (T)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a collection of enum values to a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values"> The collection of enum values to convert.</param>
|
||||||
|
/// <typeparam name="T">The enum type.</typeparam>
|
||||||
|
/// <returns>A byte array representing the collection of enum values.</returns>
|
||||||
|
public static byte[] EnumsToBytes<T>(IEnumerable<T> values) where T : struct, Enum
|
||||||
|
{
|
||||||
|
var underlyingType = typeof(T).GetEnumUnderlyingType();
|
||||||
|
var size = GetManagedSize(underlyingType);
|
||||||
|
var valuesArray = values.ToArray();
|
||||||
|
var count = valuesArray.Length;
|
||||||
|
|
||||||
|
var underlyingArray = Array.CreateInstance(underlyingType, count);
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
underlyingArray.SetValue(ConvertToUnderlyingType(valuesArray[i], underlyingType), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = new byte[count * size];
|
||||||
|
Buffer.BlockCopy(underlyingArray, 0, bytes, 0, bytes.Length);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object ConvertToUnderlyingType<T>(T value, Type underlyingType) where T : struct, Enum
|
||||||
|
{
|
||||||
|
return underlyingType switch
|
||||||
|
{
|
||||||
|
not null when underlyingType == typeof(byte) => (byte)(object)value,
|
||||||
|
not null when underlyingType == typeof(sbyte) => (sbyte)(object)value,
|
||||||
|
not null when underlyingType == typeof(short) => (short)(object)value,
|
||||||
|
not null when underlyingType == typeof(ushort) => (ushort)(object)value,
|
||||||
|
not null when underlyingType == typeof(int) => (int)(object)value,
|
||||||
|
not null when underlyingType == typeof(uint) => (uint)(object)value,
|
||||||
|
not null when underlyingType == typeof(long) => (long)(object)value,
|
||||||
|
not null when underlyingType == typeof(ulong) => (ulong)(object)value,
|
||||||
|
_ => throw new InvalidOperationException("Unsupported underlying type."),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetManagedSize(Type type)
|
||||||
|
{
|
||||||
|
return Type.GetTypeCode(type) switch
|
||||||
|
{
|
||||||
|
TypeCode.Byte or TypeCode.SByte => 1,
|
||||||
|
TypeCode.Int16 or TypeCode.UInt16 => 2,
|
||||||
|
TypeCode.Int32 or TypeCode.UInt32 => 4,
|
||||||
|
TypeCode.Int64 or TypeCode.UInt64 => 8,
|
||||||
|
_ => throw new ArgumentException("Unsupported underlying type: " + type.Name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -12,6 +13,7 @@ using TMPro;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using MethodBase = System.Reflection.MethodBase;
|
using MethodBase = System.Reflection.MethodBase;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
using Random = UnityEngine.Random;
|
||||||
|
|
||||||
namespace MiraAPI.Utilities;
|
namespace MiraAPI.Utilities;
|
||||||
|
|
||||||
@@ -522,6 +524,79 @@ public static class Helpers
|
|||||||
(Il2CppSystem.Object[])[value.ToString(formatString, CultureInfo.InvariantCulture)]),
|
(Il2CppSystem.Object[])[value.ToString(formatString, CultureInfo.InvariantCulture)]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Shorthand for <see cref="string.Split(char, StringSplitOptions)"/> that removes empty entries and trims leading and ending spaces.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The string to split.</param>
|
||||||
|
/// <param name="separator">The separator.</param>
|
||||||
|
/// <returns>An array of substrings.</returns>
|
||||||
|
public static string[] TrueSplit(this string value, char separator) => value.Split(separator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Joins a collection of items that are separated by a character.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="separator">The separator.</param>
|
||||||
|
/// <param name="items">The collection of items to string together.</param>
|
||||||
|
/// <typeparam name="T">The type of the items.</typeparam>
|
||||||
|
/// <returns>A string formed by concatenating the ToString results of each item, each separated by a separator.</returns>
|
||||||
|
public static string Join<T>(char separator, IEnumerable<T> items) => Join(separator.ToString(), items);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Join{T}(char, IEnumerable{T})"/>
|
||||||
|
public static string Join<T>(string separator, IEnumerable<T> items)
|
||||||
|
{
|
||||||
|
if (!items.Any())
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var result = string.Empty;
|
||||||
|
items.Do(x => result += $"{separator}{x}");
|
||||||
|
return result[separator.Length..];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shorthand helper for overriding the on click listeners for PassiveButton.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="passive">The PassiveButton being modified.</param>
|
||||||
|
/// <param name="action">The action that's replacing the original listeners.</param>
|
||||||
|
public static void OverrideOnClickListeners(this PassiveButton passive, Action action)
|
||||||
|
{
|
||||||
|
if (!passive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
passive.OnClick?.RemoveAllListeners();
|
||||||
|
passive.OnClick = new();
|
||||||
|
passive.OnClick.AddListener(action);
|
||||||
|
passive.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shorthand helper for overriding the on mouse over listeners for PassiveButton.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="OverrideOnClickListeners"/>
|
||||||
|
public static void OverrideOnMouseOverListeners(this PassiveButton passive, Action action)
|
||||||
|
{
|
||||||
|
if (!passive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
passive.OnMouseOver?.RemoveAllListeners();
|
||||||
|
passive.OnMouseOver = new();
|
||||||
|
passive.OnMouseOver.AddListener(action);
|
||||||
|
passive.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shorthand helper for overriding the on mouse out listeners for PassiveButton.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="OverrideOnClickListeners"/>
|
||||||
|
public static void OverrideOnMouseOutListeners(this PassiveButton passive, Action action)
|
||||||
|
{
|
||||||
|
if (!passive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
passive.OnMouseOut?.RemoveAllListeners();
|
||||||
|
passive.OnMouseOut = new();
|
||||||
|
passive.OnMouseOut.AddListener(action);
|
||||||
|
passive.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the string of a role.
|
/// Returns the string of a role.
|
||||||
|
|||||||
@@ -24,4 +24,9 @@ public enum MiraNumberSuffixes
|
|||||||
/// Percentage suffix (%).
|
/// Percentage suffix (%).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Percent,
|
Percent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display in units.
|
||||||
|
/// </summary>
|
||||||
|
Distance,
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user