mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-20 01:46:15 +00:00
11 files changed
+1714
-80
No files matched your search
@@ -10,6 +10,7 @@ public class ExampleOptions : AbstractOptionGroup
|
|||||||
{
|
{
|
||||||
public override string GroupName => "options.exampleOptions1";
|
public override string GroupName => "options.exampleOptions1";
|
||||||
public override Color GroupColor => Color.green;
|
public override Color GroupColor => Color.green;
|
||||||
|
public override bool ShowInMatchInfoGuide => true;
|
||||||
|
|
||||||
[ModdedToggleOption("options.exampleOptions1.toggleOpt1")]
|
[ModdedToggleOption("options.exampleOptions1.toggleOpt1")]
|
||||||
public bool ToggleOpt { get; set; } = false;
|
public bool ToggleOpt { get; set; } = false;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ public class ExampleOptions2 : AbstractOptionGroup
|
|||||||
public override uint GroupPriority => 0; // This group will be displayed first. The default value is uint.MaxValue.
|
public override uint GroupPriority => 0; // This group will be displayed first. The default value is uint.MaxValue.
|
||||||
|
|
||||||
public override MenuCategory ParentMenu => MenuCategory.CustomTwo;
|
public override MenuCategory ParentMenu => MenuCategory.CustomTwo;
|
||||||
|
public override bool ShowInMatchInfoGuide => true;
|
||||||
|
|
||||||
public ModdedPlayerOption PlayerOption2 { get; } = new("options.exampleOptions2.youMustChoseAPlayer", false);
|
public ModdedPlayerOption PlayerOption2 { get; } = new("options.exampleOptions2.youMustChoseAPlayer", false);
|
||||||
public ModdedPlayerOption PlayerOption { get; } = new("options.exampleOptions2.ehWhatever");
|
public ModdedPlayerOption PlayerOption { get; } = new("options.exampleOptions2.ehWhatever");
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Linq;
|
|||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using MiraAPI.GameOptions;
|
using MiraAPI.GameOptions;
|
||||||
using MiraAPI.Networking;
|
using MiraAPI.Networking;
|
||||||
|
using MiraAPI.Patches;
|
||||||
using MiraAPI.Patches.GameModes;
|
using MiraAPI.Patches.GameModes;
|
||||||
using MiraAPI.Patches.Options;
|
using MiraAPI.Patches.Options;
|
||||||
using MiraAPI.Translation;
|
using MiraAPI.Translation;
|
||||||
@@ -128,6 +129,9 @@ public static class GameModeOption
|
|||||||
MenuState.Instance.QueuedRoleMenuRefresh[roleMenu.Key] = true;
|
MenuState.Instance.QueuedRoleMenuRefresh[roleMenu.Key] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GameOptionsMenuPatch.RefreshForGameMode();
|
||||||
|
RoleGuidePatches.RefreshSettingsForGameMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: could make Values a dict of AbstractGameMode too
|
// TODO: could make Values a dict of AbstractGameMode too
|
||||||
@@ -143,12 +147,6 @@ public static class GameModeOption
|
|||||||
Info($"Game mode changed to {option.GetInt()}");
|
Info($"Game mode changed to {option.GetInt()}");
|
||||||
RpcSyncGamemode(PlayerControl.LocalPlayer, option.GetInt());
|
RpcSyncGamemode(PlayerControl.LocalPlayer, option.GetInt());
|
||||||
|
|
||||||
if (GameSettingMenu.Instance && CustomGameModeManager.ActiveMode != null)
|
|
||||||
{
|
|
||||||
GameOptionsMenuPatch.ToggleGamemodeOptions(CustomGameModeManager.ActiveMode, __instance);
|
|
||||||
GameSettingMenu.Instance.RoleSettingsButton.gameObject.SetActive(CustomGameModeManager.ActiveMode.ShowNormalRoleSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Il2CppInterop.Runtime.Attributes;
|
||||||
using MiraAPI.Modifiers;
|
using MiraAPI.Modifiers;
|
||||||
using MiraAPI.Roles;
|
using MiraAPI.Roles;
|
||||||
|
using MiraAPI.Utilities.Assets;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace MiraAPI.GameOptions;
|
namespace MiraAPI.GameOptions;
|
||||||
@@ -26,6 +28,12 @@ public abstract class AbstractOptionGroup
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract string GroupName { get; }
|
public abstract string GroupName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the sprite of the group that is displayed in the match info guide.
|
||||||
|
/// </summary>
|
||||||
|
[HideFromIl2Cpp]
|
||||||
|
public virtual LoadableAsset<Sprite> Sprite => MiraAssets.IconSettings;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Optionable type of the group.
|
/// Gets the Optionable type of the group.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -49,6 +57,42 @@ public abstract class AbstractOptionGroup
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual Func<bool> GroupVisible => () => true;
|
public virtual Func<bool> GroupVisible => () => true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this group's options are shown in the Match Info Guide.
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool ShowInMatchInfoGuide => ParentMenu is MenuCategory.Game;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name title of the group when it is in the match info guide.
|
||||||
|
/// </summary>
|
||||||
|
public virtual string RoleGuideTitle => GroupName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the key used to combine this group with other option groups on one Match Info Guide page.
|
||||||
|
/// Groups are combined only with groups from the same plugin that use the same non-empty key.
|
||||||
|
/// </summary>
|
||||||
|
public virtual string? MatchInfoGuidePageKey => null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the title of the Match Info Guide page. For groups sharing a page key, the first
|
||||||
|
/// group in menu order supplies the page title.
|
||||||
|
/// </summary>
|
||||||
|
public virtual string MatchInfoGuidePageTitle => RoleGuideTitle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ordering priority of this Match Info Guide page. Lower values appear first
|
||||||
|
/// among pages with the same menu category and group priority. Groups sharing a page key
|
||||||
|
/// use the lowest page-order value among those groups.
|
||||||
|
/// </summary>
|
||||||
|
public virtual int MatchInfoGuidePageOrder => int.MaxValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of columns used to lay out options on this Match Info Guide page.
|
||||||
|
/// For a combined page, groups are placed into these columns. Values are limited to 1-6;
|
||||||
|
/// for groups sharing a page key, the first group in menu order supplies this value.
|
||||||
|
/// </summary>
|
||||||
|
public virtual int MatchInfoGuidePageColumns => 2;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the group <see cref="Color"/>. This is used to color the group in the options menu.
|
/// Gets the group <see cref="Color"/>. This is used to color the group in the options menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -69,6 +69,10 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
|
|||||||
|
|
||||||
OldUiButtonScaleFactor = TopRightButtonsFactorSlider.Value;
|
OldUiButtonScaleFactor = TopRightButtonsFactorSlider.Value;
|
||||||
}
|
}
|
||||||
|
else if (configEntry == MatchInfoGuideScaleFactor && MatchInfoGuide.Instance)
|
||||||
|
{
|
||||||
|
RoleGuidePatches.ApplyGuideScale(MatchInfoGuide.Instance, MatchInfoGuideScaleFactor.Value);
|
||||||
|
}
|
||||||
else if (configEntry == WikiOnBottomRow)
|
else if (configEntry == WikiOnBottomRow)
|
||||||
{
|
{
|
||||||
SetUpButtonPositions();
|
SetUpButtonPositions();
|
||||||
@@ -198,6 +202,12 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
|
|||||||
[LocalSliderSetting(min: 0.3f, max: 2f, suffixType: MiraNumberSuffixes.Multiplier, formatString: "0.00", displayValue: true)]
|
[LocalSliderSetting(min: 0.3f, max: 2f, suffixType: MiraNumberSuffixes.Multiplier, formatString: "0.00", displayValue: true)]
|
||||||
public ConfigEntry<float> TopRightButtonsFactorSlider { get; private set; } = config.Bind("MiraApi.VisualsUi", "MiraApi.TopRightButtonScaleFactor", 1f);
|
public ConfigEntry<float> TopRightButtonsFactorSlider { get; private set; } = config.Bind("MiraApi.VisualsUi", "MiraApi.TopRightButtonScaleFactor", 1f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the scale of the match info guide.
|
||||||
|
/// </summary>
|
||||||
|
[LocalSliderSetting(min: 0.5f, max: 1f, suffixType: MiraNumberSuffixes.Multiplier, formatString: "0.00", displayValue: true)]
|
||||||
|
public ConfigEntry<float> MatchInfoGuideScaleFactor { get; private set; } = config.Bind("MiraApi.VisualsUi", "MiraApi.MatchInfoGuideScaleFactor", 1f);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the fps specified by the player.
|
/// Gets the fps specified by the player.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ internal static class GameOptionsMenuPatch
|
|||||||
[HarmonyPatch(nameof(GameOptionsMenu.Initialize))]
|
[HarmonyPatch(nameof(GameOptionsMenu.Initialize))]
|
||||||
public static bool InitPatch(GameOptionsMenu __instance)
|
public static bool InitPatch(GameOptionsMenu __instance)
|
||||||
{
|
{
|
||||||
|
Helpers.CacheAdvancedSettingsRowPrefab(__instance.numberOptionOrigin);
|
||||||
__instance.Children ??= new();
|
__instance.Children ??= new();
|
||||||
__instance.Children.Clear();
|
__instance.Children.Clear();
|
||||||
|
|
||||||
@@ -187,6 +188,66 @@ internal static class GameOptionsMenuPatch
|
|||||||
instance.scrollBar.SetYBoundsMax(-num - 1.65f);
|
instance.scrollBar.SetYBoundsMax(-num - 1.65f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void RefreshForGameMode()
|
||||||
|
{
|
||||||
|
if (CustomGameModeManager.ActiveMode == null ||
|
||||||
|
!GameSettingMenu.Instance ||
|
||||||
|
!GameSettingMenu.Instance.GameSettingsTab ||
|
||||||
|
!MenuState.Instance ||
|
||||||
|
MenuState.Instance.CurrentModIdx != 0 ||
|
||||||
|
GameModeGroups.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ToggleGamemodeOptions(CustomGameModeManager.ActiveMode, GameSettingMenu.Instance.GameSettingsTab);
|
||||||
|
GameSettingMenu.Instance.RoleSettingsButton.gameObject.SetActive(
|
||||||
|
CustomGameModeManager.ActiveMode.ShowNormalRoleSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static List<KeyValuePair<string, string>> GetActiveGameSettingsRows(Func<bool, string> formatBool)
|
||||||
|
{
|
||||||
|
var menu = GameSettingMenu.Instance ? GameSettingMenu.Instance.GameSettingsTab : null;
|
||||||
|
if (!menu || menu.Children == null)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows = new List<KeyValuePair<string, string>>();
|
||||||
|
foreach (var option in menu.Children)
|
||||||
|
{
|
||||||
|
if (!option || !option!.gameObject.activeInHierarchy ||
|
||||||
|
option == menu.MapPicker ||
|
||||||
|
(GameModeOption.OptionBehaviour && option == GameModeOption.OptionBehaviour) ||
|
||||||
|
!option.Data)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var title = option switch
|
||||||
|
{
|
||||||
|
ToggleOption toggle => toggle.TitleText.text,
|
||||||
|
NumberOption number => number.TitleText.text,
|
||||||
|
StringOption str => str.TitleText.text,
|
||||||
|
PlayerOption player => player.TitleText.text,
|
||||||
|
_ => TranslationController.Instance.GetString(option.Data.Title),
|
||||||
|
};
|
||||||
|
|
||||||
|
var value = option switch
|
||||||
|
{
|
||||||
|
ToggleOption toggle => formatBool(toggle.GetBool()),
|
||||||
|
NumberOption number => number.ValueText.text,
|
||||||
|
StringOption str => str.ValueText.text,
|
||||||
|
PlayerOption player => player.ValueText.text,
|
||||||
|
_ => option.Data.GetValueString(option.GetFloat()),
|
||||||
|
};
|
||||||
|
|
||||||
|
rows.Add(new KeyValuePair<string, string>(title, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
private static void CreateSettings(GameOptionsMenu instance, Transform container)
|
private static void CreateSettings(GameOptionsMenu instance, Transform container)
|
||||||
{
|
{
|
||||||
var num = 0.713f;
|
var num = 0.713f;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
using MiraAPI.Utilities;
|
||||||
|
|
||||||
namespace MiraAPI.Patches.Options;
|
namespace MiraAPI.Patches.Options;
|
||||||
|
|
||||||
@@ -13,6 +14,11 @@ internal static class GameSettingMenuPatches
|
|||||||
[HarmonyPatch(nameof(GameSettingMenu.OnEnable))]
|
[HarmonyPatch(nameof(GameSettingMenu.OnEnable))]
|
||||||
public static void OnEnablePrefix(GameSettingMenu __instance)
|
public static void OnEnablePrefix(GameSettingMenu __instance)
|
||||||
{
|
{
|
||||||
|
if (__instance.GameSettingsTab && __instance.GameSettingsTab.numberOptionOrigin)
|
||||||
|
{
|
||||||
|
Helpers.CacheAdvancedSettingsRowPrefab(__instance.GameSettingsTab.numberOptionOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
// Add Menu State object
|
// Add Menu State object
|
||||||
if (!__instance.gameObject.GetComponent<MenuState>())
|
if (!__instance.gameObject.GetComponent<MenuState>())
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -8,6 +8,7 @@
|
|||||||
<string name="MiraApi.ShowKeybindsOnButtons">Show Keybinds on Buttons</string>
|
<string name="MiraApi.ShowKeybindsOnButtons">Show Keybinds on Buttons</string>
|
||||||
<string name="MiraApi.ButtonScaleFactor">Button Scale Factor</string>
|
<string name="MiraApi.ButtonScaleFactor">Button Scale Factor</string>
|
||||||
<string name="MiraApi.TopRightButtonScaleFactor">Top-Right UI Scale Factor</string>
|
<string name="MiraApi.TopRightButtonScaleFactor">Top-Right UI Scale Factor</string>
|
||||||
|
<string name="MiraApi.MatchInfoGuideScaleFactor">Match Info Guide Scale Factor</string>
|
||||||
<string name="MiraApi.MaxFps">Max FPS</string>
|
<string name="MiraApi.MaxFps">Max FPS</string>
|
||||||
<string name="MiraApi.WikiOnBottomRow">Display Wiki on Bottom Row</string>
|
<string name="MiraApi.WikiOnBottomRow">Display Wiki on Bottom Row</string>
|
||||||
<string name="MiraApi.EnableSabotageFlashes">Enable Sabotage Flashes</string>
|
<string name="MiraApi.EnableSabotageFlashes">Enable Sabotage Flashes</string>
|
||||||
@@ -60,4 +61,9 @@ Outlive the timer or eliminate the crew.</string>
|
|||||||
<string name="MiraApi.Wiki.FilterLabel">Filter</string>
|
<string name="MiraApi.Wiki.FilterLabel">Filter</string>
|
||||||
<string name="MiraApi.Wiki.GroupingLabel">Grouping</string>
|
<string name="MiraApi.Wiki.GroupingLabel">Grouping</string>
|
||||||
<string name="MiraApi.Wiki.OrderLabel">Order</string>
|
<string name="MiraApi.Wiki.OrderLabel">Order</string>
|
||||||
|
<string name="MiraApi.Wiki.VanillaSettings">Vanilla</string>
|
||||||
|
<string name="MiraApi.Wiki.SearchResults">Search Results</string>
|
||||||
|
<string name="MiraApi.Wiki.PreviousPage">Previous</string>
|
||||||
|
<string name="MiraApi.Wiki.NextPage">Next</string>
|
||||||
|
<string name="MiraApi.Wiki.NoResultsFound">No Results Found</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -0,0 +1,344 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using MiraAPI.GameModes;
|
||||||
|
using MiraAPI.GameOptions;
|
||||||
|
using MiraAPI.GameOptions.OptionTypes;
|
||||||
|
using MiraAPI.PluginLoading;
|
||||||
|
using MiraAPI.Translation;
|
||||||
|
using MiraAPI.Utilities.Assets;
|
||||||
|
using Reactor.Utilities;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace MiraAPI.Utilities;
|
||||||
|
|
||||||
|
internal sealed record GuideOptionEntry(
|
||||||
|
string Title,
|
||||||
|
string Value,
|
||||||
|
AbstractOptionGroup Group,
|
||||||
|
string GroupName,
|
||||||
|
string ModAbbreviation,
|
||||||
|
IReadOnlyList<string> ModSearchTerms);
|
||||||
|
|
||||||
|
internal sealed class GuideOptionPage(
|
||||||
|
AbstractOptionGroup group,
|
||||||
|
string title,
|
||||||
|
int order,
|
||||||
|
int menuOrder,
|
||||||
|
uint groupPriority,
|
||||||
|
int columns,
|
||||||
|
string modAbbreviation,
|
||||||
|
IReadOnlyList<string> modSearchTerms,
|
||||||
|
List<GuideOptionEntry> entries)
|
||||||
|
{
|
||||||
|
public AbstractOptionGroup Group { get; } = group;
|
||||||
|
|
||||||
|
public List<AbstractOptionGroup> Groups { get; } = [group];
|
||||||
|
|
||||||
|
public string Title { get; } = title;
|
||||||
|
|
||||||
|
public LoadableAsset<Sprite> Sprite { get; } = group.Sprite;
|
||||||
|
|
||||||
|
public int Order { get; set; } = order;
|
||||||
|
|
||||||
|
public int MenuOrder { get; set; } = menuOrder;
|
||||||
|
|
||||||
|
public uint GroupPriority { get; set; } = groupPriority;
|
||||||
|
|
||||||
|
public int Columns { get; } = columns;
|
||||||
|
|
||||||
|
public string ModAbbreviation { get; } = modAbbreviation;
|
||||||
|
|
||||||
|
public HashSet<string> ModSearchTerms { get; } = new(modSearchTerms, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
public List<GuideOptionEntry> Entries { get; } = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class GuideOptionPages
|
||||||
|
{
|
||||||
|
private static readonly Regex TrailingZeroRegex = new(@"\.0+(?!\d)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));
|
||||||
|
|
||||||
|
public static List<GuideOptionPage> BuildPages(Func<bool, string> formatBool)
|
||||||
|
{
|
||||||
|
var pages = new List<GuideOptionPage>();
|
||||||
|
var combinedPages = new Dictionary<string, GuideOptionPage>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var mode = CustomGameModeManager.ActiveMode;
|
||||||
|
|
||||||
|
var groups = MiraPluginManager.Instance.PluginsWithOptionsOrGameModes
|
||||||
|
.SelectMany(plugin => plugin.InternalOptionGroups
|
||||||
|
.Where(group => group.ShowInMatchInfoGuide && IsSettingsGroup(group, mode) && SafeGroupVisible(group))
|
||||||
|
.Select(group => (
|
||||||
|
Plugin: plugin,
|
||||||
|
Group: group,
|
||||||
|
ModAbbreviation: plugin.MiraPlugin.GetAbbreviatedModName())))
|
||||||
|
.OrderBy(item => GetMenuOrder(item.Group.ParentMenu))
|
||||||
|
.ThenBy(item => item.Group.GroupPriority)
|
||||||
|
.ThenBy(item => item.Group.GroupName, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ThenBy(item => item.ModAbbreviation, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (var item in groups)
|
||||||
|
{
|
||||||
|
var groupName = item.Group.GroupName.Translate();
|
||||||
|
var modSearchTerms = GetModSearchTerms(item.Plugin, item.Group);
|
||||||
|
var entries = new List<GuideOptionEntry>();
|
||||||
|
|
||||||
|
foreach (var option in item.Group.Options)
|
||||||
|
{
|
||||||
|
if (!SafeOptionVisible(option))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry = TryCreateEntry(
|
||||||
|
option,
|
||||||
|
item.Group,
|
||||||
|
groupName,
|
||||||
|
item.ModAbbreviation,
|
||||||
|
modSearchTerms,
|
||||||
|
formatBool);
|
||||||
|
if (entry != null)
|
||||||
|
{
|
||||||
|
entries.Add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.Count > 0)
|
||||||
|
{
|
||||||
|
var pageKey = item.Group.MatchInfoGuidePageKey?.Trim();
|
||||||
|
var combinedKey = string.IsNullOrEmpty(pageKey) ? null : $"{item.Plugin.PluginId}\0{pageKey}";
|
||||||
|
if (combinedKey != null && combinedPages.TryGetValue(combinedKey, out var combinedPage))
|
||||||
|
{
|
||||||
|
combinedPage.Groups.Add(item.Group);
|
||||||
|
combinedPage.Entries.AddRange(entries);
|
||||||
|
combinedPage.ModSearchTerms.UnionWith(modSearchTerms);
|
||||||
|
combinedPage.Order = Math.Min(combinedPage.Order, item.Group.MatchInfoGuidePageOrder);
|
||||||
|
combinedPage.MenuOrder = Math.Min(
|
||||||
|
combinedPage.MenuOrder,
|
||||||
|
GetMenuOrder(item.Group.ParentMenu));
|
||||||
|
combinedPage.GroupPriority = Math.Min(
|
||||||
|
combinedPage.GroupPriority,
|
||||||
|
item.Group.GroupPriority);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var page = new GuideOptionPage(
|
||||||
|
item.Group,
|
||||||
|
item.Group.MatchInfoGuidePageTitle,
|
||||||
|
item.Group.MatchInfoGuidePageOrder,
|
||||||
|
GetMenuOrder(item.Group.ParentMenu),
|
||||||
|
item.Group.GroupPriority,
|
||||||
|
Mathf.Clamp(item.Group.MatchInfoGuidePageColumns, 1, 6),
|
||||||
|
item.ModAbbreviation,
|
||||||
|
modSearchTerms,
|
||||||
|
entries);
|
||||||
|
pages.Add(page);
|
||||||
|
if (combinedKey != null)
|
||||||
|
{
|
||||||
|
combinedPages.Add(combinedKey, page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages
|
||||||
|
.OrderBy(page => page.MenuOrder)
|
||||||
|
.ThenBy(page => page.GroupPriority)
|
||||||
|
.ThenBy(page => page.Order)
|
||||||
|
.ThenBy(page => page.Group.GroupName, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ThenBy(page => page.ModAbbreviation, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetMenuOrder(MenuCategory menu)
|
||||||
|
{
|
||||||
|
return menu switch
|
||||||
|
{
|
||||||
|
MenuCategory.Game => 0,
|
||||||
|
MenuCategory.CustomOne => 1,
|
||||||
|
MenuCategory.CustomTwo => 2,
|
||||||
|
_ => int.MaxValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<GuideOptionEntry> Search(IEnumerable<GuideOptionPage> pages, string query)
|
||||||
|
{
|
||||||
|
query = query.Trim();
|
||||||
|
if (query.Length == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const StringComparison cmp = StringComparison.OrdinalIgnoreCase;
|
||||||
|
var pageList = pages.ToList();
|
||||||
|
var normalizedQuery = NormalizeSearchText(query);
|
||||||
|
var matchingModPages = normalizedQuery.Length < 3
|
||||||
|
? Array.Empty<GuideOptionPage>()
|
||||||
|
: pageList
|
||||||
|
.Where(page => page.ModSearchTerms.Any(term =>
|
||||||
|
NormalizeSearchText(term).Contains(normalizedQuery, StringComparison.Ordinal)))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (matchingModPages.Length > 0)
|
||||||
|
{
|
||||||
|
return matchingModPages.SelectMany(page => page.Entries).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return pageList
|
||||||
|
.SelectMany(page => page.Entries
|
||||||
|
.Select(entry => (Entry: entry, Rank: Rank(entry, query, cmp)))
|
||||||
|
.Where(result => result.Rank < int.MaxValue)
|
||||||
|
.OrderBy(result => result.Rank)
|
||||||
|
.Select(result => result.Entry))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] GetModSearchTerms(MiraPluginInfo plugin, AbstractOptionGroup group)
|
||||||
|
{
|
||||||
|
var terms = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
plugin.PluginId,
|
||||||
|
plugin.PluginInfo.Metadata.Name,
|
||||||
|
plugin.MiraPlugin.OptionsTitleText,
|
||||||
|
plugin.MiraPlugin.ParentLocaleId,
|
||||||
|
plugin.MiraPlugin.GetAbbreviatedModName(),
|
||||||
|
};
|
||||||
|
|
||||||
|
AddNamespace(terms, plugin.MiraPlugin.GetType());
|
||||||
|
AddNamespace(terms, group.GetType());
|
||||||
|
foreach (var option in group.Options)
|
||||||
|
{
|
||||||
|
AddNamespace(terms, option.GetType());
|
||||||
|
}
|
||||||
|
|
||||||
|
return terms.Where(term => !string.IsNullOrWhiteSpace(term)).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddNamespace(HashSet<string> terms, Type type)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(type.Namespace))
|
||||||
|
{
|
||||||
|
terms.Add(type.Namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeSearchText(string value)
|
||||||
|
{
|
||||||
|
return new string(value.Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int Rank(GuideOptionEntry entry, string query, StringComparison cmp)
|
||||||
|
{
|
||||||
|
if (entry.Title.Equals(query, cmp))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.Title.Contains(query, cmp))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.GroupName.Contains(query, cmp) || entry.ModAbbreviation.Contains(query, cmp))
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry.Value.Contains(query, cmp) ? 3 : int.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSettingsGroup(AbstractOptionGroup group, AbstractGameMode? mode)
|
||||||
|
{
|
||||||
|
if (group.ParentMenu is not (MenuCategory.Game or MenuCategory.CustomOne or MenuCategory.CustomTwo))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.OptionableType == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mode != null && typeof(AbstractGameMode).IsAssignableFrom(group.OptionableType) &&
|
||||||
|
mode.GetType().IsAssignableTo(group.OptionableType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool SafeGroupVisible(AbstractOptionGroup group)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return group.GroupVisible();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.Warning($"GroupVisible threw for {group.GetType().Name}: {e.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool SafeOptionVisible(IModdedOption option)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return option.Visible();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.Warning($"Visible threw for option '{option.Title}': {e.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GuideOptionEntry? TryCreateEntry(
|
||||||
|
IModdedOption option,
|
||||||
|
AbstractOptionGroup group,
|
||||||
|
string groupName,
|
||||||
|
string modAbbreviation,
|
||||||
|
IReadOnlyList<string> modSearchTerms,
|
||||||
|
Func<bool, string> formatBool)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var title = TranslationController.Instance.GetString(option.StringName);
|
||||||
|
var value = FormatValue(option, formatBool);
|
||||||
|
return new GuideOptionEntry(title, value, group, groupName, modAbbreviation, modSearchTerms);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.Warning($"Could not format option '{option.Title}' for the guide: {e.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatValue(IModdedOption option, Func<bool, string> formatBool)
|
||||||
|
{
|
||||||
|
switch (option)
|
||||||
|
{
|
||||||
|
case ModdedToggleOption toggle:
|
||||||
|
return formatBool(toggle.Value);
|
||||||
|
|
||||||
|
case ModdedEnumOption enumOption:
|
||||||
|
return MiraLocaleManager.Get(
|
||||||
|
enumOption.Values[enumOption.Value],
|
||||||
|
enumOption.Values[enumOption.Value]);
|
||||||
|
|
||||||
|
case ModdedNumberOption number:
|
||||||
|
if (number is { NegativeWordValue: not "#", Value: -1 })
|
||||||
|
{
|
||||||
|
return number.NegativeWordValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (number is { ZeroWordValue: not "#", Value: 0 })
|
||||||
|
{
|
||||||
|
return number.ZeroWordValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TrailingZeroRegex.Replace(number.Data.GetValueString(number.Value), string.Empty);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return option.Data.Type == global::OptionTypes.Checkbox
|
||||||
|
? formatBool(Math.Abs(option.GetFloatData() - 1f) < 0.0001f)
|
||||||
|
: option.Data.GetValueString(option.GetFloatData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
using Il2CppInterop.Runtime;
|
||||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||||
using MiraAPI.GameOptions;
|
using MiraAPI.GameOptions;
|
||||||
using MiraAPI.GameOptions.OptionTypes;
|
using MiraAPI.GameOptions.OptionTypes;
|
||||||
@@ -25,12 +26,16 @@ using Random = UnityEngine.Random;
|
|||||||
|
|
||||||
namespace MiraAPI.Utilities;
|
namespace MiraAPI.Utilities;
|
||||||
|
|
||||||
|
internal readonly record struct AdvancedSettingsRow(string Title, string Value, AbstractOptionGroup? Group);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A class that contains helper methods.
|
/// A class that contains helper methods.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class Helpers
|
public static class Helpers
|
||||||
{
|
{
|
||||||
|
internal const float MatchInfoGuideBaseScale = 1.3f;
|
||||||
private static readonly Regex TrailingZeroRegex = new(@"\.0+(?!\d)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));
|
private static readonly Regex TrailingZeroRegex = new(@"\.0+(?!\d)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));
|
||||||
|
private static NumberOption? _advancedSettingsRowPrefab;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all of the options stored within the provided type.
|
/// Gets all of the options stored within the provided type.
|
||||||
@@ -843,7 +848,8 @@ public static class Helpers
|
|||||||
grid.localScale = new Vector3(1.3f, 1.3f, 1);
|
grid.localScale = new Vector3(1.3f, 1.3f, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.transform.SetParent(parent.Inner.transform);
|
obj.transform.SetParent(parent.Inner.transform, false);
|
||||||
|
obj.transform.localScale = Vector3.one / MatchInfoGuideBaseScale;
|
||||||
obj.transform.localPosition = new Vector3(0f, 0.5f, 0f);
|
obj.transform.localPosition = new Vector3(0f, 0.5f, 0f);
|
||||||
var scrollHeight = variableTextSizing
|
var scrollHeight = variableTextSizing
|
||||||
? desc.textBounds.size.y - 2.5f + maxTextSize + 0.475f
|
? desc.textBounds.size.y - 2.5f + maxTextSize + 0.475f
|
||||||
@@ -852,6 +858,444 @@ public static class Helpers
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Caches a persistent clone of the vanilla settings row for use in the Match Info Guide.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rowPrefab">The vanilla settings row prefab.</param>
|
||||||
|
internal static void CacheAdvancedSettingsRowPrefab(NumberOption? rowPrefab)
|
||||||
|
{
|
||||||
|
if (!rowPrefab)
|
||||||
|
{
|
||||||
|
foreach (var candidate in Resources.FindObjectsOfTypeAll(Il2CppType.Of<GameSettingMenu>()))
|
||||||
|
{
|
||||||
|
var menu = candidate.TryCast<GameSettingMenu>();
|
||||||
|
var settingsTab = menu ? menu!.GameSettingsTab : null;
|
||||||
|
if (settingsTab && settingsTab!.numberOptionOrigin)
|
||||||
|
{
|
||||||
|
rowPrefab = settingsTab.numberOptionOrigin;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rowPrefab)
|
||||||
|
{
|
||||||
|
foreach (var candidate in Resources.FindObjectsOfTypeAll(Il2CppType.Of<NumberOption>()))
|
||||||
|
{
|
||||||
|
var option = candidate.TryCast<NumberOption>();
|
||||||
|
if (!option)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option!.TitleText && option.ValueText && option.PlusBtn && option.MinusBtn)
|
||||||
|
{
|
||||||
|
rowPrefab = option;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_advancedSettingsRowPrefab || !rowPrefab)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cachedPrefab = Object.Instantiate(rowPrefab!.gameObject);
|
||||||
|
cachedPrefab.name = "MiraAPIAdvancedSettingsRowPrefab";
|
||||||
|
cachedPrefab.transform.SetParent(null, false);
|
||||||
|
cachedPrefab.transform.localPosition = Vector3.zero;
|
||||||
|
cachedPrefab.transform.localRotation = Quaternion.identity;
|
||||||
|
cachedPrefab.SetActive(false);
|
||||||
|
Object.DontDestroyOnLoad(cachedPrefab);
|
||||||
|
_advancedSettingsRowPrefab = cachedPrefab.GetComponent<NumberOption>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a page of vanilla settings rows inside a roles-style scroller.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guide">The guide object.</param>
|
||||||
|
/// <param name="parent">The <see cref="Scroller"/> the page is parented to.</param>
|
||||||
|
/// <param name="settings">The rows to show: key is the label, value is the displayed value.</param>
|
||||||
|
/// <returns>The <see cref="GameObject"/> that holds all the rows. Destroy it to remove the page.</returns>
|
||||||
|
public static GameObject CreateAdvancedSettingsPage(
|
||||||
|
MatchInfoGuide guide,
|
||||||
|
Scroller parent,
|
||||||
|
IReadOnlyList<KeyValuePair<string, string>> settings)
|
||||||
|
{
|
||||||
|
return CreateAdvancedSettingsPage(
|
||||||
|
guide,
|
||||||
|
parent,
|
||||||
|
settings.Select(setting => new AdvancedSettingsRow(setting.Key, setting.Value, null)).ToList(),
|
||||||
|
2,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static GameObject CreateAdvancedSettingsPage(
|
||||||
|
MatchInfoGuide guide,
|
||||||
|
Scroller parent,
|
||||||
|
List<AdvancedSettingsRow> settings,
|
||||||
|
int columns = 2,
|
||||||
|
bool groupsAsColumns = false)
|
||||||
|
{
|
||||||
|
const float contentTop = 0.95f;
|
||||||
|
const float horizontalOffset = -0.2f;
|
||||||
|
const float headerContentGap = 0.2f;
|
||||||
|
const int contentSortingOrder = 151;
|
||||||
|
|
||||||
|
columns = Mathf.Clamp(columns, 1, 6);
|
||||||
|
var inner = parent.Inner;
|
||||||
|
var layout = inner.GetComponent<GridLayoutGroup>();
|
||||||
|
if (layout)
|
||||||
|
{
|
||||||
|
layout.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var columnGap = layout ? Mathf.Clamp(layout.spacing.x, 0.1f, 0.6f) : 0.3f;
|
||||||
|
const int baseColumnCount = 2;
|
||||||
|
var baseColumnWidth = layout ? Mathf.Clamp(layout.cellSize.x, 2f, 3.2f) : 2.7f;
|
||||||
|
var availableWidth = baseColumnWidth * baseColumnCount + columnGap * (baseColumnCount - 1);
|
||||||
|
var columnWidth = Mathf.Clamp(
|
||||||
|
(availableWidth - (columns - 1) * columnGap) / columns,
|
||||||
|
1f,
|
||||||
|
3.2f);
|
||||||
|
var layer = inner.gameObject.layer;
|
||||||
|
var page = new GameObject("AdvancedSettingsPage") { layer = layer };
|
||||||
|
page.transform.SetParent(inner, false);
|
||||||
|
page.transform.localPosition = Vector3.zero;
|
||||||
|
page.SetActive(false);
|
||||||
|
|
||||||
|
var gameSettingsMenu = GameSettingMenu.Instance;
|
||||||
|
CacheAdvancedSettingsRowPrefab(gameSettingsMenu ? gameSettingsMenu.GameSettingsTab.numberOptionOrigin : null);
|
||||||
|
var rowPrefab = _advancedSettingsRowPrefab;
|
||||||
|
if (!rowPrefab)
|
||||||
|
{
|
||||||
|
Error("Could not render MiraAPI settings rows because the vanilla NumberOption prefab was not cached.");
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
var spriteBounds = rowPrefab!.GetComponentsInChildren<SpriteRenderer>(true)
|
||||||
|
.Where(renderer =>
|
||||||
|
renderer.enabled &&
|
||||||
|
renderer.sprite &&
|
||||||
|
renderer.transform != rowPrefab.PlusBtn.transform &&
|
||||||
|
!renderer.transform.IsChildOf(rowPrefab.PlusBtn.transform) &&
|
||||||
|
renderer.transform != rowPrefab.MinusBtn.transform &&
|
||||||
|
!renderer.transform.IsChildOf(rowPrefab.MinusBtn.transform))
|
||||||
|
.Select(renderer => renderer.bounds)
|
||||||
|
.ToArray();
|
||||||
|
if (spriteBounds.Length == 0)
|
||||||
|
{
|
||||||
|
Error("Could not render MiraAPI settings rows because the vanilla NumberOption prefab has no sprites.");
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefabBounds = spriteBounds.Skip(1).Aggregate(
|
||||||
|
spriteBounds[0],
|
||||||
|
(bounds, next) =>
|
||||||
|
{
|
||||||
|
bounds.Encapsulate(next);
|
||||||
|
return bounds;
|
||||||
|
});
|
||||||
|
var rowScale = (columnWidth / prefabBounds.size.x) * 1.22f;
|
||||||
|
var rowHeight = prefabBounds.size.y * rowScale;
|
||||||
|
var rowPitch = 0.45f * rowScale;
|
||||||
|
var groupedSettings = groupsAsColumns
|
||||||
|
? settings
|
||||||
|
.Where(setting => setting.Group != null)
|
||||||
|
.GroupBy(setting => setting.Group!)
|
||||||
|
.ToList()
|
||||||
|
: [];
|
||||||
|
groupsAsColumns = groupedSettings.Count > 1;
|
||||||
|
if (groupsAsColumns)
|
||||||
|
{
|
||||||
|
rowScale = (columnWidth / prefabBounds.size.x) * (columns > 2 ? 1.15f : 1f);
|
||||||
|
rowHeight = prefabBounds.size.y * rowScale;
|
||||||
|
rowPitch = 0.45f * rowScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupColumns = new Dictionary<AbstractOptionGroup, int>();
|
||||||
|
var groupTopOffsets = new Dictionary<AbstractOptionGroup, float>();
|
||||||
|
var groupRows = new Dictionary<AbstractOptionGroup, int>();
|
||||||
|
if (groupsAsColumns)
|
||||||
|
{
|
||||||
|
var bandCount = Mathf.CeilToInt(groupedSettings.Count / (float)columns);
|
||||||
|
var bandHeights = new float[bandCount];
|
||||||
|
for (var i = 0; i < groupedSettings.Count; i++)
|
||||||
|
{
|
||||||
|
var group = groupedSettings[i].Key;
|
||||||
|
var band = i / columns;
|
||||||
|
groupColumns[group] = i % columns;
|
||||||
|
bandHeights[band] = Mathf.Max(
|
||||||
|
bandHeights[band],
|
||||||
|
rowPitch * (groupedSettings[i].Count() + 1) + headerContentGap + 0.25f);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bandOffset = 0f;
|
||||||
|
for (var band = 0; band < bandCount; band++)
|
||||||
|
{
|
||||||
|
for (var i = band * columns; i < Mathf.Min((band + 1) * columns, groupedSettings.Count); i++)
|
||||||
|
{
|
||||||
|
groupTopOffsets[groupedSettings[i].Key] = bandOffset;
|
||||||
|
groupRows[groupedSettings[i].Key] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bandOffset += bandHeights[band];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentBottom = float.PositiveInfinity;
|
||||||
|
var previousGroup = (AbstractOptionGroup?)null;
|
||||||
|
var rowIndex = 0;
|
||||||
|
var column = 0;
|
||||||
|
var groupSpacing = 0f;
|
||||||
|
|
||||||
|
for (var i = 0; i < settings.Count; i++)
|
||||||
|
{
|
||||||
|
if (settings[i].Group != previousGroup && settings[i].Group is { } group)
|
||||||
|
{
|
||||||
|
if (groupsAsColumns)
|
||||||
|
{
|
||||||
|
column = groupColumns[group];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (previousGroup != null)
|
||||||
|
{
|
||||||
|
groupSpacing += 0.2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column != 0)
|
||||||
|
{
|
||||||
|
rowIndex++;
|
||||||
|
column = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var headerSource = group.Header;
|
||||||
|
if (!headerSource && gameSettingsMenu && gameSettingsMenu.GameSettingsTab)
|
||||||
|
{
|
||||||
|
headerSource = gameSettingsMenu.GameSettingsTab.categoryHeaderOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!headerSource)
|
||||||
|
{
|
||||||
|
foreach (var candidate in Resources.FindObjectsOfTypeAll(Il2CppType.Of<CategoryHeaderMasked>()))
|
||||||
|
{
|
||||||
|
var candidateHeader = candidate.TryCast<CategoryHeaderMasked>();
|
||||||
|
if (candidateHeader && candidateHeader!.Background && candidateHeader.Title)
|
||||||
|
{
|
||||||
|
headerSource = candidateHeader;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerSource)
|
||||||
|
{
|
||||||
|
const float headerScale = 0.46f;
|
||||||
|
var headerWidth = groupsAsColumns
|
||||||
|
? columnWidth / headerScale
|
||||||
|
: (columns * columnWidth + (columns - 1) * columnGap) / headerScale;
|
||||||
|
var header = Object.Instantiate(headerSource!, page.transform, false);
|
||||||
|
header.transform.localScale = Vector3.one * headerScale;
|
||||||
|
if (groupsAsColumns)
|
||||||
|
{
|
||||||
|
var totalWidth = columns * columnWidth + (columns - 1) * columnGap;
|
||||||
|
var groupColumn = groupColumns[group];
|
||||||
|
var groupLeft = -totalWidth / 2f + horizontalOffset +
|
||||||
|
groupColumn * (columnWidth + columnGap);
|
||||||
|
header.transform.localPosition = new Vector3(
|
||||||
|
groupLeft + (columnWidth / 2f) - 0.25f,
|
||||||
|
contentTop - groupTopOffsets[group],
|
||||||
|
-1f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
header.transform.localPosition = new Vector3(
|
||||||
|
horizontalOffset - 0.5f,
|
||||||
|
contentTop - (rowIndex * rowPitch) - groupSpacing,
|
||||||
|
-1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
header.SetHeader(MiraLocaleManager.GetOrCreateLocaleString(group.GroupName), 20);
|
||||||
|
if (header.Title)
|
||||||
|
{
|
||||||
|
var titleTranslator = header.Title.GetComponent<TextTranslatorTMP>();
|
||||||
|
if (titleTranslator)
|
||||||
|
{
|
||||||
|
Object.Destroy(titleTranslator);
|
||||||
|
}
|
||||||
|
|
||||||
|
header.Title.text = group.GroupName.Translate();
|
||||||
|
header.Title.color = Color.white;
|
||||||
|
header.Title.horizontalAlignment = HorizontalAlignmentOptions.Left;
|
||||||
|
var titleTransform = header.Title.rectTransform;
|
||||||
|
var titlePosition = titleTransform.localPosition;
|
||||||
|
titlePosition.x = 0.5f;
|
||||||
|
titleTransform.localPosition = titlePosition;
|
||||||
|
titleTransform.SetSizeWithCurrentAnchors(
|
||||||
|
RectTransform.Axis.Horizontal,
|
||||||
|
headerWidth - 0.3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.Background)
|
||||||
|
{
|
||||||
|
header.Background.sprite = MiraAssets.CategoryHeader.LoadAsset();
|
||||||
|
header.Background.drawMode = SpriteDrawMode.Sliced;
|
||||||
|
header.Background.color = group.GroupColor;
|
||||||
|
header.Background.transform.localPosition = new Vector3(0.5f, -0.1833f, 0f);
|
||||||
|
header.Background.size = new Vector2(headerWidth, header.Background.size.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.Divider)
|
||||||
|
{
|
||||||
|
header.Divider.color = group.GroupColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
header.gameObject.SetActive(true);
|
||||||
|
foreach (var renderer in header.GetComponentsInChildren<SpriteRenderer>(true))
|
||||||
|
{
|
||||||
|
renderer.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
|
||||||
|
renderer.material.SetInt(PlayerMaterial.MaskLayer, 50);
|
||||||
|
renderer.sortingOrder = contentSortingOrder;
|
||||||
|
if (renderer.enabled && renderer.sprite)
|
||||||
|
{
|
||||||
|
contentBottom = Mathf.Min(
|
||||||
|
contentBottom,
|
||||||
|
inner.InverseTransformPoint(renderer.bounds.min).y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var text in header.GetComponentsInChildren<TextMeshPro>(true))
|
||||||
|
{
|
||||||
|
text.fontMaterial.SetFloat(ShaderID.StencilComp, 3f);
|
||||||
|
text.fontMaterial.SetFloat(ShaderID.Stencil, 50f);
|
||||||
|
text.renderer.sortingOrder = contentSortingOrder;
|
||||||
|
if (text.enabled)
|
||||||
|
{
|
||||||
|
text.ForceMeshUpdate();
|
||||||
|
contentBottom = Mathf.Min(
|
||||||
|
contentBottom,
|
||||||
|
inner.InverseTransformPoint(text.bounds.min).y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!groupsAsColumns)
|
||||||
|
{
|
||||||
|
rowIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousGroup = group;
|
||||||
|
}
|
||||||
|
|
||||||
|
var originX = -((columns * columnWidth) + ((columns - 1) * columnGap)) / 2f +
|
||||||
|
(column * (columnWidth + columnGap)) +
|
||||||
|
horizontalOffset;
|
||||||
|
var centerY = groupsAsColumns && settings[i].Group is { } rowGroup
|
||||||
|
? contentTop - groupTopOffsets[rowGroup] -
|
||||||
|
(rowPitch * (groupRows[rowGroup] + 1)) - headerContentGap - (rowHeight / 2f)
|
||||||
|
: contentTop - (rowHeight / 2f) - (rowIndex * rowPitch) - groupSpacing;
|
||||||
|
var row = Object.Instantiate(rowPrefab, page.transform);
|
||||||
|
row.enabled = false;
|
||||||
|
row.PlusBtn.gameObject.SetActive(false);
|
||||||
|
row.MinusBtn.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
foreach (var button in row.GetComponentsInChildren<PassiveButton>(true))
|
||||||
|
{
|
||||||
|
button.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var collider in row.GetComponentsInChildren<Collider2D>(true))
|
||||||
|
{
|
||||||
|
collider.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
row.transform.localScale = rowPrefab.transform.localScale * rowScale;
|
||||||
|
row.transform.localPosition = new Vector3(
|
||||||
|
originX + (columnWidth / 2f) - (prefabBounds.center.x * rowScale),
|
||||||
|
centerY - (prefabBounds.center.y * rowScale),
|
||||||
|
0f);
|
||||||
|
row.TitleText.text = settings[i].Title;
|
||||||
|
row.ValueText.text = settings[i].Value;
|
||||||
|
|
||||||
|
foreach (var text in row.GetComponentsInChildren<TextMeshPro>(true))
|
||||||
|
{
|
||||||
|
if (text.TryGetComponent<TextTranslatorTMP>(out var translator))
|
||||||
|
{
|
||||||
|
translator.enabled = false;
|
||||||
|
Object.Destroy(translator);
|
||||||
|
}
|
||||||
|
|
||||||
|
text.fontMaterial.SetFloat(ShaderID.StencilComp, 3f);
|
||||||
|
text.fontMaterial.SetFloat(ShaderID.Stencil, 50f);
|
||||||
|
text.renderer.sortingOrder = contentSortingOrder;
|
||||||
|
if (text.enabled)
|
||||||
|
{
|
||||||
|
text.ForceMeshUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var renderer in row.GetComponentsInChildren<SpriteRenderer>(true))
|
||||||
|
{
|
||||||
|
renderer.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
|
||||||
|
renderer.material.SetInt(PlayerMaterial.MaskLayer, 50);
|
||||||
|
renderer.sortingOrder = contentSortingOrder;
|
||||||
|
if (renderer.enabled &&
|
||||||
|
renderer.sprite &&
|
||||||
|
renderer.transform != row.PlusBtn.transform &&
|
||||||
|
!renderer.transform.IsChildOf(row.PlusBtn.transform) &&
|
||||||
|
renderer.transform != row.MinusBtn.transform &&
|
||||||
|
!renderer.transform.IsChildOf(row.MinusBtn.transform))
|
||||||
|
{
|
||||||
|
contentBottom = Mathf.Min(
|
||||||
|
contentBottom,
|
||||||
|
inner.InverseTransformPoint(renderer.bounds.min).y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var text in row.GetComponentsInChildren<TextMeshPro>(true))
|
||||||
|
{
|
||||||
|
if (text.enabled)
|
||||||
|
{
|
||||||
|
contentBottom = Mathf.Min(
|
||||||
|
contentBottom,
|
||||||
|
inner.InverseTransformPoint(text.bounds.min).y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
row.gameObject.SetActive(true);
|
||||||
|
if (groupsAsColumns && settings[i].Group is { } renderedGroup)
|
||||||
|
{
|
||||||
|
groupRows[renderedGroup]++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
column++;
|
||||||
|
if (column == columns)
|
||||||
|
{
|
||||||
|
column = 0;
|
||||||
|
rowIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var maskArea = parent.transform.FindChild("MaskArea")?.GetComponent<SpriteRenderer>();
|
||||||
|
var maskBottom = maskArea
|
||||||
|
? inner.InverseTransformPoint(maskArea!.bounds.min).y
|
||||||
|
: -0.75f;
|
||||||
|
var contentOverflow = float.IsPositiveInfinity(contentBottom)
|
||||||
|
? 0f
|
||||||
|
: maskBottom - contentBottom;
|
||||||
|
var scrollRange = contentOverflow > 0.05f
|
||||||
|
? contentOverflow + 0.05f
|
||||||
|
: 0f;
|
||||||
|
parent.SetYBoundsMax(Mathf.Clamp(scrollRange, 0f, 999f));
|
||||||
|
page.SetActive(true);
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
private static void SetupAbilityPanel(MatchInfoRolePanel panel, AdvancedWikiAbilityDescription ability, bool variableTextSizing = false)
|
private static void SetupAbilityPanel(MatchInfoRolePanel panel, AdvancedWikiAbilityDescription ability, bool variableTextSizing = false)
|
||||||
{
|
{
|
||||||
panel.roleCount.text = ability.AbilityType;
|
panel.roleCount.text = ability.AbilityType;
|
||||||
|
|||||||
Reference in new issue
Block a user