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);