From 6d1557a48d41923aa5848600d7aa3fa74187898f Mon Sep 17 00:00:00 2001
From: Atony <75191600+AtonyGit@users.noreply.github.com>
Date: Sun, 4 Oct 2026 17:17:42 -0500
Subject: [PATCH] Add modifier support for wiki
---
MiraAPI/GameOptions/ISummarizedOptions.cs | 6 +-
MiraAPI/Patches/RoleGuidePatches.cs | 332 ++++++++++++++++++----
2 files changed, 284 insertions(+), 54 deletions(-)
diff --git a/MiraAPI/GameOptions/ISummarizedOptions.cs b/MiraAPI/GameOptions/ISummarizedOptions.cs
index 60b5ea6..7873902 100644
--- a/MiraAPI/GameOptions/ISummarizedOptions.cs
+++ b/MiraAPI/GameOptions/ISummarizedOptions.cs
@@ -9,13 +9,13 @@ namespace MiraAPI.GameOptions;
public interface ISummarizedOptions
{
///
- /// Option string keys (StringNames) to omit from the wiki options list (e.g., a large set of related options).
+ /// Gets the option string keys (StringNames) to omit from the wiki options list (e.g., a large set of related options).
///
IReadOnlySet WikiHiddenOptionKeys { get; }
///
- /// Summary lines to insert when the first hidden option would have appeared.
+ /// Gets summary lines to insert when the first hidden option would have appeared.
/// Lines should already be formatted as "Title: Value".
///
IEnumerable GetWikiOptionSummaryLines();
-}
\ No newline at end of file
+}
diff --git a/MiraAPI/Patches/RoleGuidePatches.cs b/MiraAPI/Patches/RoleGuidePatches.cs
index 5822bf8..1e24d05 100644
--- a/MiraAPI/Patches/RoleGuidePatches.cs
+++ b/MiraAPI/Patches/RoleGuidePatches.cs
@@ -5,6 +5,8 @@ using System.Linq;
using AmongUs.GameOptions;
using HarmonyLib;
using Innersloth.Assets;
+using MiraAPI.Modifiers;
+using MiraAPI.Modifiers.Types;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
@@ -167,7 +169,7 @@ public static class RoleGuidePatches
sortingFilterButton.transform.localPosition = new Vector3(1f, 0.765f, -0.1f);
sortingFilterButton.transform.localScale = Vector3.one;
- __instance.numOfTabs = 3;
+ __instance.numOfTabs = 4;
__instance.TabButtons[2].SelectButton(true);
__instance.MatchInfoRoleMaskArea.transform.localPosition = new Vector3(-0.0184f, 0.15f, -0.1f);
@@ -196,9 +198,20 @@ public static class RoleGuidePatches
__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);
+ modifiersWikiTab = 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);
@@ -269,20 +282,11 @@ public static class RoleGuidePatches
_searchIconHover.sprite = MiraAssets.SearchIconHoverSprite;
}
- var newSorted = RolePanels
- .OrderByDescending(child =>
- child.Value.GetTitle().Equals(text, StringComparison.OrdinalIgnoreCase))
- .ThenByDescending(child => child.Value.GetTitle().Contains(
- text,
- StringComparison.InvariantCultureIgnoreCase))
- .ThenBy(GetSortingOrder());
-
- foreach (var pair in newSorted)
- {
- pair.Value.Panel.transform.SetAsLastSibling();
- }
+ SortAllModifiers(text);
+ SortAllRoles(text);
_rolesScroller.ScrollToTop();
+ modifiersWikiTab.ScrollToTop();
}));
searchBoxTmp.transform.localPosition = new Vector3(-1.438f, 0.756f, -0.2f);
@@ -334,13 +338,33 @@ public static class RoleGuidePatches
rolesBtnHighlight.size = Vector2.one;
rolesBtnHighlight.transform.GetChild(0).gameObject.SetActive(false);
- playerCollider.size = settingsCollider.size = rolesCollider.size = new Vector2(0.8f, 0.74f);
- playerCollider.offset = settingsCollider.offset = rolesCollider.offset = Vector2.zero;
+ var modifiersButton = Object.Instantiate(__instance.TabButtons[2], __instance.TabButtons[2].transform.parent);
+ modifiersButton.OnClick = new Button.ButtonClickedEvent();
+ modifiersButton.OnClick.AddListener((Action)(() => OpenModifiersTab()));
+ var modifiersCollider = modifiersButton.GetComponent();
+ modifiersButton.transform.GetChild(0).gameObject.SetActive(false);
+ var modifiersBtnInactive = modifiersButton.inactiveSprites.GetComponent();
+ modifiersBtnInactive.sprite = MiraAssets.WikiRolesButtonIdleSprite;
+ modifiersBtnInactive.size = Vector2.one;
+ modifiersBtnInactive.transform.GetChild(0).gameObject.SetActive(false);
+ var modifiersBtnSelected = modifiersButton.selectedSprites.GetComponent();
+ modifiersBtnSelected.sprite = MiraAssets.WikiRolesButtonOpenSprite;
+ modifiersBtnSelected.size = Vector2.one;
+ modifiersBtnSelected.transform.GetChild(0).gameObject.SetActive(false);
+ var modifiersBtnHighlight = modifiersButton.activeSprites.GetComponent();
+ modifiersBtnHighlight.sprite = MiraAssets.WikiRolesButtonHoverSprite;
+ modifiersBtnHighlight.size = Vector2.one;
+ modifiersBtnHighlight.transform.GetChild(0).gameObject.SetActive(false);
+ __instance.TabButtons.Add(modifiersButton);
+
+ playerCollider.size = settingsCollider.size = rolesCollider.size = modifiersCollider.size = new Vector2(0.8f, 0.74f);
+ playerCollider.offset = settingsCollider.offset = rolesCollider.offset = modifiersCollider.offset = Vector2.zero;
playerButton.transform.localScale = settingButton.transform.localScale =
- rolesButton.transform.localScale = new Vector3(0.7f, 0.7f, 1);
+ rolesButton.transform.localScale = modifiersButton.transform.localScale = new Vector3(0.7f, 0.7f, 1);
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
{
@@ -368,7 +392,15 @@ public static class RoleGuidePatches
return false;
}
+ public static void OpenModifiersTab()
+ {
+ advancedWikiTab?.gameObject.SetActive(false);
+ MatchInfoGuide.Instance.SetActiveTab(3);
+ }
+
private static readonly Dictionary RolePanels = [];
+ private static readonly Dictionary ModifierPanels = [];
+ private static Scroller modifiersWikiTab;
private static Scroller advancedWikiTab;
private static TextBoxTMP searchBoxTmp;
private static GameObject currentAdvancedTabObject;
@@ -403,14 +435,18 @@ public static class RoleGuidePatches
// ignored
}
- var newSorted = sortMethod is SortingMethod.Alphabetical
- ? RolePanels.OrderBy(GetSortingOrder())
- : RolePanels.OrderByDescending(GetSortingOrder());
-
- foreach (var pair in newSorted)
+ var text = string.Empty;
+ try
{
- pair.Value.Panel.transform.SetAsLastSibling();
+ text = searchBoxTmp.outputText.text;
}
+ catch
+ {
+ // ignored
+ }
+
+ SortAllRoles(text);
+ SortAllModifiers(text);
_rolesScroller.ScrollToTop();
}
@@ -444,14 +480,7 @@ public static class RoleGuidePatches
}
ToggleRoleVisibility();
- var newSorted = sortMethod is SortingMethod.Alphabetical
- ? RolePanels.OrderBy(GetSortingOrder())
- : RolePanels.OrderByDescending(GetSortingOrder());
-
- foreach (var pair in newSorted)
- {
- pair.Value.Panel.transform.SetAsLastSibling();
- }
+ ToggleModifierVisibility();
}
public static void SwitchMethod()
@@ -473,14 +502,18 @@ public static class RoleGuidePatches
? MiraAssets.SortingAzHoverSprite
: MiraAssets.SortingZaHoverSprite;
- var newSorted = sortMethod is SortingMethod.Alphabetical
- ? RolePanels.OrderBy(GetSortingOrder())
- : RolePanels.OrderByDescending(GetSortingOrder());
-
- foreach (var pair in newSorted)
+ var text = string.Empty;
+ try
{
- pair.Value.Panel.transform.SetAsLastSibling();
+ text = searchBoxTmp.outputText.text;
}
+ catch
+ {
+ // ignored
+ }
+
+ SortAllRoles(text);
+ SortAllModifiers(text);
}
public static void SwitchGrouping()
@@ -511,14 +544,18 @@ public static class RoleGuidePatches
break;
}
- var newSorted = sortMethod is SortingMethod.Alphabetical
- ? RolePanels.OrderBy(GetSortingOrder())
- : RolePanels.OrderByDescending(GetSortingOrder());
-
- foreach (var pair in newSorted)
+ var text = string.Empty;
+ try
{
- pair.Value.Panel.transform.SetAsLastSibling();
+ text = searchBoxTmp.outputText.text;
}
+ catch
+ {
+ // ignored
+ }
+
+ SortAllRoles(text);
+ SortAllModifiers(text);
}
public static void DisplayNormalRoleSettings(MatchInfoGuide instance, bool reset)
@@ -527,6 +564,7 @@ public static class RoleGuidePatches
if (reset)
{
RolePanels.Clear();
+ ModifierPanels.Clear();
instance.CreateSettingsEntry(
StringNames.GameNumImpostors,
GameManager.Instance.AllGameSettingData[StringNames.GameNumImpostors]
@@ -553,6 +591,40 @@ public static class RoleGuidePatches
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,
+ modifiersWikiTab.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 RoleManager.Instance.AllRoles.ToArray().OrderBy(x => x.GetRoleName()))
{
if (roleBehaviour.Role is not RoleTypes.Crewmate and not RoleTypes.Impostor and
@@ -598,6 +670,7 @@ public static class RoleGuidePatches
advancedWikiTab?.gameObject.SetActive(false);
+ ToggleModifierVisibility();
ToggleRoleVisibility();
if (reset)
{
@@ -605,6 +678,64 @@ public static class RoleGuidePatches
}
}
+
+ public static void ToggleModifierVisibility()
+ {
+ 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;
+ }
+
+ panel.gameObject.SetActive(true);
+ num++;
+ }
+
+ var text = string.Empty;
+ try
+ {
+ text = searchBoxTmp.outputText.text;
+ }
+ catch
+ {
+ // ignored
+ }
+
+ SortAllModifiers(text);
+
+ /*var instance = MatchInfoGuide.Instance;
+ instance.rolesEnabledMessage.SetActive(num == 0);*/
+
+ modifiersWikiTab.SetYBoundsMax(Mathf.Clamp(Mathf.Ceil(num / 2f) * 1.3f - 1.5f, 0f, 999f));
+ modifiersWikiTab.SetYBoundsMax(Mathf.Clamp(Mathf.Ceil(num / 2f) * 1.3f - 1.5f, 0f, 999f));
+ }
+
public static void ToggleRoleVisibility()
{
var num = 0;
@@ -644,14 +775,17 @@ public static class RoleGuidePatches
num++;
}
- var newSorted = sortMethod is SortingMethod.Alphabetical
- ? RolePanels.OrderBy(GetSortingOrder())
- : RolePanels.OrderByDescending(GetSortingOrder());
-
- foreach (var pair in newSorted)
+ var text = string.Empty;
+ try
{
- pair.Value.Panel.transform.SetAsLastSibling();
+ text = searchBoxTmp.outputText.text;
}
+ catch
+ {
+ // ignored
+ }
+
+ SortAllRoles(text);
var instance = MatchInfoGuide.Instance;
instance.rolesEnabledMessage.SetActive(num == 0);
@@ -660,6 +794,38 @@ public static class RoleGuidePatches
instance.MatchInfoRoleScroller.SetYBoundsMax(Mathf.Clamp(Mathf.Ceil(num / 2f) * 1.3f - 1.5f, 0f, 999f));
}
+ public static void SortAllRoles(string searchText)
+ {
+ var newSorted = RolePanels
+ .OrderByDescending(child =>
+ child.Value.GetTitle().Equals(searchText, StringComparison.OrdinalIgnoreCase))
+ .ThenByDescending(child => child.Value.GetTitle().Contains(
+ searchText,
+ StringComparison.InvariantCultureIgnoreCase))
+ .ThenBy(GetSortingOrder());
+
+ foreach (var pair in newSorted)
+ {
+ pair.Value.Panel.transform.SetAsLastSibling();
+ }
+ }
+
+ public static void SortAllModifiers(string searchText)
+ {
+ var newSorted = ModifierPanels
+ .OrderByDescending(child =>
+ child.Value.GetTitle().Equals(searchText, StringComparison.OrdinalIgnoreCase))
+ .ThenByDescending(child => child.Value.GetTitle().Contains(
+ searchText,
+ StringComparison.InvariantCultureIgnoreCase))
+ .ThenBy(GetModifierSortingOrder());
+
+ foreach (var pair in newSorted)
+ {
+ pair.Value.Panel.transform.SetAsLastSibling();
+ }
+ }
+
public static Func, string> GetSortingOrder()
{
switch (sortOrder)
@@ -676,6 +842,22 @@ public static class RoleGuidePatches
}
}
+ 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)
@@ -741,10 +923,33 @@ public static class RoleGuidePatches
advancedWikiTab?.gameObject.SetActive(false);
}
+ 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);
+ advancedWikiTab?.gameObject.SetActive(true);
+ if (currentAdvancedTabObject)
+ {
+ currentAdvancedTabObject.SetActive(false);
+ currentAdvancedTabObject.Destroy();
+ }
+
+ if (advancedWikiTab == null)
+ {
+ Warning($"Wiki tab is null.");
+ return;
+ }
+
+ currentAdvancedTabObject = modifier.GetAdvancedWiki(instance, titleText, advancedWikiTab);
+ }
+
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);
advancedWikiTab?.gameObject.SetActive(true);
if (currentAdvancedTabObject)
{
@@ -825,6 +1030,31 @@ public static class RoleGuidePatches
return false;
}
+ public static void SetModifierPanel(
+ this MatchInfoRolePanel instance,
+ GameModifier modifier,
+ 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()})";
+
+ instance.roleIcon.SetSizeLimit(0.13f);
+ instance.roleIcon.material.SetInt(PlayerMaterial.MaskLayer, 50);
+ instance.roleName.fontMaterial.SetFloat(instance.STENCIL_NAME, 50f);
+ instance.roleDescription.fontMaterial.SetFloat(instance.STENCIL_NAME, 50f);
+ instance.roleCount.fontMaterial.SetFloat(instance.STENCIL_NAME, 50f);
+ instance.roleIcon.transform.localScale = new Vector3(4f, 4f, 1f);
+ }
+
public static string GetCategoryTitle(this RoleBehaviour role)
{
if (role is ICustomRole customRole)
@@ -846,18 +1076,18 @@ public enum SortingGroups
{
Ungrouped,
Faction,
- AmountChance
+ AmountChance,
}
public enum SortingMethod
{
Alphabetical,
- AlphabeticalDescending
+ AlphabeticalDescending,
}
public enum SortingFilter
{
EnabledOnly,
DisabledOnly,
- AllRoles
+ AllRoles,
}