diff --git a/Directory.Build.props b/Directory.Build.props
index f93a76c..1f8760c 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -8,7 +8,7 @@
true
- 0.4.2
+ 0.4.3
dev
All Of Us, XtraCube, Angxl
diff --git a/MiraAPI.Example/Options/ExampleOptions2.cs b/MiraAPI.Example/Options/ExampleOptions2.cs
index bc3c910..0f17078 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/GameOptions/AbstractOptionGroup.cs b/MiraAPI/GameOptions/AbstractOptionGroup.cs
index f8f0877..01c9e09 100644
--- a/MiraAPI/GameOptions/AbstractOptionGroup.cs
+++ b/MiraAPI/GameOptions/AbstractOptionGroup.cs
@@ -69,7 +69,7 @@ public abstract class AbstractOptionGroup
internal CategoryHeaderMasked? Header { get; set; }
- internal Coroutine? CreationCoroutine { 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/Patches/Freeplay/TaskAdderPatches.cs b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs
index fa91e7b..ad0cabc 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/HudManagerPatches.cs b/MiraAPI/Patches/HudManagerPatches.cs
index a508ccf..76adc2c 100644
--- a/MiraAPI/Patches/HudManagerPatches.cs
+++ b/MiraAPI/Patches/HudManagerPatches.cs
@@ -150,7 +150,6 @@ public static class HudManagerPatches
{
t.gameObject.Destroy();
}
- Utilities.Extensions.ClearGarbageCollector();
var gridArrange = BottomLeft.GetComponent();
var aspectPosition = BottomLeft.GetComponent();
diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs
index b50b430..2bc615d 100644
--- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs
+++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs
@@ -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,47 +83,41 @@ 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);
@@ -83,7 +125,7 @@ internal static class GameOptionsMenuPatch
private static void UpdateGroup(AbstractOptionGroup? group, ref float num)
{
- if (group is null || group.Options.Count == 0 || group.Header is null || group.CreationCoroutine is not null)
+ if (group is null || !group.Ready || group.Options.Count == 0 || group.Header is null)
{
return;
}
@@ -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,21 +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
+ var groups = MenuState.Instance.CurrentMod.InternalOptionGroups
.Where(x => x.ParentMenu is MenuCategory.Modifiers ||
- x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? [];
+ x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true);
foreach (var modGroup in groups)
{
@@ -187,132 +209,109 @@ 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());
- switch (__instance.name)
+ IEnumerator CreateGroups()
{
- case "MODIFIERS TAB":
- ModifiersCreate(__instance);
- return false;
- case "CUSTOM TAB 1":
- CustomMenuOneCreate(__instance);
- return false;
- case "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 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;
}
@@ -428,36 +427,12 @@ internal static class GameOptionsMenuPatch
newOpt.Initialize();
newOpt.gameObject.SetActive(false);
- }).WrapToIl2Cpp();
- 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);
-
- group.CreationCoroutine = null;
- }
-
- private static void CreateGroup(GameOptionsMenu menu, AbstractOptionGroup group)
- {
- if (group.CreationCoroutine != null)
- {
- GameSettingMenu.Instance.StopCoroutine(group.CreationCoroutine);
- }
-
- group.CreationCoroutine = GameSettingMenu.Instance.StartCoroutine(CoCreateGroup(menu, group).WrapToIl2Cpp());
+ newOpt.SetAsPlayer();
+ }
+ }).WrapToIl2Cpp();
+ group.Ready = true;
}
}
diff --git a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs
index ff75606..bc6dc3a 100644
--- a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs
+++ b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs
@@ -3,6 +3,7 @@ using System.IO;
using System.Linq;
using BepInEx.Configuration;
using HarmonyLib;
+using MiraAPI.GameOptions;
using MiraAPI.PluginLoading;
using MiraAPI.Presets;
using MiraAPI.Roles;
@@ -23,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();
@@ -41,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";
}
@@ -56,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))
{
@@ -90,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();
}
}
@@ -116,8 +119,6 @@ internal static class GamePresetsTabPatches
// ReSharper disable once InconsistentNaming
public static void Postfix()
{
- Error("OnDisable called");
-
if (_saveButton)
{
_saveButton.SetActive(false);
@@ -197,7 +198,7 @@ internal static class GamePresetsTabPatches
saveButton.OnClick.AddListener(
(UnityAction)(() =>
{
- if (GameSettingMenuPatches.SelectedModIdx == 0)
+ if (MenuState.Instance.CurrentModIdx == 0)
{
return;
}
@@ -205,7 +206,7 @@ internal static class GamePresetsTabPatches
SavePresetPopup.CreatePopup(
name =>
{
- if (GameSettingMenuPatches.SelectedMod == null)
+ if (MenuState.Instance.CurrentModIdx == 0)
{
return;
}
@@ -218,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);
}
@@ -285,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
@@ -301,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 d9cc5fb..c31b519 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,663 +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.RegisteredOptionablePlugins.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.RegisteredOptionablePlugins.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.RegisteredOptionablePlugins.Length})\nMain";
- _text.fontSizeMax = 3.2f;
- SelectedMod = null;
- }
- else if (_text is not null)
- {
- _text.fontSizeMax = 2.3f;
- SelectedMod = MiraPluginManager.Instance.RegisteredOptionablePlugins[SelectedModIdx - 1];
-
- var name = SelectedMod.MiraPlugin.OptionsTitleText;
- _text.text = $"(Page {SelectedModIdx}/{MiraPluginManager.Instance.RegisteredOptionablePlugins.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;
-
- CleanupTab(settings, roles);
-
- 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);
- }
- }
-
- 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);
- }
-
- Utilities.Extensions.ClearGarbageCollector();
-
- 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 dd80dc4..51e2e92 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.RegisteredOptionablePlugins[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.RegisteredOptionablePlugins.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.RegisteredOptionablePlugins.Length;
+ SelectedModIdx = MiraPluginManager.Instance.RegisteredPluginsWithOptions.Length;
}
Refresh(__instance);
diff --git a/MiraAPI/Patches/Options/MenuState.cs b/MiraAPI/Patches/Options/MenuState.cs
new file mode 100644
index 0000000..d459202
--- /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/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs
index cb1283b..0dc8721 100644
--- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs
+++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs
@@ -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,48 +38,20 @@ public static class RoleSettingMenuPatches
HudManager.Instance.PlayerCam.OverrideScreenShakeEnabled = false;
}
- [HarmonyPostfix]
- [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
- public static void SetQuotaTabPostfix(RolesSettingsMenu __instance)
- {
- __instance.scrollBar.SetScrollBounds(__instance);
- }
-
private static IEnumerator CoQuotaTabPatch(RolesSettingsMenu roleMenu)
{
- foreach (var obj in Headers)
- {
- if (!obj)
- {
- continue;
- }
-
- obj.Destroy();
- }
-
- Headers.Clear();
- foreach (var obj in RoleOptionSettings)
- {
- if (!obj)
- {
- continue;
- }
-
- obj.gameObject.Destroy();
- }
-
- RoleOptionSettings.Clear();
CurrentRole = null;
CurrentRoleOptions = null;
- roleMenu.roleChances = new Il2CppSystem.Collections.Generic.List();
- roleMenu.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List();
+ roleMenu.QuotaTabSelectables.Clear();
+ roleMenu.roleChances.Clear();
+ roleMenu.advancedSettingChildren.Clear();
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)
{
roleMenu.AllButton.transform.parent.gameObject.SetActive(true);
roleMenu.AllButton.gameObject.SetActive(true);
@@ -93,304 +61,340 @@ public static class RoleSettingMenuPatches
hitbox.localPosition = new Vector3(0.3297f, -.2333f, 4f);
hitbox.localScale = new Vector3(1, 1, 1);
dividerImage.gameObject.SetActive(true);
+ }
+ else
+ {
+ 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);
+ }
- 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)
+ if (MenuState.Instance.FinishedRoleMenus.TryGetValue(MenuState.Instance.CurrentModIdx, out var finished) && finished)
+ {
+ var roleOptionSettings = roleMenu.scrollBar.Inner.GetComponentsInChildren(true);
+ foreach (var r in roleOptionSettings)
{
- var num2 = -1.928f;
- roleMenu.roleTabs = new();
- roleMenu.roleTabs.Add(roleMenu.AllButton);
+ 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.AddRoleTab(t, ref num2);
+ 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.AddRoleTab(t, ref num2);
+ 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;
+ }
+ }
}
}
-
- 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.");
- yield break;
+ MenuState.Instance.FinishedRoleMenus[MenuState.Instance.CurrentModIdx] = true;
}
- ScrollerNum = 0.522f;
-
- 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);
-
- var num4 = 0;
-
- var roleGroups = GameSettingMenuPatches.SelectedMod?.InternalRoles.Values.OfType()
- .ToLookup(x => x.RoleOptionsGroup);
-
- if (roleGroups is null)
- {
- Error("No role groups found for the selected mod.");
- yield break;
- }
-
- // 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();
-
- Headers.Add(categoryHeaderMasked.gameObject);
- 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)
- {
- 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 = roleMenu.BackButton.GetComponent().ClickSound;
- headerBtn.OnMouseOver = new UnityEvent();
- headerBtn.OnMouseOut = new UnityEvent();
- headerBtn.OnClick.AddListener(
- (UnityAction)(() =>
- {
- RolePositions[GameSettingMenuPatches.SelectedModIdx] = roleMenu.scrollBar.Inner.localPosition;
- if (RoleGroupHidden.TryGetValue(group, out var groupHidden))
- {
- RoleGroupHidden[group] = !groupHidden;
- }
-
- foreach (var obj in Headers)
- {
- if (!obj)
- {
- continue;
- }
-
- obj.Destroy();
- }
-
- Headers.Clear();
- foreach (var obj in RoleOptionSettings)
- {
- if (!obj)
- {
- continue;
- }
-
- obj.gameObject.Destroy();
- }
-
- RoleOptionSettings.Clear();
- roleMenu.SetQuotaTab();
- }));
- headerBtn.SetButtonEnableState(true);
-
- if (RoleGroupHidden.TryGetValue(group, out var value) && !value)
- {
- ScrollerNum -= 0.4f;
- }
- }
-
- roleMenu.scrollBar.SetScrollBounds(roleMenu);
+ roleMenu.SetScrollBounds();
_quotaTabCoroutine = null;
}
private static Coroutine? _quotaTabCoroutine;
[HarmonyPrefix]
- [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
- public static bool SetQuotaTabPatch(RolesSettingsMenu __instance)
+ [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);
}
- _quotaTabCoroutine = GameSettingMenu.Instance.StartCoroutine(CoQuotaTabPatch(__instance).WrapToIl2Cpp());
+ __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 Scroller scroller, RolesSettingsMenu roleMenu)
+ private static void SetScrollBounds(this RolesSettingsMenu menu, bool isRolesConfig = false)
{
- if (GameSettingMenuPatches.SelectedModIdx == 0)
+ var scroller = menu.scrollBar;
+
+ if (MenuState.Instance.CurrentModIdx == 0)
{
- scroller.CalculateAndSetYBounds(roleMenu.roleChances.Count + 3, 1f, 6f, 0.43f);
+ scroller.CalculateAndSetYBounds(menu.roleChances.Count + 3, 1f, 6f, 0.43f);
return;
}
+ scroller.Inner = isRolesConfig ? menu.AdvancedRolesSettings.transform : menu.RoleChancesSettings.transform;
scroller.SetYBoundsMax(-ScrollerNum - 2);
- if (RolePositions.TryGetValue(GameSettingMenuPatches.SelectedModIdx, out var scroll))
- {
- scroller.Inner.localPosition = scroll;
- scroller.UpdateScrollBars();
- }
- else
- {
- scroller.ScrollToTop();
- }
}
[HarmonyPrefix]
@@ -398,12 +402,11 @@ public static class RoleSettingMenuPatches
[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;
@@ -423,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();
@@ -489,10 +478,10 @@ public static class RoleSettingMenuPatches
__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() ?? [];
+ .ToList();
foreach (var option in filteredOptions)
{
@@ -525,7 +514,6 @@ public static class RoleSettingMenuPatches
}
CurrentRoleOptions = filteredOptions;
- __instance.scrollBar.ScrollToTop();
}
private static void ChangeTab(RoleBehaviour role, RolesSettingsMenu __instance)
@@ -536,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();
@@ -592,9 +578,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");
@@ -619,6 +622,8 @@ public static class RoleSettingMenuPatches
__instance.RoleChancesSettings.SetActive(false);
__instance.AdvancedRolesSettings.SetActive(true);
+ __instance.SetScrollBounds(true);
+ __instance.scrollBar.ScrollToTop();
__instance.RefreshChildren();
}
@@ -670,8 +675,7 @@ 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);
@@ -703,7 +707,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/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs
index 171ab90..e662859 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,10 +34,7 @@ public sealed class MiraPluginManager
private readonly Dictionary _registeredPlugins = [];
internal MiraPluginInfo[] RegisteredPlugins { get; private set; } = null!;
-
- private readonly Dictionary _registeredOptionablePlugins = [];
-
- internal MiraPluginInfo[] RegisteredOptionablePlugins { get; private set; } = null!;
+ internal MiraPluginInfo[] RegisteredPluginsWithOptions { get; private set; } = null!;
internal Dictionary> QueuedRoleRegistrations { get; } = [];
internal static MiraPluginManager Instance { get; private set; } = new();
@@ -131,10 +129,6 @@ public sealed class MiraPluginManager
QueuedRoleRegistrations.Add(info, roles);
_registeredPlugins.Add(assembly, info);
- if (miraPlugin.DisplayOnOptionsMenu)
- {
- _registeredOptionablePlugins.Add(assembly, info);
- }
info.SavePublicCollections();
PresetManager.CreateDefaultPreset(info);
@@ -149,7 +143,7 @@ public sealed class MiraPluginManager
// Cache all the registered plugins into an array for easy access
RegisteredPlugins = [.. _registeredPlugins.Values];
- RegisteredOptionablePlugins = [.. _registeredOptionablePlugins.Values];
+ RegisteredPluginsWithOptions = [.. RegisteredPlugins.Where(m => m.MiraPlugin.DisplayOnOptionsMenu)];
ModifierManager.Modifiers = new ReadOnlyCollection(ModifierManager.InternalModifiers);
};
diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs
index d85ed19..6c6760b 100644
--- a/MiraAPI/Roles/CustomRoleManager.cs
+++ b/MiraAPI/Roles/CustomRoleManager.cs
@@ -141,7 +141,11 @@ 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();
+ roleBehaviour.RoleScreenshot = Sprite.Create(
+ customRole.Configuration.OptionsScreenshot?.LoadAsset().texture,
+ new Rect(0, 0, 370, 230),
+ Vector2.one / 2,
+ 100);
_emptySettings ??= new(0);
_emptyKillAnimations ??= new(0);
diff --git a/MiraAPI/Utilities/Assets/LoadableAsset.cs b/MiraAPI/Utilities/Assets/LoadableAsset.cs
index 798dfc9..7b0c032 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.
@@ -18,4 +20,20 @@ public abstract class LoadableAsset where T : UnityEngine.Object
///
/// The loaded asset.
public abstract T 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/TmpSpriteUtils.cs b/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs
index cdb4315..518e4ac 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);
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index 9dd91e9..53e395e 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -354,10 +354,6 @@ public static class Extensions
UnityEngine.Object.Destroy(go);
yield return null;
- if (clearGc)
- {
- ClearGarbageCollector();
- }
}
///
@@ -366,8 +362,8 @@ public static class Extensions
public static void ClearGarbageCollector()
{
Resources.UnloadUnusedAssets();
- Il2CppSystem.GC.Collect(0, Il2CppSystem.GCCollectionMode.Forced);
- GC.Collect(0, GCCollectionMode.Forced);
+ Il2CppSystem.GC.Collect();
+ GC.Collect();
}
///