Refactor + untargeted meeting abilities

This commit is contained in:
wanderingpix committed 2026-08-22 13:32:14 +01:00
1 parent 4b7f17fa30
commit 7bbbd7597d
15 files changed
+600 -73

No files matched your search

@@ -0,0 +1,33 @@
using MiraAPI.Example.Modifiers;
using MiraAPI.Example.Roles;
using MiraAPI.Hud;
using MiraAPI.Keybinds;
using MiraAPI.MeetingAbilities;
using MiraAPI.Modifiers;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.MeetingAbilities;
public class EndMeetingButton : MeetingActionButton
{
public override string Name => "End Meeting";
public override float Cooldown => 15;
public override float InitialCooldown => 30;
public override int MaxUses => 3;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override bool Enabled(RoleBehaviour? role)
{
return PlayerControl.LocalPlayer != null && role is MayorRole;
}
protected override void OnClick()
{
MeetingHud.Instance.RpcClose();
}
}
@@ -8,7 +8,7 @@ namespace MiraAPI.Hud;
/// <see cref="CustomActionButton"/> that has multiple target objects.
/// </summary>
/// <typeparam name="T">The type of the target objects.</typeparam>
public abstract class MultiTargetButton<T> : CustomActionButton where T : MonoBehaviour
public abstract class MultiTargetMeetingButton<T> : CustomActionButton where T : MonoBehaviour
{
/// <summary>
/// Gets or sets the list of targets of the button.
@@ -96,7 +96,7 @@ public class MeetingAbilityBehaviour : MonoBehaviour
{
Renderer.material.SetFloat("_Desat", 0.0f);
CooldownText.text = string.Empty;
UsesText.text = _button.LimitedUses ? _button.UsesRemaining.ToString(CultureInfo.InvariantCulture) : string.Empty;
UsesText.text = _button.LimitedUses ? _button.UsesLeft.ToString(CultureInfo.InvariantCulture) : string.Empty;
}
else
{
@@ -0,0 +1,321 @@
using System;
using MiraAPI.Events;
using MiraAPI.Events.Mira;
using MiraAPI.Hud;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using Object = UnityEngine.Object;
namespace MiraAPI.MeetingAbilities;
/// <summary>
/// Class for making custom meeting action buttons.
/// </summary>
public abstract class MeetingActionButton
{
/// <summary>
/// Gets the name and text of the button.
/// </summary>
public abstract string Name { get; }
/// <summary>
/// Gets the initial cooldown duration in seconds.
/// </summary>
public virtual float InitialCooldown => Cooldown;
/// <summary>
/// Gets the button's cooldown duration in seconds.
/// </summary>
public abstract float Cooldown { get; }
/// <summary>
/// Gets the sprite of the button. Use <see cref="LoadableResourceAsset"/> to load a sprite from a resource path. Use <see cref="LoadableBundleAsset{T}"/> to load a sprite from an asset bundle.
/// </summary>
public abstract LoadableAsset<Sprite> Sprite { get; }
/// <summary>
/// Gets the format string for the cooldown timer.
/// </summary>
public virtual string CooldownTimerFormatString => "0";
/// <summary>
/// Gets a value indicating whether the button has limited uses.
/// </summary>
public bool LimitedUses => ZeroIsInfinite ? MaxUses > 0 : MaxUses >= 0;
/// <summary>
/// Gets the maximum number of uses the button has. If the button has infinite uses, set to 0 or -1 based on what value that ZeroIsInfinite is set to.
/// </summary>
public virtual int MaxUses => ZeroIsInfinite ? 0 : -1;
/// <summary>
/// Gets the value indicating uses mode.
/// </summary>
public virtual MeetingButtonUsesMode UsesMode => MeetingButtonUsesMode.PerGame;
/// <summary>
/// Gets the button's text outline <see cref="Color"/>.
/// </summary>
public virtual Color TextOutlineColor => Color.clear;
/// <summary>
/// Gets or sets a value indicating whether limited uses are determined via zero or a negative number of uses.
/// </summary>
public virtual bool ZeroIsInfinite { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether the timer is currently active.
/// </summary>
public bool TimerPaused { get; set; }
/// <summary>
/// Gets or sets the amount of uses left.
/// </summary>
public int UsesLeft { get; set; }
/// <summary>
/// Gets or sets the timer variable to measure cooldowns and effects.
/// </summary>
public float Timer { get; set; }
/// <summary>
/// Gets or sets the <see cref="ActionButton"/> object in game. This is created by Mira API automatically.
/// </summary>
public ActionButton? Button { get; set; }
/// <summary>
/// The method used to create the button.
/// </summary>
/// <param name="parent">The parent of the button.</param>
public virtual void CreateButton(Transform parent)
{
if (Button)
{
return;
}
Timer = AmongUsClient.Instance?.NetworkMode == NetworkModes.FreePlay ? 0 : InitialCooldown;
TimerPaused = false;
Button = Object.Instantiate(MeetingHud.Instance.MeetingAbilityButton, parent);
Button.name = Name + "Button";
Button.OverrideText(Name.ToUpperInvariant());
Button.graphic.sprite = Sprite.LoadAsset();
Button.SetUsesRemaining(UsesLeft);
if (MaxUses <= 0)
{
Button.SetInfiniteUses();
}
if (TextOutlineColor != Color.clear)
{
SetTextOutline(TextOutlineColor);
}
var pb = Button.GetComponent<PassiveButton>();
pb.OnClick = new Button.ButtonClickedEvent();
pb.OnClick.AddListener((UnityAction)(() =>
{
ClickHandler();
}));
Button.gameObject.SetActive(Enabled(PlayerControl.LocalPlayer.Data.Role));
}
/// <summary>
/// A utility function to change the outline <see cref="Color"/> of the button's text.
/// </summary>
/// <param name="color">The new <see cref="Color"/>.</param>
public virtual void SetTextOutline(Color color)
{
Button?.buttonLabelText.SetOutlineColor(color);
}
/// <summary>
/// A utility function to override the <see cref="UnityEngine.Sprite"/> of the button.
/// </summary>
/// <param name="sprite">The new <see cref="UnityEngine.Sprite"/> to override with.</param>
public virtual void OverrideSprite(Sprite sprite)
{
if (Button != null)
{
Button.graphic.sprite = sprite;
}
}
/// <summary>
/// A utility function to override the name of the button.
/// </summary>
/// <param name="name">The new name to override with.</param>
public virtual void OverrideName(string name)
{
Button?.OverrideText(name);
}
/// <summary>
/// Set the button's timer.
/// </summary>
/// <param name="time">The time you want to set to.</param>
public virtual void SetTimer(float time)
{
Timer = Mathf.Clamp(time, -1, float.MaxValue);
}
/// <summary>
/// Increase the button's timer.
/// </summary>
/// <param name="amount">The amount you want to increase by.</param>
public virtual void IncreaseTimer(float amount)
{
SetTimer(Timer + amount);
}
/// <summary>
/// Decrease the button's timer.
/// </summary>
/// <param name="amount">The amount you want to decrease by.</param>
public virtual void DecreaseTimer(float amount)
{
SetTimer(Timer - amount);
}
/// <summary>
/// Sets whether the timer is paused or not.
/// </summary>
/// <param name="val">Whether you want to pause/resume the timer.</param>
public virtual void SetTimerPaused(bool val)
{
TimerPaused = val;
}
/// <summary>
/// Set the amount of uses this button has left.
/// </summary>
/// <param name="amount">The amount you want to set to.</param>
public virtual void SetUses(int amount)
{
UsesLeft = Mathf.Clamp(amount, 0, int.MaxValue);
Button?.SetUsesRemaining(UsesLeft);
if (Button != null)
{
Button.usesRemainingSprite.color = UsesLeft == 0 ? Color.red : Color.white;
}
}
/// <summary>
/// Increase the amount of uses this button has left.
/// </summary>
/// <param name="amount">The amount you want to increase by. Default: 1.</param>
public virtual void IncreaseUses(int amount = 1)
{
SetUses(UsesLeft + amount);
}
/// <summary>
/// Decrease the amount of uses this button has left.
/// </summary>
/// <param name="amount">The amount you want to decrease by. Default: 1.</param>
public virtual void DecreaseUses(int amount = 1)
{
SetUses(UsesLeft - amount);
}
/// <summary>
/// A utility function that runs with the local player's <see cref="MeetingHud.Update"/> if the button is enabled.
/// </summary>
/// <param name="hud">the <see cref="MeetingHud"/> instance.</param>
protected virtual void FixedUpdate(MeetingHud hud)
{
}
/// <summary>
/// Callback method for the button click event.
/// </summary>
protected abstract void OnClick();
/// <summary>
/// This method determines if the button should be active or not.
/// <see langword="true"/> means the button is active, <see langword="false"/> means the button is disabled.
/// </summary>
/// <param name="role">The <see cref="RoleBehaviour"/> of the local player.</param>
/// <returns><see langword="true"/> if the button is enabled, <see langword="false"/> otherwise.</returns>
public abstract bool Enabled(RoleBehaviour? role);
/// <summary>
/// When the button is usable, this method is called to determine if the button can be clicked.
/// By default, it takes into account the timer, effect, and uses.
/// You can override it to change the behavior.
/// </summary>
/// <returns>A value that represents whether the button can be clicked or not.</returns>
public virtual bool CanClick()
{
return Timer <= 0 && CanUse();
}
/// <summary>
/// Whether the button should light up or not. This is also the base for CanClick.
/// You can override it to change the behaviour. Do not include timer in here, that is for CanClick.
/// </summary>
/// <returns>A value that represents whether the button should light up or not.</returns>
public virtual bool CanUse()
{
return !LimitedUses || UsesLeft > 0;
}
/// <summary>
/// This method handles the button click event. It is a wrapper for the <see cref="OnClick"/> method.
/// This method takes into account cooldowns, effects, and uses, before calling the <see cref="OnClick"/> method.
/// It can be overridden for custom behavior.
/// </summary>
public virtual void ClickHandler()
{
if (!CanClick())
{
return;
}
if (LimitedUses)
{
UsesLeft--;
Button?.SetUsesRemaining(UsesLeft);
}
Timer = Cooldown;
OnClick();
}
/// <summary>
/// This method is called on the <see cref="MeetingHud.Update"/> method. It is a wrapper for the <see cref="FixedUpdate"/> method.
/// By default, it handles the cooldown and effect timers, and sets the button to enabled or disabled.
/// It can be overridden for custom behavior.
/// </summary>
/// <param name="hud">The <see cref="MeetingHud"/> instance.</param>
public virtual void UpdateHandler(MeetingHud hud)
{
if (Timer >= 0 && !TimerPaused)
{
Timer -= Time.deltaTime;
}
if (Button)
{
if (CanUse())
{
Button!.SetEnabled();
}
else
{
Button!.SetDisabled();
}
Button.SetCooldownFormat(Timer, Cooldown, CooldownTimerFormatString);
Button.SetCoolDown(Timer, Cooldown);
}
FixedUpdate(hud);
}
}
@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities.Assets;
using MiraAPI.Utilities.Components;
using UnityEngine;
namespace MiraAPI.MeetingAbilities;
/// <summary>
/// Class for managing all registered meeting abilities.
/// </summary>
public static class MeetingButtonManager
{
/// <summary>
/// Gets a list if==of all registered <see cref="TargetedMeetingButton"/>.
/// </summary>
public static List<TargetedMeetingButton> TargetedButtons { get; internal set; } = new();
/// <summary>
/// Gets a list if==of all registered <see cref="MeetingActionButton"/>.
/// </summary>
public static List<MeetingActionButton> UntargetedButtons { get; internal set; } = new();
internal static bool RegisterTargetedMeetingButton(Type type, MiraPluginInfo info)
{
if (!typeof(TargetedMeetingButton).IsAssignableFrom(type))
{
return false;
}
var button = Activator.CreateInstance(type) as TargetedMeetingButton;
info.InternalTargetedMeetingButtons.Add(button);
TargetedButtons.Add(button);
return true;
}
internal static bool RegisterMeetingButton(Type type, MiraPluginInfo info)
{
if (!typeof(MeetingActionButton).IsAssignableFrom(type))
{
return false;
}
var button = Activator.CreateInstance(type) as MeetingActionButton;
info.InternalMeetingButtons.Add(button);
UntargetedButtons.Add(button);
return true;
}
private static CenteredGridArrange? actionButtonsParent = null!;
public static void OnGameStart()
{
foreach (var ability in TargetedButtons.Where(x => x.UsesMode == MeetingButtonUsesMode.PerGame))
{
ability.UsesLeft = ability.MaxUses;
}
foreach (var ability in UntargetedButtons.Where(x => x.UsesMode == MeetingButtonUsesMode.PerGame))
{
ability.UsesLeft = ability.MaxUses;
}
}
/// <summary>
/// Called on <see cref="MeetingHud.Start"/>. Handles button creation, and ability uses.
/// </summary>
/// <param name="meetingHud">The <see cref="MeetingHud"/> instance.</param>
public static void OnMeetingStart(MeetingHud meetingHud)
{
// Untargeted buttons parent.
actionButtonsParent = new GameObject("ActionsButtonParent").AddComponent<CenteredGridArrange>();
actionButtonsParent.transform.SetParent(meetingHud.MeetingAbilityButton.transform.parent, meetingHud.MeetingAbilityButton.transform.parent);
actionButtonsParent.CellSize = new Vector2(0.7f, 0.7f);
actionButtonsParent.transform.localPosition = new Vector3(0, -2.1f, -10);
meetingHud.MeetingAbilityButton.transform.SetParent(actionButtonsParent.transform);
// Untargeted buttons creation.
foreach (var ability in UntargetedButtons)
{
try
{
ability.CreateButton(actionButtonsParent.transform);
if (ability.UsesMode == MeetingButtonUsesMode.PerMeeting) ability.UsesLeft = ability.MaxUses;
ability.Timer = ability.InitialCooldown;
}
catch (Exception e)
{
Error($"Failed to create meeting button {ability.Name}: {e.Message}");
}
}
// Targeted buttons creation.
foreach (var ability in TargetedButtons)
{
try
{
foreach (var playerVoteArea in meetingHud.playerStates)
{
var btn = ability.CreateButton(playerVoteArea);
btn.transform.SetParent(playerVoteArea.Buttons.transform);
playerVoteArea.CancelButton.transform.SetAsFirstSibling();
}
if (ability.UsesMode == MeetingButtonUsesMode.PerMeeting) ability.UsesLeft = ability.MaxUses;
if (ability is MultiTargetMeetingButton multiAbility) multiAbility.Targets = new();
}
catch (Exception e)
{
Error($"Failed to create targeted meeting button {ability.Name}: {e.Message}");
}
}
}
}
@@ -30,7 +30,7 @@ public abstract class MultiTargetMeetingButton : TargetedMeetingButton
/// <inheritdoc />
public override void ClickHandler(MeetingAbilityBehaviour button, PlayerVoteArea playerVoteArea)
{
if (UsesRemaining <= 0 && LimitedUses) return;
if (UsesLeft <= 0 && LimitedUses) return;
if (Timer > 0) return;
var toggle = !Targets.Remove(playerVoteArea);
if (toggle) Targets.Add(playerVoteArea, button);
@@ -55,7 +55,7 @@ public abstract class MultiTargetMeetingButton : TargetedMeetingButton
button.Renderer.sprite = Sprite.LoadAsset();
}
Timer = Cooldown;
UsesRemaining -= 1;
UsesLeft -= 1;
OnFinish();
Targets = new();
}
@@ -24,7 +24,7 @@ public abstract class TargetedMeetingButton
/// <summary>
/// Gets or sets the amount of uses left for this button.
/// </summary>
public int UsesRemaining { get; set; }
public int UsesLeft { get; set; }
/// <summary>
/// Gets a value indicating whether the <see cref="TargetedMeetingButton"/> has limited uses.
@@ -65,7 +65,7 @@ public abstract class TargetedMeetingButton
/// <summary>
/// Gets the <see cref="MeetingButtonUsesMode"/> for this button.
/// </summary>
public virtual MeetingButtonUsesMode ButtonUsesMode { get; } = MeetingButtonUsesMode.PerGame;
public virtual MeetingButtonUsesMode UsesMode { get; } = MeetingButtonUsesMode.PerGame;
/// <summary>
/// Determines if the button is enabled.
@@ -135,10 +135,10 @@ public abstract class TargetedMeetingButton
/// <param name="playerVoteArea">The target <see cref="PlayerVoteArea"/>.</param>
public virtual void ClickHandler(MeetingAbilityBehaviour button, PlayerVoteArea playerVoteArea)
{
if (UsesRemaining <= 0 && LimitedUses) return;
if (UsesLeft <= 0 && LimitedUses) return;
if (Timer > 0) return;
Timer = Cooldown;
UsesRemaining--;
UsesLeft--;
playerVoteArea.Cancel();
OnClick(playerVoteArea);
}
@@ -1,33 +0,0 @@
using System;
using System.Collections.Generic;
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.MeetingAbilities;
/// <summary>
/// Class for managing all registered meeting abilities.
/// </summary>
public static class TargetedMeetingButtonManager
{
/// <summary>
/// Gets a list if==of all registered <see cref="TargetedMeetingButton"/>.
/// </summary>
public static List<TargetedMeetingButton> Buttons { get; internal set; } = new();
internal static bool RegisterMeetingAbility(Type type, MiraPluginInfo info)
{
if (!typeof(TargetedMeetingButton).IsAssignableFrom(type))
{
return false;
}
var button = Activator.CreateInstance(type) as TargetedMeetingButton;
info.InternalMeetingAbilities.Add(button);
Buttons.Add(button);
return true;
}
}
@@ -15,5 +15,5 @@ public static class TargetedMeetingButtonSingleton<T> where T : TargetedMeetingB
/// Gets the instance of the <typeparamref name="T"/> button.
/// </summary>
[SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "This is a utility class to get the instance of a targeted meeting button.")]
public static T Instance => _instance ??= TargetedMeetingButtonManager.Buttons.OfType<T>().Single();
public static T Instance => _instance ??= MeetingButtonManager.TargetedButtons.OfType<T>().Single();
}
@@ -20,10 +20,7 @@ public static class FreeplayRoundStartPatch
{
if (!__result)
{
foreach (var ability in TargetedMeetingButtonManager.Buttons.Where(x => x.ButtonUsesMode == MeetingButtonUsesMode.PerGame))
{
ability.UsesRemaining = ability.MaxUses;
}
MeetingButtonManager.OnGameStart();
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
}
}
+1 -5
View File
@@ -125,16 +125,12 @@ public static class IntroCutscenePatches
Info("IntroCutscene ended");
MeetingButtonManager.OnGameStart();
MiraEventManager.InvokeEvent(new IntroEndEvent(introCutscene));
var @event = new BeforeRoundStartEvent(true);
MiraEventManager.InvokeEvent(@event);
foreach (var ability in TargetedMeetingButtonManager.Buttons.Where(x => x.ButtonUsesMode == MeetingButtonUsesMode.PerGame))
{
ability.UsesRemaining = ability.MaxUses;
}
if (@event.IsCancelled) return;
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
}
+8 -19
View File
@@ -10,6 +10,7 @@ using MiraAPI.Events.Vanilla.Meeting.Voting;
using MiraAPI.MeetingAbilities;
using MiraAPI.Modifiers;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Components;
using MiraAPI.Voting;
using UnityEngine;
@@ -37,19 +38,6 @@ internal static class MeetingHudPatches
return true;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.Start))]
public static void VoteAreaStartPostfix(PlayerVoteArea __instance)
{
foreach (var targetedMeetingAbility in TargetedMeetingButtonManager.Buttons)
{
var btn = targetedMeetingAbility.CreateButton(__instance);
btn.transform.SetParent(__instance.Buttons.transform);
btn.gameObject.SetActive(targetedMeetingAbility.Enabled(PlayerControl.LocalPlayer.Data.Role) && targetedMeetingAbility.IsTargetValid(__instance));
}
__instance.CancelButton.transform.SetAsFirstSibling();
}
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.Select))]
public static bool VoteAreaSelectPatch(PlayerVoteArea __instance)
@@ -89,6 +77,7 @@ internal static class MeetingHudPatches
[HarmonyPatch(nameof(MeetingHud.Start))]
public static void MeetingHudStartPatch(MeetingHud __instance)
{
MeetingButtonManager.OnMeetingStart(__instance);
foreach (var plr in PlayerControl.AllPlayerControls)
{
var voteData = plr.GetVoteData();
@@ -106,11 +95,6 @@ internal static class MeetingHudPatches
}
}
foreach (var ability in TargetedMeetingButtonManager.Buttons)
{
if (ability.ButtonUsesMode == MeetingButtonUsesMode.PerMeeting) ability.UsesRemaining = ability.MaxUses;
if (ability is MultiTargetMeetingButton multiAbility) multiAbility.Targets = new();
}
var @event = new StartMeetingEvent(__instance);
MiraEventManager.InvokeEvent(@event);
}
@@ -134,11 +118,16 @@ internal static class MeetingHudPatches
[HarmonyPatch(nameof(MeetingHud.Update))]
public static void ForceSkipPatch(MeetingHud __instance)
{
foreach (var targetedMeetingAbility in TargetedMeetingButtonManager.Buttons.Where(x => x.Enabled(PlayerControl.LocalPlayer.Data.Role)))
foreach (var targetedMeetingAbility in MeetingButtonManager.TargetedButtons.Where(x => x.Enabled(PlayerControl.LocalPlayer.Data.Role)))
{
targetedMeetingAbility.UpdateHandler();
}
foreach (var meetingAbility in MeetingButtonManager.UntargetedButtons.Where(x => x.Enabled(PlayerControl.LocalPlayer.Data.Role)))
{
meetingAbility.UpdateHandler(__instance);
}
if (__instance.state is not (MeetingHud.MeetingStates.NotVoted or MeetingHud.MeetingStates.Voted))
{
return;
+15 -1
View File
@@ -50,6 +50,16 @@ public class MiraPluginInfo
/// </summary>
public IReadOnlyCollection<CustomActionButton> Buttons { get; private set; } = null!;
/// <summary>
/// Gets a <see cref="IReadOnlyCollection{T}"/> of this <see cref="IMiraPlugin"/>'s <see cref="TargetedMeetingButton"/>.
/// </summary>
public IReadOnlyCollection<TargetedMeetingButton> TargetedMeetingButtons { get; private set; } = null!;
/// <summary>
/// Gets a <see cref="IReadOnlyCollection{T}"/> of this <see cref="IMiraPlugin"/>'s <see cref="MeetingActionButton"/>.
/// </summary>
public IReadOnlyCollection<MeetingActionButton> MeetingButtons { get; private set; } = null!;
/// <summary>
/// Gets a <see cref="ReadOnlyDictionary{TKey, TValue}"/> of this <see cref="IMiraPlugin"/>'s <see cref="OptionPreset"/>s.
/// </summary>
@@ -63,6 +73,8 @@ public class MiraPluginInfo
Options = [..InternalOptions];
Roles = new ReadOnlyDictionary<ushort, RoleBehaviour>(InternalRoles);
Buttons = [..InternalButtons];
MeetingButtons = [..InternalMeetingButtons];
TargetedMeetingButtons = [..InternalTargetedMeetingButtons];
}
internal List<OptionPreset> InternalPresets { get; } = [];
@@ -79,7 +91,9 @@ public class MiraPluginInfo
internal List<CustomActionButton> InternalButtons { get; } = [];
internal List<TargetedMeetingButton?> InternalMeetingAbilities { get; } = [];
internal List<TargetedMeetingButton> InternalTargetedMeetingButtons { get; } = [];
internal List<MeetingActionButton> InternalMeetingButtons { get; } = [];
/// <summary>
/// Gets the plugin's ID, as defined in the plugin's BepInEx metadata.
+19 -2
View File
@@ -118,12 +118,17 @@ public sealed class MiraPluginManager
{
continue;
}
if (RegisterMeetingAbility(type, info))
{
continue;
}
if (RegisterTargetedMeetingAbility(type, info))
{
continue;
}
RegisterColorClasses(type);
RegisterKeybinds(type, plugin);
}
@@ -454,7 +459,7 @@ public sealed class MiraPluginManager
{
try
{
return TargetedMeetingButtonManager.RegisterMeetingAbility(type, info);
return MeetingButtonManager.RegisterMeetingButton(type, info);
}
catch (Exception e)
{
@@ -462,4 +467,16 @@ public sealed class MiraPluginManager
return false;
}
}
private static bool RegisterTargetedMeetingAbility(Type type, MiraPluginInfo info)
{
try
{
return MeetingButtonManager.RegisterTargetedMeetingButton(type, info);
}
catch (Exception e)
{
Error($"Failed to register targeted meeting ability {type.Name}: {e}");
return false;
}
}
}
@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Reactor.Utilities.Attributes;
using UnityEngine;
namespace MiraAPI.Utilities.Components;
[RegisterInIl2Cpp]
public class CenteredGridArrange(IntPtr ptr) : MonoBehaviour(ptr)
{
public Vector2 CellSize;
public int MaxColumns = 3;
private List<Transform> cells;
private static List<Transform> currentChildren = new List<Transform>();
private void Start()
{
cells = new List<Transform>();
GetChildsActive();
CheckCurrentChildren();
}
private void FixedUpdate() => CheckCurrentChildren();
private void CheckCurrentChildren()
{
GetChildsActive();
if (cells.SequenceEqual(currentChildren))
return;
cells.Clear();
foreach (Transform currentChild in currentChildren)
cells.Add(currentChild);
ArrangeChilds();
}
private void GetChildsActive()
{
currentChildren.Clear();
foreach (var obj in transform)
{
var child = obj.TryCast<Transform>();
if (child == null) continue;
if (child.gameObject.activeSelf)
currentChildren.Add(child);
}
}
private void ArrangeChilds()
{
if (cells.Count == 0)
return;
int totalRows = Mathf.CeilToInt((float)cells.Count / MaxColumns);
float totalHeight = (totalRows - 1) * CellSize.y;
float startY = transform.position.y + totalHeight * 0.5f;
for (int index = 0; index < cells.Count; ++index)
{
int row = index / MaxColumns;
int rowStartIndex = row * MaxColumns;
int itemsInRow = Mathf.Min(MaxColumns, cells.Count - rowStartIndex);
int col = index - rowStartIndex;
float rowWidth = (itemsInRow - 1) * CellSize.x;
float startX = transform.position.x - rowWidth * 0.5f;
float x = startX + col * CellSize.x;
float y = startY - row * CellSize.y;
Transform cell = cells[index];
cell.position = new Vector3(x, y, cell.position.z);
}
}
}