From 1e453cf6afe673ce6f53dfde81924278722174eb Mon Sep 17 00:00:00 2001 From: neilmjain <69025896+neilmjain@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:26:00 -0400 Subject: [PATCH] add very scuffed pagination of game settings --- MiraAPI/Patches/RoleGuidePatches.cs | 458 ++++++++++++++++++++++- MiraAPI/Resources/Locale/en_US.xml | 4 + MiraAPI/Utilities/GuideOptionsPatches.cs | 203 ++++++++++ 3 files changed, 662 insertions(+), 3 deletions(-) create mode 100644 MiraAPI/Utilities/GuideOptionsPatches.cs diff --git a/MiraAPI/Patches/RoleGuidePatches.cs b/MiraAPI/Patches/RoleGuidePatches.cs index 7f6a903..9d3fbc8 100644 --- a/MiraAPI/Patches/RoleGuidePatches.cs +++ b/MiraAPI/Patches/RoleGuidePatches.cs @@ -27,6 +27,7 @@ namespace MiraAPI.Patches; [HarmonyPatch] public static class RoleGuidePatches { + private const int GeneralTabIndex = 1; private const int RolesTabIndex = 2; private const int ModifiersTabIndex = 3; private const float MinGuideScaleFactor = 0.5f; @@ -174,6 +175,7 @@ public static class RoleGuidePatches _advancedInfoTabScroller = null!; _currentAdvancedTabObject = null!; _loadingSpinner = null; + ResetGeneralState(); SearchIconButton = null!; SearchSortingOrderButton = null!; SearchSortingGroupButton = null!; @@ -468,6 +470,7 @@ public static class RoleGuidePatches .First(x => x.name.Contains("BG_Gradient")).transform.localPosition = new Vector3(0, -0.62f, -5); _modifiersScroller = modifiersTab.GetComponent(); instance.settingsTabs.Add(modifiersTab); + InitializeGeneralPanel(instance); var wikiTab = Object.Instantiate( instance.settingsTabs[RolesTabIndex], @@ -1051,6 +1054,12 @@ public static class RoleGuidePatches private static void RefreshActiveTab(bool scrollToTop) { + if (IsGeneralTabActive) + { + RefreshGeneralTab(scrollToTop); + return; + } + StartPopulate(); if (scrollToTop) { @@ -1079,6 +1088,12 @@ public static class RoleGuidePatches } _appliedSearch = text; + if (IsGeneralTabActive) + { + RefreshGeneralTab(true); + yield break; + } + if (TryRefreshSearch(text)) { yield break; @@ -1186,6 +1201,12 @@ public static class RoleGuidePatches public static void SwitchFilter() { + if (IsGeneralTabActive) + { + ChangeGeneralPage(-1); + return; + } + var stepUp = (SortingFilter)((int)_sortFilter + 1); if (Enum.IsDefined(stepUp)) { @@ -1232,6 +1253,11 @@ public static class RoleGuidePatches public static void SwitchMethod() { + if (IsGeneralTabActive) + { + return; + } + var stepUp = (SortingMethod)((int)_sortMethod + 1); if (Enum.IsDefined(stepUp)) { @@ -1266,6 +1292,12 @@ public static class RoleGuidePatches public static void SwitchGrouping() { + if (IsGeneralTabActive) + { + ChangeGeneralPage(1); + return; + } + var stepUp = (SortingGroups)((int)_sortGrouping + 1); if (Enum.IsDefined(stepUp)) { @@ -1405,8 +1437,8 @@ public static class RoleGuidePatches _titleText.text = MiraLocaleManager.Get("MiraApi.Wiki.PlayersTab"); _titleIcon.sprite = MiraAssets.IconPlayerList.LoadAsset(); break; - case 1: - _titleText.text = MiraLocaleManager.Get("MiraApi.Wiki.SettingsTab"); + case GeneralTabIndex: + _titleText.text = GetGeneralTitle(); _titleIcon.sprite = MiraAssets.IconSettings.LoadAsset(); break; case RolesTabIndex: @@ -1428,6 +1460,15 @@ public static class RoleGuidePatches _advancedInfoTabScroller?.gameObject.SetActive(false); SearchComponentContainer?.SetActive(true); + if (tabIndex == GeneralTabIndex) + { + EnterGeneralTab(); + } + else + { + LeaveGeneralTab(); + } + UpdateSpinner(); } @@ -1474,6 +1515,7 @@ public static class RoleGuidePatches { instance.settingsTabs[RolesTabIndex].SetActive(false); _modifiersScroller?.gameObject.SetActive(false); + LeaveGeneralTab(); if (_currentAdvancedTabObject) { _currentAdvancedTabObject.SetActive(false); @@ -1680,6 +1722,416 @@ public static class RoleGuidePatches { return customRole.RoleFactionTitle; } + + // ---- General (non-role) option pages: paginated General tab + cross-page search ---- + private static Scroller? _generalScroller; + private static GameObject? _generalNoResults; + private static readonly List GeneralRows = []; + private static GameObject? _generalRowTemplate; + private static Vector3 _generalRowOrigin; + private static Vector3 _generalRowColumnStep; + private static float _generalRowHeight = -0.65f; // negative: rows go down + private static int _generalColumns = 2; + private static float _generalVisibleHeight; + private static bool _generalScrollerFailed; + + /// One vanilla-looking settings row (label box + value box) that is reused between pages. + private sealed class GeneralRow(GameObject root, TextMeshPro label, TextMeshPro value) + { + public GameObject Root { get; } = root; + public TextMeshPro Label { get; } = label; + public TextMeshPro Value { get; } = value; + } + private static List _generalPages = []; + private static int _generalPage; // 0 = vanilla settings, 1..N = _generalPages[page - 1] + private static bool _generalSearching; + + // Saved state of the sort buttons so they can be restored when leaving the General tab. + private static bool _sortButtonsRepurposed; + private static string _savedFilterLabel = string.Empty; + private static string _savedGroupLabel = string.Empty; + private static Vector3 _savedFilterIconScale = Vector3.one; + private static Vector3 _savedGroupIconScale = Vector3.one; + + private static bool IsGeneralTabActive => + _uiInitialized && MatchInfoGuide.Instance && MatchInfoGuide.Instance.activeTabIndex == GeneralTabIndex; + + private static void ResetGeneralState() + { + _generalScroller = null; + _generalNoResults = null; + GeneralRows.Clear(); + _generalRowTemplate = null; + _generalScrollerFailed = false; + _generalPages = []; + _generalPage = 0; + _generalSearching = false; + _sortButtonsRepurposed = false; + } + + private static void InitializeGeneralPanel(MatchInfoGuide instance) + { + _generalNoResults = Object.Instantiate( + instance.rolesEnabledMessage, + instance.rolesEnabledMessage.transform.parent, + true); + _generalNoResults.name = "GeneralNoResultsFound"; + _generalNoResults.SetActive(false); + SetNoResultsText(_generalNoResults); + } + + /// + /// Clones the vanilla settings tab the first time it is needed, so extra pages are drawn with the exact same + /// rows, fonts, masking and scroll area as the vanilla list. Done lazily because the vanilla rows are only + /// created when the guide first opens. The clone is NOT added to settingsTabs; its visibility is managed + /// here so the guide's tab count and tab buttons stay untouched. + /// + private static bool EnsureGeneralScroller(MatchInfoGuide guide) + { + if (_generalScroller != null && _generalRowTemplate) + { + return true; + } + + if (_generalScrollerFailed) + { + return false; + } + + var vanillaTab = guide.settingsTabs[GeneralTabIndex]; + var vanillaScroller = vanillaTab.GetComponent() ?? vanillaTab.GetComponentInChildren(true); + if (!vanillaScroller || vanillaScroller.Inner.childCount < 2) + { + return false; // vanilla rows not built yet; try again next refresh + } + + // Read the vanilla grid: first row, second entry (same row => 2 columns), third entry (next row). + var vInner = vanillaScroller.Inner; + var p0 = vInner.GetChild(0).localPosition; + var p1 = vInner.GetChild(1).localPosition; + var sameRow = Mathf.Abs(p1.y - p0.y) < 0.01f; + var vanillaCount = vInner.childCount; + _generalColumns = sameRow ? 2 : 1; + _generalRowOrigin = p0; + _generalRowColumnStep = sameRow ? new Vector3(p1.x - p0.x, 0f, 0f) : Vector3.zero; + if (sameRow) + { + _generalRowHeight = vanillaCount >= 3 ? vInner.GetChild(2).localPosition.y - p0.y : -0.65f; + } + else + { + _generalRowHeight = p1.y - p0.y; + } + + if (Mathf.Abs(_generalRowHeight) < 0.05f) + { + _generalRowHeight = -0.65f; + } + + // Self-calibrate the visible height from how the vanilla list was bounded. + var vanillaRows = Mathf.CeilToInt(vanillaCount / (float)_generalColumns); + var visible = (vanillaRows * Mathf.Abs(_generalRowHeight)) - vanillaScroller.ContentYBounds.max; + _generalVisibleHeight = visible > 0.1f && visible < vanillaRows * Mathf.Abs(_generalRowHeight) + ? visible + : 3.3f * Mathf.Abs(_generalRowHeight); + + var clone = Object.Instantiate(vanillaTab, vanillaTab.transform.parent); + clone.name = "GeneralOptionsPanel"; + var cloneScroller = clone.GetComponent() ?? clone.GetComponentInChildren(true); + var cInner = cloneScroller.Inner; + + // Keep the first row as an inactive prototype, delete the rest. + var template = cInner.GetChild(0).gameObject; + for (var i = cInner.childCount - 1; i >= 1; i--) + { + var child = cInner.GetChild(i).gameObject; + child.SetActive(false); + child.Destroy(); + } + + template.SetActive(false); + _generalRowTemplate = template; + _generalScroller = cloneScroller; + clone.SetActive(false); + return true; + } + + private static GeneralRow? CreateGeneralRow() + { + if (_generalScroller == null || !_generalRowTemplate) + { + return null; + } + + var go = Object.Instantiate(_generalRowTemplate!, _generalScroller.Inner); + go.name = "GeneralOptionRow"; + + // The game would re-translate these and overwrite our text. + foreach (var translator in go.GetComponentsInChildren(true)) + { + translator.Destroy(); + } + + // Label is the left-most text, value is the right-most (same as the vanilla entries). + var texts = go.GetComponentsInChildren(true).OrderBy(t => t.transform.position.x).ToArray(); + if (texts.Length < 2) + { + Warning("Vanilla settings row has fewer than two text fields; cannot clone it for the general pages."); + go.Destroy(); + _generalScrollerFailed = true; + return null; + } + + return new GeneralRow(go, texts[0], texts[^1]); + } + + private static void EnterGeneralTab() + { + if (!_uiInitialized) + { + return; + } + + RefreshGeneralTab(true); + } + + private static void LeaveGeneralTab() + { + if (_generalScroller != null) + { + _generalScroller.gameObject.SetActive(false); + } + + if (_generalNoResults) + { + _generalNoResults!.SetActive(false); + } + + RestoreSortButtons(); + } + + private static void ChangeGeneralPage(int delta) + { + var total = _generalPages.Count + 1; + if (total <= 1) + { + return; + } + + _generalPage = (_generalPage + delta + total) % total; + RefreshGeneralTab(true); + } + + /// + /// Rebuilds pages from live option data and redraws the General tab. + /// Pages are rebuilt every time because values, group visibility and the gamemode can change. + /// + private static void RefreshGeneralTab(bool scrollToTop) + { + var guide = MatchInfoGuide.Instance; + if (!_uiInitialized || !guide) + { + return; + } + + _generalPages = GuideOptionPages.BuildPages(guide.GetBoolString); + if (_generalPage > _generalPages.Count) + { + _generalPage = 0; + } + + var vanillaTab = guide.settingsTabs[GeneralTabIndex]; + + // No MiraAPI general options in this match: leave the vanilla tab exactly as it always was. + if (_generalPages.Count == 0) + { + _generalSearching = false; + _generalScroller?.gameObject.SetActive(false); + _generalNoResults?.SetActive(false); + vanillaTab.SetActive(true); + RestoreSortButtons(); + UpdateGeneralTitle(); + return; + } + + var searchText = GetSearchText().Trim(); + _generalSearching = searchText.Length > 0; + + if (_generalSearching) + { + ShowGeneralEntries(GuideOptionPages.Search(_generalPages, searchText), true, scrollToTop); + vanillaTab.SetActive(false); + RepurposeSortButtons(false); + } + else if (_generalPage == 0) + { + _generalScroller?.gameObject.SetActive(false); + _generalNoResults?.SetActive(false); + vanillaTab.SetActive(true); + RepurposeSortButtons(true); + } + else + { + ShowGeneralEntries(_generalPages[_generalPage - 1].Entries, false, scrollToTop); + vanillaTab.SetActive(false); + RepurposeSortButtons(true); + } + + UpdateGeneralTitle(); + } + + private static void ShowGeneralEntries(IReadOnlyList entries, bool showGroup, bool scrollToTop) + { + var guide = MatchInfoGuide.Instance; + if (!guide || !EnsureGeneralScroller(guide)) + { + // Could not clone the vanilla rows: fall back to the plain vanilla tab rather than a blank page. + guide?.settingsTabs[GeneralTabIndex].SetActive(true); + return; + } + + while (GeneralRows.Count < entries.Count) + { + var row = CreateGeneralRow(); + if (row == null) + { + break; + } + + GeneralRows.Add(row); + } + + for (var i = 0; i < GeneralRows.Count; i++) + { + var row = GeneralRows[i]; + if (i >= entries.Count) + { + row.Root.SetActive(false); + continue; + } + + var entry = entries[i]; + row.Label.text = showGroup ? $"{entry.GroupName}\n{entry.Title}" : entry.Title; + row.Value.text = entry.Value; + row.Root.transform.localPosition = _generalRowOrigin + + (_generalRowColumnStep * (i % _generalColumns)) + + new Vector3(0f, _generalRowHeight * (i / _generalColumns), 0f); + row.Root.SetActive(true); + } + + var rows = Mathf.CeilToInt(Mathf.Min(entries.Count, GeneralRows.Count) / (float)_generalColumns); + _generalScroller!.gameObject.SetActive(true); + _generalScroller.SetYBoundsMax(Mathf.Clamp((rows * Mathf.Abs(_generalRowHeight)) - _generalVisibleHeight, 0f, 999f)); + _generalNoResults?.SetActive(entries.Count == 0); + if (scrollToTop) + { + _generalScroller.ScrollToTop(); + } + } + + private static string GetGeneralTitle() + { + var baseTitle = MiraLocaleManager.Get("MiraApi.Wiki.SettingsTab"); + if (!_uiInitialized || _generalPages.Count == 0) + { + return baseTitle; + } + + if (_generalSearching) + { + return $"{baseTitle}: {MiraLocaleManager.Get("MiraApi.Wiki.SearchResults", "Search Results")}"; + } + + var counter = $"({_generalPage + 1}/{_generalPages.Count + 1})"; + return _generalPage == 0 + ? $"{baseTitle} {counter}" + : $"{baseTitle}: {_generalPages[_generalPage - 1].Title} {counter}"; + } + + private static void UpdateGeneralTitle() + { + if (_titleText && IsGeneralTabActive) + { + _titleText.text = GetGeneralTitle(); + } + } + + /// + /// Turns the Filter button into "previous page" and the Grouping button into "next page", + /// and hides the Order button (it only sorts roles/modifiers). + /// + /// False while searching, since search results are a single page. + private static void RepurposeSortButtons(bool paginationVisible) + { + if (!SearchSortingFilterButton || !SearchSortingGroupButton || !SearchSortingOrderButton) + { + return; + } + + if (!_sortButtonsRepurposed) + { + _savedFilterLabel = FilterSortingTmp.text; + _savedGroupLabel = GroupSortingTmp.text; + _savedFilterIconScale = _searchSortingFilterIdle.transform.localScale; + _savedGroupIconScale = _searchSortingGroupIdle.transform.localScale; + + SetPaginationSprites(_searchSortingFilterIdle, _searchSortingFilterHover, true); + SetPaginationSprites(_searchSortingGroupIdle, _searchSortingGroupHover, false); + FilterSortingTmp.text = MiraLocaleManager.Get("MiraApi.Wiki.PrevPage", "Prev"); + GroupSortingTmp.text = MiraLocaleManager.Get("MiraApi.Wiki.NextPage", "Next"); + _sortButtonsRepurposed = true; + } + + SearchSortingOrderButton.gameObject.SetActive(false); + SearchSortingFilterButton.gameObject.SetActive(paginationVisible); + SearchSortingGroupButton.gameObject.SetActive(paginationVisible); + } + + private static void SetPaginationSprites(SpriteRenderer idle, SpriteRenderer hover, bool flip) + { + var idleSprite = MiraAssets.NextButton.LoadAsset(); + var hoverSprite = MiraAssets.NextButtonActive.LoadAsset(); + + // Match the on-screen size of the sort icon being replaced, since the arrow art differs in size. + var currentHeight = idle.sprite ? idle.sprite.bounds.size.y * idle.transform.localScale.y : 0f; + var newHeight = idleSprite.bounds.size.y; + var scale = currentHeight > 0f && newHeight > 0f ? currentHeight / newHeight : 1f; + + idle.sprite = idleSprite; + hover.sprite = hoverSprite; + idle.flipX = hover.flipX = flip; + idle.transform.localScale = hover.transform.localScale = new Vector3(scale, scale, 1f); + } + + private static void RestoreSortButtons() + { + if (!_sortButtonsRepurposed) + { + return; + } + + _sortButtonsRepurposed = false; + if (!SearchSortingFilterButton || !SearchSortingGroupButton || !SearchSortingOrderButton) + { + return; + } + + _searchSortingFilterIdle.sprite = GetSortFilterSprite(_sortFilter, false); + _searchSortingFilterHover.sprite = GetSortFilterSprite(_sortFilter, true); + _searchSortingGroupIdle.sprite = GetSortGroupSprite(_sortGrouping, false); + _searchSortingGroupHover.sprite = GetSortGroupSprite(_sortGrouping, true); + _searchSortingFilterIdle.flipX = _searchSortingFilterHover.flipX = false; + _searchSortingGroupIdle.flipX = _searchSortingGroupHover.flipX = false; + _searchSortingFilterIdle.transform.localScale = + _searchSortingFilterHover.transform.localScale = _savedFilterIconScale; + _searchSortingGroupIdle.transform.localScale = + _searchSortingGroupHover.transform.localScale = _savedGroupIconScale; + FilterSortingTmp.text = _savedFilterLabel; + GroupSortingTmp.text = _savedGroupLabel; + SearchSortingOrderButton.gameObject.SetActive(true); + SearchSortingFilterButton.gameObject.SetActive(true); + SearchSortingGroupButton.gameObject.SetActive(true); + } } public enum SortingGroups @@ -1700,4 +2152,4 @@ public enum SortingFilter EnabledOnly, DisabledOnly, AllRoles, -} +} \ No newline at end of file diff --git a/MiraAPI/Resources/Locale/en_US.xml b/MiraAPI/Resources/Locale/en_US.xml index c210db0..7d27154 100644 --- a/MiraAPI/Resources/Locale/en_US.xml +++ b/MiraAPI/Resources/Locale/en_US.xml @@ -60,5 +60,9 @@ Outlive the timer or eliminate the crew. Filter Grouping Order + Vanilla + Search Results + Previous + Next No Results Found diff --git a/MiraAPI/Utilities/GuideOptionsPatches.cs b/MiraAPI/Utilities/GuideOptionsPatches.cs new file mode 100644 index 0000000..c84cb17 --- /dev/null +++ b/MiraAPI/Utilities/GuideOptionsPatches.cs @@ -0,0 +1,203 @@ +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 Reactor.Utilities; + +namespace MiraAPI.Utilities; + +internal sealed record GuideOptionEntry(string Title, string Value, string GroupName, string ModAbbreviation); + +internal sealed class GuideOptionPage(AbstractOptionGroup group, string modAbbreviation, IReadOnlyList entries) +{ + public AbstractOptionGroup Group { get; } = group; + + public string Title { get; } = group.GroupName.Translate(); + + public string ModAbbreviation { get; } = modAbbreviation; + + public IReadOnlyList Entries { get; } = entries; +} + +internal static class GuideOptionPages +{ + private static readonly Regex TrailingZeroRegex = new(@"\.0+(?!\d)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100)); + public static List BuildPages(Func formatBool) + { + var pages = new List(); + var mode = CustomGameModeManager.ActiveMode; + + // Same plugin set the in-game options menu lists (skips mods that hide themselves from it). + foreach (var plugin in MiraPluginManager.Instance.PluginsWithOptionsOrGameModes + .OrderBy(p => p.MiraPlugin.GetAbbreviatedModName(), StringComparer.OrdinalIgnoreCase)) + { + var modAbbreviation = plugin.MiraPlugin.GetAbbreviatedModName(); + + var groups = plugin.InternalOptionGroups + .Where(g => IsGeneralGroup(g, mode) && SafeGroupVisible(g)) + .OrderBy(g => g.GroupPriority) + .ThenBy(g => g.GroupName, StringComparer.OrdinalIgnoreCase); + + foreach (var group in groups) + { + var groupName = group.GroupName.Translate(); + var entries = new List(); + + foreach (var option in group.Options) + { + if (!SafeOptionVisible(option)) + { + continue; + } + + var entry = TryCreateEntry(option, groupName, modAbbreviation, formatBool); + if (entry != null) + { + entries.Add(entry); + } + } + + if (entries.Count > 0) + { + pages.Add(new GuideOptionPage(group, modAbbreviation, entries)); + } + } + } + + return pages; + } + public static List Search(IEnumerable pages, string query) + { + query = query.Trim(); + if (query.Length == 0) + { + return []; + } + + const StringComparison cmp = StringComparison.OrdinalIgnoreCase; + return pages + .SelectMany(p => p.Entries) + .Select(e => (Entry: e, Rank: Rank(e, query, cmp))) + .Where(x => x.Rank < int.MaxValue) + .OrderBy(x => x.Rank) + .Select(x => x.Entry) + .ToList(); + } + + 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 IsGeneralGroup(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.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.Warning($"Visible threw for option '{option.Title}': {e.Message}"); + return false; + } + } + + private static GuideOptionEntry? TryCreateEntry( + IModdedOption option, + string groupName, + string modAbbreviation, + Func formatBool) + { + try + { + var title = TranslationController.Instance.GetString(option.StringName); + var value = FormatValue(option, formatBool); + return new GuideOptionEntry(title, value, groupName, modAbbreviation); + } + catch (Exception e) + { + Logger.Warning($"Could not format option '{option.Title}' for the guide: {e.Message}"); + return null; + } + } + + private static string FormatValue(IModdedOption option, Func 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()); + } + } +} \ No newline at end of file