mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-19 15:51:48 +00:00
Merge branch 'dev' into pr/111
This commit is contained in:
commit
5e863747f7
29 files changed
+319
-1145
No files matched your search
@@ -86,3 +86,15 @@ dotnet_diagnostic.SA1133.severity = none
|
|||||||
|
|
||||||
# IDE0001: Simplify Names
|
# IDE0001: Simplify Names
|
||||||
dotnet_diagnostic.IDE0001.severity = warning
|
dotnet_diagnostic.IDE0001.severity = warning
|
||||||
|
|
||||||
|
# IDE0079: Remove unnecessary suppression
|
||||||
|
dotnet_diagnostic.IDE0079.severity = none
|
||||||
|
|
||||||
|
# S3903: Types should be defined in named namespaces
|
||||||
|
dotnet_diagnostic.S3903.severity = none
|
||||||
|
|
||||||
|
# IDE0045: Convert to conditional expression
|
||||||
|
dotnet_diagnostic.IDE0045.severity = none
|
||||||
|
|
||||||
|
# IDE0046: Convert to conditional expression
|
||||||
|
dotnet_diagnostic.IDE0046.severity = none
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[*.{cs,vb}]
|
||||||
|
|
||||||
|
# SA1602: Enumeration items should be documented.
|
||||||
|
dotnet_diagnostic.SA1602.severity = none
|
||||||
@@ -16,11 +16,11 @@
|
|||||||
Your votes have double the power.</string>
|
Your votes have double the power.</string>
|
||||||
<string name="ApiExample.Role.Crewmate.Mayor.TabDescription">You get one extra vote in the meeting!</string>
|
<string name="ApiExample.Role.Crewmate.Mayor.TabDescription">You get one extra vote in the meeting!</string>
|
||||||
<string name="ApiExample.Role.Crewmate.Mayor.WikiDescription">The Mayor is a Crewmate role that gets two votes in the meeting, instead of the singular vote other Crewmates get.</string>
|
<string name="ApiExample.Role.Crewmate.Mayor.WikiDescription">The Mayor is a Crewmate role that gets two votes in the meeting, instead of the singular vote other Crewmates get.</string>
|
||||||
<string name="ApiExample.Role.Crewmate.Chamelon">Chamelon</string>
|
<string name="ApiExample.Role.Crewmate.Chameleon">Chameleon</string>
|
||||||
<string name="ApiExample.Role.Crewmate.Chamelon.IntroBlurb">Become one with the environment.</string>
|
<string name="ApiExample.Role.Crewmate.Chameleon.IntroBlurb">Become one with the environment.</string>
|
||||||
<string name="ApiExample.Role.Crewmate.Chamelon.MedDescription">Fade out of everyone's vision by not moving.</string>
|
<string name="ApiExample.Role.Crewmate.Chameleon.MedDescription">Fade out of everyone's vision by not moving.</string>
|
||||||
<string name="ApiExample.Role.Crewmate.Chamelon.TabDescription">Stay invisible while not moving.</string>
|
<string name="ApiExample.Role.Crewmate.Chameleon.TabDescription">Stay invisible while not moving.</string>
|
||||||
<string name="ApiExample.Role.Crewmate.Chamelon.WikiDescription">The Chamelon is a Crewmate role that slowly becomes invisible while standing still.</string>
|
<string name="ApiExample.Role.Crewmate.Chameleon.WikiDescription">The Chameleon is a Crewmate role that slowly becomes invisible while standing still.</string>
|
||||||
<string name="ApiExample.Role.Outcast.OutcastKiller">Outcast Killer</string>
|
<string name="ApiExample.Role.Outcast.OutcastKiller">Outcast Killer</string>
|
||||||
<string name="ApiExample.Role.Outcast.OutcastKiller.IntroBlurb">Kill everyone else.</string>
|
<string name="ApiExample.Role.Outcast.OutcastKiller.IntroBlurb">Kill everyone else.</string>
|
||||||
<string name="ApiExample.Role.Outcast.OutcastKiller.MedDescription">Be the last outcast standing.
|
<string name="ApiExample.Role.Outcast.OutcastKiller.MedDescription">Be the last outcast standing.
|
||||||
@@ -73,7 +73,7 @@ Eliminate every other faction.</string>
|
|||||||
<string name="settings.miraExample.exampleBool2">Example Bool 2</string>
|
<string name="settings.miraExample.exampleBool2">Example Bool 2</string>
|
||||||
<string name="settings.miraExample.slider">Slider</string>
|
<string name="settings.miraExample.slider">Slider</string>
|
||||||
<string name="settings.miraExample.exampleNumber">Example Number</string>
|
<string name="settings.miraExample.exampleNumber">Example Number</string>
|
||||||
<string name="settings.miraExample.exampleMultiplier">Example Multplier</string>
|
<string name="settings.miraExample.exampleMultiplier">Example Multiplier</string>
|
||||||
<string name="settings.miraExample.exampleEnum">Example Enum</string>
|
<string name="settings.miraExample.exampleEnum">Example Enum</string>
|
||||||
<string name="settings.miraExample.button.exampleButton">Example Button</string>
|
<string name="settings.miraExample.button.exampleButton">Example Button</string>
|
||||||
<string name="gameover.outcastKiller.win">Outcast Killer Wins!</string>
|
<string name="gameover.outcastKiller.win">Outcast Killer Wins!</string>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AmongUs.Data;
|
using AmongUs.Data;
|
||||||
@@ -462,7 +461,6 @@ public class HideAndSeekMode : AbstractGameMode
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Warning cascades into forcing the entire tree to be ternary operators.")]
|
|
||||||
public override PlayerBodyTypes GetBodyType(PlayerControl player)
|
public override PlayerBodyTypes GetBodyType(PlayerControl player)
|
||||||
{
|
{
|
||||||
if (player == null || player.Data == null || player.Data.Role == null)
|
if (player == null || player.Data == null || player.Data.Role == null)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class ModdedPlayerOption : ModdedOption<int>
|
|||||||
data.Title = StringName;
|
data.Title = StringName;
|
||||||
data.Type = global::OptionTypes.Player;
|
data.Type = global::OptionTypes.Player;
|
||||||
|
|
||||||
Values = new();
|
Values = [];
|
||||||
AllowNone = allowNone;
|
AllowNone = allowNone;
|
||||||
Value = DefaultValue;
|
Value = DefaultValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ public abstract class CustomPaginableMenu : CustomPhoneMenu<CustomPaginableMenu.
|
|||||||
: text.Trim().ToLowerInvariant();
|
: text.Trim().ToLowerInvariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Creates a rather large ternary operation.")]
|
|
||||||
private List<MenuEntry> GetFilteredEntries()
|
private List<MenuEntry> GetFilteredEntries()
|
||||||
{
|
{
|
||||||
var query = NormalizeForSearch(searchText);
|
var query = NormalizeForSearch(searchText);
|
||||||
|
|||||||
@@ -64,11 +64,9 @@ 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; }
|
internal static ConfigEntry<SortingGroups> WikiSortingGroup { get; private set; }
|
||||||
|
internal static ConfigEntry<SortingMethod> WikiSortingMethod { get; private set; }
|
||||||
public static ConfigEntry<SortingMethod> WikiSortingMethod { get; private set; }
|
internal static ConfigEntry<SortingFilter> WikiSortingFilter { get; private set; }
|
||||||
|
|
||||||
public static ConfigEntry<SortingFilter> WikiSortingFilter { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Load()
|
public override void Load()
|
||||||
|
|||||||
@@ -12,5 +12,5 @@ dotnet_diagnostic.SA1611.severity = none
|
|||||||
# SA1615: Element return value should be documented.
|
# SA1615: Element return value should be documented.
|
||||||
dotnet_diagnostic.SA1615.severity = none
|
dotnet_diagnostic.SA1615.severity = none
|
||||||
|
|
||||||
# S125: Sections of code should not be commented out.
|
# SA1602: Enumeration items should be documented.
|
||||||
dotnet_diagnostic.S125.severity = none
|
dotnet_diagnostic.SA1602.severity = none
|
||||||
@@ -36,7 +36,7 @@ internal static class AmongUsClientSyncPatch
|
|||||||
GameModeOption.RpcSyncGamemode(PlayerControl.LocalPlayer, GameModeOption.Value);
|
GameModeOption.RpcSyncGamemode(PlayerControl.LocalPlayer, GameModeOption.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HarmonyPostfix]
|
[HarmonyPrefix]
|
||||||
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerLeft))]
|
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerLeft))]
|
||||||
public static void PlayerLeftPatch(ClientData data, DisconnectReasons reason)
|
public static void PlayerLeftPatch(ClientData data, DisconnectReasons reason)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,193 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using AmongUs.Data;
|
|
||||||
using HarmonyLib;
|
|
||||||
using MiraAPI.Utilities;
|
|
||||||
using MiraAPI.Utilities.Assets;
|
|
||||||
using Reactor.Utilities;
|
|
||||||
using Reactor.Utilities.Extensions;
|
|
||||||
using TMPro;
|
|
||||||
using UnityEngine;
|
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
|
|
||||||
namespace MiraAPI.Patches.Menu;
|
|
||||||
|
|
||||||
[HarmonyPatch(typeof(HatsTab))]
|
|
||||||
public static class HatsTabPatches
|
|
||||||
{
|
|
||||||
private static readonly Dictionary<int, string> StoreNames = [];
|
|
||||||
private static SortedList<string, List<HatData>> sortedHats = [];
|
|
||||||
private static int currentPage;
|
|
||||||
|
|
||||||
private static void PreviousPage(HatsTab hatsTab)
|
|
||||||
{
|
|
||||||
currentPage--;
|
|
||||||
currentPage = currentPage < 0 ? sortedHats.Count - 1 : currentPage;
|
|
||||||
GenerateHats(hatsTab, currentPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void NextPage(HatsTab hatsTab)
|
|
||||||
{
|
|
||||||
currentPage++;
|
|
||||||
currentPage = currentPage > sortedHats.Count - 1 ? 0 : currentPage;
|
|
||||||
GenerateHats(hatsTab, currentPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HarmonyPatch(nameof(HatsTab.OnEnable))]
|
|
||||||
[HarmonyPrefix]
|
|
||||||
[SuppressMessage("Style", "IDE0028:Collection initialization can be simplified", Justification = "Preview feature.")]
|
|
||||||
public static bool OnEnablePrefix(HatsTab __instance)
|
|
||||||
{
|
|
||||||
if (!AddressablesLoader.AddressableHatsExist)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
__instance.currentHat = HatManager.Instance.GetHatById(DataManager.Player.Customization.Hat);
|
|
||||||
var allHats = HatManager.Instance.GetUnlockedHats().ToImmutableList();
|
|
||||||
|
|
||||||
if (sortedHats.Count == 0)
|
|
||||||
{
|
|
||||||
var num = 0;
|
|
||||||
var comparer = new ControllableComparer<string>(["vanilla"], [], StringComparer.InvariantCulture);
|
|
||||||
sortedHats = new SortedList<string, List<HatData>>(comparer);
|
|
||||||
foreach (var hat in allHats)
|
|
||||||
{
|
|
||||||
if (!sortedHats.ContainsKey(hat.StoreName)) sortedHats[hat.StoreName] = [];
|
|
||||||
sortedHats[hat.StoreName].Add(hat);
|
|
||||||
|
|
||||||
if (!StoreNames.ContainsValue(hat.StoreName))
|
|
||||||
{
|
|
||||||
StoreNames.Add(num, hat.StoreName);
|
|
||||||
num++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InventoryUtility.CreateNextBackButtons(__instance, PreviousPage, NextPage);
|
|
||||||
|
|
||||||
GenerateHats(__instance, currentPage);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HarmonyPatch(typeof(HatsTab), nameof(HatsTab.Update))]
|
|
||||||
[HarmonyPrefix]
|
|
||||||
public static void UpdatePrefix(HatsTab __instance)
|
|
||||||
{
|
|
||||||
if (!AddressablesLoader.AddressableHatsExist)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortedHats.Count == 0) return;
|
|
||||||
|
|
||||||
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
|
|
||||||
{
|
|
||||||
PreviousPage(__instance);
|
|
||||||
}
|
|
||||||
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
|
|
||||||
{
|
|
||||||
NextPage(__instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerator? loadRoutine;
|
|
||||||
private static int hatIndex;
|
|
||||||
|
|
||||||
private static void GenerateHats(HatsTab __instance, int page)
|
|
||||||
{
|
|
||||||
if (loadRoutine != null) Coroutines.Stop(loadRoutine);
|
|
||||||
|
|
||||||
hatIndex = 0;
|
|
||||||
foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy();
|
|
||||||
__instance.ColorChips.Clear();
|
|
||||||
__instance.scroller.Inner.GetComponentsInChildren<TextMeshPro>().Do(x => x.gameObject.DeepDestroy(false));
|
|
||||||
|
|
||||||
var groupNameText = __instance.GetComponentInChildren<TextMeshPro>(false);
|
|
||||||
var group = sortedHats.Where(x => x.Key == StoreNames[page]);
|
|
||||||
|
|
||||||
foreach (var (groupName, hats) in group)
|
|
||||||
{
|
|
||||||
var text = Object.Instantiate(groupNameText, __instance.scroller.Inner);
|
|
||||||
text.enabled = true;
|
|
||||||
text.gameObject.transform.localScale = Vector3.one;
|
|
||||||
text.GetComponent<TextTranslatorTMP>().Destroy();
|
|
||||||
text.EnableStencilMasking();
|
|
||||||
text.text = $"{groupName} ({currentPage + 1}/{sortedHats.Count})";
|
|
||||||
text.alignment = TextAlignmentOptions.Center;
|
|
||||||
text.fontSize = 5f;
|
|
||||||
text.fontSizeMax = 5f;
|
|
||||||
text.fontSizeMin = 0f;
|
|
||||||
var xLerp = __instance.XRange.Lerp(0.5f);
|
|
||||||
|
|
||||||
// ReSharper disable once PossibleLossOfFraction (Justification: Intended.)
|
|
||||||
var yLerp = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
|
||||||
text.transform.localPosition = new Vector3(xLerp, yLerp, -1f);
|
|
||||||
|
|
||||||
hatIndex += 5;
|
|
||||||
loadRoutine = Coroutines.Start(CoGenerateChips(__instance, hats));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReSharper disable once PossibleLossOfFraction (Justification: Intended.)
|
|
||||||
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
|
|
||||||
__instance.currentHatIsEquipped = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerator CoGenerateChips(HatsTab __instance, List<HatData> hats)
|
|
||||||
{
|
|
||||||
__instance.scroller.ScrollToTop();
|
|
||||||
var batchSize = 5;
|
|
||||||
for (var i = 0; i < hats.Count; i += batchSize)
|
|
||||||
{
|
|
||||||
var batch = hats.Skip(i).Take(batchSize).ToList();
|
|
||||||
|
|
||||||
foreach (var hat in batch.OrderBy(HatManager.Instance.allHats.IndexOf))
|
|
||||||
{
|
|
||||||
var hatXPosition = __instance.XRange.Lerp(hatIndex % __instance.NumPerRow / (__instance.NumPerRow - 1f));
|
|
||||||
|
|
||||||
// ReSharper disable once PossibleLossOfFraction (Justification: Intended.)
|
|
||||||
var hatYPosition = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
|
||||||
GenerateColorChip(__instance, new Vector2(hatXPosition, hatYPosition), hat);
|
|
||||||
hatIndex += 1;
|
|
||||||
yield return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
__instance.SetScrollerBounds();
|
|
||||||
yield return new WaitForSeconds(0.01f);
|
|
||||||
}
|
|
||||||
|
|
||||||
__instance.currentHatIsEquipped = true;
|
|
||||||
loadRoutine = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void GenerateColorChip(HatsTab __instance, Vector2 position, HatData hat)
|
|
||||||
{
|
|
||||||
var colorChip = Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner);
|
|
||||||
colorChip.gameObject.name = hat.ProductId;
|
|
||||||
if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard)
|
|
||||||
{
|
|
||||||
colorChip.Button.OnClick.AddListener((Action)__instance.ClickEquip);
|
|
||||||
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectHat(hat)));
|
|
||||||
colorChip.Button.OnMouseOut.AddListener((Action)(() => __instance.SelectHat(HatManager.Instance.GetHatById(DataManager.Player.Customization.Hat))));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
colorChip.Button.OnClick.AddListener((Action)(() => __instance.SelectHat(hat)));
|
|
||||||
}
|
|
||||||
|
|
||||||
colorChip.Inner.SetHat(hat, __instance.HasLocalPlayer() ? PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId : DataManager.Player.Customization.Color);
|
|
||||||
colorChip.Button.ClickMask = __instance.scroller.Hitbox;
|
|
||||||
colorChip.SelectionHighlight.gameObject.SetActive(false);
|
|
||||||
__instance.UpdateMaterials(colorChip.Inner.FrontLayer, hat);
|
|
||||||
colorChip.Inner.SetMaskType(PlayerMaterial.MaskType.SimpleUI);
|
|
||||||
colorChip.transform.localPosition = new Vector3(position.x, position.y, -1f);
|
|
||||||
colorChip.Inner.transform.localPosition = hat.ChipOffset + new Vector2(0f, -0.3f);
|
|
||||||
colorChip.Tag = hat;
|
|
||||||
__instance.ColorChips.Add(colorChip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
using System;
|
|
||||||
using MiraAPI.Utilities.Assets;
|
|
||||||
using TMPro;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.Events;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
|
|
||||||
namespace MiraAPI.Patches.Menu;
|
|
||||||
|
|
||||||
public static class InventoryUtility
|
|
||||||
{
|
|
||||||
public static void CreateNextBackButtons<T>(T tab, Action<T> previousPage, Action<T> nextPage)
|
|
||||||
where T : InventoryTab
|
|
||||||
{
|
|
||||||
var title = tab.transform.FindChild("Text");
|
|
||||||
title.localPosition = new Vector3(.9123f, -.23f, -55);
|
|
||||||
title.GetComponent<TextMeshPro>().alignment = TextAlignmentOptions.Center;
|
|
||||||
|
|
||||||
tab.scroller.Hitbox.transform.localScale -= new Vector3(0, 0.1f, 0);
|
|
||||||
tab.scroller.Hitbox.transform.localPosition -= new Vector3(0, 0.1f, 0);
|
|
||||||
|
|
||||||
var leftButtonElement = Object.Instantiate(PlayerCustomizationMenu.Instance.BackButton.gameObject, tab.transform);
|
|
||||||
var leftSpriteRenderer = leftButtonElement.GetComponent<SpriteRenderer>();
|
|
||||||
var leftButton = leftButtonElement.GetComponent<PassiveButton>();
|
|
||||||
|
|
||||||
leftButtonElement.transform.localPosition = new Vector3(-1.1f, -0.2f, -55);
|
|
||||||
leftSpriteRenderer.sprite = MiraAssets.NextButton.LoadAsset();
|
|
||||||
leftSpriteRenderer.flipX = true;
|
|
||||||
|
|
||||||
leftButton.OnMouseOver = new UnityEvent();
|
|
||||||
leftButton.OnMouseOver.AddListener((Action)(() => leftSpriteRenderer.sprite = MiraAssets.NextButtonActive.LoadAsset()));
|
|
||||||
leftButton.OnMouseOut = new UnityEvent();
|
|
||||||
leftButton.OnMouseOut.AddListener((Action)(() => leftSpriteRenderer.sprite = MiraAssets.NextButton.LoadAsset()));
|
|
||||||
|
|
||||||
leftButton.OnClick = new Button.ButtonClickedEvent();
|
|
||||||
leftButton.OnClick.AddListener((Action)(() => previousPage(tab)));
|
|
||||||
|
|
||||||
var rightButtonElement = Object.Instantiate(leftButtonElement, tab.transform);
|
|
||||||
var rightSpriteRenderer = rightButtonElement.GetComponent<SpriteRenderer>();
|
|
||||||
var rightButton = rightButtonElement.GetComponent<PassiveButton>();
|
|
||||||
|
|
||||||
rightButtonElement.transform.localPosition = new Vector3(2.8f, -0.2f, -55);
|
|
||||||
rightSpriteRenderer.sprite = MiraAssets.NextButton.LoadAsset();
|
|
||||||
rightSpriteRenderer.flipX = false;
|
|
||||||
|
|
||||||
rightButton.OnMouseOver = new UnityEvent();
|
|
||||||
rightButton.OnMouseOver.AddListener((Action)(() => rightSpriteRenderer.sprite = MiraAssets.NextButtonActive.LoadAsset()));
|
|
||||||
rightButton.OnMouseOut = new UnityEvent();
|
|
||||||
rightButton.OnMouseOut.AddListener((Action)(() => rightSpriteRenderer.sprite = MiraAssets.NextButton.LoadAsset()));
|
|
||||||
|
|
||||||
rightButton.OnClick = new Button.ButtonClickedEvent();
|
|
||||||
rightButton.OnClick.AddListener((Action)(() => nextPage(tab)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using MiraAPI.LocalSettings;
|
using MiraAPI.LocalSettings;
|
||||||
using MiraAPI.Utilities.Assets;
|
|
||||||
using Reactor.Utilities;
|
using Reactor.Utilities;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
@@ -23,10 +22,9 @@ public static class MainMenuManagerPatches
|
|||||||
[HarmonyPostfix]
|
[HarmonyPostfix]
|
||||||
public static void AwakePostfix()
|
public static void AwakePostfix()
|
||||||
{
|
{
|
||||||
var requiredVersion = new Version(2026, 6, 5);
|
var requiredVersion = new Version(2026, 9, 29);
|
||||||
var version = Version.Parse(Application.version);
|
var version = Version.Parse(Application.version);
|
||||||
NeedsDeepDestroy = version >= requiredVersion;
|
NeedsDeepDestroy = version >= requiredVersion;
|
||||||
AddressablesLoader.LoadAll();
|
|
||||||
Coroutines.Start(SetFps());
|
Coroutines.Start(SetFps());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,161 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using AmongUs.Data;
|
|
||||||
using HarmonyLib;
|
|
||||||
using Innersloth.Assets;
|
|
||||||
using MiraAPI.Utilities;
|
|
||||||
using MiraAPI.Utilities.Assets;
|
|
||||||
using Reactor.Utilities.Extensions;
|
|
||||||
using TMPro;
|
|
||||||
using UnityEngine;
|
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
|
|
||||||
namespace MiraAPI.Patches.Menu;
|
|
||||||
|
|
||||||
[HarmonyPatch(typeof(NameplatesTab))]
|
|
||||||
public static class NameplatesTabPatches
|
|
||||||
{
|
|
||||||
[SuppressMessage("Style", "IDE0028:Collection initialization can be simplified", Justification = "Preview feature.")]
|
|
||||||
private static readonly SortedList<string, List<NamePlateData>> SortedNameplates = new(new ControllableComparer<string>(["vanilla"], [], StringComparer.InvariantCulture));
|
|
||||||
private static int currentPage;
|
|
||||||
|
|
||||||
internal static void AddRange(IEnumerable<(string Key, NamePlateData Visor)> data)
|
|
||||||
{
|
|
||||||
foreach (var (key, visor) in data)
|
|
||||||
{
|
|
||||||
if (!SortedNameplates.ContainsKey(key)) SortedNameplates.Add(key, []);
|
|
||||||
SortedNameplates[key].Add(visor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void PreviousPage(NameplatesTab tab)
|
|
||||||
{
|
|
||||||
currentPage--;
|
|
||||||
currentPage = currentPage < 0 ? SortedNameplates.Count - 1 : currentPage;
|
|
||||||
GenerateHats(tab, currentPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void NextPage(NameplatesTab tab)
|
|
||||||
{
|
|
||||||
currentPage++;
|
|
||||||
currentPage = currentPage > SortedNameplates.Count - 1 ? 0 : currentPage;
|
|
||||||
GenerateHats(tab, currentPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HarmonyPatch(nameof(NameplatesTab.OnEnable))]
|
|
||||||
[HarmonyPrefix]
|
|
||||||
public static bool OnEnablePrefix(NameplatesTab __instance)
|
|
||||||
{
|
|
||||||
if (!AddressablesLoader.AddressableNameplatesExist)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
__instance.plateId = HatManager.Instance.GetNamePlateById(DataManager.Player.Customization.namePlate).ProdId;
|
|
||||||
|
|
||||||
if (!SortedNameplates.ContainsKey("Vanilla")) AddRange(HatManager.Instance.GetUnlockedNamePlates().Select(x => ("Vanilla", x)));
|
|
||||||
|
|
||||||
InventoryUtility.CreateNextBackButtons(__instance, PreviousPage, NextPage);
|
|
||||||
|
|
||||||
GenerateHats(__instance, currentPage);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HarmonyPatch(typeof(NameplatesTab), nameof(NameplatesTab.Update))]
|
|
||||||
[HarmonyPrefix]
|
|
||||||
|
|
||||||
public static void UpdatePrefix(NameplatesTab __instance)
|
|
||||||
{
|
|
||||||
if (!AddressablesLoader.AddressableNameplatesExist)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
|
|
||||||
{
|
|
||||||
PreviousPage(__instance);
|
|
||||||
}
|
|
||||||
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
|
|
||||||
{
|
|
||||||
NextPage(__instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void GenerateHats(NameplatesTab __instance, int page)
|
|
||||||
{
|
|
||||||
foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy();
|
|
||||||
__instance.ColorChips.Clear();
|
|
||||||
__instance.scroller.Inner.GetComponentsInChildren<TextMeshPro>().Do(x => x.gameObject.DeepDestroy(false));
|
|
||||||
|
|
||||||
var groupNameText = __instance.GetComponentInChildren<TextMeshPro>(false);
|
|
||||||
|
|
||||||
var hatIndex = 0;
|
|
||||||
|
|
||||||
var (groupName, nameplates) = SortedNameplates.ToArray()[page];
|
|
||||||
var text = Object.Instantiate(groupNameText, __instance.scroller.Inner);
|
|
||||||
text.enabled = true;
|
|
||||||
text.gameObject.transform.localScale = Vector3.one;
|
|
||||||
text.GetComponent<TextTranslatorTMP>().Destroy();
|
|
||||||
text.EnableStencilMasking();
|
|
||||||
text.text = $"{groupName} ({currentPage + 1}/{SortedNameplates.Count})";
|
|
||||||
text.alignment = TextAlignmentOptions.Center;
|
|
||||||
text.fontSize = 5f;
|
|
||||||
text.fontSizeMax = 5f;
|
|
||||||
text.fontSizeMin = 0f;
|
|
||||||
var xLerp = __instance.XRange.Lerp(0.5f);
|
|
||||||
var yLerp = __instance.YStart - __instance.YOffset;
|
|
||||||
text.transform.localPosition = new Vector3(xLerp, yLerp, -1f);
|
|
||||||
|
|
||||||
hatIndex += 2;
|
|
||||||
foreach (var visor in nameplates.OrderBy(HatManager.Instance.allNamePlates.IndexOf))
|
|
||||||
{
|
|
||||||
var hatXPosition = __instance.XRange.Lerp(hatIndex % __instance.NumPerRow / (__instance.NumPerRow - 1f));
|
|
||||||
|
|
||||||
// ReSharper disable once PossibleLossOfFraction (Justification: Intended.)
|
|
||||||
var hatYPosition = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
|
||||||
GenerateColorChip(__instance, new Vector2(hatXPosition, hatYPosition), visor);
|
|
||||||
hatIndex += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
__instance.SetScrollerBounds();
|
|
||||||
__instance.currentNameplateIsEquipped = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void GenerateColorChip(NameplatesTab __instance, Vector2 position, NamePlateData namePlate)
|
|
||||||
{
|
|
||||||
var colorChip = Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner);
|
|
||||||
colorChip.gameObject.name = namePlate.ProductId;
|
|
||||||
|
|
||||||
if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard)
|
|
||||||
{
|
|
||||||
colorChip.Button.OnClick.AddListener((Action)__instance.ClickEquip);
|
|
||||||
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectNameplate(namePlate)));
|
|
||||||
colorChip.Button.OnMouseOut.AddListener(
|
|
||||||
(Action)(() => __instance.SelectNameplate(
|
|
||||||
HatManager.Instance.GetNamePlateById(DataManager.Player.Customization.NamePlate))));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
colorChip.Button.OnClick.AddListener((Action)(() => __instance.SelectNameplate(namePlate)));
|
|
||||||
}
|
|
||||||
|
|
||||||
colorChip.Button.ClickMask = __instance.scroller.Hitbox;
|
|
||||||
colorChip.ProductId = namePlate.ProdId;
|
|
||||||
|
|
||||||
__instance.StartCoroutine(__instance.CoLoadAssetAsync<NamePlateViewData>(namePlate.GetAssetReference(), (Action<NamePlateViewData>?)LoadNameplate));
|
|
||||||
colorChip.transform.localPosition = new Vector3(position.x, position.y, -1f);
|
|
||||||
colorChip.Tag = namePlate;
|
|
||||||
__instance.ColorChips.Add(colorChip);
|
|
||||||
return;
|
|
||||||
|
|
||||||
void LoadNameplate(NamePlateViewData viewData)
|
|
||||||
{
|
|
||||||
colorChip.transform.GetChild(1).GetComponent<SpriteRenderer>().sprite = viewData.Image;
|
|
||||||
|
|
||||||
// (colorChip as NameplateChip).image.sprite = viewData?.Image;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using AmongUs.Data;
|
|
||||||
using HarmonyLib;
|
|
||||||
using MiraAPI.Utilities;
|
|
||||||
using MiraAPI.Utilities.Assets;
|
|
||||||
using Reactor.Utilities.Extensions;
|
|
||||||
using TMPro;
|
|
||||||
using UnityEngine;
|
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
|
|
||||||
namespace MiraAPI.Patches.Menu;
|
|
||||||
|
|
||||||
[HarmonyPatch(typeof(SkinsTab))]
|
|
||||||
public static class SkinsTabPatches
|
|
||||||
{
|
|
||||||
private static SortedList<string, List<SkinData>> sortedSkins = [];
|
|
||||||
private static int currentPage;
|
|
||||||
|
|
||||||
private static void PreviousPage(SkinsTab tab)
|
|
||||||
{
|
|
||||||
currentPage--;
|
|
||||||
currentPage = currentPage < 0 ? sortedSkins.Count - 1 : currentPage;
|
|
||||||
GenerateHats(tab, currentPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void NextPage(SkinsTab tab)
|
|
||||||
{
|
|
||||||
currentPage++;
|
|
||||||
currentPage = currentPage > sortedSkins.Count - 1 ? 0 : currentPage;
|
|
||||||
GenerateHats(tab, currentPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HarmonyPatch(nameof(SkinsTab.OnEnable))]
|
|
||||||
[HarmonyPrefix]
|
|
||||||
[SuppressMessage("Style", "IDE0028:Collection initialization can be simplified", Justification = "Preview feature.")]
|
|
||||||
public static bool OnEnablePrefix(SkinsTab __instance)
|
|
||||||
{
|
|
||||||
if (!AddressablesLoader.AddressableSkinsExist)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
__instance.skinId = HatManager.Instance.GetSkinById(DataManager.Player.Customization.Skin).ProdId;
|
|
||||||
var allSkins = HatManager.Instance.GetUnlockedSkins().ToImmutableList();
|
|
||||||
|
|
||||||
if (sortedSkins.Count == 0)
|
|
||||||
{
|
|
||||||
var comparer = new ControllableComparer<string>(["vanilla"], [], StringComparer.InvariantCulture);
|
|
||||||
sortedSkins = new SortedList<string, List<SkinData>>(comparer);
|
|
||||||
foreach (var skin in allSkins)
|
|
||||||
{
|
|
||||||
if (!sortedSkins.ContainsKey(skin.StoreName)) sortedSkins[skin.StoreName] = [];
|
|
||||||
sortedSkins[skin.StoreName].Add(skin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InventoryUtility.CreateNextBackButtons(__instance, PreviousPage, NextPage);
|
|
||||||
|
|
||||||
GenerateHats(__instance, currentPage);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HarmonyPatch(typeof(SkinsTab), nameof(SkinsTab.Update))]
|
|
||||||
[HarmonyPrefix]
|
|
||||||
|
|
||||||
public static void UpdatePrefix(SkinsTab __instance)
|
|
||||||
{
|
|
||||||
if (!AddressablesLoader.AddressableSkinsExist)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
|
|
||||||
{
|
|
||||||
PreviousPage(__instance);
|
|
||||||
}
|
|
||||||
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
|
|
||||||
{
|
|
||||||
NextPage(__instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void GenerateHats(SkinsTab __instance, int page)
|
|
||||||
{
|
|
||||||
foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy();
|
|
||||||
__instance.ColorChips.Clear();
|
|
||||||
__instance.scroller.Inner.GetComponentsInChildren<TextMeshPro>().Do(x => x.gameObject.DeepDestroy(false));
|
|
||||||
|
|
||||||
var groupNameText = __instance.GetComponentInChildren<TextMeshPro>(false);
|
|
||||||
|
|
||||||
var hatIndex = 0;
|
|
||||||
|
|
||||||
var (groupName, skins) = sortedSkins.ToArray()[page];
|
|
||||||
var text = Object.Instantiate(groupNameText, __instance.scroller.Inner);
|
|
||||||
text.enabled = true;
|
|
||||||
text.gameObject.transform.localScale = Vector3.one;
|
|
||||||
text.GetComponent<TextTranslatorTMP>().Destroy();
|
|
||||||
text.EnableStencilMasking();
|
|
||||||
text.text = $"{groupName} ({currentPage + 1}/{sortedSkins.Count})";
|
|
||||||
text.alignment = TextAlignmentOptions.Center;
|
|
||||||
text.fontSize = 5f;
|
|
||||||
text.fontSizeMax = 5f;
|
|
||||||
text.fontSizeMin = 0f;
|
|
||||||
var xLerp = __instance.XRange.Lerp(0.5f);
|
|
||||||
var yLerp = __instance.YStart - __instance.YOffset;
|
|
||||||
text.transform.localPosition = new Vector3(xLerp, yLerp, -1f);
|
|
||||||
|
|
||||||
hatIndex += 5;
|
|
||||||
foreach (var skin in skins.OrderBy(HatManager.Instance.allSkins.IndexOf))
|
|
||||||
{
|
|
||||||
var hatXPosition = __instance.XRange.Lerp(hatIndex % __instance.NumPerRow / (__instance.NumPerRow - 1f));
|
|
||||||
|
|
||||||
// ReSharper disable once PossibleLossOfFraction (Justification: Intended.)
|
|
||||||
var hatYPosition = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
|
||||||
GenerateColorChip(__instance, new Vector2(hatXPosition, hatYPosition), skin);
|
|
||||||
hatIndex += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
__instance.SetScrollerBounds();
|
|
||||||
__instance.currentSkinIsEquipped = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void GenerateColorChip(SkinsTab __instance, Vector2 position, SkinData skin)
|
|
||||||
{
|
|
||||||
var colorChip = Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner);
|
|
||||||
colorChip.gameObject.name = skin.ProductId;
|
|
||||||
if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard)
|
|
||||||
{
|
|
||||||
colorChip.Button.OnClick.AddListener((Action)__instance.ClickEquip);
|
|
||||||
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectSkin(skin)));
|
|
||||||
colorChip.Button.OnMouseOut.AddListener(
|
|
||||||
(Action)(() =>
|
|
||||||
__instance.SelectSkin(HatManager.Instance.GetSkinById(DataManager.Player.Customization.Skin))));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
colorChip.Button.OnClick.AddListener((Action)(() => __instance.SelectSkin(skin)));
|
|
||||||
}
|
|
||||||
|
|
||||||
colorChip.Button.ClickMask = __instance.scroller.Hitbox;
|
|
||||||
colorChip.ProductId = skin.ProductId;
|
|
||||||
colorChip.SelectionHighlight.gameObject.SetActive(false);
|
|
||||||
__instance.UpdateMaterials(colorChip.Inner.FrontLayer, skin);
|
|
||||||
skin.SetPreview(colorChip.Inner.FrontLayer, __instance.HasLocalPlayer() ? PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId : DataManager.Player.Customization.Color);
|
|
||||||
colorChip.transform.localPosition = new Vector3(position.x, position.y, -1f);
|
|
||||||
colorChip.Inner.transform.localPosition = skin.ChipOffset + new Vector2(0f, -0.3f);
|
|
||||||
colorChip.Tag = skin;
|
|
||||||
__instance.ColorChips.Add(colorChip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using AmongUs.Data;
|
|
||||||
using HarmonyLib;
|
|
||||||
using MiraAPI.Utilities;
|
|
||||||
using MiraAPI.Utilities.Assets;
|
|
||||||
using Reactor.Utilities.Extensions;
|
|
||||||
using TMPro;
|
|
||||||
using UnityEngine;
|
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
|
|
||||||
namespace MiraAPI.Patches.Menu;
|
|
||||||
|
|
||||||
[HarmonyPatch(typeof(VisorsTab))]
|
|
||||||
public static class VisorsTabPatches
|
|
||||||
{
|
|
||||||
[SuppressMessage("Style", "IDE0028:Collection initialization can be simplified", Justification = "Preview feature.")]
|
|
||||||
private static readonly SortedList<string, List<VisorData>> SortedVisors = new(new ControllableComparer<string>(["vanilla"], [], StringComparer.InvariantCulture));
|
|
||||||
private static int currentPage;
|
|
||||||
|
|
||||||
internal static void AddRange(IEnumerable<(string Key, VisorData Visor)> data)
|
|
||||||
{
|
|
||||||
foreach (var (key, visor) in data)
|
|
||||||
{
|
|
||||||
if (!SortedVisors.ContainsKey(key)) SortedVisors.Add(key, []);
|
|
||||||
SortedVisors[key].Add(visor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void PreviousPage(VisorsTab tab)
|
|
||||||
{
|
|
||||||
currentPage--;
|
|
||||||
currentPage = currentPage < 0 ? SortedVisors.Count - 1 : currentPage;
|
|
||||||
GenerateHats(tab, currentPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void NextPage(VisorsTab tab)
|
|
||||||
{
|
|
||||||
currentPage++;
|
|
||||||
currentPage = currentPage > SortedVisors.Count - 1 ? 0 : currentPage;
|
|
||||||
GenerateHats(tab, currentPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HarmonyPatch(nameof(VisorsTab.OnEnable))]
|
|
||||||
[HarmonyPrefix]
|
|
||||||
public static bool OnEnablePrefix(VisorsTab __instance)
|
|
||||||
{
|
|
||||||
if (!AddressablesLoader.AddressableVisorsExist)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
__instance.visorId = HatManager.Instance.GetVisorById(DataManager.Player.Customization.Visor).ProdId;
|
|
||||||
|
|
||||||
if (!SortedVisors.ContainsKey("Vanilla")) AddRange(HatManager.Instance.GetUnlockedVisors().Select(x => ("Vanilla", x)));
|
|
||||||
|
|
||||||
InventoryUtility.CreateNextBackButtons(__instance, PreviousPage, NextPage);
|
|
||||||
|
|
||||||
GenerateHats(__instance, currentPage);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HarmonyPatch(typeof(VisorsTab), nameof(VisorsTab.Update))]
|
|
||||||
[HarmonyPrefix]
|
|
||||||
|
|
||||||
public static void UpdatePrefix(VisorsTab __instance)
|
|
||||||
{
|
|
||||||
if (!AddressablesLoader.AddressableVisorsExist)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
|
|
||||||
{
|
|
||||||
PreviousPage(__instance);
|
|
||||||
}
|
|
||||||
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
|
|
||||||
{
|
|
||||||
NextPage(__instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void GenerateHats(VisorsTab __instance, int page)
|
|
||||||
{
|
|
||||||
foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy();
|
|
||||||
__instance.ColorChips.Clear();
|
|
||||||
__instance.scroller.Inner.GetComponentsInChildren<TextMeshPro>().Do(x => x.gameObject.DeepDestroy(false));
|
|
||||||
|
|
||||||
var groupNameText = __instance.GetComponentInChildren<TextMeshPro>(false);
|
|
||||||
|
|
||||||
var hatIndex = 0;
|
|
||||||
|
|
||||||
var (groupName, visors) = SortedVisors.ToArray()[page];
|
|
||||||
var text = Object.Instantiate(groupNameText, __instance.scroller.Inner);
|
|
||||||
text.enabled = true;
|
|
||||||
text.gameObject.transform.localScale = Vector3.one;
|
|
||||||
text.GetComponent<TextTranslatorTMP>().Destroy();
|
|
||||||
text.EnableStencilMasking();
|
|
||||||
text.text = $"{groupName} ({currentPage + 1}/{SortedVisors.Count})";
|
|
||||||
text.alignment = TextAlignmentOptions.Center;
|
|
||||||
text.fontSize = 5f;
|
|
||||||
text.fontSizeMax = 5f;
|
|
||||||
text.fontSizeMin = 0f;
|
|
||||||
var xLerp = __instance.XRange.Lerp(0.5f);
|
|
||||||
var yLerp = __instance.YStart - __instance.YOffset;
|
|
||||||
text.transform.localPosition = new Vector3(xLerp, yLerp, -1f);
|
|
||||||
|
|
||||||
hatIndex += 5;
|
|
||||||
foreach (var visor in visors.OrderBy(HatManager.Instance.allVisors.IndexOf))
|
|
||||||
{
|
|
||||||
var hatXPosition = __instance.XRange.Lerp(hatIndex % __instance.NumPerRow / (__instance.NumPerRow - 1f));
|
|
||||||
|
|
||||||
// ReSharper disable once PossibleLossOfFraction (Justification: Intended.)
|
|
||||||
var hatYPosition = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
|
||||||
GenerateColorChip(__instance, new Vector2(hatXPosition, hatYPosition), visor);
|
|
||||||
hatIndex += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
__instance.SetScrollerBounds();
|
|
||||||
__instance.currentVisorIsEquipped = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void GenerateColorChip(VisorsTab __instance, Vector2 position, VisorData visor)
|
|
||||||
{
|
|
||||||
var colorChip = Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner);
|
|
||||||
colorChip.gameObject.name = visor.ProductId;
|
|
||||||
|
|
||||||
if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard)
|
|
||||||
{
|
|
||||||
colorChip.Button.OnClick.AddListener((Action)__instance.ClickEquip);
|
|
||||||
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectVisor(visor)));
|
|
||||||
colorChip.Button.OnMouseOut.AddListener(
|
|
||||||
(Action)(() =>
|
|
||||||
__instance.SelectVisor(HatManager.Instance.GetVisorById(DataManager.Player.Customization.Visor))));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
colorChip.Button.OnClick.AddListener((Action)(() => __instance.SelectVisor(visor)));
|
|
||||||
}
|
|
||||||
|
|
||||||
colorChip.Button.ClickMask = __instance.scroller.Hitbox;
|
|
||||||
colorChip.ProductId = visor.ProductId;
|
|
||||||
colorChip.SelectionHighlight.gameObject.SetActive(false);
|
|
||||||
__instance.UpdateMaterials(colorChip.Inner.FrontLayer, visor);
|
|
||||||
visor.SetPreview(colorChip.Inner.FrontLayer, __instance.HasLocalPlayer() ? PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId : DataManager.Player.Customization.Color);
|
|
||||||
colorChip.transform.localPosition = new Vector3(position.x, position.y, -1f);
|
|
||||||
colorChip.Inner.transform.localPosition = visor.ChipOffset + new Vector2(0f, -0.3f);
|
|
||||||
colorChip.Tag = visor;
|
|
||||||
__instance.ColorChips.Add(colorChip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,9 +25,9 @@ namespace MiraAPI.Patches.Options;
|
|||||||
[HarmonyPatch(typeof(GameOptionsMenu))]
|
[HarmonyPatch(typeof(GameOptionsMenu))]
|
||||||
internal static class GameOptionsMenuPatch
|
internal static class GameOptionsMenuPatch
|
||||||
{
|
{
|
||||||
private static readonly List<CategoryHeaderMasked> VanillaHeaders = new();
|
private static readonly List<CategoryHeaderMasked> VanillaHeaders = [];
|
||||||
private static readonly List<OptionBehaviour> VanillaOptions = new();
|
private static readonly List<OptionBehaviour> VanillaOptions = [];
|
||||||
private static readonly List<OptionBehaviour> MainOptions = new();
|
private static readonly List<OptionBehaviour> MainOptions = [];
|
||||||
private static readonly Dictionary<AbstractGameMode, List<AbstractOptionGroup>> GameModeGroups = [];
|
private static readonly Dictionary<AbstractGameMode, List<AbstractOptionGroup>> GameModeGroups = [];
|
||||||
private static readonly Dictionary<AbstractGameMode, List<OptionBehaviour>> GameModeOptions = [];
|
private static readonly Dictionary<AbstractGameMode, List<OptionBehaviour>> GameModeOptions = [];
|
||||||
private static readonly Dictionary<AbstractGameMode, List<CategoryHeaderMasked>> GameModeHeaders = [];
|
private static readonly Dictionary<AbstractGameMode, List<CategoryHeaderMasked>> GameModeHeaders = [];
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ public static class OptionsPatches
|
|||||||
__instance.roleChance = 0;
|
__instance.roleChance = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__instance.roleChance == 0)
|
||||||
|
{
|
||||||
|
__instance.roleMaxCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
__instance.OnValueChanged.Invoke(__instance);
|
__instance.OnValueChanged.Invoke(__instance);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -58,6 +63,11 @@ public static class OptionsPatches
|
|||||||
__instance.roleChance = 100;
|
__instance.roleChance = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__instance.roleChance == 0)
|
||||||
|
{
|
||||||
|
__instance.roleMaxCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
__instance.OnValueChanged.Invoke(__instance);
|
__instance.OnValueChanged.Invoke(__instance);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -82,6 +92,11 @@ public static class OptionsPatches
|
|||||||
__instance.roleMaxCount = 0;
|
__instance.roleMaxCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__instance.roleMaxCount == 0)
|
||||||
|
{
|
||||||
|
__instance.roleChance = 0;
|
||||||
|
}
|
||||||
|
|
||||||
__instance.OnValueChanged.Invoke(__instance);
|
__instance.OnValueChanged.Invoke(__instance);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -106,6 +121,11 @@ public static class OptionsPatches
|
|||||||
__instance.roleMaxCount = __instance.role.MaxCount;
|
__instance.roleMaxCount = __instance.role.MaxCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__instance.roleMaxCount == 0)
|
||||||
|
{
|
||||||
|
__instance.roleChance = 0;
|
||||||
|
}
|
||||||
|
|
||||||
__instance.OnValueChanged.Invoke(__instance);
|
__instance.OnValueChanged.Invoke(__instance);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Globalization;
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using MiraAPI.GameOptions;
|
using MiraAPI.GameOptions;
|
||||||
@@ -24,7 +23,6 @@ public static class SettingPatches
|
|||||||
/// <returns><see langword="false"/> to skip original method.</returns>
|
/// <returns><see langword="false"/> to skip original method.</returns>
|
||||||
[HarmonyPrefix]
|
[HarmonyPrefix]
|
||||||
[HarmonyPatch(typeof(FloatGameSetting), nameof(FloatGameSetting.GetValueString))]
|
[HarmonyPatch(typeof(FloatGameSetting), nameof(FloatGameSetting.GetValueString))]
|
||||||
[SuppressMessage("Style", "IDE0045:Convert to conditional expression", Justification = "Warning cascades into forcing the entire tree to be ternary operators.")]
|
|
||||||
public static bool ValueStringPatch(
|
public static bool ValueStringPatch(
|
||||||
FloatGameSetting __instance,
|
FloatGameSetting __instance,
|
||||||
ref string __result,
|
ref string __result,
|
||||||
|
|||||||
@@ -37,11 +37,10 @@ public static class RoleGuidePatches
|
|||||||
private static readonly List<DetailedPanel> RoleEntries = [];
|
private static readonly List<DetailedPanel> RoleEntries = [];
|
||||||
private static readonly List<DetailedPanel> ModifierEntries = [];
|
private static readonly List<DetailedPanel> ModifierEntries = [];
|
||||||
|
|
||||||
public static GameObject SearchComponentContainer;
|
private static GameObject searchComponentContainer;
|
||||||
public static PassiveButton SearchIconButton;
|
public static PassiveButton SearchIconButton;
|
||||||
private static SpriteRenderer _searchIconIdle;
|
private static SpriteRenderer _searchIconIdle;
|
||||||
private static SpriteRenderer _searchIconHover;
|
private static SpriteRenderer _searchIconHover;
|
||||||
public static TextMeshPro OrderSortingTmp;
|
|
||||||
public static PassiveButton SearchSortingOrderButton;
|
public static PassiveButton SearchSortingOrderButton;
|
||||||
private static SpriteRenderer _searchSortingOrderIdle;
|
private static SpriteRenderer _searchSortingOrderIdle;
|
||||||
private static SpriteRenderer _searchSortingOrderHover;
|
private static SpriteRenderer _searchSortingOrderHover;
|
||||||
@@ -286,6 +285,7 @@ public static class RoleGuidePatches
|
|||||||
_titleText.transform.GetComponent<TextTranslatorTMP>().Destroy();
|
_titleText.transform.GetComponent<TextTranslatorTMP>().Destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_titleIcon)
|
if (!_titleIcon)
|
||||||
{
|
{
|
||||||
var titleIconObj = new GameObject("TitleIcon");
|
var titleIconObj = new GameObject("TitleIcon");
|
||||||
@@ -397,14 +397,14 @@ public static class RoleGuidePatches
|
|||||||
|
|
||||||
_guide = instance;
|
_guide = instance;
|
||||||
_loading = false;
|
_loading = false;
|
||||||
SearchComponentContainer = new GameObject();
|
searchComponentContainer = new GameObject();
|
||||||
SearchComponentContainer.transform.SetParent(instance.MatchInfoParent.transform);
|
searchComponentContainer.transform.SetParent(instance.MatchInfoParent.transform);
|
||||||
SearchComponentContainer.transform.localPosition = Vector3.zero;
|
searchComponentContainer.transform.localPosition = Vector3.zero;
|
||||||
instance.MatchInfoParent.transform.GetChild(1).transform.SetParent(SearchComponentContainer.transform);
|
instance.MatchInfoParent.transform.GetChild(1).transform.SetParent(searchComponentContainer.transform);
|
||||||
|
|
||||||
var sortingOrderButton = Object.Instantiate(
|
var sortingOrderButton = Object.Instantiate(
|
||||||
HudManager.Instance.SettingsButton,
|
HudManager.Instance.SettingsButton,
|
||||||
SearchComponentContainer.transform);
|
searchComponentContainer.transform);
|
||||||
sortingOrderButton.gameObject.SetActive(true);
|
sortingOrderButton.gameObject.SetActive(true);
|
||||||
SearchSortingOrderButton = sortingOrderButton.GetComponent<PassiveButton>();
|
SearchSortingOrderButton = sortingOrderButton.GetComponent<PassiveButton>();
|
||||||
SetupSearchButton(
|
SetupSearchButton(
|
||||||
@@ -419,7 +419,7 @@ public static class RoleGuidePatches
|
|||||||
|
|
||||||
var sortingGroupButton = Object.Instantiate(
|
var sortingGroupButton = Object.Instantiate(
|
||||||
sortingOrderButton,
|
sortingOrderButton,
|
||||||
SearchComponentContainer.transform);
|
searchComponentContainer.transform);
|
||||||
sortingGroupButton.gameObject.SetActive(true);
|
sortingGroupButton.gameObject.SetActive(true);
|
||||||
SearchSortingGroupButton = sortingGroupButton.GetComponent<PassiveButton>();
|
SearchSortingGroupButton = sortingGroupButton.GetComponent<PassiveButton>();
|
||||||
SetupSearchButton(
|
SetupSearchButton(
|
||||||
@@ -434,7 +434,7 @@ public static class RoleGuidePatches
|
|||||||
|
|
||||||
var sortingFilterButton = Object.Instantiate(
|
var sortingFilterButton = Object.Instantiate(
|
||||||
sortingOrderButton,
|
sortingOrderButton,
|
||||||
SearchComponentContainer.transform);
|
searchComponentContainer.transform);
|
||||||
sortingFilterButton.gameObject.SetActive(true);
|
sortingFilterButton.gameObject.SetActive(true);
|
||||||
SearchSortingFilterButton = sortingFilterButton.GetComponent<PassiveButton>();
|
SearchSortingFilterButton = sortingFilterButton.GetComponent<PassiveButton>();
|
||||||
SetupSearchButton(
|
SetupSearchButton(
|
||||||
@@ -512,7 +512,7 @@ public static class RoleGuidePatches
|
|||||||
|
|
||||||
var searchBox = Object.Instantiate(
|
var searchBox = Object.Instantiate(
|
||||||
instance.TabButtons[2].gameObject,
|
instance.TabButtons[2].gameObject,
|
||||||
SearchComponentContainer.transform);
|
searchComponentContainer.transform);
|
||||||
var searchButton = searchBox.GetComponent<MatchInfoGuideTabButton>();
|
var searchButton = searchBox.GetComponent<MatchInfoGuideTabButton>();
|
||||||
var tmpText = searchButton.transform.GetChild(0).GetComponent<TextMeshPro>();
|
var tmpText = searchButton.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||||
tmpText.GetComponent<TextTranslatorTMP>().Destroy();
|
tmpText.GetComponent<TextTranslatorTMP>().Destroy();
|
||||||
@@ -657,23 +657,23 @@ public static class RoleGuidePatches
|
|||||||
modifiersButton.OnClick.AddListener((Action)(() => OpenModifiersTab()));
|
modifiersButton.OnClick.AddListener((Action)(() => OpenModifiersTab()));
|
||||||
instance.TabButtons.Add(modifiersButton);
|
instance.TabButtons.Add(modifiersButton);
|
||||||
|
|
||||||
OrderSortingTmp = Object.Instantiate(
|
var orderSortingTmp = Object.Instantiate(
|
||||||
_titleText,
|
_titleText,
|
||||||
sortingOrderButton.transform);
|
sortingOrderButton.transform);
|
||||||
OrderSortingTmp.text = MiraLocaleManager.Get("MiraApi.Wiki.OrderLabel");
|
orderSortingTmp.text = MiraLocaleManager.Get("MiraApi.Wiki.OrderLabel");
|
||||||
OrderSortingTmp.fontSize = OrderSortingTmp.fontSizeMax = 3f;
|
orderSortingTmp.fontSize = orderSortingTmp.fontSizeMax = 3f;
|
||||||
OrderSortingTmp.alignment = TextAlignmentOptions.Center;
|
orderSortingTmp.alignment = TextAlignmentOptions.Center;
|
||||||
OrderSortingTmp.horizontalAlignment = HorizontalAlignmentOptions.Center;
|
orderSortingTmp.horizontalAlignment = HorizontalAlignmentOptions.Center;
|
||||||
OrderSortingTmp.transform.localPosition = new Vector3(-0.65f, 0.4f, 0);
|
orderSortingTmp.transform.localPosition = new Vector3(-0.65f, 0.4f, 0);
|
||||||
|
|
||||||
GroupSortingTmp = Object.Instantiate(
|
GroupSortingTmp = Object.Instantiate(
|
||||||
OrderSortingTmp,
|
orderSortingTmp,
|
||||||
sortingGroupButton.transform);
|
sortingGroupButton.transform);
|
||||||
OrderSortingTmp.text = MiraLocaleManager.Get("MiraApi.Wiki.GroupingLabel");
|
GroupSortingTmp.text = MiraLocaleManager.Get("MiraApi.Wiki.GroupingLabel");
|
||||||
GroupSortingTmp.transform.localPosition = new Vector3(-0.65f, 0.4f, 0);
|
GroupSortingTmp.transform.localPosition = new Vector3(-0.65f, 0.4f, 0);
|
||||||
|
|
||||||
FilterSortingTmp = Object.Instantiate(
|
FilterSortingTmp = Object.Instantiate(
|
||||||
OrderSortingTmp,
|
orderSortingTmp,
|
||||||
sortingFilterButton.transform);
|
sortingFilterButton.transform);
|
||||||
FilterSortingTmp.text = MiraLocaleManager.Get("MiraApi.Wiki.FilterLabel");
|
FilterSortingTmp.text = MiraLocaleManager.Get("MiraApi.Wiki.FilterLabel");
|
||||||
FilterSortingTmp.transform.localPosition = new Vector3(-0.65f, 0.4f, 0);
|
FilterSortingTmp.transform.localPosition = new Vector3(-0.65f, 0.4f, 0);
|
||||||
@@ -767,9 +767,15 @@ public static class RoleGuidePatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OpenModifiersTab() => OpenTab(ModifiersTabIndex);
|
public static void OpenModifiersTab()
|
||||||
|
{
|
||||||
|
OpenTab(ModifiersTabIndex);
|
||||||
|
}
|
||||||
|
|
||||||
public static void OpenRolesTab() => OpenTab(RolesTabIndex);
|
public static void OpenRolesTab()
|
||||||
|
{
|
||||||
|
OpenTab(RolesTabIndex);
|
||||||
|
}
|
||||||
|
|
||||||
private static void SetNoResultsText(GameObject message)
|
private static void SetNoResultsText(GameObject message)
|
||||||
{
|
{
|
||||||
@@ -791,7 +797,7 @@ public static class RoleGuidePatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DetailedPanel(string title, string category, string modId, RoleBehaviour? role, GameModifier? modifier)
|
private sealed class DetailedPanel(string title, string category, string modId, RoleBehaviour? role, GameModifier? modifier)
|
||||||
{
|
{
|
||||||
public MatchInfoRolePanel? Panel { get; set; }
|
public MatchInfoRolePanel? Panel { get; set; }
|
||||||
public string Title { get; } = title;
|
public string Title { get; } = title;
|
||||||
@@ -861,14 +867,7 @@ public static class RoleGuidePatches
|
|||||||
|
|
||||||
private static IEnumerator CoPopulate(float delay)
|
private static IEnumerator CoPopulate(float delay)
|
||||||
{
|
{
|
||||||
if (delay > 0f)
|
yield return delay > 0f ? new WaitForSeconds(delay) : null;
|
||||||
{
|
|
||||||
yield return new WaitForSeconds(delay);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
yield return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_guide)
|
if (!_guide)
|
||||||
{
|
{
|
||||||
@@ -1208,14 +1207,8 @@ public static class RoleGuidePatches
|
|||||||
}
|
}
|
||||||
|
|
||||||
var stepUp = (SortingFilter)((int)_sortFilter + 1);
|
var stepUp = (SortingFilter)((int)_sortFilter + 1);
|
||||||
if (Enum.IsDefined(stepUp))
|
|
||||||
{
|
_sortFilter = Enum.IsDefined(stepUp) ? stepUp : SortingFilter.EnabledOnly;
|
||||||
_sortFilter = stepUp;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_sortFilter = SortingFilter.EnabledOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
MiraApiPlugin.WikiSortingFilter.Value = _sortFilter;
|
MiraApiPlugin.WikiSortingFilter.Value = _sortFilter;
|
||||||
|
|
||||||
@@ -1259,14 +1252,8 @@ public static class RoleGuidePatches
|
|||||||
}
|
}
|
||||||
|
|
||||||
var stepUp = (SortingMethod)((int)_sortMethod + 1);
|
var stepUp = (SortingMethod)((int)_sortMethod + 1);
|
||||||
if (Enum.IsDefined(stepUp))
|
|
||||||
{
|
_sortMethod = Enum.IsDefined(stepUp) ? stepUp : SortingMethod.Alphabetical;
|
||||||
_sortMethod = stepUp;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_sortMethod = SortingMethod.Alphabetical;
|
|
||||||
}
|
|
||||||
|
|
||||||
MiraApiPlugin.WikiSortingMethod.Value = _sortMethod;
|
MiraApiPlugin.WikiSortingMethod.Value = _sortMethod;
|
||||||
|
|
||||||
@@ -1459,7 +1446,7 @@ public static class RoleGuidePatches
|
|||||||
}
|
}
|
||||||
|
|
||||||
_wikiScroller?.gameObject.SetActive(false);
|
_wikiScroller?.gameObject.SetActive(false);
|
||||||
SearchComponentContainer?.SetActive(true);
|
searchComponentContainer?.SetActive(true);
|
||||||
if (tabIndex == SettingsTabIndex)
|
if (tabIndex == SettingsTabIndex)
|
||||||
{
|
{
|
||||||
EnterSettingsTab();
|
EnterSettingsTab();
|
||||||
@@ -1522,9 +1509,9 @@ public static class RoleGuidePatches
|
|||||||
_currentWikiPage.Destroy();
|
_currentWikiPage.Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SearchComponentContainer)
|
if (searchComponentContainer)
|
||||||
{
|
{
|
||||||
SearchComponentContainer.SetActive(false);
|
searchComponentContainer.SetActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_wikiScroller == null)
|
if (_wikiScroller == null)
|
||||||
@@ -2004,20 +1991,20 @@ public static class RoleGuidePatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SortingGroups
|
internal enum SortingGroups
|
||||||
{
|
{
|
||||||
Ungrouped,
|
Ungrouped,
|
||||||
Faction,
|
Faction,
|
||||||
AmountChance,
|
AmountChance,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SortingMethod
|
internal enum SortingMethod
|
||||||
{
|
{
|
||||||
Alphabetical,
|
Alphabetical,
|
||||||
AlphabeticalDescending,
|
AlphabeticalDescending,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SortingFilter
|
internal enum SortingFilter
|
||||||
{
|
{
|
||||||
EnabledOnly,
|
EnabledOnly,
|
||||||
DisabledOnly,
|
DisabledOnly,
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ public static class RoleBehaviourStubs
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stub method for <see cref="RoleBehaviour.AppendTaskHint(StringBuilder)"/>.
|
/// Stub method for <see cref="RoleBehaviour.AppendTaskHint(Il2CppSystem.Text.StringBuilder)"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="instance">The <see cref="RoleBehaviour"/> object.</param>
|
/// <param name="instance">The <see cref="RoleBehaviour"/> object.</param>
|
||||||
/// <param name="taskStringBuilder">The <see cref="StringBuilder"/> to append the task hint to.</param>
|
/// <param name="taskStringBuilder">The <see cref="Il2CppSystem.Text.StringBuilder"/> to append the task hint to.</param>
|
||||||
[HarmonyReversePatch]
|
[HarmonyReversePatch]
|
||||||
[HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.AppendTaskHint), typeof(Il2CppSystem.Text.StringBuilder))]
|
[HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.AppendTaskHint), typeof(Il2CppSystem.Text.StringBuilder))]
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ 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.")]
|
||||||
|
[SuppressMessage("Design", "CA1050:Declare types in namespaces", Justification = "Unity script.")]
|
||||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member (Justification: Unity fields; ignore.)
|
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member (Justification: Unity fields; ignore.)
|
||||||
public class SavePresetPopup(nint cppPtr) : Minigame(cppPtr)
|
public class SavePresetPopup(nint cppPtr) : Minigame(cppPtr)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -345,7 +345,14 @@ public interface ICustomRole : IOptionable
|
|||||||
/// Gets the list of abilities or other similar information to display below the text of a role's wiki page.
|
/// Gets the list of abilities or other similar information to display below the text of a role's wiki page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HideFromIl2Cpp]
|
[HideFromIl2Cpp]
|
||||||
public List<AdvancedWikiAbilityDescription> WikiAbilities => [];
|
List<AdvancedWikiAbilityDescription> WikiAbilities => [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A data holder of an ability's wiki entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Name">The name of the ability.</param>
|
||||||
|
/// <param name="AbilityType">The type of the ability.</param>
|
||||||
|
/// <param name="Description">The description of the ability.</param>
|
||||||
|
/// <param name="Icon">The icon sprite of the ability.</param>
|
||||||
public record struct AdvancedWikiAbilityDescription(string Name, string AbilityType, string Description, LoadableAsset<Sprite> Icon);
|
public record struct AdvancedWikiAbilityDescription(string Name, string AbilityType, string Description, LoadableAsset<Sprite> Icon);
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@@ -162,7 +161,6 @@ public static class MiraLocaleManager
|
|||||||
/// <param name="key">The string id to find.</param>
|
/// <param name="key">The string id to find.</param>
|
||||||
/// <param name="fallback">Fallback string to use if no translation is found.</param>
|
/// <param name="fallback">Fallback string to use if no translation is found.</param>
|
||||||
/// <returns>A <see cref="string"/> based on the key provided.</returns>
|
/// <returns>A <see cref="string"/> based on the key provided.</returns>
|
||||||
[SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Warning cascades into forcing the entire tree to be ternary operators.")]
|
|
||||||
public static string Get(MiraLanguage language, string key, string fallback = "")
|
public static string Get(MiraLanguage language, string key, string fallback = "")
|
||||||
{
|
{
|
||||||
if (Locale.TryGetValue(language, out var translations) &&
|
if (Locale.TryGetValue(language, out var translations) &&
|
||||||
|
|||||||
@@ -1,48 +1,39 @@
|
|||||||
using System.Collections;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
|
||||||
using Il2CppInterop.Runtime.Attributes;
|
|
||||||
using MiraAPI.Patches.Menu;
|
|
||||||
using Reactor.Utilities;
|
|
||||||
using UnityEngine.AddressableAssets;
|
|
||||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
|
||||||
|
|
||||||
namespace MiraAPI.Utilities.Assets;
|
namespace MiraAPI.Utilities.Assets;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The component for loading Addressables, and cosmetics.
|
/// The component for loading Addressables, and cosmetics.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("This system has been deprecated in favour of CorsacCosmetics. This will be removed in a future version.", true)]
|
||||||
|
[SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Part of obsolete code.")]
|
||||||
public static class AddressablesLoader
|
public static class AddressablesLoader
|
||||||
{
|
{
|
||||||
private static readonly List<(string Location, string ProviderSuffix)> CatalogLocations = [];
|
|
||||||
private static readonly List<string> LoadedLocations = [];
|
|
||||||
|
|
||||||
private static readonly List<string> RegisteredHatKeys = [];
|
|
||||||
private static readonly List<(string, string)> RegisteredVisorKeys = [];
|
|
||||||
private static readonly List<(string, string)> RegisteredNameplateKeys = [];
|
|
||||||
private static readonly List<string> RegisteredSkinKeys = [];
|
|
||||||
|
|
||||||
private static bool _isInitialized;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether hats have been loaded by the addressables system.
|
/// Gets a value indicating whether hats have been loaded by the addressables system.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool AddressableHatsExist { get; private set; }
|
public static bool AddressableHatsExist => false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether visors have been loaded by the addressables system.
|
/// Gets a value indicating whether visors have been loaded by the addressables system.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool AddressableVisorsExist { get; private set; }
|
public static bool AddressableVisorsExist => false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether nameplates have been loaded by the addressables system.
|
/// Gets a value indicating whether nameplates have been loaded by the addressables system.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool AddressableNameplatesExist { get; private set; }
|
public static bool AddressableNameplatesExist => false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether skins have been loaded by the addressables system.
|
/// Gets a value indicating whether skins have been loaded by the addressables system.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool AddressableSkinsExist { get; private set; }
|
public static bool AddressableSkinsExist => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether catalogs have been loaded by the addressables system.
|
||||||
|
/// </summary>
|
||||||
|
public static bool CatalogsExist => false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a specific addressables package to load asynchronously at the start of the game, when possible.
|
/// Registers a specific addressables package to load asynchronously at the start of the game, when possible.
|
||||||
@@ -51,13 +42,7 @@ public static class AddressablesLoader
|
|||||||
/// <param name="providerSuffix">The suffix of the provider for an addressables package.</param>
|
/// <param name="providerSuffix">The suffix of the provider for an addressables package.</param>
|
||||||
public static void RegisterCatalog(string location, string providerSuffix = "")
|
public static void RegisterCatalog(string location, string providerSuffix = "")
|
||||||
{
|
{
|
||||||
if (_isInitialized)
|
Warning("The addressables system has been deprecated. None of the registered assets will be added.");
|
||||||
{
|
|
||||||
Error("AddressablesLoader has already been initialized, cannot register more catalogs.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CatalogLocations.Add((location, providerSuffix));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -66,14 +51,7 @@ public static class AddressablesLoader
|
|||||||
/// <param name="addressablesKey">The key/label/group for a List <see cref="HatData"/>.</param>
|
/// <param name="addressablesKey">The key/label/group for a List <see cref="HatData"/>.</param>
|
||||||
public static void RegisterHats(string addressablesKey)
|
public static void RegisterHats(string addressablesKey)
|
||||||
{
|
{
|
||||||
if (_isInitialized)
|
Warning("The addressables system has been deprecated. None of the registered assets will be added.");
|
||||||
{
|
|
||||||
Error("AddressablesLoader has already been initialized, cannot register more keys for hats.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddressableHatsExist = true;
|
|
||||||
RegisteredHatKeys.Add(addressablesKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -82,14 +60,7 @@ public static class AddressablesLoader
|
|||||||
/// <param name="addressablesKey">The key/label/group for a List <see cref="SkinData"/>.</param>
|
/// <param name="addressablesKey">The key/label/group for a List <see cref="SkinData"/>.</param>
|
||||||
public static void RegisterSkins(string addressablesKey)
|
public static void RegisterSkins(string addressablesKey)
|
||||||
{
|
{
|
||||||
if (_isInitialized)
|
Warning("The addressables system has been deprecated. None of the registered assets will be added.");
|
||||||
{
|
|
||||||
Error("AddressablesLoader has already been initialized, cannot register more keys for skins.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddressableSkinsExist = true;
|
|
||||||
RegisteredSkinKeys.Add(addressablesKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -99,14 +70,7 @@ public static class AddressablesLoader
|
|||||||
/// /// <param name="groupTitle">The title of the group for visors.</param>
|
/// /// <param name="groupTitle">The title of the group for visors.</param>
|
||||||
public static void RegisterNameplates(string addressablesKey, string groupTitle)
|
public static void RegisterNameplates(string addressablesKey, string groupTitle)
|
||||||
{
|
{
|
||||||
if (_isInitialized)
|
Warning("The addressables system has been deprecated. None of the registered assets will be added.");
|
||||||
{
|
|
||||||
Error("AddressablesLoader has already been initialized, cannot register more keys for nameplates.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddressableNameplatesExist = true;
|
|
||||||
RegisteredNameplateKeys.Add((addressablesKey, groupTitle));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -116,140 +80,6 @@ public static class AddressablesLoader
|
|||||||
/// <param name="groupTitle">The title of the group for visors.</param>
|
/// <param name="groupTitle">The title of the group for visors.</param>
|
||||||
public static void RegisterVisors(string addressablesKey, string groupTitle)
|
public static void RegisterVisors(string addressablesKey, string groupTitle)
|
||||||
{
|
{
|
||||||
if (_isInitialized)
|
Warning("The addressables system has been deprecated. None of the registered assets will be added.");
|
||||||
{
|
|
||||||
Error("AddressablesLoader has already been initialized, cannot register more keys for visors.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddressableVisorsExist = true;
|
|
||||||
RegisteredVisorKeys.Add((addressablesKey, groupTitle));
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void LoadAll()
|
|
||||||
{
|
|
||||||
if (_isInitialized)
|
|
||||||
{
|
|
||||||
Error("AddressablesLoader has already been initialized.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isInitialized = true;
|
|
||||||
foreach (var (location, providerSuffix) in CatalogLocations)
|
|
||||||
{
|
|
||||||
Coroutines.Start(CoLoadAddressables(location, providerSuffix));
|
|
||||||
}
|
|
||||||
|
|
||||||
Coroutines.Start(LoadCosmetics());
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static IEnumerator CoLoadAddressables(string location, string suffix = "")
|
|
||||||
{
|
|
||||||
while (!AmongUsClient.Instance) yield return null;
|
|
||||||
|
|
||||||
// Load the local/remote content catalog
|
|
||||||
var catalogOperation = Addressables.LoadContentCatalog(location, suffix);
|
|
||||||
yield return catalogOperation;
|
|
||||||
|
|
||||||
// Check for errors
|
|
||||||
if (catalogOperation.Status != AsyncOperationStatus.Succeeded)
|
|
||||||
{
|
|
||||||
Error($"Failed to load catalog {location}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Info($"Loaded addressables {location}.");
|
|
||||||
LoadedLocations.Add(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HideFromIl2Cpp]
|
|
||||||
internal static IEnumerator LoadCosmetics()
|
|
||||||
{
|
|
||||||
while (!AmongUsClient.Instance || CatalogLocations.Select(x => x.Location).Any(x => !LoadedLocations.Contains(x))) yield return null;
|
|
||||||
|
|
||||||
var hatBehaviours = DiscoverData<HatData>(RegisteredHatKeys);
|
|
||||||
hatBehaviours = [.. hatBehaviours.OrderBy(x => x.StoreName)];
|
|
||||||
var skinBehaviours = DiscoverData<SkinData>(RegisteredSkinKeys);
|
|
||||||
skinBehaviours = [.. skinBehaviours.OrderBy(x => x.StoreName)];
|
|
||||||
var namePlateBehaviours = DiscoverAndReportData<NamePlateData>(RegisteredNameplateKeys);
|
|
||||||
namePlateBehaviours = [.. namePlateBehaviours.OrderBy(x => x.Category)];
|
|
||||||
var visorBehaviours = DiscoverAndReportData<VisorData>(RegisteredVisorKeys);
|
|
||||||
visorBehaviours = [.. visorBehaviours.OrderBy(x => x.Category)];
|
|
||||||
|
|
||||||
var hatData = new List<HatData>();
|
|
||||||
hatData.AddRange(HatManager.Instance.allHats);
|
|
||||||
hatData.ForEach(x => x.StoreName = "Vanilla");
|
|
||||||
HatManager.Instance.allHats = PrepareArray(hatData, hatBehaviours);
|
|
||||||
|
|
||||||
var skinData = new List<SkinData>();
|
|
||||||
skinData.AddRange(HatManager.Instance.allSkins);
|
|
||||||
skinData.ForEach(x => x.StoreName = "Vanilla");
|
|
||||||
HatManager.Instance.allSkins = PrepareArray(skinData, skinBehaviours);
|
|
||||||
|
|
||||||
var visorData = new List<VisorData>();
|
|
||||||
visorData.AddRange(HatManager.Instance.allVisors);
|
|
||||||
VisorsTabPatches.AddRange(visorBehaviours);
|
|
||||||
HatManager.Instance.allVisors = PrepareArray(visorData, [.. visorBehaviours.Select(x => x.Data)]);
|
|
||||||
|
|
||||||
var namePlateData = new List<NamePlateData>();
|
|
||||||
namePlateData.AddRange(HatManager.Instance.allNamePlates);
|
|
||||||
NameplatesTabPatches.AddRange(namePlateBehaviours);
|
|
||||||
HatManager.Instance.allNamePlates = PrepareArray(namePlateData, [.. namePlateBehaviours.Select(x => x.Data)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static T[] PrepareArray<T>(List<T> data, List<T> behaviours)
|
|
||||||
where T : CosmeticData
|
|
||||||
{
|
|
||||||
var count = data.Count;
|
|
||||||
for (var i = 0; i < behaviours.Count; i++)
|
|
||||||
{
|
|
||||||
behaviours[i].displayOrder = count + i;
|
|
||||||
data.Add(behaviours[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [.. data];
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<T> DiscoverData<T>(List<string> tags)
|
|
||||||
{
|
|
||||||
var behaviours = new List<T>();
|
|
||||||
|
|
||||||
foreach (var tag in tags)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var allLocations = Addressables.LoadResourceLocationsAsync(tag).WaitForCompletion();
|
|
||||||
var assets = Addressables.LoadAssetsAsync<T>(allLocations, null, false).WaitForCompletion();
|
|
||||||
var array = new CppCollections.List<T>(assets.Pointer);
|
|
||||||
behaviours.AddRange(array.ToArray());
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Error($"Failed to find tag {tag}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return behaviours;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<(string Category, T Data)> DiscoverAndReportData<T>(List<(string Tag, string Category)> tags)
|
|
||||||
{
|
|
||||||
var behaviours = new List<(string, T)>();
|
|
||||||
|
|
||||||
foreach (var tag in tags)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var allLocations = Addressables.LoadResourceLocationsAsync(tag.Tag).WaitForCompletion();
|
|
||||||
var assets = Addressables.LoadAssetsAsync<T>(allLocations, null, false).WaitForCompletion();
|
|
||||||
var array = new CppCollections.List<T>(assets.Pointer);
|
|
||||||
behaviours.AddRange(array.ToArray().Select(x => (tag.Category, x)));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Error($"Failed to find tag {tag}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return behaviours;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Resources;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace MiraAPI.Utilities.Assets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A utility class for various audio-related operations.
|
||||||
|
/// </summary>
|
||||||
|
public static class AudioTools
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Loads and returns an audio clip from a resource path using the specified <see cref="Assembly"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="resourcePath">The path to the resource within the <see cref="Assembly"/>.</param>
|
||||||
|
/// <param name="assembly">The <see cref="Assembly"/> from which to load the resource.</param>
|
||||||
|
/// <returns>A <see cref="AudioClip"/> object loaded from the specified resource path.</returns>
|
||||||
|
/// <exception cref="NotSupportedException">Attempted to load an Audio file in non WAV format.</exception>
|
||||||
|
/// <exception cref="MissingManifestResourceException">Stream failed to load. Check if the name of your asset was correct.</exception>
|
||||||
|
public static AudioClip LoadAudioFromResources(string resourcePath, Assembly assembly)
|
||||||
|
{
|
||||||
|
using var assetStream = assembly.GetManifestResourceStream(resourcePath)
|
||||||
|
?? throw new MissingManifestResourceException($"Stream failed to load. Check if the asset name is correct: {resourcePath}");
|
||||||
|
|
||||||
|
using var reader = new BinaryReader(assetStream);
|
||||||
|
|
||||||
|
// Read RIFF header
|
||||||
|
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "RIFF")
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Invalid WAV file (missing RIFF header).");
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.ReadInt32(); // File size (ignore)
|
||||||
|
|
||||||
|
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "WAVE")
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Invalid WAV file (missing WAVE header).");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read chunks
|
||||||
|
int channels = 0, sampleRate = 0, bitsPerSample = 0;
|
||||||
|
byte[]? audioData = null;
|
||||||
|
|
||||||
|
while (reader.BaseStream.Position < reader.BaseStream.Length)
|
||||||
|
{
|
||||||
|
var chunkId = Encoding.ASCII.GetString(reader.ReadBytes(4));
|
||||||
|
var chunkSize = reader.ReadInt32();
|
||||||
|
|
||||||
|
// Format chunk
|
||||||
|
if (chunkId == "fmt ")
|
||||||
|
{
|
||||||
|
int audioFormat = reader.ReadInt16();
|
||||||
|
channels = reader.ReadInt16();
|
||||||
|
sampleRate = reader.ReadInt32();
|
||||||
|
reader.ReadInt32(); // Byte rate (ignore)
|
||||||
|
reader.ReadInt16(); // Block align (ignore)
|
||||||
|
bitsPerSample = reader.ReadInt16();
|
||||||
|
|
||||||
|
if (audioFormat != 1) // Only PCM supported
|
||||||
|
throw new NotSupportedException("Only PCM WAV files are supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data chunk
|
||||||
|
else if (chunkId == "data")
|
||||||
|
{
|
||||||
|
audioData = reader.ReadBytes(chunkSize);
|
||||||
|
break; // No need to read further
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reader.BaseStream.Seek(chunkSize, SeekOrigin.Current); // Skip unknown chunks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioData == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("WAV file does not contain audio data.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channels == 0 || sampleRate == 0 || bitsPerSample == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("WAV file does not contain valid format information.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert PCM data to float array
|
||||||
|
var samples = ConvertPcmToFloat(audioData, bitsPerSample);
|
||||||
|
|
||||||
|
// Create the AudioClip
|
||||||
|
var audioClip = AudioClip.Create(resourcePath, samples.Length / channels, channels, sampleRate, false);
|
||||||
|
audioClip.SetData(samples, 0);
|
||||||
|
|
||||||
|
return audioClip;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is somewhat magic to me, I just copied it from elsewhere.
|
||||||
|
// Could have been written better, so here - Az
|
||||||
|
private static float[] ConvertPcmToFloat(byte[] pcmData, int bitsPerSample)
|
||||||
|
{
|
||||||
|
var sampleCount = pcmData.Length / (bitsPerSample / 8);
|
||||||
|
var floatData = new float[sampleCount];
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> source = pcmData;
|
||||||
|
Span<float> destination = floatData;
|
||||||
|
|
||||||
|
switch (bitsPerSample)
|
||||||
|
{
|
||||||
|
case 8:
|
||||||
|
for (var i = 0; i < sampleCount; i++)
|
||||||
|
{
|
||||||
|
destination[i] = (source[i] - 128) / 128f; // 8-bit PCM is unsigned
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 16:
|
||||||
|
var shortSource = MemoryMarshal.Cast<byte, short>(source);
|
||||||
|
|
||||||
|
for (var i = 0; i < sampleCount; i++)
|
||||||
|
{
|
||||||
|
destination[i] = shortSource[i] / 32768f;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 24:
|
||||||
|
for (int i = 0, byteIndex = 0; i < sampleCount; i++, byteIndex += 3)
|
||||||
|
{
|
||||||
|
var sample24 = source[byteIndex] | (source[byteIndex + 1] << 8) | (source[byteIndex + 2] << 16);
|
||||||
|
|
||||||
|
// Convert from unsigned to signed
|
||||||
|
if (sample24 > 0x7FFFFF)
|
||||||
|
sample24 -= 0x1000000;
|
||||||
|
|
||||||
|
destination[i] = sample24 / 8388608f;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 32:
|
||||||
|
var intSource = MemoryMarshal.Cast<byte, int>(source);
|
||||||
|
|
||||||
|
for (var i = 0; i < sampleCount; i++)
|
||||||
|
{
|
||||||
|
destination[i] = intSource[i] / 2147483648f;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException($"Unsupported bit depth: {bitsPerSample}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return floatData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Resources;
|
|
||||||
using System.Text;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace MiraAPI.Utilities.Assets;
|
namespace MiraAPI.Utilities.Assets;
|
||||||
@@ -15,118 +11,14 @@ public class LoadableAudioResourceAsset(string path) : LoadableAsset<AudioClip>
|
|||||||
{
|
{
|
||||||
private readonly Assembly _assembly = Assembly.GetCallingAssembly();
|
private readonly Assembly _assembly = Assembly.GetCallingAssembly();
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Loads the asset from embedded resources.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The asset to load.</returns>
|
|
||||||
/// <exception cref="NotSupportedException">Attempted to load an Audio file in non WAV format.</exception>
|
|
||||||
/// <exception cref="MissingManifestResourceException">Stream failed to load. Check if the name of your asset was correct.</exception>
|
|
||||||
public override AudioClip LoadAsset()
|
public override AudioClip LoadAsset()
|
||||||
{
|
{
|
||||||
using var assetStream = _assembly.GetManifestResourceStream(path)
|
if (!LoadedAsset)
|
||||||
?? throw new MissingManifestResourceException($"Stream failed to load. Check if the asset name is correct: {path}");
|
|
||||||
|
|
||||||
using var reader = new BinaryReader(assetStream);
|
|
||||||
|
|
||||||
// Read RIFF header
|
|
||||||
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "RIFF")
|
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Invalid WAV file (missing RIFF header).");
|
LoadedAsset = AudioTools.LoadAudioFromResources(path, _assembly);
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.ReadInt32(); // File size (ignore)
|
return LoadedAsset!;
|
||||||
|
|
||||||
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "WAVE")
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("Invalid WAV file (missing WAVE header).");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read chunks
|
|
||||||
int channels = 0, sampleRate = 0, bitsPerSample = 0;
|
|
||||||
byte[]? audioData = null;
|
|
||||||
|
|
||||||
while (reader.BaseStream.Position < reader.BaseStream.Length)
|
|
||||||
{
|
|
||||||
var chunkId = Encoding.ASCII.GetString(reader.ReadBytes(4));
|
|
||||||
var chunkSize = reader.ReadInt32();
|
|
||||||
|
|
||||||
// Format chunk
|
|
||||||
if (chunkId == "fmt ")
|
|
||||||
{
|
|
||||||
int audioFormat = reader.ReadInt16();
|
|
||||||
channels = reader.ReadInt16();
|
|
||||||
sampleRate = reader.ReadInt32();
|
|
||||||
reader.ReadInt32(); // Byte rate (ignore)
|
|
||||||
reader.ReadInt16(); // Block align (ignore)
|
|
||||||
bitsPerSample = reader.ReadInt16();
|
|
||||||
|
|
||||||
if (audioFormat != 1) // Only PCM supported
|
|
||||||
throw new NotSupportedException("Only PCM WAV files are supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data chunk
|
|
||||||
else if (chunkId == "data")
|
|
||||||
{
|
|
||||||
audioData = reader.ReadBytes(chunkSize);
|
|
||||||
break; // No need to read further
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
reader.BaseStream.Seek(chunkSize, SeekOrigin.Current); // Skip unknown chunks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audioData == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("WAV file does not contain audio data.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channels == 0 || sampleRate == 0 || bitsPerSample == 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("WAV file does not contain valid format information.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert PCM data to float array
|
|
||||||
var samples = ConvertPcmToFloat(audioData, bitsPerSample);
|
|
||||||
|
|
||||||
// Create the AudioClip
|
|
||||||
var audioClip = AudioClip.Create(path, samples.Length / channels, channels, sampleRate, false);
|
|
||||||
audioClip.SetData(samples, 0);
|
|
||||||
|
|
||||||
return audioClip;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is somewhat magic to me, I just copied it from elsewhere.
|
|
||||||
private static float[] ConvertPcmToFloat(byte[] pcmData, int bitsPerSample)
|
|
||||||
{
|
|
||||||
var sampleCount = pcmData.Length / (bitsPerSample / 8);
|
|
||||||
var floatData = new float[sampleCount];
|
|
||||||
|
|
||||||
for (var i = 0; i < sampleCount; i++)
|
|
||||||
{
|
|
||||||
var byteIndex = i * (bitsPerSample / 8);
|
|
||||||
|
|
||||||
switch (bitsPerSample)
|
|
||||||
{
|
|
||||||
case 8:
|
|
||||||
floatData[i] = (pcmData[byteIndex] - 128) / 128f; // 8-bit PCM is unsigned
|
|
||||||
break;
|
|
||||||
case 16:
|
|
||||||
floatData[i] = BitConverter.ToInt16(pcmData, byteIndex) / 32768f;
|
|
||||||
break;
|
|
||||||
case 24:
|
|
||||||
var sample24 = pcmData[byteIndex] | (pcmData[byteIndex + 1] << 8) | (pcmData[byteIndex + 2] << 16);
|
|
||||||
if (sample24 > 0x7FFFFF) sample24 -= 0x1000000; // Convert from unsigned to signed
|
|
||||||
floatData[i] = sample24 / 8388608f;
|
|
||||||
break;
|
|
||||||
case 32:
|
|
||||||
floatData[i] = BitConverter.ToInt32(pcmData, byteIndex) / 2147483648f;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new NotSupportedException($"Unsupported bit depth: {bitsPerSample}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return floatData;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using HarmonyLib;
|
|
||||||
using Il2CppInterop.Runtime;
|
using Il2CppInterop.Runtime;
|
||||||
using Reactor.Utilities.Extensions;
|
using Reactor.Utilities.Extensions;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -11,71 +9,55 @@ namespace MiraAPI.Utilities.Assets;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A utility class for loading multiple assets from an <see cref="AssetBundle"/>.
|
/// A utility class for loading multiple assets from an <see cref="AssetBundle"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LoadableBundleSubAssetHolder
|
/// <param name="names">The name of the assets to pull from.</param>
|
||||||
|
/// <param name="bundle">The <see cref="AssetBundle"/> that contains the assets.</param>
|
||||||
|
public class LoadableBundleSubAssetHolder(string[] names, AssetBundle bundle)
|
||||||
{
|
{
|
||||||
private readonly string[] spriteNames;
|
|
||||||
private readonly AssetBundle bundle;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the sprites contained within the asset.
|
/// Gets the sprites contained within the asset.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Sprite[] SubSprites { get; private set; } = [];
|
public Sprite[] SubSprites { get; private set; } = [];
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="LoadableBundleSubAssetHolder"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="names">The name of the assets to pull from.</param>
|
|
||||||
/// <param name="bundle">The <see cref="AssetBundle"/> that contains the assets.</param>
|
|
||||||
public LoadableBundleSubAssetHolder(string[] names, AssetBundle bundle)
|
|
||||||
{
|
|
||||||
this.bundle = bundle;
|
|
||||||
spriteNames = names;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="LoadableBundleSubAssetHolder"/> class.
|
/// Initializes a new instance of the <see cref="LoadableBundleSubAssetHolder"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the asset.</param>
|
/// <param name="name">The name of the asset.</param>
|
||||||
/// <param name="bundle">The <see cref="AssetBundle"/> that contains the assets.</param>
|
/// <param name="bundle">The <see cref="AssetBundle"/> that contains the assets.</param>
|
||||||
public LoadableBundleSubAssetHolder(string name, AssetBundle bundle)
|
public LoadableBundleSubAssetHolder(string name, AssetBundle bundle)
|
||||||
|
: this([name], bundle)
|
||||||
{
|
{
|
||||||
this.bundle = bundle;
|
|
||||||
spriteNames = [name];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to load all of the sprite assets within the sprite sheet.
|
/// Attempts to load all of the sprite assets within the sprite sheet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <exception cref="InvalidOperationException">Thrown if the asset is not actually a sprite sheet.</exception>
|
/// <exception cref="InvalidOperationException">Thrown if the asset is not actually a sprite sheet.</exception>
|
||||||
[SuppressMessage("Style", "IDE0270:Use coalesce expression", Justification = "Null coalescing bypasses Unity lifetime checks.")]
|
|
||||||
public void TryInit()
|
public void TryInit()
|
||||||
{
|
{
|
||||||
if (SubSprites.Length == 0)
|
if (SubSprites.Length != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var newSprites = new List<Sprite>();
|
||||||
|
var type = Il2CppType.From(typeof(Sprite));
|
||||||
|
|
||||||
|
foreach (var name in names)
|
||||||
{
|
{
|
||||||
var newSprites = Array.Empty<Sprite>();
|
var loadedAssets = bundle.LoadAssetWithSubAssets(name, type)
|
||||||
|
?? throw new InvalidOperationException($"INVALID ASSETS: {name}");
|
||||||
|
|
||||||
foreach (var name in spriteNames)
|
foreach (var obj in loadedAssets)
|
||||||
{
|
{
|
||||||
var loadedAssets = bundle.LoadAssetWithSubAssets(name, Il2CppType.From(typeof(Sprite))).ToArray();
|
var img = obj.TryCast<Sprite>();
|
||||||
|
|
||||||
if (loadedAssets == null)
|
if (img == null)
|
||||||
{
|
continue;
|
||||||
throw new InvalidOperationException($"INVALID ASSETS: {name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var obj in loadedAssets)
|
img.DontDestroy().DontUnload();
|
||||||
{
|
newSprites.Add(img);
|
||||||
var img = obj.TryCast<Sprite>();
|
|
||||||
if (img != null)
|
|
||||||
{
|
|
||||||
img.DontDestroy().DontUnload();
|
|
||||||
newSprites = newSprites.AddToArray(img);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SubSprites = newSprites;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SubSprites = [.. newSprites];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace MiraAPI.Utilities;
|
namespace MiraAPI.Utilities;
|
||||||
@@ -9,7 +8,6 @@ internal sealed class ControllableComparer<T>(T[] forcedToBottom, T[] forcedToTo
|
|||||||
where T : IComparable
|
where T : IComparable
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Warning cascades into forcing the entire tree to be ternary operators.")]
|
|
||||||
public int Compare(T? x, T? y)
|
public int Compare(T? x, T? y)
|
||||||
{
|
{
|
||||||
if ((forcedToBottom.Contains(x) && forcedToBottom.Contains(y)) || (forcedToTop.Contains(x) && forcedToTop.Contains(y)))
|
if ((forcedToBottom.Contains(x) && forcedToBottom.Contains(y)) || (forcedToTop.Contains(x) && forcedToTop.Contains(y)))
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ public static class Helpers
|
|||||||
private static readonly Regex TrailingZeroRegex = new(@"\.0+(?!\d)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));
|
private static readonly Regex TrailingZeroRegex = new(@"\.0+(?!\d)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));
|
||||||
private static NumberOption? _advancedSettingsRowPrefab;
|
private static NumberOption? _advancedSettingsRowPrefab;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all of the options stored within the provided type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="classType">The type to get the options of.</param>
|
||||||
|
/// <returns>A readonly list of options from the provided type.</returns>
|
||||||
public static ReadOnlyCollection<IModdedOption>? GetModdedOptionsForType(Type classType)
|
public static ReadOnlyCollection<IModdedOption>? GetModdedOptionsForType(Type classType)
|
||||||
{
|
{
|
||||||
var group = ModdedOptionsManager.Groups.FirstOrDefault(x => x.OptionableType == classType);
|
var group = ModdedOptionsManager.Groups.FirstOrDefault(x => x.OptionableType == classType);
|
||||||
@@ -42,6 +47,11 @@ public static class Helpers
|
|||||||
return group?.Children;
|
return group?.Children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the option text of the provided type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="classType">The type to get the text for.</param>
|
||||||
|
/// <returns>The options text of the provided type.</returns>
|
||||||
public static string GetOptionsText(Type classType)
|
public static string GetOptionsText(Type classType)
|
||||||
{
|
{
|
||||||
var group = ModdedOptionsManager.Groups.FirstOrDefault(x => x.OptionableType == classType);
|
var group = ModdedOptionsManager.Groups.FirstOrDefault(x => x.OptionableType == classType);
|
||||||
@@ -840,7 +850,7 @@ public static class Helpers
|
|||||||
obj.transform.localScale = Vector3.one / MatchInfoGuideBaseScale;
|
obj.transform.localScale = Vector3.one / MatchInfoGuideBaseScale;
|
||||||
obj.transform.localPosition = new Vector3(0f, 0.5f, 0f);
|
obj.transform.localPosition = new Vector3(0f, 0.5f, 0f);
|
||||||
var scrollHeight = variableTextSizing
|
var scrollHeight = variableTextSizing
|
||||||
? (desc.textBounds.size.y - 2.5f) + maxTextSize + 0.475f
|
? desc.textBounds.size.y - 2.5f + maxTextSize + 0.475f
|
||||||
: desc.textBounds.size.y - 2.5f + Mathf.Ceil(num / 2f) * 1.45f;
|
: desc.textBounds.size.y - 2.5f + Mathf.Ceil(num / 2f) * 1.45f;
|
||||||
parent.SetYBoundsMax(Mathf.Clamp(scrollHeight, 0f, 999f));
|
parent.SetYBoundsMax(Mathf.Clamp(scrollHeight, 0f, 999f));
|
||||||
return obj;
|
return obj;
|
||||||
@@ -1067,6 +1077,7 @@ public static class Helpers
|
|||||||
panel.roleDescription.fontSizeMin = 1.5f;
|
panel.roleDescription.fontSizeMin = 1.5f;
|
||||||
panel.roleDescription.ForceMeshUpdate();
|
panel.roleDescription.ForceMeshUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
panel.roleIcon.sprite = ability.Icon.LoadAsset();
|
panel.roleIcon.sprite = ability.Icon.LoadAsset();
|
||||||
panel.roleIcon.SetSizeLimit(0.13f);
|
panel.roleIcon.SetSizeLimit(0.13f);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user