diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs
index 0ca73e0..cf108b3 100644
--- a/MiraAPI/Modifiers/BaseModifier.cs
+++ b/MiraAPI/Modifiers/BaseModifier.cs
@@ -9,8 +9,6 @@ using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using TMPro;
using UnityEngine;
-using UnityEngine.UI;
-using Object = UnityEngine.Object;
namespace MiraAPI.Modifiers;
@@ -113,54 +111,15 @@ public abstract class BaseModifier : IOptionable
/// The of the wiki.
public virtual GameObject GetAdvancedWiki(MatchInfoGuide guide, TextMeshPro titleText, Scroller parent)
{
- parent.ScrollToTop();
- var obj = Helpers.CreateAdvancedWikiTab(
+ var abilities = WikiAbilities;
+ return Helpers.CreateAdvancedWikiPage(
guide,
- ModifierNameLocale,
- ModifierName + $" ({ModifierCategoryTitle})",
- ModifierWikiDescription,
titleText,
- out var desc);
- var num = 0;
- if (WikiAbilities.Count != 0)
- {
- var grid = Object.Instantiate(parent.Inner, obj.transform);
- var layoutGroup = grid.GetComponent();
- layoutGroup.startAxis = GridLayoutGroup.Axis.Vertical;
- layoutGroup.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
- layoutGroup.spacing = new Vector2(0.75f, 0.4f);
- grid.DestroyChildren();
- foreach (var ability in WikiAbilities)
- {
- num++;
- var panel = Object.Instantiate(
- guide.MatchInfoRolePanelPrefab,
- grid);
- panel.roleCount.text = ability.AbilityType;
- panel.roleCount.transform.localPosition += new Vector3(-0.04f, 0);
- panel.roleName.text = ability.Name;
- panel.roleName.transform.localPosition += new Vector3(-0.04f, 0);
- panel.roleDescription.text = ability.Description;
- panel.roleDescription.rectTransform.sizeDelta = new Vector2(2.601f, 0.8f);
- panel.roleDescription.transform.localPosition += new Vector3(0, -0.1f);
- panel.roleIcon.sprite = ability.Icon.LoadAsset();
- panel.roleIcon.SetSizeLimit(0.13f);
-
- panel.roleIcon.material.SetInt(PlayerMaterial.MaskLayer, 50);
- panel.roleName.fontMaterial.SetFloat(panel.STENCIL_NAME, 50f);
- panel.roleDescription.fontMaterial.SetFloat(panel.STENCIL_NAME, 50f);
- panel.roleCount.fontMaterial.SetFloat(panel.STENCIL_NAME, 50f);
- panel.roleIcon.transform.localScale = new Vector3(3.5f, 3.5f, 1f);
- }
-
- grid.localPosition = new Vector3(-3.9f, 1.1f - desc.textBounds.size.y, 0f);
- grid.localScale = new Vector3(1.3f, 1.3f, 1);
- }
-
- obj.transform.SetParent(parent.Inner.transform);
- obj.transform.localPosition = new Vector3(0f, 0f, 0f);
- parent.SetYBoundsMax(Mathf.Clamp(desc.textBounds.size.y - 2 + Mathf.Ceil(num / 2f) * 1.45f, 0f, 999f));
- return obj;
+ parent,
+ ModifierNameLocale,
+ $"{this.GetTmpIcon()} {ModifierName} ({ModifierCategoryTitle})".TrimStart(),
+ ModifierWikiDescription,
+ abilities);
}
///
diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs
index 7f0920d..e65f4c3 100644
--- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs
+++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs
@@ -618,6 +618,8 @@ internal static class GameOptionsMenuPatch
{
menu.MapPicker.gameObject.SetActive(false);
var mod = MenuState.Instance.CurrentMod;
+ var container = MenuState.Instance.CurrentContainer.transform;
+ PresetManager.DefaultPresets.TryGetValue(mod, out var defaultPreset);
var filteredGroups = MenuState.Instance.CurrentMenu switch
{
@@ -643,15 +645,13 @@ internal static class GameOptionsMenuPatch
{
foreach (var group in filteredGroups)
{
- yield return CoCreateGroup(menu, group);
+ yield return CoCreateGroup(menu, group, container, defaultPreset);
}
}
}
- private static IEnumerator CoCreateGroup(GameOptionsMenu menu, AbstractOptionGroup group)
+ private static IEnumerator CoCreateGroup(GameOptionsMenu menu, AbstractOptionGroup group, Transform container, OptionPreset? defaultPreset)
{
- var container = MenuState.Instance.CurrentContainer.transform;
-
group.Ready = false;
if (group.Header == null || !group.Header)
{
@@ -713,12 +713,6 @@ internal static class GameOptionsMenuPatch
menu.playerOptionOrigin,
container));
- OptionPreset? defaultPreset = null;
- if (PresetManager.DefaultPresets.TryGetValue(MenuState.Instance.CurrentMod, out var preset))
- {
- defaultPreset = preset;
- }
-
yield return options.CoLoopWithBudget(newOpt =>
{
newOpt.SetClickMask(menu.ButtonClickMask);
@@ -765,73 +759,53 @@ internal static class GameOptionsMenuPatch
}
menu.Children.Add(newOpt);
- var resetBtn = new GameObject("ResetOption")
+ if (defaultPreset != null && defaultPreset.IsOptionInPreset(newOpt) &&
+ newOpt is ToggleOption or NumberOption or StringOption)
{
- transform =
+ var resetBtn = new GameObject("ResetOption")
{
- parent = newOpt.transform,
- localScale = new(.5f, .5f, 1),
- localPosition = new Vector3(-3.1f, 0f, -2f),
- },
- layer = LayerMask.NameToLayer("UI"),
- };
- var resetRend = resetBtn.AddComponent();
- resetRend.sprite = MiraAssets.ResetButton.LoadAsset();
- resetRend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
- resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor)
- ? Color.white
- : group.GroupColor.FindAlternateColor();
- var resetBoxCol = resetBtn.gameObject.AddComponent();
- resetBoxCol.size = new Vector2(1f, 1f);
- resetBoxCol.offset = new Vector2(0, 0);
- var passiveButton = resetBtn.AddComponent();
- passiveButton.OnClick = new Button.ButtonClickedEvent();
- passiveButton.ClickSound = menu.BackButton.GetComponent().ClickSound;
- passiveButton.OnMouseOver = new UnityEvent();
- passiveButton.OnMouseOver.AddListener(
- (UnityAction)(() =>
- {
- resetRend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor
- ? group.GroupColor
- : MiraAssets.AcceptedTeal;
- }));
- passiveButton.OnMouseOut = new UnityEvent();
- passiveButton.OnMouseOut.AddListener(
- (UnityAction)(() =>
- {
- resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor)
- ? Color.white
- : group.GroupColor.FindAlternateColor();
- }));
- if (newOpt is ToggleOption toggleOpt)
- {
+ transform =
+ {
+ parent = newOpt.transform,
+ localScale = new(.5f, .5f, 1),
+ localPosition = new Vector3(-3.1f, 0f, -2f),
+ },
+ layer = LayerMask.NameToLayer("UI"),
+ };
+ var resetRend = resetBtn.AddComponent();
+ resetRend.sprite = MiraAssets.ResetButton.LoadAsset();
+ resetRend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
+ resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor)
+ ? Color.white
+ : group.GroupColor.FindAlternateColor();
+ var resetBoxCol = resetBtn.gameObject.AddComponent();
+ resetBoxCol.size = new Vector2(1f, 1f);
+ resetBoxCol.offset = new Vector2(0, 0);
+ var passiveButton = resetBtn.AddComponent();
+ passiveButton.OnClick = new Button.ButtonClickedEvent();
+ passiveButton.ClickSound = menu.BackButton.GetComponent().ClickSound;
+ passiveButton.OnMouseOver = new UnityEvent();
+ passiveButton.OnMouseOver.AddListener(
+ (UnityAction)(() =>
+ {
+ resetRend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor
+ ? group.GroupColor
+ : MiraAssets.AcceptedTeal;
+ }));
+ passiveButton.OnMouseOut = new UnityEvent();
+ passiveButton.OnMouseOut.AddListener(
+ (UnityAction)(() =>
+ {
+ resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor)
+ ? Color.white
+ : group.GroupColor.FindAlternateColor();
+ }));
passiveButton.OnClick.AddListener(
(UnityAction)(() =>
{
- defaultPreset!.ResetOption(toggleOpt);
+ defaultPreset.ResetOption(newOpt);
}));
}
- else if (newOpt is NumberOption numOpt)
- {
- passiveButton.OnClick.AddListener(
- (UnityAction)(() =>
- {
- defaultPreset!.ResetOption(numOpt);
- }));
- }
- else if (newOpt is StringOption strOpt)
- {
- passiveButton.OnClick.AddListener(
- (UnityAction)(() =>
- {
- defaultPreset!.ResetOption(strOpt);
- }));
- }
-
- if (!defaultPreset!.IsOptionInPreset(newOpt))
- {
- resetBtn.Destroy();
- }
newOpt.Initialize();
newOpt.gameObject.SetActive(false);
diff --git a/MiraAPI/Patches/Options/OptionsPatches.cs b/MiraAPI/Patches/Options/OptionsPatches.cs
index 8ed0f7d..0b7f5b9 100644
--- a/MiraAPI/Patches/Options/OptionsPatches.cs
+++ b/MiraAPI/Patches/Options/OptionsPatches.cs
@@ -4,7 +4,6 @@ using MiraAPI.Roles;
using MiraAPI.Translation;
using MiraAPI.Utilities;
using UnityEngine;
-using Object = Il2CppSystem.Object;
namespace MiraAPI.Patches.Options;
diff --git a/MiraAPI/Patches/RoleGuidePatches.cs b/MiraAPI/Patches/RoleGuidePatches.cs
index 1a18950..e3b134a 100644
--- a/MiraAPI/Patches/RoleGuidePatches.cs
+++ b/MiraAPI/Patches/RoleGuidePatches.cs
@@ -1,5 +1,7 @@
using System;
+using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.Linq;
using AmongUs.GameOptions;
@@ -11,6 +13,7 @@ using MiraAPI.Roles;
using MiraAPI.Translation;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
+using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
@@ -23,6 +26,13 @@ namespace MiraAPI.Patches;
[HarmonyPatch]
public static class RoleGuidePatches
{
+ private const int RolesTabIndex = 2;
+ private const int ModifiersTabIndex = 3;
+ private const float SearchDebounceSeconds = 0.3f;
+
+ private static readonly List RoleEntries = [];
+ private static readonly List ModifierEntries = [];
+
public static PassiveButton SearchIconButton;
private static SpriteRenderer _searchIconIdle;
private static SpriteRenderer _searchIconHover;
@@ -36,10 +46,25 @@ public static class RoleGuidePatches
private static SpriteRenderer _searchSortingFilterIdle;
private static SpriteRenderer _searchSortingFilterHover;
private static Scroller _rolesScroller;
- private static bool _needsRoleRefresh;
- private static bool _needsModifierRefresh;
- private static bool _needsToggledRoleRefresh;
- private static bool _needsToggledModifierRefresh;
+ private static Scroller _modifiersScroller;
+ private static Scroller _advancedInfoTabScroller;
+ private static TextBoxTMP _searchBoxTmp;
+ private static GameObject _currentAdvancedTabObject;
+ private static TextMeshPro _titleText;
+ private static bool _suppressSearchRefresh;
+ private static IEnumerator? _searchDebounce;
+ private static string _appliedSearch = string.Empty;
+ private static float _openedAt;
+ private static bool _uiInitialized;
+ private static MatchInfoGuide? _guide;
+ private static IEnumerator? _populate;
+ private static bool _loading;
+ private static LoadingRing? _loadingSpinner;
+ private static SortingFilter _sortFilter = SortingFilter.EnabledOnly;
+ private static SortingMethod _sortMethod = SortingMethod.Alphabetical;
+ private static SortingGroups _sortGrouping = SortingGroups.Ungrouped;
+
+ private static bool IsModifiersTabActive => MatchInfoGuide.Instance.activeTabIndex == ModifiersTabIndex;
[HarmonyPostfix]
[HarmonyPriority(Priority.First)]
@@ -47,20 +72,25 @@ public static class RoleGuidePatches
[HarmonyPatch(typeof(MatchInfoGuide), nameof(MatchInfoGuide.CreateHnSModeSettings))]
public static void CreateNormalModeSettings(MatchInfoGuide __instance)
{
- __instance.MatchInfoRoleMaskArea.transform.localPosition = new Vector3(-0.0184f, 0.15f, -0.1f);
+ ApplyPanelLayout(__instance);
+ }
- __instance.matchInfoPlayersMaskArea.transform.localPosition =
- __instance.matchInfoSettingsMaskArea.transform.localPosition = new Vector3(1.22f, -0.335f, -0.1f);
+ private static void ApplyPanelLayout(MatchInfoGuide instance)
+ {
+ instance.MatchInfoRoleMaskArea.transform.localPosition = new Vector3(-0.0184f, 0.15f, -0.1f);
- __instance.matchInfoPlayersMaskArea.size =
- __instance.matchInfoSettingsMaskArea.size =
- __instance.MatchInfoRoleMaskArea.size = new Vector2(-6, 1.8f);
+ instance.matchInfoPlayersMaskArea.transform.localPosition =
+ instance.matchInfoSettingsMaskArea.transform.localPosition = new Vector3(1.22f, -0.335f, -0.1f);
- var playerMenuGradient = __instance.matchInfoPlayersMaskArea.transform.parent.GetAllChildren()
+ instance.matchInfoPlayersMaskArea.size =
+ instance.matchInfoSettingsMaskArea.size =
+ instance.MatchInfoRoleMaskArea.size = new Vector2(-6, 1.8f);
+
+ var playerMenuGradient = instance.matchInfoPlayersMaskArea.transform.parent.GetAllChildren()
.First(x => x.name.Contains("BG_Gradient"));
- var settingsenuGradient = __instance.matchInfoSettingsMaskArea.transform.parent.GetAllChildren()
+ var settingsenuGradient = instance.matchInfoSettingsMaskArea.transform.parent.GetAllChildren()
.First(x => x.name.Contains("BG_Gradient"));
- var roleMenuGradient = __instance.MatchInfoRoleMaskArea.transform.parent.GetAllChildren()
+ var roleMenuGradient = instance.MatchInfoRoleMaskArea.transform.parent.GetAllChildren()
.First(x => x.name.Contains("BG_Gradient"));
playerMenuGradient
.GetComponent()
@@ -71,8 +101,6 @@ public static class RoleGuidePatches
.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
roleMenuGradient.transform.localScale = new Vector3(0.5297f, 0.1565f, 1);
roleMenuGradient.transform.localPosition = new Vector3(0, -0.62f, -5);
- var wikiTab = Object.Instantiate(__instance.settingsTabs[2], __instance.settingsTabs[2].transform.parent);
- _advancedInfoTabScroller = wikiTab.GetComponent();
}
[HarmonyPrefix]
@@ -81,6 +109,106 @@ public static class RoleGuidePatches
public static void Awake(MatchInfoGuide __instance)
{
__instance.transitionOpen.targetSize = 1.3f;
+ if (_guide != __instance)
+ {
+ ResetState();
+ }
+ }
+
+ ///
+ /// Clears all cached state tied to a previous instance.
+ /// Every game spawns a fresh guide, so anything we cached from the old one is a destroyed Unity object.
+ ///
+ private static void ResetState()
+ {
+ if (_populate != null)
+ {
+ Coroutines.Stop(_populate);
+ _populate = null;
+ }
+
+ if (_searchDebounce != null)
+ {
+ Coroutines.Stop(_searchDebounce);
+ _searchDebounce = null;
+ }
+
+ RoleEntries.Clear();
+ ModifierEntries.Clear();
+ _guide = null;
+ _uiInitialized = false;
+ _loading = false;
+ _appliedSearch = string.Empty;
+ _suppressSearchRefresh = false;
+ _titleText = null!;
+ _searchBoxTmp = null!;
+ _rolesScroller = null!;
+ _modifiersScroller = null!;
+ _advancedInfoTabScroller = null!;
+ _currentAdvancedTabObject = null!;
+ _loadingSpinner = null;
+ SearchIconButton = null!;
+ SearchSortingOrderButton = null!;
+ SearchSortingGroupButton = null!;
+ SearchSortingFilterButton = null!;
+ _searchIconIdle = null!;
+ _searchIconHover = null!;
+ _searchSortingOrderIdle = null!;
+ _searchSortingOrderHover = null!;
+ _searchSortingGroupIdle = null!;
+ _searchSortingGroupHover = null!;
+ _searchSortingFilterIdle = null!;
+ _searchSortingFilterHover = null!;
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(MatchInfoGuide), nameof(MatchInfoGuide.Awake))]
+ private static void AwakePostfix(MatchInfoGuide __instance)
+ {
+ Coroutines.Start(CoPreload(__instance));
+ }
+
+ public static void TriggerPreload(MatchInfoGuide instance)
+ {
+ if (!instance || _populate != null)
+ {
+ return;
+ }
+
+ Coroutines.Start(CoPreload(instance));
+ }
+
+ private static IEnumerator CoPreload(MatchInfoGuide instance)
+ {
+ while (instance && (!HudManager.InstanceExists || !GameManager.Instance))
+ {
+ yield return null;
+ }
+
+ if (!instance || GameManager.Instance.TryCast() == null)
+ {
+ yield break;
+ }
+
+ _guide = instance;
+ if (RoleEntries.Count == 0)
+ {
+ BuildEntries();
+ }
+
+ if (_populate != null)
+ {
+ yield break;
+ }
+
+ _populate = Coroutines.Start(CoPreloadPanels());
+ }
+
+ private static IEnumerator CoPreloadPanels()
+ {
+ yield return CoForEachBudgeted(RoleEntries, PreloadPanel);
+ yield return CoForEachBudgeted(ModifierEntries, PreloadPanel);
+ _populate = null;
}
[HarmonyPrefix]
@@ -88,6 +216,11 @@ public static class RoleGuidePatches
[HarmonyPatch(typeof(MatchInfoGuide), nameof(MatchInfoGuide.Open))]
public static bool Open(MatchInfoGuide __instance)
{
+ if (!HudManager.InstanceExists || !GameManager.Instance || !PlayerControl.LocalPlayer)
+ {
+ return false;
+ }
+
if (HudManager.Instance.GameMenu.IsOpen || HudManager.Instance.Chat.IsOpenOrOpening)
{
return false;
@@ -112,239 +245,64 @@ public static class RoleGuidePatches
var enabled = ActiveInputManager.currentControlType == ActiveInputManager.InputType.Joystick;
__instance.glyphL.enabled = enabled;
__instance.glyphR.enabled = enabled;
- if (!titleText)
+ if (!_titleText)
{
- titleText = __instance.transitionOpen.transform.FindChild("Text_Title")?.GetComponent()!;
- if (titleText)
+ _titleText = __instance.transitionOpen.transform.FindChild("Text_Title")?.GetComponent()!;
+ if (_titleText)
{
- titleText.transform.GetComponent().Destroy();
+ _titleText.transform.GetComponent().Destroy();
}
}
- if (titleText)
+ if (_titleText)
{
- titleText.text = TranslationController.Instance.GetString(StringNames.MatchInfoGuideTitle);
+ _titleText.text = TranslationController.Instance.GetString(StringNames.MatchInfoGuideTitle);
}
var regGame = GameManager.Instance.TryCast() != null;
if (regGame)
{
+ if (!_uiInitialized)
+ {
+ Initialize(__instance);
+ }
+
+ if (RoleEntries.Count == 0)
+ {
+ BuildEntries();
+ }
+
if (__instance.NormalModeSettings.Count == 0)
{
- var sortingOrderButton = Object.Instantiate(
- HudManager.Instance.SettingsButton,
- __instance.TabButtons[2].transform.parent);
- SearchSortingOrderButton = sortingOrderButton.GetComponent();
- FixSearchButton(
- SearchSortingOrderButton,
- "SearchSortingOrderButton",
- SwitchMethod,
- MiraAssets.SortingAzIdleSprite,
- MiraAssets.SortingAzHoverSprite,
- new Vector3(2.4f, 0.765f, -0.1f),
- out _searchSortingOrderIdle,
- out _searchSortingOrderHover);
-
- var sortingGroupButton = Object.Instantiate(
- sortingOrderButton,
- sortingOrderButton.transform.parent);
- SearchSortingGroupButton = sortingGroupButton.GetComponent();
- FixSearchButton(
- SearchSortingGroupButton,
- "SearchSortingGroupButton",
- SwitchGrouping,
- MiraAssets.GroupNoneIdleSprite,
- MiraAssets.GroupNoneHoverSprite,
- new Vector3(1.8f, 0.765f, -0.1f),
- out _searchSortingGroupIdle,
- out _searchSortingGroupHover);
-
- var sortingFilterButton = Object.Instantiate(
- sortingOrderButton,
- sortingOrderButton.transform.parent);
- SearchSortingFilterButton = sortingFilterButton.GetComponent();
- FixSearchButton(
- SearchSortingFilterButton,
- "SearchSortingFilterButton",
- SwitchFilter,
- MiraAssets.VisibleOnIdleSprite,
- MiraAssets.VisibleOnHoverSprite,
- new Vector3(1f, 0.765f, -0.1f),
- out _searchSortingFilterIdle,
- out _searchSortingFilterHover);
-
- __instance.numOfTabs = 4;
- __instance.TabButtons[2].SelectButton(true);
- __instance.MatchInfoRoleMaskArea.transform.localPosition = new Vector3(-0.0184f, 0.15f, -0.1f);
-
- __instance.matchInfoPlayersMaskArea.transform.localPosition =
- __instance.matchInfoSettingsMaskArea.transform.localPosition = new Vector3(1.22f, -0.335f, -0.1f);
-
- __instance.matchInfoPlayersMaskArea.size =
- __instance.matchInfoSettingsMaskArea.size =
- __instance.MatchInfoRoleMaskArea.size = new Vector2(-6, 1.8f);
-
- var playerMenuGradient = __instance.matchInfoPlayersMaskArea.transform.parent.GetAllChildren()
- .First(x => x.name.Contains("BG_Gradient"));
- var settingsenuGradient = __instance.matchInfoSettingsMaskArea.transform.parent.GetAllChildren()
- .First(x => x.name.Contains("BG_Gradient"));
- var roleMenuGradient = __instance.MatchInfoRoleMaskArea.transform.parent.GetAllChildren()
- .First(x => x.name.Contains("BG_Gradient"));
- playerMenuGradient
- .GetComponent()
- .maskInteraction =
- settingsenuGradient.GetComponent()
- .maskInteraction =
- roleMenuGradient.GetComponent()
- .maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
- roleMenuGradient.transform.localScale = new Vector3(0.5297f, 0.1565f, 1);
- roleMenuGradient.transform.localPosition = new Vector3(0, -0.62f, -5);
- __instance.MatchInfoRoleMaskArea.material.SetInt(PlayerMaterial.MaskLayer, 50);
- __instance.matchInfoSettingsMaskArea.material.SetInt(PlayerMaterial.MaskLayer, 50);
-
- var modifiersTab = Object.Instantiate(
- __instance.settingsTabs[2],
- __instance.settingsTabs[2].transform.parent);
- modifiersTab.name = "ModifiersPanel";
- modifiersTab.transform.FindChild("MaskArea")?.transform.localPosition =
- new Vector3(-0.0184f, 0.15f, -0.1f);
- modifiersTab.transform.GetAllChildren()
- .First(x => x.name.Contains("BG_Gradient")).transform.localPosition = new Vector3(0, -0.62f, -5);
- _modifiersScroller = modifiersTab.GetComponent();
- __instance.settingsTabs.Add(modifiersTab);
-
- var wikiTab = Object.Instantiate(
- __instance.settingsTabs[2],
- __instance.settingsTabs[2].transform.parent);
- wikiTab.name = "AdvancedWikiPanels";
- wikiTab.transform.FindChild("MaskArea")?.transform.localPosition = new Vector3(-0.0184f, 0.15f, -0.1f);
- wikiTab.transform.GetAllChildren()
- .First(x => x.name.Contains("BG_Gradient")).transform.localPosition = new Vector3(0, -0.62f, -5);
- _advancedInfoTabScroller = wikiTab.GetComponent();
-
- DisplayNormalRoleSettings(__instance, true);
-
- var searchIconButton = Object.Instantiate(
- sortingOrderButton,
- sortingOrderButton.transform.parent);
- SearchIconButton = searchIconButton.GetComponent();
- FixSearchButton(
- SearchIconButton,
- "SearchIconButton",
- ClearSearchText,
- MiraAssets.SearchIconIdleSprite,
- MiraAssets.SearchIconHoverSprite,
- new Vector3(-2.79f, 0.765f, -0.1f),
- out _searchIconIdle,
- out _searchIconHover);
-
- var searchBox = Object.Instantiate(
- __instance.TabButtons[2].gameObject,
- __instance.TabButtons[2].transform.parent);
- var searchButton = searchBox.GetComponent();
- var tmpText = searchButton.transform.GetChild(0).GetComponent();
- tmpText.GetComponent().Destroy();
- tmpText.color = new Color(0.75f, 0.75f, 0.75f);
- tmpText.text = string.Empty;
- tmpText.fontSizeMax = 4;
- tmpText.overflowMode = TextOverflowModes.Ellipsis;
- tmpText.alignment = TextAlignmentOptions.Left;
- tmpText.horizontalAlignment = HorizontalAlignmentOptions.Left;
- tmpText.rectTransform.offsetMax = new Vector2(-0.1531f, 0.2972f);
- tmpText.rectTransform.sizeDelta = new Vector2(3, 1);
- tmpText.transform.localPosition = new Vector3(0, 0.0343f, -0.2f);
- var inactive = searchButton.inactiveSprites;
- var selected = searchButton.selectedSprites;
- var highlight = searchButton.activeSprites;
- var disabledSprite = searchButton.disabledSprites;
- disabledSprite.GetComponent().size = new Vector2(3, 0.6f);
- searchButton.Destroy();
- inactive.gameObject.SetActive(false);
- selected.gameObject.SetActive(false);
- highlight.gameObject.SetActive(false);
- disabledSprite.gameObject.SetActive(true);
- var button = searchBox.AddComponent();
- button.OnUp = true;
- searchBoxTmp = searchBox.AddComponent();
- searchBoxTmp.outputText = tmpText;
- button.OnClick.AddListener((UnityAction)(() => { searchBoxTmp.GiveFocus(); }));
- button.OnMouseOver = new UnityEvent();
- button.OnMouseOut = new UnityEvent();
- _rolesScroller = __instance.settingsTabs[2].GetComponent();
- searchBoxTmp.OnChange = new Button.ButtonClickedEvent();
- searchBoxTmp.OnChange.AddListener(
- (UnityAction)(() =>
- {
- var text = searchBoxTmp.outputText.text;
- if (text != string.Empty)
- {
- _searchIconIdle.sprite = MiraAssets.SearchClearIdleSprite;
- _searchIconHover.sprite = MiraAssets.SearchClearHoverSprite;
- }
- else
- {
- _searchIconIdle.sprite = MiraAssets.SearchIconIdleSprite;
- _searchIconHover.sprite = MiraAssets.SearchIconHoverSprite;
- }
-
- if (MatchInfoGuide.Instance.activeTabIndex != 3)
- {
- SortAllRoles(text);
- _rolesScroller.ScrollToTop();
- }
- else
- {
- SortAllModifiers(text);
- _modifiersScroller.ScrollToTop();
- }
- }));
- searchBoxTmp.transform.localPosition = new Vector3(-1.438f, 0.756f, -0.2f);
-
- var playerButton = __instance.TabButtons[0];
- FixTabButton(
- playerButton,
- MiraAssets.WikiPlayersButtonIdleSprite,
- MiraAssets.WikiPlayersButtonOpenSprite,
- MiraAssets.WikiPlayersButtonHoverSprite);
-
- var settingButton = __instance.TabButtons[1];
- FixTabButton(
- settingButton,
- MiraAssets.WikiSettingsButtonIdleSprite,
- MiraAssets.WikiSettingsButtonOpenSprite,
- MiraAssets.WikiSettingsButtonHoverSprite);
-
- var rolesButton = __instance.TabButtons[2];
- FixTabButton(
- rolesButton,
- MiraAssets.WikiRolesButtonIdleSprite,
- MiraAssets.WikiRolesButtonOpenSprite,
- MiraAssets.WikiRolesButtonHoverSprite);
- rolesButton.OnClick = new Button.ButtonClickedEvent();
- rolesButton.OnClick.AddListener((Action)(() => OpenRolesTab()));
-
- var modifiersButton = Object.Instantiate(
- __instance.TabButtons[2],
- __instance.TabButtons[2].transform.parent);
- FixTabButton(
- modifiersButton,
- MiraAssets.WikiRolesButtonIdleSprite,
- MiraAssets.WikiRolesButtonOpenSprite,
- MiraAssets.WikiRolesButtonHoverSprite);
- modifiersButton.OnClick = new Button.ButtonClickedEvent();
- modifiersButton.OnClick.AddListener((Action)(() => OpenModifiersTab()));
- __instance.TabButtons.Add(modifiersButton);
-
- playerButton.transform.localPosition = new Vector3(-3.6f, 0.656f, -0.2f);
- settingButton.transform.localPosition = new Vector3(-3.6f, 0.056f, -0.2f);
- rolesButton.transform.localPosition = new Vector3(-3.6f, -0.544f, -0.2f);
- modifiersButton.transform.localPosition = new Vector3(-3.6f, -1.144f, -0.2f);
- }
- else
- {
- DisplayNormalRoleSettings(__instance, false);
- ClearSearchText();
+ __instance.CreateSettingsEntry(
+ StringNames.GameNumImpostors,
+ GameManager.Instance.AllGameSettingData[StringNames.GameNumImpostors]
+ .GetValueString(GameManager.Instance.LogicOptions.NumImpostors));
+ __instance.CreateSettingsEntry(
+ StringNames.GameKillCooldown,
+ GameManager.Instance.AllGameSettingData[StringNames.GameKillCooldown]
+ .GetValueString(GameManager.Instance.LogicOptions.GetKillCooldown()));
+ __instance.CreateSettingsEntry(
+ StringNames.GameEmergencyCooldown,
+ GameManager.Instance.AllGameSettingData[StringNames.GameEmergencyCooldown]
+ .GetValueString(GameManager.Instance.LogicOptions.GetEmergencyCooldown()));
+ __instance.CreateSettingsEntry(
+ StringNames.GameVisualTasks,
+ __instance.GetBoolString(GameManager.Instance.LogicOptions.GetVisualTasks()));
+ __instance.CreateSettingsEntry(
+ StringNames.GameAnonymousVotes,
+ __instance.GetBoolString(GameManager.Instance.LogicOptions.GetAnonymousVotes()));
+ __instance.CreateSettingsEntry(
+ StringNames.GameConfirmImpostor,
+ __instance.GetBoolString(GameManager.Instance.LogicOptions.GetConfirmImpostor()));
+ __instance.CreateSettingsEntry(
+ StringNames.GameTaskBarMode,
+ GameManager.Instance.LogicOptions.GetTaskBarMode().ToString());
+ __instance.CreatePlayerEntries();
+ __instance.rolesEnabledMessage.SetActive(false);
}
+
+ ResetSearchBox();
}
else if (__instance.HnSModeSettings.Count == 0)
{
@@ -354,19 +312,319 @@ public static class RoleGuidePatches
}
PlayerControl.LocalPlayer.NetTransform.Halt();
+ HidePanels(RoleEntries);
+ HidePanels(ModifierEntries);
__instance.MatchInfoParent.SetActive(true);
- var instance = ControllerManager.Instance;
- var currentUiState = ControllerManager.Instance.CurrentUiState;
- var controllerSelectable = __instance.ControllerSelectable;
- instance.SetUpSelectables(currentUiState, controllerSelectable[^1], __instance.ControllerSelectable);
- var instance2 = ControllerManager.Instance;
- var controllerSelectable2 = __instance.ControllerSelectable;
- instance2.SetCurrentSelected(controllerSelectable2[^1]);
- __instance.SetActiveTab(regGame ? 2 : 0);
+ _openedAt = Time.time;
+ if (__instance.ControllerSelectable.Count > 0)
+ {
+ var controllerManager = ControllerManager.Instance;
+ controllerManager.SetUpSelectables(
+ controllerManager.CurrentUiState,
+ __instance.ControllerSelectable[^1],
+ __instance.ControllerSelectable);
+ controllerManager.SetCurrentSelected(__instance.ControllerSelectable[^1]);
+ }
+
+ __instance.SetActiveTab(regGame ? RolesTabIndex : 0);
+ if (regGame)
+ {
+ OpenTab(RolesTabIndex, __instance.transitionOpen.duration);
+ }
+
return false;
}
- public static void FixTabButton(MatchInfoGuideTabButton button, Sprite inactive, Sprite selected, Sprite hover)
+ private static void Initialize(MatchInfoGuide instance)
+ {
+ if (_uiInitialized)
+ {
+ return;
+ }
+
+ if (_populate != null)
+ {
+ Coroutines.Stop(_populate);
+ _populate = null;
+ }
+
+ _guide = instance;
+ _loading = false;
+ var sortingOrderButton = Object.Instantiate(
+ HudManager.Instance.SettingsButton,
+ instance.TabButtons[2].transform.parent);
+ sortingOrderButton.gameObject.SetActive(true);
+ SearchSortingOrderButton = sortingOrderButton.GetComponent();
+ SetupSearchButton(
+ SearchSortingOrderButton,
+ "SearchSortingOrderButton",
+ SwitchMethod,
+ MiraAssets.SortingAzIdleSprite,
+ MiraAssets.SortingAzHoverSprite,
+ new Vector3(2.4f, 0.765f, -0.1f),
+ out _searchSortingOrderIdle,
+ out _searchSortingOrderHover);
+
+ var sortingGroupButton = Object.Instantiate(
+ sortingOrderButton,
+ sortingOrderButton.transform.parent);
+ sortingGroupButton.gameObject.SetActive(true);
+ SearchSortingGroupButton = sortingGroupButton.GetComponent();
+ SetupSearchButton(
+ SearchSortingGroupButton,
+ "SearchSortingGroupButton",
+ SwitchGrouping,
+ MiraAssets.GroupNoneIdleSprite,
+ MiraAssets.GroupNoneHoverSprite,
+ new Vector3(1.7f, 0.765f, -0.1f),
+ out _searchSortingGroupIdle,
+ out _searchSortingGroupHover);
+
+ var sortingFilterButton = Object.Instantiate(
+ sortingOrderButton,
+ sortingOrderButton.transform.parent);
+ sortingFilterButton.gameObject.SetActive(true);
+ SearchSortingFilterButton = sortingFilterButton.GetComponent();
+ SetupSearchButton(
+ SearchSortingFilterButton,
+ "SearchSortingFilterButton",
+ SwitchFilter,
+ MiraAssets.VisibleOnIdleSprite,
+ MiraAssets.VisibleOnHoverSprite,
+ new Vector3(1f, 0.765f, -0.1f),
+ out _searchSortingFilterIdle,
+ out _searchSortingFilterHover);
+
+ instance.numOfTabs = 4;
+ instance.TabButtons[2].SelectButton(true);
+ ApplyPanelLayout(instance);
+ instance.MatchInfoRoleMaskArea.material.SetInt(PlayerMaterial.MaskLayer, 50);
+ instance.matchInfoSettingsMaskArea.material.SetInt(PlayerMaterial.MaskLayer, 50);
+
+ var modifiersTab = Object.Instantiate(
+ instance.settingsTabs[RolesTabIndex],
+ instance.settingsTabs[RolesTabIndex].transform.parent);
+ modifiersTab.name = "ModifiersPanel";
+ modifiersTab.transform.FindChild("MaskArea")?.transform.localPosition =
+ new Vector3(-0.0184f, 0.15f, -0.1f);
+ modifiersTab.transform.GetAllChildren()
+ .First(x => x.name.Contains("BG_Gradient")).transform.localPosition = new Vector3(0, -0.62f, -5);
+ _modifiersScroller = modifiersTab.GetComponent();
+ instance.settingsTabs.Add(modifiersTab);
+
+ var wikiTab = Object.Instantiate(
+ instance.settingsTabs[RolesTabIndex],
+ instance.settingsTabs[RolesTabIndex].transform.parent);
+ wikiTab.name = "AdvancedWikiPanels";
+ wikiTab.transform.FindChild("MaskArea")?.transform.localPosition = new Vector3(-0.0184f, 0.15f, -0.1f);
+ wikiTab.transform.GetAllChildren()
+ .First(x => x.name.Contains("BG_Gradient")).transform.localPosition = new Vector3(0, -0.62f, -5);
+ _advancedInfoTabScroller = wikiTab.GetComponent();
+
+ var spinnerParent = instance.settingsTabs[RolesTabIndex].transform.parent;
+ var spinnerPos = spinnerParent.InverseTransformPoint(instance.MatchInfoRoleMaskArea.transform.position);
+ spinnerPos.z = -5f;
+ _loadingSpinner = LoadingRing.Create(spinnerParent, spinnerPos, 0.6f);
+ _loadingSpinner.GetComponent().color = MiraAssets.AcceptedTeal;
+
+ var searchIconButton = Object.Instantiate(
+ sortingOrderButton,
+ sortingOrderButton.transform.parent);
+ searchIconButton.gameObject.SetActive(true);
+ SearchIconButton = searchIconButton.GetComponent();
+ SetupSearchButton(
+ SearchIconButton,
+ "SearchIconButton",
+ ClearSearchText,
+ MiraAssets.SearchIconIdleSprite,
+ MiraAssets.SearchIconHoverSprite,
+ new Vector3(-2.79f, 0.765f, -0.1f),
+ out _searchIconIdle,
+ out _searchIconHover);
+
+ var searchBox = Object.Instantiate(
+ instance.TabButtons[2].gameObject,
+ instance.TabButtons[2].transform.parent);
+ var searchButton = searchBox.GetComponent();
+ var tmpText = searchButton.transform.GetChild(0).GetComponent();
+ tmpText.GetComponent().Destroy();
+ tmpText.color = new Color(0.75f, 0.75f, 0.75f);
+ tmpText.text = string.Empty;
+ tmpText.fontSizeMax = 4;
+ tmpText.overflowMode = TextOverflowModes.Ellipsis;
+ tmpText.alignment = TextAlignmentOptions.Left;
+ tmpText.horizontalAlignment = HorizontalAlignmentOptions.Left;
+ tmpText.rectTransform.offsetMax = new Vector2(-0.1531f, 0.2972f);
+ tmpText.rectTransform.sizeDelta = new Vector2(3, 1);
+ tmpText.transform.localPosition = new Vector3(0, 0.0343f, -0.2f);
+ var inactive = searchButton.inactiveSprites;
+ var selected = searchButton.selectedSprites;
+ var highlight = searchButton.activeSprites;
+ var disabledSprite = searchButton.disabledSprites;
+ disabledSprite.GetComponent().size = new Vector2(3, 0.6f);
+ searchButton.Destroy();
+ inactive.gameObject.SetActive(false);
+ selected.gameObject.SetActive(false);
+ highlight.gameObject.SetActive(false);
+ disabledSprite.gameObject.SetActive(true);
+ var button = searchBox.AddComponent();
+ button.OnUp = true;
+ var template = HudManager.Instance.Chat.freeChatField.textArea;
+ _searchBoxTmp = Object.Instantiate(template, searchBox.transform);
+ _searchBoxTmp.gameObject.SetActive(true);
+ _searchBoxTmp.name = "SearchField";
+ _searchBoxTmp.transform.localPosition = Vector3.zero;
+ _searchBoxTmp.OnEnter = new Button.ButtonClickedEvent();
+ _searchBoxTmp.OnChange = new Button.ButtonClickedEvent();
+ _searchBoxTmp.OnFocus = new Button.ButtonClickedEvent();
+ _searchBoxTmp.OnFocusLost = new Button.ButtonClickedEvent();
+ _searchBoxTmp.sendButtonGlyph = null;
+ _searchBoxTmp.SendOnFullChars = false;
+ _searchBoxTmp.ClearOnFocus = false;
+ _searchBoxTmp.characterLimit = 40;
+ _searchBoxTmp.AllowSymbols = true;
+ _searchBoxTmp.allowAllCharacters = true;
+ if (_searchBoxTmp.Background)
+ {
+ _searchBoxTmp.Background.enabled = false;
+ }
+
+ var boxCollider = _searchBoxTmp.GetComponent();
+ if (boxCollider)
+ {
+ boxCollider.size = new Vector2(3f, 0.6f);
+ boxCollider.offset = Vector2.zero;
+ }
+
+ var outputText = _searchBoxTmp.outputText;
+ var textParent = tmpText.transform.parent;
+ outputText.transform.SetParent(textParent, false);
+ outputText.GetComponent()?.Destroy();
+ CopyTextLayout(tmpText, outputText);
+ outputText.color = new Color(0.75f, 0.75f, 0.75f);
+ outputText.text = string.Empty;
+
+ var placeholderText = _searchBoxTmp.placeholderText;
+ if (placeholderText)
+ {
+ placeholderText.transform.SetParent(textParent, false);
+ placeholderText.GetComponent()?.Destroy();
+ CopyTextLayout(tmpText, placeholderText);
+ placeholderText.color = new Color(0.55f, 0.55f, 0.55f);
+ placeholderText.text = MiraLocaleManager.Get("MiraApi.Wiki.SearchPlaceholder");
+ placeholderText.gameObject.SetActive(true);
+ }
+
+ if (_searchBoxTmp.Pipe)
+ {
+ _searchBoxTmp.Pipe.transform.SetParent(textParent, false);
+ _searchBoxTmp.Pipe.transform.localPosition = new Vector3(
+ outputText.transform.localPosition.x -
+ (outputText.rectTransform.sizeDelta.x * outputText.rectTransform.pivot.x),
+ outputText.transform.localPosition.y + _searchBoxTmp.caretYOffset,
+ -0.3f);
+ }
+
+ tmpText.gameObject.Destroy();
+
+ button.OnClick.AddListener((UnityAction)(() => { _searchBoxTmp.GiveFocus(); }));
+ button.OnMouseOver = new UnityEvent();
+ button.OnMouseOut = new UnityEvent();
+ _rolesScroller = instance.settingsTabs[RolesTabIndex].GetComponent();
+ _searchBoxTmp.OnChange.AddListener(
+ (UnityAction)(() =>
+ {
+ if (GetSearchText() != string.Empty)
+ {
+ _searchIconIdle.sprite = MiraAssets.SearchClearIdleSprite;
+ _searchIconHover.sprite = MiraAssets.SearchClearHoverSprite;
+ }
+ else
+ {
+ _searchIconIdle.sprite = MiraAssets.SearchIconIdleSprite;
+ _searchIconHover.sprite = MiraAssets.SearchIconHoverSprite;
+ }
+
+ if (_suppressSearchRefresh)
+ {
+ return;
+ }
+
+ QueueSearchRefresh();
+ }));
+ searchBox.transform.localPosition = new Vector3(-1.438f, 0.756f, -0.2f);
+
+ var playerButton = instance.TabButtons[0];
+ SetupTabButton(
+ playerButton,
+ MiraAssets.WikiPlayersButtonIdleSprite,
+ MiraAssets.WikiPlayersButtonOpenSprite,
+ MiraAssets.WikiPlayersButtonHoverSprite);
+
+ var settingButton = instance.TabButtons[1];
+ SetupTabButton(
+ settingButton,
+ MiraAssets.WikiSettingsButtonIdleSprite,
+ MiraAssets.WikiSettingsButtonOpenSprite,
+ MiraAssets.WikiSettingsButtonHoverSprite);
+
+ var rolesButton = instance.TabButtons[2];
+ SetupTabButton(
+ rolesButton,
+ MiraAssets.WikiRolesButtonIdleSprite,
+ MiraAssets.WikiRolesButtonOpenSprite,
+ MiraAssets.WikiRolesButtonHoverSprite);
+ rolesButton.OnClick = new Button.ButtonClickedEvent();
+ rolesButton.OnClick.AddListener((Action)(() => OpenRolesTab()));
+
+ var modifiersButton = Object.Instantiate(
+ instance.TabButtons[2],
+ instance.TabButtons[2].transform.parent);
+ SetupTabButton(
+ modifiersButton,
+ MiraAssets.WikiRolesButtonIdleSprite,
+ MiraAssets.WikiRolesButtonOpenSprite,
+ MiraAssets.WikiRolesButtonHoverSprite);
+ modifiersButton.OnClick = new Button.ButtonClickedEvent();
+ modifiersButton.OnClick.AddListener((Action)(() => OpenModifiersTab()));
+ instance.TabButtons.Add(modifiersButton);
+
+ playerButton.transform.localPosition = new Vector3(-3.6f, 0.656f, -0.2f);
+ settingButton.transform.localPosition = new Vector3(-3.6f, 0.056f, -0.2f);
+ rolesButton.transform.localPosition = new Vector3(-3.6f, -0.544f, -0.2f);
+ modifiersButton.transform.localPosition = new Vector3(-3.6f, -1.144f, -0.2f);
+ _uiInitialized = true;
+ }
+
+ private static void BuildEntries()
+ {
+ foreach (var modifier in ModifierManager.Modifiers.OfType())
+ {
+ ModifierEntries.Add(new DetailedPanel(
+ modifier.ModifierName,
+ modifier.ModifierCategoryTitle,
+ modifier.ParentMod.MiraPlugin.GetAbbreviatedModName(),
+ null,
+ modifier));
+ }
+
+ foreach (var roleBehaviour in CustomRoleManager.AllStoredRoleBehaviours.Where(x =>
+ x.Role is not RoleTypes.Crewmate and not RoleTypes.Impostor and
+ not RoleTypes.CrewmateGhost and not RoleTypes.ImpostorGhost))
+ {
+ var modId = roleBehaviour is ICustomRole custom
+ ? custom.ParentMod.MiraPlugin.GetAbbreviatedModName()
+ : "AU";
+ RoleEntries.Add(new DetailedPanel(
+ roleBehaviour.GetRoleName(),
+ roleBehaviour.GetCategoryTitle(),
+ modId,
+ roleBehaviour,
+ null));
+ }
+ }
+
+ private static void SetupTabButton(MatchInfoGuideTabButton button, Sprite inactive, Sprite selected, Sprite hover)
{
var buttonCollider = button.GetComponent();
button.transform.GetChild(0).gameObject.SetActive(false);
@@ -378,6 +636,7 @@ public static class RoleGuidePatches
settingBtnSelected.transform.GetChild(0).gameObject.SetActive(false);
var settingBtnHighlight = button.activeSprites.GetComponent();
settingBtnHighlight.sprite = hover;
+ settingBtnInactive.drawMode = settingBtnSelected.drawMode = settingBtnHighlight.drawMode = SpriteDrawMode.Sliced;
settingBtnInactive.size = settingBtnSelected.size = settingBtnHighlight.size = Vector2.one;
settingBtnHighlight.transform.GetChild(0).gameObject.SetActive(false);
buttonCollider.size = new Vector2(0.8f, 0.74f);
@@ -385,7 +644,7 @@ public static class RoleGuidePatches
button.transform.localScale = new Vector3(0.7f, 0.7f, 1);
}
- public static void FixSearchButton(PassiveButton button, string objName, Action onClick, Sprite inactive, Sprite hover, Vector3 pos, out SpriteRenderer inactiveSprite, out SpriteRenderer hoverSprite)
+ private static void SetupSearchButton(PassiveButton button, string objName, Action onClick, Sprite inactive, Sprite hover, Vector3 pos, out SpriteRenderer inactiveSprite, out SpriteRenderer hoverSprite)
{
button.name = objName;
inactiveSprite = button.inactiveSprites.GetComponent();
@@ -400,128 +659,352 @@ public static class RoleGuidePatches
button.transform.GetChild(2).gameObject.SetActive(false);
}
- public static void OpenModifiersTab()
+ private static void OpenTab(int tabIndex, float delay = 0f)
{
_advancedInfoTabScroller?.gameObject.SetActive(false);
- MatchInfoGuide.Instance.SetActiveTab(3);
- if (_needsToggledModifierRefresh)
- {
- ToggleModifierVisibility();
- }
-
- if (_needsModifierRefresh)
- {
- var text = string.Empty;
- try
- {
- text = searchBoxTmp.outputText.text;
- }
- catch
- {
- // ignored
- }
-
- SortAllModifiers(text);
- }
-
- _needsModifierRefresh = false;
- _needsToggledModifierRefresh = false;
+ MatchInfoGuide.Instance.SetActiveTab(tabIndex);
+ HidePanels(tabIndex == ModifiersTabIndex ? ModifierEntries : RoleEntries);
+ StartPopulate(delay);
}
- public static void OpenRolesTab()
+ private static void HidePanels(List entries)
{
- _advancedInfoTabScroller?.gameObject.SetActive(false);
- MatchInfoGuide.Instance.SetActiveTab(2);
- if (_needsToggledRoleRefresh)
+ foreach (var entry in entries)
{
- ToggleRoleVisibility();
- }
-
- if (_needsRoleRefresh)
- {
- var text = string.Empty;
- try
+ if (entry.Panel && entry.Panel!.gameObject.activeSelf)
{
- text = searchBoxTmp.outputText.text;
+ entry.Panel.gameObject.SetActive(false);
}
- catch
- {
- // ignored
- }
-
- SortAllRoles(text);
}
-
- _needsRoleRefresh = false;
- _needsToggledRoleRefresh = false;
}
- private static readonly Dictionary RolePanels = [];
- private static readonly Dictionary ModifierPanels = [];
- private static Scroller _modifiersScroller;
- private static Scroller _advancedInfoTabScroller;
- private static TextBoxTMP searchBoxTmp;
- private static GameObject currentAdvancedTabObject;
- private static TextMeshPro titleText;
+ public static void OpenModifiersTab() => OpenTab(ModifiersTabIndex);
- public sealed class DetailedPanel(MatchInfoRolePanel panel, string title, string category, string modId)
+ public static void OpenRolesTab() => OpenTab(RolesTabIndex);
+
+ public sealed class DetailedPanel(string title, string category, string modId, RoleBehaviour? role, GameModifier? modifier)
{
- public MatchInfoRolePanel Panel = panel;
- public string Title = title;
- public string Category = category;
- public string ModId = modId;
+ public MatchInfoRolePanel? Panel { get; set; }
+ public string Title { get; } = title;
+ public string Category { get; } = category;
+ public string ModId { get; } = modId;
+ public RoleBehaviour? Role { get; } = role;
+ public GameModifier? Modifier { get; } = modifier;
public int Amount { get; set; }
public int Chance { get; set; }
- public float LikelyhoodOfRole { get; set; }
+ public float Likelihood { get; set; }
+ public bool Visible { get; set; }
+
+ public string GetSortKey(SortingGroups grouping)
+ {
+ return grouping switch
+ {
+ SortingGroups.Faction =>
+ $"{Category} ({Title}) {Likelihood.ToString("0000.0", CultureInfo.InvariantCulture)}",
+ SortingGroups.AmountChance =>
+ $"{Likelihood.ToString("0000.0", CultureInfo.InvariantCulture)} {Title} ({Category})",
+ _ =>
+ $"{Title} ({Category}) {Likelihood.ToString("0000.0", CultureInfo.InvariantCulture)}",
+ };
+ }
}
- private static SortingFilter sortFilter = SortingFilter.EnabledOnly;
- private static SortingMethod sortMethod = SortingMethod.Alphabetical;
- private static SortingGroups sortOrder = SortingGroups.Ungrouped;
-
- public static void ClearSearchText()
+ private static string GetSearchText()
{
- _searchIconIdle.sprite = MiraAssets.SearchIconIdleSprite;
- _searchIconHover.sprite = MiraAssets.SearchIconHoverSprite;
- try
+ return _searchBoxTmp && _searchBoxTmp.outputText ? _searchBoxTmp.outputText.text : string.Empty;
+ }
+
+ private static bool ShouldShowPanel(bool? forceShow, bool isDisabled, bool isHidden)
+ {
+ return forceShow ??
+ !(isHidden || (isDisabled && _sortFilter is SortingFilter.EnabledOnly) ||
+ (!isDisabled && _sortFilter is SortingFilter.DisabledOnly));
+ }
+
+ private static void UpdateSpinner()
+ {
+ if (_loadingSpinner != null)
{
- searchBoxTmp.outputText.SetText(string.Empty);
- searchBoxTmp.SetText(string.Empty);
+ _loadingSpinner.gameObject.SetActive(
+ _loading && MatchInfoGuide.Instance.activeTabIndex is RolesTabIndex or ModifiersTabIndex &&
+ !(_advancedInfoTabScroller && _advancedInfoTabScroller.gameObject.activeSelf));
}
- catch
+ }
+
+ private static float GetGridScrollBounds(int count)
+ {
+ return Mathf.Clamp(Mathf.Ceil(count / 2f) * 1.3f - 1.5f, 0f, 999f);
+ }
+
+ private static void StartPopulate(float delay = 0f)
+ {
+ if (_populate != null)
{
- // ignored
+ Coroutines.Stop(_populate);
}
- var text = string.Empty;
+ _loading = true;
+ UpdateSpinner();
+ _populate = Coroutines.Start(CoPopulate(delay));
+ }
- if (MatchInfoGuide.Instance.activeTabIndex != 3)
+ private static IEnumerator CoPopulate(float delay)
+ {
+ if (delay > 0f)
{
- SortAllRoles(text);
- _rolesScroller.ScrollToTop();
- _needsModifierRefresh = true;
+ yield return new WaitForSeconds(delay);
}
else
{
- SortAllModifiers(text);
- _modifiersScroller.ScrollToTop();
- _needsRoleRefresh = true;
+ yield return null;
}
+
+ if (!_guide)
+ {
+ yield break;
+ }
+
+ var modifiers = IsModifiersTabActive;
+ var entries = modifiers ? ModifierEntries : RoleEntries;
+ var shown = UpdateEntries(entries, modifiers);
+ yield return CoForEachBudgeted(shown, entry =>
+ {
+ EnsurePanel(entry);
+ entry.Panel!.transform.SetAsLastSibling();
+ if (!entry.Panel.gameObject.activeSelf)
+ {
+ entry.Panel.gameObject.SetActive(true);
+ }
+ });
+ _loading = false;
+ UpdateSpinner();
+ yield return CoForEachBudgeted(entries, EnsurePanel);
+ yield return CoForEachBudgeted(modifiers ? RoleEntries : ModifierEntries, PreloadPanel);
+ _populate = null;
+ }
+
+ private static IEnumerator CoForEachBudgeted(IEnumerable collection, Action action)
+ {
+ var fps = Application.targetFrameRate > 0 ? Application.targetFrameRate : 60;
+ var budget = Math.Max(1L, 1000L / (fps * 4));
+ var timer = new Stopwatch();
+ timer.Start();
+ foreach (var item in collection)
+ {
+ action(item);
+ if (timer.ElapsedMilliseconds > budget)
+ {
+ timer.Restart();
+ yield return null;
+ if (!_guide)
+ {
+ yield break;
+ }
+ }
+ }
+ }
+
+ private static void EnsurePanel(DetailedPanel entry)
+ {
+ if (entry.Panel)
+ {
+ return;
+ }
+
+ if (!_guide || !_guide!.MatchInfoRolePanelPrefab)
+ {
+ return;
+ }
+
+ var parent = entry.Modifier != null ? _modifiersScroller?.Inner : _rolesScroller?.Inner;
+ if (parent == null)
+ {
+ return;
+ }
+
+ var panel = Object.Instantiate(_guide!.MatchInfoRolePanelPrefab, parent);
+ if (entry.Modifier != null)
+ {
+ panel.SetModifierPanel(entry.Modifier, entry.Amount, entry.Chance);
+ SetupPanelButton(panel, () => DisplayAdvancedWiki(MatchInfoGuide.Instance, entry.Modifier));
+ }
+ else
+ {
+ panel.SetRolePanel(entry.Role!, entry.Amount, entry.Chance);
+ SetupPanelButton(panel, () => DisplayAdvancedWiki(MatchInfoGuide.Instance, entry.Role!));
+ }
+
+ panel.gameObject.SetActive(false);
+ entry.Panel = panel;
+ }
+
+ private static void PreloadPanel(DetailedPanel entry)
+ {
+ if (!entry.Panel)
+ {
+ ComputeCounts(entry);
+ EnsurePanel(entry);
+ }
+ }
+
+ private static void ComputeCounts(DetailedPanel entry)
+ {
+ int amount;
+ int chance;
+ bool? forceShow = null;
+ bool isHidden;
+ if (entry.Modifier is { } modifier)
+ {
+ amount = modifier.GetAmountPerGame();
+ chance = modifier.GetAssignmentChance();
+ forceShow = modifier.ForceShowModifierOnWiki;
+ isHidden = !modifier.CanSpawnOnCurrentMode() || modifier.GetDescription() == string.Empty;
+ }
+ else
+ {
+ var role = entry.Role!;
+ if (role is ICustomRole custom)
+ {
+ amount = custom.GetCount().GetValueOrDefault(0);
+ chance = custom.GetChance().GetValueOrDefault(0);
+ forceShow = custom.ForceShowRoleOnWiki;
+ }
+ else
+ {
+ amount = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(role.Role);
+ chance = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(role.Role);
+ }
+
+ isHidden = (role is ICustomRole custom2 && !custom2.CanSpawnOnCurrentMode()) ||
+ (Enum.IsDefined(role.Role) && role.IsRoleBlacklisted());
+ }
+
+ entry.Amount = amount;
+ entry.Chance = chance;
+ entry.Likelihood = amount * chance;
+ entry.Visible = ShouldShowPanel(forceShow, amount == 0 || chance == 0, isHidden);
+ }
+
+ private static List UpdateEntries(List entries, bool modifiers)
+ {
+ foreach (var entry in entries)
+ {
+ ComputeCounts(entry);
+ if (entry.Panel)
+ {
+ SetPanelCount(entry.Panel!, entry.Amount, entry.Chance, entry.ModId);
+ if (!entry.Visible && entry.Panel!.gameObject.activeSelf)
+ {
+ entry.Panel.gameObject.SetActive(false);
+ }
+ }
+ }
+
+ var searchText = GetSearchText();
+ var sorted = entries.Where(p => p.Visible)
+ .OrderByDescending(p => p.Title.Equals(searchText, StringComparison.OrdinalIgnoreCase))
+ .ThenByDescending(p =>
+ p.Title.Contains(searchText, StringComparison.OrdinalIgnoreCase) ||
+ p.Category.Contains(searchText, StringComparison.OrdinalIgnoreCase));
+ var shown = (_sortMethod is SortingMethod.Alphabetical
+ ? sorted.ThenBy(p => p.GetSortKey(_sortGrouping))
+ : sorted.ThenByDescending(p => p.GetSortKey(_sortGrouping)))
+ .ToList();
+
+ var instance = MatchInfoGuide.Instance;
+ if (modifiers)
+ {
+ _modifiersScroller.SetYBoundsMax(GetGridScrollBounds(shown.Count));
+ /*instance.rolesEnabledMessage.SetActive(shown.Count == 0);*/
+ }
+ else
+ {
+ instance.MatchInfoRoleScroller.SetYBoundsMax(GetGridScrollBounds(shown.Count));
+ instance.rolesEnabledMessage.SetActive(shown.Count == 0);
+ }
+
+ return shown;
+ }
+
+ private static void RefreshActiveTab(bool scrollToTop)
+ {
+ StartPopulate();
+ if (scrollToTop)
+ {
+ (IsModifiersTabActive ? _modifiersScroller : _rolesScroller).ScrollToTop();
+ }
+ }
+
+ private static void QueueSearchRefresh()
+ {
+ if (_searchDebounce != null)
+ {
+ Coroutines.Stop(_searchDebounce);
+ }
+
+ _searchDebounce = Coroutines.Start(CoSearchRefresh());
+ }
+
+ private static IEnumerator CoSearchRefresh()
+ {
+ yield return new WaitForSeconds(SearchDebounceSeconds);
+ _searchDebounce = null;
+ var text = GetSearchText();
+ if (text == _appliedSearch)
+ {
+ yield break;
+ }
+
+ _appliedSearch = text;
+ RefreshActiveTab(true);
+ }
+
+ private static void ResetSearchBox()
+ {
+ if (_searchDebounce != null)
+ {
+ Coroutines.Stop(_searchDebounce);
+ _searchDebounce = null;
+ }
+
+ _appliedSearch = string.Empty;
+ if (_searchIconIdle)
+ {
+ _searchIconIdle.sprite = MiraAssets.SearchIconIdleSprite;
+ }
+
+ if (_searchIconHover)
+ {
+ _searchIconHover.sprite = MiraAssets.SearchIconHoverSprite;
+ }
+
+ if (_searchBoxTmp && GetSearchText().Length > 0)
+ {
+ _suppressSearchRefresh = true;
+ _searchBoxTmp.Clear();
+ _suppressSearchRefresh = false;
+ }
+ }
+
+ public static void ClearSearchText()
+ {
+ ResetSearchBox();
+ RefreshActiveTab(true);
}
public static void SwitchFilter()
{
- var stepUp = (SortingFilter)((int)sortFilter + 1);
+ var stepUp = (SortingFilter)((int)_sortFilter + 1);
if (Enum.IsDefined(stepUp))
{
- sortFilter = stepUp;
+ _sortFilter = stepUp;
}
else
{
- sortFilter = SortingFilter.EnabledOnly;
+ _sortFilter = SortingFilter.EnabledOnly;
}
- switch (sortFilter)
+ switch (_sortFilter)
{
case SortingFilter.AllRoles:
_searchSortingFilterIdle.sprite = MiraAssets.VisibleAllIdleSprite;
@@ -537,72 +1020,44 @@ public static class RoleGuidePatches
break;
}
- if (MatchInfoGuide.Instance.activeTabIndex != 3)
- {
- ToggleRoleVisibility();
- _needsToggledModifierRefresh = _needsModifierRefresh = true;
- }
- else
- {
- ToggleModifierVisibility();
- _needsToggledRoleRefresh = _needsRoleRefresh = true;
- }
+ RefreshActiveTab(false);
}
public static void SwitchMethod()
{
- var stepUp = (SortingMethod)((int)sortMethod + 1);
+ var stepUp = (SortingMethod)((int)_sortMethod + 1);
if (Enum.IsDefined(stepUp))
{
- sortMethod = stepUp;
+ _sortMethod = stepUp;
}
else
{
- sortMethod = SortingMethod.Alphabetical;
+ _sortMethod = SortingMethod.Alphabetical;
}
- _searchSortingOrderIdle.sprite = sortMethod is SortingMethod.Alphabetical
+ _searchSortingOrderIdle.sprite = _sortMethod is SortingMethod.Alphabetical
? MiraAssets.SortingAzIdleSprite
: MiraAssets.SortingZaIdleSprite;
- _searchSortingOrderHover.sprite = sortMethod is SortingMethod.Alphabetical
+ _searchSortingOrderHover.sprite = _sortMethod is SortingMethod.Alphabetical
? MiraAssets.SortingAzHoverSprite
: MiraAssets.SortingZaHoverSprite;
- var text = string.Empty;
- try
- {
- text = searchBoxTmp.outputText.text;
- }
- catch
- {
- // ignored
- }
-
- if (MatchInfoGuide.Instance.activeTabIndex != 3)
- {
- SortAllRoles(text);
- _needsModifierRefresh = true;
- }
- else
- {
- SortAllModifiers(text);
- _needsRoleRefresh = true;
- }
+ RefreshActiveTab(false);
}
public static void SwitchGrouping()
{
- var stepUp = (SortingGroups)((int)sortOrder + 1);
+ var stepUp = (SortingGroups)((int)_sortGrouping + 1);
if (Enum.IsDefined(stepUp))
{
- sortOrder = stepUp;
+ _sortGrouping = stepUp;
}
else
{
- sortOrder = SortingGroups.Ungrouped;
+ _sortGrouping = SortingGroups.Ungrouped;
}
- switch (sortOrder)
+ switch (_sortGrouping)
{
case SortingGroups.Faction:
_searchSortingGroupIdle.sprite = MiraAssets.GroupFactionIdleSprite;
@@ -618,387 +1073,50 @@ public static class RoleGuidePatches
break;
}
- var text = string.Empty;
- try
- {
- text = searchBoxTmp.outputText.text;
- }
- catch
- {
- // ignored
- }
-
- if (MatchInfoGuide.Instance.activeTabIndex != 3)
- {
- SortAllRoles(text);
- _needsModifierRefresh = true;
- }
- else
- {
- SortAllModifiers(text);
- _needsRoleRefresh = true;
- }
+ RefreshActiveTab(false);
}
- public static void DisplayNormalRoleSettings(MatchInfoGuide instance, bool reset)
+ private static void CopyTextLayout(TextMeshPro source, TextMeshPro target)
{
- var inner = instance.settingsTabs[2].GetComponent().Inner;
- if (reset)
- {
- RolePanels.Clear();
- ModifierPanels.Clear();
- instance.CreateSettingsEntry(
- StringNames.GameNumImpostors,
- GameManager.Instance.AllGameSettingData[StringNames.GameNumImpostors]
- .GetValueString(GameManager.Instance.LogicOptions.NumImpostors));
- instance.CreateSettingsEntry(
- StringNames.GameKillCooldown,
- GameManager.Instance.AllGameSettingData[StringNames.GameKillCooldown]
- .GetValueString(GameManager.Instance.LogicOptions.GetKillCooldown()));
- instance.CreateSettingsEntry(
- StringNames.GameEmergencyCooldown,
- GameManager.Instance.AllGameSettingData[StringNames.GameEmergencyCooldown]
- .GetValueString(GameManager.Instance.LogicOptions.GetEmergencyCooldown()));
- instance.CreateSettingsEntry(
- StringNames.GameVisualTasks,
- instance.GetBoolString(GameManager.Instance.LogicOptions.GetVisualTasks()));
- instance.CreateSettingsEntry(
- StringNames.GameAnonymousVotes,
- instance.GetBoolString(GameManager.Instance.LogicOptions.GetAnonymousVotes()));
- instance.CreateSettingsEntry(
- StringNames.GameConfirmImpostor,
- instance.GetBoolString(GameManager.Instance.LogicOptions.GetConfirmImpostor()));
- instance.CreateSettingsEntry(
- StringNames.GameTaskBarMode,
- GameManager.Instance.LogicOptions.GetTaskBarMode().ToString());
-
- var hoverColor = new Color32(255, 255, 255, 150);
- foreach (var modifier in ModifierManager.Modifiers.OfType())
- {
- var panel = Object.Instantiate(
- instance.MatchInfoRolePanelPrefab,
- _modifiersScroller.Inner);
- var amount =
- modifier.GetAmountPerGame();
- var chance =
- modifier.GetAssignmentChance();
- panel.SetModifierPanel(
- modifier,
- amount,
- chance);
- var collider = panel.roleIcon.gameObject.AddComponent();
- collider.size = new Vector2(0.13f, 0.13f);
- collider.offset = new Vector2(0, 0);
- var passiveButton = panel.roleIcon.gameObject.AddComponent();
- passiveButton.ClickSound = HudManager.Instance.MapButton.ClickSound;
- passiveButton.OnMouseOver = new UnityEvent();
- passiveButton.OnMouseOver.AddListener((UnityAction)(() => { panel.roleIcon.color = hoverColor; }));
- passiveButton.OnMouseOut = new UnityEvent();
- passiveButton.OnMouseOut.AddListener((UnityAction)(() => { panel.roleIcon.color = Color.white; }));
-
- passiveButton.OnClick = new Button.ButtonClickedEvent();
- passiveButton.OnClick.AddListener((Action)(() => { DisplayAdvancedWiki(instance, modifier); }));
- ModifierPanels.Add(
- modifier,
- new DetailedPanel(
- panel,
- modifier.ModifierName,
- modifier.ModifierCategoryTitle,
- modifier.ParentMod.MiraPlugin.GetAbbreviatedModName()));
- }
-
- foreach (var roleBehaviour in CustomRoleManager.AllStoredRoleBehaviours)
- {
- if (roleBehaviour.Role is not RoleTypes.Crewmate and not RoleTypes.Impostor and
- not RoleTypes.CrewmateGhost and
- not RoleTypes.ImpostorGhost)
- {
- var panel = Object.Instantiate(
- instance.MatchInfoRolePanelPrefab,
- inner);
- string abbreviation;
- int amount;
- int chance;
- if (roleBehaviour is ICustomRole custom)
- {
- amount = custom.GetCount().GetValueOrDefault(0);
- chance = custom.GetChance().GetValueOrDefault(0);
- abbreviation = custom.ParentMod.MiraPlugin.GetAbbreviatedModName();
- }
- else
- {
- abbreviation = "AU";
- amount =
- GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role);
- chance =
- GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleBehaviour.Role);
- }
-
- panel.SetRolePanel(
- roleBehaviour,
- amount,
- chance);
- var collider = panel.roleIcon.gameObject.AddComponent();
- collider.size = new Vector2(0.13f, 0.13f);
- collider.offset = new Vector2(0, 0);
- var passiveButton = panel.roleIcon.gameObject.AddComponent();
- passiveButton.ClickSound = HudManager.Instance.MapButton.ClickSound;
- passiveButton.OnMouseOver = new UnityEvent();
- passiveButton.OnMouseOver.AddListener((UnityAction)(() => { panel.roleIcon.color = hoverColor; }));
- passiveButton.OnMouseOut = new UnityEvent();
- passiveButton.OnMouseOut.AddListener((UnityAction)(() => { panel.roleIcon.color = Color.white; }));
-
- passiveButton.OnClick = new Button.ButtonClickedEvent();
- passiveButton.OnClick.AddListener(
- (Action)(() => { DisplayAdvancedWiki(instance, roleBehaviour); }));
- RolePanels.Add(
- roleBehaviour,
- new DetailedPanel(
- panel,
- roleBehaviour.GetRoleName(),
- roleBehaviour.GetCategoryTitle(),
- abbreviation));
- }
- }
- }
-
- _advancedInfoTabScroller?.gameObject.SetActive(false);
-
- if (MatchInfoGuide.Instance.activeTabIndex != 3)
- {
- ToggleRoleVisibility();
- }
- else
- {
- ToggleModifierVisibility();
- }
-
- if (reset)
- {
- instance.CreatePlayerEntries();
- }
+ target.font = source.font;
+ target.fontSharedMaterial = source.fontSharedMaterial;
+ target.fontSize = source.fontSize;
+ target.fontSizeMin = source.fontSizeMin;
+ target.fontSizeMax = source.fontSizeMax;
+ target.enableAutoSizing = source.enableAutoSizing;
+ target.overflowMode = source.overflowMode;
+ target.alignment = source.alignment;
+ target.horizontalAlignment = source.horizontalAlignment;
+ target.verticalAlignment = source.verticalAlignment;
+ target.margin = source.margin;
+ target.enableWordWrapping = source.enableWordWrapping;
+ var sourceRect = source.rectTransform;
+ var targetRect = target.rectTransform;
+ targetRect.anchorMin = sourceRect.anchorMin;
+ targetRect.anchorMax = sourceRect.anchorMax;
+ targetRect.pivot = sourceRect.pivot;
+ targetRect.sizeDelta = sourceRect.sizeDelta;
+ targetRect.anchoredPosition = sourceRect.anchoredPosition;
+ targetRect.localScale = sourceRect.localScale;
+ targetRect.localRotation = sourceRect.localRotation;
+ targetRect.localPosition = sourceRect.localPosition;
}
-
- public static void ToggleModifierVisibility()
+ private static void SetupPanelButton(MatchInfoRolePanel panel, Action onClick)
{
- var num = 0;
- foreach (var (modifier, holder) in ModifierPanels)
- {
- var panel = holder.Panel;
- var amount =
- modifier.GetAmountPerGame();
- var chance =
- modifier.GetAssignmentChance();
- var forciblyShow = modifier.ForceShowModifierOnWiki switch
- {
- true => 2,
- false => 1,
- _ => 0,
- };
- panel.SetModifierPanel(
- modifier,
- amount,
- chance);
- holder.Amount = amount;
- holder.Chance = chance;
- holder.LikelyhoodOfRole = amount * chance;
- var isRoleDisabled = amount == 0 || chance == 0;
- var isRoleNotVisible = !modifier.CanSpawnOnCurrentMode() || modifier.GetDescription() == string.Empty;
- if ((forciblyShow == 0 && ((isRoleDisabled && sortFilter is SortingFilter.EnabledOnly) ||
- (!isRoleDisabled && sortFilter is SortingFilter.DisabledOnly) ||
- isRoleNotVisible))
- || forciblyShow == 1)
- {
- panel.gameObject.SetActive(false);
- continue;
- }
+ var collider = panel.roleIcon.gameObject.AddComponent();
+ collider.size = new Vector2(0.13f, 0.13f);
+ collider.offset = new Vector2(0, 0);
+ var passiveButton = panel.roleIcon.gameObject.AddComponent();
+ passiveButton.ClickSound = HudManager.Instance.MapButton.ClickSound;
+ passiveButton.OnMouseOver = new UnityEvent();
+ passiveButton.OnMouseOver.AddListener(
+ (UnityAction)(() => { panel.roleIcon.color = new Color32(255, 255, 255, 150); }));
+ passiveButton.OnMouseOut = new UnityEvent();
+ passiveButton.OnMouseOut.AddListener((UnityAction)(() => { panel.roleIcon.color = Color.white; }));
- panel.gameObject.SetActive(true);
- num++;
- }
-
- var text = string.Empty;
- try
- {
- text = searchBoxTmp.outputText.text;
- }
- catch
- {
- // ignored
- }
-
- SortAllModifiers(text);
- _needsRoleRefresh = true;
-
- /*var instance = MatchInfoGuide.Instance;
- instance.rolesEnabledMessage.SetActive(num == 0);*/
-
- _modifiersScroller.SetYBoundsMax(Mathf.Clamp(Mathf.Ceil(num / 2f) * 1.3f - 1.5f, 0f, 999f));
- }
-
- public static void ToggleRoleVisibility()
- {
- var num = 0;
- foreach (var (roleData, holder) in RolePanels)
- {
- var panel = holder.Panel;
- int amount;
- int chance;
- int forciblyShow = 0;
- if (roleData is ICustomRole custom)
- {
- amount = custom.GetCount().GetValueOrDefault(0);
- chance = custom.GetChance().GetValueOrDefault(0);
- if (custom.ForceShowRoleOnWiki.HasValue)
- {
- forciblyShow = custom.ForceShowRoleOnWiki.Value ? 2 : 1;
- }
- }
- else
- {
- amount =
- GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleData.Role);
- chance =
- GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleData.Role);
- }
-
- panel.SetRolePanel(
- roleData,
- amount,
- chance);
- holder.Amount = amount;
- holder.Chance = chance;
- holder.LikelyhoodOfRole = amount * chance;
- var isRoleDisabled = amount == 0 || chance == 0;
- var isRoleNotVisible = (roleData is ICustomRole custom2 &&
- !custom2.CanSpawnOnCurrentMode()) ||
- (Enum.IsDefined(roleData.Role) && roleData.IsRoleBlacklisted());
- if ((forciblyShow == 0 && ((isRoleDisabled && sortFilter is SortingFilter.EnabledOnly) ||
- (!isRoleDisabled && sortFilter is SortingFilter.DisabledOnly) ||
- isRoleNotVisible))
- || forciblyShow == 1)
- {
- panel.gameObject.SetActive(false);
- continue;
- }
-
- panel.gameObject.SetActive(true);
- num++;
- }
-
- var text = string.Empty;
- try
- {
- text = searchBoxTmp.outputText.text;
- }
- catch
- {
- // ignored
- }
-
- SortAllRoles(text);
-
- var instance = MatchInfoGuide.Instance;
- instance.rolesEnabledMessage.SetActive(num == 0);
-
- instance.MatchInfoRoleScroller.SetYBoundsMax(Mathf.Clamp(Mathf.Ceil(num / 2f) * 1.3f - 1.5f, 0f, 999f));
- }
-
- public static void SortAllRoles(string searchText)
- {
- var newSorted = sortMethod is SortingMethod.Alphabetical
- ? RolePanels
- .OrderByDescending(child =>
- child.Value.GetTitle().Equals(searchText, StringComparison.OrdinalIgnoreCase))
- .ThenByDescending(child => child.Value.GetTitle().Contains(
- searchText,
- StringComparison.InvariantCultureIgnoreCase))
- .ThenBy(GetSortingOrder())
- : RolePanels
- .OrderByDescending(child =>
- child.Value.GetTitle().Equals(searchText, StringComparison.OrdinalIgnoreCase))
- .ThenByDescending(child => child.Value.GetTitle().Contains(
- searchText,
- StringComparison.InvariantCultureIgnoreCase))
- .ThenByDescending(GetSortingOrder());
-
- foreach (var pair in newSorted)
- {
- pair.Value.Panel.transform.SetAsLastSibling();
- }
- }
-
- public static void SortAllModifiers(string searchText)
- {
- var newSorted = sortMethod is SortingMethod.Alphabetical
- ? ModifierPanels
- .OrderByDescending(child =>
- child.Value.GetTitle().Equals(searchText, StringComparison.OrdinalIgnoreCase))
- .ThenByDescending(child => child.Value.GetTitle().Contains(
- searchText,
- StringComparison.InvariantCultureIgnoreCase))
- .ThenBy(GetModifierSortingOrder())
- : ModifierPanels
- .OrderByDescending(child =>
- child.Value.GetTitle().Equals(searchText, StringComparison.OrdinalIgnoreCase))
- .ThenByDescending(child => child.Value.GetTitle().Contains(
- searchText,
- StringComparison.InvariantCultureIgnoreCase))
- .ThenByDescending(GetModifierSortingOrder());
-
- foreach (var pair in newSorted)
- {
- pair.Value.Panel.transform.SetAsLastSibling();
- }
- }
-
- public static Func, string> GetSortingOrder()
- {
- switch (sortOrder)
- {
- case SortingGroups.Faction:
- return x =>
- $"{x.Value.Category} ({x.Value.Title}) {x.Value.LikelyhoodOfRole.ToString("0000.0", CultureInfo.InvariantCulture)}";
- case SortingGroups.AmountChance:
- return x =>
- $"{x.Value.LikelyhoodOfRole.ToString("0000.0", CultureInfo.InvariantCulture)} {x.Value.Title} ({x.Value.Category})";
- default:
- return x =>
- $"{x.Value.Title} ({x.Value.Category}) {x.Value.LikelyhoodOfRole.ToString("0000.0", CultureInfo.InvariantCulture)}";
- }
- }
-
- public static Func, string> GetModifierSortingOrder()
- {
- switch (sortOrder)
- {
- case SortingGroups.Faction:
- return x =>
- $"{x.Value.Category} ({x.Value.Title}) {x.Value.LikelyhoodOfRole.ToString("0000.0", CultureInfo.InvariantCulture)}";
- case SortingGroups.AmountChance:
- return x =>
- $"{x.Value.LikelyhoodOfRole.ToString("0000.0", CultureInfo.InvariantCulture)} {x.Value.Title} ({x.Value.Category})";
- default:
- return x =>
- $"{x.Value.Title} ({x.Value.Category}) {x.Value.LikelyhoodOfRole.ToString("0000.0", CultureInfo.InvariantCulture)}";
- }
- }
-
- public static string GetTitle(this DetailedPanel panel)
- {
- switch (sortOrder)
- {
- case SortingGroups.Faction:
- return
- $"{panel.Category} ({panel.Title}) {panel.LikelyhoodOfRole.ToString("0000.0", CultureInfo.InvariantCulture)}";
- case SortingGroups.AmountChance:
- return
- $"{panel.LikelyhoodOfRole.ToString("0000.0", CultureInfo.InvariantCulture)} {panel.Title} ({panel.Category})";
- default:
- return
- $"{panel.Title} ({panel.Category}) {panel.LikelyhoodOfRole.ToString("0000.0", CultureInfo.InvariantCulture)}";
- }
+ passiveButton.OnClick = new Button.ButtonClickedEvent();
+ passiveButton.OnClick.AddListener(onClick);
}
[HarmonyPrefix]
@@ -1021,10 +1139,13 @@ public static class RoleGuidePatches
component.NameText.text += $"\n{networkedPlayerInfo.GetPlayerColorString()}";
var namePlate = HatManager.Instance.GetNamePlateById(networkedPlayerInfo.DefaultOutfit.NamePlateId);
- __instance.StartCoroutine(
- __instance.CoLoadAssetAsync(
- namePlate.GetAssetReference(),
- (Action?)LoadNameplate));
+ if (namePlate != null)
+ {
+ __instance.StartCoroutine(
+ __instance.CoLoadAssetAsync(
+ namePlate.GetAssetReference(),
+ (Action?)LoadNameplate));
+ }
void LoadNameplate(NamePlateViewData viewData)
{
@@ -1042,79 +1163,103 @@ public static class RoleGuidePatches
[HarmonyPatch(typeof(MatchInfoGuide), nameof(MatchInfoGuide.SetActiveTab))]
private static void SetActiveTab(int tabIndex)
{
- if (titleText)
+ if (_titleText)
{
switch (tabIndex)
{
case 0:
- titleText.text = MiraLocaleManager.Get("MiraApi.Wiki.PlayersTab");
+ _titleText.text = MiraLocaleManager.Get("MiraApi.Wiki.PlayersTab");
break;
case 1:
- titleText.text = MiraLocaleManager.Get("MiraApi.Wiki.SettingsTab");
+ _titleText.text = MiraLocaleManager.Get("MiraApi.Wiki.SettingsTab");
break;
- case 2:
- titleText.text = MiraLocaleManager.Get("Roles");
+ case RolesTabIndex:
+ _titleText.text = MiraLocaleManager.Get("Roles");
break;
- case 3:
- titleText.text = MiraLocaleManager.Get("Modifiers");
+ case ModifiersTabIndex:
+ _titleText.text = MiraLocaleManager.Get("Modifiers");
break;
default:
- titleText.text = TranslationController.Instance.GetString(StringNames.MatchInfoGuideTitle);
+ _titleText.text = TranslationController.Instance.GetString(StringNames.MatchInfoGuideTitle);
break;
}
}
_advancedInfoTabScroller?.gameObject.SetActive(false);
+ UpdateSpinner();
+ }
+
+ private static bool TryPrepareAdvancedWiki(MatchInfoGuide instance)
+ {
+ instance.settingsTabs[RolesTabIndex].SetActive(false);
+ _modifiersScroller?.gameObject.SetActive(false);
+ if (_currentAdvancedTabObject)
+ {
+ _currentAdvancedTabObject.SetActive(false);
+ _currentAdvancedTabObject.Destroy();
+ }
+
+ if (_advancedInfoTabScroller == null)
+ {
+ Warning("Wiki tab is null.");
+ return false;
+ }
+
+ _advancedInfoTabScroller.gameObject.SetActive(true);
+ UpdateSpinner();
+ return true;
+ }
+
+ private static IEnumerator CoDisplayAdvancedWikiLater(float delay, Action display)
+ {
+ yield return new WaitForSeconds(delay);
+ if (_guide && MatchInfoGuide.Instance && MatchInfoGuide.Instance.MatchInfoParent.activeSelf)
+ {
+ display();
+ }
}
public static void DisplayAdvancedWiki(MatchInfoGuide instance, BaseModifier modifier)
{
- var name = modifier.ModifierName;
- Warning($"Opening advanced tab for {name}.");
- instance.settingsTabs[2].SetActive(false);
- instance.settingsTabs[3].SetActive(false);
- _advancedInfoTabScroller?.gameObject.SetActive(true);
- if (currentAdvancedTabObject)
+ var remaining = _openedAt + instance.transitionOpen.duration - Time.time;
+ if (remaining > 0f)
{
- currentAdvancedTabObject.SetActive(false);
- currentAdvancedTabObject.Destroy();
- }
-
- if (_advancedInfoTabScroller == null)
- {
- Warning($"Wiki tab is null.");
+ Coroutines.Start(CoDisplayAdvancedWikiLater(remaining, () => DisplayAdvancedWiki(instance, modifier)));
return;
}
- currentAdvancedTabObject = modifier.GetAdvancedWiki(instance, titleText, _advancedInfoTabScroller);
+ var name = modifier.ModifierName;
+ Info($"Opening advanced tab for {name}.");
+ if (!TryPrepareAdvancedWiki(instance))
+ {
+ return;
+ }
+
+ _currentAdvancedTabObject = modifier.GetAdvancedWiki(instance, _titleText, _advancedInfoTabScroller);
}
public static void DisplayAdvancedWiki(MatchInfoGuide instance, RoleBehaviour role)
{
- Warning($"Opening advanced tab for {role.GetRoleName()}.");
- instance.settingsTabs[2].SetActive(false);
- instance.settingsTabs[3].SetActive(false);
- _advancedInfoTabScroller?.gameObject.SetActive(true);
- if (currentAdvancedTabObject)
+ var remaining = _openedAt + instance.transitionOpen.duration - Time.time;
+ if (remaining > 0f)
{
- currentAdvancedTabObject.SetActive(false);
- currentAdvancedTabObject.Destroy();
+ Coroutines.Start(CoDisplayAdvancedWikiLater(remaining, () => DisplayAdvancedWiki(instance, role)));
+ return;
}
- if (_advancedInfoTabScroller == null)
+ Info($"Opening advanced tab for {role.GetRoleName()}.");
+ if (!TryPrepareAdvancedWiki(instance))
{
- Warning($"Wiki tab is null.");
return;
}
if (role is ICustomRole custom)
{
- currentAdvancedTabObject = custom.GetAdvancedWiki(instance, titleText, _advancedInfoTabScroller);
+ _currentAdvancedTabObject = custom.GetAdvancedWiki(instance, _titleText, _advancedInfoTabScroller);
}
else
{
- _advancedInfoTabScroller.ScrollToTop();
- var titleTxt = role.GetRoleName() +
+ var titleTxt = $"{role.GetTmpIcon()} {role.GetRoleName()}" +
$" ({TranslationController.Instance.GetString(role.TeamType is RoleTeamTypes.Crewmate ? StringNames.Crewmate : StringNames.Impostor)})";
var description = TranslationController.Instance.GetString(role.BlurbNameLong);
if (description.Contains("STRMISS"))
@@ -1126,16 +1271,14 @@ public static class RoleGuidePatches
}
}
- currentAdvancedTabObject = Helpers.CreateAdvancedWikiTab(
+ _currentAdvancedTabObject = Helpers.CreateAdvancedWikiPage(
instance,
+ _titleText,
+ _advancedInfoTabScroller,
role.Role.ToString(),
titleTxt,
description,
- titleText,
- out var desc);
- currentAdvancedTabObject.transform.SetParent(_advancedInfoTabScroller.Inner.transform);
- currentAdvancedTabObject.transform.localPosition = new Vector3(0f, 0f, 0f);
- _advancedInfoTabScroller.SetYBoundsMax(Mathf.Clamp(desc.textBounds.size.y - 2, 0f, 999f));
+ []);
}
}
@@ -1174,31 +1317,36 @@ public static class RoleGuidePatches
return false;
}
+ private static void SetPanelCount(MatchInfoRolePanel panel, int amount, int chance, string modId)
+ {
+ panel.roleCount.text = string.Format(
+ CultureInfo.InvariantCulture,
+ "{0} at {1}% ({2})",
+ amount.ToString(CultureInfo.InvariantCulture),
+ chance,
+ modId);
+ }
+
public static void SetRolePanel(
this MatchInfoRolePanel instance,
RoleBehaviour role,
int numPerGame,
int chancePerGame)
{
- instance.roleCount.text = string.Format(
- CultureInfo.InvariantCulture,
- "{0} at {1}%",
- numPerGame.ToString(CultureInfo.InvariantCulture),
- chancePerGame);
if (role is ICustomRole customRole)
{
instance.roleName.text = customRole.RoleName;
instance.roleDescription.text =
$"{role.GetCategoryTitle()}\n" + customRole.RoleMedDescription;
instance.roleIcon.sprite = customRole.Configuration.Icon?.LoadAsset();
- instance.roleCount.text += $" ({customRole.ParentMod.MiraPlugin.GetAbbreviatedModName()})";
+ SetPanelCount(instance, numPerGame, chancePerGame, customRole.ParentMod.MiraPlugin.GetAbbreviatedModName());
}
else
{
instance.roleName.text = role.NiceName;
instance.roleDescription.text = $"{role.GetCategoryTitle()}\n" + role.BlurbMed;
instance.roleIcon.sprite = role.RoleIconColor;
- instance.roleCount.text += " (AU)";
+ SetPanelCount(instance, numPerGame, chancePerGame, "AU");
}
instance.roleIcon.SetSizeLimit(0.13f);
@@ -1215,16 +1363,11 @@ public static class RoleGuidePatches
int numPerGame,
int chancePerGame)
{
- instance.roleCount.text = string.Format(
- CultureInfo.InvariantCulture,
- "{0} at {1}%",
- numPerGame.ToString(CultureInfo.InvariantCulture),
- chancePerGame);
instance.roleName.text = modifier.ModifierName;
instance.roleDescription.text =
$"{modifier.ModifierCategoryTitle}\n" + modifier.ModifierMedDescription;
instance.roleIcon.sprite = modifier.ModifierIcon?.LoadAsset();
- instance.roleCount.text += $" ({modifier.ParentMod.MiraPlugin.GetAbbreviatedModName()})";
+ SetPanelCount(instance, numPerGame, chancePerGame, modifier.ParentMod.MiraPlugin.GetAbbreviatedModName());
instance.roleIcon.SetSizeLimit(0.13f);
instance.roleIcon.material.SetInt(PlayerMaterial.MaskLayer, 50);
diff --git a/MiraAPI/Presets/OptionPreset.cs b/MiraAPI/Presets/OptionPreset.cs
index 01758c5..077afc9 100644
--- a/MiraAPI/Presets/OptionPreset.cs
+++ b/MiraAPI/Presets/OptionPreset.cs
@@ -70,7 +70,12 @@ public class OptionPreset(string name, MiraPluginInfo plugin, ConfigFile presetC
/// The option to reset.
public void ResetOption(OptionBehaviour baseOption)
{
- var selectedOpt = Plugin.InternalOptions.First(x => x.OptionBehaviour == baseOption);
+ var selectedOpt = Plugin.InternalOptions.FirstOrDefault(x => x.OptionBehaviour == baseOption);
+ if (selectedOpt == null)
+ {
+ return;
+ }
+
selectedOpt.LoadFromPreset(PresetConfig);
/*ModdedOptionsManager.SyncAllOptions();
@@ -84,7 +89,7 @@ public class OptionPreset(string name, MiraPluginInfo plugin, ConfigFile presetC
/// The value of whether the option is in the preset.
public bool IsOptionInPreset(OptionBehaviour baseOption)
{
- var selectedOpt = Plugin.InternalOptions.First(x => x.OptionBehaviour == baseOption);
- return selectedOpt.ConfigDefinition != null && PresetConfig.ContainsKey(selectedOpt.ConfigDefinition);
+ var selectedOpt = Plugin.InternalOptions.FirstOrDefault(x => x.OptionBehaviour == baseOption);
+ return selectedOpt != null && selectedOpt.ConfigDefinition != null && PresetConfig.ContainsKey(selectedOpt.ConfigDefinition);
}
}
diff --git a/MiraAPI/Resources/Locale/en_US.xml b/MiraAPI/Resources/Locale/en_US.xml
index 245b7ae..be120fa 100644
--- a/MiraAPI/Resources/Locale/en_US.xml
+++ b/MiraAPI/Resources/Locale/en_US.xml
@@ -55,4 +55,5 @@ Outlive the timer or eliminate the crew.
Options
Player List
Game Settings
+ Search...
diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs
index 25ab64e..0548eaa 100644
--- a/MiraAPI/Roles/CustomRoleManager.cs
+++ b/MiraAPI/Roles/CustomRoleManager.cs
@@ -57,6 +57,7 @@ public static class CustomRoleManager
internal static readonly Dictionary CustomRoles = [];
internal static readonly Dictionary RoleIds = [];
+ internal static readonly Dictionary RoleParentMods = [];
private static CppCollections.List? _emptySettings;
private static Il2CppReferenceArray? _emptyKillAnimations;
@@ -105,6 +106,7 @@ public static class CustomRoleManager
}
pluginInfo.InternalRoles.Add((ushort)role.Role, role);
+ RoleParentMods[roleType] = pluginInfo;
}
catch (Exception ex)
{
@@ -211,7 +213,9 @@ public static class CustomRoleManager
/// A object representing the parent mod of the role.
public static MiraPluginInfo FindParentMod(ICustomRole role)
{
- return MiraPluginManager.Instance.RegisteredPlugins.First(plugin => plugin.InternalRoles.ContainsValue(role as RoleBehaviour ?? throw new InvalidOperationException()));
+ return RoleParentMods.TryGetValue(role.GetType(), out var mod)
+ ? mod
+ : MiraPluginManager.Instance.RegisteredPlugins.First(plugin => plugin.InternalRoles.ContainsValue(role as RoleBehaviour ?? throw new InvalidOperationException()));
}
///
diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs
index dc38423..307b6ca 100644
--- a/MiraAPI/Roles/ICustomRole.cs
+++ b/MiraAPI/Roles/ICustomRole.cs
@@ -12,8 +12,6 @@ using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using TMPro;
using UnityEngine;
-using UnityEngine.UI;
-using Object = UnityEngine.Object;
namespace MiraAPI.Roles;
@@ -324,54 +322,15 @@ public interface ICustomRole : IOptionable
/// The of the wiki.
GameObject GetAdvancedWiki(MatchInfoGuide guide, TextMeshPro titleText, Scroller parent)
{
- parent.ScrollToTop();
- var obj = Helpers.CreateAdvancedWikiTab(
+ var abilities = WikiAbilities;
+ return Helpers.CreateAdvancedWikiPage(
guide,
- RoleNameLocale,
- RoleName + $" ({RoleFactionTitle})",
- RoleWikiDescription,
titleText,
- out var desc);
- var num = 0;
- if (WikiAbilities.Count != 0)
- {
- var grid = Object.Instantiate(parent.Inner, obj.transform);
- var layoutGroup = grid.GetComponent();
- layoutGroup.startAxis = GridLayoutGroup.Axis.Vertical;
- layoutGroup.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
- layoutGroup.spacing = new Vector2(0.75f, 0.4f);
- grid.DestroyChildren();
- foreach (var ability in WikiAbilities)
- {
- num++;
- var panel = Object.Instantiate(
- guide.MatchInfoRolePanelPrefab,
- grid);
- panel.roleCount.text = ability.AbilityType;
- panel.roleCount.transform.localPosition += new Vector3(-0.04f, 0);
- panel.roleName.text = ability.Name;
- panel.roleName.transform.localPosition += new Vector3(-0.04f, 0);
- panel.roleDescription.text = ability.Description;
- panel.roleDescription.rectTransform.sizeDelta = new Vector2(2.601f, 0.8f);
- panel.roleDescription.transform.localPosition += new Vector3(0, -0.1f);
- panel.roleIcon.sprite = ability.Icon.LoadAsset();
- panel.roleIcon.SetSizeLimit(0.13f);
-
- panel.roleIcon.material.SetInt(PlayerMaterial.MaskLayer, 50);
- panel.roleName.fontMaterial.SetFloat(panel.STENCIL_NAME, 50f);
- panel.roleDescription.fontMaterial.SetFloat(panel.STENCIL_NAME, 50f);
- panel.roleCount.fontMaterial.SetFloat(panel.STENCIL_NAME, 50f);
- panel.roleIcon.transform.localScale = new Vector3(3.5f, 3.5f, 1f);
- }
-
- grid.localPosition = new Vector3(-3.9f, 1.1f - desc.textBounds.size.y, 0f);
- grid.localScale = new Vector3(1.3f, 1.3f, 1);
- }
-
- obj.transform.SetParent(parent.Inner.transform);
- obj.transform.localPosition = new Vector3(0f, 0f, 0f);
- parent.SetYBoundsMax(Mathf.Clamp((desc.textBounds.size.y - 2) + (Mathf.Ceil(num / 2f) * 1.45f), 0f, 999f));
- return obj;
+ parent,
+ RoleNameLocale,
+ $"{this.GetTmpIcon()} {RoleName} ({RoleFactionTitle})",
+ RoleWikiDescription,
+ abilities);
}
///
diff --git a/MiraAPI/Utilities/Assets/MiraAssets.cs b/MiraAPI/Utilities/Assets/MiraAssets.cs
index 249e942..a57332b 100644
--- a/MiraAPI/Utilities/Assets/MiraAssets.cs
+++ b/MiraAPI/Utilities/Assets/MiraAssets.cs
@@ -19,7 +19,7 @@ public static class MiraAssets
new Vector2(0.5f, 0.5f),
100f,
0U,
- SpriteMeshType.Tight,
+ SpriteMeshType.FullRect,
new Vector4(20, 20, 20, 20));
RoundedBox = new LoadableAssetWrapper(boxSprite);
diff --git a/MiraAPI/Utilities/Assets/SpriteTools.cs b/MiraAPI/Utilities/Assets/SpriteTools.cs
index b0d4a1e..05ebcfc 100644
--- a/MiraAPI/Utilities/Assets/SpriteTools.cs
+++ b/MiraAPI/Utilities/Assets/SpriteTools.cs
@@ -48,7 +48,13 @@ public static class SpriteTools
public static Sprite LoadSpriteFromPath(string resourcePath, Assembly assembly, float pixelsPerUnit)
{
var tex = LoadTextureFromResourcePath(resourcePath, assembly);
- var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), pixelsPerUnit);
+ var sprite = Sprite.Create(
+ tex,
+ new Rect(0, 0, tex.width, tex.height),
+ new Vector2(0.5f, 0.5f),
+ pixelsPerUnit,
+ 0,
+ SpriteMeshType.FullRect);
sprite.name = resourcePath;
return sprite;
}
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index 1a8f962..7a3381c 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -242,7 +242,7 @@ public static class Extensions
public static IEnumerator CoLoopWithBudget(this IEnumerable collection, Action action)
{
var fps = Application.targetFrameRate > 0 ? Application.targetFrameRate : 60;
- long budget = 1 / (fps * 2); // default is half of current frame time.
+ var budget = 1000L / (fps * 2); // default is half of current frame time.
yield return collection.CoLoopWithBudget(budget, action);
}
diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs
index 3584588..8472e6b 100644
--- a/MiraAPI/Utilities/Helpers.cs
+++ b/MiraAPI/Utilities/Helpers.cs
@@ -6,16 +6,19 @@ using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
+using System.Text.RegularExpressions;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.OptionTypes;
+using MiraAPI.Modifiers;
using MiraAPI.Roles;
using MiraAPI.Translation;
using MiraAPI.Utilities.Assets;
using Rewired;
using TMPro;
using UnityEngine;
+using UnityEngine.UI;
using MethodBase = System.Reflection.MethodBase;
using Object = UnityEngine.Object;
using Random = UnityEngine.Random;
@@ -27,83 +30,67 @@ namespace MiraAPI.Utilities;
///
public static class Helpers
{
+ private static readonly Regex TrailingZeroRegex = new(@"\.0+(?!\d)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));
+
public static ReadOnlyCollection? GetModdedOptionsForType(Type classType)
{
- var optionGroups =
- AccessTools.Field(typeof(ModdedOptionsManager), "Groups").GetValue(null) as List;
+ var group = ModdedOptionsManager.Groups.FirstOrDefault(x => x.OptionableType == classType);
- return optionGroups?.FirstOrDefault(x => x.OptionableType == classType)?.Children;
+ return group?.Children;
}
public static string GetOptionsText(Type classType)
{
- var options = GetModdedOptionsForType(classType);
+ var group = ModdedOptionsManager.Groups.FirstOrDefault(x => x.OptionableType == classType);
+ var options = group?.Children;
+ var summaryProvider = group as ISummarizedOptions;
+ var hiddenKeys = summaryProvider?.WikiHiddenOptionKeys;
+
if (options == null)
{
return string.Empty;
}
- ISummarizedOptions? summaryProvider;
- IReadOnlySet? hiddenKeys;
- try
- {
- var optionGroups =
- AccessTools.Field(typeof(ModdedOptionsManager), "Groups").GetValue(null) as List;
- summaryProvider =
- optionGroups?.FirstOrDefault(x => x.OptionableType == classType) as ISummarizedOptions;
- hiddenKeys = summaryProvider?.WikiHiddenOptionKeys;
- }
- catch
- {
- summaryProvider = null;
- hiddenKeys = null;
- }
-
- var builder = new StringBuilder();
- builder.AppendLine(
- MiraApiPlugin.Culture,
- $"\n \n{MiraLocaleManager.Get("Options")}");
-
+ var lines = new List();
var insertedSummary = false;
foreach (var option in options)
{
- if (!insertedSummary && summaryProvider != null && hiddenKeys != null)
+ StringNames? key = option switch
{
- StringNames? key = option switch
- {
- ModdedToggleOption t => t.StringName,
- ModdedEnumOption e => e.StringName,
- ModdedNumberOption n => n.StringName,
- _ => null,
- };
- if (key.HasValue && hiddenKeys.Contains(key.Value))
- {
- foreach (var line in summaryProvider.GetWikiOptionSummaryLines())
- {
- if (!string.IsNullOrWhiteSpace(line))
- {
- builder.AppendLine(line);
- }
- }
+ ModdedToggleOption t => t.StringName,
+ ModdedEnumOption e => e.StringName,
+ ModdedNumberOption n => n.StringName,
+ _ => null,
+ };
- insertedSummary = true;
+ if (!insertedSummary && summaryProvider != null && hiddenKeys != null &&
+ key.HasValue && hiddenKeys.Contains(key.Value))
+ {
+ foreach (var line in summaryProvider.GetWikiOptionSummaryLines())
+ {
+ if (!string.IsNullOrWhiteSpace(line))
+ {
+ lines.Add(line);
+ }
}
+
+ insertedSummary = true;
+ }
+
+ if (!option.Visible())
+ {
+ continue;
+ }
+
+ if (key.HasValue && hiddenKeys?.Contains(key.Value) == true)
+ {
+ continue;
}
switch (option)
{
case ModdedToggleOption toggleOption:
- if (!toggleOption.Visible())
- {
- continue;
- }
-
- if (hiddenKeys != null && hiddenKeys.Contains(toggleOption.StringName))
- {
- continue;
- }
-
- builder.AppendLine(
+ lines.Add(
TranslationController.Instance.GetString(toggleOption.StringName) + ": " +
toggleOption.Value);
break;
@@ -116,65 +103,49 @@ public static class Helpers
builder.AppendLine(enumOption.Title + ": " + enumOption.Values[enumOption.Value]);
break;*/
case ModdedEnumOption enumOption:
- if (!enumOption.Visible())
- {
- continue;
- }
-
- if (hiddenKeys != null && hiddenKeys.Contains(enumOption.StringName))
- {
- continue;
- }
-
- builder.AppendLine(
+ lines.Add(
TranslationController.Instance.GetString(enumOption.StringName) + ": " +
MiraLocaleManager.Get(
enumOption.Values[enumOption.Value],
enumOption.Values[enumOption.Value]));
break;
case ModdedNumberOption numberOption:
- if (!numberOption.Visible())
- {
- continue;
- }
-
- if (hiddenKeys != null && hiddenKeys.Contains(numberOption.StringName))
- {
- continue;
- }
-
- var optionStr = numberOption.Data.GetValueString(numberOption.Value);
- if (optionStr.Contains(".000"))
- {
- optionStr = optionStr.Replace(".000", string.Empty);
- }
- else if (optionStr.Contains(".00"))
- {
- optionStr = optionStr.Replace(".00", string.Empty);
- }
- else if (optionStr.Contains(".0"))
- {
- optionStr = optionStr.Replace(".0", string.Empty);
- }
+ var optionStr = TrailingZeroRegex.Replace(
+ numberOption.Data.GetValueString(numberOption.Value),
+ string.Empty);
var title = TranslationController.Instance.GetString(numberOption.StringName);
if (numberOption is { NegativeWordValue: not "#", Value: -1 })
{
- builder.AppendLine(title + $": {numberOption.NegativeWordValue}");
+ lines.Add(title + $": {numberOption.NegativeWordValue}");
}
else if (numberOption is { ZeroWordValue: not "#", Value: 0 })
{
- builder.AppendLine(title + $": {numberOption.ZeroWordValue}");
+ lines.Add(title + $": {numberOption.ZeroWordValue}");
}
else
{
- builder.AppendLine(title + ": " + optionStr);
+ lines.Add(title + ": " + optionStr);
}
break;
}
}
+ if (lines.Count == 0)
+ {
+ return string.Empty;
+ }
+
+ var builder = new StringBuilder();
+ builder.AppendLine(
+ MiraApiPlugin.Culture,
+ $"\n \n{MiraLocaleManager.Get("Options")}");
+ foreach (var line in lines)
+ {
+ builder.AppendLine(line);
+ }
+
return builder.ToString();
}
@@ -718,6 +689,40 @@ public static class Helpers
return role is ICustomRole custom ? custom.RoleLongDescription : role.BlurbLong;
}
+ ///
+ /// Returns the TextMeshPro sprite tag for a role, matching how role names are shown in-game.
+ /// Uses the role's if set, otherwise falls back to the faction icon.
+ ///
+ /// The .
+ /// A <sprite> rich text tag.
+ public static string GetTmpIcon(this RoleBehaviour role)
+ {
+ return role is ICustomRole custom ? custom.GetTmpIcon() : $"";
+ }
+
+ ///
+ /// Returns the TextMeshPro sprite tag for a custom role, matching how role names are shown in-game.
+ /// Uses the role's if set, otherwise falls back to the faction icon.
+ ///
+ /// The .
+ /// A <sprite> rich text tag.
+ public static string GetTmpIcon(this ICustomRole role)
+ {
+ return role.Configuration.IconTmp
+ ? $""
+ : $"";
+ }
+
+ ///
+ /// Returns the TextMeshPro sprite tag for a modifier, or an empty string if it has no .
+ ///
+ /// The .
+ /// A <sprite> rich text tag, or .
+ public static string GetTmpIcon(this BaseModifier modifier)
+ {
+ return modifier.IconTmp ? $"" : string.Empty;
+ }
+
///
/// Returns whether the is blacklisted from appearing and spawning. (Only applicable to vanilla roles).
///
@@ -743,6 +748,7 @@ public static class Helpers
public static GameObject CreateAdvancedWikiTab(MatchInfoGuide guide, string objName, string title, string description, TextMeshPro titleTmp, out TextMeshPro descriptionTmp)
{
var obj = new GameObject(objName);
+ titleTmp.richText = true;
titleTmp.text = title;
descriptionTmp = Object.Instantiate(guide.MatchInfoRolePanelPrefab.roleCount, obj.transform);
descriptionTmp.fontSizeMin = descriptionTmp.fontSizeMax = descriptionTmp.fontSize = 2f;
@@ -757,4 +763,97 @@ public static class Helpers
descriptionTmp.ForceMeshUpdate();
return obj;
}
+
+ ///
+ /// Creates a full advanced wiki page with a title, a description, and an optional grid of ability panels.
+ ///
+ /// The guide object.
+ /// The instance of the title.
+ /// The the page should be parented to.
+ /// The name of the page object.
+ /// The title of the page.
+ /// The description of the page.
+ /// The abilities to display in a grid below the description.
+ /// Whether to use variable text sizing for ability descriptions (allows different heights).
+ /// The of the wiki page.
+ public static GameObject CreateAdvancedWikiPage(
+ MatchInfoGuide guide,
+ TextMeshPro titleTmp,
+ Scroller parent,
+ string objName,
+ string title,
+ string description,
+ IReadOnlyCollection abilities,
+ bool variableTextSizing = false)
+ {
+ parent.ScrollToTop();
+ var obj = CreateAdvancedWikiTab(
+ guide,
+ objName,
+ title,
+ description,
+ titleTmp,
+ out var desc);
+ var num = 0;
+ var maxTextSize = 0f;
+ if (abilities.Count != 0)
+ {
+ var grid = Object.Instantiate(parent.Inner, obj.transform);
+ var layoutGroup = grid.GetComponent();
+ layoutGroup.startAxis = GridLayoutGroup.Axis.Vertical;
+ layoutGroup.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
+ layoutGroup.spacing = new Vector2(0.75f, 0.4f);
+ grid.DestroyChildren();
+ foreach (var ability in abilities)
+ {
+ num++;
+ var panel = Object.Instantiate(guide.MatchInfoRolePanelPrefab, grid);
+ SetupAbilityPanel(panel, ability, variableTextSizing);
+ if (variableTextSizing)
+ {
+ var descSize = panel.roleDescription.textBounds.size.y;
+ if (maxTextSize < descSize)
+ {
+ maxTextSize = descSize;
+ }
+ }
+ }
+
+ grid.localPosition = new Vector3(-3.9f, 1.1f - desc.textBounds.size.y, 0f);
+ grid.localScale = new Vector3(1.3f, 1.3f, 1);
+ }
+
+ obj.transform.SetParent(parent.Inner.transform);
+ obj.transform.localPosition = new Vector3(0f, 0f, 0f);
+ var scrollHeight = variableTextSizing
+ ? (desc.textBounds.size.y - 2) + maxTextSize + 0.475f
+ : desc.textBounds.size.y - 2 + Mathf.Ceil(num / 2f) * 1.45f;
+ parent.SetYBoundsMax(Mathf.Clamp(scrollHeight, 0f, 999f));
+ return obj;
+ }
+
+ private static void SetupAbilityPanel(MatchInfoRolePanel panel, AdvancedWikiAbilityDescription ability, bool variableTextSizing = false)
+ {
+ panel.roleCount.text = ability.AbilityType;
+ panel.roleCount.transform.localPosition += new Vector3(-0.04f, 0);
+ panel.roleName.text = ability.Name;
+ panel.roleName.transform.localPosition += new Vector3(-0.04f, 0);
+ panel.roleDescription.text = ability.Description;
+ panel.roleDescription.rectTransform.sizeDelta = new Vector2(2.601f, 0.8f);
+ panel.roleDescription.transform.localPosition += new Vector3(0, -0.1f);
+ if (variableTextSizing)
+ {
+ panel.roleDescription.alignment = TextAlignmentOptions.Top;
+ panel.roleDescription.fontSizeMin = 1.5f;
+ panel.roleDescription.ForceMeshUpdate();
+ }
+ panel.roleIcon.sprite = ability.Icon.LoadAsset();
+ panel.roleIcon.SetSizeLimit(0.13f);
+
+ panel.roleIcon.material.SetInt(PlayerMaterial.MaskLayer, 50);
+ panel.roleName.fontMaterial.SetFloat(panel.STENCIL_NAME, 50f);
+ panel.roleDescription.fontMaterial.SetFloat(panel.STENCIL_NAME, 50f);
+ panel.roleCount.fontMaterial.SetFloat(panel.STENCIL_NAME, 50f);
+ panel.roleIcon.transform.localScale = new Vector3(3.5f, 3.5f, 1f);
+ }
}
diff --git a/MiraAPI/Utilities/LoadingRing.cs b/MiraAPI/Utilities/LoadingRing.cs
new file mode 100644
index 0000000..3f8af87
--- /dev/null
+++ b/MiraAPI/Utilities/LoadingRing.cs
@@ -0,0 +1,88 @@
+using System;
+using Reactor.Utilities.Attributes;
+using UnityEngine;
+
+namespace MiraAPI.Utilities;
+
+///
+/// A spinning ring used to indicate that content is loading.
+///
+[RegisterInIl2Cpp]
+public class LoadingRing(IntPtr cppPtr) : MonoBehaviour(cppPtr)
+{
+ private static Sprite? _ringSprite;
+
+ ///
+ /// Gets or sets the rotation speed in degrees per second.
+ ///
+ public float Speed { get; set; } = 270f;
+
+ ///
+ /// Creates a on the UI layer.
+ ///
+ /// The transform to parent the spinner to.
+ /// The local position of the spinner.
+ /// The uniform scale of the spinner.
+ /// The created .
+ public static LoadingRing Create(Transform parent, Vector3 localPosition, float scale)
+ {
+ if (!_ringSprite)
+ {
+ _ringSprite = CreateRingSprite();
+ }
+
+ var obj = new GameObject("LoadingRing")
+ {
+ transform =
+ {
+ parent = parent,
+ localPosition = localPosition,
+ localScale = Vector3.one * scale,
+ },
+ layer = LayerMask.NameToLayer("UI"),
+ };
+ var renderer = obj.AddComponent();
+ renderer.sprite = _ringSprite;
+ return obj.AddComponent();
+ }
+
+ ///
+ /// Rotates the ring.
+ ///
+ public void Update()
+ {
+ transform.Rotate(0f, 0f, -Speed * Time.deltaTime);
+ }
+
+ private static Sprite CreateRingSprite()
+ {
+ const int size = 128;
+ const float innerRadius = 46f;
+ const float outerRadius = 60f;
+ var texture = new Texture2D(size, size, TextureFormat.RGBA32, false)
+ {
+ filterMode = FilterMode.Bilinear,
+ wrapMode = TextureWrapMode.Clamp,
+ };
+ var pixels = new Color[size * size];
+ for (var y = 0; y < size; y++)
+ {
+ for (var x = 0; x < size; x++)
+ {
+ var dx = x + 0.5f - (size / 2f);
+ var dy = y + 0.5f - (size / 2f);
+ var radius = Mathf.Sqrt((dx * dx) + (dy * dy));
+ var ring = Mathf.Clamp01(radius - innerRadius) * Mathf.Clamp01(outerRadius - radius);
+ var angle = (Mathf.Atan2(dy, dx) + Mathf.PI) / (2f * Mathf.PI);
+ pixels[(y * size) + x] = new Color(1f, 1f, 1f, ring * Mathf.Lerp(1f, 0.1f, angle));
+ }
+ }
+
+ texture.SetPixels(pixels);
+ texture.Apply();
+ texture.hideFlags = HideFlags.HideAndDontSave;
+ var sprite = Sprite.Create(texture, new Rect(0, 0, size, size), new Vector2(0.5f, 0.5f), 100f);
+ sprite.hideFlags = HideFlags.HideAndDontSave;
+ return sprite;
+ }
+}