Merge pull request #110 from rewalo/wiki-preset-fixes

Wiki preset fixes
This commit is contained in:
Sameer Umar authored and GitHub committed 2026-10-11 00:42:13 -04:00
commit 362cfc02ff
13 files changed
+1298 -1061

No files matched your search

+7 -48
View File
@@ -9,8 +9,6 @@ using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets; using MiraAPI.Utilities.Assets;
using TMPro; using TMPro;
using UnityEngine; using UnityEngine;
using UnityEngine.UI;
using Object = UnityEngine.Object;
namespace MiraAPI.Modifiers; namespace MiraAPI.Modifiers;
@@ -113,54 +111,15 @@ public abstract class BaseModifier : IOptionable
/// <returns>The <see cref="GameObject"/> of the wiki.</returns> /// <returns>The <see cref="GameObject"/> of the wiki.</returns>
public virtual GameObject GetAdvancedWiki(MatchInfoGuide guide, TextMeshPro titleText, Scroller parent) public virtual GameObject GetAdvancedWiki(MatchInfoGuide guide, TextMeshPro titleText, Scroller parent)
{ {
parent.ScrollToTop(); var abilities = WikiAbilities;
var obj = Helpers.CreateAdvancedWikiTab( return Helpers.CreateAdvancedWikiPage(
guide, guide,
ModifierNameLocale,
ModifierName + $" ({ModifierCategoryTitle})",
ModifierWikiDescription,
titleText, titleText,
out var desc); parent,
var num = 0; ModifierNameLocale,
if (WikiAbilities.Count != 0) $"{this.GetTmpIcon()} {ModifierName} ({ModifierCategoryTitle})".TrimStart(),
{ ModifierWikiDescription,
var grid = Object.Instantiate(parent.Inner, obj.transform); abilities);
var layoutGroup = grid.GetComponent<GridLayoutGroup>();
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;
} }
/// <summary> /// <summary>
+44 -70
View File
@@ -618,6 +618,8 @@ internal static class GameOptionsMenuPatch
{ {
menu.MapPicker.gameObject.SetActive(false); menu.MapPicker.gameObject.SetActive(false);
var mod = MenuState.Instance.CurrentMod; var mod = MenuState.Instance.CurrentMod;
var container = MenuState.Instance.CurrentContainer.transform;
PresetManager.DefaultPresets.TryGetValue(mod, out var defaultPreset);
var filteredGroups = MenuState.Instance.CurrentMenu switch var filteredGroups = MenuState.Instance.CurrentMenu switch
{ {
@@ -643,15 +645,13 @@ internal static class GameOptionsMenuPatch
{ {
foreach (var group in filteredGroups) 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; group.Ready = false;
if (group.Header == null || !group.Header) if (group.Header == null || !group.Header)
{ {
@@ -713,12 +713,6 @@ internal static class GameOptionsMenuPatch
menu.playerOptionOrigin, menu.playerOptionOrigin,
container)); container));
OptionPreset? defaultPreset = null;
if (PresetManager.DefaultPresets.TryGetValue(MenuState.Instance.CurrentMod, out var preset))
{
defaultPreset = preset;
}
yield return options.CoLoopWithBudget(newOpt => yield return options.CoLoopWithBudget(newOpt =>
{ {
newOpt.SetClickMask(menu.ButtonClickMask); newOpt.SetClickMask(menu.ButtonClickMask);
@@ -765,73 +759,53 @@ internal static class GameOptionsMenuPatch
} }
menu.Children.Add(newOpt); 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, transform =
localScale = new(.5f, .5f, 1), {
localPosition = new Vector3(-3.1f, 0f, -2f), parent = newOpt.transform,
}, localScale = new(.5f, .5f, 1),
layer = LayerMask.NameToLayer("UI"), localPosition = new Vector3(-3.1f, 0f, -2f),
}; },
var resetRend = resetBtn.AddComponent<SpriteRenderer>(); layer = LayerMask.NameToLayer("UI"),
resetRend.sprite = MiraAssets.ResetButton.LoadAsset(); };
resetRend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; var resetRend = resetBtn.AddComponent<SpriteRenderer>();
resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) resetRend.sprite = MiraAssets.ResetButton.LoadAsset();
? Color.white resetRend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
: group.GroupColor.FindAlternateColor(); resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor)
var resetBoxCol = resetBtn.gameObject.AddComponent<BoxCollider2D>(); ? Color.white
resetBoxCol.size = new Vector2(1f, 1f); : group.GroupColor.FindAlternateColor();
resetBoxCol.offset = new Vector2(0, 0); var resetBoxCol = resetBtn.gameObject.AddComponent<BoxCollider2D>();
var passiveButton = resetBtn.AddComponent<PassiveButton>(); resetBoxCol.size = new Vector2(1f, 1f);
passiveButton.OnClick = new Button.ButtonClickedEvent(); resetBoxCol.offset = new Vector2(0, 0);
passiveButton.ClickSound = menu.BackButton.GetComponent<PassiveButton>().ClickSound; var passiveButton = resetBtn.AddComponent<PassiveButton>();
passiveButton.OnMouseOver = new UnityEvent(); passiveButton.OnClick = new Button.ButtonClickedEvent();
passiveButton.OnMouseOver.AddListener( passiveButton.ClickSound = menu.BackButton.GetComponent<PassiveButton>().ClickSound;
(UnityAction)(() => passiveButton.OnMouseOver = new UnityEvent();
{ passiveButton.OnMouseOver.AddListener(
resetRend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor (UnityAction)(() =>
? group.GroupColor {
: MiraAssets.AcceptedTeal; resetRend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor
})); ? group.GroupColor
passiveButton.OnMouseOut = new UnityEvent(); : MiraAssets.AcceptedTeal;
passiveButton.OnMouseOut.AddListener( }));
(UnityAction)(() => passiveButton.OnMouseOut = new UnityEvent();
{ passiveButton.OnMouseOut.AddListener(
resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) (UnityAction)(() =>
? Color.white {
: group.GroupColor.FindAlternateColor(); resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor)
})); ? Color.white
if (newOpt is ToggleOption toggleOpt) : group.GroupColor.FindAlternateColor();
{ }));
passiveButton.OnClick.AddListener( passiveButton.OnClick.AddListener(
(UnityAction)(() => (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.Initialize();
newOpt.gameObject.SetActive(false); newOpt.gameObject.SetActive(false);
@@ -4,7 +4,6 @@ using MiraAPI.Roles;
using MiraAPI.Translation; using MiraAPI.Translation;
using MiraAPI.Utilities; using MiraAPI.Utilities;
using UnityEngine; using UnityEngine;
using Object = Il2CppSystem.Object;
namespace MiraAPI.Patches.Options; namespace MiraAPI.Patches.Options;
File diff suppressed because it is too large. Load diff
+8 -3
View File
@@ -70,7 +70,12 @@ public class OptionPreset(string name, MiraPluginInfo plugin, ConfigFile presetC
/// <param name="baseOption">The option to reset.</param> /// <param name="baseOption">The option to reset.</param>
public void ResetOption(OptionBehaviour baseOption) 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); selectedOpt.LoadFromPreset(PresetConfig);
/*ModdedOptionsManager.SyncAllOptions(); /*ModdedOptionsManager.SyncAllOptions();
@@ -84,7 +89,7 @@ public class OptionPreset(string name, MiraPluginInfo plugin, ConfigFile presetC
/// <returns>The value of whether the option is in the preset.</returns> /// <returns>The value of whether the option is in the preset.</returns>
public bool IsOptionInPreset(OptionBehaviour baseOption) public bool IsOptionInPreset(OptionBehaviour baseOption)
{ {
var selectedOpt = Plugin.InternalOptions.First(x => x.OptionBehaviour == baseOption); var selectedOpt = Plugin.InternalOptions.FirstOrDefault(x => x.OptionBehaviour == baseOption);
return selectedOpt.ConfigDefinition != null && PresetConfig.ContainsKey(selectedOpt.ConfigDefinition); return selectedOpt != null && selectedOpt.ConfigDefinition != null && PresetConfig.ContainsKey(selectedOpt.ConfigDefinition);
} }
} }
+1
View File
@@ -55,4 +55,5 @@ Outlive the timer or eliminate the crew.</string>
<string name="Options">Options</string> <string name="Options">Options</string>
<string name="MiraApi.Wiki.PlayersTab">Player List</string> <string name="MiraApi.Wiki.PlayersTab">Player List</string>
<string name="MiraApi.Wiki.SettingsTab">Game Settings</string> <string name="MiraApi.Wiki.SettingsTab">Game Settings</string>
<string name="MiraApi.Wiki.SearchPlaceholder">Search...</string>
</resources> </resources>
+5 -1
View File
@@ -57,6 +57,7 @@ public static class CustomRoleManager
internal static readonly Dictionary<ushort, RoleBehaviour> CustomRoles = []; internal static readonly Dictionary<ushort, RoleBehaviour> CustomRoles = [];
internal static readonly Dictionary<Type, ushort> RoleIds = []; internal static readonly Dictionary<Type, ushort> RoleIds = [];
internal static readonly Dictionary<Type, MiraPluginInfo> RoleParentMods = [];
private static CppCollections.List<BaseGameSetting>? _emptySettings; private static CppCollections.List<BaseGameSetting>? _emptySettings;
private static Il2CppReferenceArray<OverlayKillAnimation>? _emptyKillAnimations; private static Il2CppReferenceArray<OverlayKillAnimation>? _emptyKillAnimations;
@@ -105,6 +106,7 @@ public static class CustomRoleManager
} }
pluginInfo.InternalRoles.Add((ushort)role.Role, role); pluginInfo.InternalRoles.Add((ushort)role.Role, role);
RoleParentMods[roleType] = pluginInfo;
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -211,7 +213,9 @@ public static class CustomRoleManager
/// <returns>A <see cref="MiraPluginInfo"/> object representing the parent mod of the role.</returns> /// <returns>A <see cref="MiraPluginInfo"/> object representing the parent mod of the role.</returns>
public static MiraPluginInfo FindParentMod(ICustomRole 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()));
} }
/// <summary> /// <summary>
+7 -48
View File
@@ -12,8 +12,6 @@ using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets; using MiraAPI.Utilities.Assets;
using TMPro; using TMPro;
using UnityEngine; using UnityEngine;
using UnityEngine.UI;
using Object = UnityEngine.Object;
namespace MiraAPI.Roles; namespace MiraAPI.Roles;
@@ -324,54 +322,15 @@ public interface ICustomRole : IOptionable
/// <returns>The <see cref="GameObject"/> of the wiki.</returns> /// <returns>The <see cref="GameObject"/> of the wiki.</returns>
GameObject GetAdvancedWiki(MatchInfoGuide guide, TextMeshPro titleText, Scroller parent) GameObject GetAdvancedWiki(MatchInfoGuide guide, TextMeshPro titleText, Scroller parent)
{ {
parent.ScrollToTop(); var abilities = WikiAbilities;
var obj = Helpers.CreateAdvancedWikiTab( return Helpers.CreateAdvancedWikiPage(
guide, guide,
RoleNameLocale,
RoleName + $" ({RoleFactionTitle})",
RoleWikiDescription,
titleText, titleText,
out var desc); parent,
var num = 0; RoleNameLocale,
if (WikiAbilities.Count != 0) $"{this.GetTmpIcon()} {RoleName} ({RoleFactionTitle})",
{ RoleWikiDescription,
var grid = Object.Instantiate(parent.Inner, obj.transform); abilities);
var layoutGroup = grid.GetComponent<GridLayoutGroup>();
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;
} }
/// <summary> /// <summary>
+1 -1
View File
@@ -19,7 +19,7 @@ public static class MiraAssets
new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
100f, 100f,
0U, 0U,
SpriteMeshType.Tight, SpriteMeshType.FullRect,
new Vector4(20, 20, 20, 20)); new Vector4(20, 20, 20, 20));
RoundedBox = new LoadableAssetWrapper<Sprite>(boxSprite); RoundedBox = new LoadableAssetWrapper<Sprite>(boxSprite);
+7 -1
View File
@@ -48,7 +48,13 @@ public static class SpriteTools
public static Sprite LoadSpriteFromPath(string resourcePath, Assembly assembly, float pixelsPerUnit) public static Sprite LoadSpriteFromPath(string resourcePath, Assembly assembly, float pixelsPerUnit)
{ {
var tex = LoadTextureFromResourcePath(resourcePath, assembly); 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; sprite.name = resourcePath;
return sprite; return sprite;
} }
+1 -1
View File
@@ -242,7 +242,7 @@ public static class Extensions
public static IEnumerator CoLoopWithBudget<T>(this IEnumerable<T> collection, Action<T> action) public static IEnumerator CoLoopWithBudget<T>(this IEnumerable<T> collection, Action<T> action)
{ {
var fps = Application.targetFrameRate > 0 ? Application.targetFrameRate : 60; 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); yield return collection.CoLoopWithBudget(budget, action);
} }
+190 -91
View File
@@ -6,16 +6,19 @@ using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using HarmonyLib; using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppInterop.Runtime.InteropTypes.Arrays;
using MiraAPI.GameOptions; using MiraAPI.GameOptions;
using MiraAPI.GameOptions.OptionTypes; using MiraAPI.GameOptions.OptionTypes;
using MiraAPI.Modifiers;
using MiraAPI.Roles; using MiraAPI.Roles;
using MiraAPI.Translation; using MiraAPI.Translation;
using MiraAPI.Utilities.Assets; using MiraAPI.Utilities.Assets;
using Rewired; using Rewired;
using TMPro; using TMPro;
using UnityEngine; using UnityEngine;
using UnityEngine.UI;
using MethodBase = System.Reflection.MethodBase; using MethodBase = System.Reflection.MethodBase;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
using Random = UnityEngine.Random; using Random = UnityEngine.Random;
@@ -27,83 +30,67 @@ namespace MiraAPI.Utilities;
/// </summary> /// </summary>
public static class Helpers public static class Helpers
{ {
private static readonly Regex TrailingZeroRegex = new(@"\.0+(?!\d)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));
public static ReadOnlyCollection<IModdedOption>? GetModdedOptionsForType(Type classType) public static ReadOnlyCollection<IModdedOption>? GetModdedOptionsForType(Type classType)
{ {
var optionGroups = var group = ModdedOptionsManager.Groups.FirstOrDefault(x => x.OptionableType == classType);
AccessTools.Field(typeof(ModdedOptionsManager), "Groups").GetValue(null) as List<AbstractOptionGroup>;
return optionGroups?.FirstOrDefault(x => x.OptionableType == classType)?.Children; return group?.Children;
} }
public static string GetOptionsText(Type classType) 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) if (options == null)
{ {
return string.Empty; return string.Empty;
} }
ISummarizedOptions? summaryProvider; var lines = new List<string>();
IReadOnlySet<StringNames>? hiddenKeys;
try
{
var optionGroups =
AccessTools.Field(typeof(ModdedOptionsManager), "Groups").GetValue(null) as List<AbstractOptionGroup>;
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<size=50%> \n</size><b><color=#29D7B5>{MiraLocaleManager.Get("Options")}</color></b>");
var insertedSummary = false; var insertedSummary = false;
foreach (var option in options) 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,
ModdedToggleOption t => t.StringName, ModdedNumberOption n => n.StringName,
ModdedEnumOption e => e.StringName, _ => null,
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);
}
}
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) switch (option)
{ {
case ModdedToggleOption toggleOption: case ModdedToggleOption toggleOption:
if (!toggleOption.Visible()) lines.Add(
{
continue;
}
if (hiddenKeys != null && hiddenKeys.Contains(toggleOption.StringName))
{
continue;
}
builder.AppendLine(
TranslationController.Instance.GetString(toggleOption.StringName) + ": " + TranslationController.Instance.GetString(toggleOption.StringName) + ": " +
toggleOption.Value); toggleOption.Value);
break; break;
@@ -116,65 +103,49 @@ public static class Helpers
builder.AppendLine(enumOption.Title + ": " + enumOption.Values[enumOption.Value]); builder.AppendLine(enumOption.Title + ": " + enumOption.Values[enumOption.Value]);
break;*/ break;*/
case ModdedEnumOption enumOption: case ModdedEnumOption enumOption:
if (!enumOption.Visible()) lines.Add(
{
continue;
}
if (hiddenKeys != null && hiddenKeys.Contains(enumOption.StringName))
{
continue;
}
builder.AppendLine(
TranslationController.Instance.GetString(enumOption.StringName) + ": " + TranslationController.Instance.GetString(enumOption.StringName) + ": " +
MiraLocaleManager.Get( MiraLocaleManager.Get(
enumOption.Values[enumOption.Value], enumOption.Values[enumOption.Value],
enumOption.Values[enumOption.Value])); enumOption.Values[enumOption.Value]));
break; break;
case ModdedNumberOption numberOption: case ModdedNumberOption numberOption:
if (!numberOption.Visible()) var optionStr = TrailingZeroRegex.Replace(
{ numberOption.Data.GetValueString(numberOption.Value),
continue; string.Empty);
}
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 title = TranslationController.Instance.GetString(numberOption.StringName); var title = TranslationController.Instance.GetString(numberOption.StringName);
if (numberOption is { NegativeWordValue: not "#", Value: -1 }) 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 }) else if (numberOption is { ZeroWordValue: not "#", Value: 0 })
{ {
builder.AppendLine(title + $": {numberOption.ZeroWordValue}"); lines.Add(title + $": {numberOption.ZeroWordValue}");
} }
else else
{ {
builder.AppendLine(title + ": " + optionStr); lines.Add(title + ": " + optionStr);
} }
break; break;
} }
} }
if (lines.Count == 0)
{
return string.Empty;
}
var builder = new StringBuilder();
builder.AppendLine(
MiraApiPlugin.Culture,
$"\n<size=50%> \n</size><b><color=#29D7B5>{MiraLocaleManager.Get("Options")}</color></b>");
foreach (var line in lines)
{
builder.AppendLine(line);
}
return builder.ToString(); return builder.ToString();
} }
@@ -718,6 +689,40 @@ public static class Helpers
return role is ICustomRole custom ? custom.RoleLongDescription : role.BlurbLong; return role is ICustomRole custom ? custom.RoleLongDescription : role.BlurbLong;
} }
/// <summary>
/// Returns the TextMeshPro sprite tag for a role, matching how role names are shown in-game.
/// Uses the role's <see cref="CustomRoleConfiguration.IconTmp"/> if set, otherwise falls back to the faction icon.
/// </summary>
/// <param name="role">The <see cref="RoleBehaviour"/>.</param>
/// <returns>A <c>&lt;sprite&gt;</c> rich text tag.</returns>
public static string GetTmpIcon(this RoleBehaviour role)
{
return role is ICustomRole custom ? custom.GetTmpIcon() : $"<sprite name=\"AmongUs.Role.{role.Role}\">";
}
/// <summary>
/// Returns the TextMeshPro sprite tag for a custom role, matching how role names are shown in-game.
/// Uses the role's <see cref="CustomRoleConfiguration.IconTmp"/> if set, otherwise falls back to the faction icon.
/// </summary>
/// <param name="role">The <see cref="ICustomRole"/>.</param>
/// <returns>A <c>&lt;sprite&gt;</c> rich text tag.</returns>
public static string GetTmpIcon(this ICustomRole role)
{
return role.Configuration.IconTmp
? $"<sprite name=\"{role.Configuration.IconTmp.name}\">"
: $"<sprite name=\"AmongUs.Role.{role.Team}\">";
}
/// <summary>
/// Returns the TextMeshPro sprite tag for a modifier, or an empty string if it has no <see cref="BaseModifier.IconTmp"/>.
/// </summary>
/// <param name="modifier">The <see cref="BaseModifier"/>.</param>
/// <returns>A <c>&lt;sprite&gt;</c> rich text tag, or <see cref="string.Empty"/>.</returns>
public static string GetTmpIcon(this BaseModifier modifier)
{
return modifier.IconTmp ? $"<sprite name=\"{modifier.IconTmp.name}\">" : string.Empty;
}
/// <summary> /// <summary>
/// Returns whether the <see cref="RoleBehaviour"/> is blacklisted from appearing and spawning. (Only applicable to vanilla roles). /// Returns whether the <see cref="RoleBehaviour"/> is blacklisted from appearing and spawning. (Only applicable to vanilla roles).
/// </summary> /// </summary>
@@ -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) public static GameObject CreateAdvancedWikiTab(MatchInfoGuide guide, string objName, string title, string description, TextMeshPro titleTmp, out TextMeshPro descriptionTmp)
{ {
var obj = new GameObject(objName); var obj = new GameObject(objName);
titleTmp.richText = true;
titleTmp.text = title; titleTmp.text = title;
descriptionTmp = Object.Instantiate(guide.MatchInfoRolePanelPrefab.roleCount, obj.transform); descriptionTmp = Object.Instantiate(guide.MatchInfoRolePanelPrefab.roleCount, obj.transform);
descriptionTmp.fontSizeMin = descriptionTmp.fontSizeMax = descriptionTmp.fontSize = 2f; descriptionTmp.fontSizeMin = descriptionTmp.fontSizeMax = descriptionTmp.fontSize = 2f;
@@ -757,4 +763,97 @@ public static class Helpers
descriptionTmp.ForceMeshUpdate(); descriptionTmp.ForceMeshUpdate();
return obj; return obj;
} }
/// <summary>
/// Creates a full advanced wiki page with a title, a description, and an optional grid of ability panels.
/// </summary>
/// <param name="guide">The guide object.</param>
/// <param name="titleTmp">The <see cref="TextMeshPro"/> instance of the title.</param>
/// <param name="parent">The <see cref="Scroller"/> the page should be parented to.</param>
/// <param name="objName">The name of the page object.</param>
/// <param name="title">The title of the page.</param>
/// <param name="description">The description of the page.</param>
/// <param name="abilities">The abilities to display in a grid below the description.</param>
/// <param name="variableTextSizing">Whether to use variable text sizing for ability descriptions (allows different heights).</param>
/// <returns>The <see cref="GameObject"/> of the wiki page.</returns>
public static GameObject CreateAdvancedWikiPage(
MatchInfoGuide guide,
TextMeshPro titleTmp,
Scroller parent,
string objName,
string title,
string description,
IReadOnlyCollection<AdvancedWikiAbilityDescription> 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<GridLayoutGroup>();
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);
}
} }
+88
View File
@@ -0,0 +1,88 @@
using System;
using Reactor.Utilities.Attributes;
using UnityEngine;
namespace MiraAPI.Utilities;
/// <summary>
/// A spinning ring used to indicate that content is loading.
/// </summary>
[RegisterInIl2Cpp]
public class LoadingRing(IntPtr cppPtr) : MonoBehaviour(cppPtr)
{
private static Sprite? _ringSprite;
/// <summary>
/// Gets or sets the rotation speed in degrees per second.
/// </summary>
public float Speed { get; set; } = 270f;
/// <summary>
/// Creates a <see cref="LoadingRing"/> on the UI layer.
/// </summary>
/// <param name="parent">The transform to parent the spinner to.</param>
/// <param name="localPosition">The local position of the spinner.</param>
/// <param name="scale">The uniform scale of the spinner.</param>
/// <returns>The created <see cref="LoadingRing"/>.</returns>
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<SpriteRenderer>();
renderer.sprite = _ringSprite;
return obj.AddComponent<LoadingRing>();
}
/// <summary>
/// Rotates the ring.
/// </summary>
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;
}
}