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;