From b67fdf5ab1b3a1efc3f83a4ad55de5c200978c8e Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Fri, 28 Feb 2025 23:11:31 -0500 Subject: [PATCH 01/11] optionable v1 --- .../Options/Roles/FreezerRoleSettings.cs | 4 +--- .../Options/Roles/TeleporterOptions.cs | 4 +--- MiraAPI/GameOptions/AbstractOptionGroup.cs | 17 ++++++++++++----- .../Attributes/ModdedEnumOptionAttribute.cs | 6 +++--- .../Attributes/ModdedNumberOptionAttribute.cs | 7 +++---- .../Attributes/ModdedToggleOptionAttribute.cs | 5 ++--- MiraAPI/GameOptions/IModdedOption.cs | 1 - MiraAPI/GameOptions/IOptionable.cs | 6 ++++++ MiraAPI/GameOptions/IOptionableGroup.cs | 14 ++++++++++++++ MiraAPI/GameOptions/ModdedOptionsManager.cs | 3 --- .../GameOptions/OptionTypes/ModdedEnumOption.cs | 8 ++------ .../OptionTypes/ModdedNumberOption.cs | 6 ++---- MiraAPI/GameOptions/OptionTypes/ModdedOption.cs | 13 +------------ .../OptionTypes/ModdedToggleOption.cs | 3 +-- MiraAPI/Patches/Options/GameOptionsMenuPatch.cs | 9 ++++++--- .../Patches/Options/GameSettingMenuPatches.cs | 4 +++- MiraAPI/Patches/Options/LobbyViewPanePatches.cs | 14 +++++++++----- .../Patches/Options/RoleSettingMenuPatches.cs | 16 ++++++++-------- MiraAPI/Roles/ICustomRole.cs | 3 ++- 19 files changed, 76 insertions(+), 67 deletions(-) create mode 100644 MiraAPI/GameOptions/IOptionable.cs create mode 100644 MiraAPI/GameOptions/IOptionableGroup.cs diff --git a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs index a99c9e0..6113408 100644 --- a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs +++ b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs @@ -6,12 +6,10 @@ using MiraAPI.Utilities; namespace MiraAPI.Example.Options.Roles; -public class FreezerRoleSettings : AbstractOptionGroup +public class FreezerRoleSettings : AbstractOptionGroup { public override string GroupName => "Custom Role"; - public override Type AdvancedRole => typeof(FreezerRole); - [ModdedNumberOption("Freeze Duration", 1, 15, 1, MiraNumberSuffixes.Seconds)] public float FreezeDuration { get; set; } = 5; diff --git a/MiraAPI.Example/Options/Roles/TeleporterOptions.cs b/MiraAPI.Example/Options/Roles/TeleporterOptions.cs index 34ccb94..1b0c1da 100644 --- a/MiraAPI.Example/Options/Roles/TeleporterOptions.cs +++ b/MiraAPI.Example/Options/Roles/TeleporterOptions.cs @@ -7,12 +7,10 @@ using MiraAPI.Utilities; namespace MiraAPI.Example.Options.Roles; -public class TeleporterOptions : AbstractOptionGroup +public class TeleporterOptions : AbstractOptionGroup { public override string GroupName => "Teleporter"; - public override Type AdvancedRole => typeof(TeleporterRole); - public ModdedNumberOption TeleportCooldown { get; set; } = new("Teleport Cooldown", 10, 5, 60, 2.5f, MiraNumberSuffixes.Seconds); [ModdedNumberOption("Teleport Duration", 5, 25, 1, MiraNumberSuffixes.Seconds)] diff --git a/MiraAPI/GameOptions/AbstractOptionGroup.cs b/MiraAPI/GameOptions/AbstractOptionGroup.cs index 418f25b..96dcdd7 100644 --- a/MiraAPI/GameOptions/AbstractOptionGroup.cs +++ b/MiraAPI/GameOptions/AbstractOptionGroup.cs @@ -32,12 +32,19 @@ public abstract class AbstractOptionGroup /// public virtual uint GroupPriority => uint.MaxValue; - /// - /// Gets the role the group is associated with. This is used for the advanced role options menu. - /// - public virtual Type? AdvancedRole => null; - internal bool AllOptionsHidden { get; set; } internal CategoryHeaderMasked? Header { get; set; } } + +/// +/// Base class for option groups. An option group is a collection of options that are displayed together in the options menu. +/// +/// The type of the optionable that this group contains. +public abstract class AbstractOptionGroup : AbstractOptionGroup, IOptionableGroup where T : IOptionable +{ + /// + /// Gets the Optionable type of the group. + /// + public Type OptionableType => typeof(T); +} diff --git a/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs index da6094b..54680f2 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs @@ -8,12 +8,12 @@ namespace MiraAPI.GameOptions.Attributes; /// Attribute for creating an enum option. /// [AttributeUsage(AttributeTargets.Property)] -public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? values = null, Type? roleType = null) - : ModdedOptionAttribute(title, roleType) +public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? values = null) + : ModdedOptionAttribute(title) { internal override IModdedOption CreateOption(object? value, PropertyInfo property) { - var opt = new ModdedEnumOption(Title, (int)(value ?? 0), enumType, values, RoleType); + var opt = new ModdedEnumOption(Title, (int)(value ?? 0), enumType, values); return opt; } diff --git a/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs index d679051..07b5cfc 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs @@ -16,13 +16,12 @@ public class ModdedNumberOptionAttribute( float increment = 1, MiraNumberSuffixes suffixType = MiraNumberSuffixes.None, string? formatString = null, - bool zeroInfinity = false, - Type? roleType = null) - : ModdedOptionAttribute(title, roleType) + bool zeroInfinity = false) + : ModdedOptionAttribute(title) { internal override IModdedOption CreateOption(object? value, PropertyInfo property) { - return new ModdedNumberOption(Title, (float)(value ?? min+increment), min, max, increment, suffixType, formatString, zeroInfinity, RoleType); + return new ModdedNumberOption(Title, (float)(value ?? min+increment), min, max, increment, suffixType, formatString, zeroInfinity); } /// diff --git a/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs index d44a7c0..0ad64da 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs @@ -8,13 +8,12 @@ namespace MiraAPI.GameOptions.Attributes; /// Attribute for a toggle option. /// /// The option title. -/// An optional role type. [AttributeUsage(AttributeTargets.Property)] -public class ModdedToggleOptionAttribute(string title, Type? roleType = null) : ModdedOptionAttribute(title, roleType) +public class ModdedToggleOptionAttribute(string title) : ModdedOptionAttribute(title) { internal override IModdedOption CreateOption(object? value, PropertyInfo property) { - var toggleOpt = new ModdedToggleOption(Title, (bool)(value ?? false), RoleType); + var toggleOpt = new ModdedToggleOption(Title, (bool)(value ?? false)); return toggleOpt; } diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs index 1aee3c4..f40f3cf 100644 --- a/MiraAPI/GameOptions/IModdedOption.cs +++ b/MiraAPI/GameOptions/IModdedOption.cs @@ -13,7 +13,6 @@ public interface IModdedOption StringNames StringName { get; } IMiraPlugin? ParentMod { get; set; } BaseGameSetting Data { get; } - Type? AdvancedRole { get; set; } OptionBehaviour? OptionBehaviour { get; } Func Visible { get; set; } ConfigDefinition? ConfigDefinition { get; set; } diff --git a/MiraAPI/GameOptions/IOptionable.cs b/MiraAPI/GameOptions/IOptionable.cs new file mode 100644 index 0000000..adc28f6 --- /dev/null +++ b/MiraAPI/GameOptions/IOptionable.cs @@ -0,0 +1,6 @@ +namespace MiraAPI.GameOptions; + +/// +/// Interface for classes that can be used for option groups. +/// +public interface IOptionable; diff --git a/MiraAPI/GameOptions/IOptionableGroup.cs b/MiraAPI/GameOptions/IOptionableGroup.cs new file mode 100644 index 0000000..e476548 --- /dev/null +++ b/MiraAPI/GameOptions/IOptionableGroup.cs @@ -0,0 +1,14 @@ +using System; + +namespace MiraAPI.GameOptions; + +/// +/// Interface for groups that have some type of "Optionable" class. +/// +public interface IOptionableGroup +{ + /// + /// Gets the type of the Optionable class. + /// + Type OptionableType { get; } +} diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index a6dd1c9..cbb6459 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -119,10 +119,7 @@ public static class ModdedOptionsManager option.ConfigDefinition = new ConfigDefinition(groupName, propertyName); option.ParentMod = pluginInfo.MiraPlugin; - option.AdvancedRole = group.AdvancedRole; - pluginInfo.Options.Add(option); - ModdedOptions.Add(option.Id, option); group.Options.Add(option); } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs index f25fda6..ff422f5 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs @@ -27,8 +27,7 @@ public class ModdedEnumOption : ModdedOption /// The default value as an int. /// The Enum type. /// An option list of string values to use in place of the enum name. - /// An optional role type to specify for a specific role. - public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null, Type? roleType = null) : base(title, defaultValue, roleType) + public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null) : base(title, defaultValue) { Values = values ?? Enum.GetNames(enumType); Data = ScriptableObject.CreateInstance(); @@ -101,7 +100,6 @@ public class ModdedEnumOption : ModdedOption } } - /// /// An option for selecting an enum value. /// @@ -118,10 +116,8 @@ public class ModdedEnumOption : ModdedOption where T : Enum /// /// The title of the option. /// The default value as an int. - /// The Enum type. /// An option list of string values to use in place of the enum name. - /// An optional role type to specify for a specific role. - public ModdedEnumOption(string title, T defaultValue, string[]? values = null, Type? roleType = null) : base(title, defaultValue, roleType) + public ModdedEnumOption(string title, T defaultValue, string[]? values = null) : base(title, defaultValue) { Values = values ?? Enum.GetNames(typeof(T)); Data = ScriptableObject.CreateInstance(); diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs index f075662..0174326 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs @@ -48,7 +48,6 @@ public class ModdedNumberOption : ModdedOption /// The suffix type. /// Optional format string for the option screen. /// Whether zero is infinity or not. - /// An optional role type. public ModdedNumberOption( string title, float defaultValue, @@ -57,8 +56,7 @@ public class ModdedNumberOption : ModdedOption float increment, MiraNumberSuffixes suffixType, string? formatString = null, - bool zeroInfinity = false, - Type? roleType = null) : base(title, defaultValue, roleType) + bool zeroInfinity = false) : base(title, defaultValue) { Min = min; Max = max; @@ -144,7 +142,7 @@ public class ModdedNumberOption : ModdedOption Value = Mathf.Clamp(newValue, Min, Max); HudManager.Instance.Notifier.AddSettingsChangeMessage( StringName, - Data?.GetValueString(Value), + Data.GetValueString(Value), false); if (OptionBehaviour is NumberOption opt) diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index 9d42be0..0cfefa8 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -72,11 +72,6 @@ public abstract class ModdedOption : IModdedOption /// public Func Visible { get; set; } - /// - /// Gets or sets the advanced role of the option. - /// - public Type? AdvancedRole { get; set; } - /// /// Gets or sets the option behaviour of the option. /// @@ -102,8 +97,7 @@ public abstract class ModdedOption : IModdedOption /// /// The option title. /// The default value. - /// The Role Type or null if it doesn't belong to a role. - protected ModdedOption(string title, T defaultValue, Type? roleType) + protected ModdedOption(string title, T defaultValue) { Id = ModdedOptionsManager.NextId; Title = title; @@ -111,11 +105,6 @@ public abstract class ModdedOption : IModdedOption Value = defaultValue; StringName = CustomStringName.CreateAndRegister(Title); Visible = () => true; - - if (roleType is not null && roleType.IsAssignableTo(typeof(ICustomRole))) - { - AdvancedRole = roleType; - } } internal void ValueChanged(OptionBehaviour optionBehaviour) diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs index 1773512..3495e86 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs @@ -15,8 +15,7 @@ public class ModdedToggleOption : ModdedOption /// /// The option title. /// The default value. - /// An optional role type. - public ModdedToggleOption(string title, bool defaultValue, Type? roleType = null) : base(title, defaultValue, roleType) + public ModdedToggleOption(string title, bool defaultValue) : base(title, defaultValue) { Data = ScriptableObject.CreateInstance(); diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index c641e88..c0aef14 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -1,5 +1,7 @@ using System.Linq; using HarmonyLib; +using MiraAPI.GameOptions; +using MiraAPI.Roles; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; @@ -31,8 +33,8 @@ public static class GameOptionsMenuPatch var num = 2.1f; var filteredGroups = - GameSettingMenuPatches.SelectedMod?.OptionGroups.Where( - x => x.AdvancedRole is null) ?? []; + GameSettingMenuPatches.SelectedMod?.OptionGroups + .Where(x=> x is not IOptionableGroup) ?? []; foreach (var group in filteredGroups) { @@ -99,7 +101,8 @@ public static class GameOptionsMenuPatch __instance.MapPicker.gameObject.SetActive(false); - var filteredGroups = GameSettingMenuPatches.SelectedMod?.OptionGroups.Where(x => x.AdvancedRole is null) ?? []; + var filteredGroups = GameSettingMenuPatches.SelectedMod?.OptionGroups + .Where(x => x is not IOptionableGroup) ?? []; foreach (var group in filteredGroups) { diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index 3c65092..4f87780 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -113,6 +113,8 @@ public static class GameSettingMenuPatches menu.GameSettingsButton.gameObject.SetActive(true); menu.RoleSettingsButton.gameObject.SetActive(true); + // TODO: reimplement this + /* if (SelectedModIdx != 0) { if (SelectedMod?.Options.Where(x=>x.AdvancedRole==null).ToList().Count == 0) @@ -129,7 +131,7 @@ public static class GameSettingMenuPatches { menu.RoleSettingsButton.transform.localPosition = menu.GameSettingsButton.transform.localPosition; } - } + }*/ if (roles.roleChances != null && SelectedMod != null && SelectedMod.CustomRoles.Count != 0) { diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index 4f8c060..bda1c2b 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using HarmonyLib; +using MiraAPI.GameOptions; using MiraAPI.PluginLoading; using MiraAPI.Roles; using MiraAPI.Utilities; @@ -125,7 +126,8 @@ public static class LobbyViewPanePatches var num = 1.44f; - var filteredGroups = SelectedMod.OptionGroups.Where(x => x.GroupVisible.Invoke() && x.AdvancedRole is null); + var filteredGroups = SelectedMod.OptionGroups + .Where(x => x.GroupVisible.Invoke() && x is not IOptionableGroup); foreach (var group in filteredGroups) { @@ -277,8 +279,9 @@ public static class LobbyViewPanePatches viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one; viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f); - var advancedRoleOptions = SelectedMod.Options - .Where(x => x.AdvancedRole == customRole.GetType()) + var advancedRoleOptions = SelectedMod.OptionGroups + .Where(x => x is IOptionableGroup optionGroup && optionGroup.OptionableType == customRole.GetType()) + .SelectMany(x => x.Options) .ToList(); if (numPerGame > 0 && advancedRoleOptions.Count > 0) @@ -386,8 +389,9 @@ public static class LobbyViewPanePatches var num = viewPanel.yPosStart; var num2 = 1.08f; - var filteredOptions = SelectedMod.Options - .Where(x => x.AdvancedRole == roleType) + var filteredOptions = SelectedMod.OptionGroups + .Where(x => x is IOptionableGroup optionGroup && optionGroup.OptionableType == roleType) + .SelectMany(x=>x.Options) .ToList(); for (var i = 0; i < filteredOptions.Count; i++) diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 33061d4..20ffa18 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; +using MiraAPI.GameOptions; using MiraAPI.Networking; using MiraAPI.Roles; using MiraAPI.Utilities; @@ -304,15 +305,14 @@ public static class RoleSettingMenuPatches var hasImage = role.RoleScreenshot != null; var num = hasImage ? -0.872f : -1; - var filteredOptions = GameSettingMenuPatches.SelectedMod?.Options.Where(x => x.AdvancedRole == role.GetType()) ?? []; + // TODO: create sub groups under the role settings. + var filteredOptions = GameSettingMenuPatches.SelectedMod?.OptionGroups + .Where(x=> x is IOptionableGroup optionableGroup && optionableGroup.OptionableType == role.GetType()) + .SelectMany(x=>x.Options) + .ToList() ?? []; foreach (var option in filteredOptions) { - if (option.AdvancedRole is not null && option.AdvancedRole != role.GetType()) - { - continue; - } - var newOpt = option.CreateOption( __instance.checkboxOrigin, __instance.numberOptionOrigin, @@ -470,8 +470,8 @@ public static class RoleSettingMenuPatches roleOptionSetting.titleText.horizontalAlignment = HorizontalAlignmentOptions.Left; if (GameSettingMenuPatches.SelectedMod is null || - GameSettingMenuPatches.SelectedMod.Options.Exists( - x => x.AdvancedRole != null && x.AdvancedRole.IsInstanceOfType(role))) + GameSettingMenuPatches.SelectedMod.OptionGroups + .Exists(x => x is IOptionableGroup optionGroup && optionGroup.OptionableType == role.GetType())) { var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform); newButton.name = "ConfigButton"; diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index 3c52fb8..ecb4326 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -1,5 +1,6 @@ using System.Text; using BepInEx.Configuration; +using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.PluginLoading; using MiraAPI.Utilities; @@ -11,7 +12,7 @@ namespace MiraAPI.Roles; /// /// Interface for custom roles. /// -public interface ICustomRole +public interface ICustomRole : IOptionable { /// /// Gets the name of the role. From 6f248221d64c1d6199d1d1a8756677232f9bb8e0 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sat, 1 Mar 2025 12:38:04 -0500 Subject: [PATCH 02/11] remove extra log --- MiraAPI/PluginLoading/MiraPluginManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 1fc6be7..eced634 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -54,7 +54,6 @@ public sealed class MiraPluginManager var eventAttribute = method.GetCustomAttribute(); if (eventAttribute != null) { - Logger.Message("Registering event"); var parameters = method.GetParameters(); if (parameters.Length != 1 || !parameters[0].ParameterType.IsSubclassOf(typeof(MiraEvent))) { From bbff2dd2d9ce457d045508554b47922af2303431 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sat, 1 Mar 2025 18:34:18 -0500 Subject: [PATCH 03/11] modifier options (WIP) --- MiraAPI.Example/Modifiers/CaptainModifier.cs | 10 - .../Modifiers/HighPriorityModifier.cs | 9 - .../Modifiers/CaptainModifierSettings.cs | 13 + MiraAPI/GameOptions/ModifierOptionGroup.cs | 15 + .../OptionTypes/ModdedNumberOption.cs | 10 +- MiraAPI/Modifiers/AssignmentConfiguration.cs | 10 + MiraAPI/Modifiers/BaseModifier.cs | 14 +- MiraAPI/Modifiers/ModifierManager.cs | 18 +- MiraAPI/Modifiers/Types/GameModifier.cs | 67 +++- .../Patches/Options/GameOptionsMenuPatch.cs | 352 ++++++++++-------- .../Patches/Options/GameSettingMenuPatches.cs | 116 +++++- .../Patches/Options/RoleSettingMenuPatches.cs | 2 +- MiraAPI/PluginLoading/MiraPluginInfo.cs | 3 + MiraAPI/Roles/ICustomRole.cs | 4 +- MiraAPI/Utilities/Extensions.cs | 10 + 15 files changed, 458 insertions(+), 195 deletions(-) create mode 100644 MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs create mode 100644 MiraAPI/GameOptions/ModifierOptionGroup.cs create mode 100644 MiraAPI/Modifiers/AssignmentConfiguration.cs diff --git a/MiraAPI.Example/Modifiers/CaptainModifier.cs b/MiraAPI.Example/Modifiers/CaptainModifier.cs index 63b7c41..5845e5a 100644 --- a/MiraAPI.Example/Modifiers/CaptainModifier.cs +++ b/MiraAPI.Example/Modifiers/CaptainModifier.cs @@ -5,14 +5,4 @@ namespace MiraAPI.Example.Modifiers; public class CaptainModifier : GameModifier { public override string ModifierName => "Captain"; - - public override int GetAmountPerGame() - { - return 1; - } - - public override int GetAssignmentChance() - { - return 100; - } } diff --git a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs index c640ed8..1d1ddd5 100644 --- a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs +++ b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs @@ -5,15 +5,6 @@ namespace MiraAPI.Example.Modifiers; public class HighPriorityModifier : GameModifier { public override string ModifierName => "High Priority"; - public override int GetAssignmentChance() - { - return 100; - } - - public override int GetAmountPerGame() - { - return 1; - } public override int Priority() { diff --git a/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs b/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs new file mode 100644 index 0000000..2ae297b --- /dev/null +++ b/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs @@ -0,0 +1,13 @@ +using MiraAPI.Example.Modifiers; +using MiraAPI.GameOptions; +using MiraAPI.GameOptions.Attributes; + +namespace MiraAPI.Example.Options.Modifiers; + +public class CaptainModifierSettings : AbstractOptionGroup +{ + public override string GroupName => "Captain Settings"; + + [ModdedNumberOption("Uses", 1, 5)] + public float NumUses { get; set; } = 3; +} diff --git a/MiraAPI/GameOptions/ModifierOptionGroup.cs b/MiraAPI/GameOptions/ModifierOptionGroup.cs new file mode 100644 index 0000000..f845952 --- /dev/null +++ b/MiraAPI/GameOptions/ModifierOptionGroup.cs @@ -0,0 +1,15 @@ +using System.Linq; + +namespace MiraAPI.GameOptions; + +internal class ModifierOptionGroup : AbstractOptionGroup +{ + public override string GroupName { get; } + + public ModifierOptionGroup(string name, IModdedOption[] options, params AbstractOptionGroup[] others) + { + GroupName = name; + Options.AddRange(options); + Options.AddRange(others.SelectMany(x=>x.Options)); + } +} diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs index 0174326..4d38c04 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs @@ -75,11 +75,11 @@ public class ModdedNumberOption : ModdedOption data.Increment = Increment; data.ValidRange = new FloatRange(Min, Max); data.FormatString = formatString ?? - (!Mathf.Approximately(defaultValue, 0) || - !Mathf.Approximately(Increment % 1, 0) || - !Mathf.Approximately(Value % 1, 0) || - !Mathf.Approximately(Min % 1, 0) || - !Mathf.Approximately(Max % 1, 0) ? "0.0" : "0"); + (defaultValue.IsInteger() && + Increment.IsInteger() && + Value.IsInteger() && + Min.IsInteger() && + Max.IsInteger() ? "0" : "0.0"); data.ZeroIsInfinity = ZeroInfinity; data.SuffixType = (NumberSuffixes)SuffixType; diff --git a/MiraAPI/Modifiers/AssignmentConfiguration.cs b/MiraAPI/Modifiers/AssignmentConfiguration.cs new file mode 100644 index 0000000..fd4f3ba --- /dev/null +++ b/MiraAPI/Modifiers/AssignmentConfiguration.cs @@ -0,0 +1,10 @@ +namespace MiraAPI.Modifiers; + +/// +/// Represents the assignment configuration for a game modifier. +/// +/// The default amount of players that can have this modifier in a game. +/// The default chance of the modifier being assigned to a player. +/// Whether Mira should create an amount option for the modifier. +/// Whether Mira should create a chance option for the modifier. +public record struct AssignmentConfiguration(int DefaultAmount, int DefaultChance, bool CreateAmountOption, bool CreateChanceOption); diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs index 473c78f..1bd1082 100644 --- a/MiraAPI/Modifiers/BaseModifier.cs +++ b/MiraAPI/Modifiers/BaseModifier.cs @@ -1,11 +1,13 @@ using System; +using MiraAPI.GameOptions; +using MiraAPI.PluginLoading; namespace MiraAPI.Modifiers; /// /// Base class for all modifiers. /// -public abstract class BaseModifier +public abstract class BaseModifier : IOptionable { /// /// Gets the player that the modifier is attached to. @@ -32,6 +34,16 @@ public abstract class BaseModifier /// public uint TypeId => ModifierManager.GetModifierTypeId(GetType()) ?? throw new InvalidOperationException("Modifier is not registered."); + /// + /// Gets the parent mod of the modifier. + /// + public MiraPluginInfo ParentMod => Array.Find( + MiraPluginManager.Instance.RegisteredPlugins(), + x => x.Modifiers.Exists(y => y.TypeId == TypeId) + ) ?? throw new InvalidOperationException("Modifier is not registered."); + + internal ModifierOptionGroup? ModifierOptionsGroup { get; set; } + /// /// Gets the modifier name. /// diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index 4285a73..fee96ab 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -71,7 +71,23 @@ public static class ModifierManager return true; } - var mod = Activator.CreateInstance(modifierType) as GameModifier; + if (modifierType.GetConstructor(Type.EmptyTypes) == null) + { + Logger.Error($"Modifier {modifierType.FullName} does not have a parameterless constructor!"); + return false; + } + + if (Activator.CreateInstance(modifierType) is not GameModifier mod) + { + Logger.Error($"Modifier {modifierType.FullName} could not be cast to GameModifier!"); + return false; + } + + info.GameModifiers.Add(mod); + + info.PluginConfig.Bind(mod.AmountDefinition, 0); + info.PluginConfig.Bind(mod.ChanceDefinition, 0); + var priority = mod!.Priority(); if (!PrioritiesToIdsMap.TryGetValue(priority, out var list)) diff --git a/MiraAPI/Modifiers/Types/GameModifier.cs b/MiraAPI/Modifiers/Types/GameModifier.cs index abcd1f7..7e4cccb 100644 --- a/MiraAPI/Modifiers/Types/GameModifier.cs +++ b/MiraAPI/Modifiers/Types/GameModifier.cs @@ -1,4 +1,10 @@ -namespace MiraAPI.Modifiers.Types; +using System; +using BepInEx.Configuration; +using MiraAPI.GameOptions.OptionTypes; +using MiraAPI.Utilities; +using Reactor.Utilities; + +namespace MiraAPI.Modifiers.Types; /// /// The base class for a game modifier. Game modifiers are applied at the start of the game on top of the player's role. @@ -8,17 +14,72 @@ public abstract class GameModifier : BaseModifier /// public override bool ShowInFreeplay => true; + /// + /// Gets the assignment configuration for the modifier. + /// + public virtual AssignmentConfiguration AssignmentConfiguration => new(0, 0, true, true); + + internal ConfigDefinition AmountDefinition => new("Modifiers", $"Amount {GetType().FullName}"); + + internal ConfigDefinition ChanceDefinition => new("Modifiers", $"Chance {GetType().FullName}"); + + internal ModdedNumberOption AmountOption { get; } = new("Amount", 0, 0, 15, 1, MiraNumberSuffixes.None); + + internal ModdedNumberOption ChanceOption { get; } = new("Chance", 0, 0, 100, 1, MiraNumberSuffixes.Percent); + /// /// Gets the chance of the modifier being assigned to a player. /// /// An int value between 0 and 100 representing percent. - public abstract int GetAssignmentChance(); + public virtual int GetAssignmentChance() + { + return AssignmentConfiguration.CreateChanceOption && + ParentMod.PluginConfig.TryGetEntry(ChanceDefinition, out ConfigEntry entry) + ? entry.Value + : AssignmentConfiguration.DefaultChance; + } /// /// Gets the amount of players that can have this modifier in a game. /// /// An int value greater than or equal to zero. - public abstract int GetAmountPerGame(); + public virtual int GetAmountPerGame() + { + return AssignmentConfiguration.CreateAmountOption && + ParentMod.PluginConfig.TryGetEntry(AmountDefinition, out ConfigEntry entry) + ? entry.Value + : AssignmentConfiguration.DefaultAmount; + } + + /// + /// Sets the chance of the modifier being assigned to a player. + /// + /// The chance between 0 and 100. + public virtual void SetAssignmentChance(int chance) + { + if (ParentMod.PluginConfig.TryGetEntry(ChanceDefinition, out ConfigEntry entry)) + { + entry.Value = Math.Clamp(chance, 0, 100); + return; + } + + Logger.Error($"Error getting chance configuration for modifier: {ModifierName}"); + } + + /// + /// Sets the amount of players that can have this modifier in a game. + /// + /// The amount of players between zero and the max player count. + public virtual void SetAmountPerGame(int amount) + { + if (ParentMod.PluginConfig.TryGetEntry(AmountDefinition, out ConfigEntry entry)) + { + entry.Value = Math.Max(amount, 0); + return; + } + + Logger.Error($"Error getting amount configuration for modifier: {ModifierName}"); + } /// /// Gets the priority at which the modifier will spawn. The higher the value, the higher up on the assignment list. diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index c0aef14..7b24619 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -1,14 +1,18 @@ -using System.Linq; +using System; +using System.Linq; using HarmonyLib; using MiraAPI.GameOptions; +using MiraAPI.Modifiers.Types; using MiraAPI.Roles; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; +using Reactor.Utilities; using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; using UnityEngine.Events; +using Object = UnityEngine.Object; namespace MiraAPI.Patches.Options; @@ -32,64 +36,105 @@ public static class GameOptionsMenuPatch } var num = 2.1f; - var filteredGroups = - GameSettingMenuPatches.SelectedMod?.OptionGroups - .Where(x=> x is not IOptionableGroup) ?? []; - - foreach (var group in filteredGroups) + if (__instance.name == "MODIFIERS TAB") { - if (group.Options.Count == 0 || group.Header is null) + ModifiersUpdate(ref num); + } + else + { + var filteredGroups = + GameSettingMenuPatches.SelectedMod?.OptionGroups + .Where(x => x is not IOptionableGroup) ?? []; + + foreach (var group in filteredGroups) { - continue; - } - - if (!group.GroupVisible.Invoke()) - { - group.Header.gameObject.SetActive(false); - foreach (var option in group.Options) - { - option.OptionBehaviour?.gameObject.SetActive(false); - } - continue; - } - - group.Header.gameObject.SetActive(true); - group.Header.transform.localScale = Vector3.one * 0.63f; - group.Header.transform.localPosition = new Vector3(-0.903f, num, -2f); - - num -= 0.58f; - - foreach (var opt in group.Options) - { - var newOpt = opt.OptionBehaviour; - - if (newOpt is null) - { - continue; - } - - if (!opt.Visible.Invoke()) - { - newOpt.gameObject.SetActive(false); - continue; - } - - if (!group.AllOptionsHidden) - { - newOpt.gameObject.SetActive(true); - newOpt.transform.localPosition = new Vector3(0.952f, num, -2f); - num -= 0.45f; - } - else - { - newOpt.gameObject.SetActive(false); - } + UpdateGroup(group, ref num); } } __instance.scrollBar.SetYBoundsMax(-num - 1.65f); } + private static void UpdateGroup(AbstractOptionGroup group, ref float num) + { + if (group.Options.Count == 0 || group.Header is null) + { + return; + } + + if (!group.GroupVisible.Invoke()) + { + group.Header.gameObject.SetActive(false); + foreach (var option in group.Options) + { + option.OptionBehaviour?.gameObject.SetActive(false); + } + + return; + } + + group.Header.gameObject.SetActive(true); + group.Header.transform.localScale = Vector3.one * 0.63f; + group.Header.transform.localPosition = new Vector3(-0.903f, num, -2f); + + num -= 0.58f; + + foreach (var opt in group.Options) + { + var newOpt = opt.OptionBehaviour; + + if (newOpt is null) + { + continue; + } + + if (!opt.Visible.Invoke()) + { + newOpt.gameObject.SetActive(false); + continue; + } + + if (!group.AllOptionsHidden) + { + newOpt.gameObject.SetActive(true); + newOpt.transform.localPosition = new Vector3(0.952f, num, -2f); + num -= 0.45f; + } + else + { + newOpt.gameObject.SetActive(false); + } + } + } + + private static void ModifiersUpdate(ref float num) + { + var modifiers = GameSettingMenuPatches.SelectedMod?.Modifiers.Select(x=>x.ModifierOptionsGroup) ?? []; + + foreach (var modGroup in modifiers) + { + if (modGroup != null) + { + UpdateGroup(modGroup, ref num); + } + } + } + + private static void ModifiersCreate(GameOptionsMenu menu) + { + var modifiers = GameSettingMenuPatches.SelectedMod?.GameModifiers ?? []; + + var optLookup = GameSettingMenuPatches.SelectedMod?.OptionGroups + .Where(x => x is IOptionableGroup group && group.OptionableType.IsAssignableTo(typeof(GameModifier))) + .ToLookup(x => (x as IOptionableGroup)!.OptionableType); + + foreach (var mod in modifiers) + { + mod.ModifierOptionsGroup = new ModifierOptionGroup(mod.ModifierName, [mod.AmountOption, mod.ChanceOption], [.. optLookup?[mod.GetType()] ?? []]); + CreateGroup(menu, mod.ModifierOptionsGroup); + } + } + [HarmonyPrefix] [HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))] public static bool SettingsPatch(GameOptionsMenu __instance) @@ -101,107 +146,19 @@ public static class GameOptionsMenuPatch __instance.MapPicker.gameObject.SetActive(false); + if (__instance.name == "MODIFIERS TAB") + { + Logger.Error("Creating modifiers"); + ModifiersCreate(__instance); + return false; + } + var filteredGroups = GameSettingMenuPatches.SelectedMod?.OptionGroups .Where(x => x is not IOptionableGroup) ?? []; foreach (var group in filteredGroups) { - var categoryHeaderMasked = Object.Instantiate( - __instance.categoryHeaderOrigin, - Vector3.zero, - Quaternion.identity, - __instance.settingsContainer); - categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20); - if (group.GroupColor != Color.clear) - { - categoryHeaderMasked.Background.color = group.GroupColor; - categoryHeaderMasked.Divider.color = group.GroupColor; - categoryHeaderMasked.Title.color = group.GroupColor.GetAlternateColor(); - } - - categoryHeaderMasked.Background.size = new Vector2( - categoryHeaderMasked.Background.size.x + 1.5f, - categoryHeaderMasked.Background.size.y); - categoryHeaderMasked.gameObject.SetActive(false); - group.Header = categoryHeaderMasked; - - var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform); - newText.text = "(Click to close)"; - newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f); - newText.gameObject.GetComponent().Destroy(); - - var options = group.Options.Select( - opt => opt.CreateOption( - __instance.checkboxOrigin, - __instance.numberOptionOrigin, - __instance.stringOptionOrigin, - __instance.settingsContainer)); - - foreach (var newOpt in options) - { - newOpt.SetClickMask(__instance.ButtonClickMask); - - SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren(true); - foreach (var renderer in componentsInChildren) - { - if (group.GroupColor != Color.clear) - { - renderer.color = group.GroupColor.GetAlternateColor(); - if (renderer.transform.parent.TryGetComponent(out var btn)) - { - btn.interactableColor = group.GroupColor.GetAlternateColor(); - btn.interactableHoveredColor = Color.white; - } - } - - renderer.material.SetInt(PlayerMaterial.MaskLayer, 20); - } - - foreach (var textMeshPro in newOpt.GetComponentsInChildren(true)) - { - if (group.GroupColor != Color.clear) - { - textMeshPro.color = group.GroupColor; - } - - textMeshPro.fontMaterial.SetFloat(ShaderID.StencilComp, 3f); - textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20); - } - - if (newOpt is ToggleOption toggle) - { - toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset(); - toggle.CheckMark.color = - group.GroupColor != Color.clear ? group.GroupColor : MiraAssets.AcceptedTeal; - var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite") - .GetComponent(); - rend.sprite = MiraAssets.CheckmarkBox.LoadAsset(); - rend.color = group.GroupColor != Color.clear ? group.GroupColor : MiraAssets.AcceptedTeal; - } - - __instance.Children.Add(newOpt); - - newOpt.Initialize(); - newOpt.gameObject.SetActive(false); - } - - var boxCol = categoryHeaderMasked.gameObject.AddComponent(); - boxCol.size = new Vector2(7, 0.7f); - boxCol.offset = new Vector2(1.5f, -0.3f); - - var headerBtn = categoryHeaderMasked.gameObject.AddComponent(); - headerBtn.ClickSound = __instance.BackButton.GetComponent().ClickSound; - headerBtn.OnMouseOver = new UnityEvent(); - headerBtn.OnMouseOut = new UnityEvent(); - headerBtn.OnClick.AddListener( - (UnityAction)(() => - { - group.AllOptionsHidden = !group.AllOptionsHidden; - newText.text = group.AllOptionsHidden - ? "(Click to open)" - : "(Click to close)"; - })); - headerBtn.SetButtonEnableState(true); + CreateGroup(__instance, group); } return false; @@ -241,4 +198,105 @@ public static class GameOptionsMenuPatch return false; } + + private static void CreateGroup(GameOptionsMenu menu, AbstractOptionGroup group) + { + var categoryHeaderMasked = Object.Instantiate( + menu.categoryHeaderOrigin, + Vector3.zero, + Quaternion.identity, + menu.settingsContainer); + + categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20); + if (group.GroupColor != Color.clear) + { + categoryHeaderMasked.Background.color = group.GroupColor; + categoryHeaderMasked.Divider.color = group.GroupColor; + categoryHeaderMasked.Title.color = group.GroupColor.GetAlternateColor(); + } + + categoryHeaderMasked.Background.size = new Vector2( + categoryHeaderMasked.Background.size.x + 1.5f, + categoryHeaderMasked.Background.size.y); + categoryHeaderMasked.gameObject.SetActive(false); + group.Header = categoryHeaderMasked; + + var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform); + newText.text = "(Click to close)"; + newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f); + newText.gameObject.GetComponent().Destroy(); + + var options = group.Options.Select( + opt => opt.CreateOption( + menu.checkboxOrigin, + menu.numberOptionOrigin, + menu.stringOptionOrigin, + menu.settingsContainer)); + + foreach (var newOpt in options) + { + newOpt.SetClickMask(menu.ButtonClickMask); + + SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren(true); + foreach (var renderer in componentsInChildren) + { + if (group.GroupColor != Color.clear) + { + renderer.color = group.GroupColor.GetAlternateColor(); + if (renderer.transform.parent.TryGetComponent(out var btn)) + { + btn.interactableColor = group.GroupColor.GetAlternateColor(); + btn.interactableHoveredColor = Color.white; + } + } + + renderer.material.SetInt(PlayerMaterial.MaskLayer, 20); + } + + foreach (var textMeshPro in newOpt.GetComponentsInChildren(true)) + { + if (group.GroupColor != Color.clear) + { + textMeshPro.color = group.GroupColor; + } + + textMeshPro.fontMaterial.SetFloat(ShaderID.StencilComp, 3f); + textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20); + } + + if (newOpt is ToggleOption toggle) + { + toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset(); + toggle.CheckMark.color = + group.GroupColor != Color.clear ? group.GroupColor : MiraAssets.AcceptedTeal; + var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite") + .GetComponent(); + rend.sprite = MiraAssets.CheckmarkBox.LoadAsset(); + rend.color = group.GroupColor != Color.clear ? group.GroupColor : MiraAssets.AcceptedTeal; + } + + menu.Children.Add(newOpt); + + newOpt.Initialize(); + newOpt.gameObject.SetActive(false); + } + + var boxCol = categoryHeaderMasked.gameObject.AddComponent(); + boxCol.size = new Vector2(7, 0.7f); + boxCol.offset = new Vector2(1.5f, -0.3f); + + var headerBtn = categoryHeaderMasked.gameObject.AddComponent(); + headerBtn.ClickSound = menu.BackButton.GetComponent().ClickSound; + headerBtn.OnMouseOver = new UnityEvent(); + headerBtn.OnMouseOut = new UnityEvent(); + headerBtn.OnClick.AddListener( + (UnityAction)(() => + { + group.AllOptionsHidden = !group.AllOptionsHidden; + newText.text = group.AllOptionsHidden + ? "(Click to open)" + : "(Click to close)"; + })); + headerBtn.SetButtonEnableState(true); + } } diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index 4f87780..ab3ead7 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using HarmonyLib; using MiraAPI.PluginLoading; using MiraAPI.Utilities.Assets; @@ -12,26 +11,41 @@ using Object = UnityEngine.Object; namespace MiraAPI.Patches.Options; -/// -/// Patches for the to add support for custom options. -/// [HarmonyPatch(typeof(GameSettingMenu))] -public static class GameSettingMenuPatches +internal static class GameSettingMenuPatches { - /// - /// Gets the currently selected mod index. - /// public static int SelectedModIdx { get; private set; } - /// - /// Gets the currently selected mod. - /// public static MiraPluginInfo? SelectedMod { get; private set; } private static TextMeshPro? _text; private static Vector3 _roleBtnOgPos; + private static GameOptionsMenu? _modifiersTab; + private static PassiveButton? _modifiersButton; + private static PassiveButton? _smallRolesButton; + + [HarmonyPostfix] + [HarmonyPatch(nameof(GameSettingMenu.ChangeTab))] + public static void ChangeTabPostfix(GameSettingMenu __instance, int tabNum, bool previewOnly) + { + if ((previewOnly && Controller.currentTouchType == Controller.TouchType.Joystick) || !previewOnly) + { + _modifiersTab?.gameObject.SetActive(tabNum == 3); + _modifiersButton?.SelectButton(tabNum == 3); + _smallRolesButton?.SelectButton(tabNum == 2); + } + + if (previewOnly) + { + return; + } + + _modifiersButton?.SelectButton(tabNum == 3); + _smallRolesButton?.SelectButton(tabNum == 2); + } + /// /// Prefix for the method. Sets up the custom options. /// @@ -54,8 +68,6 @@ public static class GameSettingMenuPatches _text.fontSizeMax = 3.2f; _text.overflowMode = TextOverflowModes.Overflow; - UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); - _text.alignment = TextAlignmentOptions.Center; var nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject; @@ -91,6 +103,61 @@ public static class GameSettingMenuPatches } UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); })); + + // clone game settings tab for modifiers + _modifiersTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent); + _modifiersTab.name = "MODIFIERS TAB"; + + // create button for modifiers + __instance.RoleSettingsButton.gameObject.SetActive(false); + _smallRolesButton = Object.Instantiate(__instance.RoleSettingsButton, __instance.RoleSettingsButton.transform.parent); + var pos = new Vector3(-3.65f, _smallRolesButton.transform.localPosition.y, _smallRolesButton.transform.localPosition.z); + _smallRolesButton.transform.localPosition = pos; + _smallRolesButton.OnClick.AddListener((UnityAction)(() => + { + __instance.ChangeTab(2, false); + })); + _smallRolesButton.OnMouseOver.AddListener((UnityAction)(() => + { + __instance.ChangeTab(2, true); + })); + + var roleText = _smallRolesButton.buttonText; + roleText.text = "Roles"; + roleText.GetComponent().Destroy(); + roleText.alignment = TextAlignmentOptions.Center; + roleText.transform.parent.localPosition = new Vector3(-.525f, roleText.transform.parent.localPosition.y, roleText.transform.parent.localPosition.z); + + foreach (var collider in _smallRolesButton.Colliders) + { + if (collider.TryCast() is { } col) + { + col.size = new Vector2(col.size.x / 2, col.size.y); + } + } + foreach (var rend in _smallRolesButton.GetComponentsInChildren(true)) + { + rend.size = new Vector2(rend.size.x / 2, rend.size.y); + } + + _modifiersButton = Object.Instantiate(_smallRolesButton, _smallRolesButton.transform.parent); + _modifiersButton.OnClick = new ButtonClickedEvent(); + _modifiersButton.OnClick.AddListener((UnityAction)(() => + { + __instance.ChangeTab(3, false); + })); + _modifiersButton.OnMouseOver = new UnityEvent(); + _modifiersButton.OnMouseOver.AddListener((UnityAction)(() => + { + __instance.ChangeTab(3, true); + })); + + _modifiersButton.buttonText.text = "Modifiers"; + pos.x = -2.27f; + _modifiersButton.transform.localPosition = pos; + _modifiersButton.name = "ModifiersButton"; + + UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); } private static void UpdateText(GameSettingMenu menu, GameOptionsMenu settings, RolesSettingsMenu roles) @@ -109,9 +176,26 @@ public static class GameSettingMenuPatches _text.text = name[..Math.Min(name.Length, 25)]; } - menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos; - menu.GameSettingsButton.gameObject.SetActive(true); - menu.RoleSettingsButton.gameObject.SetActive(true); + _modifiersButton.gameObject.SetActive(false); + _smallRolesButton.gameObject.SetActive(false); + menu.RoleSettingsButton.gameObject.SetActive(false); + if (SelectedModIdx != 0 && SelectedMod?.GameModifiers.Count != 0) + { + _modifiersButton.gameObject.SetActive(true); + _smallRolesButton.gameObject.SetActive(true); + } + else + { + menu.RoleSettingsButton.gameObject.SetActive(true); + if (SelectedModIdx == 0 && _modifiersTab.gameObject.active) + { + menu.ChangeTab(0, false); + } + } + + //menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos; + //menu.GameSettingsButton.gameObject.SetActive(true); + //menu.RoleSettingsButton.gameObject.SetActive(true); // TODO: reimplement this /* diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 20ffa18..13c190f 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -504,7 +504,7 @@ public static class RoleSettingMenuPatches if (index < GameSettingMenuPatches.SelectedMod?.CustomRoles.Count - 1) { - ScrollerNum += -0.43f; + ScrollerNum -= 0.43f; } return roleOptionSetting; diff --git a/MiraAPI/PluginLoading/MiraPluginInfo.cs b/MiraAPI/PluginLoading/MiraPluginInfo.cs index 68d3ff0..2dfe479 100644 --- a/MiraAPI/PluginLoading/MiraPluginInfo.cs +++ b/MiraAPI/PluginLoading/MiraPluginInfo.cs @@ -6,6 +6,7 @@ using MiraAPI.GameModes; using MiraAPI.GameOptions; using MiraAPI.Hud; using MiraAPI.Modifiers; +using MiraAPI.Modifiers.Types; namespace MiraAPI.PluginLoading; @@ -73,6 +74,8 @@ public class MiraPluginInfo internal List Modifiers { get; } = []; + internal List GameModifiers { get; } = []; + internal Dictionary CustomRoles { get; } = []; internal Dictionary GameModes { get; } = []; diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index ecb4326..27df3cd 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -47,7 +47,7 @@ public interface ICustomRole : IOptionable /// /// Gets the role options group. /// - public RoleOptionsGroup RoleOptionsGroup => Team switch + RoleOptionsGroup RoleOptionsGroup => Team switch { ModdedRoleTeams.Crewmate => RoleOptionsGroup.Crewmate, ModdedRoleTeams.Impostor => RoleOptionsGroup.Impostor, @@ -58,7 +58,7 @@ public interface ICustomRole : IOptionable /// /// Gets the role's TeamIntroCutscene configuration. /// - public TeamIntroConfiguration? IntroConfiguration => Team switch + TeamIntroConfiguration? IntroConfiguration => Team switch { ModdedRoleTeams.Custom => TeamIntroConfiguration.Neutral, _ => null, diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 0a4fee3..a85da1d 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -39,6 +39,16 @@ public static class Extensions BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value))); } + /// + /// Determines if a float is an integer. + /// + /// The float number. + /// True if the float is an integer, false otherwise. + public static bool IsInteger(this float number) + { + return Mathf.Approximately(number, Mathf.Round(number)); + } + /// /// Gets the best constructor for a type based on the specified arguments. /// From f2add2a65be0e89af3c96651a04240382155a6d7 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 2 Mar 2025 19:21:37 -0500 Subject: [PATCH 04/11] this is very broken idk why --- .../Options/Modifiers/FreezeModifierSettings.cs | 13 +++++++++++++ MiraAPI/GameOptions/ModifierOptionGroup.cs | 4 ++-- MiraAPI/Patches/Options/GameOptionsMenuPatch.cs | 15 +++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 MiraAPI.Example/Options/Modifiers/FreezeModifierSettings.cs diff --git a/MiraAPI.Example/Options/Modifiers/FreezeModifierSettings.cs b/MiraAPI.Example/Options/Modifiers/FreezeModifierSettings.cs new file mode 100644 index 0000000..13cdef9 --- /dev/null +++ b/MiraAPI.Example/Options/Modifiers/FreezeModifierSettings.cs @@ -0,0 +1,13 @@ +using MiraAPI.Example.Modifiers.Freezer; +using MiraAPI.GameOptions; +using MiraAPI.GameOptions.Attributes; + +namespace MiraAPI.Example.Options.Modifiers; + +public class FreezeModifierSettings : AbstractOptionGroup +{ + public override string GroupName => "Freeze"; + + [ModdedToggleOption("Use Color")] + public bool UseColor { get; set; } = true; +} diff --git a/MiraAPI/GameOptions/ModifierOptionGroup.cs b/MiraAPI/GameOptions/ModifierOptionGroup.cs index f845952..cfa2eff 100644 --- a/MiraAPI/GameOptions/ModifierOptionGroup.cs +++ b/MiraAPI/GameOptions/ModifierOptionGroup.cs @@ -6,10 +6,10 @@ internal class ModifierOptionGroup : AbstractOptionGroup { public override string GroupName { get; } - public ModifierOptionGroup(string name, IModdedOption[] options, params AbstractOptionGroup[] others) + public ModifierOptionGroup(string name, IModdedOption[] options, params AbstractOptionGroup[] groups) { GroupName = name; Options.AddRange(options); - Options.AddRange(others.SelectMany(x=>x.Options)); + Options.AddRange(groups.SelectMany(x=>x.Options)); } } diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 7b24619..7867c20 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -122,15 +122,22 @@ public static class GameOptionsMenuPatch private static void ModifiersCreate(GameOptionsMenu menu) { - var modifiers = GameSettingMenuPatches.SelectedMod?.GameModifiers ?? []; + var modifiers = GameSettingMenuPatches.SelectedMod?.Modifiers ?? []; var optLookup = GameSettingMenuPatches.SelectedMod?.OptionGroups - .Where(x => x is IOptionableGroup group && group.OptionableType.IsAssignableTo(typeof(GameModifier))) - .ToLookup(x => (x as IOptionableGroup)!.OptionableType); + .OfType() + .ToLookup(x => x.OptionableType); foreach (var mod in modifiers) { - mod.ModifierOptionsGroup = new ModifierOptionGroup(mod.ModifierName, [mod.AmountOption, mod.ChanceOption], [.. optLookup?[mod.GetType()] ?? []]); + IModdedOption[] opts = mod is GameModifier gm ? [gm.AmountOption, gm.ChanceOption] : []; + if (optLookup?[mod.GetType()].Any() == false && opts.Length == 0) + { + continue; + } + + var groups = optLookup?[mod.GetType()] ?? []; + mod.ModifierOptionsGroup = new ModifierOptionGroup(mod.ModifierName, opts, [.. groups.OfType()]); CreateGroup(menu, mod.ModifierOptionsGroup); } } From 68304a5eb83bc36f5bc9377c8c677c7ff2a2c162 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 3 Mar 2025 17:10:14 -0500 Subject: [PATCH 05/11] simplify + partial unscuff --- MiraAPI.Example/Modifiers/CaptainModifier.cs | 14 ++- .../Modifiers/HighPriorityModifier.cs | 13 ++- .../Modifiers/CaptainModifierSettings.cs | 6 + MiraAPI/GameOptions/AbstractOptionGroup.cs | 13 ++- MiraAPI/GameOptions/IOptionableGroup.cs | 14 --- MiraAPI/GameOptions/ModdedOptionsManager.cs | 2 +- MiraAPI/Modifiers/BaseModifier.cs | 2 - MiraAPI/Modifiers/ModifierManager.cs | 32 +++--- MiraAPI/Modifiers/Types/GameModifier.cs | 67 +---------- .../Patches/Options/GameOptionsMenuPatch.cs | 35 ++---- .../Patches/Options/GameSettingMenuPatches.cs | 106 +++++++++++------- .../Patches/Options/LobbyViewPanePatches.cs | 6 +- .../Patches/Options/RoleSettingMenuPatches.cs | 4 +- MiraAPI/PluginLoading/MiraPluginInfo.cs | 2 - MiraAPI/PluginLoading/MiraPluginManager.cs | 5 +- 15 files changed, 144 insertions(+), 177 deletions(-) delete mode 100644 MiraAPI/GameOptions/IOptionableGroup.cs diff --git a/MiraAPI.Example/Modifiers/CaptainModifier.cs b/MiraAPI.Example/Modifiers/CaptainModifier.cs index 5845e5a..ca8a570 100644 --- a/MiraAPI.Example/Modifiers/CaptainModifier.cs +++ b/MiraAPI.Example/Modifiers/CaptainModifier.cs @@ -1,8 +1,20 @@ -using MiraAPI.Modifiers.Types; +using MiraAPI.Example.Options.Modifiers; +using MiraAPI.GameOptions; +using MiraAPI.Modifiers.Types; namespace MiraAPI.Example.Modifiers; public class CaptainModifier : GameModifier { public override string ModifierName => "Captain"; + + public override int GetAssignmentChance() + { + return (int)OptionGroupSingleton.Instance.Chance; + } + + public override int GetAmountPerGame() + { + return (int)OptionGroupSingleton.Instance.Amount; + } } diff --git a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs index 1d1ddd5..f4dc30b 100644 --- a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs +++ b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs @@ -1,4 +1,5 @@ -using MiraAPI.Modifiers.Types; +using MiraAPI.GameOptions; +using MiraAPI.Modifiers.Types; namespace MiraAPI.Example.Modifiers; @@ -6,6 +7,16 @@ public class HighPriorityModifier : GameModifier { public override string ModifierName => "High Priority"; + public override int GetAssignmentChance() + { + return 100; + } + + public override int GetAmountPerGame() + { + return 1; + } + public override int Priority() { return 100; diff --git a/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs b/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs index 2ae297b..0246a1d 100644 --- a/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs +++ b/MiraAPI.Example/Options/Modifiers/CaptainModifierSettings.cs @@ -8,6 +8,12 @@ public class CaptainModifierSettings : AbstractOptionGroup { public override string GroupName => "Captain Settings"; + [ModdedNumberOption("Amount", 0, 5)] + public float Amount { get; set; } = 1; + + [ModdedNumberOption("Chance", 0, 100, 10)] + public float Chance { get; set; } = 50; + [ModdedNumberOption("Uses", 1, 5)] public float NumUses { get; set; } = 3; } diff --git a/MiraAPI/GameOptions/AbstractOptionGroup.cs b/MiraAPI/GameOptions/AbstractOptionGroup.cs index 96dcdd7..c3e9cde 100644 --- a/MiraAPI/GameOptions/AbstractOptionGroup.cs +++ b/MiraAPI/GameOptions/AbstractOptionGroup.cs @@ -16,6 +16,11 @@ public abstract class AbstractOptionGroup /// public abstract string GroupName { get; } + /// + /// Gets the Optionable type of the group. + /// + public virtual Type? OptionableType => null; + /// /// Gets the function that determines whether the group should be visible or not. /// @@ -41,10 +46,8 @@ public abstract class AbstractOptionGroup /// Base class for option groups. An option group is a collection of options that are displayed together in the options menu. /// /// The type of the optionable that this group contains. -public abstract class AbstractOptionGroup : AbstractOptionGroup, IOptionableGroup where T : IOptionable +public abstract class AbstractOptionGroup : AbstractOptionGroup where T : IOptionable { - /// - /// Gets the Optionable type of the group. - /// - public Type OptionableType => typeof(T); + /// + public override Type OptionableType => typeof(T); } diff --git a/MiraAPI/GameOptions/IOptionableGroup.cs b/MiraAPI/GameOptions/IOptionableGroup.cs deleted file mode 100644 index e476548..0000000 --- a/MiraAPI/GameOptions/IOptionableGroup.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace MiraAPI.GameOptions; - -/// -/// Interface for groups that have some type of "Optionable" class. -/// -public interface IOptionableGroup -{ - /// - /// Gets the type of the Optionable class. - /// - Type OptionableType { get; } -} diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index cbb6459..7aaa154 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -108,7 +108,7 @@ public static class ModdedOptionsManager RegisterOption(option, group, property.Name, pluginInfo); } - private static void RegisterOption( + internal static void RegisterOption( IModdedOption option, AbstractOptionGroup group, string propertyName, diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs index 1bd1082..0dd2149 100644 --- a/MiraAPI/Modifiers/BaseModifier.cs +++ b/MiraAPI/Modifiers/BaseModifier.cs @@ -42,8 +42,6 @@ public abstract class BaseModifier : IOptionable x => x.Modifiers.Exists(y => y.TypeId == TypeId) ) ?? throw new InvalidOperationException("Modifier is not registered."); - internal ModifierOptionGroup? ModifierOptionsGroup { get; set; } - /// /// Gets the modifier name. /// diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index fee96ab..5f6bbff 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -63,32 +63,32 @@ public static class ModifierManager IdToTypeModifierMap.Add(GetNextTypeId(), modifierType); TypeToIdModifierMap.Add(modifierType, _nextTypeId); - var bm = (BaseModifier)FormatterServices.GetUninitializedObject(modifierType); // this probably isn't a great idea - info.Modifiers.Add(bm); - if (!typeof(GameModifier).IsAssignableFrom(modifierType)) + BaseModifier modifier; + if (modifierType.GetConstructor(Type.EmptyTypes) != null) + { + modifier = (BaseModifier)Activator.CreateInstance(modifierType)!; + } + else + { + // this is probably terrible but its good enough for now. + modifier = (BaseModifier)FormatterServices.GetUninitializedObject(modifierType); + } + + info.Modifiers.Add(modifier); + + if (modifier is not GameModifier gameModifier) { return true; } if (modifierType.GetConstructor(Type.EmptyTypes) == null) { - Logger.Error($"Modifier {modifierType.FullName} does not have a parameterless constructor!"); + Logger.Error($"Game Modifier {modifierType.FullName} does not have a parameterless constructor!"); return false; } - if (Activator.CreateInstance(modifierType) is not GameModifier mod) - { - Logger.Error($"Modifier {modifierType.FullName} could not be cast to GameModifier!"); - return false; - } - - info.GameModifiers.Add(mod); - - info.PluginConfig.Bind(mod.AmountDefinition, 0); - info.PluginConfig.Bind(mod.ChanceDefinition, 0); - - var priority = mod!.Priority(); + var priority = gameModifier.Priority(); if (!PrioritiesToIdsMap.TryGetValue(priority, out var list)) { diff --git a/MiraAPI/Modifiers/Types/GameModifier.cs b/MiraAPI/Modifiers/Types/GameModifier.cs index 7e4cccb..abcd1f7 100644 --- a/MiraAPI/Modifiers/Types/GameModifier.cs +++ b/MiraAPI/Modifiers/Types/GameModifier.cs @@ -1,10 +1,4 @@ -using System; -using BepInEx.Configuration; -using MiraAPI.GameOptions.OptionTypes; -using MiraAPI.Utilities; -using Reactor.Utilities; - -namespace MiraAPI.Modifiers.Types; +namespace MiraAPI.Modifiers.Types; /// /// The base class for a game modifier. Game modifiers are applied at the start of the game on top of the player's role. @@ -14,72 +8,17 @@ public abstract class GameModifier : BaseModifier /// public override bool ShowInFreeplay => true; - /// - /// Gets the assignment configuration for the modifier. - /// - public virtual AssignmentConfiguration AssignmentConfiguration => new(0, 0, true, true); - - internal ConfigDefinition AmountDefinition => new("Modifiers", $"Amount {GetType().FullName}"); - - internal ConfigDefinition ChanceDefinition => new("Modifiers", $"Chance {GetType().FullName}"); - - internal ModdedNumberOption AmountOption { get; } = new("Amount", 0, 0, 15, 1, MiraNumberSuffixes.None); - - internal ModdedNumberOption ChanceOption { get; } = new("Chance", 0, 0, 100, 1, MiraNumberSuffixes.Percent); - /// /// Gets the chance of the modifier being assigned to a player. /// /// An int value between 0 and 100 representing percent. - public virtual int GetAssignmentChance() - { - return AssignmentConfiguration.CreateChanceOption && - ParentMod.PluginConfig.TryGetEntry(ChanceDefinition, out ConfigEntry entry) - ? entry.Value - : AssignmentConfiguration.DefaultChance; - } + public abstract int GetAssignmentChance(); /// /// Gets the amount of players that can have this modifier in a game. /// /// An int value greater than or equal to zero. - public virtual int GetAmountPerGame() - { - return AssignmentConfiguration.CreateAmountOption && - ParentMod.PluginConfig.TryGetEntry(AmountDefinition, out ConfigEntry entry) - ? entry.Value - : AssignmentConfiguration.DefaultAmount; - } - - /// - /// Sets the chance of the modifier being assigned to a player. - /// - /// The chance between 0 and 100. - public virtual void SetAssignmentChance(int chance) - { - if (ParentMod.PluginConfig.TryGetEntry(ChanceDefinition, out ConfigEntry entry)) - { - entry.Value = Math.Clamp(chance, 0, 100); - return; - } - - Logger.Error($"Error getting chance configuration for modifier: {ModifierName}"); - } - - /// - /// Sets the amount of players that can have this modifier in a game. - /// - /// The amount of players between zero and the max player count. - public virtual void SetAmountPerGame(int amount) - { - if (ParentMod.PluginConfig.TryGetEntry(AmountDefinition, out ConfigEntry entry)) - { - entry.Value = Math.Max(amount, 0); - return; - } - - Logger.Error($"Error getting amount configuration for modifier: {ModifierName}"); - } + public abstract int GetAmountPerGame(); /// /// Gets the priority at which the modifier will spawn. The higher the value, the higher up on the assignment list. diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 7867c20..744c754 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -2,6 +2,7 @@ using System.Linq; using HarmonyLib; using MiraAPI.GameOptions; +using MiraAPI.Modifiers; using MiraAPI.Modifiers.Types; using MiraAPI.Roles; using MiraAPI.Utilities; @@ -44,7 +45,7 @@ public static class GameOptionsMenuPatch { var filteredGroups = GameSettingMenuPatches.SelectedMod?.OptionGroups - .Where(x => x is not IOptionableGroup) ?? []; + .Where(x => x.OptionableType == null) ?? []; foreach (var group in filteredGroups) { @@ -109,36 +110,22 @@ public static class GameOptionsMenuPatch private static void ModifiersUpdate(ref float num) { - var modifiers = GameSettingMenuPatches.SelectedMod?.Modifiers.Select(x=>x.ModifierOptionsGroup) ?? []; + var groups = GameSettingMenuPatches.SelectedMod?.OptionGroups + .Where(x => x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; - foreach (var modGroup in modifiers) + foreach (var modGroup in groups) { - if (modGroup != null) - { - UpdateGroup(modGroup, ref num); - } + UpdateGroup(modGroup, ref num); } } private static void ModifiersCreate(GameOptionsMenu menu) { - var modifiers = GameSettingMenuPatches.SelectedMod?.Modifiers ?? []; - - var optLookup = GameSettingMenuPatches.SelectedMod?.OptionGroups - .OfType() - .ToLookup(x => x.OptionableType); - - foreach (var mod in modifiers) + var groups = GameSettingMenuPatches.SelectedMod?.OptionGroups + .Where(x => x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true) ?? []; + foreach (var group in groups) { - IModdedOption[] opts = mod is GameModifier gm ? [gm.AmountOption, gm.ChanceOption] : []; - if (optLookup?[mod.GetType()].Any() == false && opts.Length == 0) - { - continue; - } - - var groups = optLookup?[mod.GetType()] ?? []; - mod.ModifierOptionsGroup = new ModifierOptionGroup(mod.ModifierName, opts, [.. groups.OfType()]); - CreateGroup(menu, mod.ModifierOptionsGroup); + CreateGroup(menu, group); } } @@ -161,7 +148,7 @@ public static class GameOptionsMenuPatch } var filteredGroups = GameSettingMenuPatches.SelectedMod?.OptionGroups - .Where(x => x is not IOptionableGroup) ?? []; + .Where(x => x.OptionableType == null) ?? []; foreach (var group in filteredGroups) { diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index ab3ead7..bb52c4a 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -1,5 +1,8 @@ using System; +using System.Linq; using HarmonyLib; +using MiraAPI.Modifiers; +using MiraAPI.Modifiers.Types; using MiraAPI.PluginLoading; using MiraAPI.Utilities.Assets; using Reactor.Utilities.Extensions; @@ -73,36 +76,43 @@ internal static class GameSettingMenuPatches var nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject; nextButton.transform.localPosition = new Vector3(-2.2663f, 1.5272f, -25f); nextButton.name = "RightArrowButton"; - nextButton.transform.FindChild("Inactive").gameObject.GetComponent().sprite = MiraAssets.NextButton.LoadAsset(); - nextButton.transform.FindChild("Active").gameObject.GetComponent().sprite = MiraAssets.NextButtonActive.LoadAsset(); + nextButton.transform.FindChild("Inactive").gameObject.GetComponent().sprite = + MiraAssets.NextButton.LoadAsset(); + nextButton.transform.FindChild("Active").gameObject.GetComponent().sprite = + MiraAssets.NextButtonActive.LoadAsset(); nextButton.gameObject.GetComponent().DestroyImmediate(); var passiveButton = nextButton.gameObject.GetComponent(); passiveButton.OnClick = new ButtonClickedEvent(); - passiveButton.OnClick.AddListener((UnityAction)(() => - { - SelectedModIdx += 1; - if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length) + passiveButton.OnClick.AddListener( + (UnityAction)(() => { - SelectedModIdx = 0; - } - UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); - })); + SelectedModIdx += 1; + if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length) + { + SelectedModIdx = 0; + } + + UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); + })); var backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject; backButton.transform.localPosition = new Vector3(-4.4209f, 1.5272f, -25f); backButton.name = "LeftArrowButton"; backButton.gameObject.GetComponent().Destroy(); - backButton.transform.FindChild("Active").gameObject.GetComponent().flipX = backButton.transform.FindChild("Inactive").gameObject.GetComponent().flipX = true; - backButton.gameObject.GetComponent().OnClick.AddListener((UnityAction)(() => - { - SelectedModIdx -= 1; - if (SelectedModIdx < 0) + backButton.transform.FindChild("Active").gameObject.GetComponent().flipX = + backButton.transform.FindChild("Inactive").gameObject.GetComponent().flipX = true; + backButton.gameObject.GetComponent().OnClick.AddListener( + (UnityAction)(() => { - SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length; - } - UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); - })); + SelectedModIdx -= 1; + if (SelectedModIdx < 0) + { + SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length; + } + + UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); + })); // clone game settings tab for modifiers _modifiersTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent); @@ -110,23 +120,33 @@ internal static class GameSettingMenuPatches // create button for modifiers __instance.RoleSettingsButton.gameObject.SetActive(false); - _smallRolesButton = Object.Instantiate(__instance.RoleSettingsButton, __instance.RoleSettingsButton.transform.parent); - var pos = new Vector3(-3.65f, _smallRolesButton.transform.localPosition.y, _smallRolesButton.transform.localPosition.z); + _smallRolesButton = Object.Instantiate( + __instance.RoleSettingsButton, + __instance.RoleSettingsButton.transform.parent); + var pos = new Vector3( + -3.65f, + _smallRolesButton.transform.localPosition.y, + _smallRolesButton.transform.localPosition.z); _smallRolesButton.transform.localPosition = pos; - _smallRolesButton.OnClick.AddListener((UnityAction)(() => - { - __instance.ChangeTab(2, false); - })); - _smallRolesButton.OnMouseOver.AddListener((UnityAction)(() => - { - __instance.ChangeTab(2, true); - })); + _smallRolesButton.OnClick.AddListener( + (UnityAction)(() => + { + __instance.ChangeTab(2, false); + })); + _smallRolesButton.OnMouseOver.AddListener( + (UnityAction)(() => + { + __instance.ChangeTab(2, true); + })); var roleText = _smallRolesButton.buttonText; roleText.text = "Roles"; roleText.GetComponent().Destroy(); roleText.alignment = TextAlignmentOptions.Center; - roleText.transform.parent.localPosition = new Vector3(-.525f, roleText.transform.parent.localPosition.y, roleText.transform.parent.localPosition.z); + roleText.transform.parent.localPosition = new Vector3( + -.525f, + roleText.transform.parent.localPosition.y, + roleText.transform.parent.localPosition.z); foreach (var collider in _smallRolesButton.Colliders) { @@ -135,6 +155,7 @@ internal static class GameSettingMenuPatches col.size = new Vector2(col.size.x / 2, col.size.y); } } + foreach (var rend in _smallRolesButton.GetComponentsInChildren(true)) { rend.size = new Vector2(rend.size.x / 2, rend.size.y); @@ -142,15 +163,17 @@ internal static class GameSettingMenuPatches _modifiersButton = Object.Instantiate(_smallRolesButton, _smallRolesButton.transform.parent); _modifiersButton.OnClick = new ButtonClickedEvent(); - _modifiersButton.OnClick.AddListener((UnityAction)(() => - { - __instance.ChangeTab(3, false); - })); + _modifiersButton.OnClick.AddListener( + (UnityAction)(() => + { + __instance.ChangeTab(3, false); + })); _modifiersButton.OnMouseOver = new UnityEvent(); - _modifiersButton.OnMouseOver.AddListener((UnityAction)(() => - { - __instance.ChangeTab(3, true); - })); + _modifiersButton.OnMouseOver.AddListener( + (UnityAction)(() => + { + __instance.ChangeTab(3, true); + })); _modifiersButton.buttonText.text = "Modifiers"; pos.x = -2.27f; @@ -179,7 +202,8 @@ internal static class GameSettingMenuPatches _modifiersButton.gameObject.SetActive(false); _smallRolesButton.gameObject.SetActive(false); menu.RoleSettingsButton.gameObject.SetActive(false); - if (SelectedModIdx != 0 && SelectedMod?.GameModifiers.Count != 0) + if (SelectedModIdx != 0 && + SelectedMod?.OptionGroups.Exists(x => x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) == true) { _modifiersButton.gameObject.SetActive(true); _smallRolesButton.gameObject.SetActive(true); @@ -225,11 +249,13 @@ internal static class GameSettingMenuPatches { child.gameObject.DestroyImmediate(); } + roles.advancedSettingChildren.Clear(); roles.advancedSettingChildren = null; } - foreach (var header in roles.RoleChancesSettings.transform.GetComponentsInChildren()) + foreach (var header in roles.RoleChancesSettings.transform + .GetComponentsInChildren()) { header.gameObject.DestroyImmediate(); } diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index bda1c2b..fedded2 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -127,7 +127,7 @@ public static class LobbyViewPanePatches var num = 1.44f; var filteredGroups = SelectedMod.OptionGroups - .Where(x => x.GroupVisible.Invoke() && x is not IOptionableGroup); + .Where(x => x.OptionableType == null && x.GroupVisible.Invoke()); foreach (var group in filteredGroups) { @@ -280,7 +280,7 @@ public static class LobbyViewPanePatches viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f); var advancedRoleOptions = SelectedMod.OptionGroups - .Where(x => x is IOptionableGroup optionGroup && optionGroup.OptionableType == customRole.GetType()) + .Where(x => x.OptionableType == customRole.GetType()) .SelectMany(x => x.Options) .ToList(); @@ -390,7 +390,7 @@ public static class LobbyViewPanePatches var num2 = 1.08f; var filteredOptions = SelectedMod.OptionGroups - .Where(x => x is IOptionableGroup optionGroup && optionGroup.OptionableType == roleType) + .Where(x => x.OptionableType == roleType) .SelectMany(x=>x.Options) .ToList(); diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 13c190f..af17682 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -307,7 +307,7 @@ public static class RoleSettingMenuPatches // TODO: create sub groups under the role settings. var filteredOptions = GameSettingMenuPatches.SelectedMod?.OptionGroups - .Where(x=> x is IOptionableGroup optionableGroup && optionableGroup.OptionableType == role.GetType()) + .Where(x=> x.OptionableType == role.GetType()) .SelectMany(x=>x.Options) .ToList() ?? []; @@ -471,7 +471,7 @@ public static class RoleSettingMenuPatches if (GameSettingMenuPatches.SelectedMod is null || GameSettingMenuPatches.SelectedMod.OptionGroups - .Exists(x => x is IOptionableGroup optionGroup && optionGroup.OptionableType == role.GetType())) + .Exists(x => x.OptionableType == role.GetType())) { var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform); newButton.name = "ConfigButton"; diff --git a/MiraAPI/PluginLoading/MiraPluginInfo.cs b/MiraAPI/PluginLoading/MiraPluginInfo.cs index 2dfe479..efbf20c 100644 --- a/MiraAPI/PluginLoading/MiraPluginInfo.cs +++ b/MiraAPI/PluginLoading/MiraPluginInfo.cs @@ -74,8 +74,6 @@ public class MiraPluginInfo internal List Modifiers { get; } = []; - internal List GameModifiers { get; } = []; - internal Dictionary CustomRoles { get; } = []; internal Dictionary GameModes { get; } = []; diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index eced634..15fe686 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using BepInEx.Unity.IL2CPP; @@ -78,7 +79,7 @@ public sealed class MiraPluginManager if (RegisterRoleAttribute(type, info, out var role)) { - roles.Add(role!); + roles.Add(role); continue; } @@ -172,7 +173,7 @@ public sealed class MiraPluginManager return false; } - private static bool RegisterRoleAttribute(Type type, MiraPluginInfo pluginInfo, out Type? role) + private static bool RegisterRoleAttribute(Type type, MiraPluginInfo pluginInfo, [NotNullWhen(true)] out Type? role) { role = null; try From d97588b356142d5cb802901913ac98caa8f842fd Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 3 Mar 2025 19:12:09 -0500 Subject: [PATCH 06/11] add lobby view pane settings --- .../Patches/Options/LobbyViewPanePatches.cs | 116 ++++++++++++------ 1 file changed, 76 insertions(+), 40 deletions(-) diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index fedded2..f5895be 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -3,11 +3,13 @@ using System.Collections.Generic; using System.Linq; using HarmonyLib; using MiraAPI.GameOptions; +using MiraAPI.Modifiers; using MiraAPI.PluginLoading; using MiraAPI.Roles; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; +using Reactor.Utilities.Extensions; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; @@ -18,17 +20,38 @@ namespace MiraAPI.Patches.Options; [HarmonyPatch(typeof(LobbyViewSettingsPane))] public static class LobbyViewPanePatches { - private static int CurrentSelectedMod { get; set; } + private static int SelectedModIdx { get; set; } - private static MiraPluginInfo? SelectedMod => CurrentSelectedMod == 0 + private static MiraPluginInfo? SelectedMod => SelectedModIdx == 0 ? null - : MiraPluginManager.Instance.RegisteredPlugins()[CurrentSelectedMod - 1]; + : MiraPluginManager.Instance.RegisteredPlugins()[SelectedModIdx - 1]; + + private static PassiveButton? ModifiersTabButton { get; set; } + + private static StringNames ModifiersTabName { get; } = CustomStringName.CreateAndRegister("ModifiersTab"); [HarmonyPostfix] [HarmonyPatch(nameof(LobbyViewSettingsPane.Awake))] public static void AwakePatch(LobbyViewSettingsPane __instance) { __instance.gameModeText.transform.localPosition = new Vector3(-2.3f, 2.4f, -2); + __instance.gameModeText.GetComponent().Destroy(); + + // create modifiers button + ModifiersTabButton = Object.Instantiate(__instance.rolesTabButton, __instance.rolesTabButton.transform.parent); + ModifiersTabButton.buttonText.gameObject.GetComponent().Destroy(); + ModifiersTabButton.name = "ModifiersTabButton"; + var pos = ModifiersTabButton.transform.localPosition; + pos.x = 2.1f; + ModifiersTabButton.transform.localPosition = pos; + ModifiersTabButton.buttonText.text = "Modifiers"; + ModifiersTabButton.OnClick = new Button.ButtonClickedEvent(); + ModifiersTabButton.OnClick.AddListener( + (UnityAction)(() => + { + __instance.ChangeTab(ModifiersTabName); + })); + ModifiersTabButton.gameObject.SetActive(SelectedModIdx!=0); // Create the next button var nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject; @@ -50,14 +73,13 @@ public static class LobbyViewPanePatches passiveButton.OnClick.AddListener( (UnityAction)(() => { - CurrentSelectedMod += 1; - if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins().Length) + SelectedModIdx += 1; + if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length) { - CurrentSelectedMod = 0; + SelectedModIdx = 0; } - __instance.RefreshTab(); - __instance.scrollBar.ScrollToTop(); + Refresh(__instance); })); // Create the back button @@ -73,34 +95,56 @@ public static class LobbyViewPanePatches passiveButton2.OnClick.AddListener( (UnityAction)(() => { - CurrentSelectedMod -= 1; - if (CurrentSelectedMod < 0) + SelectedModIdx -= 1; + if (SelectedModIdx < 0) { - CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins().Length; + SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length; } - __instance.RefreshTab(); - __instance.scrollBar.ScrollToTop(); + Refresh(__instance); })); } - [HarmonyPostfix] - [HarmonyPatch(nameof(LobbyViewSettingsPane.Update))] - public static void UpdatePatch(LobbyViewSettingsPane __instance) + private static void Refresh(LobbyViewSettingsPane menu) { - __instance.gameModeText.text = SelectedMod?.PluginInfo.Metadata.Name ?? "Default"; + ModifiersTabButton?.gameObject.SetActive(SelectedModIdx != 0); + menu.gameModeText.text = SelectedMod?.PluginInfo.Metadata.Name ?? "Default"; + menu.RefreshTab(); + menu.scrollBar.ScrollToTop(); + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(LobbyViewSettingsPane.SetTab))] + public static bool SetTabPatch(LobbyViewSettingsPane __instance) + { + if (__instance.currentTab != ModifiersTabName || SelectedMod == null) + { + ModifiersTabButton?.SelectButton(false); + return true; + } + + __instance.taskTabButton.SelectButton(false); + __instance.rolesTabButton.SelectButton(false); + + ModifiersTabButton?.SelectButton(true); + var filteredGroups = SelectedMod.OptionGroups + .Where(x => x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true); + DrawOptions(__instance, filteredGroups); + return false; } [HarmonyPrefix] [HarmonyPatch(nameof(LobbyViewSettingsPane.DrawNormalTab))] public static bool DrawNormalTabPatch(LobbyViewSettingsPane __instance) { - if (CurrentSelectedMod == 0) + if (SelectedModIdx == 0 || SelectedMod == null) { return true; } - DrawOptionsTab(__instance); + var filteredGroups = SelectedMod.OptionGroups + .Where(x => x.OptionableType == null && x.GroupVisible.Invoke()); + DrawOptions(__instance, filteredGroups); return false; } @@ -108,7 +152,7 @@ public static class LobbyViewPanePatches [HarmonyPatch(nameof(LobbyViewSettingsPane.DrawRolesTab))] public static bool DrawRolesTabPatch(LobbyViewSettingsPane __instance) { - if (CurrentSelectedMod == 0) + if (SelectedModIdx == 0) { return true; } @@ -117,30 +161,22 @@ public static class LobbyViewPanePatches return false; } - private static void DrawOptionsTab(LobbyViewSettingsPane instance) + private static void DrawOptions(LobbyViewSettingsPane menu, IEnumerable groups) { - if (SelectedMod == null) - { - return; - } - var num = 1.44f; - var filteredGroups = SelectedMod.OptionGroups - .Where(x => x.OptionableType == null && x.GroupVisible.Invoke()); - - foreach (var group in filteredGroups) + foreach (var group in groups) { var categoryHeaderMasked = Object.Instantiate( - instance.categoryHeaderOrigin, - instance.settingsContainer, + menu.categoryHeaderOrigin, + menu.settingsContainer, true); categoryHeaderMasked.SetHeader(StringNames.Name, 61); categoryHeaderMasked.Title.text = group.GroupName; categoryHeaderMasked.transform.localScale = Vector3.one; categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, num, -2f); - instance.settingsInfo.Add(categoryHeaderMasked.gameObject); - num -= 0.85f; + menu.settingsInfo.Add(categoryHeaderMasked.gameObject); + num -= 1.05f; var i = 0; @@ -152,8 +188,8 @@ public static class LobbyViewPanePatches } var viewSettingsInfoPanel = Object.Instantiate( - instance.infoPanelOrigin, - instance.settingsContainer, + menu.infoPanelOrigin, + menu.settingsContainer, true); viewSettingsInfoPanel.transform.localScale = Vector3.one; float num2; @@ -162,7 +198,7 @@ public static class LobbyViewPanePatches num2 = -8.95f; if (i > 0) { - num -= 0.59f; + num -= 0.85f; } } else @@ -186,14 +222,14 @@ public static class LobbyViewPanePatches viewSettingsInfoPanel.SetInfo(data.Title, data.GetValueString(option.GetFloatData()), 61); } - instance.settingsInfo.Add(viewSettingsInfoPanel.gameObject); + menu.settingsInfo.Add(viewSettingsInfoPanel.gameObject); i++; } - num -= 0.59f; + num -= 0.85f; } - instance.scrollBar.CalculateAndSetYBounds(instance.settingsInfo.Count + 10, 2f, 6f, 0.59f); + menu.scrollBar.CalculateAndSetYBounds(menu.settingsInfo.Count + 10, 2f, 6f, 0.85f); } private static void DrawRolesTab(LobbyViewSettingsPane instance) From e52f48c20d870e996d10a855274c6dafeee5c3d3 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 3 Mar 2025 19:28:20 -0500 Subject: [PATCH 07/11] allow any groups to be shown in modifiers menu --- .../Modifiers/GeneralModifierOptions.cs | 19 +++++++++++++++++++ MiraAPI/GameOptions/AbstractOptionGroup.cs | 6 ++++++ .../Patches/Options/GameOptionsMenuPatch.cs | 4 ++-- .../Patches/Options/GameSettingMenuPatches.cs | 2 +- .../Patches/Options/LobbyViewPanePatches.cs | 2 +- 5 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 MiraAPI.Example/Modifiers/GeneralModifierOptions.cs diff --git a/MiraAPI.Example/Modifiers/GeneralModifierOptions.cs b/MiraAPI.Example/Modifiers/GeneralModifierOptions.cs new file mode 100644 index 0000000..378c460 --- /dev/null +++ b/MiraAPI.Example/Modifiers/GeneralModifierOptions.cs @@ -0,0 +1,19 @@ +using MiraAPI.GameOptions; +using MiraAPI.GameOptions.Attributes; + +namespace MiraAPI.Example.Modifiers; + +public class GeneralModifierOptions : AbstractOptionGroup +{ + public override string GroupName => "General"; + + public override uint GroupPriority => 0; + + public override bool ShowInModifiersMenu => true; + + [ModdedToggleOption("Some Boolean Option")] + public bool SomeBooleanOption { get; set; } = true; + + [ModdedNumberOption("Number Option", 0, 10, 1)] + public float NumberOption { get; set; } = 5; +} diff --git a/MiraAPI/GameOptions/AbstractOptionGroup.cs b/MiraAPI/GameOptions/AbstractOptionGroup.cs index c3e9cde..45f9bd4 100644 --- a/MiraAPI/GameOptions/AbstractOptionGroup.cs +++ b/MiraAPI/GameOptions/AbstractOptionGroup.cs @@ -21,6 +21,12 @@ public abstract class AbstractOptionGroup /// public virtual Type? OptionableType => null; + /// + /// Gets a value indicating whether the group should be shown in the modifiers menu. + /// + // TODO: make this not a boolean + public virtual bool ShowInModifiersMenu => false; + /// /// Gets the function that determines whether the group should be visible or not. /// diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 744c754..825ecc6 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -111,7 +111,7 @@ public static class GameOptionsMenuPatch private static void ModifiersUpdate(ref float num) { var groups = GameSettingMenuPatches.SelectedMod?.OptionGroups - .Where(x => x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; + .Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; foreach (var modGroup in groups) { @@ -122,7 +122,7 @@ public static class GameOptionsMenuPatch private static void ModifiersCreate(GameOptionsMenu menu) { var groups = GameSettingMenuPatches.SelectedMod?.OptionGroups - .Where(x => x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true) ?? []; + .Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true) ?? []; foreach (var group in groups) { CreateGroup(menu, group); diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index bb52c4a..cb84518 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -203,7 +203,7 @@ internal static class GameSettingMenuPatches _smallRolesButton.gameObject.SetActive(false); menu.RoleSettingsButton.gameObject.SetActive(false); if (SelectedModIdx != 0 && - SelectedMod?.OptionGroups.Exists(x => x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) == true) + SelectedMod?.OptionGroups.Exists(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) == true) { _modifiersButton.gameObject.SetActive(true); _smallRolesButton.gameObject.SetActive(true); diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index f5895be..9b2a934 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -128,7 +128,7 @@ public static class LobbyViewPanePatches ModifiersTabButton?.SelectButton(true); var filteredGroups = SelectedMod.OptionGroups - .Where(x => x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true); + .Where(x => x.GroupVisible() && (x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true)); DrawOptions(__instance, filteredGroups); return false; } From b6f304fe1dfcdb2d93d9f629736836a2727e30e1 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 4 Mar 2025 16:59:55 -0500 Subject: [PATCH 08/11] organize example --- .../{ => Options}/Modifiers/GeneralModifierOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename MiraAPI.Example/{ => Options}/Modifiers/GeneralModifierOptions.cs (91%) diff --git a/MiraAPI.Example/Modifiers/GeneralModifierOptions.cs b/MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs similarity index 91% rename from MiraAPI.Example/Modifiers/GeneralModifierOptions.cs rename to MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs index 378c460..1414799 100644 --- a/MiraAPI.Example/Modifiers/GeneralModifierOptions.cs +++ b/MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs @@ -1,7 +1,7 @@ using MiraAPI.GameOptions; using MiraAPI.GameOptions.Attributes; -namespace MiraAPI.Example.Modifiers; +namespace MiraAPI.Example.Options.Modifiers; public class GeneralModifierOptions : AbstractOptionGroup { From e680b5c87244c4209431e293f9fedb74264cb800 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 4 Mar 2025 17:00:05 -0500 Subject: [PATCH 09/11] unity lifetime check --- MiraAPI/Patches/Options/GameOptionsMenuPatch.cs | 2 +- MiraAPI/Patches/Options/GameSettingMenuPatches.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 825ecc6..fa49d2b 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -111,7 +111,7 @@ public static class GameOptionsMenuPatch private static void ModifiersUpdate(ref float num) { var groups = GameSettingMenuPatches.SelectedMod?.OptionGroups - .Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; + .Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true) ?? []; foreach (var modGroup in groups) { diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index cb84518..8676d4b 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -35,7 +35,7 @@ internal static class GameSettingMenuPatches { if ((previewOnly && Controller.currentTouchType == Controller.TouchType.Joystick) || !previewOnly) { - _modifiersTab?.gameObject.SetActive(tabNum == 3); + if (_modifiersTab) _modifiersTab?.gameObject.SetActive(tabNum == 3); _modifiersButton?.SelectButton(tabNum == 3); _smallRolesButton?.SelectButton(tabNum == 2); } From e43cd0cefffc430ff41091f86bb6403bd1a4de8b Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 4 Mar 2025 17:00:16 -0500 Subject: [PATCH 10/11] remove debug log --- MiraAPI/Patches/Options/GameOptionsMenuPatch.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index fa49d2b..f207d3b 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -142,7 +142,6 @@ public static class GameOptionsMenuPatch if (__instance.name == "MODIFIERS TAB") { - Logger.Error("Creating modifiers"); ModifiersCreate(__instance); return false; } From e02c06ca07d13d2acd4dd810664dac1e849df03d Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 4 Mar 2025 18:29:30 -0500 Subject: [PATCH 11/11] final cleanup --- MiraAPI.Example/Modifiers/HighPriorityModifier.cs | 3 +-- MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs | 3 +-- MiraAPI.Example/Options/Roles/TeleporterOptions.cs | 3 +-- MiraAPI/GameOptions/OptionTypes/ModdedOption.cs | 1 - MiraAPI/Patches/Options/GameOptionsMenuPatch.cs | 5 +---- MiraAPI/Patches/Options/GameSettingMenuPatches.cs | 2 -- MiraAPI/Patches/Options/RoleSettingMenuPatches.cs | 1 - MiraAPI/PluginLoading/MiraPluginInfo.cs | 1 - 8 files changed, 4 insertions(+), 15 deletions(-) diff --git a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs index f4dc30b..9f13b18 100644 --- a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs +++ b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs @@ -1,5 +1,4 @@ -using MiraAPI.GameOptions; -using MiraAPI.Modifiers.Types; +using MiraAPI.Modifiers.Types; namespace MiraAPI.Example.Modifiers; diff --git a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs index 6113408..1473068 100644 --- a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs +++ b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs @@ -1,5 +1,4 @@ -using System; -using MiraAPI.Example.Roles; +using MiraAPI.Example.Roles; using MiraAPI.GameOptions; using MiraAPI.GameOptions.Attributes; using MiraAPI.Utilities; diff --git a/MiraAPI.Example/Options/Roles/TeleporterOptions.cs b/MiraAPI.Example/Options/Roles/TeleporterOptions.cs index 1b0c1da..b6d02a4 100644 --- a/MiraAPI.Example/Options/Roles/TeleporterOptions.cs +++ b/MiraAPI.Example/Options/Roles/TeleporterOptions.cs @@ -1,5 +1,4 @@ -using System; -using MiraAPI.Example.Roles; +using MiraAPI.Example.Roles; using MiraAPI.GameOptions; using MiraAPI.GameOptions.Attributes; using MiraAPI.GameOptions.OptionTypes; diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index 0cfefa8..670fb3c 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -2,7 +2,6 @@ using BepInEx.Configuration; using MiraAPI.Networking; using MiraAPI.PluginLoading; -using MiraAPI.Roles; using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; using UnityEngine; diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index f207d3b..aca0f2e 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -3,12 +3,9 @@ using System.Linq; using HarmonyLib; using MiraAPI.GameOptions; using MiraAPI.Modifiers; -using MiraAPI.Modifiers.Types; -using MiraAPI.Roles; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; @@ -186,7 +183,7 @@ public static class GameOptionsMenuPatch continue; } - optionBehaviour.OnValueChanged = new System.Action(__instance.ValueChanged); + optionBehaviour.OnValueChanged = new Action(__instance.ValueChanged); } return false; diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index 8676d4b..7781bf8 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -1,8 +1,6 @@ using System; -using System.Linq; using HarmonyLib; using MiraAPI.Modifiers; -using MiraAPI.Modifiers.Types; using MiraAPI.PluginLoading; using MiraAPI.Utilities.Assets; using Reactor.Utilities.Extensions; diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index af17682..126f0b5 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; -using MiraAPI.GameOptions; using MiraAPI.Networking; using MiraAPI.Roles; using MiraAPI.Utilities; diff --git a/MiraAPI/PluginLoading/MiraPluginInfo.cs b/MiraAPI/PluginLoading/MiraPluginInfo.cs index efbf20c..68d3ff0 100644 --- a/MiraAPI/PluginLoading/MiraPluginInfo.cs +++ b/MiraAPI/PluginLoading/MiraPluginInfo.cs @@ -6,7 +6,6 @@ using MiraAPI.GameModes; using MiraAPI.GameOptions; using MiraAPI.Hud; using MiraAPI.Modifiers; -using MiraAPI.Modifiers.Types; namespace MiraAPI.PluginLoading;