mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-19 15:51:48 +00:00
Merge remote-tracking branch 'origin/presets' into dev
# Conflicts: # MiraAPI/GameOptions/IModdedOption.cs # MiraAPI/GameOptions/ModdedOptionsManager.cs # MiraAPI/GameOptions/OptionTypes/ModdedOption.cs # MiraAPI/Patches/Options/GameSettingMenuPatches.cs # MiraAPI/Patches/Options/LobbyViewPanePatches.cs # MiraAPI/Patches/Options/RoleSettingMenuPatches.cs # MiraAPI/Utilities/Assets/MiraAssets.cs # MiraAPI/Utilities/Assets/SpriteTools.cs # MiraAPI/Utilities/Extensions.cs
This commit is contained in:
commit
5628b79814
29 files changed
+1103
-163
No files matched your search
@@ -46,6 +46,11 @@ public interface IModdedOption
|
||||
/// </summary>
|
||||
Func<bool> Visible { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the option should be included with presets.
|
||||
/// </summary>
|
||||
bool IncludeInPreset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ConfigDefinition for the option, used for BepInEx configuration.
|
||||
/// </summary>
|
||||
@@ -78,4 +83,23 @@ public interface IModdedOption
|
||||
/// </summary>
|
||||
/// <param name="data">>The byte array representing the network data.</param>
|
||||
void HandleNetData(byte[] data);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the option to a preset configuration file.
|
||||
/// </summary>
|
||||
/// <param name="presetConfig">The ConfigFile representing the preset configuration.</param>
|
||||
/// <param name="saveDefault">Indicates whether to save the default value instead of the current value.</param>
|
||||
void SaveToPreset(ConfigFile presetConfig, bool saveDefault = false);
|
||||
|
||||
/// <summary>
|
||||
/// Binds the option to a configuration file.
|
||||
/// </summary>
|
||||
/// <param name="config">The ConfigFile to bind the option to.</param>
|
||||
void Bind(ConfigFile config);
|
||||
|
||||
/// <summary>
|
||||
/// Loads the option from a preset configuration file, applying the values to the option's configuration.
|
||||
/// </summary>
|
||||
/// <param name="presetConfig">The ConfigFile representing the preset configuration.</param>
|
||||
void LoadFromPreset(ConfigFile presetConfig);
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public static class ModdedOptionsManager
|
||||
|
||||
Groups.Add(group);
|
||||
TypeToGroup.Add(type, group);
|
||||
pluginInfo.OptionGroups.Add(group);
|
||||
pluginInfo.InternalOptionGroups.Add(group);
|
||||
|
||||
typeof(OptionGroupSingleton<>).MakeGenericType(type)
|
||||
#pragma warning disable S3011
|
||||
@@ -122,7 +122,7 @@ public static class ModdedOptionsManager
|
||||
option.ConfigDefinition = new ConfigDefinition(groupName, propertyName);
|
||||
|
||||
option.ParentMod = pluginInfo.MiraPlugin;
|
||||
pluginInfo.Options.Add(option);
|
||||
pluginInfo.InternalOptions.Add(option);
|
||||
ModdedOptions.Add(option.Id, option);
|
||||
group.Options.Add(option);
|
||||
}
|
||||
@@ -143,7 +143,7 @@ public static class ModdedOptionsManager
|
||||
// we dont know how other plugins handle their configs
|
||||
// this way, all the options are saved at once, instead of one by one
|
||||
var oldConfigSetting = new Dictionary<MiraPluginInfo, bool>();
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
oldConfigSetting.Add(plugin, plugin.PluginConfig.SaveOnConfigSet);
|
||||
plugin.PluginConfig.SaveOnConfigSet = false;
|
||||
@@ -159,7 +159,7 @@ public static class ModdedOptionsManager
|
||||
option.HandleNetData(netData.Data);
|
||||
}
|
||||
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
plugin.PluginConfig.Save();
|
||||
plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin];
|
||||
|
||||
@@ -18,7 +18,7 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
/// <summary>
|
||||
/// Gets the string values of the enum.
|
||||
/// </summary>
|
||||
public string[]? Values { get; }
|
||||
public string[] Values { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedEnumOption"/> class.
|
||||
@@ -27,7 +27,8 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
/// <param name="defaultValue">The default value as an int.</param>
|
||||
/// <param name="enumType">The Enum type.</param>
|
||||
/// <param name="values">An option list of string values to use in place of the enum name.</param>
|
||||
public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null) : base(title, defaultValue)
|
||||
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
||||
public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null, bool includeInPreset=true) : base(title, defaultValue, includeInPreset)
|
||||
{
|
||||
Values = values ?? Enum.GetNames(enumType);
|
||||
Data = ScriptableObject.CreateInstance<StringGameSetting>();
|
||||
@@ -37,7 +38,7 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
data.Type = global::OptionTypes.String;
|
||||
data.Values = values is null ?
|
||||
Enum.GetNames(enumType).Select(CustomStringName.CreateAndRegister).ToArray()
|
||||
: values.Select(CustomStringName.CreateAndRegister).ToArray();
|
||||
: [.. values.Select(CustomStringName.CreateAndRegister)];
|
||||
|
||||
data.Index = Value;
|
||||
}
|
||||
@@ -87,7 +88,7 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(int newValue)
|
||||
{
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data?.GetValueString(newValue), false);
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
@@ -109,7 +110,7 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
/// <summary>
|
||||
/// Gets the string values of the enum.
|
||||
/// </summary>
|
||||
public string[]? Values { get; }
|
||||
public string[] Values { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedEnumOption{T}"/> class.
|
||||
@@ -117,7 +118,8 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
/// <param name="title">The title of the option.</param>
|
||||
/// <param name="defaultValue">The default value as an int.</param>
|
||||
/// <param name="values">An option list of string values to use in place of the enum name.</param>
|
||||
public ModdedEnumOption(string title, T defaultValue, string[]? values = null) : base(title, defaultValue)
|
||||
/// <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)
|
||||
{
|
||||
Values = values ?? Enum.GetNames(typeof(T));
|
||||
Data = ScriptableObject.CreateInstance<StringGameSetting>();
|
||||
@@ -127,7 +129,7 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
data.Type = global::OptionTypes.String;
|
||||
data.Values = values is null ?
|
||||
Enum.GetNames(typeof(T)).Select(CustomStringName.CreateAndRegister).ToArray()
|
||||
: values.Select(CustomStringName.CreateAndRegister).ToArray();
|
||||
: [.. values.Select(CustomStringName.CreateAndRegister)];
|
||||
|
||||
data.Index = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
/// <param name="suffixType">The suffix type.</param>
|
||||
/// <param name="formatString">Optional format string for the option screen.</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>
|
||||
public ModdedNumberOption(
|
||||
string title,
|
||||
float defaultValue,
|
||||
@@ -56,7 +57,8 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
float increment,
|
||||
MiraNumberSuffixes suffixType,
|
||||
string? formatString = null,
|
||||
bool zeroInfinity = false) : base(title, defaultValue)
|
||||
bool zeroInfinity = false,
|
||||
bool includeInPreset = true) : base(title, defaultValue, includeInPreset)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
|
||||
@@ -4,6 +4,7 @@ using MiraAPI.Networking;
|
||||
using MiraAPI.PluginLoading;
|
||||
using Reactor.Localization.Utilities;
|
||||
using Reactor.Networking.Rpc;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.GameOptions.OptionTypes;
|
||||
@@ -16,29 +17,19 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
{
|
||||
private IMiraPlugin? _parentMod;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public uint Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the title of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public string Title { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the StringName object of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public StringNames StringName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the BaseGameSetting data of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public BaseGameSetting Data { get; protected set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parent mod of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public IMiraPlugin? ParentMod
|
||||
{
|
||||
get => _parentMod;
|
||||
@@ -66,19 +57,16 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
/// </summary>
|
||||
public Action<T>? ChangedEvent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the visibility of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public Func<bool> Visible { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the option behaviour of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public bool IncludeInPreset { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public OptionBehaviour? OptionBehaviour { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the config definition of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public ConfigDefinition? ConfigDefinition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -86,7 +74,8 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
/// </summary>
|
||||
/// <param name="title">The option title.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
protected ModdedOption(string title, T defaultValue)
|
||||
/// <param name="includeInPreset">Whether to include the option in the preset.</param>
|
||||
protected ModdedOption(string title, T defaultValue, bool includeInPreset = true)
|
||||
{
|
||||
Id = ModdedOptionsManager.NextId;
|
||||
Title = title;
|
||||
@@ -94,6 +83,7 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
Value = defaultValue;
|
||||
StringName = CustomStringName.CreateAndRegister(Title);
|
||||
Visible = () => true;
|
||||
IncludeInPreset = includeInPreset;
|
||||
}
|
||||
|
||||
internal void ValueChanged(OptionBehaviour optionBehaviour)
|
||||
@@ -105,7 +95,8 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
/// Sets the value of the option.
|
||||
/// </summary>
|
||||
/// <param name="newValue">The new value.</param>
|
||||
public void SetValue(T newValue)
|
||||
/// <param name="sendRpc">Whether to send the value to other players.</param>
|
||||
public void SetValue(T newValue, bool sendRpc = true)
|
||||
{
|
||||
var oldVal = Value;
|
||||
Value = newValue;
|
||||
@@ -115,7 +106,7 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
ChangedEvent?.Invoke(Value);
|
||||
}
|
||||
|
||||
if (AmongUsClient.Instance.AmHost)
|
||||
if (sendRpc && AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
if (ParentMod?.GetConfigFile().TryGetEntry<T>(ConfigDefinition, out var entry) == true)
|
||||
{
|
||||
@@ -128,22 +119,44 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
OnValueChanged(newValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the float data of the option.
|
||||
/// </summary>
|
||||
/// <returns>A float object representing the option's value.</returns>
|
||||
/// <inheritdoc />
|
||||
public void SaveToPreset(ConfigFile presetConfig, bool saveDefault=false)
|
||||
{
|
||||
if (ConfigDefinition is null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Attempted to save {Title} to preset, but ConfigDefinition is null.");
|
||||
return;
|
||||
}
|
||||
Bind(presetConfig);
|
||||
presetConfig[ConfigDefinition].BoxedValue = saveDefault ? DefaultValue : Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Bind(ConfigFile config)
|
||||
{
|
||||
config.Bind(ConfigDefinition, DefaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void LoadFromPreset(ConfigFile presetConfig)
|
||||
{
|
||||
if (presetConfig.TryGetEntry(ConfigDefinition, out ConfigEntry<T> entry))
|
||||
{
|
||||
SetValue(entry.Value, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Attempted to load {Title} from preset, but ConfigDefinition is not found in preset.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract float GetFloatData();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the net data of the option.
|
||||
/// </summary>
|
||||
/// <returns>A NetData object representing this option's data.</returns>
|
||||
/// <inheritdoc />
|
||||
public abstract NetData GetNetData();
|
||||
|
||||
/// <summary>
|
||||
/// Handles incoming net data.
|
||||
/// </summary>
|
||||
/// <param name="data">The NetData's byte array.</param>
|
||||
/// <inheritdoc />
|
||||
public abstract void HandleNetData(byte[] data);
|
||||
|
||||
/// <summary>
|
||||
@@ -159,14 +172,7 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
/// <returns>The value.</returns>
|
||||
public abstract T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the option behaviour.
|
||||
/// </summary>
|
||||
/// <param name="toggleOpt">The ToggleOption prefab.</param>
|
||||
/// <param name="numberOpt">The NumberOption prefab.</param>
|
||||
/// <param name="stringOpt">The StringOption prefab.</param>
|
||||
/// <param name="container">The options container.</param>
|
||||
/// <returns>A new OptionBehaviour for this modded option.</returns>
|
||||
/// <inheritdoc />
|
||||
public abstract OptionBehaviour CreateOption(
|
||||
ToggleOption toggleOpt,
|
||||
NumberOption numberOpt,
|
||||
|
||||
@@ -15,7 +15,8 @@ public class ModdedToggleOption : ModdedOption<bool>
|
||||
/// </summary>
|
||||
/// <param name="title">The option title.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
public ModdedToggleOption(string title, bool defaultValue) : base(title, defaultValue)
|
||||
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
||||
public ModdedToggleOption(string title, bool defaultValue, bool includeInPreset=true) : base(title, defaultValue, includeInPreset)
|
||||
{
|
||||
Data = ScriptableObject.CreateInstance<CheckboxGameSetting>();
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ public static class CustomButtonManager
|
||||
}
|
||||
|
||||
CustomButtons.Add(button);
|
||||
pluginInfo.Buttons.Add(button);
|
||||
pluginInfo.InternalButtons.Add(button);
|
||||
typeof(CustomButtonSingleton<>).MakeGenericType(buttonType)
|
||||
#pragma warning disable S3011
|
||||
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
/// <summary>
|
||||
/// Simple input box for user input.
|
||||
/// </summary>
|
||||
[RegisterInIl2Cpp]
|
||||
public class InputBox : MonoBehaviour
|
||||
{
|
||||
public InputField inputField = null!;
|
||||
|
||||
public void CreateDialog(string title, string placeholder, string submitText, Action<string> onSubmit)
|
||||
{
|
||||
var canvasObj = new GameObject("DialogCanvas");
|
||||
canvasObj.transform.SetParent(transform);
|
||||
canvasObj.layer = LayerMask.NameToLayer("UI");
|
||||
|
||||
var canvas = canvasObj.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.overrideSorting = true;
|
||||
canvas.sortingOrder = 1000; // Ensure the canvas is on top of other UI elements
|
||||
|
||||
canvasObj.AddComponent<CanvasScaler>().uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
canvasObj.AddComponent<GraphicRaycaster>();
|
||||
|
||||
var panelObj = new GameObject("DialogPanel");
|
||||
panelObj.transform.SetParent(canvasObj.transform);
|
||||
var panelRect = panelObj.AddComponent<RectTransform>();
|
||||
var img = panelObj.AddComponent<Image>();
|
||||
|
||||
img.color = new Color(1, 1, 1, 0.9f);
|
||||
img.raycastTarget = true;
|
||||
img.sprite = MiraAssets.RoundedBox.LoadAsset();
|
||||
img.type = Image.Type.Sliced;
|
||||
|
||||
panelRect.sizeDelta = new Vector2(300, 200);
|
||||
panelRect.anchoredPosition = Vector2.zero;
|
||||
|
||||
var titleText = CreateText(panelObj, title);
|
||||
titleText.alignment = TextAnchor.MiddleCenter;
|
||||
titleText.fontSize = 26;
|
||||
titleText.fontStyle = FontStyle.Bold;
|
||||
titleText.color = Color.white;
|
||||
|
||||
var titleRect = titleText.GetComponent<RectTransform>();
|
||||
titleRect.sizeDelta = new Vector2(280, 50);
|
||||
titleRect.anchoredPosition = new Vector2(0, 70);
|
||||
|
||||
var inputObj = new GameObject("InputField");
|
||||
inputObj.transform.SetParent(panelObj.transform);
|
||||
var inputRect = inputObj.AddComponent<RectTransform>();
|
||||
inputRect.sizeDelta = new Vector2(220, 40);
|
||||
inputRect.anchoredPosition = new Vector2(0, 20);
|
||||
|
||||
var inputBg = inputObj.AddComponent<Image>();
|
||||
inputBg.color = new Color(1, 1, 1, 0.8f);
|
||||
|
||||
inputField = inputObj.AddComponent<InputField>();
|
||||
inputField.textComponent = CreateText(inputObj, string.Empty, 18, Color.black);
|
||||
inputField.placeholder = CreateText(inputObj, placeholder, 18, new Color(0.5f, 0.5f, 0.5f));
|
||||
|
||||
var buttonObj = new GameObject("SubmitButton");
|
||||
buttonObj.transform.SetParent(panelObj.transform);
|
||||
var button = buttonObj.AddComponent<Button>();
|
||||
var buttonRect = buttonObj.AddComponent<RectTransform>();
|
||||
buttonRect.sizeDelta = new Vector2(150, 50);
|
||||
buttonRect.anchoredPosition = new Vector2(0, -50);
|
||||
|
||||
var buttonImg = buttonObj.AddComponent<Image>();
|
||||
buttonImg.color = new Color(1f, 1f, 1f, 1f);
|
||||
buttonImg.sprite = MiraAssets.RoundedBox.LoadAsset();
|
||||
buttonImg.type = Image.Type.Sliced;
|
||||
|
||||
var buttonText = CreateText(buttonObj, submitText, 20, Color.white);
|
||||
buttonText.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
var colors = button.colors;
|
||||
colors.highlightedColor = Color.green;
|
||||
colors.pressedColor = MiraAssets.AcceptedTeal;
|
||||
button.colors = colors;
|
||||
|
||||
button.onClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
onSubmit(inputField.text);
|
||||
DestroyImmediate(canvasObj);
|
||||
}));
|
||||
|
||||
inputField.ActivateInputField();
|
||||
}
|
||||
|
||||
private static Text CreateText(GameObject parent, string textContent = "", int fontSize = 20, Color? textColor = null, Font? font = null)
|
||||
{
|
||||
var textObj = new GameObject("Text");
|
||||
textObj.transform.SetParent(parent.transform);
|
||||
|
||||
var text = textObj.AddComponent<Text>();
|
||||
text.font = font ?? Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||
text.text = textContent;
|
||||
text.color = textColor ?? Color.white;
|
||||
text.fontSize = fontSize;
|
||||
text.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
var rect = textObj.GetComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(200, 40);
|
||||
rect.anchoredPosition = Vector2.zero;
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -40,8 +40,8 @@ public abstract class BaseModifier : IOptionable
|
||||
/// Gets the parent mod of the modifier.
|
||||
/// </summary>
|
||||
public MiraPluginInfo ParentMod => Array.Find(
|
||||
MiraPluginManager.Instance.RegisteredPlugins(),
|
||||
x => x.Modifiers.Exists(y => y.TypeId == TypeId)
|
||||
MiraPluginManager.Instance.RegisteredPlugins,
|
||||
x => x.InternalModifiers.Exists(y => y.TypeId == TypeId)
|
||||
) ?? throw new InvalidOperationException("Modifier is not registered.");
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -74,7 +74,7 @@ public static class ModifierManager
|
||||
modifier = (BaseModifier)FormatterServices.GetUninitializedObject(modifierType);
|
||||
}
|
||||
|
||||
info.Modifiers.Add(modifier);
|
||||
info.InternalModifiers.Add(modifier);
|
||||
|
||||
if (modifier is not GameModifier gameModifier)
|
||||
{
|
||||
|
||||
@@ -67,9 +67,9 @@ internal static class TaskAdderPatches
|
||||
modifiersFolder.FolderName = "Modifiers";
|
||||
modifiersFolder.name = "ModifiersFolder";
|
||||
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
if (plugin.CustomRoles.Any(x => x.Value is ICustomRole { Configuration.ShowInFreeplay: true }))
|
||||
if (plugin.InternalRoles.Any(x => x.Value is ICustomRole { Configuration.ShowInFreeplay: true }))
|
||||
{
|
||||
var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
|
||||
newFolder.name = plugin.PluginId+":Roles";
|
||||
@@ -78,7 +78,7 @@ internal static class TaskAdderPatches
|
||||
rolesFolder.SubFolders.Add(newFolder);
|
||||
}
|
||||
|
||||
if (plugin.Modifiers.Exists(x => x.ShowInFreeplay))
|
||||
if (plugin.InternalModifiers.Exists(x => x.ShowInFreeplay))
|
||||
{
|
||||
var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
|
||||
newFolder.name = plugin.PluginId+":Modifiers";
|
||||
@@ -317,9 +317,9 @@ internal static class TaskAdderPatches
|
||||
ref float num2,
|
||||
ref float num3)
|
||||
{
|
||||
for (var m = 0; m < plugin.Modifiers.Count; m++)
|
||||
for (var m = 0; m < plugin.InternalModifiers.Count; m++)
|
||||
{
|
||||
var modifier = plugin.Modifiers[m];
|
||||
var modifier = plugin.InternalModifiers[m];
|
||||
if (!modifier.ShowInFreeplay)
|
||||
{
|
||||
continue;
|
||||
@@ -368,9 +368,9 @@ internal static class TaskAdderPatches
|
||||
|
||||
private static void PluginRoles(TaskAdderGame instance, MiraPluginInfo plugin, ref float num, ref float num2, ref float num3)
|
||||
{
|
||||
for (var m = 0; m < plugin.CustomRoles.Count; m++)
|
||||
for (var m = 0; m < plugin.InternalRoles.Count; m++)
|
||||
{
|
||||
var roleBehaviour = plugin.CustomRoles.ElementAt(m).Value;
|
||||
var roleBehaviour = plugin.InternalRoles.ElementAt(m).Value;
|
||||
if (roleBehaviour.Role == RoleTypes.ImpostorGhost || roleBehaviour.Role == RoleTypes.CrewmateGhost ||
|
||||
roleBehaviour is ICustomRole { Configuration.ShowInFreeplay: false })
|
||||
{
|
||||
|
||||
@@ -14,18 +14,12 @@ using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Patches the GameOptionsMenu to add custom options.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(GameOptionsMenu))]
|
||||
public static class GameOptionsMenuPatch
|
||||
internal static class GameOptionsMenuPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Update patch for the GameOptionsMenu.
|
||||
/// </summary>
|
||||
/// <param name="__instance">The GameOptionsMenu instance.</param>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(GameOptionsMenu.Update))]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void UpdatePatch(GameOptionsMenu __instance)
|
||||
{
|
||||
if (GameSettingMenuPatches.SelectedModIdx == 0)
|
||||
@@ -41,7 +35,7 @@ public static class GameOptionsMenuPatch
|
||||
else
|
||||
{
|
||||
var filteredGroups =
|
||||
GameSettingMenuPatches.SelectedMod?.OptionGroups
|
||||
GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
|
||||
|
||||
foreach (var group in filteredGroups)
|
||||
@@ -107,7 +101,7 @@ public static class GameOptionsMenuPatch
|
||||
|
||||
private static void ModifiersUpdate(ref float num)
|
||||
{
|
||||
var groups = GameSettingMenuPatches.SelectedMod?.OptionGroups
|
||||
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||
.Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true) ?? [];
|
||||
|
||||
foreach (var modGroup in groups)
|
||||
@@ -118,7 +112,7 @@ public static class GameOptionsMenuPatch
|
||||
|
||||
private static void ModifiersCreate(GameOptionsMenu menu)
|
||||
{
|
||||
var groups = GameSettingMenuPatches.SelectedMod?.OptionGroups
|
||||
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||
.Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true) ?? [];
|
||||
foreach (var group in groups)
|
||||
{
|
||||
@@ -128,6 +122,7 @@ public static class GameOptionsMenuPatch
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static bool SettingsPatch(GameOptionsMenu __instance)
|
||||
{
|
||||
if (GameSettingMenuPatches.SelectedModIdx == 0)
|
||||
@@ -143,7 +138,7 @@ public static class GameOptionsMenuPatch
|
||||
return false;
|
||||
}
|
||||
|
||||
var filteredGroups = GameSettingMenuPatches.SelectedMod?.OptionGroups
|
||||
var filteredGroups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
|
||||
|
||||
foreach (var group in filteredGroups)
|
||||
@@ -156,6 +151,7 @@ public static class GameOptionsMenuPatch
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(GameOptionsMenu.Initialize))]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static bool InitPatch(GameOptionsMenu __instance)
|
||||
{
|
||||
if (__instance.Children != null && __instance.Children.Count != 0)
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BepInEx.Configuration;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Presets;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
internal static class GamePresetsTabPatches
|
||||
{
|
||||
private static GameObject _saveButton = null!;
|
||||
private static GameObject _refreshButton = null!;
|
||||
private static GameObject _folderButton = null!;
|
||||
private static GameObject _newDivider = null!;
|
||||
private static GameObject _presetHolder = null!;
|
||||
private static GridArrange _arrange = null!;
|
||||
|
||||
private static readonly object Lock = new();
|
||||
|
||||
public static void DivideSize(GameObject obj, float amount)
|
||||
{
|
||||
foreach (var collider in obj.GetComponentsInChildren<Collider2D>(true))
|
||||
{
|
||||
if (collider.TryCast<BoxCollider2D>() is { } col)
|
||||
{
|
||||
col.size = new Vector2(col.size.x / amount, col.size.y);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var rend in obj.GetComponentsInChildren<SpriteRenderer>(true))
|
||||
{
|
||||
rend.size = new Vector2(rend.size.x / amount, rend.size.y);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(GamePresetsTab), nameof(GamePresetsTab.OnEnable))]
|
||||
public static class GamePresetsOnEnablePatch
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void Postfix(GamePresetsTab __instance)
|
||||
{
|
||||
if (!GameSettingMenu.Instance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CreatePresetMenu(__instance);
|
||||
|
||||
__instance.StandardPresetButton.gameObject.SetActive(GameSettingMenuPatches.SelectedModIdx == 0);
|
||||
__instance.SecondPresetButton.gameObject.SetActive(GameSettingMenuPatches.SelectedModIdx == 0);
|
||||
|
||||
if (GameSettingMenuPatches.SelectedModIdx != 0)
|
||||
{
|
||||
__instance.PresetDescriptionText.text = "Select or create an options preset for this mod";
|
||||
}
|
||||
else
|
||||
{
|
||||
__instance.SetSelectedText();
|
||||
}
|
||||
|
||||
// Show the save and refresh buttons
|
||||
if (_saveButton)
|
||||
{
|
||||
_saveButton.SetActive(GameSettingMenuPatches.SelectedModIdx != 0);
|
||||
}
|
||||
if (_refreshButton)
|
||||
{
|
||||
_refreshButton.SetActive(GameSettingMenuPatches.SelectedModIdx != 0);
|
||||
}
|
||||
if (_folderButton)
|
||||
{
|
||||
_folderButton.SetActive(GameSettingMenuPatches.SelectedModIdx != 0);
|
||||
}
|
||||
if (_newDivider)
|
||||
{
|
||||
_newDivider.SetActive(GameSettingMenuPatches.SelectedModIdx != 0);
|
||||
}
|
||||
|
||||
var prefab = GameSettingMenu.Instance.GameSettingsButton;
|
||||
|
||||
foreach (var preset in MiraPluginManager.Instance.RegisteredPlugins.SelectMany(x => x.Presets))
|
||||
{
|
||||
var button = Object.Instantiate(prefab, _presetHolder.transform);
|
||||
button.buttonText.text = preset.Name;
|
||||
if (button.buttonText.TryGetComponent<TextTranslatorTMP>(out var tmp))
|
||||
{
|
||||
tmp.DestroyImmediate();
|
||||
}
|
||||
|
||||
button.buttonText.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
button.OnClick = new Button.ButtonClickedEvent();
|
||||
button.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
preset.LoadPreset();
|
||||
}));
|
||||
button.gameObject.SetActive(false);
|
||||
preset.PresetButton = button.gameObject;
|
||||
}
|
||||
|
||||
foreach (var mod in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
foreach (var button in mod.Presets.Select(x => x.PresetButton))
|
||||
{
|
||||
if (button != null)
|
||||
{
|
||||
button.SetActive(mod == GameSettingMenuPatches.SelectedMod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_arrange.Start();
|
||||
_arrange.ArrangeChilds();
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(GamePresetsTab), nameof(GamePresetsTab.OnDisable))]
|
||||
public static class GamePresetsOnDisablePatch
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void Postfix()
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("OnDisable called");
|
||||
|
||||
if (_saveButton)
|
||||
{
|
||||
_saveButton.SetActive(false);
|
||||
}
|
||||
if (_refreshButton)
|
||||
{
|
||||
_refreshButton.SetActive(false);
|
||||
}
|
||||
if (_folderButton)
|
||||
{
|
||||
_folderButton.SetActive(false);
|
||||
}
|
||||
|
||||
if (_newDivider)
|
||||
{
|
||||
_newDivider.SetActive(false);
|
||||
}
|
||||
|
||||
foreach (var mod in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
foreach (var button in mod.Presets.Select(x => x.PresetButton))
|
||||
{
|
||||
if (button != null)
|
||||
{
|
||||
button.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreatePresetMenu(GamePresetsTab presetTab)
|
||||
{
|
||||
var prefab = GameSettingMenu.Instance.GameSettingsButton;
|
||||
if (prefab == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("GameSettingsButton prefab is null");
|
||||
return;
|
||||
}
|
||||
|
||||
var tmpTranslator = presetTab.PresetDescriptionText.gameObject.GetComponent<TextTranslatorTMP>();
|
||||
if (tmpTranslator)
|
||||
{
|
||||
tmpTranslator.Destroy();
|
||||
}
|
||||
|
||||
if (!_newDivider)
|
||||
{
|
||||
var oldDiv = presetTab.transform.FindChild("DividerImage");
|
||||
_newDivider = Object.Instantiate(oldDiv.gameObject, oldDiv.transform.parent);
|
||||
_newDivider.transform.localPosition = new Vector3(1.85f, 0.13f, 0f);
|
||||
_newDivider.transform.localScale = new Vector3(1.13f, 1, 1);
|
||||
_newDivider.transform.localRotation = Quaternion.Euler(new Vector3(0, 0, 90));
|
||||
}
|
||||
|
||||
PassiveButton saveButton;
|
||||
if (!_saveButton)
|
||||
{
|
||||
saveButton = Object.Instantiate(prefab, presetTab.transform);
|
||||
_saveButton = saveButton.gameObject;
|
||||
_saveButton.gameObject.name = "SaveButton";
|
||||
_saveButton.transform.localPosition = new Vector3(3.2f, 1.7f, -2);
|
||||
|
||||
// set the button text and alignment
|
||||
var saveText = saveButton.buttonText;
|
||||
saveText.text = "Save";
|
||||
saveText.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
saveText.alignment = TextAlignmentOptions.Center;
|
||||
saveText.transform.parent.localPosition = new Vector3(
|
||||
-.525f,
|
||||
saveText.transform.parent.localPosition.y,
|
||||
saveText.transform.parent.localPosition.z);
|
||||
|
||||
DivideSize(saveButton.gameObject, 2f);
|
||||
|
||||
saveButton.OnClick = new Button.ButtonClickedEvent();
|
||||
saveButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
if (GameSettingMenuPatches.SelectedModIdx == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SavePresetPopup.CreatePopup(
|
||||
name =>
|
||||
{
|
||||
if (GameSettingMenuPatches.SelectedMod == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (name == string.Empty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var presetFile = new ConfigFile(
|
||||
Path.Combine(
|
||||
PresetManager.PresetDirectory,
|
||||
GameSettingMenuPatches.SelectedMod.PluginId,
|
||||
$"{name}.cfg"),
|
||||
false)
|
||||
{
|
||||
SaveOnConfigSet = false,
|
||||
};
|
||||
|
||||
foreach (var option in GameSettingMenuPatches.SelectedMod.InternalOptions.Where(x => x.IncludeInPreset))
|
||||
{
|
||||
option.SaveToPreset(presetFile);
|
||||
}
|
||||
|
||||
foreach (var role in GameSettingMenuPatches.SelectedMod.InternalRoles.Values.OfType<ICustomRole>().Where(x=>!x.Configuration.HideSettings))
|
||||
{
|
||||
role.SaveToPreset(presetFile);
|
||||
}
|
||||
|
||||
presetFile.Save();
|
||||
Refresh();
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
saveButton = _saveButton.GetComponent<PassiveButton>();
|
||||
|
||||
PassiveButton refreshButton;
|
||||
if (!_refreshButton)
|
||||
{
|
||||
refreshButton = Object.Instantiate(saveButton, presetTab.transform);
|
||||
_refreshButton = refreshButton.gameObject;
|
||||
_refreshButton.name = "RefreshButton";
|
||||
_refreshButton.transform.localPosition = new Vector3(2.85f, 1.1f, -2);
|
||||
|
||||
var icon = new GameObject("Sprite");
|
||||
icon.transform.SetParent(refreshButton.transform);
|
||||
icon.transform.localPosition = new Vector3(-0.47f, -0.08f, -5f);
|
||||
icon.transform.localScale = new Vector3(0.5f, 0.5f, 1);
|
||||
icon.layer = refreshButton.gameObject.layer;
|
||||
|
||||
var iconSpriteRend = icon.AddComponent<SpriteRenderer>();
|
||||
iconSpriteRend.sprite = MiraAssets.RefreshIcon.LoadAsset();
|
||||
|
||||
DivideSize(refreshButton.gameObject, 2f);
|
||||
refreshButton.buttonText.gameObject.Destroy();
|
||||
|
||||
refreshButton.OnClick = new Button.ButtonClickedEvent();
|
||||
refreshButton.OnClick.AddListener((UnityAction)Refresh);
|
||||
}
|
||||
refreshButton = _refreshButton.GetComponent<PassiveButton>();
|
||||
|
||||
if (!_folderButton)
|
||||
{
|
||||
var openFolderButton = Object.Instantiate(refreshButton, presetTab.transform);
|
||||
_folderButton = openFolderButton.gameObject;
|
||||
_folderButton.name = "OpenFolderButton";
|
||||
_folderButton.transform.localPosition = new Vector3(3.55f, 1.1f, -2);
|
||||
|
||||
var folderRend = openFolderButton.transform.FindChild("Sprite").gameObject.GetComponent<SpriteRenderer>();
|
||||
folderRend.sprite = MiraAssets.FolderIcon.LoadAsset();
|
||||
folderRend.transform.localScale = new Vector3(0.4f, 0.4f, 1);
|
||||
|
||||
openFolderButton.OnClick = new Button.ButtonClickedEvent();
|
||||
openFolderButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
var directory = Path.Combine(
|
||||
PresetManager.PresetDirectory,
|
||||
GameSettingMenuPatches.SelectedMod?.PluginId ?? string.Empty);
|
||||
Directory.CreateDirectory(directory);
|
||||
Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = directory,
|
||||
UseShellExecute = true,
|
||||
Verb = "open",
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
_saveButton.SetActive(false);
|
||||
_refreshButton.SetActive(false);
|
||||
_folderButton.SetActive(false);
|
||||
_newDivider.SetActive(false);
|
||||
|
||||
if (!_presetHolder)
|
||||
{
|
||||
_presetHolder = new GameObject("PresetHolder");
|
||||
_presetHolder.transform.SetParent(presetTab.transform, false);
|
||||
_presetHolder.transform.localScale = new Vector3(0.9f, 0.9f, 1);
|
||||
_presetHolder.transform.localPosition = new Vector3(-1.9f, 1.7f, 0);
|
||||
_arrange = _presetHolder.AddComponent<GridArrange>();
|
||||
_arrange.Alignment = GridArrange.StartAlign.Right;
|
||||
_arrange.CellSize = new Vector2(2.15f, -.55f);
|
||||
_arrange.MaxColumns = 2;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Refresh()
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("Refreshing presets");
|
||||
if (GameSettingMenuPatches.SelectedMod == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("Selected mod is null, cannot refresh presets");
|
||||
return;
|
||||
}
|
||||
lock (Lock)
|
||||
{
|
||||
PresetManager.LoadPresets(GameSettingMenuPatches.SelectedMod);
|
||||
}
|
||||
GameSettingMenu.Instance.ChangeTab(0, false); // refresh the tab
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Presets;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
@@ -87,7 +88,10 @@ internal static class GameSettingMenuPatches
|
||||
_modifiersButton?.SelectButton(tabNum == 3);
|
||||
_smallRolesButton?.SelectButton(tabNum == 2);
|
||||
|
||||
if (tabNum != 3) return;
|
||||
if (tabNum != 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (__instance.RoleSettingsButton.gameObject.active)
|
||||
{
|
||||
@@ -139,7 +143,7 @@ internal static class GameSettingMenuPatches
|
||||
{
|
||||
SaveScrollPositions(__instance);
|
||||
SelectedModIdx += 1;
|
||||
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length)
|
||||
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins.Length)
|
||||
{
|
||||
SelectedModIdx = 0;
|
||||
}
|
||||
@@ -160,7 +164,7 @@ internal static class GameSettingMenuPatches
|
||||
SelectedModIdx -= 1;
|
||||
if (SelectedModIdx < 0)
|
||||
{
|
||||
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length;
|
||||
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins.Length;
|
||||
}
|
||||
|
||||
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
||||
@@ -237,54 +241,61 @@ internal static class GameSettingMenuPatches
|
||||
|
||||
_modifierBtnOgPos = _modifiersButton.transform.localPosition;
|
||||
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
PresetManager.LoadPresets(plugin);
|
||||
}
|
||||
|
||||
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
||||
}
|
||||
|
||||
private static void ChangeRoleSettingButton(bool replace, GameSettingMenu __instance)
|
||||
private static void ChangeRoleSettingButton(bool replace, GameSettingMenu menu)
|
||||
{
|
||||
__instance.RoleSettingsButton.OnClick = new ButtonClickedEvent();
|
||||
__instance.RoleSettingsButton.OnMouseOver = new UnityEvent();
|
||||
menu.RoleSettingsButton.OnClick = new ButtonClickedEvent();
|
||||
menu.RoleSettingsButton.OnMouseOver = new UnityEvent();
|
||||
|
||||
if (replace)
|
||||
{
|
||||
__instance.RoleSettingsButton.buttonText.text = "Modifiers Settings";
|
||||
__instance.RoleSettingsButton.OnClick.AddListener(
|
||||
menu.RoleSettingsButton.buttonText.text = "Modifiers Settings";
|
||||
menu.RoleSettingsButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(3, false);
|
||||
menu.ChangeTab(3, false);
|
||||
}));
|
||||
__instance.RoleSettingsButton.OnMouseOver.AddListener(
|
||||
menu.RoleSettingsButton.OnMouseOver.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(3, true);
|
||||
menu.ChangeTab(3, true);
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
__instance.RoleSettingsButton.buttonText.text = "Roles Settings";
|
||||
__instance.RoleSettingsButton.OnClick.AddListener(
|
||||
menu.RoleSettingsButton.buttonText.text = "Roles Settings";
|
||||
menu.RoleSettingsButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(2, false);
|
||||
menu.ChangeTab(2, false);
|
||||
}));
|
||||
__instance.RoleSettingsButton.OnMouseOver.AddListener(
|
||||
menu.RoleSettingsButton.OnMouseOver.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(2, true);
|
||||
menu.ChangeTab(2, true);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateText(GameSettingMenu menu, GameOptionsMenu settings, RolesSettingsMenu roles)
|
||||
{
|
||||
if (_text is not null && SelectedModIdx == 0)
|
||||
{
|
||||
_text.text = "Default";
|
||||
_text.fontSizeMax = 3.2f;
|
||||
SelectedMod = null;
|
||||
}
|
||||
else if (_text is not null)
|
||||
{
|
||||
_text.fontSizeMax = 2.3f;
|
||||
SelectedMod = MiraPluginManager.Instance.RegisteredPlugins()[SelectedModIdx - 1];
|
||||
SelectedMod = MiraPluginManager.Instance.RegisteredPlugins[SelectedModIdx - 1];
|
||||
|
||||
var name = SelectedMod.MiraPlugin.OptionsTitleText;
|
||||
_text.text = name[..Math.Min(name.Length, 25)];
|
||||
@@ -297,9 +308,11 @@ internal static class GameSettingMenuPatches
|
||||
|
||||
if (SelectedModIdx != 0)
|
||||
{
|
||||
var modHasRoles = SelectedMod!.CustomRoles.Count != 0;
|
||||
var modHasModifiers = SelectedMod.OptionGroups.Exists(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true);
|
||||
var modHasOptions = SelectedMod.OptionGroups.Exists(x => x.OptionableType == null && !x.ShowInModifiersMenu);
|
||||
var modHasRoles = SelectedMod!.InternalRoles.Count != 0;
|
||||
var modHasModifiers = SelectedMod.InternalOptionGroups.Exists(
|
||||
x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true);
|
||||
var modHasOptions =
|
||||
SelectedMod.InternalOptionGroups.Exists(x => x.OptionableType == null && !x.ShowInModifiersMenu);
|
||||
|
||||
_modifiersButton.gameObject.SetActive(true);
|
||||
_smallRolesButton.gameObject.SetActive(true);
|
||||
@@ -339,6 +352,7 @@ internal static class GameSettingMenuPatches
|
||||
{
|
||||
menu.ChangeTab(0, false);
|
||||
}
|
||||
|
||||
// If the mod has roles, we can enable the bigger role button and disable the small one.
|
||||
if (modHasRoles)
|
||||
{
|
||||
@@ -395,6 +409,11 @@ internal static class GameSettingMenuPatches
|
||||
ChangeRoleSettingButton(replaceWithModifiers, menu);
|
||||
}
|
||||
|
||||
if (menu.PresetsTab.gameObject.active)
|
||||
{
|
||||
menu.ChangeTab(0, false);
|
||||
}
|
||||
|
||||
CleanupTab(settings, roles);
|
||||
}
|
||||
|
||||
@@ -406,8 +425,10 @@ internal static class GameSettingMenuPatches
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
child.gameObject.DestroyImmediate();
|
||||
}
|
||||
|
||||
options.Clear();
|
||||
}
|
||||
|
||||
@@ -446,6 +467,7 @@ internal static class GameSettingMenuPatches
|
||||
{
|
||||
role.gameObject.DestroyImmediate();
|
||||
}
|
||||
|
||||
rolesMenu.roleChances?.Clear();
|
||||
|
||||
rolesMenu.AdvancedRolesSettings.gameObject.SetActive(false);
|
||||
|
||||
@@ -24,7 +24,7 @@ public static class LobbyViewPanePatches
|
||||
|
||||
private static MiraPluginInfo? SelectedMod => SelectedModIdx == 0
|
||||
? null
|
||||
: MiraPluginManager.Instance.RegisteredPlugins()[SelectedModIdx - 1];
|
||||
: MiraPluginManager.Instance.RegisteredPlugins[SelectedModIdx - 1];
|
||||
|
||||
private static PassiveButton? ModifiersTabButton { get; set; }
|
||||
|
||||
@@ -74,7 +74,7 @@ public static class LobbyViewPanePatches
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
SelectedModIdx += 1;
|
||||
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length)
|
||||
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins.Length)
|
||||
{
|
||||
SelectedModIdx = 0;
|
||||
}
|
||||
@@ -98,7 +98,7 @@ public static class LobbyViewPanePatches
|
||||
SelectedModIdx -= 1;
|
||||
if (SelectedModIdx < 0)
|
||||
{
|
||||
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length;
|
||||
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins.Length;
|
||||
}
|
||||
|
||||
Refresh(__instance);
|
||||
@@ -129,8 +129,8 @@ public static class LobbyViewPanePatches
|
||||
__instance.rolesTabButton.SelectButton(false);
|
||||
|
||||
ModifiersTabButton?.SelectButton(true);
|
||||
var filteredGroups = SelectedMod.OptionGroups
|
||||
.Where(x => x.GroupVisible() && (x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true));
|
||||
var filteredGroups = SelectedMod.InternalOptionGroups
|
||||
.Where(x => x.GroupVisible() && (x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true));
|
||||
DrawOptions(__instance, filteredGroups);
|
||||
}
|
||||
|
||||
@@ -148,8 +148,8 @@ public static class LobbyViewPanePatches
|
||||
return false;
|
||||
}
|
||||
|
||||
var filteredGroups = SelectedMod.OptionGroups
|
||||
.Where(x => x is { ShowInModifiersMenu: false, OptionableType: null } && x.GroupVisible.Invoke());
|
||||
var filteredGroups = SelectedMod.InternalOptionGroups
|
||||
.Where(x => x.OptionableType == null && x.GroupVisible.Invoke());
|
||||
DrawOptions(__instance, filteredGroups);
|
||||
return false;
|
||||
}
|
||||
@@ -261,7 +261,7 @@ public static class LobbyViewPanePatches
|
||||
|
||||
var list = new List<Type>();
|
||||
|
||||
var roleGroups = SelectedMod.CustomRoles.Values.OfType<ICustomRole>()
|
||||
var roleGroups = SelectedMod.InternalRoles.Values.OfType<ICustomRole>()
|
||||
.ToLookup(x => x.RoleOptionsGroup);
|
||||
|
||||
// sort the groups by priority
|
||||
@@ -326,7 +326,7 @@ public static class LobbyViewPanePatches
|
||||
viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one;
|
||||
viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f);
|
||||
|
||||
var advancedRoleOptions = SelectedMod.OptionGroups
|
||||
var advancedRoleOptions = SelectedMod.InternalOptionGroups
|
||||
.Where(x => x.OptionableType == customRole.GetType())
|
||||
.SelectMany(x => x.Options)
|
||||
.ToList();
|
||||
@@ -413,7 +413,7 @@ public static class LobbyViewPanePatches
|
||||
return 0;
|
||||
}
|
||||
|
||||
var role = SelectedMod.CustomRoles.Values.FirstOrDefault(x => x.GetType() == roleType);
|
||||
var role = SelectedMod.InternalRoles.Values.FirstOrDefault(x => x.GetType() == roleType);
|
||||
|
||||
if (role == null)
|
||||
{
|
||||
@@ -436,7 +436,7 @@ public static class LobbyViewPanePatches
|
||||
var num = viewPanel.yPosStart;
|
||||
var num2 = 1.08f;
|
||||
|
||||
var filteredOptions = SelectedMod.OptionGroups
|
||||
var filteredOptions = SelectedMod.InternalOptionGroups
|
||||
.Where(x => x.OptionableType == roleType)
|
||||
.SelectMany(x=>x.Options)
|
||||
.ToList();
|
||||
|
||||
@@ -76,7 +76,7 @@ public static class RoleSettingMenuPatches
|
||||
|
||||
var num3 = 0;
|
||||
|
||||
var roleGroups = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values.OfType<ICustomRole>()
|
||||
var roleGroups = GameSettingMenuPatches.SelectedMod?.InternalRoles.Values.OfType<ICustomRole>()
|
||||
.ToLookup(x => x.RoleOptionsGroup);
|
||||
|
||||
if (roleGroups is null)
|
||||
@@ -332,7 +332,7 @@ public static class RoleSettingMenuPatches
|
||||
var num = hasImage ? -0.872f : -1;
|
||||
|
||||
// TODO: create sub groups under the role settings.
|
||||
var filteredOptions = GameSettingMenuPatches.SelectedMod?.OptionGroups
|
||||
var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||
.Where(x=> x.OptionableType == role.GetType())
|
||||
.SelectMany(x=>x.Options)
|
||||
.ToList() ?? [];
|
||||
@@ -498,7 +498,7 @@ public static class RoleSettingMenuPatches
|
||||
roleOptionSetting.titleText.horizontalAlignment = HorizontalAlignmentOptions.Left;
|
||||
|
||||
if (GameSettingMenuPatches.SelectedMod is null ||
|
||||
GameSettingMenuPatches.SelectedMod.OptionGroups
|
||||
GameSettingMenuPatches.SelectedMod.InternalOptionGroups
|
||||
.Exists(x => x.OptionableType == role.GetType()))
|
||||
{
|
||||
var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform);
|
||||
@@ -530,7 +530,7 @@ public static class RoleSettingMenuPatches
|
||||
roleOptionSetting.ChancePlusBtn.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
if (index < GameSettingMenuPatches.SelectedMod?.CustomRoles.Count - 1)
|
||||
if (index < GameSettingMenuPatches.SelectedMod?.InternalRoles.Count - 1)
|
||||
{
|
||||
ScrollerNum -= 0.43f;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ using MiraAPI.GameModes;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Presets;
|
||||
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
namespace MiraAPI.PluginLoading;
|
||||
|
||||
/// <summary>
|
||||
@@ -23,61 +25,58 @@ public class MiraPluginInfo
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a read only collection of this plugin's modifiers. This is probably not safe because they are uninitialized.
|
||||
/// Gets a read only collection of this plugin's modifiers. This is not safe for modifiers with constructors.
|
||||
/// </summary>
|
||||
/// <returns>Readonly collection of modifiers.</returns>
|
||||
public IReadOnlyCollection<BaseModifier> GetModifiers()
|
||||
{
|
||||
return Modifiers.AsReadOnly();
|
||||
}
|
||||
public IReadOnlyCollection<BaseModifier> Modifiers { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Get a read only collection of this plugin's Option Groups.
|
||||
/// Gets a read only collection of this plugin's Option Groups.
|
||||
/// </summary>
|
||||
/// <returns>Readonly collection of option groups.</returns>
|
||||
public IReadOnlyCollection<AbstractOptionGroup> GetOptionGroups()
|
||||
{
|
||||
return OptionGroups.AsReadOnly();
|
||||
}
|
||||
public IReadOnlyCollection<AbstractOptionGroup> OptionGroups { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only collection of this plugin's options.
|
||||
/// </summary>
|
||||
/// <returns>Read only collection of options.</returns>
|
||||
public IReadOnlyCollection<IModdedOption> GetOptions()
|
||||
{
|
||||
return Options.AsReadOnly();
|
||||
}
|
||||
public IReadOnlyCollection<IModdedOption> Options { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only dictionary of Role IDs and the RoleBehaviour object they are associated with.
|
||||
/// </summary>
|
||||
/// <returns>Read only dictionary of IDs and Roles.</returns>
|
||||
public ReadOnlyDictionary<ushort, RoleBehaviour> GetRoles()
|
||||
{
|
||||
return new ReadOnlyDictionary<ushort, RoleBehaviour>(CustomRoles);
|
||||
}
|
||||
public ReadOnlyDictionary<ushort, RoleBehaviour> Roles { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only collection of this plugin's custom buttons.
|
||||
/// </summary>
|
||||
/// <returns>Read only collection of buttons.</returns>
|
||||
public IReadOnlyCollection<CustomActionButton> GetButtons()
|
||||
public IReadOnlyCollection<CustomActionButton> Buttons { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only dictionary of this plugin's custom game modes.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<OptionPreset> Presets { get; internal set; } = null!;
|
||||
|
||||
internal void SavePublicCollections()
|
||||
{
|
||||
return Buttons.AsReadOnly();
|
||||
Presets = [..InternalPresets];
|
||||
Modifiers = [..InternalModifiers];
|
||||
OptionGroups = [..InternalOptionGroups];
|
||||
Options = [..InternalOptions];
|
||||
Roles = new ReadOnlyDictionary<ushort, RoleBehaviour>(InternalRoles);
|
||||
Buttons = [..InternalButtons];
|
||||
}
|
||||
|
||||
internal List<AbstractOptionGroup> OptionGroups { get; } = [];
|
||||
internal List<OptionPreset> InternalPresets { get; } = [];
|
||||
|
||||
internal List<IModdedOption> Options { get; } = [];
|
||||
internal List<AbstractOptionGroup> InternalOptionGroups { get; } = [];
|
||||
|
||||
internal List<BaseModifier> Modifiers { get; } = [];
|
||||
internal List<IModdedOption> InternalOptions { get; } = [];
|
||||
|
||||
internal Dictionary<ushort, RoleBehaviour> CustomRoles { get; } = [];
|
||||
internal List<BaseModifier> InternalModifiers { get; } = [];
|
||||
|
||||
internal Dictionary<int, CustomGameMode> GameModes { get; } = [];
|
||||
internal Dictionary<ushort, RoleBehaviour> InternalRoles { get; } = [];
|
||||
|
||||
internal List<CustomActionButton> Buttons { get; } = [];
|
||||
internal Dictionary<int, CustomGameMode> InternalGameModes { get; } = [];
|
||||
|
||||
internal List<CustomActionButton> InternalButtons { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's ID, as defined in the plugin's BepInEx metadata.
|
||||
|
||||
@@ -12,6 +12,7 @@ using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Presets;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Networking;
|
||||
@@ -26,8 +27,9 @@ public sealed class MiraPluginManager
|
||||
{
|
||||
private readonly Dictionary<Assembly, MiraPluginInfo> _registeredPlugins = [];
|
||||
|
||||
internal MiraPluginInfo[] RegisteredPlugins() => [.. _registeredPlugins.Values];
|
||||
internal Dictionary<MiraPluginInfo, List<Type>> QueuedRoleRegistrations { get; } = new();
|
||||
internal MiraPluginInfo[] RegisteredPlugins { get; private set; } = null!;
|
||||
|
||||
internal Dictionary<MiraPluginInfo, List<Type>> QueuedRoleRegistrations { get; } = [];
|
||||
internal static MiraPluginManager Instance { get; private set; } = new();
|
||||
|
||||
internal void Initialize()
|
||||
@@ -110,16 +112,24 @@ public sealed class MiraPluginManager
|
||||
info.PluginConfig.Save();
|
||||
info.PluginConfig.SaveOnConfigSet = oldConfigSetting;
|
||||
|
||||
info.OptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority));
|
||||
info.InternalOptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority));
|
||||
QueuedRoleRegistrations.Add(info, roles);
|
||||
|
||||
_registeredPlugins.Add(assembly, info);
|
||||
|
||||
info.SavePublicCollections();
|
||||
PresetManager.CreateDefaultPreset(info);
|
||||
PresetManager.LoadPresets(info);
|
||||
Logger<MiraApiPlugin>.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API.");
|
||||
};
|
||||
IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors;
|
||||
IL2CPPChainloader.Instance.Finished += () =>
|
||||
{
|
||||
// Save all buttons into a read-only collection for easy access
|
||||
CustomButtonManager.Buttons = new ReadOnlyCollection<CustomActionButton>(CustomButtonManager.CustomButtons);
|
||||
|
||||
// Cache all the registered plugins into an array for easy access
|
||||
RegisteredPlugins = [.._registeredPlugins.Values];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Linq;
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Roles;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Presets;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a preset of game options that can be applied to the game.
|
||||
/// </summary>
|
||||
public class OptionPreset
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the preset.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin associated with the preset.
|
||||
/// </summary>
|
||||
public MiraPluginInfo Plugin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configuration file of the plugin associated with the preset.
|
||||
/// </summary>
|
||||
public ConfigFile PluginConfig { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configuration file for the preset.
|
||||
/// </summary>
|
||||
public ConfigFile PresetConfig { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the button associated with the preset in the UI.
|
||||
/// </summary>
|
||||
public GameObject? PresetButton { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OptionPreset"/> class with the specified name and configuration file.
|
||||
/// </summary>
|
||||
/// <param name="name">>The name of the preset.</param>
|
||||
/// <param name="plugin">The plugin associated with the preset.</param>
|
||||
/// <param name="presetConfig">>The configuration file for the preset.</param>
|
||||
public OptionPreset(string name, MiraPluginInfo plugin, ConfigFile presetConfig)
|
||||
{
|
||||
Name = name;
|
||||
Plugin = plugin;
|
||||
PluginConfig = plugin.PluginConfig;
|
||||
PresetConfig = presetConfig;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the preset by applying the values from the preset configuration to the plugin configuration.
|
||||
/// </summary>
|
||||
public void LoadPreset()
|
||||
{
|
||||
PresetConfig.Reload();
|
||||
foreach (var option in Plugin.InternalOptions)
|
||||
{
|
||||
option.LoadFromPreset(PresetConfig);
|
||||
}
|
||||
|
||||
ModdedOptionsManager.SyncAllOptions();
|
||||
|
||||
foreach (var role in Plugin.InternalRoles.Values.OfType<ICustomRole>())
|
||||
{
|
||||
role.LoadFromPreset(PresetConfig);
|
||||
}
|
||||
|
||||
CustomRoleManager.SyncAllRoleSettings();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Roles;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Presets;
|
||||
|
||||
/// <summary>
|
||||
/// Provides functionality to manage and load game option presets for plugins.
|
||||
/// </summary>
|
||||
public static class PresetManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the directory where the presets are stored.
|
||||
/// </summary>
|
||||
public static string PresetDirectory { get; } = Path.GetFullPath("mira_presets", Application.persistentDataPath);
|
||||
|
||||
internal static void CreateDefaultPreset(MiraPluginInfo plugin)
|
||||
{
|
||||
var presetPath = Path.Combine(PresetDirectory, plugin.PluginId);
|
||||
if (!Directory.Exists(presetPath))
|
||||
{
|
||||
Directory.CreateDirectory(presetPath);
|
||||
}
|
||||
|
||||
var presetConfig = new ConfigFile(Path.Combine(presetPath, "Default.cfg"), false);
|
||||
|
||||
foreach (var option in plugin.InternalOptions.Where(x => x.IncludeInPreset))
|
||||
{
|
||||
option.SaveToPreset(presetConfig, true);
|
||||
}
|
||||
|
||||
foreach (var role in plugin.InternalRoles.Values.OfType<ICustomRole>())
|
||||
{
|
||||
role.SaveToPreset(presetConfig);
|
||||
}
|
||||
|
||||
presetConfig.Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the presets for the specified plugin by reading the configuration files from the preset directory.
|
||||
/// </summary>
|
||||
/// <param name="plugin">>The plugin for which the presets should be loaded.</param>
|
||||
public static void LoadPresets(MiraPluginInfo plugin)
|
||||
{
|
||||
foreach (var btn in plugin.InternalPresets.Select(x=>x.PresetButton))
|
||||
{
|
||||
if (btn != null)
|
||||
{
|
||||
Object.DestroyImmediate(btn);
|
||||
}
|
||||
}
|
||||
|
||||
plugin.InternalPresets.Clear();
|
||||
if (!Directory.Exists(PresetDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(PresetDirectory);
|
||||
}
|
||||
|
||||
var pluginPresetPath = Path.Combine(PresetDirectory, plugin.PluginId);
|
||||
if (!Directory.Exists(pluginPresetPath))
|
||||
{
|
||||
Directory.CreateDirectory(pluginPresetPath);
|
||||
}
|
||||
|
||||
foreach (var file in Directory.GetFiles(pluginPresetPath, "*.cfg"))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Info($"Loading preset file {file}");
|
||||
var presetName = Path.GetFileNameWithoutExtension(file);
|
||||
var presetConfig = new ConfigFile(file, false)
|
||||
{
|
||||
SaveOnConfigSet = false,
|
||||
};
|
||||
|
||||
foreach (var option in plugin.InternalOptions.Where(x=>x.IncludeInPreset))
|
||||
{
|
||||
option.Bind(presetConfig);
|
||||
}
|
||||
|
||||
foreach (var role in plugin.InternalRoles.Values.OfType<ICustomRole>().Where(x=>!x.Configuration.HideSettings))
|
||||
{
|
||||
role.BindConfig(presetConfig);
|
||||
}
|
||||
|
||||
presetConfig.Save();
|
||||
|
||||
plugin.InternalPresets.Add(new OptionPreset(presetName, plugin, presetConfig));
|
||||
}
|
||||
plugin.Presets = [.. plugin.InternalPresets];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Fields;
|
||||
using MiraAPI.Modifiers.ModifierDisplay;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
|
||||
[RegisterInIl2Cpp]
|
||||
public class SavePresetPopup(nint ptr) : Minigame(ptr)
|
||||
{
|
||||
private PassiveButton saveButton;
|
||||
private PassiveButton closeButton;
|
||||
private TextMeshPro textBoxText;
|
||||
private TextBoxTMP textBox;
|
||||
|
||||
private Action<string> onSave;
|
||||
|
||||
// Cleanup holder object
|
||||
public void OnDestroy()
|
||||
{
|
||||
transform.parent.gameObject.Destroy();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
var textboxHolder = transform.GetChild(1).GetChild(1);
|
||||
saveButton = transform.FindChild("SaveButton").GetComponent<PassiveButton>();
|
||||
closeButton = transform.FindChild("CloseButton").GetComponent<PassiveButton>();
|
||||
textBox = textboxHolder.GetChild(0).GetComponent<TextBoxTMP>();
|
||||
textBoxText = textBox.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||
|
||||
saveButton.OnClick = new Button.ButtonClickedEvent();
|
||||
saveButton.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
onSave.Invoke(textBoxText.text);
|
||||
this.BaseClose();
|
||||
}));
|
||||
|
||||
closeButton.OnClick = new Button.ButtonClickedEvent();
|
||||
closeButton.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
this.BaseClose();
|
||||
}));
|
||||
|
||||
textBox.OnChange = new Button.ButtonClickedEvent();
|
||||
textBox.OnChange.AddListener((UnityAction)(() =>
|
||||
{
|
||||
textBoxText.text = textBoxText.text.Replace(" ", string.Empty);
|
||||
}));
|
||||
|
||||
textBox.GetComponent<PassiveButton>().OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
textBox.GiveFocus();
|
||||
}));
|
||||
|
||||
foreach (var tmp in gameObject.GetComponentsInChildren<TextMeshPro>())
|
||||
{
|
||||
if (tmp.gameObject.transform.parent.name == "Textbox") continue;
|
||||
var text = tmp.text;
|
||||
tmp.text = $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF RadialMenu Material\">{text}</font>";
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreatePopup(Action<string> saveAction)
|
||||
{
|
||||
// Because innerscuff doesnt account for Z value in Close animation
|
||||
var holder = new GameObject("PopupHolder")
|
||||
{
|
||||
transform =
|
||||
{
|
||||
parent = Camera.main!.transform,
|
||||
},
|
||||
};
|
||||
holder.transform.localPosition = new Vector3(0, -1f, -500);
|
||||
|
||||
var gameObject = Instantiate(MiraAssets.PresetSavePopup.LoadAsset(), holder.transform);
|
||||
var popup = gameObject.GetComponent<SavePresetPopup>();
|
||||
popup.onSave = saveAction;
|
||||
popup.Begin(null);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
@@ -72,7 +72,7 @@ public static class CustomRoleManager
|
||||
continue;
|
||||
}
|
||||
|
||||
pluginInfo.CustomRoles.Add((ushort)role.Role, role);
|
||||
pluginInfo.InternalRoles.Add((ushort)role.Role, role);
|
||||
}
|
||||
|
||||
pluginInfo.PluginConfig.Save();
|
||||
@@ -161,7 +161,7 @@ public static class CustomRoleManager
|
||||
/// <returns>A MiraPluginInfo object representing the parent mod of the role.</returns>
|
||||
public static MiraPluginInfo FindParentMod(ICustomRole role)
|
||||
{
|
||||
return MiraPluginManager.Instance.RegisteredPlugins().First(plugin => plugin.CustomRoles.ContainsValue(role as RoleBehaviour ?? throw new InvalidOperationException()));
|
||||
return MiraPluginManager.Instance.RegisteredPlugins.First(plugin => plugin.InternalRoles.ContainsValue(role as RoleBehaviour ?? throw new InvalidOperationException()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -236,7 +236,7 @@ public static class CustomRoleManager
|
||||
// we dont know how other plugins handle their configs
|
||||
// this way, all the options are saved at once, instead of one by one
|
||||
var oldConfigSetting = new Dictionary<MiraPluginInfo, bool>();
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
oldConfigSetting.Add(plugin, plugin.PluginConfig.SaveOnConfigSet);
|
||||
plugin.PluginConfig.SaveOnConfigSet = false;
|
||||
@@ -271,7 +271,7 @@ public static class CustomRoleManager
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
plugin.PluginConfig.Save();
|
||||
plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin];
|
||||
|
||||
@@ -76,6 +76,45 @@ public interface ICustomRole : IOptionable
|
||||
internal ConfigDefinition NumConfigDefinition => new("Roles", $"Num {GetType().FullName}");
|
||||
internal ConfigDefinition ChanceConfigDefinition => new("Roles", $"Chance {GetType().FullName}");
|
||||
|
||||
/// <summary>
|
||||
/// Binds the configuration options for this role to the provided ConfigFile.
|
||||
/// </summary>
|
||||
/// <param name="config">The ConfigFile to bind the options to.</param>
|
||||
public virtual void BindConfig(ConfigFile config)
|
||||
{
|
||||
config.Bind(NumConfigDefinition, Configuration.DefaultRoleCount);
|
||||
config.Bind(ChanceConfigDefinition, Configuration.DefaultChance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the role's configuration to a preset ConfigFile.
|
||||
/// </summary>
|
||||
/// <param name="presetConfig">The ConfigFile to save the preset configuration to.</param>
|
||||
/// <param name="useDefault">Whether to use the default values for the configuration.</param>
|
||||
public virtual void SaveToPreset(ConfigFile presetConfig, bool useDefault=false)
|
||||
{
|
||||
BindConfig(presetConfig);
|
||||
presetConfig[NumConfigDefinition].BoxedValue = useDefault ? Configuration.DefaultRoleCount : GetCount();
|
||||
presetConfig[ChanceConfigDefinition].BoxedValue = useDefault ? Configuration.DefaultChance : GetChance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the role's configuration from a preset ConfigFile.
|
||||
/// </summary>
|
||||
/// <param name="presetConfig">The ConfigFile containing the preset configuration.</param>
|
||||
public virtual void LoadFromPreset(ConfigFile presetConfig)
|
||||
{
|
||||
if (presetConfig.TryGetEntry(NumConfigDefinition, out ConfigEntry<int> numEntry))
|
||||
{
|
||||
SetCount(numEntry.Value);
|
||||
}
|
||||
|
||||
if (presetConfig.TryGetEntry(ChanceConfigDefinition, out ConfigEntry<int> chanceEntry))
|
||||
{
|
||||
SetChance(chanceEntry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the role chance option.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using Reactor.Utilities.Extensions;
|
||||
|
||||
namespace MiraAPI.Utilities.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper class to turn any UnityEngine.Object into a <see cref="LoadableAsset{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the asset to be loaded. Must be a subclass of <see cref="UnityEngine.Object"/>.</typeparam>
|
||||
public class LoadableAssetWrapper<T> : LoadableAsset<T> where T : UnityEngine.Object
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoadableAssetWrapper{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="asset">The asset to wrap. This should be a reference to a UnityEngine.Object.</param>
|
||||
public LoadableAssetWrapper(T asset)
|
||||
{
|
||||
LoadedAsset = asset.DontDestroy();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override T LoadAsset()
|
||||
{
|
||||
return LoadedAsset ?? throw new System.InvalidOperationException("The asset could not be loaded!");
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,23 @@ namespace MiraAPI.Utilities.Assets;
|
||||
/// </summary>
|
||||
public static class MiraAssets
|
||||
{
|
||||
static MiraAssets()
|
||||
{
|
||||
var boxTex = SpriteTools.LoadTextureFromResourcePath(
|
||||
"MiraAPI.Resources.RoundedBox.png",
|
||||
System.Reflection.Assembly.GetCallingAssembly());
|
||||
var boxSprite = Sprite.Create(
|
||||
boxTex,
|
||||
new Rect(0, 0, boxTex.width, boxTex.height),
|
||||
new Vector2(0.5f, 0.5f),
|
||||
100f,
|
||||
0U,
|
||||
SpriteMeshType.Tight,
|
||||
new Vector4(20, 20, 20, 20));
|
||||
|
||||
RoundedBox = new LoadableAssetWrapper<Sprite>(boxSprite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color used for teal highlighting in UI.
|
||||
/// </summary>
|
||||
@@ -23,11 +40,31 @@ public static class MiraAssets
|
||||
/// </summary>
|
||||
public static LoadableAsset<GameObject> ModifierDisplay { get; } = new LoadableBundleAsset<GameObject>("Modifiers", MiraAssetBundle);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Popup prefab for saving presets.
|
||||
/// </summary>
|
||||
public static LoadableAsset<GameObject> PresetSavePopup { get; } = new LoadableBundleAsset<GameObject>("PresetSavePopup", MiraAssetBundle);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Refresh Icon sprite.
|
||||
/// </summary>
|
||||
public static LoadableAsset<Sprite> RefreshIcon { get; } = new LoadableBundleAsset<Sprite>("refresh", MiraAssetBundle);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Folder Icon sprite.
|
||||
/// </summary>
|
||||
public static LoadableAsset<Sprite> FolderIcon { get; } = new LoadableBundleAsset<Sprite>("freePlay_folderTaskRoom", MiraAssetBundle);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the empty sprite asset.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset Empty { get; } = new("MiraAPI.Resources.Empty.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RoundedBox sprite, which is a rounded rectangle used for UI elements.
|
||||
/// </summary>
|
||||
public static LoadableAsset<Sprite> RoundedBox { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Next Button sprite.
|
||||
/// </summary>
|
||||
|
||||
@@ -11,14 +11,13 @@ namespace MiraAPI.Utilities.Assets;
|
||||
public static class SpriteTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Load a sprite from a resource path.
|
||||
/// Loads and returns a texture from a resource path using the specified assembly.
|
||||
/// </summary>
|
||||
/// <param name="resourcePath">The path to the resource.</param>
|
||||
/// <param name="assembly">The assembly containing the resource.</param>
|
||||
/// <param name="pixelsPerUnit">The pixels per unit for the loaded sprite.</param>
|
||||
/// <returns>A sprite made from the resource.</returns>
|
||||
/// <exception cref="Exception">The resource cannot be found.</exception>
|
||||
public static Sprite LoadSpriteFromPath(string resourcePath, Assembly assembly, float pixelsPerUnit)
|
||||
/// <param name="resourcePath">The path to the resource within the assembly.</param>
|
||||
/// <param name="assembly">The assembly from which to load the resource.</param>
|
||||
/// <returns>A <see cref="Texture2D"/> object loaded from the specified resource path.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the resource cannot be found in the specified assembly.</exception>
|
||||
public static Texture2D LoadTextureFromResourcePath(string resourcePath, Assembly assembly)
|
||||
{
|
||||
var tex = new Texture2D(1, 1, TextureFormat.ARGB32, false);
|
||||
var myStream = assembly.GetManifestResourceStream(resourcePath);
|
||||
@@ -33,6 +32,19 @@ public static class SpriteTools
|
||||
}
|
||||
|
||||
tex.name = resourcePath;
|
||||
return tex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads and returns a <see cref="Sprite"/> from a resource path using the specified assembly.
|
||||
/// </summary>
|
||||
/// <param name="resourcePath">The path to the resource within the assembly.</param>
|
||||
/// <param name="assembly">The assembly from which to load the resource.</param>
|
||||
/// <param name="pixelsPerUnit">The number of pixels per unit for the sprite.</param>
|
||||
/// <returns>A <see cref="Sprite"/> object created from the texture loaded from the specified resource path.</returns>
|
||||
public static Sprite LoadSpriteFromPath(string resourcePath, Assembly assembly, float pixelsPerUnit)
|
||||
{
|
||||
var tex = LoadTextureFromResourcePath(resourcePath, assembly);
|
||||
var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), pixelsPerUnit);
|
||||
sprite.name = resourcePath;
|
||||
return sprite;
|
||||
|
||||
@@ -42,6 +42,49 @@ public static class Extensions
|
||||
BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used if you override Minigame.Close.
|
||||
/// </summary>
|
||||
/// <param name="self">The minigame.</param>
|
||||
public static void BaseClose(this Minigame self)
|
||||
{
|
||||
bool isComplete;
|
||||
if (self.amClosing == Minigame.CloseState.Closing)
|
||||
{
|
||||
UnityEngine.Object.Destroy(self.gameObject);
|
||||
return;
|
||||
}
|
||||
if (self.CloseSound && Constants.ShouldPlaySfx())
|
||||
{
|
||||
SoundManager.Instance.PlaySound(self.CloseSound, false, 1f, null);
|
||||
}
|
||||
if (PlayerControl.LocalPlayer.Data.Role.TeamType == RoleTeamTypes.Crewmate)
|
||||
{
|
||||
GameManager.Instance.LogicMinigame.OnMinigameClose();
|
||||
}
|
||||
if (PlayerControl.LocalPlayer)
|
||||
{
|
||||
PlayerControl.HideCursorTemporarily();
|
||||
}
|
||||
self.amClosing = Minigame.CloseState.Closing;
|
||||
self.logger.Info(string.Concat("Closing minigame ", self.GetType().Name));
|
||||
IAnalyticsReporter analytics = DestroyableSingleton<DebugAnalytics>.Instance.Analytics;
|
||||
NetworkedPlayerInfo data = PlayerControl.LocalPlayer.Data;
|
||||
TaskTypes taskType = self.TaskType;
|
||||
float realtimeSinceStartup = Time.realtimeSinceStartup - self.timeOpened;
|
||||
PlayerTask myTask = self.MyTask;
|
||||
if (myTask != null)
|
||||
{
|
||||
isComplete = myTask.IsComplete;
|
||||
}
|
||||
else
|
||||
{
|
||||
isComplete = false;
|
||||
}
|
||||
analytics.MinigameClosed(data, taskType, realtimeSinceStartup, isComplete);
|
||||
self.StartCoroutine(self.CoDestroySelf());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the cooldown of a button with a formatted string.
|
||||
/// </summary>
|
||||
|
||||
Reference in new issue
Block a user