diff --git a/Directory.Build.props b/Directory.Build.props index b09c823f..1f8760c7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,7 +8,7 @@ true - 0.4.1 + 0.4.3 dev All Of Us, XtraCube, Angxl diff --git a/MiraAPI.Example/ExamplePlugin.cs b/MiraAPI.Example/ExamplePlugin.cs index 0aed9c4d..b20ec419 100644 --- a/MiraAPI.Example/ExamplePlugin.cs +++ b/MiraAPI.Example/ExamplePlugin.cs @@ -1,5 +1,4 @@ -using System.Linq; -using BepInEx; +using BepInEx; using BepInEx.Configuration; using BepInEx.Unity.IL2CPP; using HarmonyLib; diff --git a/MiraAPI.Example/Options/ExampleOptions2.cs b/MiraAPI.Example/Options/ExampleOptions2.cs index 59de0c6a..6efb8382 100644 --- a/MiraAPI.Example/Options/ExampleOptions2.cs +++ b/MiraAPI.Example/Options/ExampleOptions2.cs @@ -10,6 +10,8 @@ public class ExampleOptions2 : AbstractOptionGroup public override uint GroupPriority => 0; // This group will be displayed first. The default value is uint.MaxValue. + public override MenuCategory ParentMenu => MenuCategory.CustomTwo; + public ModdedPlayerOption PlayerOption2 { get; } = new("YOU MUST CHOSE A PLAYER", false); public ModdedPlayerOption PlayerOption { get; } = new("Eh whatever"); public ModdedToggleOption ToggleOpt1 { get; } = new("Toggle Option 1", false); diff --git a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs index 1be36490..3c1fdeb3 100644 --- a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs +++ b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs @@ -3,8 +3,6 @@ using MiraAPI.GameOptions; using MiraAPI.GameOptions.Attributes; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; -using TMPro; -using UnityEngine; namespace MiraAPI.Example.Options.Roles; diff --git a/MiraAPI/Events/Mira/MiraButtonClickEvent.cs b/MiraAPI/Events/Mira/MiraButtonClickEvent.cs index 401cc11e..0b18028a 100644 --- a/MiraAPI/Events/Mira/MiraButtonClickEvent.cs +++ b/MiraAPI/Events/Mira/MiraButtonClickEvent.cs @@ -6,7 +6,7 @@ namespace MiraAPI.Events.Mira; /// Button click event for s only. Do not use for vanilla s. /// /// The type. -public sealed class MiraButtonClickEvent : MiraCancelableEvent where T : CustomActionButton +public class MiraButtonClickEvent : MiraCancelableEvent where T : CustomActionButton { /// /// Gets the that was clicked. @@ -33,7 +33,7 @@ public sealed class MiraButtonClickEvent : MiraCancelableEvent where T : Cust /// /// Button click event for s only. Do not use for vanilla s. /// -public sealed class MiraButtonClickEvent : MiraCancelableEvent +public class MiraButtonClickEvent : MiraCancelableEvent { /// /// Gets the that was clicked. diff --git a/MiraAPI/Events/MiraEventManager.cs b/MiraAPI/Events/MiraEventManager.cs index 005415ba..30ffb740 100644 --- a/MiraAPI/Events/MiraEventManager.cs +++ b/MiraAPI/Events/MiraEventManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Reflection; -using Reactor.Utilities; namespace MiraAPI.Events; diff --git a/MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs b/MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs index 8b856229..8db74b9a 100644 --- a/MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs +++ b/MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs @@ -16,20 +16,76 @@ public class AfterMurderEvent : MiraEvent public PlayerControl Target { get; } /// - /// Gets the 's , if it exists. + /// Gets the that was framed, if any. + /// + public PlayerControl? Framed { get; } + + /// + /// Gets a value indicating whether the murder was done indirectly. + /// This is commonly used to prevent the from being killed. + /// + public bool IsIndirectAttack { get; } + + /// + /// Gets a value indicating whether the murder was able to bypass protective roles or general defense, such as . + /// + public bool IgnoreDefense { get; } + + /// + /// Gets the 's , if it exists. /// public DeadBody? DeadBody { get; } /// /// Initializes a new instance of the class. /// - /// The killer. - /// The killed . - /// The 's , if it exists. + /// The that killed the . + /// The that was killed. + /// The 's , if it exists. + /// Whether the murder was caused indirectly. + /// Whether the murder ignored protection. + public AfterMurderEvent(PlayerControl source, PlayerControl target, DeadBody? deadBody, bool isIndirect, bool ignoreDefense = false) + { + Source = source; + Target = target; + Framed = null; + DeadBody = deadBody; + IsIndirectAttack = isIndirect; + IgnoreDefense = ignoreDefense; + } + + /// + /// Initializes a new instance of the class. + /// + /// The that killed the . + /// The that was killed. + /// The that was framed. + /// The 's , if it exists. + /// Whether the murder was caused indirectly. + /// Whether the murder ignored protection. + public AfterMurderEvent(PlayerControl source, PlayerControl target, PlayerControl framed, DeadBody? deadBody, bool isIndirect = true, bool ignoreDefense = false) + { + Source = source; + Target = target; + Framed = framed; + DeadBody = deadBody; + IsIndirectAttack = isIndirect; + IgnoreDefense = ignoreDefense; + } + + /// + /// Initializes a new instance of the class. + /// + /// The that killed the . + /// The that was killed. + /// The 's , if it exists. public AfterMurderEvent(PlayerControl source, PlayerControl target, DeadBody? deadBody) { Source = source; Target = target; + Framed = null; DeadBody = deadBody; + IsIndirectAttack = false; + IgnoreDefense = false; } } diff --git a/MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs b/MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs index cc07e219..00a977ef 100644 --- a/MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs +++ b/MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs @@ -17,11 +17,64 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent /// public PlayerControl Target { get; } + /// + /// Gets the that is being framed, if any. + /// + public PlayerControl? Framed { get; } + + /// + /// Gets or sets a value indicating whether the murder is done indirectly. + /// This is commonly used to prevent the from being killed. + /// + public bool IsIndirectAttack { get; set; } + + /// + /// Gets or sets a value indicating whether the murder should bypass protective roles or general defense, such as . + /// + public bool IgnoreDefense { get; set; } + /// /// Gets whether the murder was meant to be done in a meeting, via an enum. /// public MeetingCheck InMeeting { get; } + /// + /// Initializes a new instance of the class. + /// + /// The that is killing the . + /// The that is being killed. + /// Whether the murder is caused indirectly. + /// Whether the murder should ignore protection. + /// Whether the murder is intended to be triggered in a meeting. + public BeforeMurderEvent(PlayerControl source, PlayerControl target, bool isIndirect, bool ignoreDefense = false, MeetingCheck inMeeting = MeetingCheck.Ignore) + { + Source = source; + Target = target; + Framed = null; + IsIndirectAttack = isIndirect; + IgnoreDefense = ignoreDefense; + InMeeting = inMeeting; + } + + /// + /// Initializes a new instance of the class. + /// + /// The that is killing the . + /// The that is being killed. + /// The that is being framed. + /// Whether the murder is caused indirectly. + /// Whether the murder should ignore protection. + /// Whether the murder is intended to be triggered in a meeting. + public BeforeMurderEvent(PlayerControl source, PlayerControl target, PlayerControl framed, bool isIndirect = true, bool ignoreDefense = false, MeetingCheck inMeeting = MeetingCheck.Ignore) + { + Source = source; + Target = target; + Framed = framed; + IsIndirectAttack = isIndirect; + IgnoreDefense = ignoreDefense; + InMeeting = inMeeting; + } + /// /// Initializes a new instance of the class. /// @@ -32,6 +85,9 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent { Source = source; Target = target; + Framed = null; + IsIndirectAttack = false; + IgnoreDefense = false; InMeeting = inMeeting; } @@ -44,6 +100,9 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent { Source = source; Target = target; + Framed = null; + IsIndirectAttack = false; + IgnoreDefense = false; InMeeting = MeetingCheck.Ignore; } } diff --git a/MiraAPI/Events/Vanilla/Meeting/Voting/AfterVoteEvent.cs b/MiraAPI/Events/Vanilla/Meeting/Voting/AfterVoteEvent.cs index 95583f31..60a3d364 100644 --- a/MiraAPI/Events/Vanilla/Meeting/Voting/AfterVoteEvent.cs +++ b/MiraAPI/Events/Vanilla/Meeting/Voting/AfterVoteEvent.cs @@ -1,6 +1,4 @@ -using MiraAPI.Voting; - -namespace MiraAPI.Events.Vanilla.Meeting.Voting; +namespace MiraAPI.Events.Vanilla.Meeting.Voting; /// /// Event that is invoked after the local player successfully votes a player or skips. This event is not cancelable. diff --git a/MiraAPI/Events/Vanilla/Meeting/Voting/BeforeVoteEvent.cs b/MiraAPI/Events/Vanilla/Meeting/Voting/BeforeVoteEvent.cs index 74fde386..e6b28f95 100644 --- a/MiraAPI/Events/Vanilla/Meeting/Voting/BeforeVoteEvent.cs +++ b/MiraAPI/Events/Vanilla/Meeting/Voting/BeforeVoteEvent.cs @@ -1,6 +1,4 @@ -using MiraAPI.Voting; - -namespace MiraAPI.Events.Vanilla.Meeting.Voting; +namespace MiraAPI.Events.Vanilla.Meeting.Voting; /// /// Event that is invoked before the local player confirms a vote on a player or when skipping. This event is cancelable. diff --git a/MiraAPI/Events/Vanilla/Meeting/Voting/HandleVoteEvent.cs b/MiraAPI/Events/Vanilla/Meeting/Voting/HandleVoteEvent.cs index c4457785..2145b32a 100644 --- a/MiraAPI/Events/Vanilla/Meeting/Voting/HandleVoteEvent.cs +++ b/MiraAPI/Events/Vanilla/Meeting/Voting/HandleVoteEvent.cs @@ -25,9 +25,9 @@ public class HandleVoteEvent : MiraCancelableEvent public byte TargetId { get; } /// - /// Gets a value indicating whether to prevent the vote from commencing. + /// Gets or sets a value indicating whether to prevent the vote from commencing. /// - public bool PreventVote { get; } + public bool PreventVote { get; set; } /// /// Gets the of the target. diff --git a/MiraAPI/GameEnd/GameOverManager.cs b/MiraAPI/GameEnd/GameOverManager.cs index eb398358..28d2f484 100644 --- a/MiraAPI/GameEnd/GameOverManager.cs +++ b/MiraAPI/GameEnd/GameOverManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using Reactor.Utilities; namespace MiraAPI.GameEnd; diff --git a/MiraAPI/GameModes/CustomGameModeManager.cs b/MiraAPI/GameModes/CustomGameModeManager.cs index 7e274c48..3edd028b 100644 --- a/MiraAPI/GameModes/CustomGameModeManager.cs +++ b/MiraAPI/GameModes/CustomGameModeManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Reactor.Utilities; namespace MiraAPI.GameModes; diff --git a/MiraAPI/GameOptions/AbstractOptionGroup.cs b/MiraAPI/GameOptions/AbstractOptionGroup.cs index 8be08c6a..01c9e09a 100644 --- a/MiraAPI/GameOptions/AbstractOptionGroup.cs +++ b/MiraAPI/GameOptions/AbstractOptionGroup.cs @@ -2,10 +2,8 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using Il2CppInterop.Runtime.Attributes; using MiraAPI.Modifiers; using MiraAPI.Roles; -using TMPro; using UnityEngine; namespace MiraAPI.GameOptions; @@ -70,6 +68,8 @@ public abstract class AbstractOptionGroup internal bool AllOptionsHidden { get; set; } internal CategoryHeaderMasked? Header { get; set; } + + internal bool Ready { get; set; } } /// @@ -132,6 +132,11 @@ public abstract class AbstractRoleOptionGroup() : AbstractOptionGroup wher /// public enum MenuCategory { + /// + /// Placeholder for indexing purposes. Options don't exist in the presset tab. + /// + Preset, + /// /// Determines the option group is in the game settings tab. /// diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index 3f5352e8..ab92a9e6 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -1,11 +1,9 @@ using System; using BepInEx.Configuration; -using Il2CppInterop.Runtime.Attributes; using MiraAPI.Networking; using MiraAPI.PluginLoading; using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; -using TMPro; using UnityEngine; namespace MiraAPI.GameOptions.OptionTypes; diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index 8a58e39a..3ba33357 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -7,7 +7,6 @@ using MiraAPI.LocalSettings; using MiraAPI.Patches; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; -using Reactor.Utilities; using Rewired; using TMPro; using UnityEngine; diff --git a/MiraAPI/Hud/CustomButtonManager.cs b/MiraAPI/Hud/CustomButtonManager.cs index 5a1ba3b3..b1ddbce8 100644 --- a/MiraAPI/Hud/CustomButtonManager.cs +++ b/MiraAPI/Hud/CustomButtonManager.cs @@ -4,7 +4,6 @@ using System.Collections.ObjectModel; using System.Reflection; using MiraAPI.Events.Mira; using MiraAPI.PluginLoading; -using UnityEngine; namespace MiraAPI.Hud; @@ -18,6 +17,20 @@ public static class CustomButtonManager /// public static ReadOnlyCollection Buttons { get; internal set; } = new([]); + /// + /// Gets a list of all registered button-specific s. + /// + public static ReadOnlyDictionary EventTypes { get; internal set; } = new( + new Dictionary + { }); + + /// + /// Gets a list of all registered button-specific s. + /// + public static ReadOnlyDictionary CancelledEventTypes { get; internal set; } = new( + new Dictionary + { }); + internal static readonly List CustomButtons = []; internal static readonly Dictionary ButtonEventTypes = []; internal static readonly Dictionary ButtonCancelledEventTypes = []; diff --git a/MiraAPI/Hud/MultiTargetButton.cs b/MiraAPI/Hud/MultiTargetButton.cs index 12458d42..d52d845c 100644 --- a/MiraAPI/Hud/MultiTargetButton.cs +++ b/MiraAPI/Hud/MultiTargetButton.cs @@ -1,6 +1,5 @@ using System.Linq; using HarmonyLib; -using Reactor.Utilities.Extensions; using UnityEngine; namespace MiraAPI.Hud; diff --git a/MiraAPI/Keybinds/KeybindManager.cs b/MiraAPI/Keybinds/KeybindManager.cs index 207fab59..042322af 100644 --- a/MiraAPI/Keybinds/KeybindManager.cs +++ b/MiraAPI/Keybinds/KeybindManager.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using Il2CppInterop.Runtime.Attributes; using MiraAPI.Utilities; -using Reactor.Utilities; using Rewired; namespace MiraAPI.Keybinds; diff --git a/MiraAPI/Keybinds/VanillaKeybind.cs b/MiraAPI/Keybinds/VanillaKeybind.cs index 594e5831..5f342883 100644 --- a/MiraAPI/Keybinds/VanillaKeybind.cs +++ b/MiraAPI/Keybinds/VanillaKeybind.cs @@ -1,5 +1,4 @@ using System.Globalization; -using Rewired; namespace MiraAPI.Keybinds; diff --git a/MiraAPI/LocalSettings/Attributes/LocalSettingsButtonAttribute.cs b/MiraAPI/LocalSettings/Attributes/LocalSettingsButtonAttribute.cs index 043a81d5..83a39556 100644 --- a/MiraAPI/LocalSettings/Attributes/LocalSettingsButtonAttribute.cs +++ b/MiraAPI/LocalSettings/Attributes/LocalSettingsButtonAttribute.cs @@ -1,5 +1,4 @@ using System; -using BepInEx.Configuration; namespace MiraAPI.LocalSettings.Attributes; diff --git a/MiraAPI/LocalSettings/LocalSettingsButton.cs b/MiraAPI/LocalSettings/LocalSettingsButton.cs index 6f1fa57a..5a1f3717 100644 --- a/MiraAPI/LocalSettings/LocalSettingsButton.cs +++ b/MiraAPI/LocalSettings/LocalSettingsButton.cs @@ -1,6 +1,5 @@ using System; using MiraAPI.Utilities; -using Reactor.Localization.Utilities; using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; diff --git a/MiraAPI/LocalSettings/LocalSettingsManager.cs b/MiraAPI/LocalSettings/LocalSettingsManager.cs index 2f6fc164..874e2586 100644 --- a/MiraAPI/LocalSettings/LocalSettingsManager.cs +++ b/MiraAPI/LocalSettings/LocalSettingsManager.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Reflection; using BepInEx.Unity.IL2CPP; -using MiraAPI.GameOptions; -using Reactor.Utilities; namespace MiraAPI.LocalSettings; @@ -14,6 +12,7 @@ public static class LocalSettingsManager { internal static readonly Dictionary TypeToTab = []; internal static readonly List Tabs = []; + internal static readonly List AvailableTabs = []; internal static bool RegisterTab(Type type, BasePlugin pluginInfo) { @@ -29,6 +28,10 @@ public static class LocalSettingsManager } Tabs.Add(tab); + if (tab.ShouldCreateButton) + { + AvailableTabs.Add(tab); + } TypeToTab.Add(type, tab); typeof(LocalSettingsTabSingleton<>).MakeGenericType(type) diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 1b3d8213..cea542dd 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -8,7 +8,6 @@ using MiraAPI.LocalSettings; using MiraAPI.Modifiers.ModifierDisplay; using MiraAPI.Modifiers.Types; using MiraAPI.Patches.Roles; -using Reactor.Utilities; using Reactor.Utilities.Attributes; using UnityEngine; diff --git a/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs b/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs index 4a6411ff..e93f477b 100644 --- a/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs +++ b/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs @@ -4,7 +4,6 @@ using System.Linq; using HarmonyLib; using Il2CppInterop.Runtime.Attributes; using MiraAPI.LocalSettings; -using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Utilities.Attributes; using Reactor.Utilities.Extensions; @@ -138,7 +137,7 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr) internal void DestroyComponent(ModifierUiComponent component) { _modifiers.Remove(component.Modifier!); - component.gameObject.DeepDestroy(); + component.gameObject.Destroy(); RefreshModifiers(); } @@ -208,7 +207,7 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr) continue; } - mod.Value.gameObject.DeepDestroy(); + mod.Value.gameObject.Destroy(); _modifiers.Remove(mod.Key); } diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs index cb1671a5..4d694288 100644 --- a/MiraAPI/Modifiers/ModifierExtensions.cs +++ b/MiraAPI/Modifiers/ModifierExtensions.cs @@ -5,7 +5,6 @@ using MiraAPI.Networking; using MiraAPI.Networking.Modifiers; using Reactor.Networking.Attributes; using Reactor.Networking.Rpc; -using Reactor.Utilities; namespace MiraAPI.Modifiers; diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index 877e43b9..28faa8bc 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -6,7 +6,6 @@ using MiraAPI.Modifiers.Types; using MiraAPI.PluginLoading; using MiraAPI.Roles; using MiraAPI.Utilities; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using Random = System.Random; @@ -137,7 +136,10 @@ public static class ModifierManager var validPlayers = plrs.Where(x => IsGameModifierValid(x, modifier, modifier.TypeId)).ToList(); if (validPlayers.Count == 0) { - Warning($"No valid players for modifier {modifier.ModifierName}"); + if (MiraApiPlugin.IsDevBuild) + { + Warning($"No valid players for modifier {modifier.ModifierName}"); + } continue; } @@ -153,20 +155,28 @@ public static class ModifierManager } var candidates = availablePlayers - .Where(x => IsGameModifierValid(x, modifier, modifier.TypeId)) + .Where(x => IsGameModifierPostCheck(x, modifier, modifier.TypeId)) .ToList(); if (candidates.Count == 0) { - Warning( - $"No available players for modifier {modifier.ModifierName} at assignment {i + 1}"); + if (MiraApiPlugin.IsDevBuild) + { + Warning( + $"No available players for modifier {modifier.ModifierName} at assignment {i + 1}"); + } + break; } var plr = candidates.Random(); if (plr == null) { - Warning($"Valid player for modifier {modifier.ModifierName} disappeared"); + if (MiraApiPlugin.IsDevBuild) + { + Warning($"Valid player for modifier {modifier.ModifierName} disappeared"); + } + continue; } @@ -179,7 +189,12 @@ public static class ModifierManager private static bool IsGameModifierValid(PlayerControl player, GameModifier modifier, uint modifierId) { return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) && - modifier.IsModifierValidOn(player.Data.Role) && !player.HasModifier(modifierId) && + !player.HasModifier(modifierId) && modifier.IsModifierValidOn(player.Data.Role) && modifier.CanSpawnOnCurrentMode(); } + private static bool IsGameModifierPostCheck(PlayerControl player, GameModifier modifier, uint modifierId) + { + return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) && + !player.HasModifier(modifierId) && modifier.IsModifierValidOnPostCheck(player.Data.Role); + } } diff --git a/MiraAPI/Modifiers/Types/GameModifier.cs b/MiraAPI/Modifiers/Types/GameModifier.cs index b58f8e04..f5ae8831 100644 --- a/MiraAPI/Modifiers/Types/GameModifier.cs +++ b/MiraAPI/Modifiers/Types/GameModifier.cs @@ -33,6 +33,13 @@ public abstract class GameModifier : BaseModifier /// if the modifier is valid on the role, otherwise . public virtual bool IsModifierValidOn(RoleBehaviour role) => true; + /// + /// Determines whether the modifier is valid on a role, which runs after players are determined but before the modifiers are given. + /// + /// The role to be checked. + /// if the modifier is valid on the role, otherwise . + public virtual bool IsModifierValidOnPostCheck(RoleBehaviour role) => IsModifierValidOn(role); + /// /// Determines whether the player won the game with this modifier. The modifier with the highest priority will override other modifiers. /// diff --git a/MiraAPI/Modifiers/Types/TimedModifier.cs b/MiraAPI/Modifiers/Types/TimedModifier.cs index 6fe1c1d8..472b3cfa 100644 --- a/MiraAPI/Modifiers/Types/TimedModifier.cs +++ b/MiraAPI/Modifiers/Types/TimedModifier.cs @@ -1,5 +1,4 @@ using System; -using Reactor.Utilities; using UnityEngine; namespace MiraAPI.Modifiers.Types; diff --git a/MiraAPI/Networking/CustomGameOverRpc.cs b/MiraAPI/Networking/CustomGameOverRpc.cs index f7df4a70..6475d6a4 100644 --- a/MiraAPI/Networking/CustomGameOverRpc.cs +++ b/MiraAPI/Networking/CustomGameOverRpc.cs @@ -4,7 +4,6 @@ using InnerNet; using MiraAPI.GameEnd; using Reactor.Networking.Attributes; using Reactor.Networking.Rpc; -using Reactor.Utilities; namespace MiraAPI.Networking; diff --git a/MiraAPI/Networking/CustomMurderRpc.cs b/MiraAPI/Networking/CustomMurderRpc.cs index 9a6ff0ba..06224920 100644 --- a/MiraAPI/Networking/CustomMurderRpc.cs +++ b/MiraAPI/Networking/CustomMurderRpc.cs @@ -10,9 +10,6 @@ using MiraAPI.Events.Vanilla.Gameplay; using MiraAPI.Utilities; using Reactor.Networking.Attributes; using Reactor.Networking.Rpc; -using Reactor.Utilities; -using Reactor.Utilities.Extensions; -using Rewired; using UnityEngine; using Object = UnityEngine.Object; @@ -87,13 +84,15 @@ public static class CustomMurderRpc var beforeMurderEvent = new BeforeMurderEvent(source, target, inMeeting); MiraEventManager.InvokeEvent(beforeMurderEvent); + var defenseFlag = beforeMurderEvent.IgnoreDefense; + var indirectFlag = beforeMurderEvent.IsIndirectAttack; var isMeetingActive = MeetingHud.Instance != null || ExileController.Instance != null; if ((inMeeting is MeetingCheck.ForMeeting && !isMeetingActive) || (inMeeting is MeetingCheck.OutsideMeeting && isMeetingActive)) { beforeMurderEvent.Cancel(); } - if (target.ProtectedByGa()) + if (!defenseFlag && target.ProtectedByGa()) { beforeMurderEvent.Cancel(); murderResultFlags = MurderResultFlags.FailedProtected; @@ -117,6 +116,8 @@ public static class CustomMurderRpc PlayerControl.LocalPlayer, source, target, + indirectFlag, + defenseFlag, murderResultFlags, resetKillTimer, createDeadBody, @@ -131,6 +132,8 @@ public static class CustomMurderRpc /// The game host. /// The killer. /// The player to murder. + /// Should the kill be indirect. + /// Should the kill ignore defense. /// End result for the murder. /// Should the kill timer be reset. /// Should a dead body be created. @@ -142,6 +145,149 @@ public static class CustomMurderRpc this PlayerControl host, PlayerControl source, PlayerControl target, + bool isIndirect, + bool ignoreDefense, + MurderResultFlags murderResultFlags, + bool resetKillTimer = true, + bool createDeadBody = true, + bool teleportMurderer = true, + bool showKillAnim = true, + bool playKillSound = true) + { + if (LobbyBehaviour.Instance || !host.IsHost() || target.Data.IsDead || target.Data.Disconnected) + { + source.isKilling = false; + return; + } + + var murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags; + + if (isIndirect || ignoreDefense) + { + source.CustomMurder( + target, + null, + isIndirect, + ignoreDefense, + murderResultFlags2, + resetKillTimer, + createDeadBody, + teleportMurderer, + showKillAnim, + playKillSound); + } + else + { + source.CustomMurder( + target, + murderResultFlags2, + resetKillTimer, + createDeadBody, + teleportMurderer, + showKillAnim, + playKillSound); + } + } + + /// + /// Networked Custom Murder method, which checks for meetings as well, ran by clients for the host to confirm kills. + /// + /// The killer. + /// The player to murder. + /// Whether the murder is intended to be triggered in a meeting. + /// Should the kill be indirect. + /// Should the kill ignore defense. + /// Whether the murder was successful or not. + /// Should the kill timer be reset. + /// Should a dead body be created. + /// Should the killer be snapped to the dead player. + /// Should the kill animation be shown. + /// Should the kill sound be played. + [MethodRpc((uint)MiraRpc.AdvancedCustomMurder, LocalHandling = RpcLocalHandling.Before)] + public static void RpcAdvancedCustomMurder( + this PlayerControl source, + PlayerControl target, + MeetingCheck inMeeting, + bool isIndirect, + bool ignoreDefense = false, + bool didSucceed = true, + bool resetKillTimer = true, + bool createDeadBody = true, + bool teleportMurderer = true, + bool showKillAnim = true, + bool playKillSound = true) + { + if (LobbyBehaviour.Instance) + { + return; + } + var murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError; + + var beforeMurderEvent = new BeforeMurderEvent(source, target, isIndirect, ignoreDefense, inMeeting); + MiraEventManager.InvokeEvent(beforeMurderEvent); + var defenseFlag = beforeMurderEvent.IgnoreDefense; + var indirectFlag = beforeMurderEvent.IsIndirectAttack; + var isMeetingActive = MeetingHud.Instance != null || ExileController.Instance != null; + if ((inMeeting is MeetingCheck.ForMeeting && !isMeetingActive) || (inMeeting is MeetingCheck.OutsideMeeting && isMeetingActive)) + { + beforeMurderEvent.Cancel(); + } + + if (!defenseFlag && target.ProtectedByGa()) + { + beforeMurderEvent.Cancel(); + murderResultFlags = MurderResultFlags.FailedProtected; + } + else if (beforeMurderEvent.IsCancelled) + { + murderResultFlags = MurderResultFlags.FailedError; + } + + if (beforeMurderEvent.IsCancelled && source.AmOwner) + { + source.isKilling = true; + } + + if (!PlayerControl.LocalPlayer.IsHost()) + { + return; + } + + RpcConfirmAdvancedCustomMurder( + PlayerControl.LocalPlayer, + source, + target, + indirectFlag, + defenseFlag, + murderResultFlags, + resetKillTimer, + createDeadBody, + teleportMurderer, + showKillAnim, + playKillSound); + } + + /// + /// Networked Custom Murder method, which checks for meetings as well, ran by clients for the host to confirm kills. + /// + /// The game host. + /// The killer. + /// The player to murder. + /// Should the kill be indirect. + /// Should the kill ignore defense. + /// End result for the murder. + /// Should the kill timer be reset. + /// Should a dead body be created. + /// Should the killer be snapped to the dead player. + /// Should the kill animation be shown. + /// Should the kill sound be played. + [MethodRpc((uint)MiraRpc.ConfirmAdvancedCustomMurder, LocalHandling = RpcLocalHandling.After)] + public static void RpcConfirmAdvancedCustomMurder( + this PlayerControl host, + PlayerControl source, + PlayerControl target, + bool isIndirect, + bool ignoreDefense, MurderResultFlags murderResultFlags, bool resetKillTimer = true, bool createDeadBody = true, @@ -159,6 +305,9 @@ public static class CustomMurderRpc source.CustomMurder( target, + null, + isIndirect, + ignoreDefense, murderResultFlags2, resetKillTimer, createDeadBody, @@ -167,6 +316,274 @@ public static class CustomMurderRpc playKillSound); } + /// + /// Networked Custom Murder method, which checks for meetings as well, ran by clients for the host to confirm kills. + /// + /// The killer. + /// The player to murder. + /// The player to frame. + /// Whether the murder is intended to be triggered in a meeting. + /// Should the kill be indirect. + /// Should the kill ignore defense. + /// Whether the murder was successful or not. + /// Should the kill timer be reset. + /// Should a dead body be created. + /// Should the framed player be snapped to the dead player. + /// Should the kill animation be shown. + /// Should the kill sound be played. + [MethodRpc((uint)MiraRpc.FramedCustomMurder, LocalHandling = RpcLocalHandling.Before)] + public static void RpcFramedCustomMurder( + this PlayerControl source, + PlayerControl target, + PlayerControl framed, + MeetingCheck inMeeting, + bool isIndirect = true, + bool ignoreDefense = false, + bool didSucceed = true, + bool resetKillTimer = true, + bool createDeadBody = true, + bool teleportMurderer = true, + bool showKillAnim = true, + bool playKillSound = true) + { + if (LobbyBehaviour.Instance) + { + return; + } + var murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError; + + var beforeMurderEvent = new BeforeMurderEvent(source, target, framed, isIndirect, ignoreDefense, inMeeting); + MiraEventManager.InvokeEvent(beforeMurderEvent); + var defenseFlag = beforeMurderEvent.IgnoreDefense; + var indirectFlag = beforeMurderEvent.IsIndirectAttack; + var isMeetingActive = MeetingHud.Instance != null || ExileController.Instance != null; + if ((inMeeting is MeetingCheck.ForMeeting && !isMeetingActive) || (inMeeting is MeetingCheck.OutsideMeeting && isMeetingActive)) + { + beforeMurderEvent.Cancel(); + } + + if (!defenseFlag && target.ProtectedByGa()) + { + beforeMurderEvent.Cancel(); + murderResultFlags = MurderResultFlags.FailedProtected; + } + else if (beforeMurderEvent.IsCancelled) + { + murderResultFlags = MurderResultFlags.FailedError; + } + + if (beforeMurderEvent.IsCancelled && source.AmOwner) + { + source.isKilling = true; + } + + if (!PlayerControl.LocalPlayer.IsHost()) + { + return; + } + + RpcConfirmFramedCustomMurder( + PlayerControl.LocalPlayer, + source, + target, + framed, + indirectFlag, + defenseFlag, + murderResultFlags, + resetKillTimer, + createDeadBody, + teleportMurderer, + showKillAnim, + playKillSound); + } + + /// + /// Networked Custom Murder method, which checks for meetings as well, ran by clients for the host to confirm kills. + /// + /// The game host. + /// The killer. + /// The player to murder. + /// The player to frame. + /// Should the kill be indirect. + /// Should the kill ignore defense. + /// End result for the murder. + /// Should the kill timer be reset. + /// Should a dead body be created. + /// Should the framed player be snapped to the dead player. + /// Should the kill animation be shown. + /// Should the kill sound be played. + [MethodRpc((uint)MiraRpc.ConfirmFramedCustomMurder, LocalHandling = RpcLocalHandling.After)] + public static void RpcConfirmFramedCustomMurder( + this PlayerControl host, + PlayerControl source, + PlayerControl target, + PlayerControl framed, + bool isIndirect, + bool ignoreDefense, + MurderResultFlags murderResultFlags, + bool resetKillTimer = true, + bool createDeadBody = true, + bool teleportMurderer = true, + bool showKillAnim = true, + bool playKillSound = true) + { + if (LobbyBehaviour.Instance || !host.IsHost() || target.Data.IsDead || target.Data.Disconnected) + { + source.isKilling = false; + return; + } + + var murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags; + + source.CustomMurder( + target, + framed, + isIndirect, + ignoreDefense, + murderResultFlags2, + resetKillTimer, + createDeadBody, + teleportMurderer, + showKillAnim, + playKillSound); + } + + /// + /// Custom Murder method without networking. If you need a networked version, use or . + /// + /// The killer. + /// The player to murder. + /// The player to frame, if any. + /// Should the kill be indirect. + /// Should the kill ignore defense. + /// Murder result flags. + /// Should the kill timer be reset. + /// Should a dead body be created. + /// Should the killer be snapped to the dead player. + /// Should the kill animation be shown. + /// Should the kill sound be played. + public static void CustomMurder( + this PlayerControl source, + PlayerControl target, + PlayerControl? framed, + bool isIndirect, + bool ignoreDefense, + MurderResultFlags resultFlags, + bool resetKillTimer = true, + bool createDeadBody = true, + bool teleportMurderer = true, + bool showKillAnim = true, + bool playKillSound = true) + { + source.isKilling = false; + if (resultFlags.HasFlag(MurderResultFlags.FailedError) || LobbyBehaviour.Instance) + { + return; + } + + if (!ignoreDefense && (resultFlags.HasFlag(MurderResultFlags.FailedProtected) || + (resultFlags.HasFlag(MurderResultFlags.DecisionByHost) && + target.protectedByGuardianId > -1))) + { + target.protectedByGuardianThisRound = true; + var flag = PlayerControl.LocalPlayer.Data.Role.Role == RoleTypes.GuardianAngel; + if (flag && PlayerControl.LocalPlayer.Data.PlayerId == target.protectedByGuardianId) + { + DataManager.Player.Stats.IncrementStat(StatID.Role_GuardianAngel_CrewmatesProtected); + AchievementManager.Instance.OnProtectACrewmate(); + } + + if (source.AmOwner || flag) + { + target.ShowFailedMurder(); + + if (resetKillTimer) + { + source.SetKillTimer( + GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown) / 2f); + } + } + else + { + target.RemoveProtection(); + } + + return; + } + + if (!resultFlags.HasFlag(MurderResultFlags.Succeeded) && + !resultFlags.HasFlag(MurderResultFlags.DecisionByHost)) + { + return; + } + + DebugAnalytics.Instance.Analytics.Kill(target.Data, source.Data); + if (source.AmOwner) + { + DataManager.Player.Stats.IncrementStat( + GameManager.Instance.IsHideAndSeek() + ? StatID.HideAndSeek_ImpostorKills + : StatID.ImpostorKills); + + if (source.CurrentOutfitType == PlayerOutfitType.Shapeshifted) + { + DataManager.Player.Stats.IncrementStat(StatID.Role_Shapeshifter_ShiftedKills); + } + + if (Constants.ShouldPlaySfx() && playKillSound) + { + SoundManager.Instance.PlaySound(source.KillSfx, false, 0.8f); + } + + if (resetKillTimer) + { + source.SetKillTimer(GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown)); + } + } + + UnityTelemetry.Instance.WriteMurder(); + target.gameObject.layer = LayerMask.NameToLayer("Ghost"); + if (target.AmOwner) + { + DataManager.Player.Stats.IncrementStat(StatID.TimesMurdered); + if (Minigame.Instance) + { + try + { + Minigame.Instance.Close(); + Minigame.Instance.Close(); + } + catch + { + // ignored + } + } + + if (showKillAnim) + { + try + { + HudManager.Instance.KillOverlay.ShowKillAnimation(source.Data, target.Data); + } + catch (Exception e) + { + Error($"Error with kill animation: {e.ToString()}"); + } + } + + target.cosmetics.SetNameMask(false); + target.RpcSetScanner(false); + } + + AchievementManager.Instance.OnMurder( + source.AmOwner, + target.AmOwner, + source.CurrentOutfitType == PlayerOutfitType.Shapeshifted, + source.shapeshiftTargetPlayerId, + target.PlayerId); + source.MyPhysics.StartCoroutine(source.KillAnimations.RandomSnapshot().CoPerformCustomKill(source, target, framed, isIndirect, ignoreDefense, createDeadBody, teleportMurderer)); + } + /// /// Custom Murder method without networking. If you need a networked version, use . /// @@ -293,7 +710,7 @@ public static class CustomMurderRpc source.CurrentOutfitType == PlayerOutfitType.Shapeshifted, source.shapeshiftTargetPlayerId, target.PlayerId); - source.MyPhysics.StartCoroutine(source.KillAnimations.RandomSnapshot().CoPerformCustomKill(source, target, createDeadBody, teleportMurderer)); + source.MyPhysics.StartCoroutine(source.KillAnimations.RandomSnapshot().CoPerformCustomKill(source, target, null, false, false, createDeadBody, teleportMurderer)); } /// @@ -302,6 +719,9 @@ public static class CustomMurderRpc /// The kill animation. /// The murderer. /// The murdered player. + /// The framed player, if any. + /// Was the kill indirect. + /// Would the kill ignore defense. /// Should a dead body be created. /// Should the murder be teleported. /// Coroutine. @@ -309,6 +729,9 @@ public static class CustomMurderRpc this KillAnimation anim, PlayerControl source, PlayerControl target, + PlayerControl? framed, + bool isIndirect, + bool ignoreDefense, bool createDeadBody = true, bool teleportMurderer = true) { @@ -317,12 +740,13 @@ public static class CustomMurderRpc yield break; } var cam = Camera.main?.GetComponent(); - var isParticipant = source.AmOwner || target.AmOwner; - var sourcePhys = source.MyPhysics; + var playerToLunge = framed ?? source; + var isParticipant = playerToLunge.AmOwner || target.AmOwner; + var sourcePhys = playerToLunge.MyPhysics; if (teleportMurderer) { - KillAnimation.SetMovement(source, false); + KillAnimation.SetMovement(playerToLunge, false); } KillAnimation.SetMovement(target, false); @@ -376,7 +800,7 @@ public static class CustomMurderRpc yield return source.MyPhysics.Animations.CoPlayCustomAnimation(anim.BlurAnim); sourcePhys.Animations.PlayIdleAnimation(); source.NetTransform.SnapTo(target.transform.position); - KillAnimation.SetMovement(source, true); + KillAnimation.SetMovement(playerToLunge, true); } KillAnimation.SetMovement(target, true); @@ -386,7 +810,7 @@ public static class CustomMurderRpc deadBody.enabled = true; } - var afterMurderEvent = new AfterMurderEvent(source, target, deadBody); + var afterMurderEvent = framed != null ? new AfterMurderEvent(source, target, framed, deadBody, isIndirect, ignoreDefense) : new AfterMurderEvent(source, target, deadBody, isIndirect, ignoreDefense); MiraEventManager.InvokeEvent(afterMurderEvent); if (!isParticipant) diff --git a/MiraAPI/Networking/MiraRpc.cs b/MiraAPI/Networking/MiraRpc.cs index edc3a94e..25312e0c 100644 --- a/MiraAPI/Networking/MiraRpc.cs +++ b/MiraAPI/Networking/MiraRpc.cs @@ -40,6 +40,26 @@ public enum MiraRpc : uint /// ConfirmCustomMurder, + /// + /// Advanced Custom Murder RPC. + /// + AdvancedCustomMurder, + + /// + /// Advanced Custom Murder RPC Confirmation. + /// + ConfirmAdvancedCustomMurder, + + /// + /// Framed Custom Murder RPC. + /// + FramedCustomMurder, + + /// + /// Framed Custom Murder RPC Confirmation. + /// + ConfirmFramedCustomMurder, + /// /// Custom Game Over RPC. /// diff --git a/MiraAPI/Patches/Events/ReportBodyEventPatch.cs b/MiraAPI/Patches/Events/ReportBodyEventPatch.cs index 305311ef..3eb1c291 100644 --- a/MiraAPI/Patches/Events/ReportBodyEventPatch.cs +++ b/MiraAPI/Patches/Events/ReportBodyEventPatch.cs @@ -4,7 +4,6 @@ using InnerNet; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Meeting; using MiraAPI.Utilities; -using Reactor.Utilities; namespace MiraAPI.Patches.Events; diff --git a/MiraAPI/Patches/Events/VanillaButtonPatches.cs b/MiraAPI/Patches/Events/VanillaButtonPatches.cs index 490e401f..71df73f4 100644 --- a/MiraAPI/Patches/Events/VanillaButtonPatches.cs +++ b/MiraAPI/Patches/Events/VanillaButtonPatches.cs @@ -1,9 +1,7 @@ -using System.Linq; -using AmongUs.GameOptions; +using AmongUs.GameOptions; using HarmonyLib; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; -using MiraAPI.Events.Vanilla.Usables; namespace MiraAPI.Patches.Events; diff --git a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs index fa91e7b2..ad0cabcc 100644 --- a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs +++ b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs @@ -254,12 +254,6 @@ public static class TaskAdderPatches .ToArray() .Any(x => x.FolderName == child.FolderName); - [HarmonyPostfix] - [HarmonyPatch(nameof(TaskAdderGame.OnDisable))] - public static void ClosePatch() - { - Utilities.Extensions.ClearGarbageCollector(); - } // yes it might be crazy patching the entire method, but i tried so many other methods and only this works :cry: // true -chip [HarmonyPrefix] diff --git a/MiraAPI/Patches/Hud/ButtonResetPatches.cs b/MiraAPI/Patches/Hud/ButtonResetPatches.cs index 5e460488..ab0dc320 100644 --- a/MiraAPI/Patches/Hud/ButtonResetPatches.cs +++ b/MiraAPI/Patches/Hud/ButtonResetPatches.cs @@ -1,6 +1,5 @@ using HarmonyLib; using MiraAPI.Hud; -using Reactor.Utilities; namespace MiraAPI.Patches.Hud; diff --git a/MiraAPI/Patches/HudManagerPatches.cs b/MiraAPI/Patches/HudManagerPatches.cs index a6cd68b1..76adc2ca 100644 --- a/MiraAPI/Patches/HudManagerPatches.cs +++ b/MiraAPI/Patches/HudManagerPatches.cs @@ -148,9 +148,8 @@ public static class HudManagerPatches foreach (var t in BottomLeft.GetComponentsInChildren(true)) { - t.gameObject.DeepDestroy(false); + t.gameObject.Destroy(); } - Utilities.Extensions.ClearGarbageCollector(); var gridArrange = BottomLeft.GetComponent(); var aspectPosition = BottomLeft.GetComponent(); diff --git a/MiraAPI/Patches/IntroCutscenePatches.cs b/MiraAPI/Patches/IntroCutscenePatches.cs index 7d52287e..4ee990c4 100644 --- a/MiraAPI/Patches/IntroCutscenePatches.cs +++ b/MiraAPI/Patches/IntroCutscenePatches.cs @@ -1,12 +1,10 @@ -using System.Linq; -using System.Reflection; +using System.Reflection; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; using MiraAPI.Roles; using MiraAPI.Utilities; -using Reactor.Utilities; namespace MiraAPI.Patches; diff --git a/MiraAPI/Patches/Keybinds/ControlMapperPatches.cs b/MiraAPI/Patches/Keybinds/ControlMapperPatches.cs index f0c51c02..6666a2c4 100644 --- a/MiraAPI/Patches/Keybinds/ControlMapperPatches.cs +++ b/MiraAPI/Patches/Keybinds/ControlMapperPatches.cs @@ -1,13 +1,8 @@ using System.Linq; using HarmonyLib; using MiraAPI.Keybinds; -using MiraAPI.LocalSettings; -using Reactor.Utilities; -using Reactor.Utilities.Extensions; using Rewired.UI.ControlMapper; -using TMPro; using UnityEngine; -using UnityEngine.Events; using UnityEngine.UI; namespace MiraAPI.Patches.Keybinds; diff --git a/MiraAPI/Patches/Keybinds/KeybindsPatch.cs b/MiraAPI/Patches/Keybinds/KeybindsPatch.cs index c08a47f6..9c1034fb 100644 --- a/MiraAPI/Patches/Keybinds/KeybindsPatch.cs +++ b/MiraAPI/Patches/Keybinds/KeybindsPatch.cs @@ -1,6 +1,5 @@ using HarmonyLib; using MiraAPI.Keybinds; -using MonoMod.Utils; using Rewired; namespace MiraAPI.Patches.Keybinds; diff --git a/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs b/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs index ab8ed2c3..a8cab15f 100644 --- a/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs +++ b/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs @@ -3,7 +3,6 @@ using System.Linq; using HarmonyLib; using MiraAPI.LocalSettings; using MiraAPI.Utilities.Assets; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using UnityEngine; using UnityEngine.Events; @@ -70,7 +69,7 @@ public static class OptionsMenuPatches int i = 0; int tabIdx = 0; int page = 1; - foreach (var settings in LocalSettingsManager.Tabs) + foreach (var settings in LocalSettingsManager.AvailableTabs) { var tab = settings.CreateTab(__instance); if (settings.ShouldCreateButton) @@ -165,12 +164,12 @@ public static class OptionsMenuPatches public static bool OpenTabGroupPrefix(OptionsMenuBehaviour __instance, ref int index) { __instance.Tabs.ToList().ForEach(x => x.Close()); // Close all vanilla tabs - LocalSettingsManager.Tabs.ForEach(CustomClose); // Close all mod tabs + LocalSettingsManager.AvailableTabs.ForEach(CustomClose); // Close all mod tabs // Tabs with index 10 and above are the mod settings tabs if (index >= 10) { - CustomOpen(LocalSettingsManager.Tabs[index - 10]); + CustomOpen(LocalSettingsManager.AvailableTabs[index - 10]); return false; } @@ -184,7 +183,7 @@ public static class OptionsMenuPatches [HarmonyPatch(nameof(OptionsMenuBehaviour.Open))] public static void OpenPostfix(OptionsMenuBehaviour __instance) { - LocalSettingsManager.Tabs.ForEach(CustomClose); + LocalSettingsManager.AvailableTabs.ForEach(CustomClose); currentPage = 1; UpdatePages(); } diff --git a/MiraAPI/Patches/Menu/HatsTabPatches.cs b/MiraAPI/Patches/Menu/HatsTabPatches.cs index b06b11ad..43d96c33 100644 --- a/MiraAPI/Patches/Menu/HatsTabPatches.cs +++ b/MiraAPI/Patches/Menu/HatsTabPatches.cs @@ -100,7 +100,7 @@ public static class HatsTabPatches if (loadRoutine != null) Coroutines.Stop(loadRoutine); hatIndex = 0; - foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); + foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy(); __instance.ColorChips.Clear(); __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); diff --git a/MiraAPI/Patches/Menu/NameplatesTabPatches.cs b/MiraAPI/Patches/Menu/NameplatesTabPatches.cs index 4e9b6930..6e96d5fb 100644 --- a/MiraAPI/Patches/Menu/NameplatesTabPatches.cs +++ b/MiraAPI/Patches/Menu/NameplatesTabPatches.cs @@ -81,7 +81,7 @@ public static class NameplatesTabPatches private static void GenerateHats(NameplatesTab __instance, int page) { - foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); + foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy(); __instance.ColorChips.Clear(); __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); diff --git a/MiraAPI/Patches/Menu/SkinsTabPatches.cs b/MiraAPI/Patches/Menu/SkinsTabPatches.cs index 44d08d58..e1c97a54 100644 --- a/MiraAPI/Patches/Menu/SkinsTabPatches.cs +++ b/MiraAPI/Patches/Menu/SkinsTabPatches.cs @@ -83,7 +83,7 @@ public static class SkinsTabPatches private static void GenerateHats(SkinsTab __instance, int page) { - foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); + foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy(); __instance.ColorChips.Clear(); __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); diff --git a/MiraAPI/Patches/Menu/VisorsTabPatches.cs b/MiraAPI/Patches/Menu/VisorsTabPatches.cs index c5fe9a4e..a1627cfc 100644 --- a/MiraAPI/Patches/Menu/VisorsTabPatches.cs +++ b/MiraAPI/Patches/Menu/VisorsTabPatches.cs @@ -80,7 +80,7 @@ public static class VisorsTabPatches private static void GenerateHats(VisorsTab __instance, int page) { - foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); + foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy(); __instance.ColorChips.Clear(); __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index cbc11aad..2bc615d9 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -1,8 +1,8 @@ using System; +using System.Collections; using System.Linq; -using AmongUs.GameOptions; +using BepInEx.Unity.IL2CPP.Utils.Collections; using HarmonyLib; -using Il2CppInterop.Runtime.Attributes; using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.Presets; @@ -21,6 +21,54 @@ namespace MiraAPI.Patches.Options; [HarmonyPatch(typeof(GameOptionsMenu))] internal static class GameOptionsMenuPatch { + [HarmonyPrefix] + [HarmonyPatch(nameof(GameOptionsMenu.Initialize))] + // ReSharper disable once InconsistentNaming + public static bool InitPatch(GameOptionsMenu __instance) + { + __instance.Children ??= new Il2CppSystem.Collections.Generic.List(); + __instance.Children.Clear(); + + if (MenuState.Instance.CurrentModIdx == 0) + { + __instance.MapPicker.gameObject.SetActive(true); + __instance.cachedData = GameOptionsManager.Instance.CurrentGameOptions; + var container = MenuState.Instance.CurrentContainer.transform; + + if (container.childCount == 1) + { + __instance.MapPicker.Initialize(20); + var mapNameSetting = GameManager.Instance.GameSettingsList.MapNameSetting; + __instance.MapPicker.SetUpFromData(mapNameSetting, 20); + __instance.Children.Add(__instance.MapPicker); + __instance.CreateSettings(); + foreach (var optionBehaviour in __instance.Children) + { + if (AmongUsClient.Instance && !AmongUsClient.Instance.AmHost) + { + optionBehaviour.SetAsPlayer(); + } + + optionBehaviour.OnValueChanged = new Action(__instance.ValueChanged); + } + } + else + { + foreach (var child in container.GetComponentsInChildren(true)) + { + __instance.Children.Add(child); + } + } + } + else + { + __instance.MapPicker.gameObject.SetActive(false); + CustomCreateSettings(__instance); + } + + return false; + } + [HarmonyPostfix] [HarmonyPatch(nameof(GameOptionsMenu.OnEnable))] public static void OpenPatch(GameOptionsMenu __instance) @@ -35,55 +83,49 @@ internal static class GameOptionsMenuPatch HudManager.Instance.PlayerCam.OverrideScreenShakeEnabled = true; } - [HarmonyPostfix] - [HarmonyPatch(nameof(GameOptionsMenu.CloseMenu))] - public static void FullMenuClosePatch(GameOptionsMenu __instance) - { - Utilities.Extensions.ClearGarbageCollector(); - } - [HarmonyPostfix] [HarmonyPatch(nameof(GameOptionsMenu.Update))] // ReSharper disable once InconsistentNaming public static void UpdatePatch(GameOptionsMenu __instance) { - if (GameSettingMenuPatches.SelectedModIdx == 0) + if (MenuState.Instance.CurrentModIdx == 0) { return; } var num = 2.1f; - if (__instance.name == "MODIFIERS TAB") - { - ModifiersUpdate(ref num); - } - else if (__instance.name == "CUSTOM TAB 1") - { - CustomMenuOneUpdate(ref num); - } - else if (__instance.name == "CUSTOM TAB 2") - { - CustomMenuTwoUpdate(ref num); - } - else - { - var filteredGroups = - GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.OptionableType == null && - x.ParentMenu == MenuCategory.Game) ?? []; - foreach (var group in filteredGroups) - { - UpdateGroup(group, ref num); - } + switch (MenuState.Instance.CurrentMenu) + { + case MenuCategory.Modifiers: + ModifiersUpdate(ref num); + break; + case MenuCategory.CustomOne: + CustomMenuOneUpdate(ref num); + break; + case MenuCategory.CustomTwo: + CustomMenuTwoUpdate(ref num); + break; + default: + var filteredGroups = + MenuState.Instance.CurrentMod.InternalOptionGroups + .Where(x => x.OptionableType == null && + x.ParentMenu == MenuCategory.Game) ?? []; + + foreach (var group in filteredGroups) + { + UpdateGroup(group, ref num); + } + + break; } __instance.scrollBar.SetYBoundsMax(-num - 1.65f); } - private static void UpdateGroup(AbstractOptionGroup group, ref float num) + private static void UpdateGroup(AbstractOptionGroup? group, ref float num) { - if (group.Options.Count == 0 || group.Header is null) + if (group is null || !group.Ready || group.Options.Count == 0 || group.Header is null) { return; } @@ -109,7 +151,7 @@ internal static class GameOptionsMenuPatch { var newOpt = opt.OptionBehaviour; - if (newOpt is null) + if (!newOpt || newOpt == null) { continue; } @@ -135,8 +177,8 @@ internal static class GameOptionsMenuPatch private static void CustomMenuOneUpdate(ref float num) { - var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.ParentMenu == MenuCategory.CustomOne) ?? []; + var groups = MenuState.Instance.CurrentMod.InternalOptionGroups + .Where(x => x.ParentMenu == MenuCategory.CustomOne); foreach (var modGroup in groups) { @@ -144,20 +186,10 @@ internal static class GameOptionsMenuPatch } } - private static void CustomMenuOneCreate(GameOptionsMenu menu) - { - var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.ParentMenu == MenuCategory.CustomOne) ?? []; - foreach (var group in groups) - { - CreateGroup(menu, group); - } - } - private static void CustomMenuTwoUpdate(ref float num) { - var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.ParentMenu == MenuCategory.CustomTwo) ?? []; + var groups = MenuState.Instance.CurrentMod.InternalOptionGroups + .Where(x => x.ParentMenu == MenuCategory.CustomTwo); foreach (var modGroup in groups) { @@ -165,20 +197,11 @@ internal static class GameOptionsMenuPatch } } - private static void CustomMenuTwoCreate(GameOptionsMenu menu) - { - var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.ParentMenu == MenuCategory.CustomTwo) ?? []; - foreach (var group in groups) - { - CreateGroup(menu, group); - } - } - private static void ModifiersUpdate(ref float num) { - var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.ParentMenu is MenuCategory.Modifiers || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; + var groups = MenuState.Instance.CurrentMod.InternalOptionGroups + .Where(x => x.ParentMenu is MenuCategory.Modifiers || + x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true); foreach (var modGroup in groups) { @@ -186,139 +209,114 @@ internal static class GameOptionsMenuPatch } } - private static void ModifiersCreate(GameOptionsMenu menu) + private static Coroutine? _creationCoroutine; + + public static void CustomCreateSettings(GameOptionsMenu menu) { - var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.ParentMenu is MenuCategory.Modifiers || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; - foreach (var group in groups) - { - CreateGroup(menu, group); - } - } + menu.MapPicker.gameObject.SetActive(false); + var mod = MenuState.Instance.CurrentMod; - [HarmonyPrefix] - [HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))] - // ReSharper disable once InconsistentNaming - public static bool SettingsPatch(GameOptionsMenu __instance) - { - if (GameSettingMenuPatches.SelectedModIdx == 0) + var filteredGroups = MenuState.Instance.CurrentMenu switch { - return true; + MenuCategory.Game => mod.InternalOptionGroups.Where(x => + x.OptionableType == null && x.ParentMenu == MenuCategory.Game), + MenuCategory.Modifiers => mod.InternalOptionGroups.Where(x => + x.ParentMenu is MenuCategory.Modifiers || + x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true), + MenuCategory.CustomOne => mod.InternalOptionGroups.Where(x => x.ParentMenu == MenuCategory.CustomOne), + MenuCategory.CustomTwo => mod.InternalOptionGroups.Where(x => x.ParentMenu == MenuCategory.CustomTwo), + _ => [], + }; + + if (_creationCoroutine != null) + { + GameSettingMenu.Instance.StopCoroutine(_creationCoroutine); } - __instance.MapPicker.gameObject.SetActive(false); + _creationCoroutine = GameSettingMenu.Instance.StartCoroutine(CreateGroups().WrapToIl2Cpp()); - if (__instance.name == "MODIFIERS TAB") + IEnumerator CreateGroups() { - ModifiersCreate(__instance); - return false; - } - else if (__instance.name == "CUSTOM TAB 1") - { - CustomMenuOneCreate(__instance); - return false; - } - else if (__instance.name == "CUSTOM TAB 2") - { - CustomMenuTwoCreate(__instance); - return false; - } - - var filteredGroups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.OptionableType == null && x.ParentMenu == MenuCategory.Game) ?? []; - - foreach (var group in filteredGroups) - { - CreateGroup(__instance, group); - } - - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(nameof(GameOptionsMenu.Initialize))] - // ReSharper disable once InconsistentNaming - public static bool InitPatch(GameOptionsMenu __instance) - { - if (__instance.Children != null && __instance.Children.Count != 0) - { - return false; - } - - __instance.MapPicker.gameObject.SetActive(true); - __instance.MapPicker.Initialize(20); - var mapNameSetting = GameManager.Instance.GameSettingsList.MapNameSetting; - __instance.MapPicker.SetUpFromData(mapNameSetting, 20); - __instance.Children = new Il2CppSystem.Collections.Generic.List(); - __instance.Children.Add(__instance.MapPicker); - __instance.CreateSettings(); - __instance.cachedData = GameOptionsManager.Instance.CurrentGameOptions; - foreach (var optionBehaviour in __instance.Children) - { - if (AmongUsClient.Instance && !AmongUsClient.Instance.AmHost) + foreach (var group in filteredGroups) { - optionBehaviour.SetAsPlayer(); + yield return CoCreateGroup(menu, group); } - - if (optionBehaviour.IsCustom()) - { - continue; - } - - optionBehaviour.OnValueChanged = new Action(__instance.ValueChanged); } - - return false; } - private static void CreateGroup(GameOptionsMenu menu, AbstractOptionGroup group) + private static IEnumerator CoCreateGroup(GameOptionsMenu menu, AbstractOptionGroup group) { - var categoryHeaderMasked = Object.Instantiate( - menu.categoryHeaderOrigin, - Vector3.zero, - Quaternion.identity, - menu.settingsContainer); + var container = MenuState.Instance.CurrentContainer.transform; - categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20); - categoryHeaderMasked.Background.color = group.GroupColor; - categoryHeaderMasked.Divider.color = group.GroupColor; - categoryHeaderMasked.Title.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) - ? Color.white - : group.GroupColor.FindAlternateColor(); + group.Ready = false; + if (group.Header == null || !group.Header) + { + var categoryHeaderMasked = Object.Instantiate( + menu.categoryHeaderOrigin, + Vector3.zero, + Quaternion.identity, + container); - categoryHeaderMasked.Background.sprite = MiraAssets.CategoryHeader.LoadAsset(); - categoryHeaderMasked.Background.sprite.texture.filterMode = FilterMode.Bilinear; - categoryHeaderMasked.Background.sprite.texture.wrapMode = TextureWrapMode.Clamp; + categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20); + categoryHeaderMasked.Background.color = group.GroupColor; + categoryHeaderMasked.Divider.color = group.GroupColor; + categoryHeaderMasked.Title.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) + ? Color.white + : group.GroupColor.FindAlternateColor(); - categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.5f, -0.1833f, 0); - categoryHeaderMasked.Background.size = new Vector2( - categoryHeaderMasked.Background.size.x + 1.5f, - categoryHeaderMasked.Background.size.y); + categoryHeaderMasked.Background.sprite = MiraAssets.CategoryHeader.LoadAsset(); + categoryHeaderMasked.Background.sprite.texture.filterMode = FilterMode.Bilinear; + categoryHeaderMasked.Background.sprite.texture.wrapMode = TextureWrapMode.Clamp; - categoryHeaderMasked.gameObject.SetActive(false); - group.Header = categoryHeaderMasked; + categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.5f, -0.1833f, 0); + categoryHeaderMasked.Background.size = new Vector2( + categoryHeaderMasked.Background.size.x + 1.5f, + categoryHeaderMasked.Background.size.y); - 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(); + categoryHeaderMasked.gameObject.SetActive(false); - var options = group.Options.Select(opt => opt.CreateOption( + 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 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); + + group.Header = categoryHeaderMasked; + } + + var options = group.Options.Where(opt => opt.OptionBehaviour == null || !opt.OptionBehaviour + ).Select(opt => opt.CreateOption( menu.checkboxOrigin, menu.numberOptionOrigin, menu.stringOptionOrigin, menu.playerOptionOrigin, - menu.settingsContainer)); + container) + ); OptionPreset? defaultPreset = null; - if (GameSettingMenuPatches.SelectedMod != null && PresetManager.DefaultPresets.TryGetValue( - GameSettingMenuPatches.SelectedMod, - out var preset)) + if (PresetManager.DefaultPresets.TryGetValue(MenuState.Instance.CurrentMod, out var preset)) { defaultPreset = preset; } - foreach (var newOpt in options) + yield return options.CoLoopWithBudget(newOpt => { newOpt.SetClickMask(menu.ButtonClickMask); @@ -421,6 +419,7 @@ internal static class GameOptionsMenuPatch defaultPreset!.ResetOption(strOpt); })); } + if (!defaultPreset!.IsOptionInPreset(newOpt)) { resetBtn.Destroy(); @@ -428,24 +427,12 @@ internal static class GameOptionsMenuPatch 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)(() => + if (AmongUsClient.Instance && !AmongUsClient.Instance.AmHost) { - group.AllOptionsHidden = !group.AllOptionsHidden; - newText.text = group.AllOptionsHidden - ? "(Click to open)" - : "(Click to close)"; - })); - headerBtn.SetButtonEnableState(true); + newOpt.SetAsPlayer(); + } + }).WrapToIl2Cpp(); + group.Ready = true; } } diff --git a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs index a4b6cca6..bc6dc3ae 100644 --- a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs +++ b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs @@ -3,12 +3,12 @@ using System.IO; using System.Linq; using BepInEx.Configuration; using HarmonyLib; +using MiraAPI.GameOptions; using MiraAPI.PluginLoading; using MiraAPI.Presets; using MiraAPI.Roles; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; @@ -24,8 +24,6 @@ internal static class GamePresetsTabPatches private static GameObject _refreshButton = null!; private static GameObject _folderButton = null!; private static GameObject _newDivider = null!; - private static GameObject _presetHolder = null!; - private static GridArrange _arrange = null!; private static readonly object Lock = new(); @@ -42,10 +40,10 @@ internal static class GamePresetsTabPatches CreatePresetMenu(__instance); - __instance.StandardPresetButton.gameObject.SetActive(GameSettingMenuPatches.SelectedModIdx == 0); - __instance.SecondPresetButton.gameObject.SetActive(GameSettingMenuPatches.SelectedModIdx == 0); + __instance.StandardPresetButton.gameObject.SetActive(MenuState.Instance.CurrentModIdx == 0); + __instance.SecondPresetButton.gameObject.SetActive(MenuState.Instance.CurrentModIdx == 0); - if (GameSettingMenuPatches.SelectedModIdx != 0) + if (MenuState.Instance.CurrentModIdx != 0) { __instance.PresetDescriptionText.text = "Select or create an options preset for this mod"; } @@ -57,26 +55,41 @@ internal static class GamePresetsTabPatches // Show the save and refresh buttons if (_saveButton) { - _saveButton.SetActive(GameSettingMenuPatches.SelectedModIdx != 0); + _saveButton.SetActive(MenuState.Instance.CurrentModIdx != 0); } if (_refreshButton) { - _refreshButton.SetActive(GameSettingMenuPatches.SelectedModIdx != 0); + _refreshButton.SetActive(MenuState.Instance.CurrentModIdx != 0); } if (_folderButton) { - _folderButton.SetActive(GameSettingMenuPatches.SelectedModIdx != 0); + _folderButton.SetActive(MenuState.Instance.CurrentModIdx != 0); } if (_newDivider) { - _newDivider.SetActive(GameSettingMenuPatches.SelectedModIdx != 0); + _newDivider.SetActive(MenuState.Instance.CurrentModIdx != 0); + } + + if (MenuState.Instance.CurrentModIdx == 0) + { + return; } var prefab = GameSettingMenu.Instance.GameSettingsButton; - foreach (var preset in MiraPluginManager.Instance.RegisteredPlugins.SelectMany(x => x.InternalPresets)) + var presetHolder = MenuState.Instance.CurrentContainer; + var arrange = presetHolder.GetComponent(); + presetHolder.SetActive(true); + + foreach (var preset in MenuState.Instance.CurrentMod.InternalPresets) { - var button = Object.Instantiate(prefab, _presetHolder.transform); + if (preset.PresetButton != null && preset.PresetButton) + { + preset.PresetButton.SetActive(true); + continue; + } + + var button = Object.Instantiate(prefab, presetHolder.transform); button.buttonText.text = preset.Name; if (button.buttonText.TryGetComponent(out var tmp)) { @@ -91,23 +104,12 @@ internal static class GamePresetsTabPatches { preset.LoadPreset(); })); - button.gameObject.SetActive(false); + button.gameObject.SetActive(true); preset.PresetButton = button.gameObject; } - foreach (var mod in MiraPluginManager.Instance.RegisteredPlugins) - { - foreach (var button in mod.InternalPresets.Select(x => x.PresetButton)) - { - if (button != null) - { - button.SetActive(mod == GameSettingMenuPatches.SelectedMod); - } - } - } - - _arrange.Start(); - _arrange.ArrangeChilds(); + arrange.Start(); + arrange.ArrangeChilds(); } } @@ -117,8 +119,6 @@ internal static class GamePresetsTabPatches // ReSharper disable once InconsistentNaming public static void Postfix() { - Error("OnDisable called"); - if (_saveButton) { _saveButton.SetActive(false); @@ -198,7 +198,7 @@ internal static class GamePresetsTabPatches saveButton.OnClick.AddListener( (UnityAction)(() => { - if (GameSettingMenuPatches.SelectedModIdx == 0) + if (MenuState.Instance.CurrentModIdx == 0) { return; } @@ -206,7 +206,7 @@ internal static class GamePresetsTabPatches SavePresetPopup.CreatePopup( name => { - if (GameSettingMenuPatches.SelectedMod == null) + if (MenuState.Instance.CurrentModIdx == 0) { return; } @@ -219,19 +219,19 @@ internal static class GamePresetsTabPatches var presetFile = new ConfigFile( Path.Combine( PresetManager.PresetDirectory, - GameSettingMenuPatches.SelectedMod.PluginId, + MenuState.Instance.CurrentMod.PluginId, $"{name}.cfg"), false) { SaveOnConfigSet = false, }; - foreach (var option in GameSettingMenuPatches.SelectedMod.InternalOptions.Where(x => x.IncludeInPreset)) + foreach (var option in MenuState.Instance.CurrentMod.InternalOptions.Where(x => x.IncludeInPreset)) { option.SaveToPreset(presetFile); } - foreach (var role in GameSettingMenuPatches.SelectedMod.InternalRoles.Values.OfType().Where(x=>!x.Configuration.HideSettings)) + foreach (var role in MenuState.Instance.CurrentMod.InternalRoles.Values.OfType().Where(x=>!x.Configuration.HideSettings)) { role.SaveToPreset(presetFile); } @@ -262,7 +262,7 @@ internal static class GamePresetsTabPatches iconSpriteRend.sprite = MiraAssets.RefreshIcon.LoadAsset(); Helpers.DivideSize(refreshButton.gameObject, 2f); - refreshButton.buttonText.gameObject.DeepDestroy(); + refreshButton.buttonText.gameObject.Destroy(); refreshButton.OnClick = new Button.ButtonClickedEvent(); refreshButton.OnClick.AddListener((UnityAction)Refresh); @@ -286,7 +286,7 @@ internal static class GamePresetsTabPatches { var directory = Path.Combine( PresetManager.PresetDirectory, - GameSettingMenuPatches.SelectedMod?.PluginId ?? string.Empty); + MenuState.Instance.CurrentMod.PluginId); Directory.CreateDirectory(directory); Process.Start( new ProcessStartInfo @@ -302,31 +302,20 @@ internal static class GamePresetsTabPatches _refreshButton.SetActive(false); _folderButton.SetActive(false); _newDivider.SetActive(false); - - if (!_presetHolder) - { - _presetHolder = new GameObject("PresetHolder"); - _presetHolder.transform.SetParent(presetTab.transform, false); - _presetHolder.transform.localScale = new Vector3(0.9f, 0.9f, 1); - _presetHolder.transform.localPosition = new Vector3(-1.9f, 1.7f, 0); - _arrange = _presetHolder.AddComponent(); - _arrange.Alignment = GridArrange.StartAlign.Right; - _arrange.CellSize = new Vector2(2.15f, -.55f); - _arrange.MaxColumns = 2; - } } private static void Refresh() { - Error("Refreshing presets"); - if (GameSettingMenuPatches.SelectedMod == null) + Info("Refreshing presets"); + if (MenuState.Instance.CurrentModIdx == 0) { - Error("Selected mod is null, cannot refresh presets"); + Error("Current page is vanilla, cannot refresh presets"); return; } + lock (Lock) { - PresetManager.LoadPresets(GameSettingMenuPatches.SelectedMod); + PresetManager.LoadPresets(MenuState.Instance.CurrentMod); } GameSettingMenu.Instance.ChangeTab(0, false); // refresh the tab } diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index 6e12c616..c31b5190 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using HarmonyLib; using MiraAPI.GameOptions; using MiraAPI.Modifiers; @@ -19,661 +18,38 @@ namespace MiraAPI.Patches.Options; [HarmonyPatch(typeof(GameSettingMenu))] internal static class GameSettingMenuPatches { - public static int SelectedModIdx { get; private set; } - - public static MiraPluginInfo? SelectedMod { get; private set; } - - private static TextMeshPro? _text; - - private static Vector3 _roleBtnOgPos; - private static Vector3 _smallRoleBtnOgPos; - private static Vector3 _modifierBtnOgPos; - private static Vector3 _customOneBtnOgPos; - private static Vector3 _customTwoBtnOgPos; - - private static GameOptionsMenu? _modifiersTab; - private static PassiveButton? _modifiersButton; - private static PassiveButton? _smallRolesButton; - private static GameOptionsMenu? _customOneTab; - private static PassiveButton? _customOneButton; - private static GameOptionsMenu? _customTwoTab; - private static PassiveButton? _customTwoButton; - private static GameObject? _nextModButton; - private static GameObject? _previousModButton; - - private static Dictionary OptionsPositions { get; } = []; - private static Dictionary ModifiersPositions { get; } = []; - private static Dictionary CustomOnePositions { get; } = []; - private static Dictionary CustomTwoPositions { get; } = []; - - private static void SaveScrollPositions(GameSettingMenu gameSettingMenu) - { - if (_modifiersTab) - { - ModifiersPositions[SelectedModIdx] = _modifiersTab!.scrollBar.Inner.localPosition; - } - else if (_customOneTab) - { - CustomOnePositions[SelectedModIdx] = _customOneTab!.scrollBar.Inner.localPosition; - } - else if (_customTwoTab) - { - CustomTwoPositions[SelectedModIdx] = _customTwoTab!.scrollBar.Inner.localPosition; - } - - RoleSettingMenuPatches.RolePositions[SelectedModIdx] = gameSettingMenu.RoleSettingsTab.scrollBar.Inner.localPosition; - OptionsPositions[SelectedModIdx] = gameSettingMenu.GameSettingsTab.scrollBar.Inner.localPosition; - } - - [HarmonyPrefix] - [HarmonyPatch(nameof(GameSettingMenu.ChangeTab))] - - public static void ChangeTabPrefix(GameSettingMenu __instance) - { - SaveScrollPositions(__instance); - } - - [HarmonyPostfix] - [HarmonyPatch(nameof(GameSettingMenu.ChangeTab))] - public static void ChangeTabPostfix(GameSettingMenu __instance, int tabNum, bool previewOnly) - { - if ((previewOnly && Controller.currentTouchType == Controller.TouchType.Joystick) || !previewOnly) - { - if (_modifiersTab) - { - _modifiersTab!.gameObject.SetActive(tabNum == 3); - } - - if (_customOneTab) - { - _customOneTab!.gameObject.SetActive(tabNum == 4); - } - - if (_customTwoTab) - { - _customTwoTab!.gameObject.SetActive(tabNum == 5); - } - - _modifiersButton?.SelectButton(tabNum == 3); - _smallRolesButton?.SelectButton(tabNum == 2); - _customOneButton?.SelectButton(tabNum == 4); - _customTwoButton?.SelectButton(tabNum == 5); - if (tabNum >= 3 && tabNum <= 5) - { - if (__instance.RoleSettingsButton.gameObject.active) - { - __instance.RoleSettingsButton.SelectButton(true); - } - else - { - switch (tabNum) - { - case 3: - _modifiersButton?.SelectButton(true); - __instance.MenuDescriptionText.text = SelectedMod != null ? SelectedMod.MiraPlugin.ModifierMenuDescription : "Configure modifiers and their settings here!"; - break; - case 4: - _customOneButton?.SelectButton(true); - __instance.MenuDescriptionText.text = SelectedMod != null ? SelectedMod.MiraPlugin.CustomOptionMenuOneDescription : "Apply game settings for this mod!"; - break; - case 5: - _customTwoButton?.SelectButton(true); - __instance.MenuDescriptionText.text = SelectedMod != null ? SelectedMod.MiraPlugin.CustomOptionMenuTwoDescription : "Apply game settings for this mod!"; - break; - } - } - } - } - - if (previewOnly) - { - return; - } - - _modifiersButton?.SelectButton(tabNum == 3); - _smallRolesButton?.SelectButton(tabNum == 2); - _customOneButton?.SelectButton(tabNum == 4); - _customTwoButton?.SelectButton(tabNum == 5); - - if (tabNum >= 3 && tabNum <= 5) - { - if (__instance.RoleSettingsButton.gameObject.active) - { - __instance.RoleSettingsButton.SelectButton(true); - } - else - { - switch (tabNum) - { - case 3: - _modifiersButton?.SelectButton(true); - break; - case 4: - _customOneButton?.SelectButton(true); - break; - case 5: - _customTwoButton?.SelectButton(true); - break; - } - } - } - } - /// - /// Prefix for the method. Sets up the custom options. + /// Prefix for the method. Sets up the custom options. /// /// The instance. [HarmonyPrefix] - [HarmonyPatch(nameof(GameSettingMenu.Start))] - public static void StartPrefix(GameSettingMenu __instance) + [HarmonyPatch(nameof(GameSettingMenu.OnEnable))] + public static void OnEnablePrefix(GameSettingMenu __instance) { - _roleBtnOgPos = __instance.RoleSettingsButton.transform.localPosition; - __instance.transform.FindChild("GameSettingsLabel").gameObject.SetActive(false); - - var helpThing = __instance.transform.FindChild("What Is This?"); - var tmpText = Object.Instantiate(helpThing.transform.FindChild("InfoText"), helpThing.parent).gameObject; - - tmpText.GetComponent().Destroy(); - tmpText.name = "SelectedMod"; - tmpText.transform.localPosition = new Vector3(-3.3382f, 1.5399f, -2); - - _text = tmpText.GetComponent(); - _text.fontSizeMax = 3.2f; - _text.overflowMode = TextOverflowModes.Overflow; - - _text.alignment = TextAlignmentOptions.Center; - - _nextModButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject; - _nextModButton.transform.localPosition = new Vector3(-2.2663f, 1.5272f, -25f); - _nextModButton.name = "RightArrowButton"; - _nextModButton.transform.FindChild("Inactive").gameObject.GetComponent().sprite = - MiraAssets.NextButton.LoadAsset(); - _nextModButton.transform.FindChild("Active").gameObject.GetComponent().sprite = - MiraAssets.NextButtonActive.LoadAsset(); - _nextModButton.gameObject.GetComponent().DestroyImmediate(); - - var passiveButton = _nextModButton.gameObject.GetComponent(); - passiveButton.OnClick = new ButtonClickedEvent(); - passiveButton.OnClick.AddListener( - (UnityAction)(() => - { - SaveScrollPositions(__instance); - SelectedModIdx += 1; - if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins.Length) - { - SelectedModIdx = 0; - } - - UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); - })); - - _previousModButton = Object.Instantiate(_nextModButton, __instance.BackButton.transform.parent).gameObject; - _previousModButton.transform.localPosition = new Vector3(-4.4209f, 1.5272f, -25f); - _previousModButton.name = "LeftArrowButton"; - _previousModButton.gameObject.GetComponent().Destroy(); - _previousModButton.transform.FindChild("Active").gameObject.GetComponent().flipX = - _previousModButton.transform.FindChild("Inactive").gameObject.GetComponent().flipX = true; - _previousModButton.gameObject.GetComponent().OnClick.AddListener( - (UnityAction)(() => - { - SaveScrollPositions(__instance); - 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); - _modifiersTab.name = "MODIFIERS TAB"; - - // clone game settings tab for both custom tabs - _customOneTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent); - _customOneTab.name = "CUSTOM TAB 1"; - _customTwoTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent); - _customTwoTab.name = "CUSTOM TAB 2"; - - // create button for the first custom category - var customPos = new Vector3( - __instance.RoleSettingsButton.transform.localPosition.x, - __instance.RoleSettingsButton.transform.localPosition.y, - __instance.RoleSettingsButton.transform.localPosition.z); - _customOneButton = Object.Instantiate(__instance.RoleSettingsButton, __instance.RoleSettingsButton.transform.parent); - _customOneButton.buttonText.gameObject.GetComponent().Destroy(); - _customOneButton.OnClick = new ButtonClickedEvent(); - _customOneButton.OnClick.AddListener( - (UnityAction)(() => - { - __instance.ChangeTab(4, false); - })); - _customOneButton.OnMouseOver = new UnityEvent(); - _customOneButton.OnMouseOver.AddListener( - (UnityAction)(() => - { - __instance.ChangeTab(4, true); - })); - - _customOneButton.buttonText.text = SelectedMod != null ? SelectedMod.MiraPlugin.CustomOptionMenuNameOne : "Custom Category 1"; - customPos.y -= 0.637f; - _customOneButton.transform.localPosition = customPos; - _customOneButton.name = "CustomOneButton"; - - _customOneBtnOgPos = _customOneButton.transform.localPosition; - - // create button for the first custom category - _customTwoButton = Object.Instantiate(_customOneButton, _customOneButton.transform.parent); - _customTwoButton.OnClick = new ButtonClickedEvent(); - _customTwoButton.OnClick.AddListener( - (UnityAction)(() => - { - __instance.ChangeTab(5, false); - })); - _customTwoButton.OnMouseOver = new UnityEvent(); - _customTwoButton.OnMouseOver.AddListener( - (UnityAction)(() => - { - __instance.ChangeTab(5, true); - })); - - _customTwoButton.buttonText.text = SelectedMod != null ? SelectedMod.MiraPlugin.CustomOptionMenuNameTwo : "Custom Category 2"; - customPos.y -= 0.637f; - _customTwoButton.transform.localPosition = customPos; - _customTwoButton.name = "CustomTwoButton"; - - _customTwoBtnOgPos = _customTwoButton.transform.localPosition; - - // create button for modifiers - __instance.RoleSettingsButton.buttonText.gameObject.GetComponent().Destroy(); - __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); - })); - - _smallRoleBtnOgPos = _smallRolesButton.transform.localPosition; - - 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) + // Add Menu State object + if (!__instance.gameObject.GetComponent()) { - 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"; - - _modifierBtnOgPos = _modifiersButton.transform.localPosition; - - foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins) - { - PresetManager.LoadPresets(plugin); - } - - UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); - } - - private static void ChangeRoleSettingButton(bool replace, GameSettingMenu menu) - { - menu.RoleSettingsButton.OnClick = new ButtonClickedEvent(); - menu.RoleSettingsButton.OnMouseOver = new UnityEvent(); - - if (replace) - { - menu.RoleSettingsButton.buttonText.text = "Modifiers Settings"; - menu.RoleSettingsButton.OnClick.AddListener( - (UnityAction)(() => - { - menu.ChangeTab(3, false); - })); - menu.RoleSettingsButton.OnMouseOver.AddListener( - (UnityAction)(() => - { - menu.ChangeTab(3, true); - })); + __instance.gameObject.AddComponent(); + Info("MenuState component added to GameSettingMenu."); } else { - menu.RoleSettingsButton.buttonText.text = "Roles Settings"; - menu.RoleSettingsButton.OnClick.AddListener( - (UnityAction)(() => - { - menu.ChangeTab(2, false); - })); - menu.RoleSettingsButton.OnMouseOver.AddListener( - (UnityAction)(() => - { - menu.ChangeTab(2, true); - })); + Info("MenuState component already added to GameSettingMenu."); } } - private static void UpdateText(GameSettingMenu menu, GameOptionsMenu settings, RolesSettingsMenu roles) + [HarmonyPrefix] + [HarmonyPatch(nameof(GameSettingMenu.ChangeTab))] + public static bool ChangeTabPrefix(GameSettingMenu __instance, int tabNum, bool previewOnly) { - if (_text is not null && SelectedModIdx == 0) + if (MenuState.Instance) { - _text.text = $"(Page 0/{MiraPluginManager.Instance.RegisteredPlugins.Length})\nDefault"; - _text.fontSizeMax = 3.2f; - SelectedMod = null; - } - else if (_text is not null) - { - _text.fontSizeMax = 2.3f; - SelectedMod = MiraPluginManager.Instance.RegisteredPlugins[SelectedModIdx - 1]; - - var name = SelectedMod.MiraPlugin.OptionsTitleText; - _text.text = $"(Page {SelectedModIdx}/{MiraPluginManager.Instance.RegisteredPlugins.Length})\n" + name[..Math.Min(name.Length, 25)]; - } - - bool replaceWithModifiers = true; - _smallRolesButton!.transform.localPosition = _smallRoleBtnOgPos; - _modifiersButton!.transform.localPosition = _modifierBtnOgPos; - _customOneButton!.transform.localPosition = _customOneBtnOgPos; - _customTwoButton!.transform.localPosition = _customTwoBtnOgPos; - menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos; - - if (SelectedModIdx != 0) - { - _customOneButton.buttonText.text = SelectedMod!.MiraPlugin.CustomOptionMenuNameOne; - _customTwoButton.buttonText.text = SelectedMod!.MiraPlugin.CustomOptionMenuNameTwo; - var modHasRoles = SelectedMod!.InternalRoles.Count != 0; - var modHasCustomOne = SelectedMod.InternalOptionGroups.Exists( - x => x.ParentMenu == MenuCategory.CustomOne); - var modHasCustomTwo = SelectedMod.InternalOptionGroups.Exists( - x => x.ParentMenu == MenuCategory.CustomTwo); - var modHasModifiers = SelectedMod.InternalOptionGroups.Exists( - x => x.ParentMenu == MenuCategory.Modifiers || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true); - var modHasOptions = - SelectedMod.InternalOptionGroups.Exists(x => x.OptionableType == null && - x.ParentMenu == MenuCategory.Game); - - _modifiersButton.gameObject.SetActive(true); - _smallRolesButton.gameObject.SetActive(true); - _customOneButton.gameObject.SetActive(true); - _customTwoButton.gameObject.SetActive(true); - menu.GameSettingsButton.gameObject.SetActive(true); - menu.RoleSettingsButton.gameObject.SetActive(false); - var defaultButton = menu.RoleSettingsButton; - - // If there are no registered custom roles in the selected mod, hide the button. - if (!modHasRoles) - { - _smallRolesButton.gameObject.SetActive(false); - - if (roles.gameObject.active) - { - menu.ChangeTab(0, false); - } - - // If the mod has modifiers, we can disable the smaller modifier button and enable the bigger one. - if (modHasModifiers) - { - _modifiersButton.gameObject.SetActive(false); - menu.RoleSettingsButton.gameObject.SetActive(true); - defaultButton = _modifiersButton; - - if (_modifiersTab!.gameObject.active) - { - menu.RoleSettingsButton.SelectButton(true); - } - } - } - - // If there are no modifiers in the selected mod, hide the modifiers button. - if (!modHasModifiers) - { - replaceWithModifiers = false; - _modifiersButton.gameObject.SetActive(false); - - if (_modifiersTab!.gameObject.active) - { - menu.ChangeTab(0, false); - } - - // If the mod has roles, we can enable the bigger role button and disable the small one. - if (modHasRoles) - { - defaultButton = menu.RoleSettingsButton; - menu.RoleSettingsButton.gameObject.SetActive(true); - _smallRolesButton.gameObject.SetActive(false); - } - } - - // If there are no custom game options registered (that aren't modifier options), hide game settings button. - if (!modHasOptions) - { - menu.GameSettingsButton.gameObject.SetActive(false); - - if (settings.gameObject.active) - { - menu.ChangeTab(0, false); - } - defaultButton = menu.RoleSettingsButton; - - // If the mod has roles and modifiers, we can move their buttons to the game settings button position, since nothing is there. - if (menu.RoleSettingsButton.gameObject.active) - { - menu.RoleSettingsButton.transform.localPosition = menu.GameSettingsButton.transform.localPosition; - } - else if (_modifiersButton.gameObject.active && _smallRolesButton.gameObject.active) - { - _modifiersButton.transform.localPosition = new Vector3( - _modifiersButton.transform.localPosition.x, - menu.GameSettingsButton.transform.localPosition.y, - _modifiersButton.transform.localPosition.z); - - _smallRolesButton.transform.localPosition = new Vector3( - _smallRolesButton.transform.localPosition.x, - menu.GameSettingsButton.transform.localPosition.y, - _smallRolesButton.transform.localPosition.z); - } - } - - // If the mod has no primary custom tab, we can go ahead and hide the button. - if (!modHasCustomOne) - { - _customOneButton.gameObject.SetActive(false); - if (_customOneTab!.gameObject.active) - { - defaultButton.SelectButton(true); - } - } - // If the mod has no secondary custom tab, we can go ahead and hide the button. - if (!modHasCustomTwo) - { - _customTwoButton.gameObject.SetActive(false); - if (_customTwoTab!.gameObject.active) - { - defaultButton.SelectButton(true); - } - } + MenuState.Instance.ChangeTabPatch(__instance, tabNum, previewOnly); } else { - _modifiersButton.gameObject.SetActive(false); - _smallRolesButton.gameObject.SetActive(false); - _customOneButton.gameObject.SetActive(false); - _customTwoButton.gameObject.SetActive(false); - menu.RoleSettingsButton.gameObject.SetActive(true); - menu.GameSettingsButton.gameObject.SetActive(true); - replaceWithModifiers = false; - - if (_modifiersTab!.gameObject.active || _customOneTab!.gameObject.active || _customTwoTab!.gameObject.active) - { - menu.ChangeTab(0, false); - } - } - - if (menu.RoleSettingsButton.gameObject.active) - { - ChangeRoleSettingButton(replaceWithModifiers, menu); - } - - if (menu.PresetsTab.gameObject.active) - { - menu.ChangeTab(0, false); - } - - CleanupTab(settings, roles); - } - - private static void ClearOptions(Il2CppSystem.Collections.Generic.List options) - { - foreach (var child in options) - { - if (child.TryCast() || !child.gameObject) - { - continue; - } - - child.gameObject.DestroyImmediate(); - } - - options.Clear(); - } - - private static void CleanupTab(GameOptionsMenu settings, RolesSettingsMenu roles) - { - if (roles.roleChances != null) - { - CleanupRoleSettings(roles); - } - - if (settings.Children != null) - { - CleanupSettings(settings); - } - - if (_modifiersTab?.Children?.Count > 0) - { - CleanupSettings(_modifiersTab, MenuCategory.Modifiers); - } - - if (_customOneTab?.Children?.Count > 0) - { - CleanupSettings(_customOneTab, MenuCategory.CustomOne); - } - - if (_customTwoTab?.Children?.Count > 0) - { - CleanupSettings(_customTwoTab, MenuCategory.CustomTwo); - } - - void CleanupRoleSettings(RolesSettingsMenu rolesMenu) - { - if (rolesMenu.advancedSettingChildren != null) - { - ClearOptions(rolesMenu.advancedSettingChildren); - rolesMenu.advancedSettingChildren = null; - } - - rolesMenu.RoleChancesSettings - .transform - .GetComponentsInChildren() - .ToList() - .ForEach(header => header.gameObject.DestroyImmediate()); - - foreach (var role in rolesMenu.roleChances) - { - role.gameObject.DestroyImmediate(); - } - - rolesMenu.roleChances?.Clear(); - - rolesMenu.AdvancedRolesSettings.gameObject.SetActive(false); - rolesMenu.RoleChancesSettings.gameObject.SetActive(true); - rolesMenu.SetQuotaTab(); - if (RoleSettingMenuPatches.RolePositions.TryGetValue(SelectedModIdx, out var pos)) - { - rolesMenu.scrollBar.Inner.localPosition = pos; - rolesMenu.scrollBar.UpdateScrollBars(); - } - else - { - rolesMenu.scrollBar.ScrollToTop(); - } - } - - void CleanupSettings(GameOptionsMenu gameOptMenu, MenuCategory menuCategory = MenuCategory.Roles) - { - ClearOptions(gameOptMenu.Children); - gameOptMenu.Children = null; - - gameOptMenu.settingsContainer - .GetComponentsInChildren() - .ToList() - .ForEach(header => header.gameObject.DestroyImmediate()); - - gameOptMenu.Initialize(); - var positions = OptionsPositions; - switch (menuCategory) - { - case MenuCategory.Modifiers: - positions = ModifiersPositions; - break; - case MenuCategory.CustomOne: - positions = CustomOnePositions; - break; - case MenuCategory.CustomTwo: - positions = CustomTwoPositions; - break; - } - if (positions.TryGetValue(SelectedModIdx, out var pos)) - { - gameOptMenu.scrollBar.Inner.localPosition = pos; - gameOptMenu.scrollBar.UpdateScrollBars(); - } - else - { - gameOptMenu.scrollBar.ScrollToTop(); - } + Error("MenuState instance is null. Cannot change tab."); } + return false; } } diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index c543447d..51e2e92b 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -25,7 +25,7 @@ public static class LobbyViewPanePatches private static MiraPluginInfo? SelectedMod => SelectedModIdx == 0 ? null - : MiraPluginManager.Instance.RegisteredPlugins[SelectedModIdx - 1]; + : MiraPluginManager.Instance.RegisteredPluginsWithOptions[SelectedModIdx - 1]; private static PassiveButton? ModifiersTabButton { get; set; } @@ -75,7 +75,7 @@ public static class LobbyViewPanePatches (UnityAction)(() => { SelectedModIdx += 1; - if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins.Length) + if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPluginsWithOptions.Length) { SelectedModIdx = 0; } @@ -99,7 +99,7 @@ public static class LobbyViewPanePatches SelectedModIdx -= 1; if (SelectedModIdx < 0) { - SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins.Length; + SelectedModIdx = MiraPluginManager.Instance.RegisteredPluginsWithOptions.Length; } Refresh(__instance); @@ -109,7 +109,7 @@ public static class LobbyViewPanePatches private static void Refresh(LobbyViewSettingsPane menu) { ModifiersTabButton?.gameObject.SetActive(SelectedModIdx != 0); - menu.gameModeText.text = SelectedMod?.PluginInfo.Metadata.Name ?? "Default"; + menu.gameModeText.text = SelectedMod?.PluginInfo.Metadata.Name ?? "Main"; menu.RefreshTab(); menu.scrollBar.ScrollToTop(); } diff --git a/MiraAPI/Patches/Options/MenuState.cs b/MiraAPI/Patches/Options/MenuState.cs new file mode 100644 index 00000000..d4592020 --- /dev/null +++ b/MiraAPI/Patches/Options/MenuState.cs @@ -0,0 +1,683 @@ +using System; +using System.Collections.Generic; +using Il2CppInterop.Runtime.Attributes; +using MiraAPI.GameOptions; +using MiraAPI.Modifiers; +using MiraAPI.PluginLoading; +using MiraAPI.Presets; +using MiraAPI.Utilities.Assets; +using Reactor.Utilities.Attributes; +using Reactor.Utilities.Extensions; +using TMPro; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.UI; + +namespace MiraAPI.Patches.Options; + +[RegisterInIl2Cpp] +public class MenuState(IntPtr cppPtr) : MonoBehaviour(cppPtr) +{ + public static MenuState Instance { get; private set; } = null!; + + public static int ModCount => MiraPluginManager.Instance.RegisteredPluginsWithOptions.Length; + + private GameSettingMenu Gsm { get; set; } + + public MenuCategory CurrentMenu { get; private set; } + + public int CurrentModIdx { get; private set; } + + public GameObject CurrentContainer => Containers[CurrentMenu][CurrentModIdx]; + + [HideFromIl2Cpp] + public MiraPluginInfo CurrentMod => MiraPluginManager.Instance.RegisteredPluginsWithOptions[CurrentModIdx - 1]; + + // UI elements + private TextMeshPro _text = null!; + + private enum MenuButton + { + Roles, + Modifiers, + CustomOne, + CustomTwo, + } + + private readonly Dictionary _smallButtons = []; + private readonly Dictionary _largeButtons = []; + + [HideFromIl2Cpp] + public Dictionary> Containers { get; } = new() + { + { MenuCategory.Preset, [] }, + { MenuCategory.Game, [] }, + { MenuCategory.Roles, [] }, + { MenuCategory.Modifiers, [] }, + { MenuCategory.CustomOne, [] }, + { MenuCategory.CustomTwo, [] }, + }; + + [HideFromIl2Cpp] + internal Dictionary FinishedRoleMenus { get; } = []; + + public void Awake() + { + if (Instance) + { + Error($"MenuState already exists! Destroying duplicate {name}."); + Destroy(this); + return; + } + + Instance = this; + Gsm = GetComponent(); + + if (!Gsm) + { + Error($"MenuState could not find GameSettingMenu component! Destroying {name}."); + Destroy(this); + return; + } + + // Reload presets + foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins) + { + PresetManager.LoadPresets(plugin); + } + + // Initialize and setup UI + InitializeContainers(); + InitializeUi(); + UpdateUi(); + } + + private void InitializeUi() + { + // Disable game settings label + Gsm.transform.FindChild("GameSettingsLabel").gameObject.SetActive(false); + + // Create mod name text + var helpThing = Gsm.transform.FindChild("What Is This?"); + var tmpText = Instantiate(helpThing.transform.FindChild("InfoText"), helpThing.parent).gameObject; + + Destroy(tmpText.GetComponent()); + tmpText.name = "SelectedMod"; + tmpText.transform.localPosition = new Vector3(-3.3382f, 1.5399f, -2); + + _text = tmpText.GetComponent(); + _text.fontSizeMax = 3.2f; + _text.overflowMode = TextOverflowModes.Overflow; + _text.alignment = TextAlignmentOptions.Center; + _text.rectTransform.sizeDelta = new Vector2(1.5f, 0.8f); + + // Create next and previous buttons + var nextButton = Instantiate(Gsm.BackButton, Gsm.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.gameObject.GetComponent().DestroyImmediate(); + + var nextPassiveButton = nextButton.gameObject.GetComponent(); + nextPassiveButton.OnClick = new Button.ButtonClickedEvent(); + nextPassiveButton.OnClick.AddListener((UnityAction)NextMod); + + var previousButton = Instantiate(nextButton, Gsm.BackButton.transform.parent).gameObject; + previousButton.transform.localPosition = new Vector3(-4.4209f, 1.5272f, -25f); + previousButton.name = "LeftArrowButton"; + previousButton.gameObject.GetComponent().Destroy(); + previousButton.transform.FindChild("Active").gameObject.GetComponent().flipX = + previousButton.transform.FindChild("Inactive").gameObject.GetComponent().flipX = true; + + var prevPassiveButton = previousButton.gameObject.GetComponent(); + prevPassiveButton.OnClick = new Button.ButtonClickedEvent(); + prevPassiveButton.OnClick.AddListener((UnityAction)PreviousMod); + + // Create menu buttons + _largeButtons[(int)MenuButton.Roles] = Gsm.RoleSettingsButton; + + // modifiers button + var mBtn = Instantiate(Gsm.RoleSettingsButton, Gsm.RoleSettingsButton.transform.parent); + _largeButtons[MenuButton.Modifiers] = mBtn; + mBtn.name = "ModifiersButton"; + mBtn.buttonText.gameObject.GetComponent().Destroy(); + mBtn.buttonText.text = "Modifiers"; + mBtn.OnClick = new Button.ButtonClickedEvent(); + mBtn.OnClick.AddListener( + (UnityAction)(() => + { + Gsm.ChangeTab((int)MenuCategory.Modifiers, false); + })); + mBtn.OnMouseOver = new UnityEvent(); + mBtn.OnMouseOver.AddListener( + (UnityAction)(() => + { + Gsm.ChangeTab((int)MenuCategory.Modifiers, true); + })); + + // first custom menu + var c1Btn = Instantiate(mBtn, mBtn.transform.parent); + _largeButtons[MenuButton.CustomOne] = c1Btn; + c1Btn.name = "CustomOneButton"; + c1Btn.buttonText.gameObject.GetComponent().Destroy(); + c1Btn.OnClick = new Button.ButtonClickedEvent(); + c1Btn.OnClick.AddListener( + (UnityAction)(() => + { + Gsm.ChangeTab((int)MenuCategory.CustomOne, false); + })); + c1Btn.OnMouseOver = new UnityEvent(); + c1Btn.OnMouseOver.AddListener( + (UnityAction)(() => + { + Gsm.ChangeTab((int)MenuCategory.CustomOne, true); + })); + + // second custom menu + var c2Btn = Instantiate(c1Btn, c1Btn.transform.parent); + _largeButtons[MenuButton.CustomTwo] = c2Btn; + c2Btn.name = "CustomTwoButton"; + c2Btn.OnClick = new Button.ButtonClickedEvent(); + c2Btn.OnClick.AddListener( + (UnityAction)(() => + { + Gsm.ChangeTab((int)MenuCategory.CustomTwo, false); + })); + c2Btn.OnMouseOver = new UnityEvent(); + c2Btn.OnMouseOver.AddListener( + (UnityAction)(() => + { + Gsm.ChangeTab((int)MenuCategory.CustomTwo, true); + })); + + // small roles button + var smRolesBtn = Instantiate( + Gsm.RoleSettingsButton, + Gsm.RoleSettingsButton.transform.parent); + _smallButtons[MenuButton.Roles] = smRolesBtn; + smRolesBtn.name = "SmallRolesButton"; + smRolesBtn.OnClick = new Button.ButtonClickedEvent(); + smRolesBtn.OnClick.AddListener( + (UnityAction)(() => + { + Gsm.ChangeTab((int)MenuCategory.Roles, false); + })); + smRolesBtn.OnMouseOver = new UnityEvent(); + smRolesBtn.OnMouseOver.AddListener( + (UnityAction)(() => + { + Gsm.ChangeTab((int)MenuCategory.Roles, true); + })); + + var roleText = smRolesBtn.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 smRolesBtn.Colliders) + { + if (collider.TryCast() is { } col) + { + col.size = new Vector2(col.size.x / 2, col.size.y); + } + } + + foreach (var rend in smRolesBtn.GetComponentsInChildren(true)) + { + rend.size = new Vector2(rend.size.x / 2, rend.size.y); + } + + // small modifiers button + var smModBtn = Instantiate(smRolesBtn, smRolesBtn.transform.parent); + _smallButtons[MenuButton.Modifiers] = smModBtn; + smModBtn.name = "SmallModifiersButton"; + smModBtn.buttonText.text = "Modifiers"; + smModBtn.OnClick = new Button.ButtonClickedEvent(); + smModBtn.OnClick.AddListener( + (UnityAction)(() => + { + Gsm.ChangeTab((int)MenuCategory.Modifiers, false); + })); + smModBtn.OnMouseOver = new UnityEvent(); + smModBtn.OnMouseOver.AddListener( + (UnityAction)(() => + { + Gsm.ChangeTab((int)MenuCategory.Modifiers, true); + })); + + // small c1 button + var smC1Btn = Instantiate(smModBtn, smModBtn.transform.parent); + _smallButtons[MenuButton.CustomOne] = smC1Btn; + smC1Btn.name = "SmallCustomOneButton"; + smC1Btn.buttonText.text = "Custom 1"; + smC1Btn.OnClick = new Button.ButtonClickedEvent(); + smC1Btn.OnClick.AddListener( + (UnityAction)(() => + { + Gsm.ChangeTab((int)MenuCategory.CustomOne, false); + })); + smC1Btn.OnMouseOver = new UnityEvent(); + smC1Btn.OnMouseOver.AddListener( + (UnityAction)(() => + { + Gsm.ChangeTab((int)MenuCategory.CustomOne, true); + })); + + // small c2 button + var smC2Btn = Instantiate(smModBtn, smModBtn.transform.parent); + _smallButtons[MenuButton.CustomTwo] = smC2Btn; + smC2Btn.name = "SmallCustomTwoButton"; + smC2Btn.buttonText.text = "Custom 2"; + smC2Btn.OnClick = new Button.ButtonClickedEvent(); + smC2Btn.OnClick.AddListener( + (UnityAction)(() => + { + Gsm.ChangeTab((int)MenuCategory.CustomTwo, false); + })); + smC2Btn.OnMouseOver = new UnityEvent(); + smC2Btn.OnMouseOver.AddListener( + (UnityAction)(() => + { + Gsm.ChangeTab((int)MenuCategory.CustomTwo, true); + })); + } + + private void InitializeContainers() + { + // Set vanilla containers + Containers[MenuCategory.Game][0] = Gsm.GameSettingsTab.settingsContainer.gameObject; + Containers[MenuCategory.Roles][0] = Gsm.RoleSettingsTab.RoleChancesSettings; + + for (var idx = 0; idx < ModCount; idx++) + { + var mod = MiraPluginManager.Instance.RegisteredPluginsWithOptions[idx]; + + // Create preset container for each mod + var presetContainer = new GameObject($"PresetHolder_{mod.PluginInfo.Metadata.Name}"); + presetContainer.transform.SetParent(Gsm.PresetsTab.transform, false); + presetContainer.transform.localScale = new Vector3(0.9f, 0.9f, 1); + presetContainer.transform.localPosition = new Vector3(-1.9f, 1.7f, 0); + presetContainer.gameObject.SetActive(false); + + var arrange = presetContainer.AddComponent(); + arrange.Alignment = GridArrange.StartAlign.Right; + arrange.CellSize = new Vector2(2.15f, -.55f); + arrange.MaxColumns = 2; + + Containers[MenuCategory.Preset][idx + 1] = presetContainer; + + // Create game options container + var gameContainer = Containers[MenuCategory.Game][idx + 1] = Instantiate( + Gsm.GameSettingsTab.settingsContainer.gameObject, + Gsm.GameSettingsTab.settingsContainer.gameObject.transform.parent); + gameContainer.name = $"GameSettings_{mod.PluginInfo.Metadata.Name}"; + gameContainer.transform.DestroyChildren(); + gameContainer.SetActive(false); + + // Roles container + var rolesContainer = Containers[MenuCategory.Roles][idx + 1] = Instantiate( + Gsm.RoleSettingsTab.RoleChancesSettings, + Gsm.RoleSettingsTab.RoleChancesSettings.transform.parent); + rolesContainer.name = $"RoleSettings_{mod.PluginInfo.Metadata.Name}"; + rolesContainer.transform.DestroyChildren(); + rolesContainer.SetActive(false); + + // Modifiers + Custom containers + for (var i = MenuCategory.Modifiers; i <= MenuCategory.CustomTwo; i++) + { + var cont = Containers[i][idx + 1] = Instantiate( + Gsm.GameSettingsTab.settingsContainer.gameObject, + Gsm.GameSettingsTab.settingsContainer.gameObject.transform.parent); + cont.name = $"{i}_{mod.PluginInfo.Metadata.Name}"; + cont.transform.DestroyChildren(); + cont.SetActive(false); + } + } + } + + private void UpdateContainers() + { + foreach (var (_, dict) in Containers) + { + foreach (var (_, container) in dict) + { + container.SetActive(false); + } + } + + Info($"Enabled {CurrentMenu} for {CurrentModIdx}"); + + // Preset containers + if (CurrentModIdx != 0) + { + Containers[MenuCategory.Preset][CurrentModIdx].SetActive(true); + } + + // Game / Modifier / Custom Settings tab + // GST.settingsContainer is same as GST.scrollBar.Inner + if (CurrentMenu != MenuCategory.Preset && CurrentMenu != MenuCategory.Roles) + { + Gsm.GameSettingsTab.settingsContainer = Gsm.GameSettingsTab.scrollBar.Inner = CurrentContainer.transform; + CurrentContainer.SetActive(true); + } + + // Role Settings Tab + Gsm.RoleSettingsTab.RoleChancesSettings = Containers[MenuCategory.Roles][CurrentModIdx]; + Gsm.RoleSettingsTab.scrollBar.Inner = Containers[MenuCategory.Roles][CurrentModIdx].transform; + Containers[MenuCategory.Roles][CurrentModIdx].SetActive(true); + } + + private void UpdateUi() + { + // Update mod text + if (CurrentModIdx == 0) + { + _text.text = $"(Page 1/{ModCount + 1})\nMain"; + } + else + { + var modName = CurrentMod.MiraPlugin.OptionsTitleText; + _text.text = $"(Page {CurrentModIdx + 1}/{ModCount + 1})\n" + + modName[..Math.Min(modName.Length, 25)]; + } + + UpdateContainers(); + + // Update buttons + foreach (var (_, btn) in _smallButtons) + { + btn.gameObject.SetActive(false); + } + + foreach (var (_, btn) in _largeButtons) + { + btn.gameObject.SetActive(false); + } + + if (CurrentModIdx == 0) + { + Gsm.GameSettingsButton.gameObject.SetActive(true); + Gsm.RoleSettingsButton.gameObject.SetActive(true); + } + else + { + var hasOptions = + CurrentMod.InternalOptionGroups.Exists(g => + g.OptionableType == null && g.ParentMenu == MenuCategory.Game); + var hasRoles = CurrentMod.InternalRoles.Count > 0; + var hasModifiers = CurrentMod.InternalOptionGroups.Exists(g => + g.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true + || g.ParentMenu == MenuCategory.Modifiers); + var hasC1 = CurrentMod.InternalOptionGroups.Exists(g => g.ParentMenu == MenuCategory.CustomOne); + var hasC2 = CurrentMod.InternalOptionGroups.Exists(g => g.ParentMenu == MenuCategory.CustomTwo); + const float leftPos = -3.65f; + const float rightPos = -2.27f; + + var smallRolesModifiers = hasRoles && hasModifiers && (hasC1 || hasC2); + var smallC1C2 = hasC1 && hasC2; + + Info("hasOptions: " + hasOptions); + Info("hasRoles: " + hasRoles); + Info("hasModifiers: " + hasModifiers); + Info("hasC1: " + hasC1); + Info("hasC2: " + hasC2); + Info("smallC1C2: " + smallC1C2); + Info("smallRolesModifiers: " + smallRolesModifiers); + + var position = Gsm.GameSettingsButton.transform.localPosition; + + // If this mod has game options, enable the game settings button + if (hasOptions) + { + Gsm.GameSettingsButton.gameObject.SetActive(true); + position.y -= 0.637f; + } + else + { + Gsm.GameSettingsButton.gameObject.SetActive(false); + } + + // If we should use small roles/modifiers buttons, enable them and position them accordingly + if (smallRolesModifiers) + { + _smallButtons[MenuButton.Roles].gameObject.SetActive(true); + _smallButtons[MenuButton.Roles].transform.localPosition = new Vector3(leftPos, position.y, position.z); + _smallButtons[MenuButton.Modifiers].gameObject.SetActive(true); + _smallButtons[MenuButton.Modifiers].transform.localPosition = + new Vector3(rightPos, position.y, position.z); + position.y -= 0.637f; + } + + // If we should use large roles button, enable and position, otherwise disable + if (hasRoles && !smallRolesModifiers) + { + _largeButtons[MenuButton.Roles].gameObject.SetActive(true); + _largeButtons[MenuButton.Roles].transform.localPosition = position; + position.y -= 0.637f; + } + else + { + _largeButtons[MenuButton.Roles].gameObject.SetActive(false); + } + + // If we should use large modifiers button, enable and position, otherwise disable + if (hasModifiers && !smallRolesModifiers) + { + _largeButtons[MenuButton.Modifiers].gameObject.SetActive(true); + _largeButtons[MenuButton.Modifiers].transform.localPosition = position; + position.y -= 0.637f; + } + else + { + _largeButtons[MenuButton.Modifiers].gameObject.SetActive(false); + } + + // if we should use small custom buttons, enable and position accordingly + if (smallC1C2) + { + _smallButtons[MenuButton.CustomOne].gameObject.SetActive(true); + _smallButtons[MenuButton.CustomOne].transform.localPosition = + new Vector3(leftPos, position.y, position.z); + _smallButtons[MenuButton.CustomOne].buttonText.text = CurrentMod.MiraPlugin.CustomOptionMenuNameOne; + + _smallButtons[MenuButton.CustomTwo].gameObject.SetActive(true); + _smallButtons[MenuButton.CustomTwo].transform.localPosition = + new Vector3(rightPos, position.y, position.z); + _smallButtons[MenuButton.CustomTwo].buttonText.text = CurrentMod.MiraPlugin.CustomOptionMenuNameTwo; + position.y -= 0.637f; + } + + // C1 only + if (hasC1 && !smallC1C2) + { + _largeButtons[MenuButton.CustomOne].gameObject.SetActive(true); + _largeButtons[MenuButton.CustomOne].transform.localPosition = position; + _largeButtons[MenuButton.CustomOne].buttonText.text = CurrentMod.MiraPlugin.CustomOptionMenuNameOne; + position.y -= 0.637f; + } + else + { + _largeButtons[MenuButton.CustomOne].gameObject.SetActive(false); + } + + // C2 only + if (hasC2 && !smallC1C2) + { + _largeButtons[MenuButton.CustomTwo].gameObject.SetActive(true); + _largeButtons[MenuButton.CustomTwo].transform.localPosition = position; + _largeButtons[MenuButton.CustomTwo].buttonText.text = CurrentMod.MiraPlugin.CustomOptionMenuNameTwo; + position.y -= 0.637f; + } + else + { + _largeButtons[MenuButton.CustomTwo].gameObject.SetActive(false); + } + } + } + + internal void ChangeTabPatch(GameSettingMenu menu, int tabNum, bool previewOnly) + { + Info($"Changed tab to {(MenuCategory)tabNum} (previewOnly: {previewOnly}) for mod {CurrentModIdx}"); + if ((previewOnly && Controller.currentTouchType == Controller.TouchType.Joystick) || !previewOnly) + { + CurrentMenu = (MenuCategory)tabNum; + UpdateContainers(); + + menu.PresetsTab.gameObject.SetActive(false); + menu.GameSettingsTab.gameObject.SetActive(false); + menu.RoleSettingsTab.gameObject.SetActive(false); + + menu.GamePresetsButton.SelectButton(false); + menu.GameSettingsButton.SelectButton(false); + menu.RoleSettingsButton.SelectButton(false); + + foreach (var (_, btn) in _largeButtons) + { + btn.SelectButton(false); + } + + foreach (var (_, btn) in _smallButtons) + { + btn.SelectButton(false); + } + + switch (CurrentMenu) + { + case MenuCategory.Preset: + menu.PresetsTab.gameObject.SetActive(true); + menu.MenuDescriptionText.text = TranslationController.Instance.GetString(StringNames.GamePresetsDescription); + break; + case MenuCategory.Game: + menu.GameSettingsTab.gameObject.SetActive(true); + menu.MenuDescriptionText.text = TranslationController.Instance.GetString(StringNames.GameSettingsDescription); + break; + case MenuCategory.Roles: + menu.RoleSettingsTab.gameObject.SetActive(true); + menu.RoleSettingsTab.OpenMenu(false); + menu.MenuDescriptionText.text = TranslationController.Instance.GetString(StringNames.RoleSettingsDescription); + break; + + case MenuCategory.Modifiers: + menu.GameSettingsTab.gameObject.SetActive(true); + menu.MenuDescriptionText.text = CurrentMod.MiraPlugin.ModifierMenuDescription; + break; + case MenuCategory.CustomOne: + menu.GameSettingsTab.gameObject.SetActive(true); + menu.MenuDescriptionText.text = CurrentMod.MiraPlugin.CustomOptionMenuOneDescription; + break; + case MenuCategory.CustomTwo: + menu.GameSettingsTab.gameObject.SetActive(true); + menu.MenuDescriptionText.text = CurrentMod.MiraPlugin.CustomOptionMenuTwoDescription; + break; + } + } + + if (previewOnly) + { + menu.ToggleLeftSideDarkener(false); + menu.ToggleRightSideDarkener(true); + return; + } + + menu.ToggleLeftSideDarkener(true); + menu.ToggleRightSideDarkener(false); + + CurrentMenu = (MenuCategory)tabNum; + UpdateContainers(); + switch (CurrentMenu) + { + case MenuCategory.Preset: + menu.PresetsTab.OpenMenu(); + menu.GamePresetsButton.SelectButton(true); + break; + case MenuCategory.Roles: + menu.RoleSettingsTab.OpenMenu(); + SelectButton(MenuButton.Roles, MenuCategory.Roles); + break; + case MenuCategory.Game: + case MenuCategory.Modifiers: + case MenuCategory.CustomOne: + case MenuCategory.CustomTwo: + menu.GameSettingsButton.SelectButton(CurrentMenu == MenuCategory.Game); + SelectButton(MenuButton.Modifiers, MenuCategory.Modifiers); + SelectButton(MenuButton.CustomOne, MenuCategory.CustomOne); + SelectButton(MenuButton.CustomTwo, MenuCategory.CustomTwo); + break; + } + } + + private void SelectButton(MenuButton button, MenuCategory category) + { + if (_largeButtons[button].gameObject.activeInHierarchy) + { + _largeButtons[button].SelectButton(CurrentMenu == category); + } + + if (_smallButtons[button].gameObject.activeInHierarchy) + { + _smallButtons[button].SelectButton(CurrentMenu == category); + } + } + + private MenuCategory GetNewPageIfNeeded() + { + if (CurrentModIdx == 0) + { + if (CurrentMenu >= MenuCategory.Modifiers) + { + return MenuCategory.Game; + } + } + else + { + var hasOptions = + CurrentMod.InternalOptionGroups.Exists(g => + g.OptionableType == null && g.ParentMenu == MenuCategory.Game); + var hasRoles = CurrentMod.InternalRoles.Count > 0; + var hasModifiers = CurrentMod.InternalOptionGroups.Exists(g => + g.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true + || g.ParentMenu == MenuCategory.Modifiers); + var hasC1 = CurrentMod.InternalOptionGroups.Exists(g => g.ParentMenu == MenuCategory.CustomOne); + var hasC2 = CurrentMod.InternalOptionGroups.Exists(g => g.ParentMenu == MenuCategory.CustomTwo); + + switch (CurrentMenu) + { + case MenuCategory.Game when !hasOptions: + case MenuCategory.Roles when !hasRoles: + case MenuCategory.Modifiers when !hasModifiers: + case MenuCategory.CustomOne when !hasC1: + case MenuCategory.CustomTwo when !hasC2: + return MenuCategory.Preset; + } + } + + return CurrentMenu; + } + + public void NextMod() + { + CurrentModIdx++; + // The existence of vanilla settings offsets the off-by-one errors. + if (CurrentModIdx > ModCount) CurrentModIdx = 0; + CurrentMenu = GetNewPageIfNeeded(); + UpdateUi(); + ChangeTabPatch(Gsm, (int)CurrentMenu, false); + } + + public void PreviousMod() + { + CurrentModIdx--; + // The existence of vanilla settings offsets the off-by-one errors. + if (CurrentModIdx < 0) CurrentModIdx = ModCount; + CurrentMenu = GetNewPageIfNeeded(); + UpdateUi(); + ChangeTabPatch(Gsm, (int)CurrentMenu, false); + } +} diff --git a/MiraAPI/Patches/Options/NotificationPopperPatch.cs b/MiraAPI/Patches/Options/NotificationPopperPatch.cs index 349f1038..8024ea49 100644 --- a/MiraAPI/Patches/Options/NotificationPopperPatch.cs +++ b/MiraAPI/Patches/Options/NotificationPopperPatch.cs @@ -1,8 +1,6 @@ using System.Linq; using HarmonyLib; -using Il2CppSystem; using MiraAPI.Roles; -using Object = Il2CppSystem.Object; namespace MiraAPI.Patches.Options; diff --git a/MiraAPI/Patches/Options/OptionsPatches.cs b/MiraAPI/Patches/Options/OptionsPatches.cs index 0f996917..36651f94 100644 --- a/MiraAPI/Patches/Options/OptionsPatches.cs +++ b/MiraAPI/Patches/Options/OptionsPatches.cs @@ -1,9 +1,6 @@ -using System.Collections.Generic; -using System.Linq; -using HarmonyLib; +using HarmonyLib; using Il2CppSystem; using MiraAPI.GameOptions; -using MiraAPI.GameOptions.OptionTypes; using MiraAPI.Roles; using MiraAPI.Utilities; using UnityEngine; diff --git a/MiraAPI/Patches/Options/PlayerOptionPatches.cs b/MiraAPI/Patches/Options/PlayerOptionPatches.cs index 2b77bc16..9cf0134d 100644 --- a/MiraAPI/Patches/Options/PlayerOptionPatches.cs +++ b/MiraAPI/Patches/Options/PlayerOptionPatches.cs @@ -1,12 +1,8 @@ -using System; -using System.Linq; +using System.Linq; using HarmonyLib; using MiraAPI.GameOptions; -using MiraAPI.GameOptions.OptionTypes; using MiraAPI.Utilities; -using Reactor.Utilities; using UnityEngine; -using Object = Il2CppSystem.Object; namespace MiraAPI.Patches.Options; diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 55947c80..3c1e645a 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -1,9 +1,10 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; +using BepInEx.Unity.IL2CPP.Utils.Collections; using HarmonyLib; -using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.GameOptions; using MiraAPI.Networking; using MiraAPI.Roles; @@ -11,7 +12,6 @@ using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; @@ -24,11 +24,7 @@ namespace MiraAPI.Patches.Options; [HarmonyPatch(typeof(RolesSettingsMenu))] public static class RoleSettingMenuPatches { - public static Dictionary RolePositions { get; set; } = []; - private static Dictionary RoleGroupHidden { get; set; } = []; - private static List Headers { get; set; } = []; - private static List RoleOptionSettings { get; set; } = []; private static float ScrollerNum { get; set; } = 0.522f; @@ -42,335 +38,375 @@ public static class RoleSettingMenuPatches HudManager.Instance.PlayerCam.OverrideScreenShakeEnabled = false; } - [HarmonyPostfix] - [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))] - public static void SetQuotaTabPostfix(RolesSettingsMenu __instance) + private static IEnumerator CoQuotaTabPatch(RolesSettingsMenu roleMenu) { - __instance.scrollBar.SetScrollBounds(__instance); - } - - [HarmonyPrefix] - [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))] - public static bool SetQuotaTabPatch(RolesSettingsMenu __instance) - { - foreach (var obj in Headers) - { - if (!obj) - { - continue; - } - obj.DeepDestroy(false); - } - Headers.Clear(); - foreach (var obj in RoleOptionSettings) - { - if (!obj) - { - continue; - } - obj.gameObject.DeepDestroy(false); - } - RoleOptionSettings.Clear(); CurrentRole = null; CurrentRoleOptions = null; - __instance.roleChances = new Il2CppSystem.Collections.Generic.List(); - __instance.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List(); + roleMenu.QuotaTabSelectables.Clear(); + roleMenu.roleChances.Clear(); + roleMenu.advancedSettingChildren.Clear(); - var maskBg = __instance.scrollBar.transform.FindChild("MaskBg"); - var hitbox = __instance.scrollBar.transform.FindChild("Hitbox"); - var dividerImage = __instance.transform.FindChild("HeaderButtons/DividerImage"); + var maskBg = roleMenu.scrollBar.transform.FindChild("MaskBg"); + var hitbox = roleMenu.scrollBar.transform.FindChild("Hitbox"); + var dividerImage = roleMenu.transform.FindChild("HeaderButtons/DividerImage"); - if (GameSettingMenuPatches.SelectedModIdx == 0) + if (MenuState.Instance.CurrentModIdx == 0) { - __instance.AllButton.transform.parent.gameObject.SetActive(true); - __instance.AllButton.gameObject.SetActive(true); - __instance.scrollBar.transform.localPosition = new Vector3(-1.4957f, 0.657f, -4); + roleMenu.AllButton.transform.parent.gameObject.SetActive(true); + roleMenu.AllButton.gameObject.SetActive(true); + roleMenu.scrollBar.transform.localPosition = new Vector3(-1.4957f, 0.657f, -4); maskBg.localPosition = new Vector3(1.5353f, -.5734f, -.1f); maskBg.localScale = new Vector3(6.6811f, 3.3563f, 0.5598f); hitbox.localPosition = new Vector3(0.3297f, -.2333f, 4f); hitbox.localScale = new Vector3(1, 1, 1); dividerImage.gameObject.SetActive(true); - - var num = 0.662f; - - var list = CustomRoleManager.AllRoles.Where( - r => !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Crewmate && r.Role != RoleTypes.Crewmate && - r.Role != RoleTypes.CrewmateGhost).ToList(); - var list2 = CustomRoleManager.AllRoles.Where( - r => !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Impostor && r.Role != RoleTypes.Impostor && - r.Role != RoleTypes.ImpostorGhost).ToList(); - - if (__instance.roleTabs == null || __instance.roleTabs.Count == 0) - { - var num2 = -1.928f; - __instance.roleTabs = new(); - __instance.roleTabs.Add(__instance.AllButton); - foreach (var t in list) - { - __instance.AddRoleTab(t, ref num2); - } - - foreach (var t in list2) - { - __instance.AddRoleTab(t, ref num2); - } - } - - var categoryHeaderEditRole = Object.Instantiate( - __instance.categoryHeaderEditRoleOrigin, - Vector3.zero, - Quaternion.identity, - __instance.RoleChancesSettings.transform); - categoryHeaderEditRole.SetHeader(StringNames.CrewmateRolesHeader, 20); - categoryHeaderEditRole.transform.localPosition = new Vector3(4.986f, num, -2f); - num -= 0.522f; - var num3 = 0; - foreach (var t in list) - { - __instance.CreateQuotaOption(t, ref num, num3); - num3++; - } - - num -= 0.22f; - var categoryHeaderEditRole2 = Object.Instantiate( - __instance.categoryHeaderEditRoleOrigin, - Vector3.zero, - Quaternion.identity, - __instance.RoleChancesSettings.transform); - categoryHeaderEditRole2.SetHeader(StringNames.ImpostorRolesHeader, 20); - categoryHeaderEditRole2.transform.localPosition = new Vector3(4.986f, num, -2f); - num -= 0.522f; - foreach (var t in list2) - { - __instance.CreateQuotaOption(t, ref num, num3); - num3++; - } - - return false; - } - - ScrollerNum = 0.522f; - - __instance.AllButton.transform.parent.gameObject.SetActive(false); - __instance.AllButton.gameObject.SetActive(false); - __instance.scrollBar.transform.localPosition = new Vector3(-1.4957f, 1.5261f, -4); - maskBg.localPosition = new Vector3(1.5353f, -1.0607f, -.1f); - maskBg.localScale = new Vector3(6.6811f, 4.1563f, 0.5598f); - hitbox.localPosition = new Vector3(0.3297f, -.6333f, 4f); - hitbox.localScale = new Vector3(1, 1.2f, 1); - dividerImage.gameObject.SetActive(false); - - var num4 = 0; - - var roleGroups = GameSettingMenuPatches.SelectedMod?.InternalRoles.Values.OfType() - .ToLookup(x => x.RoleOptionsGroup); - - if (roleGroups is null) - { - return false; - } - - // sort the groups by priority - var sortedRoleGroups = roleGroups - .OrderBy(x => x.Key.Priority) - .ThenBy(x => x.Key.Name); - - var quotaThing = __instance.categoryHeaderEditRoleOrigin.transform.FindChild("QuotaHeader"); - var usingNewQuota = false; - var template = __instance.transform.parent.parent.GetComponent().GameSettingsTab.categoryHeaderOrigin; - - foreach (var grouping in sortedRoleGroups) - { - if (!grouping.Any() || grouping.All(x=> x.Configuration.HideSettings || !x.VisibleInSettings())) - { - continue; - } - - var group = grouping.Key; - - RoleGroupHidden.TryAdd(group, false); - - var name = group.Name switch - { - "Crewmate" => StringNames.CrewmateRolesHeader, - "Impostor" => StringNames.ImpostorRolesHeader, - _ => CustomStringName.CreateAndRegister(group.Name), - }; - - var categoryHeaderMasked = Object.Instantiate( - template, - Vector3.zero, - Quaternion.identity, - __instance.RoleChancesSettings.transform); - - categoryHeaderMasked.SetHeader(name, 20); - - var quotaInst = Object.Instantiate(quotaThing, categoryHeaderMasked.transform); - quotaInst.transform.localScale = new Vector3(1.3f, 1.3f, 1.3f); - quotaInst.transform.localPosition = new Vector3(0.7f, -0.82f, 0); - - var chanceText = quotaInst.transform.FindChild("Chance Text"); - chanceText.transform.localPosition = new Vector3(4.3f, 0.0993f, 0); - - var countText = quotaInst.transform.FindChild("# Text"); - countText.transform.localPosition = new Vector3(1.9f, 0.0993f, 0f); - - if (!usingNewQuota) - { - var blankLabel = quotaInst.transform.FindChild("BlankLabel").gameObject; - var chanceLabel = quotaInst.transform.FindChild("Chance Label").gameObject; - var countLabel = quotaInst.transform.FindChild("# Label").gameObject; - blankLabel.DeepDestroy(false); - chanceLabel.DeepDestroy(false); - countLabel.DeepDestroy(false); - usingNewQuota = true; - quotaThing = quotaInst; - } - - categoryHeaderMasked.Background.sprite = MiraAssets.CategoryHeader.LoadAsset(); - categoryHeaderMasked.Background.sprite.texture.filterMode = FilterMode.Bilinear; - categoryHeaderMasked.Background.sprite.texture.wrapMode = TextureWrapMode.Clamp; - - categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.5f, -0.1833f, 0); - - switch (name) - { - case StringNames.CrewmateRolesHeader: - categoryHeaderMasked.Title.color = Palette.CrewmateRoleHeaderTextBlue; - categoryHeaderMasked.Background.color = Palette.CrewmateRoleHeaderBlue; - break; - case StringNames.ImpostorRolesHeader: - categoryHeaderMasked.Title.color = Palette.ImpostorRoleHeaderTextRed; - categoryHeaderMasked.Background.color = Palette.ImpostorRoleHeaderRed; - break; - default: - categoryHeaderMasked.Title.color = group.Color.Equals(MiraApiPlugin.DefaultHeaderColor) ? Color.white : group.Color.FindAlternateColor(); - categoryHeaderMasked.Divider.color = group.Color; - categoryHeaderMasked.Background.color = group.Color; - break; - } - - categoryHeaderMasked.Title.fontStyle = __instance.categoryHeaderEditRoleOrigin.Title.fontStyle; - categoryHeaderMasked.Title.font = __instance.categoryHeaderEditRoleOrigin.Title.font; - categoryHeaderMasked.Title.fontMaterial = __instance.categoryHeaderEditRoleOrigin.Title.fontMaterial; - - categoryHeaderMasked.Divider.color = categoryHeaderMasked.Background.color; - categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.55f, -0.1833f, 0); - categoryHeaderMasked.Background.size = new Vector2(categoryHeaderMasked.Background.size.x + 1.5f, categoryHeaderMasked.Background.size.y); - - categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f; - categoryHeaderMasked.transform.localPosition = new Vector3(-0.44f, ScrollerNum, -2f); - categoryHeaderMasked.gameObject.SetActive(true); - quotaInst.gameObject.SetActive(!RoleGroupHidden[group]); - - var label = RoleGroupHidden[group] - ? "(Click to open)" - : "(Click to close)"; - var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform); - newText.text = $"{label}"; - newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f); - newText.gameObject.GetComponent().Destroy(); - - Headers.Add(categoryHeaderMasked.gameObject); - ScrollerNum -= 0.422f; - - if (!RoleGroupHidden[group]) - { - foreach (var role in grouping) - { - if (role is not RoleBehaviour roleBehaviour) - { - continue; - } - - var option = CreateQuotaOption(__instance, roleBehaviour, num4); - if (option is not null) - { - RoleOptionSettings.Add(option); - num4++; - } - } - } - - 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)(() => - { - RolePositions[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition; - if (RoleGroupHidden.TryGetValue(group, out var groupHidden)) - { - RoleGroupHidden[group] = !groupHidden; - } - foreach (var obj in Headers) - { - if (!obj) - { - continue; - } - obj.DeepDestroy(false); - } - Headers.Clear(); - foreach (var obj in RoleOptionSettings) - { - if (!obj) - { - continue; - } - obj.gameObject.DeepDestroy(false); - } - RoleOptionSettings.Clear(); - __instance.SetQuotaTab(); - })); - headerBtn.SetButtonEnableState(true); - - if (RoleGroupHidden.TryGetValue(group, out var value) && !value) - { - ScrollerNum -= 0.4f; - } - } - - __instance.scrollBar.SetScrollBounds(__instance); - return false; - } - - private static void SetScrollBounds(this Scroller scroller, RolesSettingsMenu roleMenu) - { - if (GameSettingMenuPatches.SelectedModIdx == 0) - { - scroller.CalculateAndSetYBounds(roleMenu.roleChances.Count + 3, 1f, 6f, 0.43f); - return; - } - - scroller.SetYBoundsMax(-ScrollerNum - 2); - if (RolePositions.TryGetValue(GameSettingMenuPatches.SelectedModIdx, out var scroll)) - { - scroller.Inner.localPosition = scroll; - scroller.UpdateScrollBars(); } else { - scroller.ScrollToTop(); + roleMenu.AllButton.transform.parent.gameObject.SetActive(false); + roleMenu.AllButton.gameObject.SetActive(false); + roleMenu.scrollBar.transform.localPosition = new Vector3(-1.4957f, 1.5261f, -4); + maskBg.localPosition = new Vector3(1.5353f, -1.0607f, -.1f); + maskBg.localScale = new Vector3(6.6811f, 4.1563f, 0.5598f); + hitbox.localPosition = new Vector3(0.3297f, -.6333f, 4f); + hitbox.localScale = new Vector3(1, 1.2f, 1); + dividerImage.gameObject.SetActive(false); + } + + if (MenuState.Instance.FinishedRoleMenus.TryGetValue(MenuState.Instance.CurrentModIdx, out var finished) && finished) + { + var roleOptionSettings = roleMenu.scrollBar.Inner.GetComponentsInChildren(true); + foreach (var r in roleOptionSettings) + { + roleMenu.roleChances.Add(r); + roleMenu.QuotaTabSelectables.AddRange(new Il2CppSystem.Collections.Generic.IEnumerable(r.ControllerSelectable.Pointer)); + } + + Info($"Already created role options for {MenuState.Instance.CurrentModIdx}"); + } + else + { + if (MenuState.Instance.CurrentModIdx == 0) + { + var num = 0.662f; + + var list = CustomRoleManager.AllRoles.Where(r => + !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Crewmate && + r.Role != RoleTypes.Crewmate && + r.Role != RoleTypes.CrewmateGhost).ToList(); + var list2 = CustomRoleManager.AllRoles.Where(r => + !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Impostor && + r.Role != RoleTypes.Impostor && + r.Role != RoleTypes.ImpostorGhost).ToList(); + + if (roleMenu.roleTabs == null || roleMenu.roleTabs.Count == 0) + { + var num2 = -1.928f; + roleMenu.roleTabs = new(); + roleMenu.roleTabs.Add(roleMenu.AllButton); + foreach (var t in list) + { + roleMenu.AddRoleTab(t, ref num2); + } + + foreach (var t in list2) + { + roleMenu.AddRoleTab(t, ref num2); + } + } + + var categoryHeaderEditRole = Object.Instantiate( + roleMenu.categoryHeaderEditRoleOrigin, + Vector3.zero, + Quaternion.identity, + roleMenu.RoleChancesSettings.transform); + categoryHeaderEditRole.SetHeader(StringNames.CrewmateRolesHeader, 20); + categoryHeaderEditRole.transform.localPosition = new Vector3(4.986f, num, -2f); + num -= 0.522f; + var num3 = 0; + foreach (var t in list) + { + roleMenu.CreateQuotaOption(t, ref num, num3); + num3++; + } + + num -= 0.22f; + var categoryHeaderEditRole2 = Object.Instantiate( + roleMenu.categoryHeaderEditRoleOrigin, + Vector3.zero, + Quaternion.identity, + roleMenu.RoleChancesSettings.transform); + categoryHeaderEditRole2.SetHeader(StringNames.ImpostorRolesHeader, 20); + categoryHeaderEditRole2.transform.localPosition = new Vector3(4.986f, num, -2f); + num -= 0.522f; + foreach (var t in list2) + { + roleMenu.CreateQuotaOption(t, ref num, num3); + num3++; + } + + Info($"Created {num3} role options for the default game."); + } + else + { + ScrollerNum = 0.522f; + + var num4 = 0; + + var roleGroups = MenuState.Instance.CurrentMod.InternalRoles.Values.OfType() + .ToLookup(x => x.RoleOptionsGroup); + + if (roleGroups.Count == 0) + { + Error("No role groups found for the selected mod."); + } + else + { + // sort the groups by priority + var sortedRoleGroups = roleGroups + .OrderBy(x => x.Key.Priority) + .ThenBy(x => x.Key.Name); + + var quotaThing = roleMenu.categoryHeaderEditRoleOrigin.transform.FindChild("QuotaHeader"); + var usingNewQuota = false; + var template = roleMenu.transform.parent.parent.GetComponent().GameSettingsTab + .categoryHeaderOrigin; + + foreach (var grouping in sortedRoleGroups) + { + if (!grouping.Any() || + grouping.All(x => x.Configuration.HideSettings || !x.VisibleInSettings())) + { + continue; + } + + var group = grouping.Key; + + RoleGroupHidden.TryAdd(group, false); + + var name = group.Name switch + { + "Crewmate" => StringNames.CrewmateRolesHeader, + "Impostor" => StringNames.ImpostorRolesHeader, + _ => CustomStringName.CreateAndRegister(group.Name), + }; + + var categoryHeaderMasked = Object.Instantiate( + template, + Vector3.zero, + Quaternion.identity, + roleMenu.RoleChancesSettings.transform); + + categoryHeaderMasked.SetHeader(name, 20); + + var quotaInst = Object.Instantiate(quotaThing, categoryHeaderMasked.transform); + quotaInst.transform.localScale = new Vector3(1.3f, 1.3f, 1.3f); + quotaInst.transform.localPosition = new Vector3(0.7f, -0.82f, 0); + + var chanceText = quotaInst.transform.FindChild("Chance Text"); + chanceText.transform.localPosition = new Vector3(4.3f, 0.0993f, 0); + + var countText = quotaInst.transform.FindChild("# Text"); + countText.transform.localPosition = new Vector3(1.9f, 0.0993f, 0f); + + if (!usingNewQuota) + { + var blankLabel = quotaInst.transform.FindChild("BlankLabel").gameObject; + var chanceLabel = quotaInst.transform.FindChild("Chance Label").gameObject; + var countLabel = quotaInst.transform.FindChild("# Label").gameObject; + blankLabel.Destroy(); + chanceLabel.Destroy(); + countLabel.Destroy(); + usingNewQuota = true; + quotaThing = quotaInst; + } + + categoryHeaderMasked.Background.sprite = MiraAssets.CategoryHeader.LoadAsset(); + categoryHeaderMasked.Background.sprite.texture.filterMode = FilterMode.Bilinear; + categoryHeaderMasked.Background.sprite.texture.wrapMode = TextureWrapMode.Clamp; + + categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.5f, -0.1833f, 0); + + switch (name) + { + case StringNames.CrewmateRolesHeader: + categoryHeaderMasked.Title.color = Palette.CrewmateRoleHeaderTextBlue; + categoryHeaderMasked.Background.color = Palette.CrewmateRoleHeaderBlue; + break; + case StringNames.ImpostorRolesHeader: + categoryHeaderMasked.Title.color = Palette.ImpostorRoleHeaderTextRed; + categoryHeaderMasked.Background.color = Palette.ImpostorRoleHeaderRed; + break; + default: + categoryHeaderMasked.Title.color = group.Color.Equals(MiraApiPlugin.DefaultHeaderColor) + ? Color.white + : group.Color.FindAlternateColor(); + categoryHeaderMasked.Divider.color = group.Color; + categoryHeaderMasked.Background.color = group.Color; + break; + } + + categoryHeaderMasked.Title.fontStyle = roleMenu.categoryHeaderEditRoleOrigin.Title.fontStyle; + categoryHeaderMasked.Title.font = roleMenu.categoryHeaderEditRoleOrigin.Title.font; + categoryHeaderMasked.Title.fontMaterial = + roleMenu.categoryHeaderEditRoleOrigin.Title.fontMaterial; + + categoryHeaderMasked.Divider.color = categoryHeaderMasked.Background.color; + categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.55f, -0.1833f, 0); + categoryHeaderMasked.Background.size = new Vector2( + categoryHeaderMasked.Background.size.x + 1.5f, + categoryHeaderMasked.Background.size.y); + + categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f; + categoryHeaderMasked.transform.localPosition = new Vector3(-0.44f, ScrollerNum, -2f); + categoryHeaderMasked.gameObject.SetActive(true); + quotaInst.gameObject.SetActive(!RoleGroupHidden[group]); + + var label = RoleGroupHidden[group] + ? "(Click to open)" + : "(Click to close)"; + var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform); + newText.text = $"{label}"; + newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f); + newText.gameObject.GetComponent().Destroy(); + + ScrollerNum -= 0.422f; + + if (!RoleGroupHidden[group]) + { + yield return + grouping.CoLoopWithBudget(role => + { + if (role is not RoleBehaviour roleBehaviour) + { + return; + } + + var option = CreateQuotaOption(roleMenu, roleBehaviour, num4); + if (option is not null) + { + num4++; + } + }); + } + + 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 = roleMenu.BackButton.GetComponent().ClickSound; + headerBtn.OnMouseOver = new UnityEvent(); + headerBtn.OnMouseOut = new UnityEvent(); + headerBtn.OnClick.AddListener( + (UnityAction)(() => + { + if (RoleGroupHidden.TryGetValue(group, out var groupHidden)) + { + RoleGroupHidden[group] = !groupHidden; + } + + MenuState.Instance.FinishedRoleMenus[MenuState.Instance.CurrentModIdx] = false; + + foreach (var child in roleMenu.RoleChancesSettings.transform) + { + var transform = child.Cast(); + Object.Destroy(transform.gameObject); + } + + roleMenu.OpenChancesTab(); + })); + headerBtn.SetButtonEnableState(true); + + if (RoleGroupHidden.TryGetValue(group, out var value) && !value) + { + ScrollerNum -= 0.4f; + } + } + } + } + MenuState.Instance.FinishedRoleMenus[MenuState.Instance.CurrentModIdx] = true; + } + + roleMenu.SetScrollBounds(); + _quotaTabCoroutine = null; + } + + private static Coroutine? _quotaTabCoroutine; + + [HarmonyPrefix] + [HarmonyPatch(nameof(RolesSettingsMenu.OpenChancesTab))] + public static bool OpenChancesTabPatch(RolesSettingsMenu __instance, bool controllerSelected) + { + __instance.selectedRoleTab = 0; + __instance.RoleChancesSettings.SetActive(true); + __instance.AdvancedRolesSettings.SetActive(false); + __instance.ControllerSelectable.Clear(); + if (_quotaTabCoroutine != null) + { + GameSettingMenu.Instance.StopCoroutine(_quotaTabCoroutine); + } + + __instance.QuotaTabSelectables ??= new(); + __instance.roleChances ??= new(); + __instance.advancedSettingChildren ??= new(); + + _quotaTabCoroutine = GameSettingMenu.Instance.StartCoroutine(CoOpenChancesTab().WrapToIl2Cpp()); + return false; + + IEnumerator CoOpenChancesTab() + { + yield return CoQuotaTabPatch(__instance); + if (controllerSelected) + { + __instance.ControllerSelectable.AddRange( + new Il2CppSystem.Collections.Generic.IEnumerable( + __instance.QuotaTabSelectables.Pointer)); + } + + if (controllerSelected) + { + ControllerManager.Instance.CurrentUiState.SelectableUiElements = __instance.ControllerSelectable; + if (__instance.ControllerSelectable.Count != 0) + { + ControllerManager.Instance.SetDefaultSelection(__instance.ControllerSelectable[0]); + } + } + var passiveButton = __instance.currentTabButton; + if (passiveButton) + { + passiveButton.SelectButton(false); + } + __instance.AllButton.SelectButton(true); + __instance.currentTabButton = __instance.AllButton; } } + private static void SetScrollBounds(this RolesSettingsMenu menu, bool isRolesConfig = false) + { + var scroller = menu.scrollBar; + + if (MenuState.Instance.CurrentModIdx == 0) + { + scroller.CalculateAndSetYBounds(menu.roleChances.Count + 3, 1f, 6f, 0.43f); + return; + } + + scroller.Inner = isRolesConfig ? menu.AdvancedRolesSettings.transform : menu.RoleChancesSettings.transform; + scroller.SetYBoundsMax(-ScrollerNum - 2); + } + [HarmonyPrefix] + // TODO: turn this into a fixed update [HarmonyPatch(nameof(RolesSettingsMenu.Update))] public static bool UpdatePatch(RolesSettingsMenu __instance) { - if (GameSettingMenuPatches.SelectedModIdx == 0) return true; + if (MenuState.Instance.CurrentModIdx == 0) return true; if (CurrentRole == null || CurrentRoleOptions == null) return false; var hasImage = CurrentRole.RoleScreenshot != null; var num = hasImage ? -0.872f : -1; - foreach (var opt in CurrentRoleOptions) { if (opt.OptionBehaviour == null) continue; @@ -390,20 +426,6 @@ public static class RoleSettingMenuPatches return false; } - [HarmonyPostfix] - [HarmonyPatch(nameof(RolesSettingsMenu.OpenChancesTab))] - public static void OpenChancesTabPostfix(RolesSettingsMenu __instance) - { - if (!__instance.scrollBar) - { - return; - } - - CurrentRole = null; - CurrentRoleOptions = null; - __instance.scrollBar.SetScrollBounds(__instance); - } - private static void ValueChanged(OptionBehaviour obj) { var roleSetting = obj.Cast(); @@ -449,19 +471,17 @@ public static class RoleSettingMenuPatches { foreach (var optBehaviour in __instance.AdvancedRolesSettings.GetComponentsInChildren()) { - optBehaviour.gameObject.DeepDestroy(false); + optBehaviour.gameObject.Destroy(); } CurrentRole = role; __instance.advancedSettingChildren.Clear(); // TODO: create sub groups under the role settings. - var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups + var filteredOptions = MenuState.Instance.CurrentMod.InternalOptionGroups .Where(x => x.GroupVisible() && x.OptionableType == role.GetType()) .SelectMany(x => x.Options) - .ToList() ?? []; - - CurrentRoleOptions = filteredOptions; + .ToList(); foreach (var option in filteredOptions) { @@ -493,7 +513,7 @@ public static class RoleSettingMenuPatches newOpt.Initialize(); } - __instance.scrollBar.ScrollToTop(); + CurrentRoleOptions = filteredOptions; } private static void ChangeTab(RoleBehaviour role, RolesSettingsMenu __instance) @@ -504,8 +524,6 @@ public static class RoleSettingMenuPatches return; } - RolePositions[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition; - __instance.roleDescriptionText.text = customRole.RoleLongDescription; __instance.roleTitleText.text = role.GetRoleName(); @@ -527,12 +545,8 @@ public static class RoleSettingMenuPatches __instance.roleDescriptionText.transform.parent.localScale = new Vector3(0.0675f, 0.1494f, 0.5687f); labelBg.transform.localPosition = new Vector3(1.082f, 0.1054f, -2.5f); - __instance.roleScreenshot.sprite = Sprite.Create( - role.RoleScreenshot.texture, - new Rect(0, 0, 370, 230), - Vector2.one / 2, - 100); - __instance.roleScreenshot.drawMode = SpriteDrawMode.Sliced; + __instance.roleScreenshot.sprite = role.RoleScreenshot; + __instance.roleScreenshot.drawMode = SpriteDrawMode.Simple; } __instance.roleHeaderSprite.color = customRole.OptionsMenuColor; @@ -560,9 +574,26 @@ public static class RoleSettingMenuPatches headerBtn.OnClick.AddListener( (UnityAction)(() => { - __instance.OpenChancesTab(); + GameSettingMenu.Instance.StartCoroutine(CoReturnToRoleSettings().WrapToIl2Cpp()); })); headerBtn.SetButtonEnableState(true); + + IEnumerator CoReturnToRoleSettings() + { + // set gameobjects + __instance.RoleChancesSettings.SetActive(true); + __instance.AdvancedRolesSettings.SetActive(false); + + // wait a frame + yield return null; + + // re-enable page + yield return CoQuotaTabPatch(__instance); + + // set controller selected button + __instance.AllButton.SelectButton(true); + __instance.currentTabButton = __instance.AllButton; + } } var bg = __instance.AdvancedRolesSettings.transform.Find("Background"); @@ -587,6 +618,8 @@ public static class RoleSettingMenuPatches __instance.RoleChancesSettings.SetActive(false); __instance.AdvancedRolesSettings.SetActive(true); + __instance.SetScrollBounds(true); + __instance.scrollBar.ScrollToTop(); __instance.RefreshChildren(); } @@ -638,14 +671,13 @@ public static class RoleSettingMenuPatches rend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; } - if (GameSettingMenuPatches.SelectedMod is null || - GameSettingMenuPatches.SelectedMod.InternalOptionGroups + if (MenuState.Instance.CurrentMod.InternalOptionGroups .Exists(x => x.GroupVisible() && x.OptionableType == role.GetType() && x.Options.Any(y => y.Visible()))) { var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform); newButton.name = "ConfigButton"; newButton.transform.localPosition = new Vector3(0.4473f, -0.3f, -2f); - newButton.transform.FindChild("Text_TMP").gameObject.DeepDestroy(); + newButton.transform.FindChild("Text_TMP").gameObject.Destroy(); newButton.activeSprites.Destroy(); var btnRend = newButton.transform.FindChild("ButtonSprite").GetComponent(); @@ -671,7 +703,7 @@ public static class RoleSettingMenuPatches roleOptionSetting.ChancePlusBtn.gameObject.SetActive(false); } - if (index < GameSettingMenuPatches.SelectedMod?.InternalRoles.Count - 1) + if (index < MenuState.Instance.CurrentMod.InternalRoles.Count - 1) { ScrollerNum -= 0.43f; } diff --git a/MiraAPI/Patches/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs index 3c851b0e..77b83c21 100644 --- a/MiraAPI/Patches/PlayerControlPatches.cs +++ b/MiraAPI/Patches/PlayerControlPatches.cs @@ -8,7 +8,6 @@ using MiraAPI.Hud; using MiraAPI.Modifiers; using MiraAPI.Utilities; using MiraAPI.Voting; -using Reactor.Utilities; using Reactor.Utilities.Extensions; namespace MiraAPI.Patches; diff --git a/MiraAPI/Patches/Roles/HowToPlayPatch.cs b/MiraAPI/Patches/Roles/HowToPlayPatch.cs index ee6e5ff1..c7cc2e56 100644 --- a/MiraAPI/Patches/Roles/HowToPlayPatch.cs +++ b/MiraAPI/Patches/Roles/HowToPlayPatch.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; using AmongUs.GameOptions; using HarmonyLib; using MiraAPI.Roles; diff --git a/MiraAPI/Patches/Roles/HudManagerPatches.cs b/MiraAPI/Patches/Roles/HudManagerPatches.cs index 79c23063..3de9b716 100644 --- a/MiraAPI/Patches/Roles/HudManagerPatches.cs +++ b/MiraAPI/Patches/Roles/HudManagerPatches.cs @@ -1,9 +1,7 @@ using HarmonyLib; using InnerNet; using MiraAPI.Roles; -using MiraAPI.Utilities; using Reactor.Utilities.Extensions; -using UnityEngine; namespace MiraAPI.Patches.Roles; @@ -73,7 +71,7 @@ public static class HudManagerPatches } else if (RoleTab != null) { - RoleTab.gameObject.DeepDestroy(); + RoleTab.gameObject.Destroy(); RoleTab = null; } } diff --git a/MiraAPI/Patches/Roles/LogicRoleSelectionNormalPatch.cs b/MiraAPI/Patches/Roles/LogicRoleSelectionNormalPatch.cs deleted file mode 100644 index b0540bba..00000000 --- a/MiraAPI/Patches/Roles/LogicRoleSelectionNormalPatch.cs +++ /dev/null @@ -1,93 +0,0 @@ -using AmongUs.GameOptions; -using HarmonyLib; -using Il2CppSystem.Collections.Generic; -using System.Linq; -using MiraAPI.Roles; - -namespace MiraAPI.Patches.Roles; - -[HarmonyPatch(typeof(LogicRoleSelectionNormal))] -public static class LogicRoleSelectionNormalPatch -{ - [HarmonyPrefix] - [HarmonyPatch(nameof(LogicRoleSelectionNormal.AssignRolesForTeam))] - public static bool AssignRolesForTeam( - LogicRoleSelectionNormal __instance, - List players, - IGameOptions opts, - RoleTeamTypes team, - int teamMax, - Il2CppSystem.Nullable defaultRole) - { - int num = 0; - var source = RoleManager.Instance.AllRoles.ToArray() - .Where(role => role.TeamType == team && !RoleManager.IsGhostRole(role.Role) && - CustomRoleUtils.CanSpawnOnCurrentMode(role)); - List list = new List(); - IRoleOptionsCollection roleOptions = opts.RoleOptions; - - var assignmentData = source.Where(x => !x.IsDead).Select(role => - new RoleManager.RoleAssignmentData( - role, - roleOptions.GetNumPerGame(role.Role), - roleOptions.GetChancePerGame(role.Role))).ToList(); - var source2 = CustomRoleUtils.GetPossibleRoles(assignmentData, x => x.Chance == 100); - var guaranteedRoles = source.Where(x => source2.Contains(((ushort)x.Role, 100))); - foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) => - new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100))) - { - while (true) - { - RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData; - int count = roleAssignmentData2.Count; - roleAssignmentData2.Count = count - 1; - if (count <= 0) - { - break; - } - - list.Add(roleAssignmentData.Role.Role); - } - } - - __instance.AssignRolesFromList(players, teamMax, list, ref num); - - var list2 = source.Where(x => !x.IsDead).Select(role => - new RoleManager.RoleAssignmentData( - role, - roleOptions.GetNumPerGame(role.Role), - roleOptions.GetChancePerGame(role.Role))).ToList(); - - list.Clear(); - foreach (RoleManager.RoleAssignmentData roleAssignmentData3 in list2) - { - for (int i = 0; i < roleAssignmentData3.Count; i++) - { - if (HashRandom.Next(101) < roleAssignmentData3.Chance) - { - list.Add(roleAssignmentData3.Role.Role); - } - } - } - - __instance.AssignRolesFromList(players, teamMax, list, ref num); - var defaultRole2 = team is RoleTeamTypes.Crewmate ? RoleTypes.Crewmate : RoleTypes.Impostor; - try - { - defaultRole2 = defaultRole.Value; - } - catch - { - // Ignored - } - - while (list.Count < players.Count && list.Count + num < teamMax) - { - list.Add(defaultRole2); - } - - __instance.AssignRolesFromList(players, teamMax, list, ref num); - - return false; - } -} diff --git a/MiraAPI/Patches/Roles/RoleManagerPatches.cs b/MiraAPI/Patches/Roles/RoleManagerPatches.cs index 713ffdff..8d135955 100644 --- a/MiraAPI/Patches/Roles/RoleManagerPatches.cs +++ b/MiraAPI/Patches/Roles/RoleManagerPatches.cs @@ -1,7 +1,6 @@ using System.Linq; using AmongUs.GameOptions; using HarmonyLib; -using InnerNet; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; using MiraAPI.Modifiers; diff --git a/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs b/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs index ff57b37c..d4d80d34 100644 --- a/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs +++ b/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs @@ -2,7 +2,6 @@ using HarmonyLib; using Il2CppSystem.Collections.Generic; using MiraAPI.Roles; -using Reactor.Utilities; namespace MiraAPI.Patches.Roles; diff --git a/MiraAPI/Patches/Roles/SelectRolesPatch.cs b/MiraAPI/Patches/Roles/SelectRolesPatch.cs new file mode 100644 index 00000000..dc51f03f --- /dev/null +++ b/MiraAPI/Patches/Roles/SelectRolesPatch.cs @@ -0,0 +1,111 @@ +using System.Collections.Generic; +using AmongUs.GameOptions; +using HarmonyLib; +using System.Linq; +using InnerNet; +using MiraAPI.Roles; + +namespace MiraAPI.Patches.Roles; + +[HarmonyPatch(typeof(RoleManager))] +public static class SelectRolesPatch +{ + public static bool ApiHandlesRoleSelect = true; + [HarmonyPrefix] + [HarmonyPatch(nameof(RoleManager.SelectRoles))] + public static bool SelectRoles() + { + if (!ApiHandlesRoleSelect) + { + return true; + } + Il2CppSystem.Collections.Generic.List list = new(); + AmongUsClient.Instance.GetAllClients(list); + List list2 = list.ToArray() + .Where(c => c.Character != null && c.Character.Data != null && !c.Character.Data.Disconnected && + !c.Character.Data.IsDead).OrderBy(c => c.Id).Select(c => c.Character.Data) + .ToList(); + + foreach (NetworkedPlayerInfo networkedPlayerInfo in GameData.Instance.AllPlayers) + { + if (networkedPlayerInfo.Object != null && networkedPlayerInfo.Object.isDummy) + { + list2.Add(networkedPlayerInfo); + } + } + IGameOptions currentGameOptions = GameOptionsManager.Instance.CurrentGameOptions; + int adjustedNumImpostors = GameOptionsManager.Instance.CurrentGameOptions.GetAdjustedNumImpostors(list2.Count); + AssignRolesForTeam(list2, currentGameOptions, RoleTeamTypes.Impostor, adjustedNumImpostors, RoleTypes.Impostor); + AssignRolesForTeam(list2, currentGameOptions, RoleTeamTypes.Crewmate, int.MaxValue, RoleTypes.Crewmate); + return false; + } + + public static void AssignRolesForTeam( + List players, + IGameOptions opts, + RoleTeamTypes team, + int teamMax, + RoleTypes defaultRole) + { + int num = 0; + var source = RoleManager.Instance.AllRoles.ToArray() + .Where(role => role.TeamType == team && !RoleManager.IsGhostRole(role.Role) && + CustomRoleUtils.CanSpawnOnCurrentMode(role)); + List list = new List(); + IRoleOptionsCollection roleOptions = opts.RoleOptions; + + // Assign guaranteed roles first, just like the vanilla selector. This is + // important because the list of players is shared by both team passes. + foreach (var role in source.Where(x => roleOptions.GetChancePerGame(x.Role) == 100) + .Select(role => role.Role)) + { + for (var i = 0; i < roleOptions.GetNumPerGame(role); i++) + { + list.Add(role); + } + } + + AssignRolesFromList(players, teamMax, list, ref num); + + // A 100% role was already assigned above. Including it here can consume + // another player and, more importantly, leaves the fallback count wrong. + list.Clear(); + foreach (var role in source.Where(x => + roleOptions.GetChancePerGame(x.Role) > 0 && + roleOptions.GetChancePerGame(x.Role) < 100) + .Select(role => role.Role)) + { + for (var i = 0; i < roleOptions.GetNumPerGame(role); i++) + { + if (HashRandom.Next(101) < roleOptions.GetChancePerGame(role)) + { + list.Add(role); + } + } + } + + AssignRolesFromList(players, teamMax, list, ref num); + + // Assign the remaining players up to the requested team limit. The + // vanilla list has been consumed by AssignRolesFromList at this point, + // so checking list.Count can leave players unassigned. + while (players.Count > 0 && num < teamMax) + { + list.Add(defaultRole); + AssignRolesFromList(players, teamMax, list, ref num); + } + } + private static void AssignRolesFromList(List players, int teamMax, List roleList, ref int rolesAssigned) + { + while (roleList.Count > 0 && players.Count > 0 && rolesAssigned < teamMax) + { + int index = HashRandom.FastNext(roleList.Count); + RoleTypes roleType = roleList[index]; + roleList.RemoveAt(index); + int index2 = HashRandom.FastNext(players.Count); + players[index2].Object.RpcSetRole(roleType, false); + players.RemoveAt(index2); + rolesAssigned++; + } + } +} diff --git a/MiraAPI/Patches/ServerDropdownPatch.cs b/MiraAPI/Patches/ServerDropdownPatch.cs index 6ff7e680..7749c9ba 100644 --- a/MiraAPI/Patches/ServerDropdownPatch.cs +++ b/MiraAPI/Patches/ServerDropdownPatch.cs @@ -1,6 +1,5 @@ using System.Linq; using HarmonyLib; -using Reactor.Utilities; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; diff --git a/MiraAPI/Patches/TmpAwakePatch.cs b/MiraAPI/Patches/TmpAwakePatch.cs index 2d43d233..9405e014 100644 --- a/MiraAPI/Patches/TmpAwakePatch.cs +++ b/MiraAPI/Patches/TmpAwakePatch.cs @@ -1,4 +1,3 @@ -using System.Linq; using HarmonyLib; using MiraAPI.Utilities.Assets; using TMPro; diff --git a/MiraAPI/Patches/Voting/DummyBehaviourPatch.cs b/MiraAPI/Patches/Voting/DummyBehaviourPatch.cs index 9e5c8a46..1d8c1e99 100644 --- a/MiraAPI/Patches/Voting/DummyBehaviourPatch.cs +++ b/MiraAPI/Patches/Voting/DummyBehaviourPatch.cs @@ -7,7 +7,6 @@ using MiraAPI.Events; using MiraAPI.Events.Vanilla.Meeting.Voting; using MiraAPI.Utilities; using MiraAPI.Voting; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using UnityEngine; diff --git a/MiraAPI/PluginLoading/IMiraPlugin.cs b/MiraAPI/PluginLoading/IMiraPlugin.cs index f1ef7932..a29dd4ca 100644 --- a/MiraAPI/PluginLoading/IMiraPlugin.cs +++ b/MiraAPI/PluginLoading/IMiraPlugin.cs @@ -1,4 +1,5 @@ -using BepInEx.Configuration; +using System.Linq; +using BepInEx.Configuration; namespace MiraAPI.PluginLoading; @@ -7,11 +8,25 @@ namespace MiraAPI.PluginLoading; /// public interface IMiraPlugin { + /// + /// Gets or sets a value indicating whether to display the plugin in the options menu. + /// + public virtual bool DisplayOnOptionsMenu => true; + /// /// Gets the name to display on the options menu. /// string OptionsTitleText { get; } + /// + /// Gets the abbreviated name to display for other mods to pick up. + /// + /// The text for the plugin. + public virtual string GetAbbreviatedModName() + { + return new(OptionsTitleText.Where(c => !char.IsLower(c) && !char.IsWhiteSpace(c)).ToArray()); + } + /// /// Gets the name for the first custom category in the game options menu, if any. /// diff --git a/MiraAPI/PluginLoading/MiraPluginInfo.cs b/MiraAPI/PluginLoading/MiraPluginInfo.cs index 0df7cb5e..db030b01 100644 --- a/MiraAPI/PluginLoading/MiraPluginInfo.cs +++ b/MiraAPI/PluginLoading/MiraPluginInfo.cs @@ -5,7 +5,6 @@ using BepInEx.Configuration; using MiraAPI.GameModes; using MiraAPI.GameOptions; using MiraAPI.Hud; -using MiraAPI.LocalSettings; using MiraAPI.Modifiers; using MiraAPI.Presets; diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 32069e48..21b98686 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -17,6 +17,7 @@ using MiraAPI.Keybinds; using MiraAPI.LocalSettings; using MiraAPI.LocalSettings.Attributes; using MiraAPI.Modifiers; +using MiraAPI.Patches.Options; using MiraAPI.Presets; using MiraAPI.Roles; using MiraAPI.Utilities; @@ -33,6 +34,7 @@ public sealed class MiraPluginManager private readonly Dictionary _registeredPlugins = []; internal MiraPluginInfo[] RegisteredPlugins { get; private set; } = null!; + internal MiraPluginInfo[] RegisteredPluginsWithOptions { get; private set; } = null!; internal Dictionary> QueuedRoleRegistrations { get; } = []; internal static MiraPluginManager Instance { get; private set; } = new(); @@ -138,9 +140,12 @@ public sealed class MiraPluginManager { // Save all buttons into a read-only collection for easy access CustomButtonManager.Buttons = new ReadOnlyCollection(CustomButtonManager.CustomButtons); + CustomButtonManager.EventTypes = new ReadOnlyDictionary(CustomButtonManager.ButtonEventTypes); + CustomButtonManager.CancelledEventTypes = new ReadOnlyDictionary(CustomButtonManager.ButtonCancelledEventTypes); // Cache all the registered plugins into an array for easy access RegisteredPlugins = [.. _registeredPlugins.Values]; + RegisteredPluginsWithOptions = [.. RegisteredPlugins.Where(m => m.MiraPlugin.DisplayOnOptionsMenu)]; ModifierManager.Modifiers = new ReadOnlyCollection(ModifierManager.InternalModifiers); }; diff --git a/MiraAPI/Presets/PresetManager.cs b/MiraAPI/Presets/PresetManager.cs index bec1216d..f753db54 100644 --- a/MiraAPI/Presets/PresetManager.cs +++ b/MiraAPI/Presets/PresetManager.cs @@ -4,8 +4,7 @@ using System.Linq; using BepInEx.Configuration; using MiraAPI.PluginLoading; using MiraAPI.Roles; -using MiraAPI.Utilities; -using Reactor.Utilities; +using Reactor.Utilities.Extensions; using UnityEngine; namespace MiraAPI.Presets; @@ -62,7 +61,7 @@ public static class PresetManager { if (btn != null) { - btn.DeepDestroy(); + btn.Destroy(); } } diff --git a/MiraAPI/Presets/SavePresetPopup.cs b/MiraAPI/Presets/SavePresetPopup.cs index 23ce48bd..87959e70 100644 --- a/MiraAPI/Presets/SavePresetPopup.cs +++ b/MiraAPI/Presets/SavePresetPopup.cs @@ -26,7 +26,7 @@ public class SavePresetPopup(nint cppPtr) : Minigame(cppPtr) // Cleanup holder object public void OnDestroy() { - transform.parent.gameObject.DeepDestroy(); + transform.parent.gameObject.Destroy(); } public override void Close() diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index 9e6540fa..0b2be262 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -11,7 +11,6 @@ using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; @@ -142,7 +141,18 @@ public static class CustomRoleManager roleBehaviour.CanVent = customRole.Configuration.CanUseVent || customRole.Configuration.GetsVentData; roleBehaviour.DefaultGhostRole = customRole.Configuration.GhostRole; roleBehaviour.MaxCount = customRole.Configuration.MaxRoleCount; - roleBehaviour.RoleScreenshot = customRole.Configuration.OptionsScreenshot?.LoadAsset(); + if (customRole.Configuration.OptionsScreenshot != null) + { + roleBehaviour.RoleScreenshot = customRole.Configuration.OptionsScreenshot.LoadAsset(); + } + else + { + roleBehaviour.RoleScreenshot = Sprite.Create( + null, + new Rect(0, 0, 370, 230), + Vector2.one / 2, + 100); + } _emptySettings ??= new(0); _emptyKillAnimations ??= new(0); diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index 1915949e..c0c97beb 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -4,8 +4,6 @@ using BepInEx.Configuration; using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.PluginLoading; -using Reactor.Utilities; -using TMPro; using UnityEngine; namespace MiraAPI.Roles; diff --git a/MiraAPI/Utilities/Assets/LoadableAddressableAsset.cs b/MiraAPI/Utilities/Assets/LoadableAddressableAsset.cs index b9e8c55f..144f6087 100644 --- a/MiraAPI/Utilities/Assets/LoadableAddressableAsset.cs +++ b/MiraAPI/Utilities/Assets/LoadableAddressableAsset.cs @@ -1,5 +1,4 @@ using System; -using Reactor.Utilities; using UnityEngine.AddressableAssets; namespace MiraAPI.Utilities.Assets; diff --git a/MiraAPI/Utilities/Assets/LoadableAddressableAssets.cs b/MiraAPI/Utilities/Assets/LoadableAddressableAssets.cs index 9cabf83a..c70100a9 100644 --- a/MiraAPI/Utilities/Assets/LoadableAddressableAssets.cs +++ b/MiraAPI/Utilities/Assets/LoadableAddressableAssets.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using Reactor.Utilities; using UnityEngine.AddressableAssets; namespace MiraAPI.Utilities.Assets; diff --git a/MiraAPI/Utilities/Assets/LoadableAsset.cs b/MiraAPI/Utilities/Assets/LoadableAsset.cs index 58c326c3..f20bb053 100644 --- a/MiraAPI/Utilities/Assets/LoadableAsset.cs +++ b/MiraAPI/Utilities/Assets/LoadableAsset.cs @@ -1,4 +1,6 @@ -namespace MiraAPI.Utilities.Assets; +using Reactor.Utilities.Extensions; + +namespace MiraAPI.Utilities.Assets; /// /// An abstract class that provides a simple pattern for loading assets. @@ -24,4 +26,20 @@ public abstract class LoadableAsset where T : UnityEngine.Object /// /// The to get the asset from. public static implicit operator T(LoadableAsset loadable) => loadable.LoadAsset(); + + /// + /// Unloads an asset. + /// + /// True if the asset was unloaded, false otherwise. + public virtual bool UnloadAsset() + { + if (LoadedAsset == null || !LoadedAsset) + { + return false; + } + + LoadedAsset.DestroyImmediate(); + LoadedAsset = null; + return true; + } } diff --git a/MiraAPI/Utilities/Assets/LoadableBundleSubAsset.cs b/MiraAPI/Utilities/Assets/LoadableBundleSubAsset.cs new file mode 100644 index 00000000..ef7aea33 --- /dev/null +++ b/MiraAPI/Utilities/Assets/LoadableBundleSubAsset.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using UnityEngine; + +namespace MiraAPI.Utilities.Assets; + +/// +/// A utility class for loading multiple assets from an . +/// +/// The name of the asset. +/// The that contains the asset. +public class LoadableBundleSubAsset(string name, LoadableBundleSubAssetHolder assetHolder) : LoadableAsset +{ + /// + /// Loads the asset from the . + /// + /// The asset. + /// The asset did not load properly. + public override Sprite LoadAsset() + { + if (LoadedAsset != null) + { + return LoadedAsset; + } + + assetHolder.TryInit(); + + var loadedAsset = assetHolder.SubSprites.FirstOrDefault(x => x.name == name); + + if (loadedAsset == null) + { + throw new InvalidOperationException($"INVALID ASSETS: {name}"); + } + LoadedAsset = loadedAsset; + return LoadedAsset; + } +} diff --git a/MiraAPI/Utilities/Assets/LoadableBundleSubAssetHolder.cs b/MiraAPI/Utilities/Assets/LoadableBundleSubAssetHolder.cs new file mode 100644 index 00000000..3b84d8d5 --- /dev/null +++ b/MiraAPI/Utilities/Assets/LoadableBundleSubAssetHolder.cs @@ -0,0 +1,86 @@ +using System; +using System.Linq; +using HarmonyLib; +using Il2CppInterop.Runtime; +using Reactor.Utilities.Extensions; +using UnityEngine; + +namespace MiraAPI.Utilities.Assets; + +/// +/// A utility class for loading multiple assets from an . +/// +public class LoadableBundleSubAssetHolder +{ + /// + /// Initializes a new instance of the class. + /// + /// The name of the assets to pull from. + /// The that contains the assets. + public LoadableBundleSubAssetHolder(string[] names, AssetBundle bundle) + { + Bundle = bundle; + SpriteNames = names; + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the asset. + /// The that contains the assets. + public LoadableBundleSubAssetHolder(string name, AssetBundle bundle) + { + Bundle = bundle; + SpriteNames = [name]; + } + internal string[] SpriteNames = []; + internal AssetBundle Bundle; + public Sprite[] SubSprites = []; + + public void TryInit() + { + if (SubSprites.Length == 0) + { + var newSprites = Array.Empty(); + foreach (var name in SpriteNames) + { + var loadedAssets = Bundle.LoadAssetWithSubAssets(name, Il2CppType.From(typeof(Sprite))).ToArray(); + + if (loadedAssets == null) + { + throw new InvalidOperationException($"INVALID ASSETS: {name}"); + } + foreach (var obj in loadedAssets) + { + var img = obj.TryCast(); + if (img != null) + { + img.DontDestroy().DontUnload(); + newSprites = newSprites.AddToArray(img); + } + } + } + + SubSprites = newSprites; + } + } + + /// + /// Unloads an asset. + /// + /// True if the asset was unloaded, false otherwise. + public bool UnloadAsset() + { + if (SubSprites.Length == 0) + { + return false; + } + + foreach (var sprite in SubSprites) + { + sprite.DestroyImmediate(); + } + SubSprites = []; + return true; + } +} diff --git a/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs b/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs index cdb4315a..6160a01f 100644 --- a/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs +++ b/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs @@ -40,6 +40,9 @@ public static class TmpSpriteUtils name = assetName + " Material", }; material.SetTexture(ShaderUtilities.ID_MainTex, sourceTexture); + // TODO: Using these values, the icons will NOT clip through chatboxes. However, this breaks quite literally every other tmp text that isn't masked. Please fix this if a good solution is found. + /*material.SetFloat(ShaderUtilities.ID_StencilComp, 4); + material.SetFloat(ShaderUtilities.ID_StencilID, 1);*/ material.SetFloat(ShaderUtilities.ID_StencilComp, 0); material.SetFloat(ShaderUtilities.ID_StencilID, 0); material.SetFloat(ShaderUtilities.ID_StencilOp, 0); @@ -68,8 +71,8 @@ public static class TmpSpriteUtils { name = spriteName, hashCode = TMP_TextUtilities.GetSimpleHashCode(spriteName), - x = 0, - y = 0, + x = rect.x, + y = rect.y, width = rect.width, height = rect.height, xOffset = -(rect.width / 3) + (scale - 1) * (rect.width / 2), diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 9f3a098c..a1c2b5ee 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; @@ -8,10 +9,8 @@ using System.Text.RegularExpressions; using HarmonyLib; using MiraAPI.GameOptions; using MiraAPI.Networking; -using MiraAPI.Patches.Menu; using MiraAPI.Roles; using MiraAPI.Voting; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using Rewired; using Rewired.Data; @@ -56,7 +55,7 @@ public static class Extensions bool isComplete; if (self.amClosing == Minigame.CloseState.Closing) { - self.gameObject.DeepDestroy(); + self.gameObject.Destroy(); return; } if (self.CloseSound && Constants.ShouldPlaySfx()) @@ -229,13 +228,44 @@ public static class Extensions /// Whether to run the garbage collector immediately. public static void DeepDestroy(this GameObject obj, bool clearGc = true) { - if (MainMenuManagerPatches.NeedsDeepDestroy) + obj.Destroy(); + } + + /// + /// Runs a given action in a loop with a frame budget based on the current target frame rate. Once the budget is hit, the loop continues on the next frame. + /// + /// The collection to iterate over. + /// The action to perform on each item. + /// The type of the collection. + /// Coroutine. + public static IEnumerator CoLoopWithBudget(this IEnumerable collection, Action action) + { + var fps = Application.targetFrameRate > 0 ? Application.targetFrameRate : 60; + long budget = 1 / (fps * 2); // default is half of current frame time. + yield return collection.CoLoopWithBudget(budget, action); + } + + /// + /// Runs a given action in a loop with a given frame budget. Once the budget is hit, the loop continues on the next frame. + /// + /// The collection to iterate over. + /// The # of milliseconds to spend in the loop. + /// The action to perform on each item. + /// The type of the collection. + /// Coroutine. + public static IEnumerator CoLoopWithBudget(this IEnumerable collection, long frameBudget, Action action) + { + var timer = new Stopwatch(); + timer.Start(); + foreach (var item in collection) { - Coroutines.Start(Nuke(obj, clearGc)); - } - else - { - obj?.Destroy(); + action(item); + + if (timer.ElapsedMilliseconds > frameBudget) + { + timer.Restart(); + yield return null; + } } } @@ -324,10 +354,6 @@ public static class Extensions UnityEngine.Object.Destroy(go); yield return null; - if (clearGc) - { - ClearGarbageCollector(); - } } /// @@ -336,6 +362,7 @@ public static class Extensions public static void ClearGarbageCollector() { Resources.UnloadUnusedAssets(); + Il2CppSystem.GC.Collect(); GC.Collect(); } diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index c089e40d..9f66817d 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -2,12 +2,10 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using AmongUs.GameOptions; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.Roles; using MiraAPI.Utilities.Assets; -using Reactor.Utilities; using Rewired; using TMPro; using UnityEngine; diff --git a/MiraAPI/Utilities/ShaderID.cs b/MiraAPI/Utilities/ShaderID.cs index 3ef92e6e..3f2855ac 100644 --- a/MiraAPI/Utilities/ShaderID.cs +++ b/MiraAPI/Utilities/ShaderID.cs @@ -24,80 +24,80 @@ public static class ShaderID } // For player shader - public static readonly int BodyColor = Shader.PropertyToID("_BodyColor"); - public static readonly int BackColor = Shader.PropertyToID("_BackColor"); - public static readonly int VisorColor = Shader.PropertyToID("_VisorColor"); + public static readonly int BodyColor = Get("_BodyColor"); + public static readonly int BackColor = Get("_BackColor"); + public static readonly int VisorColor = Get("_VisorColor"); // Main texture, very obviously used in any shader with a texture - public static readonly int MainTex = Shader.PropertyToID("_MainTex"); + public static readonly int MainTex = Get("_MainTex"); // Any masking stuff, like MeetingHud bubbles - public static readonly int Mask = Shader.PropertyToID("_Mask"); - public static readonly int MaskComp = Shader.PropertyToID("_MaskComp"); - public static readonly int MaskLayer = Shader.PropertyToID("_MaskLayer"); - public static readonly int Stencil = Shader.PropertyToID("_Stencil"); - public static readonly int StencilComp = Shader.PropertyToID("_StencilComp"); + public static readonly int Mask = Get("_Mask"); + public static readonly int MaskComp = Get("_MaskComp"); + public static readonly int MaskLayer = Get("_MaskLayer"); + public static readonly int Stencil = Get("_Stencil"); + public static readonly int StencilComp = Get("_StencilComp"); // Used in many tasks - public static readonly int Color = Shader.PropertyToID("_Color"); + public static readonly int Color = Get("_Color"); // Has 2 uses in the game, provided for convenience - public static readonly int Opacity = Shader.PropertyToID("_Opacity"); + public static readonly int Opacity = Get("_Opacity"); // Used once in CooldownHelpers, once in PowerBarMining - public static readonly int NormalizedUvs = Shader.PropertyToID("_NormalizedUvs"); + public static readonly int NormalizedUvs = Get("_NormalizedUvs"); // Used in many consoles - public static readonly int Outline = Shader.PropertyToID("_Outline"); - public static readonly int OutlineColor = Shader.PropertyToID("_OutlineColor"); + public static readonly int Outline = Get("_Outline"); + public static readonly int OutlineColor = Get("_OutlineColor"); // Used in some consoles - public static readonly int AddColor = Shader.PropertyToID("_AddColor"); + public static readonly int AddColor = Get("_AddColor"); // Has some uses in various locations - public static readonly int Percent = Shader.PropertyToID("_Percent"); - public static readonly int PercentY = Shader.PropertyToID("_PercentY"); - public static readonly int Desat = Shader.PropertyToID("_Desat"); + public static readonly int Percent = Get("_Percent"); + public static readonly int PercentY = Get("_PercentY"); + public static readonly int Desat = Get("_Desat"); // Used in LightSource - public static readonly int PlayerRadius = Shader.PropertyToID("_PlayerRadius"); - public static readonly int LightRadius = Shader.PropertyToID("_LightRadius"); - public static readonly int LightOffset = Shader.PropertyToID("_LightOffset"); - public static readonly int FlashlightSize = Shader.PropertyToID("_FlashlightSize"); - public static readonly int FlashlightAngle = Shader.PropertyToID("_FlashlightAngle"); + public static readonly int PlayerRadius = Get("_PlayerRadius"); + public static readonly int LightRadius = Get("_LightRadius"); + public static readonly int LightOffset = Get("_LightOffset"); + public static readonly int FlashlightSize = Get("_FlashlightSize"); + public static readonly int FlashlightAngle = Get("_FlashlightAngle"); // Used once for LightSourceGpuRenderer - public static readonly int DepthCompressionValue = Shader.PropertyToID("_DepthCompressionValue"); + public static readonly int DepthCompressionValue = Get("_DepthCompressionValue"); // Used in ProgressTracker - public static readonly int Buckets = Shader.PropertyToID("_Buckets"); - public static readonly int FullBuckets = Shader.PropertyToID("_FullBuckets"); + public static readonly int Buckets = Get("_Buckets"); + public static readonly int FullBuckets = Get("_FullBuckets"); // Used once in IntroCutscene and EndGameManager - public static readonly int Rad = Shader.PropertyToID("_Rad"); + public static readonly int Rad = Get("_Rad"); // Used only once for Quick Chat - public static readonly int FaceColor = Shader.PropertyToID("_FaceColor"); + public static readonly int FaceColor = Get("_FaceColor"); // Used for NavigationMinigame - public static readonly int CrossHair = Shader.PropertyToID("_CrossHair"); - public static readonly int CrossColor = Shader.PropertyToID("_CrossColor"); + public static readonly int CrossHair = Get("_CrossHair"); + public static readonly int CrossColor = Get("_CrossColor"); // Used for both SurveillanceMinigame (Planet and Normal) - public static readonly int Center = Shader.PropertyToID("_Center"); - public static readonly int Color2 = Shader.PropertyToID("_Color2"); + public static readonly int Center = Get("_Center"); + public static readonly int Color2 = Get("_Color2"); // Used in ReactorShipRoom - public static readonly int Speed = Shader.PropertyToID("_Speed"); + public static readonly int Speed = Get("_Speed"); // Used only once in CourseMinigame - public static readonly int AltTex = Shader.PropertyToID("_AltTex"); - public static readonly int Perc = Shader.PropertyToID("_Perc"); + public static readonly int AltTex = Get("_AltTex"); + public static readonly int Perc = Get("_Perc"); // Used once in TextMarquee - public static readonly int VertexOffsetX = Shader.PropertyToID("_VertexOffsetX"); - public static readonly int VertexOffsetY = Shader.PropertyToID("_VertexOffsetY"); + public static readonly int VertexOffsetX = Get("_VertexOffsetX"); + public static readonly int VertexOffsetY = Get("_VertexOffsetY"); // Used in VertLineBehaviour - public static readonly int Fade = Shader.PropertyToID("_Fade"); + public static readonly int Fade = Get("_Fade"); } diff --git a/MiraAPI/Voting/VotingUtils.cs b/MiraAPI/Voting/VotingUtils.cs index 130e4c43..fb117e40 100644 --- a/MiraAPI/Voting/VotingUtils.cs +++ b/MiraAPI/Voting/VotingUtils.cs @@ -5,7 +5,6 @@ using MiraAPI.Events.Vanilla.Meeting.Voting; using MiraAPI.Networking; using MiraAPI.Utilities; using Reactor.Networking.Attributes; -using Reactor.Utilities; namespace MiraAPI.Voting;