Merge branch 'dev' into options-improvements
No files matched your search
@@ -14,6 +14,7 @@ public class ExampleOptions : AbstractOptionGroup
|
||||
{
|
||||
public override string GroupName => "options.exampleOptions1";
|
||||
public override Color GroupColor => Color.green;
|
||||
public override bool ShowInMatchInfoGuide => true;
|
||||
|
||||
[ModdedToggleOption("options.exampleOptions1.toggleOpt1")]
|
||||
[ModdedOptionVisibility] // Automatically resolves to using ToggleOpt1Visibility, can also target toggle options
|
||||
|
||||
@@ -9,6 +9,7 @@ public class ExampleOptions2 : AbstractOptionGroup
|
||||
public override string GroupName => "options.exampleOptions2";
|
||||
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 bool ShowInMatchInfoGuide => true;
|
||||
|
||||
public ModdedPlayerOption PlayerOption2 { get; } = new("options.exampleOptions2.youMustChoseAPlayer", false);
|
||||
public ModdedPlayerOption PlayerOption { get; } = new("options.exampleOptions2.ehWhatever");
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.Patches;
|
||||
using MiraAPI.Patches.GameModes;
|
||||
using MiraAPI.Patches.Options;
|
||||
using MiraAPI.Translation;
|
||||
@@ -128,6 +129,9 @@ public static class GameModeOption
|
||||
MenuState.Instance.QueuedRoleMenuRefresh[roleMenu.Key] = true;
|
||||
}
|
||||
}
|
||||
|
||||
GameOptionsMenuPatch.RefreshForGameMode();
|
||||
RoleGuidePatches.RefreshSettingsForGameMode();
|
||||
}
|
||||
|
||||
// TODO: could make Values a dict of AbstractGameMode too
|
||||
@@ -143,12 +147,6 @@ public static class GameModeOption
|
||||
Info($"Game mode changed to {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;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.GameOptions;
|
||||
@@ -26,6 +28,12 @@ public abstract class AbstractOptionGroup
|
||||
/// </summary>
|
||||
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>
|
||||
/// Gets the Optionable type of the group.
|
||||
/// </summary>
|
||||
@@ -49,6 +57,42 @@ public abstract class AbstractOptionGroup
|
||||
/// </summary>
|
||||
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>
|
||||
/// Gets the group <see cref="Color"/>. This is used to color the group in the options menu.
|
||||
/// </summary>
|
||||
|
||||
@@ -69,6 +69,10 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
|
||||
|
||||
OldUiButtonScaleFactor = TopRightButtonsFactorSlider.Value;
|
||||
}
|
||||
else if (configEntry == MatchInfoGuideScaleFactor && MatchInfoGuide.Instance)
|
||||
{
|
||||
RoleGuidePatches.ApplyGuideScale(MatchInfoGuide.Instance, MatchInfoGuideScaleFactor.Value);
|
||||
}
|
||||
else if (configEntry == WikiOnBottomRow)
|
||||
{
|
||||
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)]
|
||||
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>
|
||||
/// Gets the fps specified by the player.
|
||||
/// </summary>
|
||||
|
||||
@@ -38,6 +38,7 @@ internal static class GameOptionsMenuPatch
|
||||
[HarmonyPatch(nameof(GameOptionsMenu.Initialize))]
|
||||
public static bool InitPatch(GameOptionsMenu __instance)
|
||||
{
|
||||
Helpers.CacheAdvancedSettingsRowPrefab(__instance.numberOptionOrigin);
|
||||
__instance.Children ??= new();
|
||||
__instance.Children.Clear();
|
||||
|
||||
@@ -187,6 +188,66 @@ internal static class GameOptionsMenuPatch
|
||||
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)
|
||||
{
|
||||
var num = 0.713f;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
@@ -13,6 +14,11 @@ internal static class GameSettingMenuPatches
|
||||
[HarmonyPatch(nameof(GameSettingMenu.OnEnable))]
|
||||
public static void OnEnablePrefix(GameSettingMenu __instance)
|
||||
{
|
||||
if (__instance.GameSettingsTab && __instance.GameSettingsTab.numberOptionOrigin)
|
||||
{
|
||||
Helpers.CacheAdvancedSettingsRowPrefab(__instance.GameSettingsTab.numberOptionOrigin);
|
||||
}
|
||||
|
||||
// Add Menu State object
|
||||
if (!__instance.gameObject.GetComponent<MenuState>())
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<string name="MiraApi.ShowKeybindsOnButtons">Show Keybinds on Buttons</string>
|
||||
<string name="MiraApi.ButtonScaleFactor">Button 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.WikiOnBottomRow">Display Wiki on Bottom Row</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.GroupingLabel">Grouping</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>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 7.0 KiB |
@@ -205,6 +205,21 @@ public static class MiraAssets
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiRolesButtonOpenSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarRolesButtonOpen.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the modifiers button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiModifiersButtonHoverSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarModifiersButtonHover.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the modifiers button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiModifiersButtonIdleSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarModifiersButtonIdle.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the modifiers button while hovering.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset WikiModifiersButtonOpenSprite { get; } = new("MiraAPI.Resources.Wiki.SidebarModifiersButtonOpen.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for the roles button while hovering.
|
||||
/// </summary>
|
||||
@@ -335,5 +350,25 @@ public static class MiraAssets
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset IconTerminology { get; } = new("MiraAPI.Resources.Wiki.IconTerminology.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Page switching sprite.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset PageLeftHover { get; } = new("MiraAPI.Resources.Wiki.PageLeftHover.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Page switching sprite.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset PageLeftIdle { get; } = new("MiraAPI.Resources.Wiki.PageLeftIdle.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Page switching sprite.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset PageRightHover { get; } = new("MiraAPI.Resources.Wiki.PageRightHover.png");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Page switching sprite.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset PageRightIdle { get; } = new("MiraAPI.Resources.Wiki.PageRightIdle.png");
|
||||
|
||||
internal static LoadableResourceAsset BlankSprite { get; } = new("MiraAPI.Resources.BlankSprite.png");
|
||||
}
|
||||
@@ -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.RegularExpressions;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
@@ -25,12 +26,16 @@ using Random = UnityEngine.Random;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
|
||||
internal readonly record struct AdvancedSettingsRow(string Title, string Value, AbstractOptionGroup? Group);
|
||||
|
||||
/// <summary>
|
||||
/// A class that contains helper methods.
|
||||
/// </summary>
|
||||
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 NumberOption? _advancedSettingsRowPrefab;
|
||||
|
||||
/// <summary>
|
||||
/// 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);
|
||||
}
|
||||
|
||||
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);
|
||||
var scrollHeight = variableTextSizing
|
||||
? desc.textBounds.size.y - 2.5f + maxTextSize + 0.475f
|
||||
@@ -852,6 +858,444 @@ public static class Helpers
|
||||
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)
|
||||
{
|
||||
panel.roleCount.text = ability.AbilityType;
|
||||
|
||||