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");