diff --git a/MiraAPI.Example/ExampleAssets.cs b/MiraAPI.Example/ExampleAssets.cs index 732d9f7..903afd8 100644 --- a/MiraAPI.Example/ExampleAssets.cs +++ b/MiraAPI.Example/ExampleAssets.cs @@ -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"); diff --git a/MiraAPI.Example/MiraAPI.Example.csproj b/MiraAPI.Example/MiraAPI.Example.csproj index ff27bcc..d0b23ac 100644 --- a/MiraAPI.Example/MiraAPI.Example.csproj +++ b/MiraAPI.Example/MiraAPI.Example.csproj @@ -9,11 +9,9 @@ - - - - - + + + diff --git a/MiraAPI.Example/Modifiers/CaptainModifier.cs b/MiraAPI.Example/Modifiers/CaptainModifier.cs index ca8a570..0a889d1 100644 --- a/MiraAPI.Example/Modifiers/CaptainModifier.cs +++ b/MiraAPI.Example/Modifiers/CaptainModifier.cs @@ -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? 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() { diff --git a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs index 9f13b18..e1ad218 100644 --- a/MiraAPI.Example/Modifiers/HighPriorityModifier.cs +++ b/MiraAPI.Example/Modifiers/HighPriorityModifier.cs @@ -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; diff --git a/MiraAPI.Example/Resources/CallMeeting.png b/MiraAPI.Example/Resources/CallMeeting.png new file mode 100644 index 0000000..e96524a Binary files /dev/null and b/MiraAPI.Example/Resources/CallMeeting.png differ diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs index 6578ca9..435bba5 100644 --- a/MiraAPI/Modifiers/BaseModifier.cs +++ b/MiraAPI/Modifiers/BaseModifier.cs @@ -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; } /// - /// Gets a value indicating whether the modifier is hidden on the UI. + /// Gets the modifier icon. Useless if HideOnUi is true. /// - public virtual bool HideOnUi => false; + public virtual LoadableAsset? ModifierIcon => null; + + /// + /// Gets a value indicating whether the modifier is hidden on the UI. Will be hidden either way if no description is provided. + /// + public virtual bool HideOnUi => GetDescription() == string.Empty; /// /// 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; /// - /// Gets the HUD information for this modifier. Defaults to the modifier name. Does nothing if is true. + /// Gets the HUD description for this modifier. Does nothing if is true. Required to be visible on UI. /// - /// The information string for the HUD. - public virtual string GetHudString() => ModifierName; + /// The description string for the HUD. + public virtual string GetDescription() => string.Empty; /// /// Called when the modifier is activated. diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 988eac6..1a83838 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -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 ActiveModifiers { get; private set; } = ImmutableList.Empty; + private ModifierDisplayComponent? ModifierDisplay { get; set; } + [HideFromIl2Cpp] private List Modifiers { get; set; } = []; private PlayerControl _player = null!; - private TextMeshPro _modifierText = null!; - private readonly List _toRemove = []; private readonly List _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("Modifiers:\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() diff --git a/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs b/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs new file mode 100644 index 0000000..e58838e --- /dev/null +++ b/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs @@ -0,0 +1,121 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +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; + +/// +/// The code used to display Mira modifiers. +/// +[RegisterInIl2Cpp] +public class ModifierDisplayComponent(nint ptr) : MonoBehaviour(ptr) +{ + /// + /// Gets the instance of the Modifier Display. + /// + public static ModifierDisplayComponent? Instance { get; private set; } + + private GameObject _children = null!; + private GameObject _modTemplate = null!; + private PassiveButton _toggleButton = null!; + private TextMeshPro _toggleBtnText = null!; + + private readonly Dictionary _modifiers = []; + + /// + /// Gets a read only dictionary of the created modifier UI components. + /// + [HideFromIl2Cpp] + public ReadOnlyDictionary Modifiers => new(_modifiers); + + /// + /// Gets a value indicating whether the modifier display hud is currently open. + /// + public bool IsOpen { get; private set; } + + private void Awake() + { + Instance = this; + + _children = transform.GetChild(0).gameObject; + _modTemplate = transform.GetChild(2).gameObject; + + _toggleButton = transform.GetChild(1).GetComponent(); + _toggleButton.OnClick = new Button.ButtonClickedEvent(); + _toggleButton.OnClick.AddListener((UnityAction)(() => + { + IsOpen = !IsOpen; + _children.gameObject.SetActive(!_children.gameObject.activeSelf); + })); + + _toggleButton.ClickSound = HudManager.Instance.GameMenu.StreamerModeButton.GetComponent().ClickSound; + _toggleButton.HoverSound = HudManager.Instance.GameMenu.StreamerModeButton.GetComponent().HoverSound; + _toggleButton.inactiveSprites = _toggleButton.transform.GetChild(1).gameObject; + _toggleBtnText = _toggleButton.transform.GetChild(0).GetComponent(); + _toggleBtnText.font = HudManager.Instance.TaskPanel.taskText.font; + _toggleBtnText.fontMaterial = HudManager.Instance.TaskPanel.taskText.fontMaterial; + + IsOpen = false; + _children.gameObject.SetActive(false); + } + + [HideFromIl2Cpp] + internal static ModifierDisplayComponent CreateDisplay() + { + var gameObject = Instantiate(MiraAssets.ModifierDisplay.LoadAsset(), HudManager.Instance.transform); + var minigame = gameObject.AddComponent(); + return minigame; + } + + [HideFromIl2Cpp] + private ModifierUiComponent CreateForModifier(BaseModifier modifier) + { + var newMod = Instantiate(_modTemplate, _children.transform); + newMod.transform.name = modifier.ModifierName; + var comp = newMod.AddComponent(); + comp.Modifier = modifier; + newMod.gameObject.SetActive(true); + return comp; + } + + [HideFromIl2Cpp] + internal void UpdateModifiersList(List modifiers) + { + var filteredModifiers = modifiers.Where(x => !x.HideOnUi && x.GetDescription() != string.Empty).ToList(); + + foreach (var mod in _modifiers.ToArray()) + { + if (filteredModifiers.Contains(mod.Key)) + { + filteredModifiers.Remove(mod.Key); + continue; + } + + mod.Value.gameObject.Destroy(); + _modifiers.Remove(mod.Key); + } + + _toggleBtnText.text = $"Modifiers ({modifiers.Count})"; + + if (modifiers.Count == 0) + { + _toggleButton.gameObject.SetActive(false); + return; + } + + _toggleButton.gameObject.SetActive(true); + + foreach (var mod in filteredModifiers) + { + _modifiers.Add(mod, CreateForModifier(mod)); + } + } +} diff --git a/MiraAPI/Modifiers/ModifierDisplay/ModifierUiComponent.cs b/MiraAPI/Modifiers/ModifierDisplay/ModifierUiComponent.cs new file mode 100644 index 0000000..cd8ca79 --- /dev/null +++ b/MiraAPI/Modifiers/ModifierDisplay/ModifierUiComponent.cs @@ -0,0 +1,65 @@ +using Il2CppInterop.Runtime.Attributes; +using Reactor.Utilities.Attributes; +using TMPro; +using UnityEngine; + +namespace MiraAPI.Modifiers.ModifierDisplay; + +/// +/// The code placed on every Modifier HUD object. Used to handle updating. +/// +[RegisterInIl2Cpp] +public class ModifierUiComponent(nint ptr) : MonoBehaviour(ptr) +{ + /// + /// Gets the modifier which this component is for. + /// + [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(); + desc = modBg.transform.GetChild(0).GetComponent(); + icon = modBg.transform.GetChild(1).GetComponent(); + var nameBg = transform.GetChild(1); + nameText = nameBg.GetChild(0).GetComponent(); + descRect = desc.gameObject.GetComponent(); + + 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; + + 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); + } + } +} diff --git a/MiraAPI/Modifiers/Types/TimedModifier.cs b/MiraAPI/Modifiers/Types/TimedModifier.cs index ebb0457..d1c8017 100644 --- a/MiraAPI/Modifiers/Types/TimedModifier.cs +++ b/MiraAPI/Modifiers/Types/TimedModifier.cs @@ -45,12 +45,12 @@ public abstract class TimedModifier : BaseModifier public override bool HideOnUi => !TimerActive; /// - /// Gets the HUD information for Timed Modifier, including the time remaining. + /// Gets the HUD description for Timed Modifier, including the time remaining. /// - /// A string with the hud information + timer. - public override string GetHudString() + /// A string with the time remaining. + public override string GetDescription() { - return base.GetHudString() + $" ({Math.Round(Duration - TimeRemaining, 0)}s/{Duration}s)"; + return $"Remaining Time: {Math.Round(Duration - TimeRemaining, 0)}s/{Duration}s"; } /// diff --git a/MiraAPI/Resources/mirabundle-win-x86.bundle b/MiraAPI/Resources/mirabundle-win-x86.bundle new file mode 100644 index 0000000..ac9590f Binary files /dev/null and b/MiraAPI/Resources/mirabundle-win-x86.bundle differ diff --git a/MiraAPI/Utilities/Assets/MiraAssets.cs b/MiraAPI/Utilities/Assets/MiraAssets.cs index d2e855f..283d57c 100644 --- a/MiraAPI/Utilities/Assets/MiraAssets.cs +++ b/MiraAPI/Utilities/Assets/MiraAssets.cs @@ -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 ModifierDisplay = new LoadableBundleAsset("Modifiers", MiraAssetBundle); + public static readonly LoadableResourceAsset Empty = new("MiraAPI.Resources.Empty.png"); public static readonly LoadableResourceAsset NextButton = new("MiraAPI.Resources.NextButton.png");