mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-19 15:51:48 +00:00
Merge branch 'dev' into 2025.3.25
This commit is contained in:
commit
878eab25b8
26 files changed
+1261
-67
No files matched your search
@@ -5,6 +5,7 @@ namespace MiraAPI.Example;
|
||||
public static class ExampleAssets
|
||||
{
|
||||
public static LoadableResourceAsset ExampleButton { get; } = new("MiraAPI.Example.Resources.ExampleButton.png");
|
||||
public static LoadableResourceAsset CallMeetingButton { get; } = new("MiraAPI.Example.Resources.CallMeeting.png");
|
||||
|
||||
// Credit to EpicHorrors for the teleport button asset.
|
||||
public static LoadableResourceAsset TeleportButton { get; } = new("MiraAPI.Example.Resources.TeleportButton.png");
|
||||
|
||||
@@ -9,7 +9,10 @@ using MiraAPI.Events.Vanilla.Player;
|
||||
using MiraAPI.Example.Buttons.Freezer;
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example;
|
||||
|
||||
|
||||
@@ -9,11 +9,9 @@
|
||||
<ProjectReference Include="..\MiraAPI\MiraAPI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\ExampleButton.png" />
|
||||
<EmbeddedResource Include="Resources\FortniteBanner.jpeg" />
|
||||
<EmbeddedResource Include="Resources\TeleportButton.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\**\*.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\AmongUs.props" />
|
||||
</Project>
|
||||
@@ -1,12 +1,29 @@
|
||||
using MiraAPI.Example.Options.Modifiers;
|
||||
using Il2CppSystem.Collections.Generic;
|
||||
using MiraAPI.Example.Options.Modifiers;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using UnityEngine;
|
||||
using Random = System.Random;
|
||||
|
||||
namespace MiraAPI.Example.Modifiers;
|
||||
|
||||
public class CaptainModifier : GameModifier
|
||||
{
|
||||
public override string ModifierName => "Captain";
|
||||
public override LoadableAsset<Sprite>? ModifierIcon => ExampleAssets.CallMeetingButton;
|
||||
|
||||
public override void OnDeath(DeathReason reason)
|
||||
{
|
||||
Player.RemoveModifier(this);
|
||||
}
|
||||
|
||||
public override string GetDescription()
|
||||
{
|
||||
return $"You can call a meeting from anywhere on the map.";
|
||||
}
|
||||
|
||||
public override int GetAssignmentChance()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Modifiers;
|
||||
|
||||
@@ -6,6 +8,11 @@ public class HighPriorityModifier : GameModifier
|
||||
{
|
||||
public override string ModifierName => "High Priority";
|
||||
|
||||
public override string GetDescription()
|
||||
{
|
||||
return "You are high priority! Idrk what this means either tbh...";
|
||||
}
|
||||
|
||||
public override int GetAssignmentChance()
|
||||
{
|
||||
return 100;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Modifiers;
|
||||
|
||||
@@ -48,9 +50,14 @@ public abstract class BaseModifier : IOptionable
|
||||
public abstract string ModifierName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the modifier is hidden on the UI.
|
||||
/// Gets the modifier icon. Useless if HideOnUi is true.
|
||||
/// </summary>
|
||||
public virtual bool HideOnUi => false;
|
||||
public virtual LoadableAsset<Sprite>? ModifierIcon => null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the modifier is hidden on the UI. Will be hidden either way if no description is provided.
|
||||
/// </summary>
|
||||
public virtual bool HideOnUi => GetDescription() == string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the modifier is shown in the freeplay menu.
|
||||
@@ -63,10 +70,10 @@ public abstract class BaseModifier : IOptionable
|
||||
public virtual bool Unique => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HUD information for this modifier. Defaults to the modifier name. Does nothing if <see cref="HideOnUi"/> is true.
|
||||
/// Gets the HUD description for this modifier. Does nothing if <see cref="HideOnUi"/> is true. Required to be visible on UI.
|
||||
/// </summary>
|
||||
/// <returns>The information string for the HUD.</returns>
|
||||
public virtual string GetHudString() => ModifierName;
|
||||
/// <returns>The description string for the HUD.</returns>
|
||||
public virtual string GetDescription() => string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Called when the modifier is activated.
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Modifiers.ModifierDisplay;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities;
|
||||
@@ -26,13 +27,13 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
[HideFromIl2Cpp]
|
||||
public ImmutableList<BaseModifier> ActiveModifiers { get; private set; } = ImmutableList<BaseModifier>.Empty;
|
||||
|
||||
private ModifierDisplayComponent? ModifierDisplay { get; set; }
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
private List<BaseModifier> Modifiers { get; set; } = [];
|
||||
|
||||
private PlayerControl _player = null!;
|
||||
|
||||
private TextMeshPro _modifierText = null!;
|
||||
|
||||
private readonly List<BaseModifier> _toRemove = [];
|
||||
|
||||
private readonly List<BaseModifier> _toAdd = [];
|
||||
@@ -61,8 +62,8 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
return;
|
||||
}
|
||||
|
||||
_modifierText = Helpers.CreateTextLabel("ModifierText", HudManager.Instance.transform, AspectPosition.EdgeAlignments.RightTop, new Vector3(10.1f, 3.5f, 0), textAlignment: TextAlignmentOptions.Right);
|
||||
_modifierText.verticalAlignment = VerticalAlignmentOptions.Top;
|
||||
ModifierDisplay = ModifierDisplayComponent.CreateDisplay();
|
||||
ModifierDisplay?.UpdateModifiersList(Modifiers);
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
@@ -93,7 +94,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
{
|
||||
if (_player.AmOwner && HudManager.InstanceExists)
|
||||
{
|
||||
HudManager.Instance.SetHudActive(_player, _player.Data.Role, HudManager.Instance.TaskPanel.isActiveAndEnabled);
|
||||
ModifierDisplay?.UpdateModifiersList(Modifiers);
|
||||
}
|
||||
|
||||
ActiveModifiers = Modifiers.ToImmutableList();
|
||||
@@ -103,6 +104,11 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
{
|
||||
modifier.FixedUpdate();
|
||||
}
|
||||
|
||||
if (_player.AmOwner && ModifierDisplay && HudManager.InstanceExists)
|
||||
{
|
||||
ModifierDisplay!.gameObject.SetActive(MeetingHud.Instance || HudManager.Instance.TaskPanel.isActiveAndEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
@@ -111,32 +117,6 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
{
|
||||
modifier.Update();
|
||||
}
|
||||
|
||||
if (!_modifierText || !_player.AmOwner)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (MeetingHud.Instance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var showText = false;
|
||||
var builder = new StringBuilder("<b><size=130%>Modifiers:</b></size>\n");
|
||||
|
||||
foreach (var modifier in ActiveModifiers)
|
||||
{
|
||||
if (modifier.HideOnUi)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
showText = true;
|
||||
builder.AppendLine(modifier.GetHudString());
|
||||
}
|
||||
|
||||
_modifierText.text = showText ? builder.ToString() : string.Empty;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace MiraAPI.Modifiers.ModifierDisplay;
|
||||
|
||||
/// <summary>
|
||||
/// The code used to display Mira modifiers.
|
||||
/// </summary>
|
||||
[RegisterInIl2Cpp]
|
||||
public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the instance of the Modifier Display.
|
||||
/// </summary>
|
||||
public static ModifierDisplayComponent? Instance { get; private set; }
|
||||
|
||||
private RectTransform _children = null!;
|
||||
private GameObject _modTemplate = null!;
|
||||
private PassiveButton _toggleButton = null!;
|
||||
private TextMeshPro _toggleBtnText = null!;
|
||||
|
||||
private RectTransform _pagination = null!;
|
||||
private TextMeshPro _pageText = null!;
|
||||
private PassiveButton _nextButton = null!;
|
||||
private PassiveButton _backButton = null!;
|
||||
|
||||
private int _currentPage = 0;
|
||||
private const int _itemsPerPage = 3;
|
||||
|
||||
private readonly Dictionary<BaseModifier, ModifierUiComponent> _modifiers = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only dictionary of the created modifier UI components.
|
||||
/// </summary>
|
||||
[HideFromIl2Cpp]
|
||||
public ReadOnlyDictionary<BaseModifier, ModifierUiComponent> Modifiers => new(_modifiers);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the modifier display hud is currently open.
|
||||
/// </summary>
|
||||
public bool IsOpen { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
_children = transform.GetChild(0).GetComponent<RectTransform>();
|
||||
_modTemplate = transform.GetChild(2).gameObject;
|
||||
|
||||
_toggleButton = transform.GetChild(1).GetComponent<PassiveButton>();
|
||||
_toggleButton.OnClick = new Button.ButtonClickedEvent();
|
||||
_toggleButton.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
IsOpen = !IsOpen;
|
||||
_children.gameObject.SetActive(!_children.gameObject.activeSelf);
|
||||
}));
|
||||
|
||||
_toggleButton.ClickSound = HudManager.Instance.GameMenu.StreamerModeButton.GetComponent<PassiveButton>().ClickSound;
|
||||
_toggleButton.HoverSound = HudManager.Instance.GameMenu.StreamerModeButton.GetComponent<PassiveButton>().HoverSound;
|
||||
_toggleButton.inactiveSprites = _toggleButton.transform.GetChild(1).gameObject;
|
||||
_toggleBtnText = _toggleButton.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||
|
||||
_pagination = _children.transform.GetChild(0).GetComponent<RectTransform>();
|
||||
_pageText = _pagination.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||
_nextButton = _pagination.transform.GetChild(1).GetComponent<PassiveButton>();
|
||||
_backButton = _pagination.transform.GetChild(2).GetComponent<PassiveButton>();
|
||||
|
||||
_toggleBtnText.font = _pageText.font = _nextButton.buttonText.font = _backButton.buttonText.font = HudManager.Instance.TaskPanel.taskText.font;
|
||||
_toggleBtnText.fontMaterial = _pageText.fontMaterial = _nextButton.buttonText.fontMaterial = _backButton.buttonText.fontMaterial = HudManager.Instance.TaskPanel.taskText.fontMaterial;
|
||||
|
||||
_nextButton.OnClick = new Button.ButtonClickedEvent();
|
||||
_nextButton.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
_currentPage++;
|
||||
UpdatePage();
|
||||
}));
|
||||
|
||||
_backButton.OnClick = new Button.ButtonClickedEvent();
|
||||
_backButton.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
_currentPage--;
|
||||
UpdatePage();
|
||||
}));
|
||||
|
||||
IsOpen = false;
|
||||
_children.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use this to refresh the modifiers UI. Useful if you hide modifiers uis based on options.
|
||||
/// </summary>
|
||||
public void RefreshModifiers()
|
||||
{
|
||||
UpdateModifiersList([.. PlayerControl.LocalPlayer.GetModifierComponent().ActiveModifiers]);
|
||||
}
|
||||
|
||||
internal void DestroyComponent(ModifierUiComponent component)
|
||||
{
|
||||
_modifiers.Remove(component.Modifier!);
|
||||
component.gameObject.DestroyImmediate();
|
||||
|
||||
RefreshModifiers();
|
||||
}
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
internal static ModifierDisplayComponent CreateDisplay()
|
||||
{
|
||||
var gameObject = Instantiate(MiraAssets.ModifierDisplay.LoadAsset(), HudManager.Instance.transform);
|
||||
var minigame = gameObject.AddComponent<ModifierDisplayComponent>();
|
||||
return minigame;
|
||||
}
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
private ModifierUiComponent CreateForModifier(BaseModifier modifier)
|
||||
{
|
||||
var newMod = Instantiate(_modTemplate, _children.transform);
|
||||
newMod.transform.name = modifier.ModifierName;
|
||||
var comp = newMod.AddComponent<ModifierUiComponent>();
|
||||
comp.Modifier = modifier;
|
||||
return comp;
|
||||
}
|
||||
|
||||
private void UpdatePage()
|
||||
{
|
||||
var totalPages = Mathf.CeilToInt((float)_modifiers.Count / _itemsPerPage);
|
||||
_currentPage = Mathf.Clamp(_currentPage, 0, Mathf.Max(0, totalPages - 1));
|
||||
|
||||
if (_modifiers.Count <= _itemsPerPage)
|
||||
{
|
||||
_modifiers.Do(x => x.Value.gameObject.SetActive(true));
|
||||
_pagination.gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_pagination.gameObject.SetActive(true);
|
||||
if (_pageText != null)
|
||||
{
|
||||
_pageText.text = $"Page {_currentPage + 1}/{Mathf.Max(totalPages, 1)}";
|
||||
}
|
||||
|
||||
_nextButton.gameObject.SetActive(true);
|
||||
_backButton.gameObject.SetActive(true);
|
||||
|
||||
_modifiers.Do(x => x.Value.gameObject.SetActive(false));
|
||||
var start = _currentPage * _itemsPerPage;
|
||||
var end = Mathf.Min(start + _itemsPerPage, _modifiers.Count);
|
||||
var modifiers = _modifiers.ToArray();
|
||||
|
||||
for (var i = start; i < end; i++)
|
||||
{
|
||||
modifiers[i].Value.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
_pagination.SetAsLastSibling();
|
||||
}
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
internal void UpdateModifiersList(List<BaseModifier> modifiers)
|
||||
{
|
||||
var filteredModifiers = modifiers.Where(x => !x.HideOnUi && x.GetDescription() != string.Empty).ToList();
|
||||
var modifiersToAdd = filteredModifiers.ToList();
|
||||
|
||||
foreach (var mod in _modifiers.ToArray())
|
||||
{
|
||||
if (modifiersToAdd.Contains(mod.Key))
|
||||
{
|
||||
modifiersToAdd.Remove(mod.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
mod.Value.gameObject.Destroy();
|
||||
_modifiers.Remove(mod.Key);
|
||||
}
|
||||
|
||||
_toggleBtnText.text = $"Modifiers ({filteredModifiers.Count})";
|
||||
|
||||
if (filteredModifiers.Count == 0)
|
||||
{
|
||||
_toggleButton.gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_toggleButton.gameObject.SetActive(true);
|
||||
|
||||
foreach (var mod in modifiersToAdd)
|
||||
{
|
||||
_modifiers.Add(mod, CreateForModifier(mod));
|
||||
}
|
||||
|
||||
UpdatePage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Modifiers.ModifierDisplay;
|
||||
|
||||
/// <summary>
|
||||
/// The code placed on every Modifier HUD object. Used to handle updating.
|
||||
/// </summary>
|
||||
[RegisterInIl2Cpp]
|
||||
public class ModifierUiComponent(nint ptr) : MonoBehaviour(ptr)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the modifier which this component is for.
|
||||
/// </summary>
|
||||
[HideFromIl2Cpp]
|
||||
public BaseModifier? Modifier { get; internal set; }
|
||||
|
||||
private SpriteRenderer modBg = null!;
|
||||
private TextMeshPro desc = null!;
|
||||
private SpriteRenderer icon = null!;
|
||||
private TextMeshPro nameText = null!;
|
||||
private RectTransform descRect = null!;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Modifier == null) return;
|
||||
|
||||
modBg = transform.GetChild(0).GetComponent<SpriteRenderer>();
|
||||
desc = modBg.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||
icon = modBg.transform.GetChild(1).GetComponent<SpriteRenderer>();
|
||||
var nameBg = transform.GetChild(1);
|
||||
nameText = nameBg.GetChild(0).GetComponent<TextMeshPro>();
|
||||
descRect = desc.gameObject.GetComponent<RectTransform>();
|
||||
|
||||
nameText.font = desc.font = HudManager.Instance.TaskPanel.taskText.font;
|
||||
nameText.fontMaterial = desc.fontMaterial = HudManager.Instance.TaskPanel.taskText.fontMaterial;
|
||||
}
|
||||
|
||||
// Might not be the best solution, but used to update name/description/icon if updated in the modifier.
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (Modifier == null || ModifierDisplayComponent.Instance == null || !ModifierDisplayComponent.Instance.IsOpen) return;
|
||||
|
||||
if (Modifier.HideOnUi || Modifier.GetDescription() == string.Empty)
|
||||
{
|
||||
ModifierDisplayComponent.Instance.DestroyComponent(this);
|
||||
return;
|
||||
}
|
||||
|
||||
nameText.text = Modifier.ModifierName;
|
||||
desc.text = Modifier.GetDescription();
|
||||
modBg.gameObject.SetActive(true);
|
||||
|
||||
icon.gameObject.SetActive(Modifier.ModifierIcon != null);
|
||||
if (icon.gameObject && icon.gameObject.activeSelf && Modifier.ModifierIcon != null)
|
||||
{
|
||||
icon.sprite = Modifier.ModifierIcon?.LoadAsset();
|
||||
icon.size = new Vector2(0.7f, 0.7f);
|
||||
icon.tileMode = SpriteTileMode.Continuous;
|
||||
descRect.localPosition = new Vector3(0.38f, -0.1f, -5);
|
||||
descRect.sizeDelta = new Vector2(2.38f, 0.65f);
|
||||
}
|
||||
else
|
||||
{
|
||||
descRect.localPosition = new Vector3(0, -0.1f, -5);
|
||||
descRect.sizeDelta = new Vector2(3.1f, 0.65f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public abstract class GameModifier : BaseModifier
|
||||
public virtual bool IsModifierValidOn(RoleBehaviour role) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the player won the game with this modifier.
|
||||
/// Determines whether the player won the game with this modifier. The modifier with the highest priority will override other modifiers.
|
||||
/// </summary>
|
||||
/// <param name="reason">The reason why the game ended.</param>
|
||||
/// <returns>True if the player won, false if they lost. Return null to use the player's default win condition.</returns>
|
||||
|
||||
@@ -45,12 +45,12 @@ public abstract class TimedModifier : BaseModifier
|
||||
public override bool HideOnUi => !TimerActive;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HUD information for Timed Modifier, including the time remaining.
|
||||
/// Gets the HUD description for Timed Modifier, including the time remaining.
|
||||
/// </summary>
|
||||
/// <returns>A string with the hud information + timer.</returns>
|
||||
public override string GetHudString()
|
||||
/// <returns>A string with the time remaining.</returns>
|
||||
public override string GetDescription()
|
||||
{
|
||||
return base.GetHudString() + $" <size=70%>({Math.Round(Duration - TimeRemaining, 0)}s/{Duration}s)</size>";
|
||||
return $"Remaining Time: {Math.Round(Duration - TimeRemaining, 0)}s/{Duration}s";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using AmongUs.Data;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Utilities;
|
||||
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 SortedList<string, List<HatData>> sortedHats = [];
|
||||
private static int currentPage;
|
||||
|
||||
[HarmonyPatch(nameof(HatsTab.OnEnable))]
|
||||
[HarmonyPrefix]
|
||||
public static bool OnEnablePrefix(HatsTab __instance)
|
||||
{
|
||||
__instance.currentHat = HatManager.Instance.GetHatById(DataManager.Player.Customization.Hat);
|
||||
var allHats = HatManager.Instance.GetUnlockedHats().ToImmutableList();
|
||||
|
||||
if (sortedHats.Count == 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);
|
||||
}
|
||||
}
|
||||
|
||||
GenerateHats(__instance, currentPage);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(HatsTab), nameof(HatsTab.Update))]
|
||||
[HarmonyPrefix]
|
||||
|
||||
public static void UpdatePrefix(HatsTab __instance)
|
||||
{
|
||||
if (sortedHats.Count == 0) return;
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
|
||||
{
|
||||
currentPage--;
|
||||
currentPage = currentPage < 0 ? sortedHats.Count - 1 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
}
|
||||
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
|
||||
{
|
||||
currentPage++;
|
||||
currentPage = currentPage > sortedHats.Count - 1 ? 0 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
}
|
||||
}
|
||||
|
||||
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.Destroy());
|
||||
|
||||
var groupNameText = __instance.GetComponentInChildren<TextMeshPro>(false);
|
||||
|
||||
var (groupName, hats) = sortedHats.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}\nPress Ctrl or Tab to cycle pages";
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
text.fontSize = 3f;
|
||||
text.fontSizeMax = 3f;
|
||||
text.fontSizeMin = 0f;
|
||||
var xLerp = __instance.XRange.Lerp(0.5f);
|
||||
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));
|
||||
}
|
||||
|
||||
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));
|
||||
var hatYposition = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
||||
GenerateColorChip(__instance, new Vector2(hatXposition, hatYposition), hat);
|
||||
hatIndex += 1;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
|
||||
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;
|
||||
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))));
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
|
||||
namespace MiraAPI.Patches.Menu;
|
||||
|
||||
/// <summary>
|
||||
/// General MainMenuManage patches.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(MainMenuManager))]
|
||||
public static class MainMenuManagerPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// A postifix on Awake to load all the addressables registered.
|
||||
/// </summary>
|
||||
[HarmonyPatch(nameof(MainMenuManager.Awake))]
|
||||
[HarmonyPostfix]
|
||||
public static void AwakePostfix()
|
||||
{
|
||||
AddressablesLoader.LoadAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
using AmongUs.Data;
|
||||
using HarmonyLib;
|
||||
using Innersloth.Assets;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Menu;
|
||||
|
||||
[HarmonyPatch(typeof(NameplatesTab))]
|
||||
public static class NameplatesTabPatches
|
||||
{
|
||||
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 item in data)
|
||||
{
|
||||
if (!SortedNameplates.ContainsKey(item.Key)) SortedNameplates.Add(item.Key, []);
|
||||
SortedNameplates[item.Key].Add(item.Visor);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(nameof(NameplatesTab.OnEnable))]
|
||||
[HarmonyPrefix]
|
||||
public static bool OnEnablePrefix(NameplatesTab __instance)
|
||||
{
|
||||
__instance.plateId = HatManager.Instance.GetNamePlateById(DataManager.Player.Customization.namePlate).ProdId;
|
||||
|
||||
if (!SortedNameplates.ContainsKey("Vanilla")) AddRange(DestroyableSingleton<HatManager>.Instance.GetUnlockedNamePlates().Select(x => ("Vanilla", x)));
|
||||
GenerateHats(__instance, currentPage);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(NameplatesTab), nameof(NameplatesTab.Update))]
|
||||
[HarmonyPrefix]
|
||||
|
||||
public static void UpdatePrefix(NameplatesTab __instance)
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
|
||||
{
|
||||
currentPage--;
|
||||
currentPage = currentPage < 0 ? SortedNameplates.Count - 1 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
}
|
||||
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
|
||||
{
|
||||
currentPage++;
|
||||
currentPage = currentPage > SortedNameplates.Count - 1 ? 0 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GenerateHats(NameplatesTab __instance, int page)
|
||||
{
|
||||
foreach (ColorChip instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy();
|
||||
__instance.ColorChips.Clear();
|
||||
__instance.scroller.Inner.GetComponentsInChildren<TextMeshPro>().Do(x => x.gameObject.Destroy());
|
||||
|
||||
var groupNameText = __instance.GetComponentInChildren<TextMeshPro>(false);
|
||||
|
||||
int 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}\nPress Ctrl or Tab to cycle pages";
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
text.fontSize = 3f;
|
||||
text.fontSizeMax = 3f;
|
||||
text.fontSizeMin = 0f;
|
||||
float xLerp = __instance.XRange.Lerp(0.5f);
|
||||
float yLerp = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
||||
text.transform.localPosition = new Vector3(xLerp, yLerp, -1f);
|
||||
|
||||
hatIndex += 2;
|
||||
foreach (var visor in nameplates.OrderBy(HatManager.Instance.allNamePlates.IndexOf))
|
||||
{
|
||||
float hatXposition = __instance.XRange.Lerp(hatIndex % __instance.NumPerRow / (__instance.NumPerRow - 1f));
|
||||
float hatYposition = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
||||
GenerateColorChip(__instance, new Vector2(hatXposition, hatYposition), visor);
|
||||
hatIndex += 1;
|
||||
}
|
||||
|
||||
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
|
||||
__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;
|
||||
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))));
|
||||
colorChip.Button.ClickMask = __instance.scroller.Hitbox;
|
||||
colorChip.ProductId = namePlate.ProdId;
|
||||
|
||||
var x = (NamePlateViewData viewdata) =>
|
||||
{
|
||||
colorChip.transform.GetChild(1).GetComponent<SpriteRenderer>().sprite = viewdata?.Image;
|
||||
//(colorChip as NameplateChip).image.sprite = viewdata?.Image;
|
||||
};
|
||||
__instance.StartCoroutine(AddressableAssetExtensions.CoLoadAssetAsync<NamePlateViewData>(__instance, namePlate.GetAssetReference(), x));
|
||||
colorChip.transform.localPosition = new Vector3(position.x, position.y, -1f);
|
||||
colorChip.Tag = namePlate;
|
||||
__instance.ColorChips.Add(colorChip);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
using AmongUs.Data;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
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;
|
||||
|
||||
[HarmonyPatch(nameof(SkinsTab.OnEnable))]
|
||||
[HarmonyPrefix]
|
||||
public static bool OnEnablePrefix(SkinsTab __instance)
|
||||
{
|
||||
__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);
|
||||
}
|
||||
}
|
||||
|
||||
GenerateHats(__instance, currentPage);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(SkinsTab), nameof(SkinsTab.Update))]
|
||||
[HarmonyPrefix]
|
||||
|
||||
public static void UpdatePrefix(SkinsTab __instance)
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
|
||||
{
|
||||
currentPage--;
|
||||
currentPage = currentPage < 0 ? sortedSkins.Count - 1 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
}
|
||||
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
|
||||
{
|
||||
currentPage++;
|
||||
currentPage = currentPage > sortedSkins.Count - 1 ? 0 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GenerateHats(SkinsTab __instance, int page)
|
||||
{
|
||||
foreach (ColorChip instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy();
|
||||
__instance.ColorChips.Clear();
|
||||
__instance.scroller.Inner.GetComponentsInChildren<TextMeshPro>().Do(x => x.gameObject.Destroy());
|
||||
|
||||
var groupNameText = __instance.GetComponentInChildren<TextMeshPro>(false);
|
||||
|
||||
int 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}\nPress Ctrl or Tab to cycle pages";
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
text.fontSize = 3f;
|
||||
text.fontSizeMax = 3f;
|
||||
text.fontSizeMin = 0f;
|
||||
float xLerp = __instance.XRange.Lerp(0.5f);
|
||||
float yLerp = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
||||
text.transform.localPosition = new Vector3(xLerp, yLerp, -1f);
|
||||
|
||||
hatIndex += 5;
|
||||
foreach (var skin in skins.OrderBy(HatManager.Instance.allSkins.IndexOf))
|
||||
{
|
||||
float hatXposition = __instance.XRange.Lerp(hatIndex % __instance.NumPerRow / (__instance.NumPerRow - 1f));
|
||||
float hatYposition = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
||||
GenerateColorChip(__instance, new Vector2(hatXposition, hatYposition), skin);
|
||||
hatIndex += 1;
|
||||
}
|
||||
|
||||
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
|
||||
__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;
|
||||
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))));
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using AmongUs.Data;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Menu;
|
||||
|
||||
[HarmonyPatch(typeof(VisorsTab))]
|
||||
public static class VisorsTabPatches
|
||||
{
|
||||
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 item in data)
|
||||
{
|
||||
if (!SortedVisors.ContainsKey(item.Key)) SortedVisors.Add(item.Key, []);
|
||||
SortedVisors[item.Key].Add(item.Visor);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(nameof(VisorsTab.OnEnable))]
|
||||
[HarmonyPrefix]
|
||||
public static bool OnEnablePrefix(VisorsTab __instance)
|
||||
{
|
||||
__instance.visorId = HatManager.Instance.GetVisorById(DataManager.Player.Customization.Visor).ProdId;
|
||||
|
||||
if (!SortedVisors.ContainsKey("Vanilla")) AddRange(DestroyableSingleton<HatManager>.Instance.GetUnlockedVisors().Select(x => ("Vanilla", x)));
|
||||
GenerateHats(__instance, currentPage);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(VisorsTab), nameof(VisorsTab.Update))]
|
||||
[HarmonyPrefix]
|
||||
|
||||
public static void UpdatePrefix(VisorsTab __instance)
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
|
||||
{
|
||||
currentPage--;
|
||||
currentPage = currentPage < 0 ? SortedVisors.Count - 1 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
}
|
||||
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
|
||||
{
|
||||
currentPage++;
|
||||
currentPage = currentPage > SortedVisors.Count - 1 ? 0 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GenerateHats(VisorsTab __instance, int page)
|
||||
{
|
||||
foreach (ColorChip instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy();
|
||||
__instance.ColorChips.Clear();
|
||||
__instance.scroller.Inner.GetComponentsInChildren<TextMeshPro>().Do(x => x.gameObject.Destroy());
|
||||
|
||||
var groupNameText = __instance.GetComponentInChildren<TextMeshPro>(false);
|
||||
|
||||
int 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}\nPress Ctrl or Tab to cycle pages";
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
text.fontSize = 3f;
|
||||
text.fontSizeMax = 3f;
|
||||
text.fontSizeMin = 0f;
|
||||
float xLerp = __instance.XRange.Lerp(0.5f);
|
||||
float yLerp = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
||||
text.transform.localPosition = new Vector3(xLerp, yLerp, -1f);
|
||||
|
||||
hatIndex += 5;
|
||||
foreach (var visor in visors.OrderBy(HatManager.Instance.allVisors.IndexOf))
|
||||
{
|
||||
float hatXposition = __instance.XRange.Lerp(hatIndex % __instance.NumPerRow / (__instance.NumPerRow - 1f));
|
||||
float hatYposition = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
||||
GenerateColorChip(__instance, new Vector2(hatXposition, hatYposition), visor);
|
||||
hatIndex += 1;
|
||||
}
|
||||
|
||||
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
|
||||
__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;
|
||||
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))));
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,20 @@
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Modifiers;
|
||||
|
||||
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))]
|
||||
public static class EndGameDidWinPatch
|
||||
internal static class EndGameDidWinPatch
|
||||
{
|
||||
public static void Postfix()
|
||||
private static List<CachedPlayerData> CachedWinners { get; set; } = [];
|
||||
|
||||
public static void Prefix(EndGameResult endGameResult)
|
||||
{
|
||||
var gameOverReason = EndGameResult.CachedGameOverReason;
|
||||
EndGameResult.CachedWinners.Clear();
|
||||
CachedWinners.Clear();
|
||||
var gameOverReason = endGameResult.GameOverReason;
|
||||
|
||||
var players = GameData.Instance.AllPlayers.ToArray();
|
||||
for (var i = 0; i < GameData.Instance.PlayerCount; i++)
|
||||
@@ -26,25 +27,37 @@ public static class EndGameDidWinPatch
|
||||
|
||||
var didWin = networkedPlayerInfo.Role.DidWin(gameOverReason);
|
||||
|
||||
bool? modifierDidWin = null;
|
||||
if (!networkedPlayerInfo.Object)
|
||||
{
|
||||
if (didWin)
|
||||
{
|
||||
CachedWinners.Add(new CachedPlayerData(networkedPlayerInfo));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var modifier in networkedPlayerInfo.Object.GetModifiers<GameModifier>().OrderByDescending(x => x.Priority()))
|
||||
{
|
||||
var result = modifier.DidWin(gameOverReason);
|
||||
if (!result.HasValue) continue;
|
||||
|
||||
modifierDidWin = result.Value;
|
||||
didWin = result.Value;
|
||||
break;
|
||||
}
|
||||
|
||||
if (modifierDidWin.HasValue)
|
||||
{
|
||||
didWin = modifierDidWin.Value;
|
||||
}
|
||||
|
||||
if (didWin)
|
||||
{
|
||||
EndGameResult.CachedWinners.Add(new CachedPlayerData(networkedPlayerInfo));
|
||||
CachedWinners.Add(new CachedPlayerData(networkedPlayerInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Postfix(EndGameResult endGameResult)
|
||||
{
|
||||
EndGameResult.CachedWinners.Clear();
|
||||
foreach (var winner in CachedWinners)
|
||||
{
|
||||
EndGameResult.CachedWinners.Add(winner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ internal static class EjectionPatches
|
||||
return;
|
||||
}
|
||||
|
||||
if (role.GetCustomEjectionMessage(__instance.initData.networkedPlayer) == null)
|
||||
if (!GameManager.Instance.LogicOptions.GetConfirmImpostor() || role.GetCustomEjectionMessage(__instance.initData.networkedPlayer) == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,222 @@
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Patches.Menu;
|
||||
using Reactor.Utilities;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
|
||||
namespace MiraAPI.Utilities.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// The component for loading Addressables, and cosmetics.
|
||||
/// </summary>
|
||||
public static class AddressablesLoader
|
||||
{
|
||||
private static bool _isInitialized;
|
||||
|
||||
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 = [];
|
||||
|
||||
/// <summary>
|
||||
/// Registers a specific addressables package to load asynchronously at the start of the game, when possible.
|
||||
/// </summary>
|
||||
/// <param name="location">The location, remote or otherwise, of the addressables.</param>
|
||||
/// <param name="providerSuffix">The suffix of the provider for an addressables package.</param>
|
||||
public static void RegisterCatalog(string location, string providerSuffix = "")
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more catalogs.");
|
||||
return;
|
||||
}
|
||||
|
||||
CatalogLocations.Add((location, providerSuffix));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a specific addressables key as only containing <see cref="HatData"/>'s to load.
|
||||
/// </summary>
|
||||
/// <param name="addressablesKey">The key/label/group for a List <see cref="HatData"/>.</param>
|
||||
public static void RegisterHats(string addressablesKey)
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more keys for hats.");
|
||||
return;
|
||||
}
|
||||
|
||||
RegisteredHatKeys.Add(addressablesKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a specific addressables key as only containing <see cref="SkinData"/>'s to load.
|
||||
/// </summary>
|
||||
/// <param name="addressablesKey">The key/label/group for a List <see cref="SkinData"/>.</param>
|
||||
public static void RegisterSkins(string addressablesKey)
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more keys for skins.");
|
||||
return;
|
||||
}
|
||||
|
||||
RegisteredSkinKeys.Add(addressablesKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a specific addressables key as only containing <see cref="NamePlateData"/>'s to load.
|
||||
/// </summary>
|
||||
/// <param name="addressablesKey">The key/label/group for a List <see cref="NamePlateData"/>.</param>
|
||||
/// /// <param name="groupTitle">The title of the group for visors.</param>
|
||||
public static void RegisterNameplates(string addressablesKey, string groupTitle)
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more keys for nameplates.");
|
||||
return;
|
||||
}
|
||||
|
||||
RegisteredNameplateKeys.Add((addressablesKey, groupTitle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a specific addressables key as only containing <see cref="VisorData"/>'s to load.
|
||||
/// </summary>
|
||||
/// <param name="addressablesKey">The key/label/group for a List <see cref="VisorData"/>.</param>
|
||||
/// <param name="groupTitle">The title of the group for visors.</param>
|
||||
public static void RegisterVisors(string addressablesKey, string groupTitle)
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more keys for visors.");
|
||||
return;
|
||||
}
|
||||
|
||||
RegisteredVisorKeys.Add((addressablesKey, groupTitle));
|
||||
}
|
||||
|
||||
internal static void LoadAll()
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
Logger<MiraApiPlugin>.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)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to load catalog {location}.");
|
||||
}
|
||||
|
||||
Logger<MiraApiPlugin>.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);
|
||||
var skinBehaviours = DiscoverData<SkinData>(RegisteredSkinKeys);
|
||||
var namePlateBehaviours = DiscoverAndReportData<NamePlateData>(RegisteredNameplateKeys);
|
||||
var visorBehaviours = DiscoverAndReportData<VisorData>(RegisteredVisorKeys);
|
||||
|
||||
var hatData = new List<HatData>();
|
||||
hatData.AddRange(DestroyableSingleton<HatManager>.Instance.allHats);
|
||||
hatData.ForEach(x => x.StoreName = "Vanilla");
|
||||
DestroyableSingleton<HatManager>.Instance.allHats = PrepareArray(hatData, hatBehaviours);
|
||||
|
||||
var skinData = new List<SkinData>();
|
||||
skinData.AddRange(DestroyableSingleton<HatManager>.Instance.allSkins);
|
||||
skinData.ForEach(x => x.StoreName = "Vanilla");
|
||||
DestroyableSingleton<HatManager>.Instance.allSkins = PrepareArray(skinData, skinBehaviours);
|
||||
|
||||
var visorData = new List<VisorData>();
|
||||
visorData.AddRange(DestroyableSingleton<HatManager>.Instance.allVisors);
|
||||
VisorsTabPatches.AddRange(visorBehaviours);
|
||||
DestroyableSingleton<HatManager>.Instance.allVisors = PrepareArray(visorData, visorBehaviours.Select(x=>x.Data).ToList());
|
||||
|
||||
var namePlateData = new List<NamePlateData>();
|
||||
namePlateData.AddRange(DestroyableSingleton<HatManager>.Instance.allNamePlates);
|
||||
NameplatesTabPatches.AddRange(namePlateBehaviours);
|
||||
DestroyableSingleton<HatManager>.Instance.allNamePlates = PrepareArray(namePlateData, namePlateBehaviours.Select(x => x.Data).ToList());
|
||||
}
|
||||
|
||||
private static T[] PrepareArray<T>(List<T> data, List<T> behaviours) where T : CosmeticData
|
||||
{
|
||||
var count = data.Count;
|
||||
for (int i = 0; i < behaviours.Count; i++)
|
||||
{
|
||||
behaviours[i].displayOrder = count + i;
|
||||
data.Add(behaviours[i]);
|
||||
}
|
||||
return data.ToArray();
|
||||
}
|
||||
|
||||
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 Il2CppSystem.Collections.Generic.List<T>(assets.Pointer);
|
||||
behaviours.AddRange(array.ToArray());
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger<MiraApiPlugin>.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 Il2CppSystem.Collections.Generic.List<T>(assets.Pointer);
|
||||
behaviours.AddRange(array.ToArray().Select(x => (tag.Category, x)));
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to find tag {tag}");
|
||||
}
|
||||
}
|
||||
return behaviours;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine.AddressableAssets;
|
||||
|
||||
namespace MiraAPI.Utilities.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// A utility class for loading an asset from ALL addressable locations.
|
||||
/// </summary>
|
||||
/// <param name="uid">The guid of the asset.</param>
|
||||
/// <typeparam name="T">The type of the asset to be loaded.</typeparam>
|
||||
public class LoadableAddressableAsset<T>(string uid) : LoadableAsset<T> where T : UnityEngine.Object
|
||||
{
|
||||
private readonly Action<LoadableAddressableAsset<T>>? _gcAction;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoadableAddressableAsset{T}"/> class, with added garbageCollector logic,
|
||||
/// to help with https://github.com/BepInEx/Il2CppInterop/issues/40.
|
||||
/// </summary>
|
||||
/// <param name="uid">The guid of the asset.</param>
|
||||
/// <param name="garbageCollection">A lambda that allows GCHandle.Alloc calls without garbage collection interferences.</param>
|
||||
public LoadableAddressableAsset(string uid, Action<LoadableAddressableAsset<T>> garbageCollection) : this(uid)
|
||||
{
|
||||
_gcAction = garbageCollection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the asset from addressables.
|
||||
/// </summary>
|
||||
/// <returns>The asset.</returns>
|
||||
/// <exception cref="Exception">The asset did not load properly.</exception>
|
||||
public override T LoadAsset()
|
||||
{
|
||||
if (LoadedAsset != null)
|
||||
{
|
||||
return LoadedAsset;
|
||||
}
|
||||
|
||||
var remoteReference = new AssetReference(uid);
|
||||
|
||||
if (!GC.TryStartNoGCRegion(4096, true)) Logger<MiraApiPlugin>.Error("Could not start NoGCRegion of size 4kb, there is the possibility of injected unmanaged classes being garbage collected as per BepInEx/Il2CppInterop/issues/40");
|
||||
|
||||
LoadedAsset = remoteReference.LoadAssetAsync<T>().WaitForCompletion();
|
||||
if (LoadedAsset == null)
|
||||
{
|
||||
GC.EndNoGCRegion();
|
||||
throw new InvalidOperationException($"INVALID ASSET: {uid}");
|
||||
}
|
||||
|
||||
_gcAction?.Invoke(this);
|
||||
GC.EndNoGCRegion();
|
||||
|
||||
return LoadedAsset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine.AddressableAssets;
|
||||
|
||||
namespace MiraAPI.Utilities.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// A utility class for loading groups of assets from ALL addressable locations.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the asset to be loaded.</typeparam>
|
||||
public class LoadableAddressableAssets<T>(string key) where T : UnityEngine.Object
|
||||
{
|
||||
private readonly Action<LoadableAddressableAssets<T>>? _gcAction;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets reference to the loaded asset. Intended to be used for caching purposes.
|
||||
/// </summary>
|
||||
protected List<T>? LoadedAssets { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoadableAddressableAssets{T}"/> class, with added garbageCollector logic,
|
||||
/// to help with https://github.com/BepInEx/Il2CppInterop/issues/40.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the assets.</param>
|
||||
/// <param name="garbageCollection">A lambda that allows GCHandle.Alloc calls without garbage collection interferences.</param>
|
||||
public LoadableAddressableAssets(string key, Action<LoadableAddressableAssets<T>> garbageCollection) : this(key)
|
||||
{
|
||||
_gcAction = garbageCollection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the asset from addressables.
|
||||
/// </summary>
|
||||
/// <returns>The asset.</returns>
|
||||
/// <exception cref="Exception">The asset did not load properly.</exception>
|
||||
public ReadOnlyCollection<T> LoadAssets()
|
||||
{
|
||||
if (LoadedAssets != null)
|
||||
{
|
||||
return LoadedAssets.AsReadOnly();
|
||||
}
|
||||
|
||||
var locations = Addressables.LoadResourceLocationsAsync(key).WaitForCompletion();
|
||||
|
||||
if (!GC.TryStartNoGCRegion(4096, true)) Logger<MiraApiPlugin>.Error("Could not start NoGCRegion of size 4kb, there is the possibility of injected unmanaged classes being garbage collected as per BepInEx/Il2CppInterop/issues/40");
|
||||
|
||||
var assetsIList = Addressables.LoadAssetsAsync<T>(locations, null, false).WaitForCompletion();
|
||||
if (assetsIList == null)
|
||||
{
|
||||
GC.EndNoGCRegion();
|
||||
throw new InvalidOperationException($"INVALID ASSET/s: {key}");
|
||||
}
|
||||
|
||||
var assetsList = new Il2CppSystem.Collections.Generic.List<T>(assetsIList.Pointer);
|
||||
LoadedAssets = assetsList.ToArray().ToList();
|
||||
|
||||
_gcAction?.Invoke(this);
|
||||
GC.EndNoGCRegion();
|
||||
|
||||
return LoadedAssets.AsReadOnly();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
using UnityEngine;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities.Assets;
|
||||
|
||||
public static class MiraAssets
|
||||
{
|
||||
public static readonly AssetBundle MiraAssetBundle = AssetBundleManager.Load("mirabundle");
|
||||
public static readonly LoadableAsset<GameObject> ModifierDisplay = new LoadableBundleAsset<GameObject>("Modifiers", MiraAssetBundle);
|
||||
|
||||
public static readonly LoadableResourceAsset Empty = new("MiraAPI.Resources.Empty.png");
|
||||
|
||||
public static readonly LoadableResourceAsset NextButton = new("MiraAPI.Resources.NextButton.png");
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
namespace MiraAPI.Utilities.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// An implementation for creating loadable assets from preloaded assets.
|
||||
/// An implementation for creating loadable assets from existing assets.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="LoadableAsset{T}"/>
|
||||
/// <inheritdoc />
|
||||
public class PreloadedAsset<T>(T asset) : LoadableAsset<T> where T : UnityEngine.Object
|
||||
{
|
||||
private readonly T loadedAsset = asset ?? throw new ArgumentNullException(nameof(asset));
|
||||
private readonly T _loadedAsset = asset ?? throw new ArgumentNullException(nameof(asset));
|
||||
|
||||
/// <inheritdoc />
|
||||
public override T LoadAsset()
|
||||
{
|
||||
return loadedAsset;
|
||||
return _loadedAsset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
|
||||
internal sealed class ControllableComparer<T> : IComparer<T> where T : IComparable
|
||||
{
|
||||
private readonly T[] _forcedToBottom;
|
||||
private readonly T[] _forcedToTop;
|
||||
private readonly IComparer<T> _fallbackComparer;
|
||||
public ControllableComparer(T[] forcedToBottom, T[] forcedToTop, IComparer<T> fallbackComparer)
|
||||
{
|
||||
_forcedToBottom = forcedToBottom;
|
||||
_forcedToTop = forcedToTop;
|
||||
_fallbackComparer = fallbackComparer;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Compare(T? x, T? y)
|
||||
{
|
||||
if ((_forcedToBottom.Contains(x) && _forcedToBottom.Contains(y)) || (_forcedToTop.Contains(x) && _forcedToTop.Contains(y)))
|
||||
return _fallbackComparer.Compare(x, y);
|
||||
|
||||
if (_forcedToBottom.Contains(x))
|
||||
return 1;
|
||||
if (_forcedToBottom.Contains(y))
|
||||
return -1;
|
||||
|
||||
if (_forcedToTop.Contains(x))
|
||||
return -1;
|
||||
if (_forcedToTop.Contains(y))
|
||||
return 1;
|
||||
|
||||
return _fallbackComparer.Compare(x, y);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user