diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs index 11fac1d..cf108b3 100644 --- a/MiraAPI/Modifiers/BaseModifier.cs +++ b/MiraAPI/Modifiers/BaseModifier.cs @@ -117,7 +117,7 @@ public abstract class BaseModifier : IOptionable titleText, parent, ModifierNameLocale, - ModifierName + $" ({ModifierCategoryTitle})", + $"{this.GetTmpIcon()} {ModifierName} ({ModifierCategoryTitle})".TrimStart(), ModifierWikiDescription, abilities); } diff --git a/MiraAPI/Patches/RoleGuidePatches.cs b/MiraAPI/Patches/RoleGuidePatches.cs index 509df6a..3e520a5 100644 --- a/MiraAPI/Patches/RoleGuidePatches.cs +++ b/MiraAPI/Patches/RoleGuidePatches.cs @@ -109,6 +109,56 @@ 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] @@ -156,6 +206,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; @@ -311,7 +366,7 @@ public static class RoleGuidePatches SwitchGrouping, MiraAssets.GroupNoneIdleSprite, MiraAssets.GroupNoneHoverSprite, - new Vector3(1.8f, 0.765f, -0.1f), + new Vector3(1.7f, 0.765f, -0.1f), out _searchSortingGroupIdle, out _searchSortingGroupHover); @@ -894,8 +949,16 @@ public static class RoleGuidePatches } _appliedSearch = string.Empty; - _searchIconIdle.sprite = MiraAssets.SearchIconIdleSprite; - _searchIconHover.sprite = MiraAssets.SearchIconHoverSprite; + if (_searchIconIdle) + { + _searchIconIdle.sprite = MiraAssets.SearchIconIdleSprite; + } + + if (_searchIconHover) + { + _searchIconHover.sprite = MiraAssets.SearchIconHoverSprite; + } + if (_searchBoxTmp && GetSearchText().Length > 0) { _suppressSearchRefresh = true; @@ -1177,7 +1240,7 @@ public static class RoleGuidePatches } else { - 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")) diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index 79f6570..307b6ca 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -328,7 +328,7 @@ public interface ICustomRole : IOptionable titleText, parent, RoleNameLocale, - RoleName + $" ({RoleFactionTitle})", + $"{this.GetTmpIcon()} {RoleName} ({RoleFactionTitle})", RoleWikiDescription, abilities); } diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 52fd46a..8472e6b 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -11,6 +11,7 @@ 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; @@ -688,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). /// @@ -713,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; @@ -738,6 +774,7 @@ public static class Helpers /// 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, @@ -746,7 +783,8 @@ public static class Helpers string objName, string title, string description, - IReadOnlyCollection abilities) + IReadOnlyCollection abilities, + bool variableTextSizing = false) { parent.ScrollToTop(); var obj = CreateAdvancedWikiTab( @@ -757,6 +795,7 @@ public static class Helpers titleTmp, out var desc); var num = 0; + var maxTextSize = 0f; if (abilities.Count != 0) { var grid = Object.Instantiate(parent.Inner, obj.transform); @@ -768,7 +807,16 @@ public static class Helpers foreach (var ability in abilities) { num++; - SetupAbilityPanel(Object.Instantiate(guide.MatchInfoRolePanelPrefab, grid), ability); + 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); @@ -777,11 +825,14 @@ public static class Helpers 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)); + 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) + 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); @@ -790,6 +841,12 @@ public static class Helpers 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);