Merge branch 'dev' into overlooked-behaviour

This commit is contained in:
Atony authored and GitHub committed 2026-10-13 21:06:12 -05:00
commit a07efcd268
26 files changed
+1806 -765

No files matched your search

+20
View File
@@ -121,4 +121,24 @@ public interface IModdedOption
/// </summary> /// </summary>
public interface IModdedOptionList : IReadOnlyList<IModdedOption> public interface IModdedOptionList : IReadOnlyList<IModdedOption>
{ {
/// <summary>
/// Gets the function to name the keys of the individual options in the configuration file.
/// <para/>
/// Return <see langword="null"/> to use the default of the property's name with its index appended.
/// </summary>
Func<int, string>? ConfigName { get; init; }
}
/// <summary>
/// Interface for list of <typeparamref name="T"/> options.
/// </summary>
/// <typeparam name="T">The type of <see cref="IModdedOption"/>.</typeparam>
public interface IModdedOptionList<out T> : IModdedOptionList
where T : IModdedOption
{
/// <inheritdoc cref="IReadOnlyList{T}.this[int]"/>
new T this[int index] { get; }
/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
new IEnumerator<T> GetEnumerator();
} }
+3 -3
View File
@@ -9,13 +9,13 @@ namespace MiraAPI.GameOptions;
public interface ISummarizedOptions public interface ISummarizedOptions
{ {
/// <summary> /// <summary>
/// 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).
/// </summary> /// </summary>
IReadOnlySet<StringNames> WikiHiddenOptionKeys { get; } IReadOnlySet<StringNames> WikiHiddenOptionKeys { get; }
/// <summary> /// <summary>
/// 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". /// Lines should already be formatted as "Title: Value".
/// </summary> /// </summary>
IEnumerable<string> GetWikiOptionSummaryLines(); IEnumerable<string> GetWikiOptionSummaryLines();
} }
+4 -2
View File
@@ -198,9 +198,11 @@ public static class ModdedOptionsManager
return; return;
} }
Func<int, string> configName = optionList.ConfigName ?? (i => property.Name + (i + 1));
for (int i = 0; i < optionList.Count; i++) for (int i = 0; i < optionList.Count; i++)
{ {
RegisterOption(optionList[i], group, property.Name + i, pluginInfo); RegisterOption(optionList[i], group, configName(i), pluginInfo);
} }
} }
@@ -277,7 +279,7 @@ public static class ModdedOptionsManager
option.Visible = () => visibilityFunc(i); option.Visible = () => visibilityFunc(i);
} }
RegisterOption(option, group, property.Name + i, pluginInfo); RegisterOption(option, group, property.Name + (i + 1), pluginInfo);
} }
} }
@@ -9,7 +9,7 @@ namespace MiraAPI.GameOptions.OptionTypes;
/// Represents a modded option list. /// Represents a modded option list.
/// </summary> /// </summary>
/// <typeparam name="T">The type of options.</typeparam> /// <typeparam name="T">The type of options.</typeparam>
public class ModdedOptionList<T> : IModdedOptionList public class ModdedOptionList<T> : IModdedOptionList<T>
where T : IModdedOption where T : IModdedOption
{ {
/// <inheritdoc/> /// <inheritdoc/>
@@ -20,6 +20,9 @@ public class ModdedOptionList<T> : IModdedOptionList
/// </summary> /// </summary>
public IReadOnlyList<T> Options { get; } public IReadOnlyList<T> Options { get; }
/// <inheritdoc/>
public Func<int, string>? ConfigName { get; init; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ModdedOptionList{T}"/> class. /// Initializes a new instance of the <see cref="ModdedOptionList{T}"/> class.
/// </summary> /// </summary>
@@ -41,10 +44,10 @@ public class ModdedOptionList<T> : IModdedOptionList
Options = [.. options]; Options = [.. options];
} }
/// <inheritdoc/> /// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
public IEnumerator<IModdedOption> GetEnumerator() public IEnumerator<T> GetEnumerator()
{ {
return ((IEnumerable<IModdedOption>)Options).GetEnumerator(); return Options.GetEnumerator();
} }
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
@@ -52,6 +55,11 @@ public class ModdedOptionList<T> : IModdedOptionList
return Options.GetEnumerator(); return Options.GetEnumerator();
} }
IEnumerator<IModdedOption> IEnumerable<IModdedOption>.GetEnumerator()
{
return ((IEnumerable<IModdedOption>)Options).GetEnumerator();
}
/// <summary> /// <summary>
/// Indexes the option of type <typeparamref name="T"/>. /// Indexes the option of type <typeparamref name="T"/>.
/// </summary> /// </summary>
+22
View File
@@ -4,6 +4,7 @@ using BepInEx;
using BepInEx.Configuration; using BepInEx.Configuration;
using BepInEx.Unity.IL2CPP; using BepInEx.Unity.IL2CPP;
using HarmonyLib; using HarmonyLib;
using MiraAPI.Patches;
using MiraAPI.Patches.Roles; using MiraAPI.Patches.Roles;
using MiraAPI.PluginLoading; using MiraAPI.PluginLoading;
using MiraAPI.Translation; using MiraAPI.Translation;
@@ -63,9 +64,30 @@ public partial class MiraApiPlugin : BasePlugin, IMiraPlugin
private static MiraPluginManager? PluginManager { get; set; } private static MiraPluginManager? PluginManager { get; set; }
internal Harmony Harmony { get; } = new(Id); internal Harmony Harmony { get; } = new(Id);
public static ConfigEntry<SortingGroups> WikiSortingGroup { get; private set; }
public static ConfigEntry<SortingMethod> WikiSortingMethod { get; private set; }
public static ConfigEntry<SortingFilter> WikiSortingFilter { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public override void Load() public override void Load()
{ {
WikiSortingGroup = Config.Bind(
"Wiki",
"WikiSortingGroup",
SortingGroups.Ungrouped,
"Determines how clusters of roles / modifiers are grouped together, if at all.");
WikiSortingMethod = Config.Bind(
"Wiki",
"WikiSortingMethod",
SortingMethod.Alphabetical,
"Determines if the list of roles / modifiers are organized in ascending or descending order.");
WikiSortingFilter = Config.Bind(
"Wiki",
"WikiSortingFilter",
SortingFilter.AllRoles,
"Determines what roles / modifiers are displayed at once.");
Harmony.PatchAll(); Harmony.PatchAll();
RoleOptionsCollectionPatch.PatchRoleMethods(Harmony); RoleOptionsCollectionPatch.PatchRoleMethods(Harmony);
UiResetEvents.Initialize(); UiResetEvents.Initialize();
+94 -1
View File
@@ -1,7 +1,13 @@
using System; using System;
using System.Collections.Generic;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.GameOptions; using MiraAPI.GameOptions;
using MiraAPI.PluginLoading; using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MiraAPI.Translation;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets; using MiraAPI.Utilities.Assets;
using TMPro;
using UnityEngine; using UnityEngine;
namespace MiraAPI.Modifiers; namespace MiraAPI.Modifiers;
@@ -44,16 +50,98 @@ public abstract class BaseModifier : IOptionable
x => x.InternalModifiers.Exists(y => y.TypeId == TypeId)) x => x.InternalModifiers.Exists(y => y.TypeId == TypeId))
?? throw new InvalidOperationException("Modifier is not registered."); ?? throw new InvalidOperationException("Modifier is not registered.");
/// <summary>
/// Gets the id part used to build the modifier's translation keys. It is recommended to simply call it what the modifier is, as other mods may utilize it.
/// </summary>
public virtual string IdPart => GetType().Name;
/// <summary>
/// Gets the id part used to build the modifier's translation keys. It is recommended to call it ModGuid.Modifier.Type.
/// </summary>
public virtual string IdPrefix => GetType().Namespace!;
/// <summary> /// <summary>
/// Gets the modifier name. /// Gets the modifier name.
/// </summary> /// </summary>
public abstract string ModifierName { get; } public virtual string ModifierName => MiraLocaleManager.Get(ModifierNameLocale);
/// <summary>
/// Gets the modifier's name id for localization.
/// </summary>
public virtual string ModifierNameLocale => MiraLocaleManager.BuildTranslationId(IdPrefix, IdPart);
/// <summary>
/// Gets the medium description of the modifier. Used in the modifier guide and options menu.
/// </summary>
public virtual string ModifierMedDescription => MiraLocaleManager.Get(ModifierMedDescriptionLocale, GetDescription());
/// <summary>
/// Gets the modifier's medium description id for localization.
/// </summary>
public virtual string ModifierMedDescriptionLocale => MiraLocaleManager.BuildTranslationId(IdPrefix, IdPart, "MedDescription");
/// <summary>
/// Gets the wiki description of the modifier. Used in the wiki and normally appends the options text as well.
/// </summary>
public virtual string ModifierWikiDescription => MiraLocaleManager.Get(ModifierWikiDescriptionLocale, GetDescription()) +
Helpers.GetOptionsText(GetType());
/// <summary>
/// Gets the modifier's wiki description id for localization.
/// </summary>
public virtual string ModifierWikiDescriptionLocale => MiraLocaleManager.BuildTranslationId(IdPrefix, IdPart, "WikiDescription");
/// <summary>
/// Gets the category of the modifier. Used in the wiki.
/// </summary>
public virtual string ModifierCategoryTitle => MiraLocaleManager.Get("Modifier");
/// <summary>
/// Gets whether the modifier is forcibly shown or disabled in the wiki screen.
/// </summary>
/// <returns><see langword="true"/> if the modifier is always displayed, otherwise <see langword="false"/> if it is never displayable, or <see langword="null"/> if it is dictated by amount and chance.</returns>
public virtual bool? ForceShowModifierOnWiki => false;
/// <summary>
/// Gets the information to display in the wiki.
/// </summary>
/// <param name="guide">The guide object.</param>
/// <param name="titleText">The title text object.</param>
/// <param name="titleIcon">The title's icon.</param>
/// <param name="parent">The scroller parent.</param>
/// <returns>The <see cref="GameObject"/> of the wiki.</returns>
public virtual GameObject GetAdvancedWiki(MatchInfoGuide guide, TextMeshPro titleText, SpriteRenderer titleIcon, Scroller parent)
{
var abilities = WikiAbilities;
return Helpers.CreateAdvancedWikiPage(
guide,
titleText,
titleIcon,
parent,
ModifierNameLocale,
$"{ModifierName} ({ModifierCategoryTitle})".TrimStart(),
ModifierIcon ?? MiraAssets.IconNeutral.LoadAsset(),
ModifierWikiDescription,
abilities);
}
/// <summary>
/// Gets the list of abilities or other similar information to display below the text of a modifier's wiki page.
/// </summary>
[HideFromIl2Cpp]
public virtual List<AdvancedWikiAbilityDescription> WikiAbilities => [];
/// <summary> /// <summary>
/// Gets the modifier icon. Useless if <see cref="HideOnUi"/> is <see langword="true"/>. /// Gets the modifier icon. Useless if <see cref="HideOnUi"/> is <see langword="true"/>.
/// </summary> /// </summary>
public virtual LoadableAsset<Sprite>? ModifierIcon => null; public virtual LoadableAsset<Sprite>? ModifierIcon => null;
/// <summary>
/// Gets the <see cref="TMP_SpriteAsset"/> for the Modifier Icon.
/// </summary>
[HideFromIl2Cpp]
public virtual TMP_SpriteAsset IconTmp => null!;
/// <summary> /// <summary>
/// Gets a value indicating whether the modifier is hidden on the UI. Will be hidden either way if no description is provided. /// Gets a value indicating whether the modifier is hidden on the UI. Will be hidden either way if no description is provided.
/// </summary> /// </summary>
@@ -69,6 +157,11 @@ public abstract class BaseModifier : IOptionable
/// </summary> /// </summary>
public virtual Color FreeplayFileColor => Color.gray; public virtual Color FreeplayFileColor => Color.gray;
/// <summary>
/// Gets the <see cref="Color"/> of the modifier, used in the wiki and other UI outside freeplay.
/// </summary>
public virtual Color GeneralColor => FreeplayFileColor;
/// <summary> /// <summary>
/// Gets a value indicating whether the modifier is unique. If <see langword="true"/>, the player can only have one instance of this modifier. /// Gets a value indicating whether the modifier is unique. If <see langword="true"/>, the player can only have one instance of this modifier.
/// </summary> /// </summary>
+3
View File
@@ -9,6 +9,9 @@ namespace MiraAPI.Modifiers.Types;
[MiraIgnore] [MiraIgnore]
public abstract class GameModifier : BaseModifier public abstract class GameModifier : BaseModifier
{ {
/// <inheritdoc />
public override bool? ForceShowModifierOnWiki => null;
/// <inheritdoc /> /// <inheritdoc />
public override bool ShowInFreeplay => true; public override bool ShowInFreeplay => true;
+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);
} }
} }
+3
View File
@@ -9,7 +9,10 @@ using UnityEngine;
using UnityEngine.Events; using UnityEngine.Events;
using UnityEngine.UI; using UnityEngine.UI;
// TODO: for the love of fucking god please add a namespace on the asset bundle AND here - Atony.
/*
namespace MiraAPI.Presets; namespace MiraAPI.Presets;
*/
[RegisterInIl2Cpp] [RegisterInIl2Cpp]
[SuppressMessage("Style", "IDE0051:Remove unused private members", Justification = "Unity convention.")] [SuppressMessage("Style", "IDE0051:Remove unused private members", Justification = "Unity convention.")]
+6
View File
@@ -53,4 +53,10 @@ Outlive the timer or eliminate the crew.</string>
<string name="MiraApi.AbilityType.SelectiveMeeting">Selective Meeting Ability</string> <string name="MiraApi.AbilityType.SelectiveMeeting">Selective Meeting Ability</string>
<string name="MiraApi.AbilityType.MeetingMenu">Meeting Menu Ability</string> <string name="MiraApi.AbilityType.MeetingMenu">Meeting Menu Ability</string>
<string name="Options">Options</string> <string name="Options">Options</string>
<string name="MiraApi.Wiki.PlayersTab">Player List</string>
<string name="MiraApi.Wiki.SettingsTab">Game Settings</string>
<string name="MiraApi.Wiki.SearchPlaceholder">Search...</string>
<string name="MiraApi.Wiki.FilterLabel">Filter</string>
<string name="MiraApi.Wiki.GroupingLabel">Grouping</string>
<string name="MiraApi.Wiki.OrderLabel">Order</string>
</resources> </resources>
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+11 -1
View File
@@ -45,6 +45,11 @@ public static class CustomRoleManager
/// </summary> /// </summary>
public static IReadOnlyList<RoleBehaviour> CustomRoleBehaviours { get; private set; } = []; public static IReadOnlyList<RoleBehaviour> CustomRoleBehaviours { get; private set; } = [];
/// <summary>
/// Gets the <see cref="IReadOnlyList{T}"/> of all roles as <see cref="RoleBehaviour"/> objects.
/// </summary>
public static IReadOnlyList<RoleBehaviour> AllStoredRoleBehaviours { get; private set; } = [];
/// <summary> /// <summary>
/// Gets the <see cref="IReadOnlyList{T}"/> of custom roles as <see cref="ICustomRole"/> objects. /// Gets the <see cref="IReadOnlyList{T}"/> of custom roles as <see cref="ICustomRole"/> objects.
/// </summary> /// </summary>
@@ -52,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;
@@ -77,6 +83,7 @@ public static class CustomRoleManager
CustomRoleBehaviours = [.. CustomRoles.Values]; CustomRoleBehaviours = [.. CustomRoles.Values];
CustomMiraRoles = [.. CustomRoles.Values.OfType<ICustomRole>()]; CustomMiraRoles = [.. CustomRoles.Values.OfType<ICustomRole>()];
AllStoredRoleBehaviours = [.. RoleManager.Instance.AllRoles];
} }
internal static void RegisterRoleTypes(List<Type> roles, MiraPluginInfo pluginInfo) internal static void RegisterRoleTypes(List<Type> roles, MiraPluginInfo pluginInfo)
@@ -99,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)
{ {
@@ -205,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>
+11 -49
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;
@@ -320,58 +318,22 @@ public interface ICustomRole : IOptionable
/// </summary> /// </summary>
/// <param name="guide">The guide object.</param> /// <param name="guide">The guide object.</param>
/// <param name="titleText">The title text object.</param> /// <param name="titleText">The title text object.</param>
/// <param name="titleIcon">The title's icon.</param>
/// <param name="parent">The scroller parent.</param> /// <param name="parent">The scroller parent.</param>
/// <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, SpriteRenderer titleIcon, 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); titleIcon,
var num = 0; parent,
if (WikiAbilities.Count != 0) RoleNameLocale,
{ $"{RoleName} ({RoleFactionTitle})",
var grid = Object.Instantiate(parent.Inner, obj.transform); Configuration.Icon ?? MiraAssets.IconNeutral.LoadAsset(),
var layoutGroup = grid.GetComponent<GridLayoutGroup>(); RoleWikiDescription,
layoutGroup.startAxis = GridLayoutGroup.Axis.Vertical; abilities);
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>
+31 -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);
@@ -305,5 +305,35 @@ public static class MiraAssets
/// </summary> /// </summary>
public static LoadableResourceAsset VisibleOnIdleSprite { get; } = new("MiraAPI.Resources.Wiki.VisibleOnIdle.png"); public static LoadableResourceAsset VisibleOnIdleSprite { get; } = new("MiraAPI.Resources.Wiki.VisibleOnIdle.png");
/// <summary>
/// Gets the sprite used for crewmates.
/// </summary>
public static LoadableResourceAsset IconCrewmate { get; } = new("MiraAPI.Resources.Wiki.IconCrewmate.png");
/// <summary>
/// Gets the sprite used for impostors.
/// </summary>
public static LoadableResourceAsset IconImpostor { get; } = new("MiraAPI.Resources.Wiki.IconImpostor.png");
/// <summary>
/// Gets the sprite used for neutrals.
/// </summary>
public static LoadableResourceAsset IconNeutral { get; } = new("MiraAPI.Resources.Wiki.IconNeutral.png");
/// <summary>
/// Gets the sprite used for the player list.
/// </summary>
public static LoadableResourceAsset IconPlayerList { get; } = new("MiraAPI.Resources.Wiki.IconPlayerList.png");
/// <summary>
/// Gets the sprite used for settings.
/// </summary>
public static LoadableResourceAsset IconSettings { get; } = new("MiraAPI.Resources.Wiki.IconSettings.png");
/// <summary>
/// Gets the sprite used for terminology.
/// </summary>
public static LoadableResourceAsset IconTerminology { get; } = new("MiraAPI.Resources.Wiki.IconTerminology.png");
internal static LoadableResourceAsset BlankSprite { get; } = new("MiraAPI.Resources.BlankSprite.png"); internal static LoadableResourceAsset BlankSprite { get; } = new("MiraAPI.Resources.BlankSprite.png");
} }
+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;
} }
+3 -3
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);
} }
@@ -372,8 +372,8 @@ public static class Extensions
public static void ClearGarbageCollector() public static void ClearGarbageCollector()
{ {
Resources.UnloadUnusedAssets(); Resources.UnloadUnusedAssets();
Il2CppSystem.GC.Collect(); /*Il2CppSystem.GC.Collect();
GC.Collect(); GC.Collect();*/
} }
/// <summary> /// <summary>
+201 -92
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>
@@ -736,14 +741,19 @@ public static class Helpers
/// <param name="guide">The guide panel to attach the tab to.</param> /// <param name="guide">The guide panel to attach the tab to.</param>
/// <param name="objName">The name of the tab.</param> /// <param name="objName">The name of the tab.</param>
/// <param name="title">The title of the tab.</param> /// <param name="title">The title of the tab.</param>
/// <param name="icon">The icon of the page.</param>
/// <param name="description">The description of the tab.</param> /// <param name="description">The description of the tab.</param>
/// <param name="titleTmp">The <see cref="TextMeshPro"/> instance of the title.</param> /// <param name="titleTmp">The <see cref="TextMeshPro"/> instance of the title.</param>
/// <param name="titleIcon">The <see cref="SpriteRenderer"/> instance for the title.</param>
/// <param name="descriptionTmp">The <see cref="TextMeshPro"/> instance of the description.</param> /// <param name="descriptionTmp">The <see cref="TextMeshPro"/> instance of the description.</param>
/// <returns>The <see cref="GameObject"/> of the tab.</returns> /// <returns>The <see cref="GameObject"/> of the tab.</returns>
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, Sprite icon, string description, TextMeshPro titleTmp, SpriteRenderer titleIcon, out TextMeshPro descriptionTmp)
{ {
var obj = new GameObject(objName); var obj = new GameObject(objName);
titleTmp.richText = true;
titleTmp.text = title; titleTmp.text = title;
titleIcon.sprite = icon;
titleIcon.SetSizeLimit(0.55f);
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;
var sourceTmp = HudManager.Instance.TaskPanel.taskText; var sourceTmp = HudManager.Instance.TaskPanel.taskText;
@@ -757,4 +767,103 @@ 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="titleIcon">The <see cref="SpriteRenderer"/> instance for 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="icon">The icon 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,
SpriteRenderer titleIcon,
Scroller parent,
string objName,
string title,
Sprite icon,
string description,
IReadOnlyCollection<AdvancedWikiAbilityDescription> abilities,
bool variableTextSizing = false)
{
parent.ScrollToTop();
var obj = CreateAdvancedWikiTab(
guide,
objName,
title,
icon,
description,
titleTmp,
titleIcon,
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, 0.5f, 0f);
var scrollHeight = variableTextSizing
? (desc.textBounds.size.y - 2.5f) + maxTextSize + 0.475f
: desc.textBounds.size.y - 2.5f + 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;
}
}